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

saitoha / libsixel / 19172415645

07 Nov 2025 03:06PM UTC coverage: 46.739% (-0.1%) from 46.846%
19172415645

push

github

saitoha
feat: add -F,--final-merge option, implement an over-splitting & ward merging strategy

8060 of 25601 branches covered (31.48%)

169 of 311 new or added lines in 3 files covered. (54.34%)

45 existing lines in 1 file now uncovered.

11453 of 24504 relevant lines covered (46.74%)

2383390.89 hits per line

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

50.36
/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
};
468

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

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

482

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

492
    len = strlen(s);
57✔
493

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

501

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

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

650
    status = SIXEL_OK;
24✔
651

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

655
    return status;
45✔
656
}
657

658

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

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

669
    return result;
5,829✔
670
}
671

672

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

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

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

696
    return result;
73✔
697
}
698

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

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

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

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

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

767
    return written;
×
768
}
769

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

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

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

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

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

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

821

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

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

842
    status = SIXEL_OK;
12✔
843

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

848

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

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

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

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

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

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

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

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

908
    status = sixel_output_convert_colorspace(output,
×
909
                                             dither->palette,
910
                                             ctx->size);
911
    if (SIXEL_FAILED(status)) {
×
912
        goto end;
×
913
    }
914
    ctx->converted = 1;
×
915

916
end:
917
    output->pixelformat = pixelformat;
×
918
    output->source_colorspace = frame_colorspace;
×
919

920
    return status;
×
921
}
183✔
922

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

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

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

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

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

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

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

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

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

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

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

1106
    return status;
3✔
1107
}
1✔
1108

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

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

1124
    status = SIXEL_OK;
27✔
1125

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

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

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

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

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

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

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

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

1179

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

1188

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

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

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

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

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

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

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

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

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

1289
        break;
18✔
1290
    }
7✔
1291

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

1296

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

1300

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

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

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

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

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

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

1333
    return 1;
×
1334
}
21✔
1335

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

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

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

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

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

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

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

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

1422
    return spec;
×
1423
}
7✔
1424

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

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

1442
    return SIXEL_PALETTE_FORMAT_NONE;
21✔
1443
}
7✔
1444

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

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

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

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

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

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

1489
    return 1;
21✔
1490
}
7✔
1491

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

1504

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

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

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

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

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

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

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

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

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

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

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

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

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

1620

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

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

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

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

1647

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

1656

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

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

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

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

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

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

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

1691
    return SIXEL_PALETTE_FORMAT_NONE;
×
1692
}
1693

1694

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

1704

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

1717

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

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

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

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

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

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

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

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

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

1807

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2004

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

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

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

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

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

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

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

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

2109

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2296

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

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

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

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

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

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

2350
    return SIXEL_OK;
×
2351
}
2352

2353

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

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

2378

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

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

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

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

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

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

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

2440

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

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

2475

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2761
    *dither = callback_context.dither;
21✔
2762

2763
    return status;
21✔
2764
}
7✔
2765

2766

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

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

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

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

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

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

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

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

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

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

2912
    status = SIXEL_OK;
242✔
2913

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

2929

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

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

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

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

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

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

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

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

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

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

2998

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

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

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

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

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

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

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

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

3056

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

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

3075

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3260
    return status;
436✔
3261
}
3262

3263

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

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

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

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

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

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

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

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

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

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

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

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

3538
    return status;
93✔
3539
}
3540

3541

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

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

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

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

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

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

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

3638
    status = SIXEL_OK;
×
3639

3640
end:
3641
    return status;
×
3642
}
3643

3644

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

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

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

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

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

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

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

3746
    status = SIXEL_OK;
×
3747

3748
end:
3749
    return status;
×
3750
}
3751

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4076

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

4085
    return status;
538✔
4086
}
4087

4088

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

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

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

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

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

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

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

4251
    goto end;
547✔
4252

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

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

4266

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

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

4279
    return encoder;
×
4280
}
4281

4282

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

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

4325

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

4334

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

4345

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

4355
    encoder->cancel_flag = cancel_flag;
442✔
4356

4357
    return status;
442✔
4358
}
4359

4360

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

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

4515

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

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

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

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

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

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

4553
    return matched;
25✔
4554
}
11✔
4555

4556

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

4583
    return argument;
3✔
4584
}
3✔
4585

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

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

4608
    length = 0u;
4609

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

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

4628

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

4643

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

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

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

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

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

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

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

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

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

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

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

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

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

5810
    status = SIXEL_OK;
628✔
5811

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

5818
    return status;
715✔
5819
}
5820

5821

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

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

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

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

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

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

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

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

5912
    return SIXEL_OK;
×
5913
}
5914

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

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

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

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

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

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

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

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

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

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

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

6017
    return template_path;
11✔
6018
}
5✔
6019

6020

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

6030

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

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

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

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

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

6058

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

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

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

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

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

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

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

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

6130
    return status;
2✔
6131
}
6132

6133

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

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

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

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

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

6172
    return SIXEL_OK;
1✔
6173
}
1✔
6174

6175

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

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

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

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

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

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

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

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

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

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

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

6266
    return SIXEL_OK;
1✔
6267
}
1✔
6268

6269

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

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

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

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

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

6294
    status = sixel_decoder_decode(decoder);
9✔
6295

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

6299
    return status;
9✔
6300
}
6301

6302

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

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

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

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

6519
    }
1✔
6520

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

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

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

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

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

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

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

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

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

6624
reload:
290✔
6625

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

7092
    sixel_encoder_unref(encoder);
436✔
7093

7094
    return status;
436✔
7095
}
7096

7097

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

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

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

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

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

7134
    status = SIXEL_OK;
×
7135

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

7147

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

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

7168
    return SIXEL_OK;
3✔
7169
}
1✔
7170

7171

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

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

7194
    return SIXEL_OK;
3✔
7195
}
1✔
7196

7197

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

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

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

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

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

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

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

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

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

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

7297

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

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

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

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

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

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

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

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

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

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

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

7475
    return status;
×
7476
}
141✔
7477

7478

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

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

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

7502
    return SIXEL_OK;
3✔
7503
}
1✔
7504

7505

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

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

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

7533

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

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

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

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

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

7584
    height = sixel_frame_get_height(frame);
×
7585

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

7595
    nret = EXIT_SUCCESS;
×
7596

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

7603

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

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

7615
    nret = EXIT_SUCCESS;
×
7616

7617
error:
7618
    return nret;
×
7619
}
7620

7621

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

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

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

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

7655
    nret = EXIT_SUCCESS;
×
7656

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

7662

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

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

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

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

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

7690

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

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

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

7713
    nret = EXIT_SUCCESS;
×
7714

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

7720

7721
/* emacs Local Variables:      */
7722
/* emacs mode: c               */
7723
/* emacs tab-width: 4          */
7724
/* emacs indent-tabs-mode: nil */
7725
/* emacs c-basic-offset: 4     */
7726
/* emacs End:                  */
7727
/* vim: set expandtab ts=4 : */
7728
/* 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

© 2025 Coveralls, Inc