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

saitoha / libsixel / 20030921959

08 Dec 2025 02:08PM UTC coverage: 42.362% (-8.5%) from 50.859%
20030921959

push

github

saitoha
lookup: add VPTE lookup implementation

10766 of 39717 branches covered (27.11%)

65 of 1126 new or added lines in 6 files covered. (5.77%)

1504 existing lines in 41 files now uncovered.

14781 of 34892 relevant lines covered (42.36%)

2813440.39 hits per line

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

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

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

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

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

110
#include <sixel.h>
111
#include "loader.h"
112
#include "assessment.h"
113
#include "timer.h"
114
#include "tty.h"
115
#include "encoder.h"
116
#include "output.h"
117
#include "logger.h"
118
#include "options.h"
119
#include "dither.h"
120
#include "rgblookup.h"
121
#include "clipboard.h"
122
#include "compat_stub.h"
123
#include "sleep.h"
124
#include "threading.h"
125

126
#define SIXEL_ENCODER_PRECISION_ENVVAR "SIXEL_FLOAT32_DITHER"
127

128
typedef enum sixel_encoder_precision_mode {
129
    SIXEL_ENCODER_PRECISION_MODE_AUTO = 0,
130
    SIXEL_ENCODER_PRECISION_MODE_8BIT,
131
    SIXEL_ENCODER_PRECISION_MODE_FLOAT32
132
} sixel_encoder_precision_mode_t;
133

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

152
#if defined(_WIN32)
153

154
# include <windows.h>
155
# if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
156
#  include <io.h>
157
# endif
158
# if defined(_MSC_VER)
159
#   include <time.h>
160
# endif
161

162
# if defined(CLOCKS_PER_SEC)
163
#  undef CLOCKS_PER_SEC
164
# endif
165
# define CLOCKS_PER_SEC 1000
166

167
# if !defined(HAVE_CLOCK)
168
# define HAVE_CLOCK_WIN 1
169
static sixel_clock_t
170
clock_win(void)
171
{
172
    FILETIME ct, et, kt, ut;
173
    ULARGE_INTEGER u, k;
174

175
    if (! GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) {
176
        return (sixel_clock_t)(-1);
177
    }
178
    u.LowPart = ut.dwLowDateTime; u.HighPart = ut.dwHighDateTime;
179
    k.LowPart = kt.dwLowDateTime; k.HighPart = kt.dwHighDateTime;
180
    /* 100ns -> ms */
181
    return (sixel_clock_t)((u.QuadPart + k.QuadPart) / 10000ULL);
182
}
183
# endif  /* HAVE_CLOCK */
184

185
#endif /* _WIN32 */
186

187

188
static sixel_option_choice_t const g_option_choices_builtin_palette[] = {
189
    { "xterm16", SIXEL_BUILTIN_XTERM16 },
190
    { "xterm256", SIXEL_BUILTIN_XTERM256 },
191
    { "vt340mono", SIXEL_BUILTIN_VT340_MONO },
192
    { "vt340color", SIXEL_BUILTIN_VT340_COLOR },
193
    { "gray1", SIXEL_BUILTIN_G1 },
194
    { "gray2", SIXEL_BUILTIN_G2 },
195
    { "gray4", SIXEL_BUILTIN_G4 },
196
    { "gray8", SIXEL_BUILTIN_G8 }
197
};
198

199
static sixel_option_choice_t const g_option_choices_diffusion[] = {
200
    { "auto", SIXEL_DIFFUSE_AUTO },
201
    { "none", SIXEL_DIFFUSE_NONE },
202
    { "fs", SIXEL_DIFFUSE_FS },
203
    { "atkinson", SIXEL_DIFFUSE_ATKINSON },
204
    { "jajuni", SIXEL_DIFFUSE_JAJUNI },
205
    { "stucki", SIXEL_DIFFUSE_STUCKI },
206
    { "burkes", SIXEL_DIFFUSE_BURKES },
207
    { "sierra1", SIXEL_DIFFUSE_SIERRA1 },
208
    { "sierra2", SIXEL_DIFFUSE_SIERRA2 },
209
    { "sierra3", SIXEL_DIFFUSE_SIERRA3 },
210
    { "a_dither", SIXEL_DIFFUSE_A_DITHER },
211
    { "x_dither", SIXEL_DIFFUSE_X_DITHER },
212
    { "lso2", SIXEL_DIFFUSE_LSO2 },
213
};
214

215
static sixel_option_choice_t const g_option_choices_diffusion_scan[] = {
216
    { "auto", SIXEL_SCAN_AUTO },
217
    { "serpentine", SIXEL_SCAN_SERPENTINE },
218
    { "raster", SIXEL_SCAN_RASTER }
219
};
220

221
static sixel_option_choice_t const g_option_choices_diffusion_carry[] = {
222
    { "auto", SIXEL_CARRY_AUTO },
223
    { "direct", SIXEL_CARRY_DISABLE },
224
    { "carry", SIXEL_CARRY_ENABLE }
225
};
226

227
static sixel_option_choice_t const g_option_choices_find_largest[] = {
228
    { "auto", SIXEL_LARGE_AUTO },
229
    { "norm", SIXEL_LARGE_NORM },
230
    { "lum", SIXEL_LARGE_LUM },
231
    { "pca", SIXEL_LARGE_PCA }
232
};
233

234
static sixel_option_choice_t const g_option_choices_select_color[] = {
235
    { "auto", SIXEL_REP_AUTO },
236
    { "center", SIXEL_REP_CENTER_BOX },
237
    { "average", SIXEL_REP_AVERAGE_COLORS },
238
    { "histogram", SIXEL_REP_AVERAGE_PIXELS },
239
    { "histgram", SIXEL_REP_AVERAGE_PIXELS }
240
};
241

242
static sixel_option_choice_t const g_option_choices_quantize_model[] = {
243
    { "auto", SIXEL_QUANTIZE_MODEL_AUTO },
244
    { "heckbert", SIXEL_QUANTIZE_MODEL_MEDIANCUT },
245
    { "kmeans", SIXEL_QUANTIZE_MODEL_KMEANS }
246
};
247

248
static sixel_option_choice_t const g_option_choices_final_merge[] = {
249
    { "auto", SIXEL_FINAL_MERGE_AUTO },
250
    { "none", SIXEL_FINAL_MERGE_NONE },
251
    { "ward", SIXEL_FINAL_MERGE_WARD }
252
};
253

254
static sixel_option_choice_t const g_option_choices_resampling[] = {
255
    { "nearest", SIXEL_RES_NEAREST },
256
    { "gaussian", SIXEL_RES_GAUSSIAN },
257
    { "hanning", SIXEL_RES_HANNING },
258
    { "hamming", SIXEL_RES_HAMMING },
259
    { "bilinear", SIXEL_RES_BILINEAR },
260
    { "welsh", SIXEL_RES_WELSH },
261
    { "bicubic", SIXEL_RES_BICUBIC },
262
    { "lanczos2", SIXEL_RES_LANCZOS2 },
263
    { "lanczos3", SIXEL_RES_LANCZOS3 },
264
    { "lanczos4", SIXEL_RES_LANCZOS4 }
265
};
266

267
static sixel_option_choice_t const g_option_choices_quality[] = {
268
    { "auto", SIXEL_QUALITY_AUTO },
269
    { "high", SIXEL_QUALITY_HIGH },
270
    { "low", SIXEL_QUALITY_LOW },
271
    { "full", SIXEL_QUALITY_FULL }
272
};
273

274
static sixel_option_choice_t const g_option_choices_loopmode[] = {
275
    { "auto", SIXEL_LOOP_AUTO },
276
    { "force", SIXEL_LOOP_FORCE },
277
    { "disable", SIXEL_LOOP_DISABLE }
278
};
279

280
static sixel_option_choice_t const g_option_choices_palette_type[] = {
281
    { "auto", SIXEL_PALETTETYPE_AUTO },
282
    { "hls", SIXEL_PALETTETYPE_HLS },
283
    { "rgb", SIXEL_PALETTETYPE_RGB }
284
};
285

286
static sixel_option_choice_t const g_option_choices_encode_policy[] = {
287
    { "auto", SIXEL_ENCODEPOLICY_AUTO },
288
    { "fast", SIXEL_ENCODEPOLICY_FAST },
289
    { "size", SIXEL_ENCODEPOLICY_SIZE }
290
};
291

292
static sixel_option_choice_t const g_option_choices_lut_policy[] = {
293
    { "auto", SIXEL_LUT_POLICY_AUTO },
294
    { "5bit", SIXEL_LUT_POLICY_5BIT },
295
    { "6bit", SIXEL_LUT_POLICY_6BIT },
296
    { "none", SIXEL_LUT_POLICY_NONE },
297
    { "certlut", SIXEL_LUT_POLICY_CERTLUT },
298
    { "vpte", SIXEL_LUT_POLICY_VPTE }
299
};
300

301
static sixel_option_choice_t const g_option_choices_working_colorspace[] = {
302
    { "gamma", SIXEL_COLORSPACE_GAMMA },
303
    { "linear", SIXEL_COLORSPACE_LINEAR },
304
    { "oklab", SIXEL_COLORSPACE_OKLAB },
305
    { "cielab", SIXEL_COLORSPACE_CIELAB },
306
    { "din99d", SIXEL_COLORSPACE_DIN99D }
307
};
308

309
static sixel_option_choice_t const g_option_choices_output_colorspace[] = {
310
    { "gamma", SIXEL_COLORSPACE_GAMMA },
311
    { "linear", SIXEL_COLORSPACE_LINEAR },
312
    { "smpte-c", SIXEL_COLORSPACE_SMPTEC },
313
    { "smptec", SIXEL_COLORSPACE_SMPTEC }
314
};
315

316
static int
317
sixel_encoder_pixelformat_for_colorspace(int colorspace,
540✔
318
                                         int prefer_float32)
319
{
320
    switch (colorspace) {
540!
321
    case SIXEL_COLORSPACE_LINEAR:
322
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
UNCOV
323
    case SIXEL_COLORSPACE_OKLAB:
×
324
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
×
UNCOV
325
    case SIXEL_COLORSPACE_CIELAB:
×
326
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
×
UNCOV
327
    case SIXEL_COLORSPACE_DIN99D:
×
328
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
×
329
    default:
540✔
330
        if (prefer_float32) {
540!
331
            return SIXEL_PIXELFORMAT_RGBFLOAT32;
×
332
        }
333
        return SIXEL_PIXELFORMAT_RGB888;
334
    }
335
}
336

337
static sixel_option_choice_t const g_option_choices_precision[] = {
338
    { "auto", SIXEL_ENCODER_PRECISION_MODE_AUTO },
339
    { "8bit", SIXEL_ENCODER_PRECISION_MODE_8BIT },
340
    { "float32", SIXEL_ENCODER_PRECISION_MODE_FLOAT32 }
341
};
342

343

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

353
    len = strlen(s);
57✔
354

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

362
static int
363
sixel_encoder_env_prefers_float32(char const *text)
537✔
364
{
365
    char lowered[8];
537✔
366
    size_t i;
537✔
367

368
    if (text == NULL || *text == '\0') {
537!
369
        return 0;
370
    }
371

372
    for (i = 0; i < sizeof(lowered) - 1 && text[i] != '\0'; ++i) {
×
373
        lowered[i] = (char)tolower((unsigned char)text[i]);
×
374
    }
375
    lowered[i] = '\0';
×
376

377
    if (strcmp(lowered, "0") == 0
×
378
        || strcmp(lowered, "off") == 0
×
379
        || strcmp(lowered, "false") == 0
×
380
        || strcmp(lowered, "no") == 0) {
×
381
        return 0;
382
    }
383

384
    return 1;
385
}
386

387
static SIXELSTATUS
388
sixel_encoder_apply_precision_override(
×
389
    sixel_encoder_t *encoder,
390
    sixel_encoder_precision_mode_t mode)
391
{
392
    int prefer_float32;
×
393

394
    prefer_float32 = encoder->prefer_float32;
×
395

396
    if (mode == SIXEL_ENCODER_PRECISION_MODE_AUTO) {
×
397
        return SIXEL_OK;
398
    }
399

400
    if (mode == SIXEL_ENCODER_PRECISION_MODE_FLOAT32) {
×
401
        prefer_float32 = 1;
402
    } else if (mode == SIXEL_ENCODER_PRECISION_MODE_8BIT) {
×
403
        prefer_float32 = 0;
404
    } else {
405
        sixel_helper_set_additional_message(
×
406
            "sixel_encoder_setopt: invalid precision override.");
407
        return SIXEL_BAD_ARGUMENT;
×
408
    }
409

410
    encoder->prefer_float32 = prefer_float32;
×
411

412
    return SIXEL_OK;
×
413
}
414

415

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

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

564
    status = SIXEL_OK;
565

566
end:
45✔
567
    sixel_allocator_free(allocator, buf);
45✔
568

569
    return status;
45✔
570
}
571

572

573
/* generic writer function for passing to sixel_output_new() */
574
static int
575
sixel_write_callback(char *data, int size, void *priv)
5,214✔
576
{
577
    int result;
5,214✔
578

579
    result = (int)sixel_compat_write(*(int *)priv,
5,025✔
580
                                     data,
581
                                     (size_t)size);
582

583
    return result;
5,214✔
584
}
585

586

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

599
    for (i = j = 0; i < size; ++i, ++j) {
701,274✔
600
        hex[j] = (data[i] >> 4) & 0xf;
701,202✔
601
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
701,202!
602
        hex[++j] = data[i] & 0xf;
701,202✔
603
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
957,426✔
604
    }
605

606
    result = (int)sixel_compat_write(*(int *)priv,
144✔
607
                                     hex,
608
                                     (size_t)(size * 2));
72✔
609

610
    return result;
72✔
611
}
612

613
typedef struct sixel_encoder_output_probe {
614
    sixel_encoder_t *encoder;
615
    sixel_write_function base_write;
616
    void *base_priv;
617
} sixel_encoder_output_probe_t;
618

619
static int
620
sixel_write_with_probe(char *data, int size, void *priv)
72✔
621
{
622
    sixel_encoder_output_probe_t *probe;
72✔
623
    int written;
72✔
624
    double started_at;
72✔
625
    double finished_at;
72✔
626
    double duration;
72✔
627

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

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

676
    probe.encoder = encoder;
×
677
    probe.base_write = sixel_write_callback;
×
678
    probe.base_priv = &fd;
×
679
    written = sixel_write_with_probe(data, size, &probe);
×
680

681
    return written;
×
682
}
683

684
static void
685
sixel_encoder_log_stage(sixel_encoder_t *encoder,
6,108✔
686
                        sixel_frame_t *frame,
687
                        char const *worker,
688
                        char const *role,
689
                        char const *event,
690
                        char const *fmt,
691
                        ...)
692
{
693
    sixel_logger_t *logger;
6,108✔
694
    int job_id;
6,108✔
695
    int height;
6,108✔
696
    char message[256];
6,108✔
697
    va_list args;
6,108✔
698

699
    logger = NULL;
6,108✔
700
    if (encoder != NULL) {
6,108!
701
        logger = encoder->logger;
6,108✔
702
    }
703
    if (logger == NULL || logger->file == NULL || !logger->active) {
6,108!
704
        return;
6,108✔
705
    }
706

707
    job_id = -1;
×
708
    height = 0;
×
709
    if (frame != NULL) {
×
710
        job_id = sixel_frame_get_frame_no(frame);
×
711
        height = sixel_frame_get_height(frame);
×
712
    }
713

714
    message[0] = '\0';
×
715
#if defined(__clang__)
716
#pragma clang diagnostic push
717
#pragma clang diagnostic ignored "-Wformat-nonliteral"
718
#elif defined(__GNUC__)
719
#pragma GCC diagnostic push
720
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
721
#endif
722
    va_start(args, fmt);
×
723
    if (fmt != NULL) {
×
724
#if defined(__clang__)
725
#pragma clang diagnostic push
726
#pragma clang diagnostic ignored "-Wformat-nonliteral"
727
#elif defined(__GNUC__)
728
#pragma GCC diagnostic push
729
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
730
#endif
731
        (void)vsnprintf(message, sizeof(message), fmt, args);
×
732
#if defined(__clang__)
733
#pragma clang diagnostic pop
734
#elif defined(__GNUC__)
735
#pragma GCC diagnostic pop
736
#endif
737
    }
738
    va_end(args);
×
739
#if defined(__clang__)
740
#pragma clang diagnostic pop
741
#elif defined(__GNUC__)
742
#pragma GCC diagnostic pop
743
#endif
744

745
    sixel_logger_logf(logger,
×
746
                      role,
747
                      worker,
748
                      event,
749
                      job_id,
750
                      -1,
751
                      0,
752
                      height,
753
                      0,
754
                      height,
755
                      "%s",
756
                      message);
757
}
1!
758

759
static SIXELSTATUS
760
sixel_encoder_ensure_cell_size(sixel_encoder_t *encoder)
×
761
{
762
#if defined(TIOCGWINSZ)
763
    struct winsize ws;
×
764
    int result;
×
765
    int fd = 0;
×
766

767
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
768
        return SIXEL_OK;
769
    }
770

771
    fd = sixel_compat_open("/dev/tty", O_RDONLY);
×
772
    if (fd >= 0) {
×
773
        result = ioctl(fd, TIOCGWINSZ, &ws);
×
774
        (void)sixel_compat_close(fd);
×
775
    } else {
776
        sixel_helper_set_additional_message(
×
777
            "failed to open /dev/tty");
778
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
779
    }
780
    if (result != 0) {
×
781
        sixel_helper_set_additional_message(
×
782
            "failed to query terminal geometry with ioctl().");
783
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
784
    }
785

786
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
787
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
788
        sixel_helper_set_additional_message(
×
789
            "terminal does not report pixel cell size for drcs option.");
790
        return SIXEL_BAD_ARGUMENT;
×
791
    }
792

793
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
794
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
795
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
796
        sixel_helper_set_additional_message(
×
797
            "terminal cell size reported zero via ioctl().");
798
        return SIXEL_BAD_ARGUMENT;
×
799
    }
800

801
    return SIXEL_OK;
802
#else
803
    (void) encoder;
804
    sixel_helper_set_additional_message(
805
        "drcs option is not supported on this platform.");
806
    return SIXEL_NOT_IMPLEMENTED;
807
#endif
808
}
809

810

811
/* returns monochrome dithering context object */
812
static SIXELSTATUS
813
sixel_prepare_monochrome_palette(
12✔
814
    sixel_dither_t  /* out */ **dither,
815
     int            /* in */  finvert)
816
{
817
    SIXELSTATUS status = SIXEL_FALSE;
12✔
818

819
    if (finvert) {
12✔
820
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
3✔
821
    } else {
822
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
9✔
823
    }
824
    if (*dither == NULL) {
12!
825
        sixel_helper_set_additional_message(
×
826
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
827
        status = SIXEL_RUNTIME_ERROR;
×
828
        goto end;
×
829
    }
830

831
    status = SIXEL_OK;
832

833
end:
12✔
834
    return status;
12✔
835
}
836

837

838
static SIXELSTATUS
839
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
3✔
840
                                sixel_dither_t *dither,
841
                                unsigned char const *pixels,
842
                                size_t size,
843
                                int width,
844
                                int height,
845
                                int pixelformat,
846
                                int source_colorspace,
847
                                int colorspace)
848
{
849
    SIXELSTATUS status;
3✔
850
    int ncolors;
3✔
851
    size_t palette_bytes;
3✔
852
    unsigned char *new_pixels;
3✔
853
    unsigned char *new_palette;
3✔
854
    size_t capture_bytes;
3✔
855
    unsigned char const *capture_source;
3✔
856
    sixel_index_t *paletted_pixels;
3✔
857
    size_t quantized_pixels;
3✔
858
    sixel_allocator_t *dither_allocator;
3✔
859
    int saved_pixelformat;
3✔
860
    int restore_pixelformat;
3✔
861

862
    /*
863
     * Preserve the quantized frame for assessment observers.
864
     *
865
     *     +-----------------+     +---------------------+
866
     *     | quantized bytes | --> | encoder->capture_*  |
867
     *     +-----------------+     +---------------------+
868
     */
869

870
    status = SIXEL_OK;
3✔
871
    ncolors = 0;
3✔
872
    palette_bytes = 0;
3✔
873
    new_pixels = NULL;
3✔
874
    new_palette = NULL;
3✔
875
    capture_bytes = size;
3✔
876
    capture_source = pixels;
3✔
877
    paletted_pixels = NULL;
3✔
878
    quantized_pixels = 0;
3✔
879
    dither_allocator = NULL;
3✔
880

881
    if (encoder == NULL || pixels == NULL ||
3!
882
            (dither == NULL && size == 0)) {
3!
883
        sixel_helper_set_additional_message(
×
884
            "sixel_encoder_capture_quantized: invalid capture request.");
885
        return SIXEL_BAD_ARGUMENT;
×
886
    }
887

888
    if (!encoder->capture_quantized) {
3!
889
        return SIXEL_OK;
890
    }
891

892
    saved_pixelformat = SIXEL_PIXELFORMAT_RGB888;
3✔
893
    restore_pixelformat = 0;
3✔
894
    if (dither != NULL) {
3!
895
        dither_allocator = dither->allocator;
3✔
896
        saved_pixelformat = dither->pixelformat;
3✔
897
        restore_pixelformat = 1;
3✔
898
        if (width <= 0 || height <= 0) {
3!
899
            sixel_helper_set_additional_message(
×
900
                "sixel_encoder_capture_quantized: invalid dimensions.");
901
            status = SIXEL_BAD_ARGUMENT;
×
902
            goto cleanup;
×
903
        }
904
        quantized_pixels = (size_t)width * (size_t)height;
3✔
905
        if (height != 0 &&
3!
906
                quantized_pixels / (size_t)height != (size_t)width) {
3!
907
            sixel_helper_set_additional_message(
×
908
                "sixel_encoder_capture_quantized: image too large.");
909
            status = SIXEL_RUNTIME_ERROR;
×
910
            goto cleanup;
×
911
        }
912
        paletted_pixels = sixel_dither_apply_palette(
3✔
913
            dither, (unsigned char *)pixels, width, height);
914
        if (paletted_pixels == NULL) {
3!
915
            sixel_helper_set_additional_message(
×
916
                "sixel_encoder_capture_quantized: palette conversion failed.");
917
            status = SIXEL_RUNTIME_ERROR;
×
918
            goto cleanup;
×
919
        }
920
        capture_source = (unsigned char const *)paletted_pixels;
921
        capture_bytes = quantized_pixels;
922
    }
923

924
    if (capture_bytes > 0) {
3!
925
        if (encoder->capture_pixels == NULL ||
3!
926
                encoder->capture_pixels_size < capture_bytes) {
×
927
            new_pixels = (unsigned char *)sixel_allocator_malloc(
3✔
928
                encoder->allocator, capture_bytes);
929
            if (new_pixels == NULL) {
3!
930
                sixel_helper_set_additional_message(
×
931
                    "sixel_encoder_capture_quantized: "
932
                    "sixel_allocator_malloc() failed.");
933
                status = SIXEL_BAD_ALLOCATION;
×
934
                goto cleanup;
×
935
            }
936
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
3✔
937
            encoder->capture_pixels = new_pixels;
3✔
938
            encoder->capture_pixels_size = capture_bytes;
3✔
939
        }
940
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
3✔
941
    }
942
    encoder->capture_pixel_bytes = capture_bytes;
3✔
943

944
    ncolors = 0;
3✔
945
    palette_bytes = 0;
3✔
946
    if (dither != NULL) {
3!
947
        sixel_palette_t *palette_obj = NULL;
3✔
948
        unsigned char *palette_copy = NULL;
3✔
949
        size_t palette_count = 0U;
3✔
950

951
        status = sixel_dither_get_quantized_palette(dither, &palette_obj);
3✔
952
        if (SIXEL_SUCCEEDED(status) && palette_obj != NULL) {
3!
953
            status = sixel_palette_copy_entries_8bit(
3✔
954
                palette_obj,
955
                &palette_copy,
956
                &palette_count,
957
                SIXEL_PIXELFORMAT_RGB888,
958
                encoder->allocator);
959
            sixel_palette_unref(palette_obj);
3✔
960
            palette_obj = NULL;
3✔
961
            if (SIXEL_SUCCEEDED(status)
3!
962
                    && palette_copy != NULL
3!
963
                    && palette_count > 0U) {
3!
964
                palette_bytes = palette_count * 3U;
3✔
965
                ncolors = (int)palette_count;
3✔
966
                if (encoder->capture_palette == NULL
3!
UNCOV
967
                        || encoder->capture_palette_size < palette_bytes) {
×
968
                    new_palette = (unsigned char *)sixel_allocator_malloc(
3✔
969
                        encoder->allocator, palette_bytes);
970
                    if (new_palette == NULL) {
3!
971
                        sixel_helper_set_additional_message(
×
972
                            "sixel_encoder_capture_quantized: "
973
                            "sixel_allocator_malloc() failed.");
974
                        status = SIXEL_BAD_ALLOCATION;
×
975
                        sixel_allocator_free(encoder->allocator,
×
976
                                             palette_copy);
977
                        goto cleanup;
×
978
                    }
979
                    sixel_allocator_free(encoder->allocator,
3✔
980
                                         encoder->capture_palette);
3✔
981
                    encoder->capture_palette = new_palette;
3✔
982
                    encoder->capture_palette_size = palette_bytes;
3✔
983
                }
984
                memcpy(encoder->capture_palette,
3!
985
                       palette_copy,
986
                       palette_bytes);
987
                if (source_colorspace != colorspace) {
3!
988
                    (void)sixel_helper_convert_colorspace(
×
989
                        encoder->capture_palette,
990
                        palette_bytes,
991
                        SIXEL_PIXELFORMAT_RGB888,
992
                        source_colorspace,
993
                        colorspace);
994
                }
995
            }
996
            if (palette_copy != NULL) {
3!
997
                sixel_allocator_free(encoder->allocator, palette_copy);
3✔
998
            }
999
        }
1000
    }
1!
1001

1002
    encoder->capture_width = width;
3✔
1003
    encoder->capture_height = height;
3✔
1004
    if (dither != NULL) {
3!
1005
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
3✔
1006
    } else {
1007
        encoder->capture_pixelformat = pixelformat;
×
1008
    }
1009
    encoder->capture_colorspace = colorspace;
3✔
1010
    encoder->capture_palette_size = palette_bytes;
3✔
1011
    encoder->capture_ncolors = ncolors;
3✔
1012
    encoder->capture_valid = 1;
3✔
1013

1014
cleanup:
3✔
1015
    if (restore_pixelformat && dither != NULL) {
3!
1016
        /*
1017
         * Undo the normalization performed by sixel_dither_apply_palette().
1018
         *
1019
         *     RGBA8888 --capture--> RGB888 (temporary)
1020
         *          \______________________________/
1021
         *                          |
1022
         *                 restore original state for
1023
         *                 the real encoder execution.
1024
         */
1025
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
3✔
1026
    }
1027
    if (paletted_pixels != NULL && dither_allocator != NULL) {
3!
1028
        sixel_allocator_free(dither_allocator, paletted_pixels);
3✔
1029
    }
1030

1031
    return status;
1032
}
1033

1034
static SIXELSTATUS
1035
sixel_prepare_builtin_palette(
27✔
1036
    sixel_dither_t /* out */ **dither,
1037
    int            /* in */  builtin_palette)
1038
{
1039
    SIXELSTATUS status = SIXEL_FALSE;
27✔
1040

1041
    *dither = sixel_dither_get(builtin_palette);
27✔
1042
    if (*dither == NULL) {
27!
1043
        sixel_helper_set_additional_message(
×
1044
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
1045
        status = SIXEL_RUNTIME_ERROR;
×
1046
        goto end;
×
1047
    }
1048

1049
    status = SIXEL_OK;
1050

1051
end:
27✔
1052
    return status;
27✔
1053
}
1054

1055
static int
1056
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
450✔
1057
{
1058
    int width_hint;
450✔
1059
    int height_hint;
450✔
1060
    long base;
450✔
1061
    long size;
450✔
1062

1063
    width_hint = 0;
450✔
1064
    height_hint = 0;
450✔
1065
    base = 0;
450✔
1066
    size = 0;
450✔
1067

1068
    if (encoder == NULL) {
450!
1069
        return 0;
1070
    }
1071

1072
    width_hint = encoder->pixelwidth;
450✔
1073
    height_hint = encoder->pixelheight;
450✔
1074

1075
    /* Request extra resolution for downscaling to preserve detail. */
1076
    if (width_hint > 0 && height_hint > 0) {
450✔
1077
        /* Follow the CLI rule: double the larger axis before doubling
1078
         * again for the final request size. */
1079
        if (width_hint >= height_hint) {
9!
1080
            base = (long)width_hint;
1081
        } else {
1082
            base = (long)height_hint;
1083
        }
1084
        base *= 2L;
9✔
1085
    } else if (width_hint > 0) {
441✔
1086
        base = (long)width_hint;
48✔
1087
    } else if (height_hint > 0) {
393✔
1088
        base = (long)height_hint;
36✔
1089
    } else {
1090
        return 0;
1091
    }
1092

1093
    size = base * 2L;
93✔
1094
    if (size > (long)INT_MAX) {
93!
1095
        size = (long)INT_MAX;
1096
    }
1097
    if (size < 1L) {
1!
1098
        size = 1L;
1099
    }
1100

1101
    return (int)size;
93✔
1102
}
1103

1104

1105
typedef struct sixel_callback_context_for_mapfile {
1106
    int reqcolors;
1107
    sixel_dither_t *dither;
1108
    sixel_allocator_t *allocator;
1109
    int working_colorspace;
1110
    int lut_policy;
1111
    int prefer_float32;
1112
} sixel_callback_context_for_mapfile_t;
1113

1114

1115
/* callback function for sixel_helper_load_image_file() */
1116
static SIXELSTATUS
1117
load_image_callback_for_palette(
21✔
1118
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1119
    void            /* in */    *data)  /* private data */
1120
{
1121
    SIXELSTATUS status = SIXEL_FALSE;
21✔
1122
    sixel_callback_context_for_mapfile_t *callback_context;
21✔
1123

1124
    /* get callback context object from the private data */
1125
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
1126

1127
    status = sixel_frame_set_pixelformat(
21✔
1128
        frame,
1129
        sixel_encoder_pixelformat_for_colorspace(
1130
            callback_context->working_colorspace,
1131
            callback_context->prefer_float32));
1132
    if (SIXEL_FAILED(status)) {
21!
1133
        goto end;
×
1134
    }
1135

1136
    switch (sixel_frame_get_pixelformat(frame)) {
21!
UNCOV
1137
    case SIXEL_PIXELFORMAT_PAL1:
×
1138
    case SIXEL_PIXELFORMAT_PAL2:
1139
    case SIXEL_PIXELFORMAT_PAL4:
1140
    case SIXEL_PIXELFORMAT_PAL8:
1141
        if (sixel_frame_get_palette(frame) == NULL) {
×
1142
            status = SIXEL_LOGIC_ERROR;
×
1143
            goto end;
×
1144
        }
1145
        /* create new dither object */
1146
        status = sixel_dither_new(
×
1147
            &callback_context->dither,
1148
            sixel_frame_get_ncolors(frame),
1149
            callback_context->allocator);
1150
        if (SIXEL_FAILED(status)) {
×
1151
            goto end;
×
1152
        }
1153

1154
        sixel_dither_set_lut_policy(callback_context->dither,
×
1155
                                    callback_context->lut_policy);
1156

1157
        /* use palette which is extracted from the image */
1158
        sixel_dither_set_palette(callback_context->dither,
×
1159
                                 sixel_frame_get_palette(frame));
1160
        /* success */
1161
        status = SIXEL_OK;
×
1162
        break;
×
UNCOV
1163
    case SIXEL_PIXELFORMAT_G1:
×
1164
        /* use 1bpp grayscale builtin palette */
1165
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1166
        /* success */
1167
        status = SIXEL_OK;
×
1168
        break;
×
UNCOV
1169
    case SIXEL_PIXELFORMAT_G2:
×
1170
        /* use 2bpp grayscale builtin palette */
1171
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1172
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
1173
        /* success */
1174
        status = SIXEL_OK;
×
1175
        break;
×
UNCOV
1176
    case SIXEL_PIXELFORMAT_G4:
×
1177
        /* use 4bpp grayscale builtin palette */
1178
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
1179
        /* success */
1180
        status = SIXEL_OK;
×
1181
        break;
×
UNCOV
1182
    case SIXEL_PIXELFORMAT_G8:
×
1183
        /* use 8bpp grayscale builtin palette */
1184
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
1185
        /* success */
1186
        status = SIXEL_OK;
×
1187
        break;
×
1188
    default:
21✔
1189
        /* create new dither object */
1190
        status = sixel_dither_new(
21✔
1191
            &callback_context->dither,
1192
            callback_context->reqcolors,
1193
            callback_context->allocator);
1194
        if (SIXEL_FAILED(status)) {
21!
1195
            goto end;
×
1196
        }
1197

1198
        sixel_dither_set_lut_policy(callback_context->dither,
21✔
1199
                                    callback_context->lut_policy);
1200

1201
        /* create adaptive palette from given frame object */
1202
        status = sixel_dither_initialize(callback_context->dither,
21✔
1203
                                         sixel_frame_get_pixels(frame),
1204
                                         sixel_frame_get_width(frame),
1205
                                         sixel_frame_get_height(frame),
1206
                                         sixel_frame_get_pixelformat(frame),
1207
                                         SIXEL_LARGE_NORM,
1208
                                         SIXEL_REP_CENTER_BOX,
1209
                                         SIXEL_QUALITY_HIGH);
1210
        if (SIXEL_FAILED(status)) {
21!
1211
            sixel_dither_unref(callback_context->dither);
×
1212
            goto end;
×
1213
        }
1214

1215
        /* success */
1216
        status = SIXEL_OK;
1217

1218
        break;
1219
    }
1220

1221
end:
21✔
1222
    return status;
21✔
1223
}
1224

1225

1226
static SIXELSTATUS
1227
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1228

1229

1230
static int
1231
sixel_path_has_extension(char const *path, char const *extension)
63✔
1232
{
1233
    size_t path_len;
63✔
1234
    size_t ext_len;
63✔
1235
    size_t index;
63✔
1236

1237
    path_len = 0u;
63✔
1238
    ext_len = 0u;
63✔
1239
    index = 0u;
63✔
1240

1241
    if (path == NULL || extension == NULL) {
63!
1242
        return 0;
1243
    }
1244

1245
    path_len = strlen(path);
63✔
1246
    ext_len = strlen(extension);
63✔
1247
    if (ext_len == 0u || path_len < ext_len) {
63!
1248
        return 0;
1249
    }
1250

1251
    for (index = 0u; index < ext_len; ++index) {
144!
1252
        unsigned char path_ch;
144✔
1253
        unsigned char ext_ch;
144✔
1254

1255
        path_ch = (unsigned char)path[path_len - ext_len + index];
144✔
1256
        ext_ch = (unsigned char)extension[index];
144✔
1257
        if (tolower(path_ch) != tolower(ext_ch)) {
144✔
1258
            return 0;
1259
        }
1260
    }
2✔
1261

1262
    return 1;
1263
}
1264

1265
typedef enum sixel_palette_format {
1266
    SIXEL_PALETTE_FORMAT_NONE = 0,
1267
    SIXEL_PALETTE_FORMAT_ACT,
1268
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1269
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1270
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1271
    SIXEL_PALETTE_FORMAT_GPL
1272
} sixel_palette_format_t;
1273

1274
/*
1275
 * Palette specification parser
1276
 *
1277
 *   TYPE:PATH  -> explicit format prefix
1278
 *   PATH       -> rely on extension or heuristics
1279
 *
1280
 * The ASCII diagram below shows how the prefix is peeled:
1281
 *
1282
 *   [type] : [path]
1283
 *    ^-- left part selects decoder/encoder when present.
1284
 */
1285
static char const *
1286
sixel_palette_strip_prefix(char const *spec,
48✔
1287
                           sixel_palette_format_t *format_hint)
1288
{
1289
    char const *colon;
48✔
1290
    size_t type_len;
48✔
1291
    size_t index;
48✔
1292
    char lowered[16];
48✔
1293

1294
    colon = NULL;
48✔
1295
    type_len = 0u;
48✔
1296
    index = 0u;
48✔
1297

1298
    if (format_hint != NULL) {
48✔
1299
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
1300
    }
1301
    if (spec == NULL) {
48!
1302
        return NULL;
1303
    }
1304

1305
    colon = strchr(spec, ':');
48✔
1306
    if (colon == NULL) {
48!
1307
        return spec;
1308
    }
1309

1310
    type_len = (size_t)(colon - spec);
×
1311
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1312
        return spec;
1313
    }
1314

1315
    for (index = 0u; index < type_len; ++index) {
×
1316
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1317
    }
1318
    lowered[type_len] = '\0';
×
1319

1320
    if (strcmp(lowered, "act") == 0) {
×
1321
        if (format_hint != NULL) {
×
1322
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
×
1323
        }
1324
        return colon + 1;
×
1325
    }
1326
    if (strcmp(lowered, "pal") == 0) {
×
1327
        if (format_hint != NULL) {
×
1328
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1329
        }
1330
        return colon + 1;
×
1331
    }
1332
    if (strcmp(lowered, "pal-jasc") == 0) {
×
1333
        if (format_hint != NULL) {
×
1334
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1335
        }
1336
        return colon + 1;
×
1337
    }
1338
    if (strcmp(lowered, "pal-riff") == 0) {
×
1339
        if (format_hint != NULL) {
×
1340
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1341
        }
1342
        return colon + 1;
×
1343
    }
1344
    if (strcmp(lowered, "gpl") == 0) {
×
1345
        if (format_hint != NULL) {
×
1346
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
×
1347
        }
1348
        return colon + 1;
×
1349
    }
1350

1351
    return spec;
1352
}
1353

1354
static sixel_palette_format_t
1355
sixel_palette_format_from_extension(char const *path)
21✔
1356
{
1357
    if (path == NULL) {
21!
1358
        return SIXEL_PALETTE_FORMAT_NONE;
1359
    }
1360

1361
    if (sixel_path_has_extension(path, ".act")) {
21!
1362
        return SIXEL_PALETTE_FORMAT_ACT;
1363
    }
1364
    if (sixel_path_has_extension(path, ".pal")) {
21!
1365
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
1366
    }
1367
    if (sixel_path_has_extension(path, ".gpl")) {
21!
1368
        return SIXEL_PALETTE_FORMAT_GPL;
×
1369
    }
1370

1371
    return SIXEL_PALETTE_FORMAT_NONE;
1372
}
1373

1374
static int
1375
sixel_path_has_any_extension(char const *path)
21✔
1376
{
1377
    char const *slash_forward;
21✔
1378
#if defined(_WIN32)
1379
    char const *slash_backward;
1380
#endif
1381
    char const *start;
21✔
1382
    char const *dot;
21✔
1383

1384
    slash_forward = NULL;
21✔
1385
#if defined(_WIN32)
1386
    slash_backward = NULL;
1387
#endif
1388
    start = path;
21✔
1389
    dot = NULL;
21✔
1390

1391
    if (path == NULL) {
21!
1392
        return 0;
1393
    }
1394

1395
    slash_forward = strrchr(path, '/');
21✔
1396
#if defined(_WIN32)
1397
    slash_backward = strrchr(path, '\\');
1398
    if (slash_backward != NULL &&
1399
            (slash_forward == NULL || slash_backward > slash_forward)) {
1400
        slash_forward = slash_backward;
1401
    }
1402
#endif
1403
    if (slash_forward == NULL) {
21!
1404
        start = path;
1405
    } else {
1406
        start = slash_forward + 1;
21✔
1407
    }
1408

1409
    dot = strrchr(start, '.');
21✔
1410
    if (dot == NULL) {
21!
1411
        return 0;
1412
    }
1413

1414
    if (dot[1] == '\0') {
21!
1415
        return 0;
×
1416
    }
1417

1418
    return 1;
1419
}
1420

1421
static int
1422
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1423
{
1424
    if (data == NULL || size < 3u) {
×
1425
        return 0;
1426
    }
1427
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
1428
        return 1;
×
1429
    }
1430
    return 0;
1431
}
1432

1433

1434
/*
1435
 * Materialize palette bytes from a stream.
1436
 *
1437
 * The flow looks like:
1438
 *
1439
 *   stream --> [scratch buffer] --> [resizable heap buffer]
1440
 *                  ^ looped read        ^ returned payload
1441
 */
1442
static SIXELSTATUS
1443
sixel_palette_read_stream(FILE *stream,
×
1444
                          sixel_allocator_t *allocator,
1445
                          unsigned char **pdata,
1446
                          size_t *psize)
1447
{
1448
    SIXELSTATUS status;
×
1449
    unsigned char *buffer;
×
1450
    unsigned char *grown;
×
1451
    size_t capacity;
×
1452
    size_t used;
×
1453
    size_t read_bytes;
×
1454
    size_t needed;
×
1455
    size_t new_capacity;
×
1456
    unsigned char scratch[4096];
×
1457

1458
    status = SIXEL_FALSE;
×
1459
    buffer = NULL;
×
1460
    grown = NULL;
×
1461
    capacity = 0u;
×
1462
    used = 0u;
×
1463
    read_bytes = 0u;
×
1464
    needed = 0u;
×
1465
    new_capacity = 0u;
×
1466

1467
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1468
        sixel_helper_set_additional_message(
×
1469
            "sixel_palette_read_stream: invalid argument.");
1470
        return SIXEL_BAD_ARGUMENT;
×
1471
    }
1472

1473
    *pdata = NULL;
×
1474
    *psize = 0u;
×
1475

1476
    while (1) {
×
1477
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1478
        if (read_bytes == 0u) {
×
1479
            if (ferror(stream)) {
×
1480
                sixel_helper_set_additional_message(
×
1481
                    "sixel_palette_read_stream: fread() failed.");
1482
                status = SIXEL_LIBC_ERROR;
×
1483
                goto cleanup;
×
1484
            }
1485
            break;
×
1486
        }
1487

1488
        if (used > SIZE_MAX - read_bytes) {
×
1489
            sixel_helper_set_additional_message(
×
1490
                "sixel_palette_read_stream: size overflow.");
1491
            status = SIXEL_BAD_ALLOCATION;
×
1492
            goto cleanup;
×
1493
        }
1494
        needed = used + read_bytes;
×
1495

1496
        if (needed > capacity) {
×
1497
            new_capacity = capacity;
×
1498
            if (new_capacity == 0u) {
×
1499
                new_capacity = 4096u;
×
1500
            }
1501
            while (needed > new_capacity) {
×
1502
                if (new_capacity > SIZE_MAX / 2u) {
×
1503
                    sixel_helper_set_additional_message(
×
1504
                        "sixel_palette_read_stream: size overflow.");
1505
                    status = SIXEL_BAD_ALLOCATION;
×
1506
                    goto cleanup;
×
1507
                }
1508
                new_capacity *= 2u;
×
1509
            }
1510

1511
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1512
                                                             new_capacity);
1513
            if (grown == NULL) {
×
1514
                sixel_helper_set_additional_message(
×
1515
                    "sixel_palette_read_stream: allocation failed.");
1516
                status = SIXEL_BAD_ALLOCATION;
×
1517
                goto cleanup;
×
1518
            }
1519

1520
            if (buffer != NULL) {
×
1521
                memcpy(grown, buffer, used);
×
1522
                sixel_allocator_free(allocator, buffer);
×
1523
            }
1524

1525
            buffer = grown;
1526
            grown = NULL;
1527
            capacity = new_capacity;
1528
        }
1529

1530
        memcpy(buffer + used, scratch, read_bytes);
×
1531
        used += read_bytes;
×
1532
    }
1533

1534
    *pdata = buffer;
×
1535
    *psize = used;
×
1536
    status = SIXEL_OK;
×
1537
    return status;
×
1538

UNCOV
1539
cleanup:
×
1540
    if (grown != NULL) {
×
1541
        sixel_allocator_free(allocator, grown);
1542
    }
1543
    if (buffer != NULL) {
×
1544
        sixel_allocator_free(allocator, buffer);
×
1545
    }
1546
    return status;
1547
}
1548

1549

1550
static SIXELSTATUS
1551
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
×
1552
{
1553
    int error_value;
×
1554
    char error_message[256];
×
1555
#if HAVE_SYS_STAT_H
1556
    struct stat path_stat;
×
1557
#endif
1558

1559
    if (pstream == NULL || pclose == NULL || path == NULL) {
×
1560
        sixel_helper_set_additional_message(
×
1561
            "sixel_palette_open_read: invalid argument.");
1562
        return SIXEL_BAD_ARGUMENT;
×
1563
    }
1564

1565
    error_value = 0;
×
1566
    error_message[0] = '\0';
×
1567

1568
    if (strcmp(path, "-") == 0) {
×
1569
        *pstream = stdin;
×
1570
        *pclose = 0;
×
1571
        return SIXEL_OK;
×
1572
    }
1573

1574
#if HAVE_SYS_STAT_H
1575
    if (stat(path, &path_stat) == 0 && S_ISDIR(path_stat.st_mode)) {
×
1576
        sixel_compat_snprintf(error_message,
×
1577
                              sizeof(error_message),
1578
                              "sixel_palette_open_read: mapfile \"%s\" "
1579
                              "is a directory.",
1580
                              path);
1581
        sixel_helper_set_additional_message(error_message);
×
1582
        return SIXEL_BAD_INPUT;
×
1583
    }
1584
#endif
1585

1586
    errno = 0;
×
1587
    *pstream = fopen(path, "rb");
×
1588
    if (*pstream == NULL) {
×
1589
        error_value = errno;
×
1590
        sixel_compat_snprintf(error_message,
×
1591
                              sizeof(error_message),
1592
                              "sixel_palette_open_read: failed to open "
1593
                              "\"%s\": %s.",
1594
                              path,
1595
                              strerror(error_value));
1596
        sixel_helper_set_additional_message(error_message);
×
1597
        return SIXEL_LIBC_ERROR;
×
1598
    }
1599

1600
    *pclose = 1;
×
1601
    return SIXEL_OK;
×
1602
}
1603

1604

1605
static void
1606
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1607
{
1608
    if (close_stream && stream != NULL) {
×
1609
        (void) fclose(stream);
×
1610
    }
1611
}
×
1612

1613

1614
static sixel_palette_format_t
1615
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1616
{
1617
    size_t offset;
×
1618
    size_t data_size;
×
1619

1620
    offset = 0u;
×
1621
    data_size = size;
×
1622

1623
    if (data == NULL || size == 0u) {
×
1624
        return SIXEL_PALETTE_FORMAT_NONE;
1625
    }
1626

1627
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
1628
        return SIXEL_PALETTE_FORMAT_ACT;
1629
    }
1630

1631
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
1632
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
1633
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
1634
    }
1635

1636
    if (sixel_palette_has_utf8_bom(data, size)) {
×
1637
        offset = 3u;
×
1638
        data_size = size - 3u;
×
1639
    }
1640

1641
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
×
1642
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
1643
    }
1644
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
×
1645
        return SIXEL_PALETTE_FORMAT_GPL;
×
1646
    }
1647

1648
    return SIXEL_PALETTE_FORMAT_NONE;
1649
}
1650

1651

1652
static unsigned int
1653
sixel_palette_read_le16(unsigned char const *ptr)
×
1654
{
1655
    if (ptr == NULL) {
×
1656
        return 0u;
1657
    }
1658
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1659
}
1660

1661

1662
static unsigned int
1663
sixel_palette_read_le32(unsigned char const *ptr)
×
1664
{
1665
    if (ptr == NULL) {
×
1666
        return 0u;
1667
    }
1668
    return ((unsigned int)ptr[0])
×
1669
        | ((unsigned int)ptr[1] << 8)
×
1670
        | ((unsigned int)ptr[2] << 16)
×
1671
        | ((unsigned int)ptr[3] << 24);
×
1672
}
1673

1674

1675
/*
1676
 * Adobe Color Table (*.act) reader
1677
 *
1678
 *   +-----------+---------------------------+
1679
 *   | section   | bytes                     |
1680
 *   +-----------+---------------------------+
1681
 *   | palette   | 256 entries * 3 RGB bytes |
1682
 *   | trailer   | optional count/start pair |
1683
 *   +-----------+---------------------------+
1684
 */
1685
static SIXELSTATUS
1686
sixel_palette_parse_act(unsigned char const *data,
×
1687
                        size_t size,
1688
                        sixel_encoder_t *encoder,
1689
                        sixel_dither_t **dither)
1690
{
1691
    SIXELSTATUS status;
×
1692
    sixel_dither_t *local;
×
1693
    unsigned char const *palette_start;
×
1694
    unsigned char const *trailer;
×
1695
    sixel_palette_t *palette_obj;
×
1696
    int exported_colors;
×
1697
    int start_index;
×
1698

1699
    status = SIXEL_FALSE;
×
1700
    local = NULL;
×
1701
    palette_start = data;
×
1702
    trailer = NULL;
×
1703
    palette_obj = NULL;
×
1704
    exported_colors = 0;
×
1705
    start_index = 0;
×
1706

1707
    if (encoder == NULL || dither == NULL) {
×
1708
        sixel_helper_set_additional_message(
×
1709
            "sixel_palette_parse_act: invalid argument.");
1710
        return SIXEL_BAD_ARGUMENT;
×
1711
    }
1712
    if (data == NULL || size < 256u * 3u) {
×
1713
        sixel_helper_set_additional_message(
×
1714
            "sixel_palette_parse_act: truncated ACT palette.");
1715
        return SIXEL_BAD_INPUT;
×
1716
    }
1717

1718
    if (size == 256u * 3u) {
×
1719
        exported_colors = 256;
1720
        start_index = 0;
1721
    } else if (size == 256u * 3u + 4u) {
×
1722
        trailer = data + 256u * 3u;
×
1723
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
×
1724
                                | (unsigned int)trailer[1]);
×
1725
        start_index = (int)(((unsigned int)trailer[2] << 8)
×
1726
                            | (unsigned int)trailer[3]);
×
1727
    } else {
1728
        sixel_helper_set_additional_message(
×
1729
            "sixel_palette_parse_act: invalid ACT length.");
1730
        return SIXEL_BAD_INPUT;
×
1731
    }
1732

1733
    if (start_index < 0 || start_index >= 256) {
×
1734
        sixel_helper_set_additional_message(
×
1735
            "sixel_palette_parse_act: ACT start index out of range.");
1736
        return SIXEL_BAD_INPUT;
×
1737
    }
1738
    if (exported_colors <= 0 || exported_colors > 256) {
×
1739
        exported_colors = 256;
×
1740
    }
1741
    if (start_index + exported_colors > 256) {
×
1742
        sixel_helper_set_additional_message(
×
1743
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
1744
        return SIXEL_BAD_INPUT;
×
1745
    }
1746

1747
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
1748
    if (SIXEL_FAILED(status)) {
×
1749
        return status;
1750
    }
1751

1752
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1753

1754
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
1755
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
1756
        sixel_dither_unref(local);
×
1757
        return status;
×
1758
    }
1759
    status = sixel_palette_set_entries(
×
1760
        palette_obj,
1761
        palette_start + (size_t)start_index * 3u,
×
1762
        (unsigned int)exported_colors,
1763
        3,
1764
        encoder->allocator);
1765
    sixel_palette_unref(palette_obj);
×
1766
    if (SIXEL_FAILED(status)) {
×
1767
        sixel_dither_unref(local);
×
1768
        return status;
×
1769
    }
1770

1771
    *dither = local;
×
1772
    return SIXEL_OK;
×
1773
}
1774

1775

1776
static SIXELSTATUS
1777
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1778
                             size_t size,
1779
                             sixel_encoder_t *encoder,
1780
                             sixel_dither_t **dither)
1781
{
1782
    SIXELSTATUS status;
×
1783
    char *text;
×
1784
    size_t index;
×
1785
    size_t offset;
×
1786
    char *cursor;
×
1787
    char *line;
×
1788
    char *line_end;
×
1789
    int stage;
×
1790
    int exported_colors;
×
1791
    int parsed_colors;
×
1792
    sixel_dither_t *local;
×
1793
    sixel_palette_t *palette_obj;
×
1794
    unsigned char *palette_buffer;
×
1795
    long component;
×
1796
    char *parse_end;
×
1797
    int value_index;
×
1798
    int values[3];
×
1799
    char tail;
×
1800

1801
    status = SIXEL_FALSE;
×
1802
    text = NULL;
×
1803
    index = 0u;
×
1804
    offset = 0u;
×
1805
    cursor = NULL;
×
1806
    line = NULL;
×
1807
    line_end = NULL;
×
1808
    stage = 0;
×
1809
    exported_colors = 0;
×
1810
    parsed_colors = 0;
×
1811
    local = NULL;
×
1812
    palette_obj = NULL;
×
1813
    palette_buffer = NULL;
×
1814
    component = 0;
×
1815
    parse_end = NULL;
×
1816
    value_index = 0;
×
1817
    values[0] = 0;
×
1818
    values[1] = 0;
×
1819
    values[2] = 0;
×
1820

1821
    if (encoder == NULL || dither == NULL) {
×
1822
        sixel_helper_set_additional_message(
×
1823
            "sixel_palette_parse_pal_jasc: invalid argument.");
1824
        return SIXEL_BAD_ARGUMENT;
×
1825
    }
1826
    if (data == NULL || size == 0u) {
×
1827
        sixel_helper_set_additional_message(
×
1828
            "sixel_palette_parse_pal_jasc: empty palette.");
1829
        return SIXEL_BAD_INPUT;
×
1830
    }
1831

1832
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
1833
    if (text == NULL) {
×
1834
        sixel_helper_set_additional_message(
×
1835
            "sixel_palette_parse_pal_jasc: allocation failed.");
1836
        return SIXEL_BAD_ALLOCATION;
×
1837
    }
1838
    memcpy(text, data, size);
×
1839
    text[size] = '\0';
×
1840

1841
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1842
        offset = 3u;
×
1843
    }
1844
    cursor = text + offset;
×
1845

1846
    while (*cursor != '\0') {
×
1847
        line = cursor;
×
1848
        line_end = cursor;
1849
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1850
            ++line_end;
×
1851
        }
1852
        if (*line_end != '\0') {
×
1853
            *line_end = '\0';
×
1854
            cursor = line_end + 1;
×
1855
        } else {
1856
            cursor = line_end;
1857
        }
1858
        while (*cursor == '\n' || *cursor == '\r') {
×
1859
            ++cursor;
×
1860
        }
1861

1862
        while (*line == ' ' || *line == '\t') {
×
1863
            ++line;
×
1864
        }
1865
        index = strlen(line);
×
1866
        while (index > 0u) {
×
1867
            tail = line[index - 1];
×
1868
            if (tail != ' ' && tail != '\t') {
×
1869
                break;
1870
            }
1871
            line[index - 1] = '\0';
×
1872
            --index;
×
1873
        }
1874
        if (*line == '\0') {
×
1875
            continue;
×
1876
        }
1877
        if (*line == '#') {
×
1878
            continue;
×
1879
        }
1880

1881
        if (stage == 0) {
×
1882
            if (strcmp(line, "JASC-PAL") != 0) {
×
1883
                sixel_helper_set_additional_message(
×
1884
                    "sixel_palette_parse_pal_jasc: missing header.");
1885
                status = SIXEL_BAD_INPUT;
×
1886
                goto cleanup;
×
1887
            }
1888
            stage = 1;
×
1889
            continue;
×
1890
        }
1891
        if (stage == 1) {
×
1892
            stage = 2;
×
1893
            continue;
×
1894
        }
1895
        if (stage == 2) {
×
1896
            component = strtol(line, &parse_end, 10);
×
1897
            if (parse_end == line || component <= 0L || component > 256L) {
×
1898
                sixel_helper_set_additional_message(
×
1899
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1900
                status = SIXEL_BAD_INPUT;
×
1901
                goto cleanup;
×
1902
            }
1903
            exported_colors = (int)component;
×
1904
            if (exported_colors <= 0) {
×
1905
                sixel_helper_set_additional_message(
1906
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1907
                status = SIXEL_BAD_INPUT;
1908
                goto cleanup;
1909
            }
1910
            palette_buffer = (unsigned char *)sixel_allocator_malloc(
×
1911
                encoder->allocator,
1912
                (size_t)exported_colors * 3u);
×
1913
            if (palette_buffer == NULL) {
×
1914
                sixel_helper_set_additional_message(
×
1915
                    "sixel_palette_parse_pal_jasc: allocation failed.");
1916
                status = SIXEL_BAD_ALLOCATION;
×
1917
                goto cleanup;
×
1918
            }
1919
            status = sixel_dither_new(&local, exported_colors,
×
1920
                                      encoder->allocator);
1921
            if (SIXEL_FAILED(status)) {
×
1922
                goto cleanup;
×
1923
            }
1924
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1925
            stage = 3;
×
1926
            continue;
×
1927
        }
1928

1929
        value_index = 0;
1930
        while (value_index < 3) {
×
1931
            component = strtol(line, &parse_end, 10);
×
1932
            if (parse_end == line || component < 0L || component > 255L) {
×
1933
                sixel_helper_set_additional_message(
×
1934
                    "sixel_palette_parse_pal_jasc: invalid component.");
1935
                status = SIXEL_BAD_INPUT;
×
1936
                goto cleanup;
×
1937
            }
1938
            values[value_index] = (int)component;
×
1939
            ++value_index;
×
1940
            line = parse_end;
×
1941
            while (*line == ' ' || *line == '\t') {
×
1942
                ++line;
×
1943
            }
1944
        }
1945

1946
        if (parsed_colors >= exported_colors) {
×
1947
            sixel_helper_set_additional_message(
×
1948
                "sixel_palette_parse_pal_jasc: excess entries.");
1949
            status = SIXEL_BAD_INPUT;
×
1950
            goto cleanup;
×
1951
        }
1952

1953
        palette_buffer[parsed_colors * 3 + 0] =
×
1954
            (unsigned char)values[0];
×
1955
        palette_buffer[parsed_colors * 3 + 1] =
×
1956
            (unsigned char)values[1];
×
1957
        palette_buffer[parsed_colors * 3 + 2] =
×
1958
            (unsigned char)values[2];
×
1959
        ++parsed_colors;
×
1960
    }
1961

1962
    if (stage < 3) {
×
1963
        sixel_helper_set_additional_message(
×
1964
            "sixel_palette_parse_pal_jasc: incomplete header.");
1965
        status = SIXEL_BAD_INPUT;
×
1966
        goto cleanup;
×
1967
    }
1968
    if (parsed_colors != exported_colors) {
×
1969
        sixel_helper_set_additional_message(
×
1970
            "sixel_palette_parse_pal_jasc: color count mismatch.");
1971
        status = SIXEL_BAD_INPUT;
×
1972
        goto cleanup;
×
1973
    }
1974

1975
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
1976
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
1977
        goto cleanup;
×
1978
    }
1979
    status = sixel_palette_set_entries(palette_obj,
×
1980
                                       palette_buffer,
1981
                                       (unsigned int)exported_colors,
1982
                                       3,
1983
                                       encoder->allocator);
1984
    sixel_palette_unref(palette_obj);
×
1985
    palette_obj = NULL;
×
1986
    if (SIXEL_FAILED(status)) {
×
1987
        goto cleanup;
×
1988
    }
1989

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

UNCOV
1993
cleanup:
×
1994
    if (palette_obj != NULL) {
×
1995
        sixel_palette_unref(palette_obj);
×
1996
    }
1997
    if (SIXEL_FAILED(status) && local != NULL) {
×
1998
        sixel_dither_unref(local);
×
1999
    }
2000
    if (palette_buffer != NULL) {
×
2001
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
2002
    }
2003
    if (text != NULL) {
×
2004
        sixel_allocator_free(encoder->allocator, text);
×
2005
    }
2006
    return status;
×
2007
}
2008

2009

2010
static SIXELSTATUS
2011
sixel_palette_parse_pal_riff(unsigned char const *data,
×
2012
                             size_t size,
2013
                             sixel_encoder_t *encoder,
2014
                             sixel_dither_t **dither)
2015
{
2016
    SIXELSTATUS status;
×
2017
    size_t offset;
×
2018
    size_t chunk_size;
×
2019
    sixel_dither_t *local;
×
2020
    sixel_palette_t *palette_obj;
×
2021
    unsigned char const *chunk;
×
2022
    unsigned char *palette_buffer;
×
2023
    unsigned int entry_count;
×
2024
    unsigned int version;
×
2025
    unsigned int index;
×
2026
    size_t palette_offset;
×
2027

2028
    status = SIXEL_FALSE;
×
2029
    offset = 0u;
×
2030
    chunk_size = 0u;
×
2031
    local = NULL;
×
2032
    chunk = NULL;
×
2033
    palette_obj = NULL;
×
2034
    palette_buffer = NULL;
×
2035
    entry_count = 0u;
×
2036
    version = 0u;
×
2037
    index = 0u;
×
2038
    palette_offset = 0u;
×
2039

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

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

2071
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
2072
        sixel_helper_set_additional_message(
×
2073
            "sixel_palette_parse_pal_riff: missing data chunk.");
2074
        return SIXEL_BAD_INPUT;
×
2075
    }
2076

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

2096
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
×
2097
    if (SIXEL_FAILED(status)) {
×
2098
        return status;
2099
    }
2100
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2101
    palette_buffer = (unsigned char *)sixel_allocator_malloc(
×
2102
        encoder->allocator,
2103
        (size_t)entry_count * 3u);
×
2104
    if (palette_buffer == NULL) {
×
2105
        sixel_helper_set_additional_message(
×
2106
            "sixel_palette_parse_pal_riff: allocation failed.");
2107
        sixel_dither_unref(local);
×
2108
        return SIXEL_BAD_ALLOCATION;
×
2109
    }
2110
    palette_offset = 12u;
×
2111
    for (index = 0u; index < entry_count; ++index) {
×
2112
        palette_buffer[index * 3u + 0u] =
×
2113
            chunk[palette_offset + index * 4u + 0u];
×
2114
        palette_buffer[index * 3u + 1u] =
×
2115
            chunk[palette_offset + index * 4u + 1u];
×
2116
        palette_buffer[index * 3u + 2u] =
×
2117
            chunk[palette_offset + index * 4u + 2u];
×
2118
    }
2119

2120
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2121
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2122
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
2123
        sixel_dither_unref(local);
×
2124
        return status;
×
2125
    }
2126
    status = sixel_palette_set_entries(palette_obj,
×
2127
                                       palette_buffer,
2128
                                       (unsigned int)entry_count,
2129
                                       3,
2130
                                       encoder->allocator);
2131
    sixel_palette_unref(palette_obj);
×
2132
    palette_obj = NULL;
×
2133
    sixel_allocator_free(encoder->allocator, palette_buffer);
×
2134
    palette_buffer = NULL;
×
2135
    if (SIXEL_FAILED(status)) {
×
2136
        sixel_dither_unref(local);
×
2137
        return status;
×
2138
    }
2139

2140
    *dither = local;
×
2141
    return SIXEL_OK;
×
2142
}
2143

2144

2145
static SIXELSTATUS
2146
sixel_palette_parse_gpl(unsigned char const *data,
×
2147
                        size_t size,
2148
                        sixel_encoder_t *encoder,
2149
                        sixel_dither_t **dither)
2150
{
2151
    SIXELSTATUS status;
×
2152
    char *text;
×
2153
    size_t offset;
×
2154
    char *cursor;
×
2155
    char *line;
×
2156
    char *line_end;
×
2157
    size_t index;
×
2158
    int header_seen;
×
2159
    int parsed_colors;
×
2160
    unsigned char palette_bytes[256 * 3];
×
2161
    long component;
×
2162
    char *parse_end;
×
2163
    int value_index;
×
2164
    int values[3];
×
2165
    sixel_dither_t *local;
×
2166
    sixel_palette_t *palette_obj;
×
2167
    char tail;
×
2168

2169
    status = SIXEL_FALSE;
×
2170
    text = NULL;
×
2171
    offset = 0u;
×
2172
    cursor = NULL;
×
2173
    line = NULL;
×
2174
    line_end = NULL;
×
2175
    index = 0u;
×
2176
    header_seen = 0;
×
2177
    parsed_colors = 0;
×
2178
    component = 0;
×
2179
    parse_end = NULL;
×
2180
    value_index = 0;
×
2181
    values[0] = 0;
×
2182
    values[1] = 0;
×
2183
    values[2] = 0;
×
2184
    local = NULL;
×
2185
    palette_obj = NULL;
×
2186

2187
    if (encoder == NULL || dither == NULL) {
×
2188
        sixel_helper_set_additional_message(
×
2189
            "sixel_palette_parse_gpl: invalid argument.");
2190
        return SIXEL_BAD_ARGUMENT;
×
2191
    }
2192
    if (data == NULL || size == 0u) {
×
2193
        sixel_helper_set_additional_message(
×
2194
            "sixel_palette_parse_gpl: empty palette.");
2195
        return SIXEL_BAD_INPUT;
×
2196
    }
2197

2198
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
2199
    if (text == NULL) {
×
2200
        sixel_helper_set_additional_message(
×
2201
            "sixel_palette_parse_gpl: allocation failed.");
2202
        return SIXEL_BAD_ALLOCATION;
×
2203
    }
2204
    memcpy(text, data, size);
×
2205
    text[size] = '\0';
×
2206

2207
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2208
        offset = 3u;
×
2209
    }
2210
    cursor = text + offset;
×
2211

2212
    while (*cursor != '\0') {
×
2213
        line = cursor;
×
2214
        line_end = cursor;
2215
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2216
            ++line_end;
×
2217
        }
2218
        if (*line_end != '\0') {
×
2219
            *line_end = '\0';
×
2220
            cursor = line_end + 1;
×
2221
        } else {
2222
            cursor = line_end;
2223
        }
2224
        while (*cursor == '\n' || *cursor == '\r') {
×
2225
            ++cursor;
×
2226
        }
2227

2228
        while (*line == ' ' || *line == '\t') {
×
2229
            ++line;
×
2230
        }
2231
        index = strlen(line);
×
2232
        while (index > 0u) {
×
2233
            tail = line[index - 1];
×
2234
            if (tail != ' ' && tail != '\t') {
×
2235
                break;
2236
            }
2237
            line[index - 1] = '\0';
×
2238
            --index;
×
2239
        }
2240
        if (*line == '\0') {
×
2241
            continue;
×
2242
        }
2243
        if (*line == '#') {
×
2244
            continue;
×
2245
        }
2246
        if (strncmp(line, "Name:", 5) == 0) {
×
2247
            continue;
×
2248
        }
2249
        if (strncmp(line, "Columns:", 8) == 0) {
×
2250
            continue;
×
2251
        }
2252

2253
        if (!header_seen) {
×
2254
            if (strcmp(line, "GIMP Palette") != 0) {
×
2255
                sixel_helper_set_additional_message(
×
2256
                    "sixel_palette_parse_gpl: missing header.");
2257
                status = SIXEL_BAD_INPUT;
×
2258
                goto cleanup;
×
2259
            }
2260
            header_seen = 1;
×
2261
            continue;
×
2262
        }
2263

2264
        if (parsed_colors >= 256) {
×
2265
            sixel_helper_set_additional_message(
×
2266
                "sixel_palette_parse_gpl: too many colors.");
2267
            status = SIXEL_BAD_INPUT;
×
2268
            goto cleanup;
×
2269
        }
2270

2271
        value_index = 0;
2272
        while (value_index < 3) {
×
2273
            component = strtol(line, &parse_end, 10);
×
2274
            if (parse_end == line || component < 0L || component > 255L) {
×
2275
                sixel_helper_set_additional_message(
×
2276
                    "sixel_palette_parse_gpl: invalid component.");
2277
                status = SIXEL_BAD_INPUT;
×
2278
                goto cleanup;
×
2279
            }
2280
            values[value_index] = (int)component;
×
2281
            ++value_index;
×
2282
            line = parse_end;
×
2283
            while (*line == ' ' || *line == '\t') {
×
2284
                ++line;
×
2285
            }
2286
        }
2287

2288
        palette_bytes[parsed_colors * 3 + 0] =
×
2289
            (unsigned char)values[0];
×
2290
        palette_bytes[parsed_colors * 3 + 1] =
×
2291
            (unsigned char)values[1];
×
2292
        palette_bytes[parsed_colors * 3 + 2] =
×
2293
            (unsigned char)values[2];
×
2294
        ++parsed_colors;
×
2295
    }
2296

2297
    if (!header_seen) {
×
2298
        sixel_helper_set_additional_message(
×
2299
            "sixel_palette_parse_gpl: header missing.");
2300
        status = SIXEL_BAD_INPUT;
×
2301
        goto cleanup;
×
2302
    }
2303
    if (parsed_colors <= 0) {
×
2304
        sixel_helper_set_additional_message(
×
2305
            "sixel_palette_parse_gpl: no colors parsed.");
2306
        status = SIXEL_BAD_INPUT;
×
2307
        goto cleanup;
×
2308
    }
2309

2310
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2311
    if (SIXEL_FAILED(status)) {
×
2312
        goto cleanup;
×
2313
    }
2314
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2315
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2316
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2317
        goto cleanup;
×
2318
    }
2319
    status = sixel_palette_set_entries(palette_obj,
×
2320
                                       palette_bytes,
2321
                                       (unsigned int)parsed_colors,
2322
                                       3,
2323
                                       encoder->allocator);
2324
    sixel_palette_unref(palette_obj);
×
2325
    palette_obj = NULL;
×
2326
    if (SIXEL_FAILED(status)) {
×
2327
        goto cleanup;
×
2328
    }
2329

2330
    *dither = local;
×
2331
    status = SIXEL_OK;
×
2332

UNCOV
2333
cleanup:
×
2334
    if (palette_obj != NULL) {
×
2335
        sixel_palette_unref(palette_obj);
×
2336
    }
2337
    if (SIXEL_FAILED(status) && local != NULL) {
×
2338
        sixel_dither_unref(local);
×
2339
    }
2340
    if (text != NULL) {
×
2341
        sixel_allocator_free(encoder->allocator, text);
×
2342
    }
2343
    return status;
×
2344
}
2345

2346

2347
/*
2348
 * Palette exporters
2349
 *
2350
 *   +----------+-------------------------+
2351
 *   | format   | emission strategy       |
2352
 *   +----------+-------------------------+
2353
 *   | ACT      | fixed 256 entries + EOF |
2354
 *   | PAL JASC | textual lines           |
2355
 *   | PAL RIFF | RIFF container          |
2356
 *   | GPL      | textual lines           |
2357
 *   +----------+-------------------------+
2358
 */
2359
static SIXELSTATUS
2360
sixel_palette_write_act(FILE *stream,
×
2361
                        unsigned char const *palette,
2362
                        int exported_colors)
2363
{
2364
    SIXELSTATUS status;
×
2365
    unsigned char act_table[256 * 3];
×
2366
    unsigned char trailer[4];
×
2367
    size_t exported_bytes;
×
2368

2369
    status = SIXEL_FALSE;
×
2370
    exported_bytes = 0u;
×
2371

2372
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2373
        return SIXEL_BAD_ARGUMENT;
2374
    }
2375
    if (exported_colors > 256) {
×
2376
        exported_colors = 256;
2377
    }
2378

2379
    memset(act_table, 0, sizeof(act_table));
×
2380
    exported_bytes = (size_t)exported_colors * 3u;
×
2381
    memcpy(act_table, palette, exported_bytes);
×
2382

2383
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2384
                                 & 0xffu);
2385
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2386
    trailer[2] = 0u;
×
2387
    trailer[3] = 0u;
×
2388

2389
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2390
            != sizeof(act_table)) {
2391
        status = SIXEL_LIBC_ERROR;
×
2392
        return status;
2393
    }
2394
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2395
            != sizeof(trailer)) {
2396
        status = SIXEL_LIBC_ERROR;
×
2397
        return status;
2398
    }
2399

2400
    return SIXEL_OK;
2401
}
2402

2403

2404
static SIXELSTATUS
2405
sixel_palette_write_pal_jasc(FILE *stream,
×
2406
                             unsigned char const *palette,
2407
                             int exported_colors)
2408
{
2409
    int index;
×
2410

2411
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2412
        return SIXEL_BAD_ARGUMENT;
2413
    }
2414
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
2415
        return SIXEL_LIBC_ERROR;
2416
    }
2417
    for (index = 0; index < exported_colors; ++index) {
×
2418
        if (fprintf(stream, "%d %d %d\n",
×
2419
                    (int)palette[index * 3 + 0],
×
2420
                    (int)palette[index * 3 + 1],
×
2421
                    (int)palette[index * 3 + 2]) < 0) {
×
2422
            return SIXEL_LIBC_ERROR;
2423
        }
2424
    }
2425
    return SIXEL_OK;
2426
}
2427

2428

2429
static SIXELSTATUS
2430
sixel_palette_write_pal_riff(FILE *stream,
×
2431
                             unsigned char const *palette,
2432
                             int exported_colors)
2433
{
2434
    unsigned char header[12];
×
2435
    unsigned char chunk[8];
×
2436
    unsigned char log_palette[4 + 256 * 4];
×
2437
    unsigned int data_size;
×
2438
    unsigned int riff_size;
×
2439
    int index;
×
2440

2441
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2442
        return SIXEL_BAD_ARGUMENT;
2443
    }
2444
    if (exported_colors > 256) {
×
2445
        exported_colors = 256;
2446
    }
2447

2448
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2449
    riff_size = 4u + 8u + data_size;
×
2450

2451
    memcpy(header, "RIFF", 4);
×
2452
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2453
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2454
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2455
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2456
    memcpy(header + 8, "PAL ", 4);
×
2457

2458
    memcpy(chunk, "data", 4);
×
2459
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2460
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2461
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2462
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2463

2464
    memset(log_palette, 0, sizeof(log_palette));
×
2465
    log_palette[0] = 0x00;
×
2466
    log_palette[1] = 0x03;
×
2467
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
2468
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
2469
    for (index = 0; index < exported_colors; ++index) {
×
2470
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
2471
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
2472
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
2473
        log_palette[4 + index * 4 + 3] = 0u;
×
2474
    }
2475

2476
    if (fwrite(header, 1, sizeof(header), stream)
×
2477
            != sizeof(header)) {
2478
        return SIXEL_LIBC_ERROR;
2479
    }
2480
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2481
        return SIXEL_LIBC_ERROR;
2482
    }
2483
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2484
            != (size_t)data_size) {
2485
        return SIXEL_LIBC_ERROR;
2486
    }
2487
    return SIXEL_OK;
2488
}
2489

2490

2491
static SIXELSTATUS
2492
sixel_palette_write_gpl(FILE *stream,
×
2493
                        unsigned char const *palette,
2494
                        int exported_colors)
2495
{
2496
    int index;
×
2497

2498
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2499
        return SIXEL_BAD_ARGUMENT;
2500
    }
2501
    if (fprintf(stream, "GIMP Palette\n") < 0) {
×
2502
        return SIXEL_LIBC_ERROR;
2503
    }
2504
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
×
2505
        return SIXEL_LIBC_ERROR;
2506
    }
2507
    if (fprintf(stream, "Columns: 16\n") < 0) {
×
2508
        return SIXEL_LIBC_ERROR;
2509
    }
2510
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
×
2511
        return SIXEL_LIBC_ERROR;
2512
    }
2513
    for (index = 0; index < exported_colors; ++index) {
×
2514
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
×
2515
                    (int)palette[index * 3 + 0],
×
2516
                    (int)palette[index * 3 + 1],
×
2517
                    (int)palette[index * 3 + 2],
×
2518
                    index) < 0) {
2519
            return SIXEL_LIBC_ERROR;
2520
        }
2521
    }
2522
    return SIXEL_OK;
2523
}
2524

2525

2526
/* create palette from specified map file */
2527
static SIXELSTATUS
2528
sixel_prepare_specified_palette(
21✔
2529
    sixel_dither_t  /* out */   **dither,
2530
    sixel_encoder_t /* in */    *encoder)
2531
{
2532
    SIXELSTATUS status;
21✔
2533
    sixel_callback_context_for_mapfile_t callback_context;
21✔
2534
    sixel_loader_t *loader;
21✔
2535
    int fstatic;
21✔
2536
    int fuse_palette;
21✔
2537
    int reqcolors;
21✔
2538
    int loop_override;
21✔
2539
    char const *path;
21✔
2540
    sixel_palette_format_t format_hint;
21✔
2541
    sixel_palette_format_t format_ext;
21✔
2542
    sixel_palette_format_t format_final;
21✔
2543
    sixel_palette_format_t format_detected;
21✔
2544
    FILE *stream;
21✔
2545
    int close_stream;
21✔
2546
    unsigned char *buffer;
21✔
2547
    size_t buffer_size;
21✔
2548
    int palette_request;
21✔
2549
    int need_detection;
21✔
2550
    int treat_as_image;
21✔
2551
    int path_has_extension;
21✔
2552
    char mapfile_message[256];
21✔
2553

2554
    status = SIXEL_FALSE;
21✔
2555
    loader = NULL;
21✔
2556
    fstatic = 1;
21✔
2557
    fuse_palette = 1;
21✔
2558
    reqcolors = SIXEL_PALETTE_MAX;
21✔
2559
    loop_override = SIXEL_LOOP_DISABLE;
21✔
2560
    path = NULL;
21✔
2561
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
2562
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
21✔
2563
    format_final = SIXEL_PALETTE_FORMAT_NONE;
21✔
2564
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
21✔
2565
    stream = NULL;
21✔
2566
    close_stream = 0;
21✔
2567
    buffer = NULL;
21✔
2568
    buffer_size = 0u;
21✔
2569
    palette_request = 0;
21✔
2570
    need_detection = 0;
21✔
2571
    treat_as_image = 0;
21✔
2572
    path_has_extension = 0;
21✔
2573
    mapfile_message[0] = '\0';
21✔
2574

2575
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
21!
2576
        sixel_helper_set_additional_message(
×
2577
            "sixel_prepare_specified_palette: invalid mapfile path.");
2578
        return SIXEL_BAD_ARGUMENT;
×
2579
    }
2580

2581
    sixel_encoder_log_stage(encoder,
21✔
2582
                            NULL,
2583
                            "palette",
2584
                            "worker",
2585
                            "start",
2586
                            "mapfile=%s",
2587
                            encoder->mapfile);
2588

2589
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
21✔
2590
    if (path == NULL || *path == '\0') {
21!
2591
        sixel_helper_set_additional_message(
×
2592
            "sixel_prepare_specified_palette: empty mapfile path.");
2593
        return SIXEL_BAD_ARGUMENT;
×
2594
    }
2595

2596
    format_ext = sixel_palette_format_from_extension(path);
21✔
2597
    path_has_extension = sixel_path_has_any_extension(path);
21✔
2598

2599
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
21!
2600
        palette_request = 1;
2601
        format_final = format_hint;
2602
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
21!
2603
        palette_request = 1;
2604
        format_final = format_ext;
2605
    } else if (!path_has_extension) {
21!
2606
        palette_request = 1;
2607
        need_detection = 1;
2608
    } else {
2609
        treat_as_image = 1;
21✔
2610
    }
2611

2612
    if (palette_request) {
21!
2613
        status = sixel_palette_open_read(path, &stream, &close_stream);
×
2614
        if (SIXEL_FAILED(status)) {
×
2615
            goto palette_cleanup;
×
2616
        }
2617
        status = sixel_palette_read_stream(stream,
×
2618
                                           encoder->allocator,
2619
                                           &buffer,
2620
                                           &buffer_size);
2621
        if (close_stream) {
×
2622
            sixel_palette_close_stream(stream, close_stream);
×
2623
            stream = NULL;
×
2624
            close_stream = 0;
×
2625
        }
2626
        if (SIXEL_FAILED(status)) {
×
2627
            goto palette_cleanup;
×
2628
        }
2629
        if (buffer_size == 0u) {
×
2630
            sixel_compat_snprintf(mapfile_message,
×
2631
                                  sizeof(mapfile_message),
2632
                                  "sixel_prepare_specified_palette: "
2633
                                  "mapfile \"%s\" is empty.",
2634
                                  path != NULL ? path : "");
×
2635
            sixel_helper_set_additional_message(mapfile_message);
×
2636
            status = SIXEL_BAD_INPUT;
×
2637
            goto palette_cleanup;
×
2638
        }
2639

2640
        if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
2641
            format_detected = sixel_palette_guess_format(buffer,
×
2642
                                                         buffer_size);
2643
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2644
                sixel_helper_set_additional_message(
×
2645
                    "sixel_prepare_specified_palette: "
2646
                    "unable to detect palette format.");
2647
                status = SIXEL_BAD_INPUT;
×
2648
                goto palette_cleanup;
×
2649
            }
2650
            format_final = format_detected;
2651
        } else if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
2652
            format_detected = sixel_palette_guess_format(buffer,
×
2653
                                                         buffer_size);
2654
            if (format_detected == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
2655
                    format_detected == SIXEL_PALETTE_FORMAT_PAL_RIFF) {
2656
                format_final = format_detected;
2657
            } else {
2658
                sixel_helper_set_additional_message(
×
2659
                    "sixel_prepare_specified_palette: "
2660
                    "ambiguous .pal content.");
2661
                status = SIXEL_BAD_INPUT;
×
2662
                goto palette_cleanup;
×
2663
            }
2664
        } else if (need_detection) {
×
2665
            format_detected = sixel_palette_guess_format(buffer,
×
2666
                                                         buffer_size);
2667
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2668
                sixel_helper_set_additional_message(
×
2669
                    "sixel_prepare_specified_palette: "
2670
                    "unable to detect palette format.");
2671
                status = SIXEL_BAD_INPUT;
×
2672
                goto palette_cleanup;
×
2673
            }
2674
            format_final = format_detected;
2675
        }
2676

2677
        switch (format_final) {
×
UNCOV
2678
        case SIXEL_PALETTE_FORMAT_ACT:
×
2679
            status = sixel_palette_parse_act(buffer,
×
2680
                                             buffer_size,
2681
                                             encoder,
2682
                                             dither);
2683
            break;
×
UNCOV
2684
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
×
2685
            status = sixel_palette_parse_pal_jasc(buffer,
×
2686
                                                  buffer_size,
2687
                                                  encoder,
2688
                                                  dither);
2689
            break;
×
UNCOV
2690
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
×
2691
            status = sixel_palette_parse_pal_riff(buffer,
×
2692
                                                  buffer_size,
2693
                                                  encoder,
2694
                                                  dither);
2695
            break;
×
UNCOV
2696
        case SIXEL_PALETTE_FORMAT_GPL:
×
2697
            status = sixel_palette_parse_gpl(buffer,
×
2698
                                             buffer_size,
2699
                                             encoder,
2700
                                             dither);
2701
            break;
×
UNCOV
2702
        default:
×
2703
            sixel_helper_set_additional_message(
×
2704
                "sixel_prepare_specified_palette: "
2705
                "unsupported palette format.");
2706
            status = SIXEL_BAD_INPUT;
×
2707
            break;
×
2708
        }
2709

UNCOV
2710
palette_cleanup:
×
2711
        if (buffer != NULL) {
×
2712
            sixel_allocator_free(encoder->allocator, buffer);
×
2713
            buffer = NULL;
×
2714
        }
2715
        if (stream != NULL) {
×
2716
            sixel_palette_close_stream(stream, close_stream);
×
2717
            stream = NULL;
×
2718
        }
2719
        if (SIXEL_SUCCEEDED(status)) {
×
2720
            return status;
2721
        }
2722
        if (!treat_as_image) {
×
2723
            return status;
×
2724
        }
2725
    }
2726

2727
    callback_context.reqcolors = encoder->reqcolors;
21✔
2728
    callback_context.dither = NULL;
21✔
2729
    callback_context.allocator = encoder->allocator;
21✔
2730
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
2731
    callback_context.lut_policy = encoder->lut_policy;
21✔
2732
    callback_context.prefer_float32 = encoder->prefer_float32;
21✔
2733

2734
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
2735
    sixel_helper_set_thumbnail_size_hint(
21✔
2736
        sixel_encoder_thumbnail_hint(encoder));
2737
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
2738
    if (SIXEL_FAILED(status)) {
21!
2739
        goto end_loader;
×
2740
    }
2741

2742
    status = sixel_loader_setopt(loader,
21✔
2743
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
2744
                                 &fstatic);
2745
    if (SIXEL_FAILED(status)) {
21!
2746
        goto end_loader;
×
2747
    }
2748

2749
    status = sixel_loader_setopt(loader,
21✔
2750
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2751
                                 &fuse_palette);
2752
    if (SIXEL_FAILED(status)) {
21!
2753
        goto end_loader;
×
2754
    }
2755

2756
    status = sixel_loader_setopt(loader,
21✔
2757
                                 SIXEL_LOADER_OPTION_REQCOLORS,
2758
                                 &reqcolors);
2759
    if (SIXEL_FAILED(status)) {
21!
2760
        goto end_loader;
×
2761
    }
2762

2763
    status = sixel_loader_setopt(loader,
42✔
2764
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2765
                                 encoder->bgcolor);
21✔
2766
    if (SIXEL_FAILED(status)) {
21!
2767
        goto end_loader;
×
2768
    }
2769

2770
    status = sixel_loader_setopt(loader,
21✔
2771
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
2772
                                 &loop_override);
2773
    if (SIXEL_FAILED(status)) {
21!
2774
        goto end_loader;
×
2775
    }
2776

2777
    status = sixel_loader_setopt(loader,
42✔
2778
                                 SIXEL_LOADER_OPTION_INSECURE,
2779
                                 &encoder->finsecure);
21✔
2780
    if (SIXEL_FAILED(status)) {
21!
2781
        goto end_loader;
×
2782
    }
2783

2784
    status = sixel_loader_setopt(loader,
42✔
2785
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2786
                                 encoder->cancel_flag);
21✔
2787
    if (SIXEL_FAILED(status)) {
21!
2788
        goto end_loader;
×
2789
    }
2790

2791
    status = sixel_loader_setopt(loader,
42✔
2792
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
2793
                                 encoder->loader_order);
21✔
2794
    if (SIXEL_FAILED(status)) {
21!
2795
        goto end_loader;
×
2796
    }
2797

2798
    status = sixel_loader_setopt(loader,
21✔
2799
                                 SIXEL_LOADER_OPTION_CONTEXT,
2800
                                 &callback_context);
2801
    if (SIXEL_FAILED(status)) {
21!
2802
        goto end_loader;
×
2803
    }
2804

2805
    status = sixel_loader_load_file(loader,
21✔
2806
                                    encoder->mapfile,
21✔
2807
                                    load_image_callback_for_palette);
2808
    if (status != SIXEL_OK) {
21!
2809
        goto end_loader;
2810
    }
2811

2812
end_loader:
21✔
2813
    sixel_loader_unref(loader);
21✔
2814

2815
    if (status != SIXEL_OK) {
21!
2816
        return status;
2817
    }
2818

2819
    if (! callback_context.dither) {
21!
2820
        sixel_compat_snprintf(mapfile_message,
×
2821
                              sizeof(mapfile_message),
2822
                              "sixel_prepare_specified_palette() failed.\n"
2823
                              "reason: mapfile \"%s\" is empty.",
2824
                              encoder->mapfile != NULL
×
2825
                                ? encoder->mapfile
2826
                                : "");
2827
        sixel_helper_set_additional_message(mapfile_message);
×
2828
        return SIXEL_BAD_INPUT;
×
2829
    }
2830

2831
    *dither = callback_context.dither;
21✔
2832

2833
    sixel_encoder_log_stage(encoder,
21✔
2834
                            NULL,
2835
                            "palette",
2836
                            "worker",
2837
                            "finish",
2838
                            "mapfile=%s format=%d",
2839
                            encoder->mapfile,
2840
                            format_final);
2841

2842
    return status;
21✔
2843
}
2844

2845

2846
/* create dither object from a frame */
2847
static SIXELSTATUS
2848
sixel_encoder_prepare_palette(
519✔
2849
    sixel_encoder_t *encoder,  /* encoder object */
2850
    sixel_frame_t   *frame,    /* input frame object */
2851
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2852
{
2853
    SIXELSTATUS status = SIXEL_FALSE;
519✔
2854
    int histogram_colors;
519✔
2855
    sixel_assessment_t *assessment;
519✔
2856
    int promoted_stage;
519✔
2857

2858
    assessment = NULL;
519✔
2859
    promoted_stage = 0;
519✔
2860
    if (encoder != NULL) {
519!
2861
        assessment = encoder->assessment_observer;
519✔
2862
    }
2863

2864
    switch (encoder->color_option) {
519!
2865
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
36✔
2866
        if (encoder->dither_cache) {
36!
2867
            *dither = encoder->dither_cache;
×
2868
            status = SIXEL_OK;
×
2869
        } else {
2870
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
2871
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
2872
        }
2873
        goto end;
36✔
2874
    case SIXEL_COLOR_OPTION_MONOCHROME:
12✔
2875
        if (encoder->dither_cache) {
12!
2876
            *dither = encoder->dither_cache;
×
2877
            status = SIXEL_OK;
×
2878
        } else {
2879
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
2880
        }
2881
        goto end;
12✔
2882
    case SIXEL_COLOR_OPTION_MAPFILE:
21✔
2883
        if (encoder->dither_cache) {
21!
2884
            *dither = encoder->dither_cache;
×
2885
            status = SIXEL_OK;
×
2886
        } else {
2887
            status = sixel_prepare_specified_palette(dither, encoder);
21✔
2888
        }
2889
        goto end;
21✔
2890
    case SIXEL_COLOR_OPTION_BUILTIN:
27✔
2891
        if (encoder->dither_cache) {
27!
2892
            *dither = encoder->dither_cache;
×
2893
            status = SIXEL_OK;
×
2894
        } else {
2895
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
2896
        }
2897
        goto end;
27✔
2898
    case SIXEL_COLOR_OPTION_DEFAULT:
2899
    default:
2900
        break;
423✔
2901
    }
2902

2903
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
423✔
2904
        if (!sixel_frame_get_palette(frame)) {
228!
2905
            status = SIXEL_LOGIC_ERROR;
×
2906
            goto end;
×
2907
        }
2908
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
228✔
2909
                                  encoder->allocator);
2910
        if (SIXEL_FAILED(status)) {
228!
2911
            goto end;
×
2912
        }
2913
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
228✔
2914
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
228✔
2915
        if (sixel_frame_get_transparent(frame) != (-1)) {
228!
2916
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2917
        }
2918
        if (*dither && encoder->dither_cache) {
228!
2919
            sixel_dither_unref(encoder->dither_cache);
×
2920
        }
2921
        goto end;
228✔
2922
    }
2923

2924
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
195!
UNCOV
2925
        switch (sixel_frame_get_pixelformat(frame)) {
×
UNCOV
2926
        case SIXEL_PIXELFORMAT_G1:
×
2927
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2928
            break;
×
UNCOV
2929
        case SIXEL_PIXELFORMAT_G2:
×
2930
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2931
            break;
×
UNCOV
2932
        case SIXEL_PIXELFORMAT_G4:
×
2933
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2934
            break;
×
UNCOV
2935
        case SIXEL_PIXELFORMAT_G8:
×
UNCOV
2936
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
UNCOV
2937
            break;
×
UNCOV
2938
        default:
×
2939
            *dither = NULL;
×
2940
            status = SIXEL_LOGIC_ERROR;
×
2941
            goto end;
×
2942
        }
UNCOV
2943
        if (*dither && encoder->dither_cache) {
×
2944
            sixel_dither_unref(encoder->dither_cache);
×
2945
        }
UNCOV
2946
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
UNCOV
2947
        status = SIXEL_OK;
×
UNCOV
2948
        goto end;
×
2949
    }
2950

2951
    if (encoder->dither_cache) {
195!
2952
        sixel_dither_unref(encoder->dither_cache);
×
2953
    }
2954
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
195✔
2955
    if (SIXEL_FAILED(status)) {
195!
2956
        goto end;
×
2957
    }
2958

2959
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
195✔
2960
    sixel_dither_set_sixel_reversible(*dither,
195✔
2961
                                      encoder->sixel_reversible);
2962
    sixel_dither_set_final_merge(*dither, encoder->final_merge_mode);
195✔
2963
    (*dither)->quantize_model = encoder->quantize_model;
195✔
2964

2965
    status = sixel_dither_initialize(*dither,
195✔
2966
                                     sixel_frame_get_pixels(frame),
2967
                                     sixel_frame_get_width(frame),
2968
                                     sixel_frame_get_height(frame),
2969
                                     sixel_frame_get_pixelformat(frame),
2970
                                     encoder->method_for_largest,
2971
                                     encoder->method_for_rep,
2972
                                     encoder->quality_mode);
2973
    if (SIXEL_FAILED(status)) {
195!
2974
        sixel_dither_unref(*dither);
×
2975
        goto end;
×
2976
    }
2977

2978
    if (assessment != NULL && promoted_stage == 0) {
195!
2979
        sixel_assessment_stage_transition(
3✔
2980
            assessment,
2981
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2982
        promoted_stage = 1;
3✔
2983
    }
2984

2985
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
195✔
2986
    if (histogram_colors <= encoder->reqcolors) {
195✔
2987
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
159✔
2988
    }
2989
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
195✔
2990

2991
    status = SIXEL_OK;
195✔
2992

2993
end:
519✔
2994
    if (assessment != NULL && promoted_stage == 0) {
519!
2995
        sixel_assessment_stage_transition(
×
2996
            assessment,
2997
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2998
        promoted_stage = 1;
×
2999
    }
3000
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
519!
3001
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
519✔
3002
        /* pass down the user's demand for an exact palette size */
3003
        (*dither)->force_palette = encoder->force_palette;
519✔
3004
    }
3005
    return status;
519✔
3006
}
3007

3008

3009
/* resize a frame with settings of specified encoder object */
3010
static SIXELSTATUS
3011
sixel_encoder_do_resize(
525✔
3012
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3013
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3014
{
3015
    SIXELSTATUS status = SIXEL_FALSE;
525✔
3016
    int src_width;
525✔
3017
    int src_height;
525✔
3018
    int dst_width;
525✔
3019
    int dst_height;
525✔
3020
    int use_float_resize;
525✔
3021

3022
    /* get frame width and height */
3023
    src_width = sixel_frame_get_width(frame);
525✔
3024
    src_height = sixel_frame_get_height(frame);
525✔
3025

3026
    if (src_width < 1) {
525✔
3027
         sixel_helper_set_additional_message(
6✔
3028
             "sixel_encoder_do_resize: "
3029
             "detected a frame with a non-positive width.");
3030
        return SIXEL_BAD_ARGUMENT;
6✔
3031
    }
3032

3033
    if (src_height < 1) {
519!
3034
         sixel_helper_set_additional_message(
×
3035
             "sixel_encoder_do_resize: "
3036
             "detected a frame with a non-positive height.");
3037
        return SIXEL_BAD_ARGUMENT;
×
3038
    }
3039

3040
    /* settings around scaling */
3041
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
519✔
3042
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
519✔
3043

3044
    use_float_resize = 0;
519✔
3045

3046
    /* if the encoder has percentwidth or percentheight property,
3047
       convert them to pixelwidth / pixelheight */
3048
    if (encoder->percentwidth > 0) {
519✔
3049
        dst_width = src_width * encoder->percentwidth / 100;
12✔
3050
    }
3051
    if (encoder->percentheight > 0) {
519✔
3052
        dst_height = src_height * encoder->percentheight / 100;
9✔
3053
    }
3054

3055
    /* if only either width or height is set, set also the other
3056
       to retain frame aspect ratio */
3057
    if (dst_width > 0 && dst_height <= 0) {
519✔
3058
        dst_height = src_height * dst_width / src_width;
42✔
3059
    }
3060
    if (dst_height > 0 && dst_width <= 0) {
519✔
3061
        dst_width = src_width * dst_height / src_height;
33✔
3062
    }
3063

3064
    sixel_encoder_log_stage(encoder,
519✔
3065
                            frame,
3066
                            "scale",
3067
                            "worker",
3068
                            "start",
3069
                            "src=%dx%d dst=%dx%d resample=%d",
3070
                            src_width,
3071
                            src_height,
3072
                            dst_width,
3073
                            dst_height,
3074
                            encoder->method_for_resampling);
3075

3076
    /* do resize */
3077
    if (dst_width > 0 && dst_height > 0) {
519!
3078
        if (encoder->method_for_resampling != SIXEL_RES_NEAREST) {
93✔
3079
            if (SIXEL_PIXELFORMAT_IS_FLOAT32(
75!
3080
                    encoder->working_colorspace) != 0) {
3081
                use_float_resize = 1;
×
3082
            }
3083
            if (encoder->prefer_float32 != 0) {
75!
3084
                use_float_resize = 1;
3085
            }
3086
        }
3087

3088
        if (use_float_resize != 0) {
75!
3089
            status = sixel_frame_resize_float32(
×
3090
                frame,
3091
                dst_width,
3092
                dst_height,
3093
                encoder->method_for_resampling);
3094
        } else {
3095
            status = sixel_frame_resize(
93✔
3096
                frame,
3097
                dst_width,
3098
                dst_height,
3099
                encoder->method_for_resampling);
3100
        }
3101
        if (SIXEL_FAILED(status)) {
93!
3102
            goto end;
×
3103
        }
3104
    }
3105

3106
    sixel_encoder_log_stage(encoder,
519✔
3107
                            frame,
3108
                            "scale",
3109
                            "worker",
3110
                            "finish",
3111
                            "result=%dx%d",
3112
                            sixel_frame_get_width(frame),
3113
                            sixel_frame_get_height(frame));
3114

3115
    /* success */
3116
    status = SIXEL_OK;
519✔
3117

3118
end:
3119
    return status;
3120
}
3121

3122

3123
/* clip a frame with settings of specified encoder object */
3124
static SIXELSTATUS
3125
sixel_encoder_do_clip(
519✔
3126
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3127
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3128
{
3129
    SIXELSTATUS status = SIXEL_FALSE;
519✔
3130
    int src_width;
519✔
3131
    int src_height;
519✔
3132
    int clip_x;
519✔
3133
    int clip_y;
519✔
3134
    int clip_w;
519✔
3135
    int clip_h;
519✔
3136

3137
    /* get frame width and height */
3138
    src_width = sixel_frame_get_width(frame);
519✔
3139
    src_height = sixel_frame_get_height(frame);
519✔
3140

3141
    /* settings around clipping */
3142
    clip_x = encoder->clipx;
519✔
3143
    clip_y = encoder->clipy;
519✔
3144
    clip_w = encoder->clipwidth;
519✔
3145
    clip_h = encoder->clipheight;
519✔
3146

3147
    /* adjust clipping width with comparing it to frame width */
3148
    if (clip_w + clip_x > src_width) {
519✔
3149
        if (clip_x > src_width) {
6✔
3150
            clip_w = 0;
3151
        } else {
3152
            clip_w = src_width - clip_x;
3✔
3153
        }
3154
    }
3155

3156
    /* adjust clipping height with comparing it to frame height */
3157
    if (clip_h + clip_y > src_height) {
519✔
3158
        if (clip_y > src_height) {
6✔
3159
            clip_h = 0;
3160
        } else {
3161
            clip_h = src_height - clip_y;
3✔
3162
        }
3163
    }
3164

3165
    sixel_encoder_log_stage(encoder,
519✔
3166
                            frame,
3167
                            "crop",
3168
                            "worker",
3169
                            "start",
3170
                            "src=%dx%d region=%dx%d@%d,%d",
3171
                            src_width,
3172
                            src_height,
3173
                            clip_w,
3174
                            clip_h,
3175
                            clip_x,
3176
                            clip_y);
3177

3178
    /* do clipping */
3179
    if (clip_w > 0 && clip_h > 0) {
519!
3180
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
12✔
3181
        if (SIXEL_FAILED(status)) {
12!
3182
            goto end;
×
3183
        }
3184
    }
3185

3186
    sixel_encoder_log_stage(encoder,
519✔
3187
                            frame,
3188
                            "crop",
3189
                            "worker",
3190
                            "finish",
3191
                            "result=%dx%d",
3192
                            sixel_frame_get_width(frame),
3193
                            sixel_frame_get_height(frame));
3194

3195
    /* success */
3196
    status = SIXEL_OK;
519✔
3197

3198
end:
519✔
3199
    return status;
519✔
3200
}
3201

3202

3203
static void
3204
sixel_debug_print_palette(
3✔
3205
    sixel_dither_t /* in */ *dither /* dithering object */
3206
)
3207
{
3208
    sixel_palette_t *palette_obj;
3✔
3209
    unsigned char *palette_copy;
3✔
3210
    size_t palette_count;
3✔
3211
    int i;
3✔
3212

3213
    palette_obj = NULL;
3✔
3214
    palette_copy = NULL;
3✔
3215
    palette_count = 0U;
3✔
3216
    if (dither == NULL) {
3!
3217
        return;
×
3218
    }
3219

3220
    if (SIXEL_FAILED(
3!
3221
            sixel_dither_get_quantized_palette(dither, &palette_obj))
3222
            || palette_obj == NULL) {
3!
3223
        return;
3224
    }
3225
    if (SIXEL_FAILED(sixel_palette_copy_entries_8bit(
3!
3226
            palette_obj,
3227
            &palette_copy,
3228
            &palette_count,
3229
            SIXEL_PIXELFORMAT_RGB888,
3230
            dither->allocator))
3231
            || palette_copy == NULL) {
3!
3232
        sixel_palette_unref(palette_obj);
×
3233
        return;
×
3234
    }
3235
    sixel_palette_unref(palette_obj);
3✔
3236

3237
    fprintf(stderr, "palette:\n");
3✔
3238
    for (i = 0; i < (int)palette_count;
51✔
3239
            ++i) {
45✔
3240
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
45✔
3241
                palette_copy[i * 3 + 0],
45✔
3242
                palette_copy[i * 3 + 1],
45✔
3243
                palette_copy[i * 3 + 2]);
45✔
3244
    }
3245
    sixel_allocator_free(dither->allocator, palette_copy);
3✔
3246
}
1!
3247

3248

3249
static SIXELSTATUS
3250
sixel_encoder_output_without_macro(
435✔
3251
    sixel_frame_t       /* in */ *frame,
3252
    sixel_dither_t      /* in */ *dither,
3253
    sixel_output_t      /* in */ *output,
3254
    sixel_encoder_t     /* in */ *encoder)
3255
{
3256
    SIXELSTATUS status = SIXEL_OK;
435✔
3257
    static unsigned char *p;
435✔
3258
    int depth;
435✔
3259
    enum { message_buffer_size = 2048 };
435✔
3260
    char message[message_buffer_size];
435✔
3261
    int nwrite;
435✔
3262
    int dulation;
435✔
3263
    int delay;
435✔
3264
    unsigned int remaining_delay;
435✔
3265
    unsigned char *pixbuf;
435✔
3266
    int width;
435✔
3267
    int height;
435✔
3268
    int pixelformat = 0;
435✔
3269
    size_t size;
435✔
3270
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
435✔
3271
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3272
    sixel_clock_t last_clock;
435✔
3273
#endif
3274

3275
    if (encoder == NULL) {
435!
3276
        sixel_helper_set_additional_message(
×
3277
            "sixel_encoder_output_without_macro: encoder object is null.");
3278
        status = SIXEL_BAD_ARGUMENT;
×
3279
        goto end;
×
3280
    }
3281

3282
    if (encoder->assessment_observer != NULL) {
435✔
3283
        sixel_assessment_stage_transition(
3✔
3284
            encoder->assessment_observer,
3285
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3286
    }
3287

3288
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
435✔
3289
        if (encoder->force_palette) {
339!
3290
            /* keep every slot when the user forced the palette size */
3291
            sixel_dither_set_optimize_palette(dither, 0);
×
3292
        } else {
3293
            sixel_dither_set_optimize_palette(dither, 1);
339✔
3294
        }
3295
    }
3296

3297
    pixelformat = sixel_frame_get_pixelformat(frame);
435✔
3298
    frame_colorspace = sixel_frame_get_colorspace(frame);
435✔
3299
    output->pixelformat = pixelformat;
435✔
3300
    output->source_colorspace = frame_colorspace;
435✔
3301
    output->colorspace = encoder->output_colorspace;
435✔
3302
    sixel_dither_set_pixelformat(dither, pixelformat);
435✔
3303
    depth = sixel_helper_compute_depth(pixelformat);
435✔
3304
    if (depth < 0) {
435!
3305
        status = SIXEL_LOGIC_ERROR;
×
3306
        nwrite = sixel_compat_snprintf(
×
3307
            message,
3308
            sizeof(message),
3309
            "sixel_encoder_output_without_macro: "
3310
            "sixel_helper_compute_depth(%08x) failed.",
3311
            pixelformat);
3312
        if (nwrite > 0) {
×
3313
            sixel_helper_set_additional_message(message);
×
3314
        }
3315
        goto end;
×
3316
    }
3317

3318
    width = sixel_frame_get_width(frame);
435✔
3319
    height = sixel_frame_get_height(frame);
435✔
3320
    size = (size_t)(width * height * depth);
435✔
3321

3322
    sixel_encoder_log_stage(encoder,
435✔
3323
                            frame,
3324
                            "encode",
3325
                            "worker",
3326
                            "start",
3327
                            "size=%dx%d fmt=%08x dst_cs=%d",
3328
                            width,
3329
                            height,
3330
                            pixelformat,
3331
                            output->colorspace);
3332

3333
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
435✔
3334
    if (p == NULL) {
435!
3335
        sixel_helper_set_additional_message(
×
3336
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
3337
        status = SIXEL_BAD_ALLOCATION;
×
3338
        goto end;
×
3339
    }
3340
#if defined(HAVE_CLOCK)
3341
    if (output->last_clock == 0) {
435!
3342
        output->last_clock = clock();
435✔
3343
    }
3344
#elif defined(HAVE_CLOCK_WIN)
3345
    if (output->last_clock == 0) {
3346
        output->last_clock = clock_win();
3347
    }
3348
#endif
3349
    delay = sixel_frame_get_delay(frame);
435✔
3350
    if (delay > 0 && !encoder->fignore_delay) {
435✔
3351
# if defined(HAVE_CLOCK)
3352
        last_clock = clock();
3✔
3353
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3354
#  if defined(__APPLE__)
3355
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3356
                          / 100000);
3357
#  else
3358
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3✔
3359
                          / CLOCKS_PER_SEC);
3360
#  endif
3361
        output->last_clock = last_clock;
3✔
3362
# elif defined(HAVE_CLOCK_WIN)
3363
        last_clock = clock_win();
3364
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3365
                          / CLOCKS_PER_SEC);
3366
        output->last_clock = last_clock;
3367
# else
3368
        dulation = 0;
3369
# endif
3370
        if (dulation < 1000 * 10 * delay) {
3!
3371
            remaining_delay =
3✔
3372
                (unsigned int)(1000 * 10 * delay - dulation);
3✔
3373
            sixel_sleep(remaining_delay);
3✔
3374
        }
3375
    }
3376

3377
    pixbuf = sixel_frame_get_pixels(frame);
435✔
3378
    memcpy(p, pixbuf, (size_t)(width * height * depth));
435!
3379

3380
    if (encoder->cancel_flag && *encoder->cancel_flag) {
435!
3381
        goto end;
×
3382
    }
3383

3384
    if (encoder->capture_quantized) {
435✔
3385
        status = sixel_encoder_capture_quantized(encoder,
3✔
3386
                                                 dither,
3387
                                                 p,
3388
                                                 size,
3389
                                                 width,
3390
                                                 height,
3391
                                                 pixelformat,
3392
                                                 frame_colorspace,
3393
                                                 output->colorspace);
3394
        if (SIXEL_FAILED(status)) {
3!
3395
            goto end;
×
3396
        }
3397
    }
3398

3399
    if (encoder->assessment_observer != NULL) {
435✔
3400
        sixel_assessment_stage_transition(
3✔
3401
            encoder->assessment_observer,
3402
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3403
    }
3404
    status = sixel_encode(p, width, height, depth, dither, output);
435✔
3405
    if (encoder->assessment_observer != NULL) {
435✔
3406
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
3407
    }
3408
    if (status != SIXEL_OK) {
435!
3409
        goto end;
×
3410
    }
3411

3412
end:
435✔
UNCOV
3413
    if (SIXEL_SUCCEEDED(status)) {
×
3414
        sixel_encoder_log_stage(encoder,
435✔
3415
                                frame,
3416
                                "encode",
3417
                                "worker",
3418
                                "finish",
3419
                                "size=%dx%d",
3420
                                width,
3421
                                height);
3422
    }
3423
    output->pixelformat = pixelformat;
435✔
3424
    output->source_colorspace = frame_colorspace;
435✔
3425
    sixel_allocator_free(encoder->allocator, p);
435✔
3426

3427
    return status;
435✔
3428
}
3429

3430

3431
static SIXELSTATUS
3432
sixel_encoder_output_with_macro(
84✔
3433
    sixel_frame_t   /* in */ *frame,
3434
    sixel_dither_t  /* in */ *dither,
3435
    sixel_output_t  /* in */ *output,
3436
    sixel_encoder_t /* in */ *encoder)
3437
{
3438
    SIXELSTATUS status = SIXEL_OK;
84✔
3439
    enum { message_buffer_size = 256 };
84✔
3440
    char buffer[message_buffer_size];
84✔
3441
    int nwrite;
84✔
3442
    int dulation;
84✔
3443
    int delay;
84✔
3444
    unsigned int remaining_delay;
84✔
3445
    int width;
84✔
3446
    int height;
84✔
3447
    int pixelformat;
84✔
3448
    int depth;
84✔
3449
    size_t size = 0;
84✔
3450
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
84✔
3451
    unsigned char *converted = NULL;
84✔
3452
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3453
    sixel_clock_t last_clock;
84✔
3454
#endif
3455
    double write_started_at;
84✔
3456
    double write_finished_at;
84✔
3457
    double write_duration;
84✔
3458

3459
    if (encoder != NULL && encoder->assessment_observer != NULL) {
84!
3460
        sixel_assessment_stage_transition(
×
3461
            encoder->assessment_observer,
3462
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3463
    }
3464

3465
#if defined(HAVE_CLOCK)
3466
    if (output->last_clock == 0) {
84!
3467
        output->last_clock = clock();
84✔
3468
    }
3469
#elif defined(HAVE_CLOCK_WIN)
3470
    if (output->last_clock == 0) {
3471
        output->last_clock = clock_win();
3472
    }
3473
#endif
3474

3475
    width = sixel_frame_get_width(frame);
84✔
3476
    height = sixel_frame_get_height(frame);
84✔
3477
    pixelformat = sixel_frame_get_pixelformat(frame);
84✔
3478
    depth = sixel_helper_compute_depth(pixelformat);
84✔
3479
    if (depth < 0) {
84!
3480
        status = SIXEL_LOGIC_ERROR;
×
3481
        sixel_helper_set_additional_message(
×
3482
            "sixel_encoder_output_with_macro: "
3483
            "sixel_helper_compute_depth() failed.");
3484
        goto end;
×
3485
    }
3486

3487
    frame_colorspace = sixel_frame_get_colorspace(frame);
84✔
3488
    size = (size_t)width * (size_t)height * (size_t)depth;
84✔
3489
    converted = (unsigned char *)sixel_allocator_malloc(
84✔
3490
        encoder->allocator, size);
3491
    if (converted == NULL) {
84!
3492
        sixel_helper_set_additional_message(
×
3493
            "sixel_encoder_output_with_macro: "
3494
            "sixel_allocator_malloc() failed.");
3495
        status = SIXEL_BAD_ALLOCATION;
×
3496
        goto end;
×
3497
    }
3498

3499
    memcpy(converted, sixel_frame_get_pixels(frame), size);
84✔
3500
    output->pixelformat = pixelformat;
84✔
3501
    output->source_colorspace = frame_colorspace;
84✔
3502
    output->colorspace = encoder->output_colorspace;
84✔
3503

3504
    if (sixel_frame_get_loop_no(frame) == 0) {
84✔
3505
        if (encoder->macro_number >= 0) {
54!
UNCOV
3506
            nwrite = sixel_compat_snprintf(
×
3507
                buffer,
3508
                sizeof(buffer),
3509
                "\033P%d;0;1!z",
3510
                encoder->macro_number);
3511
        } else {
3512
            nwrite = sixel_compat_snprintf(
54✔
3513
                buffer,
3514
                sizeof(buffer),
3515
                "\033P%d;0;1!z",
3516
                sixel_frame_get_frame_no(frame));
3517
        }
3518
        if (nwrite < 0) {
54!
3519
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3520
            sixel_helper_set_additional_message(
×
3521
                "sixel_encoder_output_with_macro: command format failed.");
3522
            goto end;
×
3523
        }
3524
        write_started_at = 0.0;
54✔
3525
        write_finished_at = 0.0;
54✔
3526
        write_duration = 0.0;
54✔
3527
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3528
            write_started_at = sixel_assessment_timer_now();
×
3529
        }
3530
        nwrite = sixel_write_callback(buffer,
108✔
3531
                                      (int)strlen(buffer),
54✔
3532
                                      &encoder->outfd);
3533
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3534
            write_finished_at = sixel_assessment_timer_now();
×
3535
            write_duration = write_finished_at - write_started_at;
×
3536
            if (write_duration < 0.0) {
×
3537
                write_duration = 0.0;
54✔
3538
            }
3539
        }
3540
        if (nwrite < 0) {
54!
3541
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3542
            sixel_helper_set_additional_message(
×
3543
                "sixel_encoder_output_with_macro: "
3544
                "sixel_write_callback() failed.");
3545
            goto end;
×
3546
        }
3547
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3548
            sixel_assessment_record_output_write(
×
3549
                encoder->assessment_observer,
3550
                (size_t)nwrite,
3551
                write_duration);
3552
        }
3553

3554
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3555
            sixel_assessment_stage_transition(
×
3556
                encoder->assessment_observer,
3557
                SIXEL_ASSESSMENT_STAGE_ENCODE);
3558
        }
3559
        status = sixel_encode(converted,
54✔
3560
                              width,
3561
                              height,
3562
                              depth,
3563
                              dither,
3564
                              output);
3565
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3566
            sixel_assessment_stage_finish(
×
3567
                encoder->assessment_observer);
3568
        }
3569
        if (SIXEL_FAILED(status)) {
54!
3570
            goto end;
×
3571
        }
3572

3573
        write_started_at = 0.0;
54✔
3574
        write_finished_at = 0.0;
54✔
3575
        write_duration = 0.0;
54✔
3576
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3577
            write_started_at = sixel_assessment_timer_now();
×
3578
        }
3579
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
54✔
3580
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3581
            write_finished_at = sixel_assessment_timer_now();
×
3582
            write_duration = write_finished_at - write_started_at;
×
3583
            if (write_duration < 0.0) {
×
3584
                write_duration = 0.0;
54✔
3585
            }
3586
        }
3587
        if (nwrite < 0) {
54!
3588
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3589
            sixel_helper_set_additional_message(
×
3590
                "sixel_encoder_output_with_macro: "
3591
                "sixel_write_callback() failed.");
3592
            goto end;
×
3593
        }
3594
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3595
            sixel_assessment_record_output_write(
×
3596
                encoder->assessment_observer,
3597
                (size_t)nwrite,
3598
                write_duration);
3599
        }
3600
    }
3601
    if (encoder->macro_number < 0) {
84✔
3602
        nwrite = sixel_compat_snprintf(
81✔
3603
            buffer,
3604
            sizeof(buffer),
3605
            "\033[%d*z",
3606
            sixel_frame_get_frame_no(frame));
3607
        if (nwrite < 0) {
81!
3608
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3609
            sixel_helper_set_additional_message(
×
3610
                "sixel_encoder_output_with_macro: command format failed.");
3611
        }
3612
        write_started_at = 0.0;
81✔
3613
        write_finished_at = 0.0;
81✔
3614
        write_duration = 0.0;
81✔
3615
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3616
            write_started_at = sixel_assessment_timer_now();
×
3617
        }
3618
        nwrite = sixel_write_callback(buffer,
162✔
3619
                                      (int)strlen(buffer),
81✔
3620
                                      &encoder->outfd);
3621
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3622
            write_finished_at = sixel_assessment_timer_now();
×
3623
            write_duration = write_finished_at - write_started_at;
×
3624
            if (write_duration < 0.0) {
×
3625
                write_duration = 0.0;
81✔
3626
            }
3627
        }
3628
        if (nwrite < 0) {
81!
3629
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3630
            sixel_helper_set_additional_message(
×
3631
                "sixel_encoder_output_with_macro: "
3632
                "sixel_write_callback() failed.");
3633
            goto end;
×
3634
        }
3635
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3636
            sixel_assessment_record_output_write(
×
3637
                encoder->assessment_observer,
3638
                (size_t)nwrite,
3639
                write_duration);
3640
        }
3641
    delay = sixel_frame_get_delay(frame);
81✔
3642
    if (delay > 0 && !encoder->fignore_delay) {
81!
3643
# if defined(HAVE_CLOCK)
3644
            last_clock = clock();
54✔
3645
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3646
#  if defined(__APPLE__)
3647
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3648
                             / 100000);
3649
#  else
3650
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
54✔
3651
                             / CLOCKS_PER_SEC);
3652
#  endif
3653
            output->last_clock = last_clock;
54✔
3654
# elif defined(HAVE_CLOCK_WIN)
3655
            last_clock = clock_win();
3656
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3657
                             / CLOCKS_PER_SEC);
3658
            output->last_clock = last_clock;
3659
# else
3660
            dulation = 0;
3661
# endif
3662
            if (dulation < 1000 * 10 * delay) {
54!
3663
                remaining_delay =
54✔
3664
                    (unsigned int)(1000 * 10 * delay - dulation);
54✔
3665
                sixel_sleep(remaining_delay);
54✔
3666
            }
3667
        }
3668
    }
3669

3670
end:
30✔
3671
    output->pixelformat = pixelformat;
84✔
3672
    output->source_colorspace = frame_colorspace;
84✔
3673
    sixel_allocator_free(encoder->allocator, converted);
84✔
3674

3675
    return status;
84✔
3676
}
3677

3678

3679
static SIXELSTATUS
3680
sixel_encoder_emit_iso2022_chars(
×
3681
    sixel_encoder_t *encoder,
3682
    sixel_frame_t *frame
3683
)
3684
{
3685
    char *buf_p, *buf;
×
3686
    int col, row;
×
3687
    int charset;
×
3688
    int is_96cs;
×
3689
    unsigned int charset_no;
×
3690
    unsigned int code;
×
3691
    int num_cols, num_rows;
×
3692
    SIXELSTATUS status;
×
3693
    size_t alloc_size;
×
3694
    int nwrite;
×
3695
    int target_fd;
×
3696
    int chunk_size;
×
3697

3698
    charset_no = encoder->drcs_charset_no;
×
3699
    if (charset_no == 0u) {
×
3700
        charset_no = 1u;
3701
    }
3702
    if (encoder->drcs_mmv == 0) {
×
3703
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3704
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3705
    } else if (encoder->drcs_mmv == 1) {
×
3706
        is_96cs = 0;
×
3707
        charset = (int)(charset_no + 0x3fu);
×
3708
    } else {
3709
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3710
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3711
    }
3712
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3713
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3714
             / encoder->cell_width;
3715
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3716
             / encoder->cell_height;
3717

3718
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
3719
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
3720
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3721
    if (buf == NULL) {
×
3722
        sixel_helper_set_additional_message(
×
3723
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
3724
        status = SIXEL_BAD_ALLOCATION;
×
3725
        goto end;
×
3726
    }
3727

3728
    code = 0x20;
×
3729
    *(buf_p++) = '\016';  /* SI */
×
3730
    *(buf_p++) = '\033';
×
3731
    *(buf_p++) = ')';
×
3732
    *(buf_p++) = ' ';
×
3733
    *(buf_p++) = charset;
×
3734
    for(row = 0; row < num_rows; row++) {
×
3735
        for(col = 0; col < num_cols; col++) {
×
3736
            if ((code & 0x7f) == 0x0) {
×
3737
                if (charset == 0x7e) {
×
3738
                    is_96cs = 1 - is_96cs;
×
3739
                    charset = '0';
×
3740
                } else {
3741
                    charset++;
×
3742
                }
3743
                code = 0x20;
×
3744
                *(buf_p++) = '\033';
×
3745
                *(buf_p++) = is_96cs ? '-': ')';
×
3746
                *(buf_p++) = ' ';
×
3747
                *(buf_p++) = charset;
×
3748
            }
3749
            *(buf_p++) = code++;
×
3750
        }
3751
        *(buf_p++) = '\n';
×
3752
    }
3753
    *(buf_p++) = '\017';  /* SO */
×
3754

3755
    if (encoder->tile_outfd >= 0) {
×
3756
        target_fd = encoder->tile_outfd;
3757
    } else {
3758
        target_fd = encoder->outfd;
×
3759
    }
3760

3761
    chunk_size = (int)(buf_p - buf);
×
3762
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3763
                                          buf,
3764
                                          chunk_size,
3765
                                          target_fd);
3766
    if (nwrite != chunk_size) {
×
3767
        sixel_helper_set_additional_message(
×
3768
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3769
        status = SIXEL_RUNTIME_ERROR;
×
3770
        goto end;
×
3771
    }
3772

3773
    sixel_allocator_free(encoder->allocator, buf);
×
3774

3775
    status = SIXEL_OK;
×
3776

UNCOV
3777
end:
×
3778
    return status;
×
3779
}
3780

3781

3782
/*
3783
 * This routine is derived from mlterm's drcssixel.c
3784
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
3785
 * The original implementation is credited to Araki Ken.
3786
 * Adjusted here to integrate with libsixel's encoder pipeline.
3787
 */
3788
static SIXELSTATUS
3789
sixel_encoder_emit_drcsmmv2_chars(
×
3790
    sixel_encoder_t *encoder,
3791
    sixel_frame_t *frame
3792
)
3793
{
3794
    char *buf_p, *buf;
×
3795
    int col, row;
×
3796
    char ibytes[3] = { 0x20, 0x00, 0x00 };
×
3797
    int is_96cs;
×
3798
    unsigned int charset_no;
×
3799
    unsigned int code;
×
3800
    int num_cols, num_rows;
×
3801
    SIXELSTATUS status;
×
3802
    size_t alloc_size;
×
3803
    int nwrite;
×
3804
    int target_fd;
×
3805
    int chunk_size;
×
3806
    int fill;
×
3807

3808
    charset_no = encoder->drcs_charset_no;
×
3809
    if (charset_no == 0u) {
×
3810
        charset_no = 1u;
3811
    }
3812
    if (encoder->drcs_mmv == 0) {
×
3813
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3814
        ibytes[1] = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3815
        fill = 0;
×
3816
    } else if (encoder->drcs_mmv == 1) {
×
3817
        is_96cs = 0;
×
3818
        ibytes[1] = (int)(charset_no + 0x3fu);
×
3819
        fill = 0;
×
3820
    } else if (encoder->drcs_mmv == 2) {
×
3821
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3822
        ibytes[1] = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3823
        fill = 0;
×
3824
    } else {  /* v3 */
3825
        is_96cs = 0;
×
3826
        ibytes[1] = (int)(((charset_no - 1u) / 63u) + 0x20u);
×
3827
        ibytes[2] = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3828
        fill = 1;
×
3829
    }
3830
    if (fill) {
×
3831
        code = 0x100000 + (charset_no - 1u) * 94;
×
3832
    } else {
3833
        code = 0x100020 + (is_96cs ? 0x80 : 0) + ibytes[1] * 0x100;
×
3834
    }
3835
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3836
             / encoder->cell_width;
3837
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3838
             / encoder->cell_height;
3839

3840
    /* cols x rows x 4(out of BMP) + rows(LFs) */
3841
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
3842
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3843
    if (buf == NULL) {
×
3844
        sixel_helper_set_additional_message(
×
3845
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
3846
        status = SIXEL_BAD_ALLOCATION;
×
3847
        goto end;
×
3848
    }
3849

3850
    for (row = 0; row < num_rows; row++) {
×
3851
        for (col = 0; col < num_cols; col++) {
×
3852
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
3853
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
3854
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
3855
            *(buf_p++) = (code & 0x3f) | 0x80;
×
3856
            code++;
×
3857
            if (! fill) {
×
3858
                if ((code & 0x7f) == 0x0) {
×
3859
                    if (ibytes[1] == 0x7e) {
×
3860
                        is_96cs = 1 - is_96cs;
×
3861
                        ibytes[1] = '0';
×
3862
                    } else {
3863
                        ibytes[1]++;
×
3864
                    }
3865
                    code = 0x100020 + (is_96cs ? 0x80 : 0) + ibytes[1] * 0x100;
×
3866
                }
3867
            }
3868
        }
3869
        *(buf_p++) = '\n';
×
3870
    }
3871

3872
    if (encoder->tile_outfd >= 0) {
×
3873
        target_fd = encoder->tile_outfd;
3874
    } else {
3875
        target_fd = encoder->outfd;
×
3876
    }
3877

3878
    chunk_size = (int)(buf_p - buf);
×
3879
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3880
                                          buf,
3881
                                          chunk_size,
3882
                                          target_fd);
3883
    if (nwrite != chunk_size) {
×
3884
        sixel_helper_set_additional_message(
×
3885
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
3886
        status = SIXEL_RUNTIME_ERROR;
×
3887
        goto end;
×
3888
    }
3889

3890
    sixel_allocator_free(encoder->allocator, buf);
×
3891

3892
    status = SIXEL_OK;
×
3893

UNCOV
3894
end:
×
3895
    return status;
×
3896
}
3897

3898
static SIXELSTATUS
3899
sixel_encoder_encode_frame(
525✔
3900
    sixel_encoder_t *encoder,
3901
    sixel_frame_t   *frame,
3902
    sixel_output_t  *output)
3903
{
3904
    SIXELSTATUS status = SIXEL_FALSE;
525✔
3905
    sixel_dither_t *dither = NULL;
525✔
3906
    int height;
525✔
3907
    int is_animation = 0;
525✔
3908
    int nwrite;
525✔
3909
    int drcs_is_96cs_param;
525✔
3910
    char drcs_designate_str[4] = { 0x20, 0x20, 0x40, 0x00 };
525✔
3911
    char buf[256];
525✔
3912
    sixel_write_function fn_write;
525✔
3913
    sixel_write_function write_callback;
525✔
3914
    sixel_write_function scroll_callback;
525✔
3915
    void *write_priv;
525✔
3916
    void *scroll_priv;
525✔
3917
    sixel_encoder_output_probe_t probe;
525✔
3918
    sixel_encoder_output_probe_t scroll_probe;
525✔
3919
    sixel_assessment_t *assessment;
525✔
3920
    int width_before;
525✔
3921
    int height_before;
525✔
3922
    int width_after;
525✔
3923
    int height_after;
525✔
3924
    int target_pixelformat;
525✔
3925
    int frame_colorspace;
525✔
3926

3927
    fn_write = sixel_write_callback;
525✔
3928
    write_callback = sixel_write_callback;
525✔
3929
    scroll_callback = sixel_write_callback;
525✔
3930
    write_priv = &encoder->outfd;
525✔
3931
    scroll_priv = &encoder->outfd;
525✔
3932
    probe.encoder = NULL;
525✔
3933
    probe.base_write = NULL;
525✔
3934
    probe.base_priv = NULL;
525✔
3935
    scroll_probe.encoder = NULL;
525✔
3936
    scroll_probe.base_write = NULL;
525✔
3937
    scroll_probe.base_priv = NULL;
525✔
3938
    assessment = NULL;
525✔
3939
    if (encoder != NULL) {
525!
3940
        assessment = encoder->assessment_observer;
525✔
3941
    }
3942
    if (assessment != NULL) {
525✔
3943
        if (encoder->clipfirst) {
3!
3944
            sixel_assessment_stage_transition(
×
3945
                assessment,
3946
                SIXEL_ASSESSMENT_STAGE_CROP);
3947
        } else {
3948
            sixel_assessment_stage_transition(
3✔
3949
                assessment,
3950
                SIXEL_ASSESSMENT_STAGE_SCALE);
3951
        }
3952
    }
3953

3954
    /*
3955
     *  Geometry timeline:
3956
     *
3957
     *      +-------+    +------+    +---------------+
3958
     *      | scale | -> | crop | -> | color convert |
3959
     *      +-------+    +------+    +---------------+
3960
     *
3961
     *  The order of the first two blocks mirrors `-c`, so we hop between
3962
     *  SCALE and CROP depending on `clipfirst`.
3963
     */
3964

3965
    if (encoder->clipfirst) {
525✔
3966
        width_before = sixel_frame_get_width(frame);
6✔
3967
        height_before = sixel_frame_get_height(frame);
6✔
3968
        sixel_encoder_log_stage(encoder,
6✔
3969
                                frame,
3970
                                "crop",
3971
                                "worker",
3972
                                "start",
3973
                                "size=%dx%d",
3974
                                width_before,
3975
                                height_before);
3976
        status = sixel_encoder_do_clip(encoder, frame);
6✔
3977
        if (SIXEL_FAILED(status)) {
6!
3978
            goto end;
×
3979
        }
3980
        width_after = sixel_frame_get_width(frame);
6✔
3981
        height_after = sixel_frame_get_height(frame);
6✔
3982
        sixel_encoder_log_stage(encoder,
6✔
3983
                                frame,
3984
                                "crop",
3985
                                "worker",
3986
                                "finish",
3987
                                "result=%dx%d",
3988
                                width_after,
3989
                                height_after);
3990
        if (assessment != NULL) {
6!
3991
            sixel_assessment_stage_transition(
×
3992
                assessment,
3993
                SIXEL_ASSESSMENT_STAGE_SCALE);
3994
        }
3995
        width_before = sixel_frame_get_width(frame);
6✔
3996
        height_before = sixel_frame_get_height(frame);
6✔
3997
        sixel_encoder_log_stage(encoder,
6✔
3998
                                frame,
3999
                                "scale",
4000
                                "worker",
4001
                                "start",
4002
                                "size=%dx%d",
4003
                                width_before,
4004
                                height_before);
4005
        status = sixel_encoder_do_resize(encoder, frame);
6✔
4006
        if (SIXEL_FAILED(status)) {
6!
4007
            goto end;
×
4008
        }
4009
        width_after = sixel_frame_get_width(frame);
6✔
4010
        height_after = sixel_frame_get_height(frame);
6✔
4011
        sixel_encoder_log_stage(encoder,
6✔
4012
                                frame,
4013
                                "scale",
4014
                                "worker",
4015
                                "finish",
4016
                                "result=%dx%d",
4017
                                width_after,
4018
                                height_after);
4019
    } else {
4020
        width_before = sixel_frame_get_width(frame);
519✔
4021
        height_before = sixel_frame_get_height(frame);
519✔
4022
        sixel_encoder_log_stage(encoder,
519✔
4023
                                frame,
4024
                                "scale",
4025
                                "worker",
4026
                                "start",
4027
                                "size=%dx%d",
4028
                                width_before,
4029
                                height_before);
4030
        status = sixel_encoder_do_resize(encoder, frame);
519✔
4031
        if (SIXEL_FAILED(status)) {
519✔
4032
            goto end;
6✔
4033
        }
4034
        width_after = sixel_frame_get_width(frame);
513✔
4035
        height_after = sixel_frame_get_height(frame);
513✔
4036
        sixel_encoder_log_stage(encoder,
513✔
4037
                                frame,
4038
                                "scale",
4039
                                "worker",
4040
                                "finish",
4041
                                "result=%dx%d",
4042
                                width_after,
4043
                                height_after);
4044
        if (assessment != NULL) {
513✔
4045
            sixel_assessment_stage_transition(
3✔
4046
                assessment,
4047
                SIXEL_ASSESSMENT_STAGE_CROP);
4048
        }
4049
        width_before = sixel_frame_get_width(frame);
513✔
4050
        height_before = sixel_frame_get_height(frame);
513✔
4051
        sixel_encoder_log_stage(encoder,
513✔
4052
                                frame,
4053
                                "crop",
4054
                                "worker",
4055
                                "start",
4056
                                "size=%dx%d",
4057
                                width_before,
4058
                                height_before);
4059
        status = sixel_encoder_do_clip(encoder, frame);
513✔
4060
        if (SIXEL_FAILED(status)) {
513!
4061
            goto end;
×
4062
        }
4063
        width_after = sixel_frame_get_width(frame);
513✔
4064
        height_after = sixel_frame_get_height(frame);
513✔
4065
        sixel_encoder_log_stage(encoder,
513✔
4066
                                frame,
4067
                                "crop",
4068
                                "worker",
4069
                                "finish",
4070
                                "result=%dx%d",
4071
                                width_after,
4072
                                height_after);
4073
    }
4074

4075
    if (assessment != NULL) {
519✔
4076
        sixel_assessment_stage_transition(
3✔
4077
            assessment,
4078
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
4079
    }
4080

4081
    frame_colorspace = sixel_frame_get_colorspace(frame);
519✔
4082
    target_pixelformat = sixel_encoder_pixelformat_for_colorspace(
519✔
4083
        encoder->working_colorspace,
4084
        encoder->prefer_float32);
4085

4086
    /*
4087
     * Promote to float formats when the user opts in via the environment or
4088
     * the precision CLI flag. The selection mirrors the requested working
4089
     * colorspace to avoid losing detail before palette generation.
4090
     */
4091
    sixel_encoder_log_stage(encoder,
519✔
4092
                            frame,
4093
                            "colorspace",
4094
                            "worker",
4095
                            "start",
4096
                            "current=%d target=%d",
4097
                            frame_colorspace,
4098
                            encoder->working_colorspace);
4099

4100
    if (encoder->working_colorspace != SIXEL_COLORSPACE_GAMMA
519!
4101
        || encoder->prefer_float32 != 0) {
519!
4102
        status = sixel_frame_set_pixelformat(
×
4103
            frame,
4104
            target_pixelformat);
4105
        if (SIXEL_FAILED(status)) {
×
4106
            goto end;
×
4107
        }
4108
    }
4109

4110
    if (assessment != NULL) {
519✔
4111
        sixel_assessment_stage_transition(
3✔
4112
            assessment,
4113
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
4114
    }
4115

4116
    sixel_encoder_log_stage(encoder,
519✔
4117
                            frame,
4118
                            "colorspace",
4119
                            "worker",
4120
                            "finish",
4121
                            "result=%d",
4122
                            sixel_frame_get_colorspace(frame));
4123

4124
    /* prepare dither context */
4125
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
519✔
4126
    if (status != SIXEL_OK) {
519!
4127
        dither = NULL;
×
4128
        goto end;
×
4129
    }
4130

4131
    if (encoder->dither_cache != NULL) {
519!
4132
        encoder->dither_cache = dither;
×
4133
        sixel_dither_ref(dither);
×
4134
    }
4135

4136
    if (encoder->fdrcs) {
519!
4137
        status = sixel_encoder_ensure_cell_size(encoder);
×
4138
        if (SIXEL_FAILED(status)) {
×
4139
            goto end;
×
4140
        }
4141
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
4142
            sixel_helper_set_additional_message(
×
4143
                "drcs option cannot be used together with macro output.");
4144
            status = SIXEL_BAD_ARGUMENT;
×
4145
            goto end;
×
4146
        }
4147
    }
4148

4149
    /* evaluate -v option: print palette */
4150
    if (encoder->verbose) {
519✔
4151
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
4152
            sixel_debug_print_palette(dither);
3✔
4153
        }
4154
    }
4155

4156
    /* evaluate -d option: set method for diffusion */
4157
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
519✔
4158
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
519✔
4159
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
519✔
4160

4161
    /* evaluate -C option: set complexion score */
4162
    if (encoder->complexion > 1) {
519✔
4163
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
4164
    }
4165

4166
    if (output) {
519!
4167
        sixel_output_ref(output);
×
4168
        if (encoder->assessment_observer != NULL) {
×
4169
            probe.encoder = encoder;
×
4170
            probe.base_write = fn_write;
×
4171
            probe.base_priv = &encoder->outfd;
×
4172
            write_callback = sixel_write_with_probe;
×
4173
            write_priv = &probe;
×
4174
        }
4175
    } else {
4176
        /* create output context */
4177
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
519✔
4178
            /* -u or -n option */
4179
            fn_write = sixel_hex_write_callback;
4180
        } else {
4181
            fn_write = sixel_write_callback;
519✔
4182
        }
4183
        write_callback = fn_write;
519✔
4184
        write_priv = &encoder->outfd;
519✔
4185
        if (encoder->assessment_observer != NULL) {
519✔
4186
            probe.encoder = encoder;
3✔
4187
            probe.base_write = fn_write;
3✔
4188
            probe.base_priv = &encoder->outfd;
3✔
4189
            write_callback = sixel_write_with_probe;
3✔
4190
            write_priv = &probe;
3✔
4191
        }
4192
        status = sixel_output_new(&output,
519✔
4193
                                  write_callback,
4194
                                  write_priv,
4195
                                  encoder->allocator);
4196
        if (SIXEL_FAILED(status)) {
519!
4197
            goto end;
×
4198
        }
4199
    }
4200

4201
    if (encoder->fdrcs) {
519!
4202
        sixel_output_set_skip_dcs_envelope(output, 1);
×
4203
        sixel_output_set_skip_header(output, 1);
×
4204
    }
4205

4206
    sixel_output_set_8bit_availability(output, encoder->f8bit);
519✔
4207
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
519✔
4208
    sixel_output_set_palette_type(output, encoder->palette_type);
519✔
4209
    sixel_output_set_penetrate_multiplexer(
519✔
4210
        output, encoder->penetrate_multiplexer);
4211
    sixel_output_set_encode_policy(output, encoder->encode_policy);
519✔
4212
    sixel_output_set_ormode(output, encoder->ormode);
519✔
4213

4214
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
519✔
4215
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
108✔
4216
            is_animation = 1;
4217
        }
4218
        height = sixel_frame_get_height(frame);
108✔
4219
        if (encoder->assessment_observer != NULL) {
108!
4220
            scroll_probe.encoder = encoder;
×
4221
            scroll_probe.base_write = sixel_write_callback;
×
4222
            scroll_probe.base_priv = &encoder->outfd;
×
4223
            scroll_callback = sixel_write_with_probe;
×
4224
            scroll_priv = &scroll_probe;
×
4225
        } else {
4226
            scroll_callback = sixel_write_callback;
4227
            scroll_priv = &encoder->outfd;
4228
        }
4229
        (void) sixel_tty_scroll(scroll_callback,
108✔
4230
                                scroll_priv,
4231
                                encoder->outfd,
4232
                                height,
4233
                                is_animation);
4234
    }
4235

4236
    if (encoder->cancel_flag && *encoder->cancel_flag) {
519!
4237
        status = SIXEL_INTERRUPTED;
×
4238
        goto end;
×
4239
    }
4240

4241
    if (encoder->fdrcs) {  /* -@ option */
519!
4242
        if (encoder->drcs_mmv == 0) {
×
4243
            drcs_is_96cs_param =
×
4244
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
4245
            drcs_designate_str[1] =
×
4246
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
4247
            drcs_designate_str[2] = 0x00;
×
4248
        } else if (encoder->drcs_mmv == 1) {
×
4249
            drcs_is_96cs_param = 0;
×
4250
            drcs_designate_str[1] =
×
4251
                (int)(encoder->drcs_charset_no + 0x3fu);
×
4252
            drcs_designate_str[2] = 0x00;
×
4253
        } else if (encoder->drcs_mmv == 2) {
×
4254
            drcs_is_96cs_param =
×
4255
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
4256
            drcs_designate_str[1] =
×
4257
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
4258
            drcs_designate_str[2] = 0x00;
×
4259
        } else {
4260
            drcs_is_96cs_param = 0;
×
4261
            drcs_designate_str[1] =
×
4262
                (int)(((encoder->drcs_charset_no - 1u) / 63u) + 0x20u);
×
4263
            drcs_designate_str[2] =
×
4264
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
4265
            drcs_designate_str[3] = 0x00;
×
4266
        }
4267
        nwrite = sprintf(buf,
×
4268
                         "%s%sh%s1;0;0;%d;1;3;%d;%d{%s",
4269
                         (encoder->drcs_mmv > 0) ? (
UNCOV
4270
                             encoder->f8bit ? "\233?8800": "\033[?8800"
×
4271
                         ): "",
4272
                         (encoder->drcs_mmv >= 3) ? (
4273
                             encoder->f8bit ? ";8801": ";8801"
4274
                         ): "",
UNCOV
4275
                         encoder->f8bit ? "\220": "\033P",
×
4276
                         encoder->cell_width,
4277
                         encoder->cell_height,
4278
                         drcs_is_96cs_param,
4279
                         drcs_designate_str);
4280
        if (nwrite < 0) {
×
4281
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4282
            sixel_helper_set_additional_message(
×
4283
                "sixel_encoder_encode_frame: command format failed.");
4284
            goto end;
×
4285
        }
4286
        nwrite = write_callback(buf, nwrite, write_priv);
×
4287
        if (nwrite < 0) {
×
4288
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4289
            sixel_helper_set_additional_message(
×
4290
                "sixel_encoder_encode_frame: write() failed.");
4291
            goto end;
×
4292
        }
4293
    }
4294

4295
    /* output sixel: junction of multi-frame processing strategy */
4296
    if (encoder->fuse_macro) {  /* -u option */
519✔
4297
        /* use macro */
4298
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
81✔
4299
    } else if (encoder->macro_number >= 0) { /* -n option */
438✔
4300
        /* use macro */
4301
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
4302
    } else {
4303
        /* do not use macro */
4304
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
435✔
4305
    }
4306
    if (SIXEL_FAILED(status)) {
519!
4307
        goto end;
×
4308
    }
4309

4310
    if (encoder->cancel_flag && *encoder->cancel_flag) {
519!
4311
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
4312
        if (nwrite < 0) {
×
4313
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4314
            sixel_helper_set_additional_message(
×
4315
                "sixel_encoder_encode_frame: write_callback() failed.");
4316
            goto end;
×
4317
        }
4318
        status = SIXEL_INTERRUPTED;
4319
    }
4320
    if (SIXEL_FAILED(status)) {
1!
4321
        goto end;
4322
    }
4323

4324
    if (encoder->fdrcs) {  /* -@ option */
519!
4325
        if (encoder->f8bit) {
×
4326
            nwrite = write_callback("\234", 1, write_priv);
×
4327
        } else {
4328
            nwrite = write_callback("\033\\", 2, write_priv);
×
4329
        }
4330
        if (nwrite < 0) {
×
4331
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4332
            sixel_helper_set_additional_message(
×
4333
                "sixel_encoder_encode_frame: write_callback() failed.");
4334
            goto end;
×
4335
        }
4336

4337
        if (encoder->tile_outfd >= 0) {
×
4338
            if (encoder->drcs_mmv == 0) {
×
4339
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
4340
                if (SIXEL_FAILED(status)) {
×
4341
                    goto end;
4342
                }
4343
            } else {
4344
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
4345
                if (SIXEL_FAILED(status)) {
×
4346
                    goto end;
4347
                }
4348
            }
4349
        }
4350
    }
4351

4352

4353
end:
519✔
4354
    if (output) {
525✔
4355
        sixel_output_unref(output);
519✔
4356
    }
4357
    if (dither) {
525✔
4358
        sixel_dither_unref(dither);
519✔
4359
    }
4360

4361
    return status;
525✔
4362
}
4363

4364

4365
/* create encoder object */
4366
SIXELAPI SIXELSTATUS
4367
sixel_encoder_new(
537✔
4368
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
4369
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
4370
                                                  default allocator */
4371
{
4372
    SIXELSTATUS status = SIXEL_FALSE;
537✔
4373
    char const *env_default_bgcolor = NULL;
537✔
4374
    char const *env_default_ncolors = NULL;
537✔
4375
    char const *env_prefer_float32 = NULL;
537✔
4376
    int ncolors;
537✔
4377
    int prefer_float32;
537✔
4378

4379
    if (allocator == NULL) {
537!
4380
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
537✔
4381
        if (SIXEL_FAILED(status)) {
537!
4382
            goto end;
×
4383
        }
4384
    } else {
4385
        sixel_allocator_ref(allocator);
×
4386
    }
4387

4388
    *ppencoder
537✔
4389
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
537✔
4390
                                                    sizeof(sixel_encoder_t));
4391
    if (*ppencoder == NULL) {
537!
4392
        sixel_helper_set_additional_message(
×
4393
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
4394
        status = SIXEL_BAD_ALLOCATION;
×
4395
        sixel_allocator_unref(allocator);
×
4396
        goto end;
×
4397
    }
4398

4399
    (*ppencoder)->ref                   = 1;
537✔
4400
    (*ppencoder)->reqcolors             = (-1);
537✔
4401
    (*ppencoder)->force_palette         = 0;
537✔
4402
    (*ppencoder)->mapfile               = NULL;
537✔
4403
    (*ppencoder)->palette_output        = NULL;
537✔
4404
    (*ppencoder)->loader_order          = NULL;
537✔
4405
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
537✔
4406
    (*ppencoder)->builtin_palette       = 0;
537✔
4407
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
537✔
4408
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
537✔
4409
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
537✔
4410
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
537✔
4411
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
537✔
4412
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
537✔
4413
    (*ppencoder)->quantize_model        = SIXEL_QUANTIZE_MODEL_AUTO;
537✔
4414
    (*ppencoder)->final_merge_mode      = SIXEL_FINAL_MERGE_AUTO;
537✔
4415
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
537✔
4416
    (*ppencoder)->sixel_reversible      = 0;
537✔
4417
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
537✔
4418
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
537✔
4419
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
537✔
4420
    (*ppencoder)->f8bit                 = 0;
537✔
4421
    (*ppencoder)->has_gri_arg_limit     = 0;
537✔
4422
    (*ppencoder)->finvert               = 0;
537✔
4423
    (*ppencoder)->fuse_macro            = 0;
537✔
4424
    (*ppencoder)->fdrcs                 = 0;
537✔
4425
    (*ppencoder)->fignore_delay         = 0;
537✔
4426
    (*ppencoder)->complexion            = 1;
537✔
4427
    (*ppencoder)->fstatic               = 0;
537✔
4428
    (*ppencoder)->cell_width            = 0;
537✔
4429
    (*ppencoder)->cell_height           = 0;
537✔
4430
    (*ppencoder)->pixelwidth            = (-1);
537✔
4431
    (*ppencoder)->pixelheight           = (-1);
537✔
4432
    (*ppencoder)->percentwidth          = (-1);
537✔
4433
    (*ppencoder)->percentheight         = (-1);
537✔
4434
    (*ppencoder)->clipx                 = 0;
537✔
4435
    (*ppencoder)->clipy                 = 0;
537✔
4436
    (*ppencoder)->clipwidth             = 0;
537✔
4437
    (*ppencoder)->clipheight            = 0;
537✔
4438
    (*ppencoder)->clipfirst             = 0;
537✔
4439
    (*ppencoder)->macro_number          = (-1);
537✔
4440
    (*ppencoder)->verbose               = 0;
537✔
4441
    (*ppencoder)->penetrate_multiplexer = 0;
537✔
4442
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
537✔
4443
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
537✔
4444
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
537✔
4445
    (*ppencoder)->prefer_float32        = 0;
537✔
4446
    (*ppencoder)->ormode                = 0;
537✔
4447
    (*ppencoder)->pipe_mode             = 0;
537✔
4448
    (*ppencoder)->bgcolor               = NULL;
537✔
4449
    (*ppencoder)->outfd                 = STDOUT_FILENO;
537✔
4450
    (*ppencoder)->tile_outfd            = (-1);
537✔
4451
    (*ppencoder)->finsecure             = 0;
537✔
4452
    (*ppencoder)->cancel_flag           = NULL;
537✔
4453
    (*ppencoder)->dither_cache          = NULL;
537✔
4454
    (*ppencoder)->drcs_charset_no       = 1u;
537✔
4455
    (*ppencoder)->drcs_mmv              = 2;
537✔
4456
    (*ppencoder)->capture_quantized     = 0;
537✔
4457
    (*ppencoder)->capture_source        = 0;
537✔
4458
    (*ppencoder)->capture_pixels        = NULL;
537✔
4459
    (*ppencoder)->capture_pixels_size   = 0;
537✔
4460
    (*ppencoder)->capture_palette       = NULL;
537✔
4461
    (*ppencoder)->capture_palette_size  = 0;
537✔
4462
    (*ppencoder)->capture_pixel_bytes   = 0;
537✔
4463
    (*ppencoder)->capture_width         = 0;
537✔
4464
    (*ppencoder)->capture_height        = 0;
537✔
4465
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
537✔
4466
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
537✔
4467
    (*ppencoder)->capture_ncolors       = 0;
537✔
4468
    (*ppencoder)->capture_valid         = 0;
537✔
4469
    (*ppencoder)->capture_source_frame  = NULL;
537✔
4470
    (*ppencoder)->assessment_observer   = NULL;
537✔
4471
    (*ppencoder)->assessment_json_path  = NULL;
537✔
4472
    (*ppencoder)->assessment_sections   = SIXEL_ASSESSMENT_SECTION_NONE;
537✔
4473
    (*ppencoder)->last_loader_name[0]   = '\0';
537✔
4474
    (*ppencoder)->last_source_path[0]   = '\0';
537✔
4475
    (*ppencoder)->last_input_bytes      = 0u;
537✔
4476
    (*ppencoder)->output_is_png         = 0;
537✔
4477
    (*ppencoder)->output_png_to_stdout  = 0;
537✔
4478
    (*ppencoder)->png_output_path       = NULL;
537✔
4479
    (*ppencoder)->sixel_output_path     = NULL;
537✔
4480
    (*ppencoder)->clipboard_output_active = 0;
537✔
4481
    (*ppencoder)->clipboard_output_format[0] = '\0';
537✔
4482
    (*ppencoder)->clipboard_output_path = NULL;
537✔
4483
    (*ppencoder)->logger                = NULL;
537✔
4484
    (*ppencoder)->parallel_job_id       = -1;
537✔
4485
    (*ppencoder)->allocator             = allocator;
537✔
4486

4487
    prefer_float32 = 0;
537✔
4488
    env_prefer_float32 = sixel_compat_getenv(
537✔
4489
        SIXEL_ENCODER_PRECISION_ENVVAR);
4490
    /*
4491
     * $SIXEL_FLOAT32_DITHER seeds the precision preference and is later
4492
     * overridden by the precision CLI flag when provided.
4493
     */
4494
    prefer_float32 = sixel_encoder_env_prefers_float32(env_prefer_float32);
537✔
4495
    (*ppencoder)->prefer_float32 = prefer_float32;
537✔
4496

4497
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
4498
    env_default_bgcolor = sixel_compat_getenv("SIXEL_BGCOLOR");
537✔
4499
    if (env_default_bgcolor != NULL) {
537!
4500
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
4501
                                         env_default_bgcolor,
4502
                                         allocator);
4503
        if (SIXEL_FAILED(status)) {
×
4504
            goto error;
×
4505
        }
4506
    }
4507

4508
    /* evaluate environment variable ${SIXEL_COLORS} */
4509
    env_default_ncolors = sixel_compat_getenv("SIXEL_COLORS");
537✔
4510
    if (env_default_ncolors) {
537!
4511
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
4512
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
4513
            (*ppencoder)->reqcolors = ncolors;
×
4514
        }
4515
    }
4516

4517
    /* success */
4518
    status = SIXEL_OK;
537✔
4519

4520
    goto end;
537✔
4521

UNCOV
4522
error:
×
4523
    sixel_allocator_free(allocator, *ppencoder);
×
4524
    sixel_allocator_unref(allocator);
×
4525
    *ppencoder = NULL;
×
4526

4527
end:
537✔
4528
    return status;
537✔
4529
}
4530

4531

4532
/* create encoder object (deprecated version) */
4533
SIXELAPI /* deprecated */ sixel_encoder_t *
4534
sixel_encoder_create(void)
×
4535
{
4536
    SIXELSTATUS status = SIXEL_FALSE;
×
4537
    sixel_encoder_t *encoder = NULL;
×
4538

4539
    status = sixel_encoder_new(&encoder, NULL);
×
4540
    if (SIXEL_FAILED(status)) {
×
4541
        return NULL;
4542
    }
4543

4544
    return encoder;
×
4545
}
4546

4547

4548
/* destroy encoder object */
4549
static void
4550
sixel_encoder_destroy(sixel_encoder_t *encoder)
537✔
4551
{
4552
    sixel_allocator_t *allocator;
537✔
4553

4554
    if (encoder) {
537!
4555
        allocator = encoder->allocator;
537✔
4556
        sixel_allocator_free(allocator, encoder->mapfile);
537✔
4557
        sixel_allocator_free(allocator, encoder->palette_output);
537✔
4558
        sixel_allocator_free(allocator, encoder->loader_order);
537✔
4559
        sixel_allocator_free(allocator, encoder->bgcolor);
537✔
4560
        sixel_dither_unref(encoder->dither_cache);
537✔
4561
        if (encoder->outfd
537!
4562
            && encoder->outfd != STDOUT_FILENO
1!
4563
            && encoder->outfd != STDERR_FILENO) {
537✔
4564
            (void)sixel_compat_close(encoder->outfd);
30✔
4565
        }
4566
        if (encoder->tile_outfd >= 0
537!
UNCOV
4567
            && encoder->tile_outfd != encoder->outfd
×
4568
            && encoder->tile_outfd != STDOUT_FILENO
×
4569
            && encoder->tile_outfd != STDERR_FILENO) {
×
4570
            (void)sixel_compat_close(encoder->tile_outfd);
×
4571
        }
4572
        if (encoder->capture_source_frame != NULL) {
537✔
4573
            sixel_frame_unref(encoder->capture_source_frame);
3✔
4574
        }
4575
        if (encoder->clipboard_output_path != NULL) {
537!
4576
            (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4577
            encoder->clipboard_output_path = NULL;
×
4578
        }
4579
        encoder->clipboard_output_active = 0;
537✔
4580
        encoder->clipboard_output_format[0] = '\0';
537✔
4581
        sixel_allocator_free(allocator, encoder->capture_pixels);
537✔
4582
        sixel_allocator_free(allocator, encoder->capture_palette);
537✔
4583
        sixel_allocator_free(allocator, encoder->png_output_path);
537✔
4584
        sixel_allocator_free(allocator, encoder->sixel_output_path);
537✔
4585
        sixel_allocator_free(allocator, encoder);
537✔
4586
        sixel_allocator_unref(allocator);
537✔
4587
    }
4588
}
537✔
4589

4590

4591
/* increase reference count of encoder object (thread-unsafe) */
4592
SIXELAPI void
4593
sixel_encoder_ref(sixel_encoder_t *encoder)
1,146✔
4594
{
4595
    /* TODO: be thread safe */
4596
    ++encoder->ref;
1,146✔
4597
}
764✔
4598

4599

4600
/* decrease reference count of encoder object (thread-unsafe) */
4601
SIXELAPI void
4602
sixel_encoder_unref(sixel_encoder_t *encoder)
1,683✔
4603
{
4604
    /* TODO: be thread safe */
4605
    if (encoder != NULL && --encoder->ref == 0) {
1,683!
4606
        sixel_encoder_destroy(encoder);
537✔
4607
    }
4608
}
1,683✔
4609

4610

4611
/* set cancel state flag to encoder object */
4612
SIXELAPI SIXELSTATUS
4613
sixel_encoder_set_cancel_flag(
432✔
4614
    sixel_encoder_t /* in */ *encoder,
4615
    int             /* in */ *cancel_flag
4616
)
4617
{
4618
    SIXELSTATUS status = SIXEL_OK;
432✔
4619

4620
    encoder->cancel_flag = cancel_flag;
432✔
4621

4622
    return status;
432✔
4623
}
4624

4625

4626
/*
4627
 * parse_assessment_sections() translates a comma-separated section list into
4628
 * the bitmask understood by the assessment back-end.  The accepted grammar is
4629
 * intentionally small so that the CLI contract stays predictable:
4630
 *
4631
 *     list := section {"," section}
4632
 *     section := name | name "@" view
4633
 *
4634
 * Each name maps to a bit flag while the optional view toggles the encoded vs
4635
 * quantized quality comparison.  The helper folds case, trims ASCII
4636
 * whitespace, and rejects mixed encoded/quantized requests so that the caller
4637
 * can rely on a single coherent capture strategy.
4638
 */
4639
static int
4640
parse_assessment_sections(char const *spec,
6✔
4641
                          unsigned int *out_sections)
4642
{
4643
    char *copy;
6✔
4644
    char *cursor;
6✔
4645
    char *token;
6✔
4646
    char *context;
6✔
4647
    unsigned int sections;
6✔
4648
    unsigned int view;
6✔
4649
    int result;
6✔
4650
    size_t length;
6✔
4651
    size_t spec_len;
6✔
4652
    char *at;
6✔
4653
    char *base;
6✔
4654
    char *variant;
6✔
4655
    char *p;
6✔
4656

4657
    if (spec == NULL || out_sections == NULL) {
6!
4658
        return -1;
4659
    }
4660
    spec_len = strlen(spec);
6✔
4661
    copy = (char *)malloc(spec_len + 1u);
6✔
4662
    if (copy == NULL) {
6!
4663
        return -1;
4664
    }
4665
    memcpy(copy, spec, spec_len + 1u);
6✔
4666
    cursor = copy;
6✔
4667
    sections = 0u;
6✔
4668
    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
6✔
4669
    result = 0;
6✔
4670
    context = NULL;
6✔
4671
    while (result == 0) {
6!
4672
        token = sixel_compat_strtok(cursor, ",", &context);
12✔
4673
        if (token == NULL) {
12✔
4674
            break;
4675
        }
4676
        cursor = NULL;
6✔
4677
        while (*token == ' ' || *token == '\t') {
6!
4678
            token += 1;
×
4679
        }
4680
        length = strlen(token);
6✔
4681
        while (length > 0u &&
6!
4682
               (token[length - 1u] == ' ' || token[length - 1u] == '\t')) {
6!
4683
            token[length - 1u] = '\0';
×
4684
            length -= 1u;
×
4685
        }
4686
        if (*token == '\0') {
6!
4687
            result = -1;
4688
            break;
4689
        }
4690
        for (p = token; *p != '\0'; ++p) {
42✔
4691
            *p = (char)tolower((unsigned char)*p);
36✔
4692
        }
4693
        at = strchr(token, '@');
6✔
4694
        if (at != NULL) {
6!
4695
            *at = '\0';
×
4696
            variant = at + 1;
×
4697
            if (*variant == '\0') {
×
4698
                result = -1;
4699
                break;
4700
            }
4701
        } else {
4702
            variant = NULL;
4703
        }
4704
        base = token;
6✔
4705
        if (strcmp(base, "all") == 0) {
6!
4706
            sections |= SIXEL_ASSESSMENT_SECTION_ALL;
×
4707
            if (variant != NULL && variant[0] != '\0') {
×
4708
                if (strcmp(variant, "quantized") == 0) {
×
4709
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4710
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4711
                        result = -1;
4712
                    }
4713
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
4714
                } else if (strcmp(variant, "encoded") == 0) {
×
4715
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4716
                        result = -1;
4717
                    }
4718
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
4719
                } else {
4720
                    result = -1;
4721
                }
4722
            }
4723
        } else if (strcmp(base, "basic") == 0) {
6✔
4724
            sections |= SIXEL_ASSESSMENT_SECTION_BASIC;
3✔
4725
            if (variant != NULL) {
3!
4726
                result = -1;
4727
            }
4728
        } else if (strcmp(base, "performance") == 0) {
3!
4729
            sections |= SIXEL_ASSESSMENT_SECTION_PERFORMANCE;
×
4730
            if (variant != NULL) {
×
4731
                result = -1;
4732
            }
4733
        } else if (strcmp(base, "size") == 0) {
3!
4734
            sections |= SIXEL_ASSESSMENT_SECTION_SIZE;
×
4735
            if (variant != NULL) {
×
4736
                result = -1;
4737
            }
4738
        } else if (strcmp(base, "quality") == 0) {
3!
4739
            sections |= SIXEL_ASSESSMENT_SECTION_QUALITY;
3✔
4740
            if (variant != NULL && variant[0] != '\0') {
3!
4741
                if (strcmp(variant, "quantized") == 0) {
×
4742
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4743
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4744
                        result = -1;
4745
                    }
4746
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
4747
                } else if (strcmp(variant, "encoded") == 0) {
×
4748
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4749
                        result = -1;
4750
                    }
4751
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
4752
                } else {
4753
                    result = -1;
4754
                }
4755
            } else if (variant != NULL) {
1!
4756
                if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4757
                    result = -1;
4758
                }
4759
                view = SIXEL_ASSESSMENT_VIEW_ENCODED;
4760
            }
4761
        } else {
4762
            result = -1;
4763
        }
4764
    }
4765
    if (result == 0) {
6!
4766
        if (sections == 0u) {
6!
4767
            result = -1;
4768
        } else {
4769
            if ((sections & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u &&
6!
4770
                    view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4771
                sections |= SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4772
            }
4773
            *out_sections = sections;
6✔
4774
        }
4775
    }
4776
    free(copy);
6✔
4777
    return result;
6✔
4778
}
4779

4780

4781
static int
4782
is_png_target(char const *path)
30✔
4783
{
4784
    size_t len;
30✔
4785
    int matched;
30✔
4786

4787
    /*
4788
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
4789
     *
4790
     *   argument
4791
     *   |
4792
     *   v
4793
     *   .............. . p n g
4794
     *   ^             ^^^^^^^^^
4795
     *   |             +-- case-insensitive suffix comparison
4796
     *   +-- accepts the "png:" inline prefix used for stdout capture
4797
     */
4798

4799
    len = 0;
30✔
4800
    matched = 0;
30✔
4801

4802
    if (path == NULL) {
30!
4803
        return 0;
4804
    }
4805

4806
    if (strncmp(path, "png:", 4) == 0) {
30✔
4807
        return path[4] != '\0';
6✔
4808
    }
4809

4810
    len = strlen(path);
24✔
4811
    if (len >= 4) {
24✔
4812
        matched = (tolower((unsigned char)path[len - 4]) == '.')
21✔
4813
            && (tolower((unsigned char)path[len - 3]) == 'p')
3!
4814
            && (tolower((unsigned char)path[len - 2]) == 'n')
3!
4815
            && (tolower((unsigned char)path[len - 1]) == 'g');
24!
4816
    }
4817

4818
    return matched;
4819
}
4820

4821

4822
static char const *
4823
png_target_payload_view(char const *argument)
9✔
4824
{
4825
    /*
4826
     * Inline PNG targets split into either a prefix/payload pair or rely on
4827
     * a simple file-name suffix:
4828
     *
4829
     *   +--------------+------------+-------------+
4830
     *   | form         | payload    | destination |
4831
     *   +--------------+------------+-------------+
4832
     *   | png:         | -          | stdout      |
4833
     *   | png:         | filename   | filesystem  |
4834
     *   | *.png        | filename   | filesystem  |
4835
     *   +--------------+------------+-------------+
4836
     *
4837
     * The caller only needs the payload column, so we expose it here.  When
4838
     * the user omits the prefix we simply echo the original pointer so the
4839
     * caller can copy the value verbatim.
4840
     */
4841
    if (argument == NULL) {
9!
4842
        return NULL;
4843
    }
4844
    if (strncmp(argument, "png:", 4) == 0) {
9✔
4845
        return argument + 4;
6✔
4846
    }
4847

4848
    return argument;
4849
}
4850

4851
static void
4852
normalise_windows_drive_path(char *path)
9✔
4853
{
4854
#if defined(_WIN32)
4855
    size_t length;
4856

4857
    /*
4858
     * MSYS-like environments forward POSIX-looking absolute paths to native
4859
     * binaries.  When a user writes "/d/..." MSYS converts the command line to
4860
     * UTF-16 and preserves the literal bytes.  The Windows CRT, however,
4861
     * expects "d:/..." or "d:\...".  The tiny state machine below rewrites the
4862
     * leading token so the runtime resolves the drive correctly:
4863
     *
4864
     *   input     normalised
4865
     *   |         |
4866
     *   v         v
4867
     *   / d / ... d : / ...
4868
     *
4869
     * The body keeps the rest of the string intact so UNC paths ("//server")
4870
     * and relative references pass through untouched.
4871
     */
4872

4873
    length = 0u;
4874

4875
    if (path == NULL) {
4876
        return;
4877
    }
4878

4879
    length = strlen(path);
4880
    if (length >= 3u
4881
            && path[0] == '/'
4882
            && ((path[1] >= 'A' && path[1] <= 'Z')
4883
                || (path[1] >= 'a' && path[1] <= 'z'))
4884
            && path[2] == '/') {
4885
        path[0] = path[1];
4886
        path[1] = ':';
4887
    }
4888
#else
4889
    (void)path;
9✔
4890
#endif
4891
}
9✔
4892

4893

4894
static int
4895
is_dev_null_path(char const *path)
×
4896
{
4897
    if (path == NULL || path[0] == '\0') {
×
4898
        return 0;
4899
    }
4900
#if defined(_WIN32)
4901
    if (_stricmp(path, "nul") == 0) {
4902
        return 1;
4903
    }
4904
#endif
4905
    return strcmp(path, "/dev/null") == 0;
×
4906
}
4907

4908

4909
static int
4910
sixel_encoder_threads_token_is_auto(char const *text)
×
4911
{
4912
    if (text == NULL) {
×
4913
        return 0;
4914
    }
4915

4916
    if ((text[0] == 'a' || text[0] == 'A') &&
×
4917
        (text[1] == 'u' || text[1] == 'U') &&
×
4918
        (text[2] == 't' || text[2] == 'T') &&
×
4919
        (text[3] == 'o' || text[3] == 'O') &&
×
4920
        text[4] == '\0') {
×
4921
        return 1;
×
4922
    }
4923

4924
    return 0;
4925
}
4926

4927
static int
4928
sixel_encoder_parse_threads_argument(char const *text, int *value)
×
4929
{
4930
    long parsed;
×
4931
    char *endptr;
×
4932

4933
    parsed = 0L;
×
4934
    endptr = NULL;
×
4935

4936
    if (text == NULL || value == NULL) {
×
4937
        return 0;
4938
    }
4939

4940
    if (sixel_encoder_threads_token_is_auto(text) != 0) {
×
4941
        *value = 0;
×
4942
        return 1;
×
4943
    }
4944

4945
    errno = 0;
×
4946
    parsed = strtol(text, &endptr, 10);
×
4947
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
×
4948
        return 0;
4949
    }
4950

4951
    if (parsed < 1L || parsed > (long)INT_MAX) {
×
4952
        return 0;
4953
    }
4954

4955
    *value = (int)parsed;
×
4956
    return 1;
×
4957
}
4958

4959
/* set an option flag to encoder object */
4960
SIXELAPI SIXELSTATUS
4961
sixel_encoder_setopt(
717✔
4962
    sixel_encoder_t /* in */ *encoder,
4963
    int             /* in */ arg,
4964
    char const      /* in */ *value)
4965
{
4966
    SIXELSTATUS status = SIXEL_FALSE;
717✔
4967
    int number;
717✔
4968
    int parsed;
717✔
4969
    char unit[32];
717✔
4970
    char lowered[16];
717✔
4971
    size_t len;
717✔
4972
    size_t i;
717✔
4973
    long parsed_reqcolors;
717✔
4974
    char *endptr;
717✔
4975
    int forced_palette;
717✔
4976
    char *opt_copy;
717✔
4977
    char const *drcs_arg_delim;
717✔
4978
    char const *drcs_arg_charset;
717✔
4979
    char const *drcs_arg_second_delim;
717✔
4980
    char const *drcs_arg_path;
717✔
4981
    size_t drcs_arg_path_length;
717✔
4982
    size_t drcs_segment_length;
717✔
4983
    char drcs_segment[32];
717✔
4984
    int drcs_mmv_value;
717✔
4985
    long drcs_charset_value;
717✔
4986
    unsigned int drcs_charset_limit;
717✔
4987
    sixel_option_choice_result_t match_result;
717✔
4988
    int match_value;
717✔
4989
    char match_detail[128];
717✔
4990
    char match_message[256];
717✔
4991
    int png_argument_has_prefix = 0;
717✔
4992
    char const *png_path_view = NULL;
717✔
4993
    size_t png_path_length;
717✔
4994
    char cell_message[256];
717✔
4995
    char const *cell_detail;
717✔
4996
    unsigned int path_flags;
717✔
4997
    char const *mapfile_view;
717✔
4998
    int path_check;
717✔
4999

5000
    sixel_encoder_ref(encoder);
717✔
5001
    opt_copy = NULL;
717✔
5002
    path_flags = 0u;
717✔
5003
    mapfile_view = NULL;
717✔
5004
    path_check = 0;
717✔
5005

5006
    switch(arg) {
717!
5007
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
30✔
5008
        if (*value == '\0') {
30!
5009
            sixel_helper_set_additional_message(
×
5010
                "no file name specified.");
5011
            status = SIXEL_BAD_ARGUMENT;
×
5012
            goto end;
×
5013
        }
5014
        if (is_png_target(value)) {
30✔
5015
            encoder->output_is_png = 1;
9✔
5016
            png_argument_has_prefix =
18✔
5017
                (value != NULL)
5018
                && (strncmp(value, "png:", 4) == 0);
9!
5019
            png_path_view = png_target_payload_view(value);
9✔
5020
            if (png_argument_has_prefix
9!
5021
                    && (png_path_view == NULL
6!
5022
                        || png_path_view[0] == '\0')) {
6!
5023
                sixel_helper_set_additional_message(
×
5024
                    "sixel_encoder_setopt: missing target after the \"png:\" "
5025
                    "prefix. use png:- or png:<path> with a non-empty payload."
5026
                );
5027
                status = SIXEL_BAD_ARGUMENT;
×
5028
                goto end;
×
5029
            }
5030
            encoder->output_png_to_stdout =
12✔
5031
                (png_path_view != NULL)
5032
                && (strcmp(png_path_view, "-") == 0);
9!
5033
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
9✔
5034
            encoder->png_output_path = NULL;
9✔
5035
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
9✔
5036
            encoder->sixel_output_path = NULL;
9✔
5037
            if (! encoder->output_png_to_stdout) {
9!
5038
                /*
5039
                 * +-----------------------------------------+
5040
                 * |  PNG target normalization               |
5041
                 * +-----------------------------------------+
5042
                 * |  Raw input  |  Stored file path         |
5043
                 * |-------------+---------------------------|
5044
                 * |  png:-      |  "-" (stdout sentinel)    |
5045
                 * |  png:/foo   |  "/foo"                   |
5046
                 * +-----------------------------------------+
5047
                 * Strip the "png:" prefix so the decoder can
5048
                 * pass the true filesystem path to libpng
5049
                 * while the CLI retains its shorthand.
5050
                 */
5051
                png_path_view = value;
9✔
5052
                if (strncmp(value, "png:", 4) == 0) {
9✔
5053
                    png_path_view = value + 4;
6✔
5054
                }
5055
                if (png_path_view[0] == '\0') {
9!
5056
                    sixel_helper_set_additional_message(
×
5057
                        "sixel_encoder_setopt: PNG output path is empty.");
5058
                    status = SIXEL_BAD_ARGUMENT;
×
5059
                    goto end;
×
5060
                }
5061
                png_path_length = strlen(png_path_view);
9✔
5062
                encoder->png_output_path =
18✔
5063
                    (char *)sixel_allocator_malloc(
9✔
5064
                        encoder->allocator, png_path_length + 1u);
5065
                if (encoder->png_output_path == NULL) {
9!
5066
                    sixel_helper_set_additional_message(
×
5067
                        "sixel_encoder_setopt: sixel_allocator_malloc() "
5068
                        "failed for PNG output path.");
5069
                    status = SIXEL_BAD_ALLOCATION;
×
5070
                    goto end;
×
5071
                }
5072
                if (png_path_view != NULL) {
9!
5073
                    (void)sixel_compat_strcpy(encoder->png_output_path,
9✔
5074
                                              png_path_length + 1u,
5075
                                              png_path_view);
5076
                } else {
5077
                    encoder->png_output_path[0] = '\0';
5078
                }
5079
                normalise_windows_drive_path(encoder->png_output_path);
9✔
5080
            }
5081
        } else {
5082
            encoder->output_is_png = 0;
21✔
5083
            encoder->output_png_to_stdout = 0;
21✔
5084
            png_argument_has_prefix = 0;
21✔
5085
            png_path_view = NULL;
21✔
5086
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
21✔
5087
            encoder->png_output_path = NULL;
21✔
5088
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
21✔
5089
            encoder->sixel_output_path = NULL;
21✔
5090
            if (encoder->clipboard_output_path != NULL) {
21!
5091
                (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
5092
                sixel_allocator_free(encoder->allocator,
×
5093
                                     encoder->clipboard_output_path);
×
5094
                encoder->clipboard_output_path = NULL;
×
5095
            }
5096
            encoder->clipboard_output_active = 0;
21✔
5097
            encoder->clipboard_output_format[0] = '\0';
21✔
5098
            {
5099
                sixel_clipboard_spec_t clipboard_spec;
21✔
5100
                SIXELSTATUS clip_status;
21✔
5101
                char *spool_path;
21✔
5102
                int spool_fd;
21✔
5103

5104
                clipboard_spec.is_clipboard = 0;
21✔
5105
                clipboard_spec.format[0] = '\0';
21✔
5106
                clip_status = SIXEL_OK;
21✔
5107
                spool_path = NULL;
21✔
5108
                spool_fd = (-1);
21✔
5109

5110
                if (sixel_clipboard_parse_spec(value, &clipboard_spec)
21!
UNCOV
5111
                        && clipboard_spec.is_clipboard) {
×
UNCOV
5112
                    clip_status = clipboard_create_spool(
×
5113
                        encoder->allocator,
5114
                        "clipboard-out",
5115
                        &spool_path,
5116
                        &spool_fd);
UNCOV
5117
                    if (SIXEL_FAILED(clip_status)) {
×
5118
                        status = clip_status;
×
5119
                        goto end;
×
5120
                    }
UNCOV
5121
                    clipboard_select_format(
×
UNCOV
5122
                        encoder->clipboard_output_format,
×
5123
                        sizeof(encoder->clipboard_output_format),
5124
                        clipboard_spec.format,
5125
                        "sixel");
UNCOV
5126
                    if (encoder->outfd
×
5127
                            && encoder->outfd != STDOUT_FILENO
1!
UNCOV
5128
                            && encoder->outfd != STDERR_FILENO) {
×
5129
                        (void)sixel_compat_close(encoder->outfd);
×
5130
                    }
UNCOV
5131
                    encoder->outfd = spool_fd;
×
UNCOV
5132
                    spool_fd = (-1);
×
UNCOV
5133
                    encoder->sixel_output_path = spool_path;
×
UNCOV
5134
                    encoder->clipboard_output_path = spool_path;
×
UNCOV
5135
                    spool_path = NULL;
×
UNCOV
5136
                    encoder->clipboard_output_active = 1;
×
UNCOV
5137
                    break;
×
5138
                }
5139

5140
                if (spool_fd >= 0) {
21!
5141
                    (void)sixel_compat_close(spool_fd);
5142
                }
5143
                if (spool_path != NULL) {
21!
5144
                    sixel_allocator_free(encoder->allocator, spool_path);
5145
                }
5146
            }
2!
5147
            if (strcmp(value, "-") != 0) {
21!
5148
                encoder->sixel_output_path = (char *)sixel_allocator_malloc(
42✔
5149
                    encoder->allocator, strlen(value) + 1);
21✔
5150
                if (encoder->sixel_output_path == NULL) {
21!
5151
                    sixel_helper_set_additional_message(
×
5152
                        "sixel_encoder_setopt: malloc() failed for output path.");
5153
                    status = SIXEL_BAD_ALLOCATION;
×
5154
                    goto end;
×
5155
                }
5156
                (void)sixel_compat_strcpy(encoder->sixel_output_path,
21✔
5157
                                          strlen(value) + 1,
21✔
5158
                                          value);
5159
            }
5160
        }
5161

5162
        if (!encoder->clipboard_output_active && strcmp(value, "-") != 0) {
30!
5163
            if (encoder->outfd && encoder->outfd != STDOUT_FILENO) {
30!
5164
                (void)sixel_compat_close(encoder->outfd);
×
5165
            }
5166
            encoder->outfd = sixel_compat_open(value,
30✔
5167
                                               O_RDWR | O_CREAT | O_TRUNC,
5168
                                               S_IRUSR | S_IWUSR);
5169
        }
5170
        break;
5171
    case SIXEL_OPTFLAG_ASSESSMENT:  /* a */
6✔
5172
        if (parse_assessment_sections(value,
6!
5173
                                      &encoder->assessment_sections) != 0) {
5174
            sixel_helper_set_additional_message(
×
5175
                "sixel_encoder_setopt: cannot parse assessment section list");
5176
            status = SIXEL_BAD_ARGUMENT;
×
5177
            goto end;
×
5178
        }
5179
        break;
5180
    case SIXEL_OPTFLAG_ASSESSMENT_FILE:  /* J */
3✔
5181
        encoder->assessment_json_path = value;
3✔
5182
        break;
3✔
5183
    case SIXEL_OPTFLAG_7BIT_MODE:  /* 7 */
15✔
5184
        encoder->f8bit = 0;
15✔
5185
        break;
15✔
5186
    case SIXEL_OPTFLAG_8BIT_MODE:  /* 8 */
18✔
5187
        encoder->f8bit = 1;
18✔
5188
        break;
18✔
UNCOV
5189
    case SIXEL_OPTFLAG_6REVERSIBLE:  /* 6 */
×
5190
        encoder->sixel_reversible = 1;
×
5191
        break;
×
UNCOV
5192
    case SIXEL_OPTFLAG_HAS_GRI_ARG_LIMIT:  /* R */
×
5193
        encoder->has_gri_arg_limit = 1;
×
5194
        break;
×
UNCOV
5195
    case SIXEL_OPTFLAG_PRECISION:  /* . */
×
5196
        match_result = sixel_option_match_choice(
×
5197
            value,
5198
            g_option_choices_precision,
5199
            sizeof(g_option_choices_precision) /
5200
                sizeof(g_option_choices_precision[0]),
5201
            &match_value,
5202
            match_detail,
5203
            sizeof(match_detail));
5204
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5205
            status = sixel_encoder_apply_precision_override(
×
5206
                encoder,
5207
                (sixel_encoder_precision_mode_t)match_value);
5208
            if (SIXEL_FAILED(status)) {
×
5209
                goto end;
×
5210
            }
5211
        } else {
5212
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5213
                sixel_option_report_ambiguous_prefix(
×
5214
                    value,
5215
                    match_detail,
5216
                    match_message,
5217
                    sizeof(match_message));
5218
            } else {
5219
                sixel_option_report_invalid_choice(
×
5220
                    "precision accepts auto, 8bit, or float32.",
5221
                    match_detail,
5222
                    match_message,
5223
                    sizeof(match_message));
5224
            }
5225
            status = SIXEL_BAD_ARGUMENT;
×
5226
            goto end;
×
5227
        }
5228
        break;
UNCOV
5229
    case SIXEL_OPTFLAG_THREADS:  /* = */
×
5230
        if (sixel_encoder_parse_threads_argument(value, &number) == 0) {
×
5231
            sixel_helper_set_additional_message(
×
5232
                "threads accepts positive integers or 'auto'.");
5233
            status = SIXEL_BAD_ARGUMENT;
×
5234
            goto end;
×
5235
        }
5236
        sixel_set_threads(number);
×
5237
        break;
×
5238
    case SIXEL_OPTFLAG_COLORS:  /* p */
27✔
5239
        forced_palette = 0;
27✔
5240
        errno = 0;
27✔
5241
        endptr = NULL;
27✔
5242
        if (*value == '!' && value[1] == '\0') {
27!
5243
            /*
5244
             * Force the default palette size even when the median cut
5245
             * finished early.
5246
             *
5247
             *   requested colors
5248
             *          |
5249
             *          v
5250
             *        [ 256 ]  <--- "-p!" triggers this shortcut
5251
             */
5252
            parsed_reqcolors = SIXEL_PALETTE_MAX;
5253
            forced_palette = 1;
5254
        } else {
5255
            parsed_reqcolors = strtol(value, &endptr, 10);
27✔
5256
            if (endptr != NULL && *endptr == '!') {
27!
5257
                forced_palette = 1;
×
5258
                ++endptr;
×
5259
            }
5260
            if (errno == ERANGE || endptr == value) {
27!
5261
                sixel_helper_set_additional_message(
×
5262
                    "cannot parse -p/--colors option.");
5263
                status = SIXEL_BAD_ARGUMENT;
×
5264
                goto end;
×
5265
            }
5266
            if (endptr != NULL && *endptr != '\0') {
27!
5267
                sixel_helper_set_additional_message(
×
5268
                    "cannot parse -p/--colors option.");
5269
                status = SIXEL_BAD_ARGUMENT;
×
5270
                goto end;
×
5271
            }
5272
        }
5273
        if (parsed_reqcolors < 1) {
27!
5274
            sixel_helper_set_additional_message(
×
5275
                "-p/--colors parameter must be 1 or more.");
5276
            status = SIXEL_BAD_ARGUMENT;
×
5277
            goto end;
×
5278
        }
5279
        if (parsed_reqcolors > SIXEL_PALETTE_MAX) {
27!
5280
            sixel_helper_set_additional_message(
×
5281
                "-p/--colors parameter must be less then or equal to 256.");
5282
            status = SIXEL_BAD_ARGUMENT;
×
5283
            goto end;
×
5284
        }
5285
        encoder->reqcolors = (int)parsed_reqcolors;
27✔
5286
        encoder->force_palette = forced_palette;
27✔
5287
        break;
27✔
5288
    case SIXEL_OPTFLAG_MAPFILE:  /* m */
27✔
5289
        mapfile_view = sixel_palette_strip_prefix(value, NULL);
27✔
5290
        if (mapfile_view == NULL) {
27!
5291
            mapfile_view = value;
×
5292
        }
5293
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
27✔
5294
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
5295
            SIXEL_OPTION_PATH_ALLOW_REMOTE |
5296
            SIXEL_OPTION_PATH_ALLOW_EMPTY;
5297
        path_check = sixel_option_validate_filesystem_path(
27✔
5298
            value,
5299
            mapfile_view,
5300
            path_flags);
5301
        if (path_check != 0) {
27✔
5302
            status = SIXEL_BAD_ARGUMENT;
3✔
5303
            goto end;
3✔
5304
        }
5305
        if (encoder->mapfile) {
24✔
5306
            sixel_allocator_free(encoder->allocator, encoder->mapfile);
3✔
5307
        }
5308
        encoder->mapfile = arg_strdup(value, encoder->allocator);
24✔
5309
        if (encoder->mapfile == NULL) {
24!
5310
            sixel_helper_set_additional_message(
×
5311
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
5312
            status = SIXEL_BAD_ALLOCATION;
×
5313
            goto end;
×
5314
        }
5315
        encoder->color_option = SIXEL_COLOR_OPTION_MAPFILE;
24✔
5316
        break;
24✔
UNCOV
5317
    case SIXEL_OPTFLAG_MAPFILE_OUTPUT:  /* M */
×
5318
        if (value == NULL || *value == '\0') {
×
5319
            sixel_helper_set_additional_message(
×
5320
                "sixel_encoder_setopt: mapfile-output path is empty.");
5321
            status = SIXEL_BAD_ARGUMENT;
×
5322
            goto end;
×
5323
        }
5324
        opt_copy = arg_strdup(value, encoder->allocator);
×
5325
        if (opt_copy == NULL) {
×
5326
            sixel_helper_set_additional_message(
×
5327
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
5328
            status = SIXEL_BAD_ALLOCATION;
×
5329
            goto end;
×
5330
        }
5331
        status = sixel_encoder_enable_quantized_capture(encoder, 1);
×
5332
        if (SIXEL_FAILED(status)) {
×
5333
            sixel_allocator_free(encoder->allocator, opt_copy);
5334
            goto end;
5335
        }
5336
        sixel_allocator_free(encoder->allocator, encoder->palette_output);
×
5337
        encoder->palette_output = opt_copy;
×
5338
        opt_copy = NULL;
×
5339
        break;
×
5340
    case SIXEL_OPTFLAG_MONOCHROME:  /* e */
15✔
5341
        encoder->color_option = SIXEL_COLOR_OPTION_MONOCHROME;
15✔
5342
        break;
15✔
5343
    case SIXEL_OPTFLAG_HIGH_COLOR:  /* I */
42✔
5344
        encoder->color_option = SIXEL_COLOR_OPTION_HIGHCOLOR;
42✔
5345
        break;
42✔
5346
    case SIXEL_OPTFLAG_BUILTIN_PALETTE:  /* b */
33✔
5347
        match_result = sixel_option_match_choice(
33✔
5348
            value,
5349
            g_option_choices_builtin_palette,
5350
            sizeof(g_option_choices_builtin_palette) /
5351
            sizeof(g_option_choices_builtin_palette[0]),
5352
            &match_value,
5353
            match_detail,
5354
            sizeof(match_detail));
5355
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
33✔
5356
            encoder->builtin_palette = match_value;
30✔
5357
        } else {
5358
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5359
                sixel_option_report_ambiguous_prefix(value,
×
5360
                                              match_detail,
5361
                                              match_message,
5362
                                              sizeof(match_message));
5363
            } else {
5364
                sixel_option_report_invalid_choice(
3✔
5365
                    "cannot parse builtin palette option.",
5366
                    match_detail,
5367
                    match_message,
5368
                    sizeof(match_message));
5369
            }
5370
            status = SIXEL_BAD_ARGUMENT;
3✔
5371
            goto end;
3✔
5372
        }
5373
        encoder->color_option = SIXEL_COLOR_OPTION_BUILTIN;
30✔
5374
        break;
30✔
5375
    case SIXEL_OPTFLAG_DIFFUSION:  /* d */
75✔
5376
        match_result = sixel_option_match_choice(
75✔
5377
            value,
5378
            g_option_choices_diffusion,
5379
            sizeof(g_option_choices_diffusion) /
5380
            sizeof(g_option_choices_diffusion[0]),
5381
            &match_value,
5382
            match_detail,
5383
            sizeof(match_detail));
5384
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
75✔
5385
            encoder->method_for_diffuse = match_value;
66✔
5386
        } else {
5387
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
9✔
5388
                sixel_option_report_ambiguous_prefix(value,
3✔
5389
                                              match_detail,
5390
                                              match_message,
5391
                                              sizeof(match_message));
5392
            } else {
5393
                sixel_option_report_invalid_choice(
6✔
5394
                    "specified diffusion method is not supported.",
5395
                    match_detail,
5396
                    match_message,
5397
                    sizeof(match_message));
5398
            }
5399
            status = SIXEL_BAD_ARGUMENT;
9✔
5400
            goto end;
9✔
5401
        }
5402
        break;
66✔
5403
    case SIXEL_OPTFLAG_DIFFUSION_SCAN:  /* y */
3✔
5404
        match_result = sixel_option_match_choice(
3✔
5405
            value,
5406
            g_option_choices_diffusion_scan,
5407
            sizeof(g_option_choices_diffusion_scan) /
5408
            sizeof(g_option_choices_diffusion_scan[0]),
5409
            &match_value,
5410
            match_detail,
5411
            sizeof(match_detail));
5412
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
3!
5413
            encoder->method_for_scan = match_value;
3✔
5414
        } else {
5415
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5416
                sixel_option_report_ambiguous_prefix(value,
×
5417
                                              match_detail,
5418
                                              match_message,
5419
                                              sizeof(match_message));
5420
            } else {
5421
                sixel_option_report_invalid_choice(
×
5422
                    "specified diffusion scan is not supported.",
5423
                    match_detail,
5424
                    match_message,
5425
                    sizeof(match_message));
5426
            }
5427
            status = SIXEL_BAD_ARGUMENT;
×
5428
            goto end;
×
5429
        }
5430
        break;
3✔
UNCOV
5431
    case SIXEL_OPTFLAG_DIFFUSION_CARRY:  /* Y */
×
5432
        match_result = sixel_option_match_choice(
×
5433
            value,
5434
            g_option_choices_diffusion_carry,
5435
            sizeof(g_option_choices_diffusion_carry) /
5436
            sizeof(g_option_choices_diffusion_carry[0]),
5437
            &match_value,
5438
            match_detail,
5439
            sizeof(match_detail));
5440
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5441
            encoder->method_for_carry = match_value;
×
5442
        } else {
5443
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5444
                sixel_option_report_ambiguous_prefix(value,
×
5445
                                              match_detail,
5446
                                              match_message,
5447
                                              sizeof(match_message));
5448
            } else {
5449
                sixel_option_report_invalid_choice(
×
5450
                    "specified diffusion carry mode is not supported.",
5451
                    match_detail,
5452
                    match_message,
5453
                    sizeof(match_message));
5454
            }
5455
            status = SIXEL_BAD_ARGUMENT;
×
5456
            goto end;
×
5457
        }
5458
        break;
×
5459
    case SIXEL_OPTFLAG_FIND_LARGEST:  /* f */
15✔
5460
        if (value != NULL) {
15!
5461
            match_result = sixel_option_match_choice(
15✔
5462
                value,
5463
                g_option_choices_find_largest,
5464
                sizeof(g_option_choices_find_largest) /
5465
                sizeof(g_option_choices_find_largest[0]),
5466
                &match_value,
5467
                match_detail,
5468
                sizeof(match_detail));
5469
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
5470
                encoder->method_for_largest = match_value;
12✔
5471
            } else {
5472
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5473
                    sixel_option_report_ambiguous_prefix(value,
×
5474
                                                  match_detail,
5475
                                                  match_message,
5476
                                                  sizeof(match_message));
5477
                } else {
5478
                    sixel_option_report_invalid_choice(
3✔
5479
                        "specified finding method is not supported.",
5480
                        match_detail,
5481
                        match_message,
5482
                        sizeof(match_message));
5483
                }
5484
                status = SIXEL_BAD_ARGUMENT;
3✔
5485
                goto end;
3✔
5486
            }
5487
        }
5488
        break;
5489
    case SIXEL_OPTFLAG_SELECT_COLOR:  /* s */
21✔
5490
        match_result = sixel_option_match_choice(
21✔
5491
            value,
5492
            g_option_choices_select_color,
5493
            sizeof(g_option_choices_select_color) /
5494
            sizeof(g_option_choices_select_color[0]),
5495
            &match_value,
5496
            match_detail,
5497
            sizeof(match_detail));
5498
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
21✔
5499
            encoder->method_for_rep = match_value;
15✔
5500
        } else {
5501
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
6✔
5502
                sixel_option_report_ambiguous_prefix(value,
3✔
5503
                                              match_detail,
5504
                                              match_message,
5505
                                              sizeof(match_message));
5506
            } else {
5507
                sixel_option_report_invalid_choice(
3✔
5508
                    "specified finding method is not supported.",
5509
                    match_detail,
5510
                    match_message,
5511
                    sizeof(match_message));
5512
            }
5513
            status = SIXEL_BAD_ARGUMENT;
6✔
5514
            goto end;
6✔
5515
        }
5516
        break;
15✔
UNCOV
5517
    case SIXEL_OPTFLAG_QUANTIZE_MODEL:  /* Q */
×
5518
        match_result = sixel_option_match_choice(
×
5519
            value,
5520
            g_option_choices_quantize_model,
5521
            sizeof(g_option_choices_quantize_model) /
5522
            sizeof(g_option_choices_quantize_model[0]),
5523
            &match_value,
5524
            match_detail,
5525
            sizeof(match_detail));
5526
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5527
            encoder->quantize_model = match_value;
×
5528
        } else {
5529
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5530
                sixel_option_report_ambiguous_prefix(value,
×
5531
                                              match_detail,
5532
                                              match_message,
5533
                                              sizeof(match_message));
5534
            } else {
5535
                sixel_option_report_invalid_choice(
×
5536
                    "sixel_encoder_setopt: unsupported quantize model.",
5537
                    match_detail,
5538
                    match_message,
5539
                    sizeof(match_message));
5540
            }
5541
            status = SIXEL_BAD_ARGUMENT;
×
5542
            goto end;
×
5543
        }
5544
        break;
×
UNCOV
5545
    case SIXEL_OPTFLAG_FINAL_MERGE:  /* F */
×
5546
        match_result = sixel_option_match_choice(
×
5547
            value,
5548
            g_option_choices_final_merge,
5549
            sizeof(g_option_choices_final_merge) /
5550
            sizeof(g_option_choices_final_merge[0]),
5551
            &match_value,
5552
            match_detail,
5553
            sizeof(match_detail));
5554
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5555
            encoder->final_merge_mode = match_value;
×
5556
        } else {
5557
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5558
                sixel_option_report_ambiguous_prefix(value,
×
5559
                                              match_detail,
5560
                                              match_message,
5561
                                              sizeof(match_message));
5562
            } else {
5563
                sixel_option_report_invalid_choice(
×
5564
                    "specified final merge policy is not supported.",
5565
                    match_detail,
5566
                    match_message,
5567
                    sizeof(match_message));
5568
            }
5569
            status = SIXEL_BAD_ARGUMENT;
×
5570
            goto end;
×
5571
        }
5572
        break;
×
5573
    case SIXEL_OPTFLAG_CROP:  /* c */
15✔
5574
#if HAVE_SSCANF_S
5575
        number = sscanf_s(value, "%dx%d+%d+%d",
5576
                          &encoder->clipwidth, &encoder->clipheight,
5577
                          &encoder->clipx, &encoder->clipy);
5578
#else
5579
        number = sscanf(value, "%dx%d+%d+%d",
15✔
5580
                        &encoder->clipwidth, &encoder->clipheight,
5581
                        &encoder->clipx, &encoder->clipy);
5582
#endif  /* HAVE_SSCANF_S */
5583
        if (number != 4) {
15!
5584
            status = SIXEL_BAD_ARGUMENT;
×
5585
            goto end;
×
5586
        }
5587
        if (encoder->clipwidth <= 0 || encoder->clipheight <= 0) {
15!
5588
            status = SIXEL_BAD_ARGUMENT;
×
5589
            goto end;
×
5590
        }
5591
        if (encoder->clipx < 0 || encoder->clipy < 0) {
15!
5592
            status = SIXEL_BAD_ARGUMENT;
×
5593
            goto end;
×
5594
        }
5595
        encoder->clipfirst = 0;
15✔
5596
        break;
15✔
5597
    case SIXEL_OPTFLAG_WIDTH:  /* w */
75✔
5598
#if HAVE_SSCANF_S
5599
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
5600
#else
5601
        parsed = sscanf(value, "%d%2s", &number, unit);
75✔
5602
#endif  /* HAVE_SSCANF_S */
5603
        if (parsed == 2 && strcmp(unit, "%") == 0) {
75!
5604
            encoder->pixelwidth = (-1);
12✔
5605
            encoder->percentwidth = number;
12✔
UNCOV
5606
        } else if (parsed == 2 && strcmp(unit, "c") == 0) {
×
5607
            status = sixel_encoder_ensure_cell_size(encoder);
×
5608
            if (SIXEL_FAILED(status)) {
×
5609
                cell_detail = sixel_helper_get_additional_message();
×
5610
                if (cell_detail != NULL && cell_detail[0] != '\0') {
×
5611
                    (void) snprintf(cell_message,
×
5612
                                    sizeof(cell_message),
5613
                                    "cannot determine terminal cell size for "
5614
                                    "-w/--width option: %s",
5615
                                    cell_detail);
5616
                    sixel_helper_set_additional_message(cell_message);
×
5617
                } else {
5618
                    sixel_helper_set_additional_message(
×
5619
                        "cannot determine terminal cell size for "
5620
                        "-w/--width option.");
5621
                }
5622
                goto end;
×
5623
            }
5624
            /*
5625
             * Terminal cell units map the requested column count to pixels.
5626
             * The cell size probe caches the tty geometry so repeated calls
5627
             * reuse the same measurement.
5628
             */
5629
            encoder->pixelwidth = number * encoder->cell_width;
×
5630
            encoder->percentwidth = (-1);
×
5631
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
63!
5632
            encoder->pixelwidth = number;
51✔
5633
            encoder->percentwidth = (-1);
51✔
5634
        } else if (strcmp(value, "auto") == 0) {
12✔
5635
            encoder->pixelwidth = (-1);
9✔
5636
            encoder->percentwidth = (-1);
9✔
5637
        } else {
5638
            sixel_helper_set_additional_message(
3✔
5639
                "cannot parse -w/--width option.");
5640
            status = SIXEL_BAD_ARGUMENT;
3✔
5641
            goto end;
3✔
5642
        }
5643
        if (encoder->clipwidth) {
72✔
5644
            encoder->clipfirst = 1;
6✔
5645
        }
5646
        break;
5647
    case SIXEL_OPTFLAG_HEIGHT:  /* h */
66✔
5648
#if HAVE_SSCANF_S
5649
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
5650
#else
5651
        parsed = sscanf(value, "%d%2s", &number, unit);
66✔
5652
#endif  /* HAVE_SSCANF_S */
5653
        if (parsed == 2 && strcmp(unit, "%") == 0) {
66!
5654
            encoder->pixelheight = (-1);
9✔
5655
            encoder->percentheight = number;
9✔
UNCOV
5656
        } else if (parsed == 2 && strcmp(unit, "c") == 0) {
×
5657
            status = sixel_encoder_ensure_cell_size(encoder);
×
5658
            if (SIXEL_FAILED(status)) {
×
5659
                cell_detail = sixel_helper_get_additional_message();
×
5660
                if (cell_detail != NULL && cell_detail[0] != '\0') {
×
5661
                    (void) snprintf(cell_message,
×
5662
                                    sizeof(cell_message),
5663
                                    "cannot determine terminal cell size for "
5664
                                    "-h/--height option: %s",
5665
                                    cell_detail);
5666
                    sixel_helper_set_additional_message(cell_message);
×
5667
                } else {
5668
                    sixel_helper_set_additional_message(
×
5669
                        "cannot determine terminal cell size for "
5670
                        "-h/--height option.");
5671
                }
5672
                goto end;
×
5673
            }
5674
            /*
5675
             * Rows specified in terminal cells use the current tty metrics to
5676
             * translate into pixel counts before scaling.
5677
             */
5678
            encoder->pixelheight = number * encoder->cell_height;
×
5679
            encoder->percentheight = (-1);
×
5680
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
57!
5681
            encoder->pixelheight = number;
45✔
5682
            encoder->percentheight = (-1);
45✔
5683
        } else if (strcmp(value, "auto") == 0) {
12✔
5684
            encoder->pixelheight = (-1);
9✔
5685
            encoder->percentheight = (-1);
9✔
5686
        } else {
5687
            sixel_helper_set_additional_message(
3✔
5688
                "cannot parse -h/--height option.");
5689
            status = SIXEL_BAD_ARGUMENT;
3✔
5690
            goto end;
3✔
5691
        }
5692
        if (encoder->clipheight) {
63✔
5693
            encoder->clipfirst = 1;
3✔
5694
        }
5695
        break;
5696
    case SIXEL_OPTFLAG_RESAMPLING:  /* r */
60✔
5697
        match_result = sixel_option_match_choice(
60✔
5698
            value,
5699
            g_option_choices_resampling,
5700
            sizeof(g_option_choices_resampling) /
5701
            sizeof(g_option_choices_resampling[0]),
5702
            &match_value,
5703
            match_detail,
5704
            sizeof(match_detail));
5705
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
60✔
5706
            encoder->method_for_resampling = match_value;
48✔
5707
        } else {
5708
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
12!
5709
                sixel_option_report_ambiguous_prefix(value,
×
5710
                                              match_detail,
5711
                                              match_message,
5712
                                              sizeof(match_message));
5713
            } else {
5714
                sixel_option_report_invalid_choice(
12✔
5715
                    "specified desampling method is not supported.",
5716
                    match_detail,
5717
                    match_message,
5718
                    sizeof(match_message));
5719
            }
5720
            status = SIXEL_BAD_ARGUMENT;
12✔
5721
            goto end;
12✔
5722
        }
5723
        break;
48✔
5724
    case SIXEL_OPTFLAG_QUALITY:  /* q */
18✔
5725
        match_result = sixel_option_match_choice(
18✔
5726
            value,
5727
            g_option_choices_quality,
5728
            sizeof(g_option_choices_quality) /
5729
            sizeof(g_option_choices_quality[0]),
5730
            &match_value,
5731
            match_detail,
5732
            sizeof(match_detail));
5733
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
18✔
5734
            encoder->quality_mode = match_value;
15✔
5735
        } else {
5736
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5737
                sixel_option_report_ambiguous_prefix(value,
×
5738
                                              match_detail,
5739
                                              match_message,
5740
                                              sizeof(match_message));
5741
            } else {
5742
                sixel_option_report_invalid_choice(
3✔
5743
                    "cannot parse quality option.",
5744
                    match_detail,
5745
                    match_message,
5746
                    sizeof(match_message));
5747
            }
5748
            status = SIXEL_BAD_ARGUMENT;
3✔
5749
            goto end;
3✔
5750
        }
5751
        break;
15✔
5752
    case SIXEL_OPTFLAG_LOOPMODE:  /* l */
15✔
5753
        match_result = sixel_option_match_choice(
15✔
5754
            value,
5755
            g_option_choices_loopmode,
5756
            sizeof(g_option_choices_loopmode) /
5757
            sizeof(g_option_choices_loopmode[0]),
5758
            &match_value,
5759
            match_detail,
5760
            sizeof(match_detail));
5761
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
5762
            encoder->loop_mode = match_value;
12✔
5763
        } else {
5764
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5765
                sixel_option_report_ambiguous_prefix(value,
×
5766
                                              match_detail,
5767
                                              match_message,
5768
                                              sizeof(match_message));
5769
            } else {
5770
                sixel_option_report_invalid_choice(
3✔
5771
                    "cannot parse loop-control option.",
5772
                    match_detail,
5773
                    match_message,
5774
                    sizeof(match_message));
5775
            }
5776
            status = SIXEL_BAD_ARGUMENT;
3✔
5777
            goto end;
3✔
5778
        }
5779
        break;
12✔
5780
    case SIXEL_OPTFLAG_PALETTE_TYPE:  /* t */
27✔
5781
        match_result = sixel_option_match_choice(
27✔
5782
            value,
5783
            g_option_choices_palette_type,
5784
            sizeof(g_option_choices_palette_type) /
5785
            sizeof(g_option_choices_palette_type[0]),
5786
            &match_value,
5787
            match_detail,
5788
            sizeof(match_detail));
5789
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
27✔
5790
            encoder->palette_type = match_value;
24✔
5791
        } else {
5792
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
5793
                sixel_option_report_ambiguous_prefix(value,
×
5794
                                              match_detail,
5795
                                              match_message,
5796
                                              sizeof(match_message));
5797
            } else {
5798
                sixel_option_report_invalid_choice(
3✔
5799
                    "cannot parse palette type option.",
5800
                    match_detail,
5801
                    match_message,
5802
                    sizeof(match_message));
5803
            }
5804
            status = SIXEL_BAD_ARGUMENT;
3✔
5805
            goto end;
3✔
5806
        }
5807
        break;
24✔
5808
    case SIXEL_OPTFLAG_BGCOLOR:  /* B */
45✔
5809
        /* parse --bgcolor option */
5810
        if (encoder->bgcolor) {
45✔
5811
            sixel_allocator_free(encoder->allocator, encoder->bgcolor);
6✔
5812
            encoder->bgcolor = NULL;
6✔
5813
        }
5814
        status = sixel_parse_x_colorspec(&encoder->bgcolor,
45✔
5815
                                         value,
5816
                                         encoder->allocator);
5817
        if (SIXEL_FAILED(status)) {
45✔
5818
            sixel_helper_set_additional_message(
21✔
5819
                "cannot parse bgcolor option.");
5820
            status = SIXEL_BAD_ARGUMENT;
21✔
5821
            goto end;
21✔
5822
        }
5823
        break;
UNCOV
5824
    case SIXEL_OPTFLAG_INSECURE:  /* k */
×
UNCOV
5825
        encoder->finsecure = 1;
×
UNCOV
5826
        break;
×
5827
    case SIXEL_OPTFLAG_INVERT:  /* i */
6✔
5828
        encoder->finvert = 1;
6✔
5829
        break;
6✔
5830
    case SIXEL_OPTFLAG_USE_MACRO:  /* u */
6✔
5831
        encoder->fuse_macro = 1;
6✔
5832
        break;
6✔
5833
    case SIXEL_OPTFLAG_MACRO_NUMBER:  /* n */
5834
        encoder->macro_number = atoi(value);
3✔
5835
        if (encoder->macro_number < 0) {
3!
5836
            status = SIXEL_BAD_ARGUMENT;
×
5837
            goto end;
×
5838
        }
5839
        break;
5840
    case SIXEL_OPTFLAG_IGNORE_DELAY:  /* g */
6✔
5841
        encoder->fignore_delay = 1;
6✔
5842
        break;
6✔
5843
    case SIXEL_OPTFLAG_VERBOSE:  /* v */
9✔
5844
        encoder->verbose = 1;
9✔
5845
        sixel_helper_set_loader_trace(1);
9✔
5846
        break;
9✔
UNCOV
5847
    case SIXEL_OPTFLAG_LOADERS:  /* L */
×
5848
        if (encoder->loader_order != NULL) {
×
5849
            sixel_allocator_free(encoder->allocator,
×
5850
                                 encoder->loader_order);
5851
            encoder->loader_order = NULL;
×
5852
        }
5853
        if (value != NULL && *value != '\0') {
×
5854
            encoder->loader_order = arg_strdup(value,
×
5855
                                               encoder->allocator);
5856
            if (encoder->loader_order == NULL) {
×
5857
                sixel_helper_set_additional_message(
×
5858
                    "sixel_encoder_setopt: "
5859
                    "sixel_allocator_malloc() failed.");
5860
                status = SIXEL_BAD_ALLOCATION;
×
5861
                goto end;
×
5862
            }
5863
        }
5864
        break;
5865
    case SIXEL_OPTFLAG_STATIC:  /* S */
6✔
5866
        encoder->fstatic = 1;
6✔
5867
        break;
6✔
UNCOV
5868
    case SIXEL_OPTFLAG_DRCS:  /* @ */
×
5869
        encoder->fdrcs = 1;
×
5870
        drcs_arg_delim = NULL;
×
5871
        drcs_arg_charset = NULL;
×
5872
        drcs_arg_second_delim = NULL;
×
5873
        drcs_arg_path = NULL;
×
5874
        drcs_arg_path_length = 0u;
×
5875
        drcs_segment_length = 0u;
×
5876
        drcs_mmv_value = 2;
×
5877
        drcs_charset_value = 1L;
×
5878
        drcs_charset_limit = 0u;
×
5879
        if (value != NULL && *value != '\0') {
×
5880
            drcs_arg_delim = strchr(value, ':');
×
5881
            if (drcs_arg_delim == NULL) {
×
5882
                drcs_segment_length = strlen(value);
×
5883
                if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5884
                    sixel_helper_set_additional_message(
×
5885
                        "DRCS mapping revision is too long.");
5886
                    status = SIXEL_BAD_ARGUMENT;
×
5887
                    goto end;
×
5888
                }
5889
                memcpy(drcs_segment, value, drcs_segment_length);
×
5890
                drcs_segment[drcs_segment_length] = '\0';
×
5891
                errno = 0;
×
5892
                endptr = NULL;
×
5893
                drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
5894
                if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
5895
                    sixel_helper_set_additional_message(
×
5896
                        "cannot parse DRCS option.");
5897
                    status = SIXEL_BAD_ARGUMENT;
×
5898
                    goto end;
×
5899
                }
5900
            } else {
5901
                if (drcs_arg_delim != value) {
×
5902
                    drcs_segment_length =
×
5903
                        (size_t)(drcs_arg_delim - value);
×
5904
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5905
                        sixel_helper_set_additional_message(
×
5906
                            "DRCS mapping revision is too long.");
5907
                        status = SIXEL_BAD_ARGUMENT;
×
5908
                        goto end;
×
5909
                    }
5910
                    memcpy(drcs_segment, value, drcs_segment_length);
×
5911
                    drcs_segment[drcs_segment_length] = '\0';
×
5912
                    errno = 0;
×
5913
                    endptr = NULL;
×
5914
                    drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
5915
                    if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
5916
                        sixel_helper_set_additional_message(
×
5917
                            "cannot parse DRCS option.");
5918
                        status = SIXEL_BAD_ARGUMENT;
×
5919
                        goto end;
×
5920
                    }
5921
                }
5922
                drcs_arg_charset = drcs_arg_delim + 1;
×
5923
                drcs_arg_second_delim = strchr(drcs_arg_charset, ':');
×
5924
                if (drcs_arg_second_delim != NULL) {
×
5925
                    if (drcs_arg_second_delim != drcs_arg_charset) {
×
5926
                        drcs_segment_length =
×
5927
                            (size_t)(drcs_arg_second_delim - drcs_arg_charset);
×
5928
                        if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5929
                            sixel_helper_set_additional_message(
×
5930
                                "DRCS charset number is too long.");
5931
                            status = SIXEL_BAD_ARGUMENT;
×
5932
                            goto end;
×
5933
                        }
5934
                        memcpy(drcs_segment,
×
5935
                               drcs_arg_charset,
5936
                               drcs_segment_length);
5937
                        drcs_segment[drcs_segment_length] = '\0';
×
5938
                        errno = 0;
×
5939
                        endptr = NULL;
×
5940
                        drcs_charset_value = strtol(drcs_segment,
×
5941
                                                    &endptr,
5942
                                                    10);
5943
                        if (errno != 0 || endptr == drcs_segment ||
×
5944
                                *endptr != '\0') {
×
5945
                            sixel_helper_set_additional_message(
×
5946
                                "cannot parse DRCS charset number.");
5947
                            status = SIXEL_BAD_ARGUMENT;
×
5948
                            goto end;
×
5949
                        }
5950
                    }
5951
                    drcs_arg_path = drcs_arg_second_delim + 1;
×
5952
                    drcs_arg_path_length = strlen(drcs_arg_path);
×
5953
                    if (drcs_arg_path_length == 0u) {
×
5954
                        drcs_arg_path = NULL;
×
5955
                    }
5956
                } else if (*drcs_arg_charset != '\0') {
×
5957
                    drcs_segment_length = strlen(drcs_arg_charset);
×
5958
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
5959
                        sixel_helper_set_additional_message(
×
5960
                            "DRCS charset number is too long.");
5961
                        status = SIXEL_BAD_ARGUMENT;
×
5962
                        goto end;
×
5963
                    }
5964
                    memcpy(drcs_segment,
×
5965
                           drcs_arg_charset,
5966
                           drcs_segment_length);
5967
                    drcs_segment[drcs_segment_length] = '\0';
×
5968
                    errno = 0;
×
5969
                    endptr = NULL;
×
5970
                    drcs_charset_value = strtol(drcs_segment,
×
5971
                                                &endptr,
5972
                                                10);
5973
                    if (errno != 0 || endptr == drcs_segment ||
×
5974
                            *endptr != '\0') {
×
5975
                        sixel_helper_set_additional_message(
×
5976
                            "cannot parse DRCS charset number.");
5977
                        status = SIXEL_BAD_ARGUMENT;
×
5978
                        goto end;
×
5979
                    }
5980
                }
5981
            }
5982
        }
5983
        /*
5984
         * Layout of the DRCS option value:
5985
         *
5986
         *    value = <mmv>:<charset_no>:<path>
5987
         *          ^        ^                ^
5988
         *          |        |                |
5989
         *          |        |                +-- optional path that may reuse
5990
         *          |        |                    STDOUT when set to "-" or drop
5991
         *          |        |                    tiles when left blank
5992
         *          |        +-- charset number (defaults to 1 when omitted)
5993
         *          +-- mapping revision (defaults to 2 when omitted)
5994
         */
5995
        if (drcs_mmv_value < 0 || drcs_mmv_value > 3) {
×
5996
            sixel_helper_set_additional_message(
×
5997
                "unknown DRCS unicode mapping version.");
5998
            status = SIXEL_BAD_ARGUMENT;
×
5999
            goto end;
×
6000
        }
6001
        if (drcs_mmv_value == 0) {
×
6002
            drcs_charset_limit = 126u;
6003
        } else if (drcs_mmv_value == 1) {
×
6004
            drcs_charset_limit = 63u;
6005
        } else if (drcs_mmv_value == 2) {
×
6006
            drcs_charset_limit = 158u;
6007
        } else {
6008
            drcs_charset_limit = 697u;
×
6009
        }
6010
        if (drcs_charset_value < 1 ||
×
6011
            (unsigned long)drcs_charset_value > drcs_charset_limit) {
×
6012
            sixel_helper_set_additional_message(
×
6013
                "DRCS charset number is out of range.");
6014
            status = SIXEL_BAD_ARGUMENT;
×
6015
            goto end;
×
6016
        }
6017
        encoder->drcs_mmv = drcs_mmv_value;
×
6018
        encoder->drcs_charset_no = (unsigned short)drcs_charset_value;
×
6019
        if (encoder->tile_outfd >= 0
×
6020
            && encoder->tile_outfd != encoder->outfd
×
6021
            && encoder->tile_outfd != STDOUT_FILENO
×
6022
            && encoder->tile_outfd != STDERR_FILENO) {
×
6023
#if HAVE__CLOSE
6024
            (void) _close(encoder->tile_outfd);
6025
#else
6026
            (void) close(encoder->tile_outfd);
×
6027
#endif  /* HAVE__CLOSE */
6028
        }
6029
        encoder->tile_outfd = (-1);
×
6030
        if (drcs_arg_path != NULL) {
×
6031
            if (strcmp(drcs_arg_path, "-") == 0) {
×
6032
                encoder->tile_outfd = STDOUT_FILENO;
×
6033
            } else {
6034
#if HAVE__OPEN
6035
                encoder->tile_outfd = _open(drcs_arg_path,
6036
                                            O_RDWR|O_CREAT|O_TRUNC,
6037
                                            S_IRUSR|S_IWUSR);
6038
#else
6039
                encoder->tile_outfd = open(drcs_arg_path,
×
6040
                                           O_RDWR|O_CREAT|O_TRUNC,
6041
                                           S_IRUSR|S_IWUSR);
6042
#endif  /* HAVE__OPEN */
6043
                if (encoder->tile_outfd < 0) {
×
6044
                    sixel_helper_set_additional_message(
×
6045
                        "sixel_encoder_setopt: failed to open tile"
6046
                        " output path.");
6047
                    status = SIXEL_RUNTIME_ERROR;
×
6048
                    goto end;
×
6049
                }
6050
            }
6051
        }
6052
        break;
6053
    case SIXEL_OPTFLAG_PENETRATE:  /* P */
9✔
6054
        encoder->penetrate_multiplexer = 1;
9✔
6055
        break;
9✔
6056
    case SIXEL_OPTFLAG_ENCODE_POLICY:  /* E */
12✔
6057
        match_result = sixel_option_match_choice(
12✔
6058
            value,
6059
            g_option_choices_encode_policy,
6060
            sizeof(g_option_choices_encode_policy) /
6061
            sizeof(g_option_choices_encode_policy[0]),
6062
            &match_value,
6063
            match_detail,
6064
            sizeof(match_detail));
6065
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
12✔
6066
            encoder->encode_policy = match_value;
9✔
6067
        } else {
6068
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
6069
                sixel_option_report_ambiguous_prefix(value,
×
6070
                                              match_detail,
6071
                                              match_message,
6072
                                              sizeof(match_message));
6073
            } else {
6074
                sixel_option_report_invalid_choice(
3✔
6075
                    "cannot parse encode policy option.",
6076
                    match_detail,
6077
                    match_message,
6078
                    sizeof(match_message));
6079
            }
6080
            status = SIXEL_BAD_ARGUMENT;
3✔
6081
            goto end;
3✔
6082
        }
6083
        break;
9✔
UNCOV
6084
    case SIXEL_OPTFLAG_LUT_POLICY:  /* ~ */
×
6085
        match_result = sixel_option_match_choice(
×
6086
            value,
6087
            g_option_choices_lut_policy,
6088
            sizeof(g_option_choices_lut_policy) /
6089
            sizeof(g_option_choices_lut_policy[0]),
6090
            &match_value,
6091
            match_detail,
6092
            sizeof(match_detail));
6093
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6094
            encoder->lut_policy = match_value;
×
6095
        } else {
6096
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6097
                sixel_option_report_ambiguous_prefix(value,
×
6098
                                              match_detail,
6099
                                              match_message,
6100
                                              sizeof(match_message));
6101
            } else {
6102
                sixel_option_report_invalid_choice(
×
6103
                    "cannot parse lut policy option.",
6104
                    match_detail,
6105
                    match_message,
6106
                    sizeof(match_message));
6107
            }
6108
            status = SIXEL_BAD_ARGUMENT;
×
6109
            goto end;
×
6110
        }
6111
        if (encoder->dither_cache != NULL) {
×
6112
            sixel_dither_set_lut_policy(encoder->dither_cache,
×
6113
                                        encoder->lut_policy);
6114
        }
6115
        break;
UNCOV
6116
    case SIXEL_OPTFLAG_WORKING_COLORSPACE:  /* W */
×
6117
        if (value == NULL) {
×
6118
            sixel_helper_set_additional_message(
×
6119
                "working-colorspace requires an argument.");
6120
            status = SIXEL_BAD_ARGUMENT;
×
6121
            goto end;
×
6122
        } else {
6123
            len = strlen(value);
×
6124

6125
            if (len >= sizeof(lowered)) {
×
6126
                sixel_helper_set_additional_message(
×
6127
                    "specified working colorspace name is too long.");
6128
                status = SIXEL_BAD_ARGUMENT;
×
6129
                goto end;
×
6130
            }
6131
            for (i = 0; i < len; ++i) {
×
6132
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6133
            }
6134
            lowered[len] = '\0';
×
6135

6136
            match_result = sixel_option_match_choice(
×
6137
                lowered,
6138
                g_option_choices_working_colorspace,
6139
                sizeof(g_option_choices_working_colorspace) /
6140
                sizeof(g_option_choices_working_colorspace[0]),
6141
                &match_value,
6142
                match_detail,
6143
                sizeof(match_detail));
6144
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6145
                encoder->working_colorspace = match_value;
×
6146
            } else {
6147
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6148
                    sixel_option_report_ambiguous_prefix(value,
×
6149
                        match_detail,
6150
                        match_message,
6151
                        sizeof(match_message));
6152
                } else {
6153
                    sixel_option_report_invalid_choice(
×
6154
                        "unsupported working colorspace specified.",
6155
                        match_detail,
6156
                        match_message,
6157
                        sizeof(match_message));
6158
                }
6159
                status = SIXEL_BAD_ARGUMENT;
×
6160
                goto end;
×
6161
            }
6162
        }
6163
        break;
×
UNCOV
6164
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
×
6165
        if (value == NULL) {
×
6166
            sixel_helper_set_additional_message(
×
6167
                "output-colorspace requires an argument.");
6168
            status = SIXEL_BAD_ARGUMENT;
×
6169
            goto end;
×
6170
        } else {
6171
            len = strlen(value);
×
6172

6173
            if (len >= sizeof(lowered)) {
×
6174
                sixel_helper_set_additional_message(
×
6175
                    "specified output colorspace name is too long.");
6176
                status = SIXEL_BAD_ARGUMENT;
×
6177
                goto end;
×
6178
            }
6179
            for (i = 0; i < len; ++i) {
×
6180
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6181
            }
6182
            lowered[len] = '\0';
×
6183

6184
            match_result = sixel_option_match_choice(
×
6185
                lowered,
6186
                g_option_choices_output_colorspace,
6187
                sizeof(g_option_choices_output_colorspace) /
6188
                sizeof(g_option_choices_output_colorspace[0]),
6189
                &match_value,
6190
                match_detail,
6191
                sizeof(match_detail));
6192
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6193
                encoder->output_colorspace = match_value;
×
6194
            } else {
6195
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6196
                    sixel_option_report_ambiguous_prefix(value,
×
6197
                        match_detail,
6198
                        match_message,
6199
                        sizeof(match_message));
6200
                } else {
6201
                    sixel_option_report_invalid_choice(
×
6202
                        "unsupported output colorspace specified.",
6203
                        match_detail,
6204
                        match_message,
6205
                        sizeof(match_message));
6206
                }
6207
                status = SIXEL_BAD_ARGUMENT;
×
6208
                goto end;
×
6209
            }
6210
        }
6211
        break;
×
UNCOV
6212
    case SIXEL_OPTFLAG_ORMODE:  /* O */
×
6213
        encoder->ormode = 1;
×
6214
        break;
×
6215
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6216
        encoder->complexion = atoi(value);
9✔
6217
        if (encoder->complexion < 1) {
9✔
6218
            sixel_helper_set_additional_message(
3✔
6219
                "complexion parameter must be 1 or more.");
6220
            status = SIXEL_BAD_ARGUMENT;
3✔
6221
            goto end;
3✔
6222
        }
6223
        break;
UNCOV
6224
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
×
6225
        encoder->pipe_mode = 1;
×
6226
        break;
×
6227
    case '?':  /* unknown option */
×
6228
    default:
6229
        /* exit if unknown options are specified */
6230
        sixel_helper_set_additional_message(
×
6231
            "unknown option is specified.");
6232
        status = SIXEL_BAD_ARGUMENT;
×
6233
        goto end;
×
6234
    }
6235

6236
    /* detects arguments conflictions */
6237
    if (encoder->reqcolors != (-1)) {
639✔
6238
        switch (encoder->color_option) {
99!
UNCOV
6239
        case SIXEL_COLOR_OPTION_MAPFILE:
×
6240
            sixel_helper_set_additional_message(
×
6241
                "option -p, --colors conflicts with -m, --mapfile.");
6242
            status = SIXEL_BAD_ARGUMENT;
×
6243
            goto end;
×
6244
        case SIXEL_COLOR_OPTION_MONOCHROME:
3✔
6245
            sixel_helper_set_additional_message(
3✔
6246
                "option -e, --monochrome conflicts with -p, --colors.");
6247
            status = SIXEL_BAD_ARGUMENT;
3✔
6248
            goto end;
3✔
6249
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
3✔
6250
            sixel_helper_set_additional_message(
3✔
6251
                "option -p, --colors conflicts with -I, --high-color.");
6252
            status = SIXEL_BAD_ARGUMENT;
3✔
6253
            goto end;
3✔
6254
        case SIXEL_COLOR_OPTION_BUILTIN:
3✔
6255
            sixel_helper_set_additional_message(
3✔
6256
                "option -p, --colors conflicts with -b, --builtin-palette.");
6257
            status = SIXEL_BAD_ARGUMENT;
3✔
6258
            goto end;
3✔
6259
        default:
6260
            break;
6261
        }
6262
    }
6263

6264
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
6265
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
630✔
6266
        sixel_helper_set_additional_message(
3✔
6267
            "option -8 --8bit-mode conflicts"
6268
            " with -P, --penetrate.");
6269
        status = SIXEL_BAD_ARGUMENT;
3✔
6270
        goto end;
3✔
6271
    }
6272

6273
    status = SIXEL_OK;
6274

6275
end:
717✔
6276
    if (opt_copy != NULL) {
717!
6277
        sixel_allocator_free(encoder->allocator, opt_copy);
6278
    }
6279
    sixel_encoder_unref(encoder);
717✔
6280

6281
    return status;
717✔
6282
}
6283

6284

6285
/* called when image loader component load a image frame */
6286
static SIXELSTATUS
6287
load_image_callback(sixel_frame_t *frame, void *data)
525✔
6288
{
6289
    sixel_encoder_t *encoder;
525✔
6290

6291
    encoder = (sixel_encoder_t *)data;
525✔
6292
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
525!
6293
        sixel_frame_ref(frame);
3✔
6294
        encoder->capture_source_frame = frame;
3✔
6295
    }
6296

6297
    return sixel_encoder_encode_frame(encoder, frame, NULL);
525✔
6298
}
6299

6300
/*
6301
 * Build a tee for encoded-assessment output:
6302
 *
6303
 *     +-------------+     +-------------------+     +------------+
6304
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
6305
 *     +-------------+     +-------------------+     +------------+
6306
 *
6307
 * The tee sink can be stdout or a user-provided file such as /dev/null.
6308
 */
6309
static SIXELSTATUS
6310
copy_file_to_stream(char const *path,
×
6311
                    FILE *stream,
6312
                    sixel_assessment_t *assessment)
6313
{
6314
    FILE *source;
×
6315
    unsigned char buffer[4096];
×
6316
    size_t nread;
×
6317
    size_t nwritten;
×
6318
    double started_at;
×
6319
    double finished_at;
×
6320
    double duration;
×
6321

6322
    source = NULL;
×
6323
    nread = 0;
×
6324
    nwritten = 0;
×
6325
    started_at = 0.0;
×
6326
    finished_at = 0.0;
×
6327
    duration = 0.0;
×
6328

6329
    source = sixel_compat_fopen(path, "rb");
×
6330
    if (source == NULL) {
×
6331
        sixel_helper_set_additional_message(
×
6332
            "copy_file_to_stream: failed to open assessment staging file.");
6333
        return SIXEL_LIBC_ERROR;
×
6334
    }
6335

6336
    for (;;) {
×
6337
        nread = fread(buffer, 1, sizeof(buffer), source);
×
6338
        if (nread == 0) {
×
6339
            if (ferror(source)) {
×
6340
                sixel_helper_set_additional_message(
×
6341
                    "copy_file_to_stream: failed while reading assessment staging file.");
6342
                (void) fclose(source);
×
6343
                return SIXEL_LIBC_ERROR;
×
6344
            }
6345
            break;
×
6346
        }
6347
        if (assessment != NULL) {
×
6348
            started_at = sixel_assessment_timer_now();
×
6349
        }
6350
        nwritten = fwrite(buffer, 1, nread, stream);
×
6351
        if (nwritten != nread) {
×
6352
            sixel_helper_set_additional_message(
×
6353
                "img2sixel: failed while copying assessment staging file.");
6354
            (void) fclose(source);
×
6355
            return SIXEL_LIBC_ERROR;
×
6356
        }
6357
        if (assessment != NULL) {
×
6358
            finished_at = sixel_assessment_timer_now();
×
6359
            duration = finished_at - started_at;
×
6360
            if (duration < 0.0) {
×
6361
                duration = 0.0;
6362
            }
6363
            sixel_assessment_record_output_write(assessment,
×
6364
                                                 nwritten,
6365
                                                 duration);
6366
        }
6367
    }
6368

6369
    if (fclose(source) != 0) {
×
6370
        sixel_helper_set_additional_message(
×
6371
            "img2sixel: failed to close assessment staging file.");
6372
        return SIXEL_LIBC_ERROR;
×
6373
    }
6374

6375
    return SIXEL_OK;
6376
}
6377

6378
typedef struct assessment_json_sink {
6379
    FILE *stream;
6380
    int failed;
6381
} assessment_json_sink_t;
6382

6383
static void
6384
assessment_json_callback(char const *chunk,
42✔
6385
                         size_t length,
6386
                         void *user_data)
6387
{
6388
    assessment_json_sink_t *sink;
42✔
6389

6390
    sink = (assessment_json_sink_t *)user_data;
42✔
6391
    if (sink == NULL || sink->stream == NULL) {
42!
6392
        return;
6393
    }
6394
    if (sink->failed) {
42!
6395
        return;
6396
    }
6397
    if (fwrite(chunk, 1, length, sink->stream) != length) {
42!
6398
        sink->failed = 1;
×
6399
    }
6400
}
1!
6401

6402
static char *
6403
create_temp_template_with_prefix(sixel_allocator_t *allocator,
9✔
6404
                                 char const *prefix,
6405
                                 size_t *capacity_out)
6406
{
6407
    char const *tmpdir;
9✔
6408
    size_t tmpdir_len;
9✔
6409
    size_t prefix_len;
9✔
6410
    size_t suffix_len;
9✔
6411
    size_t template_len;
9✔
6412
    char *template_path;
9✔
6413
    int needs_separator;
9✔
6414
    size_t maximum_tmpdir_len;
9✔
6415

6416
    tmpdir = sixel_compat_getenv("TMPDIR");
9✔
6417
#if defined(_WIN32)
6418
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6419
        tmpdir = sixel_compat_getenv("TEMP");
6420
    }
6421
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6422
        tmpdir = sixel_compat_getenv("TMP");
6423
    }
6424
#endif
6425
    if (tmpdir == NULL || tmpdir[0] == '\0') {
9!
6426
#if defined(_WIN32)
6427
        tmpdir = ".";
6428
#else
6429
        tmpdir = "/tmp";
9✔
6430
#endif
6431
    }
6432

6433
    tmpdir_len = strlen(tmpdir);
9✔
6434
    prefix_len = 0u;
9✔
6435
    suffix_len = 0u;
9✔
6436
    if (prefix == NULL) {
9!
6437
        return NULL;
6438
    }
6439

6440
    prefix_len = strlen(prefix);
9✔
6441
    suffix_len = prefix_len + strlen("-XXXXXX");
9✔
6442
    maximum_tmpdir_len = (size_t)INT_MAX;
9✔
6443

6444
    if (maximum_tmpdir_len <= suffix_len + 2) {
9!
6445
        return NULL;
6446
    }
6447
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
9!
6448
        return NULL;
6449
    }
6450
    needs_separator = 1;
9✔
6451
    if (tmpdir_len > 0) {
9!
6452
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
9!
6453
            needs_separator = 0;
9✔
6454
        }
6455
    }
6456

6457
    template_len = tmpdir_len + suffix_len + 2;
9✔
6458
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
9✔
6459
    if (template_path == NULL) {
9!
6460
        return NULL;
6461
    }
6462

6463
    if (needs_separator) {
9!
6464
#if defined(_WIN32)
6465
        (void) snprintf(template_path, template_len,
6466
                        "%s\\%s-XXXXXX", tmpdir, prefix);
6467
#else
6468
        (void) snprintf(template_path, template_len,
9✔
6469
                        "%s/%s-XXXXXX", tmpdir, prefix);
6470
#endif
6471
    } else {
UNCOV
6472
        (void) snprintf(template_path, template_len,
×
6473
                        "%s%s-XXXXXX", tmpdir, prefix);
6474
    }
6475

6476
    if (capacity_out != NULL) {
9!
6477
        *capacity_out = template_len;
9✔
6478
    }
6479

6480
    return template_path;
6481
}
6482

6483

6484
static char *
6485
create_temp_template(sixel_allocator_t *allocator,
9✔
6486
                     size_t *capacity_out)
6487
{
6488
    return create_temp_template_with_prefix(allocator,
9✔
6489
                                            "img2sixel",
6490
                                            capacity_out);
6491
}
6492

6493

6494
static void
UNCOV
6495
clipboard_select_format(char *dest,
×
6496
                        size_t dest_size,
6497
                        char const *format,
6498
                        char const *fallback)
6499
{
UNCOV
6500
    char const *source;
×
UNCOV
6501
    size_t limit;
×
6502

UNCOV
6503
    if (dest == NULL || dest_size == 0u) {
×
6504
        return;
6505
    }
6506

UNCOV
6507
    source = fallback;
×
UNCOV
6508
    if (format != NULL && format[0] != '\0') {
×
6509
        source = format;
×
6510
    }
6511

UNCOV
6512
    limit = dest_size - 1u;
×
UNCOV
6513
    if (limit == 0u) {
×
6514
        dest[0] = '\0';
×
6515
        return;
×
6516
    }
6517

UNCOV
6518
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
×
6519
}
1!
6520

6521

6522
static SIXELSTATUS
UNCOV
6523
clipboard_create_spool(sixel_allocator_t *allocator,
×
6524
                       char const *prefix,
6525
                       char **path_out,
6526
                       int *fd_out)
6527
{
UNCOV
6528
    SIXELSTATUS status;
×
UNCOV
6529
    char *template_path;
×
UNCOV
6530
    size_t template_capacity;
×
UNCOV
6531
    int open_flags;
×
UNCOV
6532
    int fd;
×
UNCOV
6533
    char *tmpname_result;
×
6534

UNCOV
6535
    status = SIXEL_FALSE;
×
UNCOV
6536
    template_path = NULL;
×
UNCOV
6537
    template_capacity = 0u;
×
UNCOV
6538
    open_flags = 0;
×
UNCOV
6539
    fd = (-1);
×
UNCOV
6540
    tmpname_result = NULL;
×
6541

UNCOV
6542
    template_path = create_temp_template_with_prefix(allocator,
×
6543
                                                     prefix,
6544
                                                     &template_capacity);
UNCOV
6545
    if (template_path == NULL) {
×
6546
        sixel_helper_set_additional_message(
×
6547
            "clipboard: failed to allocate spool template.");
6548
        status = SIXEL_BAD_ALLOCATION;
×
6549
        goto end;
×
6550
    }
6551

UNCOV
6552
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
×
6553
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
6554
        tmpname_result = tmpnam(template_path);
×
6555
        if (tmpname_result == NULL) {
×
6556
            sixel_helper_set_additional_message(
×
6557
                "clipboard: failed to reserve spool template.");
6558
            status = SIXEL_LIBC_ERROR;
×
6559
            goto end;
×
6560
        }
6561
        template_capacity = strlen(template_path) + 1u;
×
6562
    }
6563

UNCOV
6564
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
×
6565
#if defined(O_EXCL)
UNCOV
6566
    open_flags |= O_EXCL;
×
6567
#endif
UNCOV
6568
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
×
UNCOV
6569
    if (fd < 0) {
×
6570
        sixel_helper_set_additional_message(
×
6571
            "clipboard: failed to open spool file.");
6572
        status = SIXEL_LIBC_ERROR;
×
6573
        goto end;
×
6574
    }
6575

UNCOV
6576
    *path_out = template_path;
×
UNCOV
6577
    if (fd_out != NULL) {
×
UNCOV
6578
        *fd_out = fd;
×
UNCOV
6579
        fd = (-1);
×
6580
    }
6581

UNCOV
6582
    template_path = NULL;
×
UNCOV
6583
    status = SIXEL_OK;
×
6584

UNCOV
6585
end:
×
UNCOV
6586
    if (fd >= 0) {
×
UNCOV
6587
        (void)sixel_compat_close(fd);
×
6588
    }
UNCOV
6589
    if (template_path != NULL) {
×
6590
        sixel_allocator_free(allocator, template_path);
×
6591
    }
6592

UNCOV
6593
    return status;
×
6594
}
6595

6596

6597
static SIXELSTATUS
UNCOV
6598
clipboard_write_file(char const *path,
×
6599
                     unsigned char const *data,
6600
                     size_t size)
6601
{
UNCOV
6602
    FILE *stream;
×
UNCOV
6603
    size_t written;
×
6604

UNCOV
6605
    if (path == NULL) {
×
6606
        sixel_helper_set_additional_message(
×
6607
            "clipboard: spool path is null.");
6608
        return SIXEL_BAD_ARGUMENT;
×
6609
    }
6610

UNCOV
6611
    stream = sixel_compat_fopen(path, "wb");
×
UNCOV
6612
    if (stream == NULL) {
×
6613
        sixel_helper_set_additional_message(
×
6614
            "clipboard: failed to open spool file for write.");
6615
        return SIXEL_LIBC_ERROR;
×
6616
    }
6617

UNCOV
6618
    written = 0u;
×
UNCOV
6619
    if (size > 0u && data != NULL) {
×
UNCOV
6620
        written = fwrite(data, 1u, size, stream);
×
UNCOV
6621
        if (written != size) {
×
6622
            (void)fclose(stream);
×
6623
            sixel_helper_set_additional_message(
×
6624
                "clipboard: failed to write spool payload.");
6625
            return SIXEL_LIBC_ERROR;
×
6626
        }
6627
    }
6628

UNCOV
6629
    if (fclose(stream) != 0) {
×
6630
        sixel_helper_set_additional_message(
×
6631
            "clipboard: failed to close spool file after write.");
6632
        return SIXEL_LIBC_ERROR;
×
6633
    }
6634

6635
    return SIXEL_OK;
6636
}
6637

6638

6639
static SIXELSTATUS
UNCOV
6640
clipboard_read_file(char const *path,
×
6641
                    unsigned char **data,
6642
                    size_t *size)
6643
{
UNCOV
6644
    FILE *stream;
×
UNCOV
6645
    long seek_result;
×
UNCOV
6646
    long file_size;
×
UNCOV
6647
    unsigned char *buffer;
×
UNCOV
6648
    size_t read_size;
×
6649

UNCOV
6650
    if (data == NULL || size == NULL) {
×
6651
        sixel_helper_set_additional_message(
×
6652
            "clipboard: read buffer pointers are null.");
6653
        return SIXEL_BAD_ARGUMENT;
×
6654
    }
6655

UNCOV
6656
    *data = NULL;
×
UNCOV
6657
    *size = 0u;
×
6658

UNCOV
6659
    if (path == NULL) {
×
6660
        sixel_helper_set_additional_message(
×
6661
            "clipboard: spool path is null.");
6662
        return SIXEL_BAD_ARGUMENT;
×
6663
    }
6664

UNCOV
6665
    stream = sixel_compat_fopen(path, "rb");
×
UNCOV
6666
    if (stream == NULL) {
×
6667
        sixel_helper_set_additional_message(
×
6668
            "clipboard: failed to open spool file for read.");
6669
        return SIXEL_LIBC_ERROR;
×
6670
    }
6671

UNCOV
6672
    seek_result = fseek(stream, 0L, SEEK_END);
×
UNCOV
6673
    if (seek_result != 0) {
×
6674
        (void)fclose(stream);
×
6675
        sixel_helper_set_additional_message(
×
6676
            "clipboard: failed to seek spool file.");
6677
        return SIXEL_LIBC_ERROR;
×
6678
    }
6679

UNCOV
6680
    file_size = ftell(stream);
×
UNCOV
6681
    if (file_size < 0) {
×
6682
        (void)fclose(stream);
×
6683
        sixel_helper_set_additional_message(
×
6684
            "clipboard: failed to determine spool size.");
6685
        return SIXEL_LIBC_ERROR;
×
6686
    }
6687

UNCOV
6688
    seek_result = fseek(stream, 0L, SEEK_SET);
×
UNCOV
6689
    if (seek_result != 0) {
×
6690
        (void)fclose(stream);
×
6691
        sixel_helper_set_additional_message(
×
6692
            "clipboard: failed to rewind spool file.");
6693
        return SIXEL_LIBC_ERROR;
×
6694
    }
6695

UNCOV
6696
    if (file_size == 0) {
×
6697
        buffer = NULL;
6698
        read_size = 0u;
6699
    } else {
UNCOV
6700
        buffer = (unsigned char *)malloc((size_t)file_size);
×
UNCOV
6701
        if (buffer == NULL) {
×
6702
            (void)fclose(stream);
×
6703
            sixel_helper_set_additional_message(
×
6704
                "clipboard: malloc() failed for spool payload.");
6705
            return SIXEL_BAD_ALLOCATION;
×
6706
        }
UNCOV
6707
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
×
UNCOV
6708
        if (read_size != (size_t)file_size) {
×
6709
            free(buffer);
×
6710
            (void)fclose(stream);
×
6711
            sixel_helper_set_additional_message(
×
6712
                "clipboard: failed to read spool payload.");
6713
            return SIXEL_LIBC_ERROR;
×
6714
        }
6715
    }
6716

UNCOV
6717
    if (fclose(stream) != 0) {
×
6718
        if (buffer != NULL) {
×
6719
            free(buffer);
×
6720
        }
6721
        sixel_helper_set_additional_message(
×
6722
            "clipboard: failed to close spool file after read.");
6723
        return SIXEL_LIBC_ERROR;
×
6724
    }
6725

UNCOV
6726
    *data = buffer;
×
UNCOV
6727
    *size = read_size;
×
6728

UNCOV
6729
    return SIXEL_OK;
×
6730
}
6731

6732

6733
static SIXELSTATUS
6734
write_png_from_sixel(char const *sixel_path, char const *output_path)
9✔
6735
{
6736
    SIXELSTATUS status;
9✔
6737
    sixel_decoder_t *decoder;
9✔
6738

6739
    status = SIXEL_FALSE;
9✔
6740
    decoder = NULL;
9✔
6741

6742
    status = sixel_decoder_new(&decoder, NULL);
9✔
6743
    if (SIXEL_FAILED(status)) {
9!
6744
        goto end;
×
6745
    }
6746

6747
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
9✔
6748
    if (SIXEL_FAILED(status)) {
9!
6749
        goto end;
×
6750
    }
6751

6752
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
9✔
6753
    if (SIXEL_FAILED(status)) {
9!
6754
        goto end;
×
6755
    }
6756

6757
    status = sixel_decoder_decode(decoder);
9✔
6758

6759
end:
9✔
6760
    sixel_decoder_unref(decoder);
9✔
6761

6762
    return status;
9✔
6763
}
6764

6765

6766
/* load source data from specified file and encode it to SIXEL format
6767
 * output to encoder->outfd */
6768
SIXELAPI SIXELSTATUS
6769
sixel_encoder_encode(
429✔
6770
    sixel_encoder_t *encoder,   /* encoder object */
6771
    char const      *filename)  /* input filename */
6772
{
6773
    SIXELSTATUS status = SIXEL_FALSE;
429✔
6774
    SIXELSTATUS palette_status = SIXEL_OK;
429✔
6775
    int fuse_palette = 1;
429✔
6776
    sixel_loader_t *loader = NULL;
429✔
6777
    sixel_allocator_t *assessment_allocator = NULL;
429✔
6778
    sixel_allocator_t *encode_allocator = NULL;
429✔
6779
    sixel_frame_t *assessment_source_frame = NULL;
429✔
6780
    sixel_frame_t *assessment_target_frame = NULL;
429✔
6781
    sixel_frame_t *assessment_expanded_frame = NULL;
429✔
6782
    unsigned int assessment_section_mask =
429✔
6783
        encoder->assessment_sections & SIXEL_ASSESSMENT_SECTION_MASK;
429✔
6784
    int assessment_need_source_capture = 0;
429✔
6785
    int assessment_need_quantized_capture = 0;
429✔
6786
    int assessment_need_quality = 0;
429✔
6787
    int assessment_quality_quantized = 0;
429✔
6788
    assessment_json_sink_t assessment_sink;
429✔
6789
    FILE *assessment_json_file = NULL;
429✔
6790
    FILE *assessment_forward_stream = NULL;
429✔
6791
    int assessment_json_owned = 0;
429✔
6792
    char *assessment_temp_path = NULL;
429✔
6793
    size_t assessment_temp_capacity = 0u;
429✔
6794
    char *assessment_tmpnam_result = NULL;
429✔
6795
    sixel_assessment_spool_mode_t assessment_spool_mode
429✔
6796
        = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
6797
    char *assessment_forward_path = NULL;
429✔
6798
    size_t assessment_output_bytes;
429✔
6799
#if HAVE_SYS_STAT_H
6800
    struct stat assessment_stat;
429✔
6801
    int assessment_stat_result;
429✔
6802
    char const *assessment_size_path = NULL;
429✔
6803
#endif
6804
    char const *png_final_path = NULL;
429✔
6805
    char *png_temp_path = NULL;
429✔
6806
    size_t png_temp_capacity = 0u;
429✔
6807
    char *png_tmpnam_result = NULL;
429✔
6808
    int png_open_flags = 0;
429✔
6809
    int spool_required;
429✔
6810
    sixel_clipboard_spec_t clipboard_spec;
429✔
6811
    char clipboard_input_format[32];
429✔
6812
    char *clipboard_input_path;
429✔
6813
    unsigned char *clipboard_blob;
429✔
6814
    size_t clipboard_blob_size;
429✔
6815
    SIXELSTATUS clipboard_status;
429✔
6816
    char const *effective_filename;
429✔
6817
    unsigned int path_flags;
429✔
6818
    int path_check;
429✔
6819
    sixel_logger_t logger;
429✔
6820
    int logger_prepared;
429✔
6821

6822
    clipboard_input_format[0] = '\0';
429✔
6823
    clipboard_input_path = NULL;
429✔
6824
    clipboard_blob = NULL;
429✔
6825
    clipboard_blob_size = 0u;
429✔
6826
    clipboard_status = SIXEL_OK;
429✔
6827
    effective_filename = filename;
429✔
6828
    path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
429✔
6829
        SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
6830
        SIXEL_OPTION_PATH_ALLOW_REMOTE;
6831
    path_check = 0;
429✔
6832
    logger_prepared = 0;
429✔
6833
    sixel_logger_init(&logger);
429✔
6834
    sixel_logger_prepare_env(&logger);
429✔
6835
    logger_prepared = logger.active;
429✔
6836
    if (encoder != NULL) {
429!
6837
        encoder->logger = &logger;
429✔
6838
        encoder->parallel_job_id = -1;
429✔
6839
    }
6840

6841
    if (filename != NULL) {
429✔
6842
        path_check = sixel_option_validate_filesystem_path(
288✔
6843
            filename,
6844
            filename,
6845
            path_flags);
6846
        if (path_check != 0) {
288!
UNCOV
6847
            status = SIXEL_BAD_ARGUMENT;
×
UNCOV
6848
            goto end;
×
6849
        }
6850
    }
6851

6852
    if (encoder != NULL) {
429!
6853
        encode_allocator = encoder->allocator;
429✔
6854
        if (encode_allocator != NULL) {
429!
6855
            /*
6856
             * Hold a reference until cleanup so worker side-effects or loader
6857
             * destruction cannot release the allocator before sequential
6858
             * teardown finishes using it.
6859
             */
6860
            sixel_allocator_ref(encode_allocator);
429✔
6861
        }
6862
    }
6863

6864
    clipboard_spec.is_clipboard = 0;
429✔
6865
    clipboard_spec.format[0] = '\0';
429✔
6866
    if (effective_filename != NULL
429!
6867
            && sixel_clipboard_parse_spec(effective_filename,
288!
6868
                                          &clipboard_spec)
UNCOV
6869
            && clipboard_spec.is_clipboard) {
×
UNCOV
6870
        clipboard_select_format(clipboard_input_format,
×
6871
                                sizeof(clipboard_input_format),
6872
                                clipboard_spec.format,
6873
                                "sixel");
UNCOV
6874
        clipboard_status = sixel_clipboard_read(
×
6875
            clipboard_input_format,
6876
            &clipboard_blob,
6877
            &clipboard_blob_size,
6878
            encoder->allocator);
UNCOV
6879
        if (SIXEL_FAILED(clipboard_status)) {
×
6880
            status = clipboard_status;
×
6881
            goto end;
×
6882
        }
UNCOV
6883
        clipboard_status = clipboard_create_spool(
×
6884
            encoder->allocator,
6885
            "clipboard-in",
6886
            &clipboard_input_path,
6887
            NULL);
UNCOV
6888
        if (SIXEL_FAILED(clipboard_status)) {
×
6889
            status = clipboard_status;
×
6890
            goto end;
×
6891
        }
UNCOV
6892
        clipboard_status = clipboard_write_file(
×
6893
            clipboard_input_path,
6894
            clipboard_blob,
6895
            clipboard_blob_size);
UNCOV
6896
        if (SIXEL_FAILED(clipboard_status)) {
×
6897
            status = clipboard_status;
×
6898
            goto end;
×
6899
        }
UNCOV
6900
        if (clipboard_blob != NULL) {
×
UNCOV
6901
            free(clipboard_blob);
×
UNCOV
6902
            clipboard_blob = NULL;
×
6903
        }
6904
        effective_filename = clipboard_input_path;
6905
    }
6906

6907
    if (assessment_section_mask != SIXEL_ASSESSMENT_SECTION_NONE) {
429✔
6908
        status = sixel_allocator_new(&assessment_allocator,
3✔
6909
                                     malloc,
6910
                                     calloc,
6911
                                     realloc,
6912
                                     free);
6913
        if (SIXEL_FAILED(status) || assessment_allocator == NULL) {
3!
6914
            goto end;
×
6915
        }
6916
        status = sixel_assessment_new(&encoder->assessment_observer,
3✔
6917
                                       assessment_allocator);
6918
        if (SIXEL_FAILED(status) || encoder->assessment_observer == NULL) {
3!
6919
            goto end;
×
6920
        }
6921
        sixel_assessment_select_sections(encoder->assessment_observer,
3✔
6922
                                         encoder->assessment_sections);
6923
        sixel_assessment_attach_encoder(encoder->assessment_observer,
3✔
6924
                                        encoder);
6925
        assessment_need_quality =
3✔
6926
            (assessment_section_mask & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u;
3✔
6927
        assessment_quality_quantized =
3✔
6928
            (encoder->assessment_sections & SIXEL_ASSESSMENT_VIEW_QUANTIZED) != 0u;
3✔
6929
        assessment_need_quantized_capture =
6✔
6930
            ((assessment_section_mask &
3✔
6931
              (SIXEL_ASSESSMENT_SECTION_BASIC |
6932
               SIXEL_ASSESSMENT_SECTION_SIZE)) != 0u) ||
3!
6933
            assessment_quality_quantized;
6934
        assessment_need_source_capture =
3✔
6935
            (assessment_section_mask &
6936
             (SIXEL_ASSESSMENT_SECTION_BASIC |
6937
              SIXEL_ASSESSMENT_SECTION_PERFORMANCE |
6938
              SIXEL_ASSESSMENT_SECTION_SIZE |
6939
              SIXEL_ASSESSMENT_SECTION_QUALITY)) != 0u;
6940
        if (assessment_need_quality && !assessment_quality_quantized &&
3!
6941
                encoder->output_is_png) {
×
6942
            sixel_helper_set_additional_message(
×
6943
                "sixel_encoder_setopt: encoded quality assessment requires SIXEL output.");
6944
            status = SIXEL_BAD_ARGUMENT;
×
6945
            goto end;
×
6946
        }
6947
        status = sixel_encoder_enable_source_capture(encoder,
3✔
6948
                                                     assessment_need_source_capture);
6949
        if (SIXEL_FAILED(status)) {
3!
6950
            goto end;
×
6951
        }
6952
        status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_STATIC, NULL);
3✔
6953
        if (SIXEL_FAILED(status)) {
3!
6954
            goto end;
×
6955
        }
6956
        if (assessment_need_quantized_capture) {
3!
6957
            status = sixel_encoder_enable_quantized_capture(encoder, 1);
3✔
6958
            if (SIXEL_FAILED(status)) {
3!
6959
                goto end;
6960
            }
6961
        }
6962
        assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
3✔
6963
        spool_required = 0;
3✔
6964
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
6965
            if (encoder->sixel_output_path == NULL) {
×
6966
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
6967
                spool_required = 1;
6968
            } else if (strcmp(encoder->sixel_output_path, "-") == 0) {
×
6969
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
6970
                spool_required = 1;
×
6971
                free(encoder->sixel_output_path);
×
6972
                encoder->sixel_output_path = NULL;
×
6973
            } else if (is_dev_null_path(encoder->sixel_output_path)) {
×
6974
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_PATH;
×
6975
                spool_required = 1;
×
6976
                assessment_forward_path = encoder->sixel_output_path;
×
6977
                encoder->sixel_output_path = NULL;
×
6978
            }
6979
        }
UNCOV
6980
        if (spool_required) {
×
6981
            assessment_temp_capacity = 0u;
×
6982
            assessment_tmpnam_result = NULL;
×
6983
            assessment_temp_path = create_temp_template(encoder->allocator,
×
6984
                                                        &assessment_temp_capacity);
6985
            if (assessment_temp_path == NULL) {
×
6986
                sixel_helper_set_additional_message(
×
6987
                    "sixel_encoder_encode: sixel_allocator_malloc() "
6988
                    "failed for assessment staging path.");
6989
                status = SIXEL_BAD_ALLOCATION;
×
6990
                goto end;
×
6991
            }
6992
            if (sixel_compat_mktemp(assessment_temp_path,
×
6993
                                    assessment_temp_capacity) != 0) {
6994
                /* Fall back to tmpnam() when mktemp variants are unavailable. */
6995
                assessment_tmpnam_result = tmpnam(assessment_temp_path);
×
6996
                if (assessment_tmpnam_result == NULL) {
×
6997
                    sixel_helper_set_additional_message(
×
6998
                        "sixel_encoder_encode: mktemp() failed for assessment staging file.");
6999
                    status = SIXEL_RUNTIME_ERROR;
×
7000
                    goto end;
×
7001
                }
7002
                assessment_temp_capacity = strlen(assessment_temp_path) + 1u;
×
7003
            }
7004
            status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_OUTFILE,
×
7005
                                          assessment_temp_path);
7006
            if (SIXEL_FAILED(status)) {
×
7007
                goto end;
×
7008
            }
7009
            encoder->sixel_output_path = (char *)sixel_allocator_malloc(
×
7010
                encoder->allocator, strlen(assessment_temp_path) + 1);
×
7011
            if (encoder->sixel_output_path == NULL) {
×
7012
                sixel_helper_set_additional_message(
×
7013
                    "sixel_encoder_encode: malloc() failed for assessment staging name.");
7014
                status = SIXEL_BAD_ALLOCATION;
×
7015
                goto end;
×
7016
            }
7017
            (void)sixel_compat_strcpy(encoder->sixel_output_path,
×
7018
                                      strlen(assessment_temp_path) + 1,
×
7019
                                      assessment_temp_path);
7020
        }
7021

7022
    }
7023

7024
    if (encoder->output_is_png) {
429✔
7025
        png_temp_capacity = 0u;
9✔
7026
        png_tmpnam_result = NULL;
9✔
7027
        png_temp_path = create_temp_template(encoder->allocator,
9✔
7028
                                             &png_temp_capacity);
7029
        if (png_temp_path == NULL) {
9!
7030
            sixel_helper_set_additional_message(
×
7031
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
7032
            status = SIXEL_BAD_ALLOCATION;
×
7033
            goto end;
×
7034
        }
7035
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
9!
7036
            /* Fall back to tmpnam() when mktemp variants are unavailable. */
7037
            png_tmpnam_result = tmpnam(png_temp_path);
×
7038
            if (png_tmpnam_result == NULL) {
×
7039
                sixel_helper_set_additional_message(
×
7040
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
7041
                status = SIXEL_RUNTIME_ERROR;
×
7042
                goto end;
×
7043
            }
7044
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
7045
        }
7046
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
9!
7047
            (void)sixel_compat_close(encoder->outfd);
3✔
7048
        }
7049
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
9✔
7050
#if defined(O_EXCL)
7051
        png_open_flags |= O_EXCL;
9✔
7052
#endif
7053
        encoder->outfd = sixel_compat_open(png_temp_path,
9✔
7054
                                           png_open_flags,
7055
                                           S_IRUSR | S_IWUSR);
7056
        if (encoder->outfd < 0) {
9!
7057
            sixel_helper_set_additional_message(
×
7058
                "sixel_encoder_encode: failed to create the PNG target file.");
7059
            status = SIXEL_LIBC_ERROR;
×
7060
            goto end;
×
7061
        }
7062
    }
7063

7064
    if (encoder == NULL) {
1!
7065
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7066
#  pragma GCC diagnostic push
7067
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7068
#endif
7069
        encoder = sixel_encoder_create();
7070
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7071
#  pragma GCC diagnostic pop
7072
#endif
7073
        if (encoder == NULL) {
×
7074
            sixel_helper_set_additional_message(
7075
                "sixel_encoder_encode: sixel_encoder_create() failed.");
7076
            status = SIXEL_BAD_ALLOCATION;
7077
            goto end;
7078
        }
7079
    } else {
7080
        sixel_encoder_ref(encoder);
429✔
7081
    }
7082

7083
    if (encode_allocator == NULL && encoder != NULL) {
429!
7084
        encode_allocator = encoder->allocator;
×
7085
        if (encode_allocator != NULL) {
×
7086
            /* Ensure the allocator stays valid after lazy encoder creation. */
7087
            sixel_allocator_ref(encode_allocator);
×
7088
        }
7089
    }
7090

7091
    if (encoder->assessment_observer != NULL) {
429✔
7092
        sixel_assessment_stage_transition(
3✔
7093
            encoder->assessment_observer,
7094
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
7095
    }
7096
    encoder->last_loader_name[0] = '\0';
429✔
7097
    encoder->last_source_path[0] = '\0';
429✔
7098
    encoder->last_input_bytes = 0u;
429✔
7099

7100
    /* if required color is not set, set the max value */
7101
    if (encoder->reqcolors == (-1)) {
429✔
7102
        encoder->reqcolors = SIXEL_PALETTE_MAX;
411✔
7103
    }
7104

7105
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
429!
7106
        sixel_frame_unref(encoder->capture_source_frame);
×
7107
        encoder->capture_source_frame = NULL;
×
7108
    }
7109

7110
    /* if required color is less then 2, set the min value */
7111
    if (encoder->reqcolors < 2) {
429✔
7112
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
7113
    }
7114

7115
    /* if color space option is not set, choose RGB color space */
7116
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
429✔
7117
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
411✔
7118
    }
7119

7120
    /* if color option is not default value, prohibit to read
7121
       the file as a paletted image */
7122
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
429✔
7123
        fuse_palette = 0;
96✔
7124
    }
7125

7126
    /* if scaling options are set, prohibit to read the file as
7127
       a paletted image */
7128
    if (encoder->percentwidth > 0 ||
429✔
7129
        encoder->percentheight > 0 ||
417✔
7130
        encoder->pixelwidth > 0 ||
411✔
7131
        encoder->pixelheight > 0) {
360✔
7132
        fuse_palette = 0;
99✔
7133
    }
7134

7135
reload:
330✔
7136

7137
    sixel_helper_set_loader_trace(encoder->verbose);
429✔
7138
    sixel_helper_set_thumbnail_size_hint(
429✔
7139
        sixel_encoder_thumbnail_hint(encoder));
7140

7141
    status = sixel_loader_new(&loader, encoder->allocator);
429✔
7142
    if (SIXEL_FAILED(status)) {
429!
7143
        goto load_end;
×
7144
    }
7145

7146
    status = sixel_loader_setopt(loader,
858✔
7147
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
7148
                                 &encoder->fstatic);
429✔
7149
    if (SIXEL_FAILED(status)) {
429!
7150
        goto load_end;
×
7151
    }
7152

7153
    status = sixel_loader_setopt(loader,
429✔
7154
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
7155
                                 &fuse_palette);
7156
    if (SIXEL_FAILED(status)) {
429!
7157
        goto load_end;
×
7158
    }
7159

7160
    status = sixel_loader_setopt(loader,
858✔
7161
                                 SIXEL_LOADER_OPTION_REQCOLORS,
7162
                                 &encoder->reqcolors);
429✔
7163
    if (SIXEL_FAILED(status)) {
429!
7164
        goto load_end;
×
7165
    }
7166

7167
    status = sixel_loader_setopt(loader,
858✔
7168
                                 SIXEL_LOADER_OPTION_BGCOLOR,
7169
                                 encoder->bgcolor);
429✔
7170
    if (SIXEL_FAILED(status)) {
429!
7171
        goto load_end;
×
7172
    }
7173

7174
    status = sixel_loader_setopt(loader,
858✔
7175
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
7176
                                 &encoder->loop_mode);
429✔
7177
    if (SIXEL_FAILED(status)) {
429!
7178
        goto load_end;
×
7179
    }
7180

7181
    status = sixel_loader_setopt(loader,
858✔
7182
                                 SIXEL_LOADER_OPTION_INSECURE,
7183
                                 &encoder->finsecure);
429✔
7184
    if (SIXEL_FAILED(status)) {
429!
7185
        goto load_end;
×
7186
    }
7187

7188
    status = sixel_loader_setopt(loader,
858✔
7189
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
7190
                                 encoder->cancel_flag);
429✔
7191
    if (SIXEL_FAILED(status)) {
429!
7192
        goto load_end;
×
7193
    }
7194

7195
    status = sixel_loader_setopt(loader,
858✔
7196
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
7197
                                 encoder->loader_order);
429✔
7198
    if (SIXEL_FAILED(status)) {
429!
7199
        goto load_end;
×
7200
    }
7201

7202
    status = sixel_loader_setopt(loader,
429✔
7203
                                 SIXEL_LOADER_OPTION_CONTEXT,
7204
                                 encoder);
7205
    if (SIXEL_FAILED(status)) {
429!
7206
        goto load_end;
×
7207
    }
7208

7209
    /*
7210
     * Wire the optional assessment observer into the loader.
7211
     *
7212
     * The observer travels separately from the callback context so mapfile
7213
     * palette probes and other callbacks can keep using arbitrary structs.
7214
     */
7215
    status = sixel_loader_setopt(loader,
858✔
7216
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
7217
                                 encoder->assessment_observer);
429✔
7218
    if (SIXEL_FAILED(status)) {
429!
7219
        goto load_end;
×
7220
    }
7221

7222
    status = sixel_loader_load_file(loader,
429✔
7223
                                    effective_filename,
7224
                                    load_image_callback);
7225
    if (status != SIXEL_OK) {
429✔
7226
        goto load_end;
9✔
7227
    }
7228
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
420✔
7229
    if (sixel_loader_get_last_success_name(loader) != NULL) {
420!
7230
        (void)snprintf(encoder->last_loader_name,
420✔
7231
                       sizeof(encoder->last_loader_name),
7232
                       "%s",
7233
                       sixel_loader_get_last_success_name(loader));
7234
    } else {
UNCOV
7235
        encoder->last_loader_name[0] = '\0';
×
7236
    }
7237
    if (sixel_loader_get_last_source_path(loader) != NULL) {
420✔
7238
        (void)snprintf(encoder->last_source_path,
282✔
7239
                       sizeof(encoder->last_source_path),
7240
                       "%s",
7241
                       sixel_loader_get_last_source_path(loader));
7242
    } else {
7243
        encoder->last_source_path[0] = '\0';
138✔
7244
    }
7245
    if (encoder->assessment_observer != NULL) {
420✔
7246
        sixel_assessment_record_loader(encoder->assessment_observer,
3✔
7247
                                       encoder->last_source_path,
3✔
7248
                                       encoder->last_loader_name,
3✔
7249
                                       encoder->last_input_bytes);
7250
    }
7251

7252
load_end:
417✔
7253
    sixel_loader_unref(loader);
429✔
7254
    loader = NULL;
429✔
7255

7256
    if (status != SIXEL_OK) {
429✔
7257
        goto end;
9✔
7258
    }
7259

7260
    palette_status = sixel_encoder_emit_palette_output(encoder);
420✔
7261
    if (SIXEL_FAILED(palette_status)) {
420!
7262
        status = palette_status;
×
7263
        goto end;
×
7264
    }
7265

7266
    if (encoder->pipe_mode) {
420!
7267
#if HAVE_CLEARERR
7268
        clearerr(stdin);
×
7269
#endif  /* HAVE_FSEEK */
7270
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
7271
            status = sixel_tty_wait_stdin(1000000);
×
7272
            if (SIXEL_FAILED(status)) {
×
7273
                goto end;
×
7274
            }
7275
            if (status != SIXEL_OK) {
×
7276
                break;
7277
            }
7278
        }
7279
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
7280
            goto reload;
×
7281
        }
7282
    }
7283

7284
    if (encoder->assessment_observer) {
420✔
7285
        if (assessment_allocator == NULL || encoder->assessment_observer == NULL) {
3!
7286
            status = SIXEL_RUNTIME_ERROR;
×
7287
            goto end;
×
7288
        }
7289
        if (assessment_need_source_capture) {
3!
7290
            status = sixel_encoder_copy_source_frame(encoder,
3✔
7291
                                                     &assessment_source_frame);
7292
            if (SIXEL_FAILED(status)) {
3!
7293
                goto end;
×
7294
            }
7295
            sixel_assessment_record_source_frame(encoder->assessment_observer,
3✔
7296
                                                 assessment_source_frame);
7297
        }
7298
        if (assessment_need_quality) {
3!
7299
            if (assessment_quality_quantized) {
×
7300
                status = sixel_encoder_copy_quantized_frame(
×
7301
                    encoder, assessment_allocator, &assessment_target_frame);
7302
                if (SIXEL_FAILED(status)) {
×
7303
                    goto end;
×
7304
                }
7305
                status = sixel_assessment_expand_quantized_frame(
×
7306
                    assessment_target_frame,
7307
                    assessment_allocator,
7308
                    &assessment_expanded_frame);
7309
                if (SIXEL_FAILED(status)) {
×
7310
                    goto end;
×
7311
                }
7312
                sixel_frame_unref(assessment_target_frame);
×
7313
                assessment_target_frame = assessment_expanded_frame;
×
7314
                assessment_expanded_frame = NULL;
×
7315
                sixel_assessment_record_quantized_capture(
×
7316
                    encoder->assessment_observer, encoder);
×
7317
            } else {
7318
                status = sixel_assessment_load_single_frame(
×
7319
                    encoder->sixel_output_path,
×
7320
                    assessment_allocator,
7321
                    &assessment_target_frame);
7322
                if (SIXEL_FAILED(status)) {
×
7323
                    goto end;
×
7324
                }
7325
            }
7326
            if (!assessment_quality_quantized &&
×
7327
                    assessment_need_quantized_capture) {
7328
                sixel_assessment_record_quantized_capture(
×
7329
                    encoder->assessment_observer, encoder);
×
7330
            }
7331
        } else if (assessment_need_quantized_capture) {
3!
7332
            sixel_assessment_record_quantized_capture(
3✔
7333
                encoder->assessment_observer, encoder);
3✔
7334
        }
7335
        if (encoder->assessment_observer != NULL &&
3!
7336
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
7337
            sixel_assessment_stage_transition(
×
7338
                encoder->assessment_observer,
7339
                SIXEL_ASSESSMENT_STAGE_OUTPUT);
7340
        }
UNCOV
7341
        if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
×
7342
            status = copy_file_to_stream(assessment_temp_path,
×
7343
                                         stdout,
7344
                                         encoder->assessment_observer);
×
7345
            if (SIXEL_FAILED(status)) {
×
7346
                goto end;
×
7347
            }
7348
        } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
3!
7349
            if (assessment_forward_path == NULL) {
×
7350
                sixel_helper_set_additional_message(
×
7351
                    "sixel_encoder_encode: missing assessment spool target.");
7352
                status = SIXEL_RUNTIME_ERROR;
×
7353
                goto end;
×
7354
            }
7355
            assessment_forward_stream = sixel_compat_fopen(
×
7356
                assessment_forward_path,
7357
                "wb");
7358
            if (assessment_forward_stream == NULL) {
×
7359
                sixel_helper_set_additional_message(
×
7360
                    "sixel_encoder_encode: failed to open assessment spool sink.");
7361
                status = SIXEL_LIBC_ERROR;
×
7362
                goto end;
×
7363
            }
7364
            status = copy_file_to_stream(assessment_temp_path,
×
7365
                                         assessment_forward_stream,
7366
                                         encoder->assessment_observer);
×
7367
            if (fclose(assessment_forward_stream) != 0) {
×
7368
                if (SIXEL_SUCCEEDED(status)) {
×
7369
                    sixel_helper_set_additional_message(
×
7370
                        "img2sixel: failed to close assessment spool sink.");
7371
                    status = SIXEL_LIBC_ERROR;
×
7372
                }
7373
            }
7374
            assessment_forward_stream = NULL;
×
7375
            if (SIXEL_FAILED(status)) {
×
7376
                goto end;
×
7377
            }
7378
        }
7379
        if (encoder->assessment_observer != NULL &&
3!
7380
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
7381
            sixel_assessment_stage_finish(encoder->assessment_observer);
×
7382
        }
7383
#if HAVE_SYS_STAT_H
7384
        assessment_output_bytes = 0u;
3✔
7385
        assessment_size_path = NULL;
3✔
7386
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
7387
            if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
×
7388
                    assessment_spool_mode ==
7389
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
7390
                assessment_size_path = assessment_temp_path;
7391
            } else if (encoder->sixel_output_path != NULL &&
×
7392
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
7393
                assessment_size_path = encoder->sixel_output_path;
7394
            }
7395
        } else {
7396
            if (encoder->sixel_output_path != NULL &&
3!
7397
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
7398
                assessment_size_path = encoder->sixel_output_path;
7399
            } else if (assessment_spool_mode ==
3!
7400
                    SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
3!
7401
                    assessment_spool_mode ==
7402
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
7403
                assessment_size_path = assessment_temp_path;
7404
            }
7405
        }
UNCOV
7406
        if (assessment_size_path != NULL) {
×
7407
            assessment_stat_result = stat(assessment_size_path,
×
7408
                                          &assessment_stat);
7409
            if (assessment_stat_result == 0 &&
×
7410
                    assessment_stat.st_size >= 0) {
×
7411
                assessment_output_bytes =
×
7412
                    (size_t)assessment_stat.st_size;
7413
            }
7414
        }
7415
#else
7416
        assessment_output_bytes = 0u;
7417
#endif
7418
        sixel_assessment_record_output_size(encoder->assessment_observer,
3✔
7419
                                            assessment_output_bytes);
7420
        if (assessment_need_quality) {
3!
7421
            status = sixel_assessment_analyze(encoder->assessment_observer,
×
7422
                                              assessment_source_frame,
7423
                                              assessment_target_frame);
7424
            if (SIXEL_FAILED(status)) {
×
7425
                goto end;
×
7426
            }
7427
        }
7428
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
7429
        if (encoder->assessment_json_path != NULL &&
3!
7430
                strcmp(encoder->assessment_json_path, "-") != 0) {
3!
7431
            assessment_json_file = sixel_compat_fopen(
3✔
7432
                encoder->assessment_json_path,
7433
                "wb");
7434
            if (assessment_json_file == NULL) {
3!
7435
                sixel_helper_set_additional_message(
×
7436
                    "sixel_encoder_encode: failed to open assessment JSON file.");
7437
                status = SIXEL_LIBC_ERROR;
×
7438
                goto end;
×
7439
            }
7440
            assessment_json_owned = 1;
3✔
7441
            assessment_sink.stream = assessment_json_file;
3✔
7442
        } else {
7443
            assessment_sink.stream = stdout;
×
7444
        }
7445
        assessment_sink.failed = 0;
3✔
7446
        status = sixel_assessment_get_json(encoder->assessment_observer,
3✔
7447
                                           encoder->assessment_sections,
7448
                                           assessment_json_callback,
7449
                                           &assessment_sink);
7450
        if (SIXEL_FAILED(status) || assessment_sink.failed) {
3!
7451
            sixel_helper_set_additional_message(
×
7452
                "sixel_encoder_encode: failed to emit assessment JSON.");
7453
            goto end;
×
7454
        }
7455
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
417!
7456
        status = copy_file_to_stream(assessment_temp_path,
×
7457
                                     stdout,
7458
                                     encoder->assessment_observer);
7459
        if (SIXEL_FAILED(status)) {
×
7460
            goto end;
×
7461
        }
7462
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
417!
7463
        if (assessment_forward_path == NULL) {
×
7464
            sixel_helper_set_additional_message(
×
7465
                "img2sixel: missing assessment spool target.");
7466
            status = SIXEL_RUNTIME_ERROR;
×
7467
            goto end;
×
7468
        }
7469
        assessment_forward_stream = sixel_compat_fopen(
×
7470
            assessment_forward_path,
7471
            "wb");
7472
        if (assessment_forward_stream == NULL) {
×
7473
            sixel_helper_set_additional_message(
×
7474
                "img2sixel: failed to open assessment spool sink.");
7475
            status = SIXEL_LIBC_ERROR;
×
7476
            goto end;
×
7477
        }
7478
        status = copy_file_to_stream(assessment_temp_path,
×
7479
                                     assessment_forward_stream,
7480
                                     encoder->assessment_observer);
×
7481
        if (fclose(assessment_forward_stream) != 0) {
×
7482
            if (SIXEL_SUCCEEDED(status)) {
×
7483
                sixel_helper_set_additional_message(
×
7484
                    "img2sixel: failed to close assessment spool sink.");
7485
                status = SIXEL_LIBC_ERROR;
×
7486
            }
7487
        }
7488
        assessment_forward_stream = NULL;
×
7489
        if (SIXEL_FAILED(status)) {
×
7490
            goto end;
×
7491
        }
7492
    }
7493

7494
    if (encoder->output_is_png) {
420✔
7495
        png_final_path = encoder->output_png_to_stdout ? "-" : encoder->png_output_path;
9!
7496
        if (! encoder->output_png_to_stdout && png_final_path == NULL) {
9!
7497
            sixel_helper_set_additional_message(
×
7498
                "sixel_encoder_encode: missing PNG output path.");
7499
            status = SIXEL_RUNTIME_ERROR;
×
7500
            goto end;
×
7501
        }
7502
        status = write_png_from_sixel(png_temp_path, png_final_path);
9✔
7503
        if (SIXEL_FAILED(status)) {
9!
7504
            goto end;
×
7505
        }
7506
    }
7507

7508
    if (encoder->clipboard_output_active
420!
UNCOV
7509
            && encoder->clipboard_output_path != NULL) {
×
UNCOV
7510
        unsigned char *clipboard_output_data;
×
UNCOV
7511
        size_t clipboard_output_size;
×
7512

UNCOV
7513
        clipboard_output_data = NULL;
×
UNCOV
7514
        clipboard_output_size = 0u;
×
7515

UNCOV
7516
        if (encoder->outfd
×
7517
                && encoder->outfd != STDOUT_FILENO
1!
UNCOV
7518
                && encoder->outfd != STDERR_FILENO) {
×
UNCOV
7519
            (void)sixel_compat_close(encoder->outfd);
×
UNCOV
7520
            encoder->outfd = STDOUT_FILENO;
×
7521
        }
7522

UNCOV
7523
        clipboard_status = clipboard_read_file(
×
UNCOV
7524
            encoder->clipboard_output_path,
×
7525
            &clipboard_output_data,
7526
            &clipboard_output_size);
UNCOV
7527
        if (SIXEL_SUCCEEDED(clipboard_status)) {
×
UNCOV
7528
            clipboard_status = sixel_clipboard_write(
×
UNCOV
7529
                encoder->clipboard_output_format,
×
7530
                clipboard_output_data,
7531
                clipboard_output_size);
7532
        }
UNCOV
7533
        if (clipboard_output_data != NULL) {
×
UNCOV
7534
            free(clipboard_output_data);
×
7535
        }
UNCOV
7536
        if (SIXEL_FAILED(clipboard_status)) {
×
7537
            status = clipboard_status;
×
7538
            goto end;
×
7539
        }
UNCOV
7540
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
UNCOV
7541
        sixel_allocator_free(encoder->allocator,
×
UNCOV
7542
                             encoder->clipboard_output_path);
×
UNCOV
7543
        encoder->clipboard_output_path = NULL;
×
UNCOV
7544
        encoder->sixel_output_path = NULL;
×
UNCOV
7545
        encoder->clipboard_output_active = 0;
×
UNCOV
7546
        encoder->clipboard_output_format[0] = '\0';
×
7547
    }
1!
7548

7549
    /* the status may not be SIXEL_OK */
7550

7551
end:
420✔
7552
    if (png_temp_path != NULL) {
429✔
7553
        (void)sixel_compat_unlink(png_temp_path);
9✔
7554
    }
7555
    sixel_allocator_free(encoder->allocator, png_temp_path);
429✔
7556
    if (clipboard_input_path != NULL) {
429!
UNCOV
7557
        (void)sixel_compat_unlink(clipboard_input_path);
×
UNCOV
7558
        sixel_allocator_free(encoder->allocator, clipboard_input_path);
×
7559
    }
7560
    if (clipboard_blob != NULL) {
429!
7561
        free(clipboard_blob);
×
7562
    }
7563
    if (encoder->clipboard_output_path != NULL) {
429!
7564
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
7565
        sixel_allocator_free(encoder->allocator,
×
7566
                             encoder->clipboard_output_path);
×
7567
        encoder->clipboard_output_path = NULL;
×
7568
        encoder->sixel_output_path = NULL;
×
7569
        encoder->clipboard_output_active = 0;
×
7570
        encoder->clipboard_output_format[0] = '\0';
×
7571
    }
7572
    sixel_allocator_free(encoder->allocator, encoder->png_output_path);
429✔
7573
    encoder->png_output_path = NULL;
429✔
7574
    if (assessment_forward_stream != NULL) {
429!
7575
        (void) fclose(assessment_forward_stream);
7576
    }
7577
    if (assessment_temp_path != NULL &&
429!
7578
            assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
429!
7579
        (void)sixel_compat_unlink(assessment_temp_path);
×
7580
    }
7581
    sixel_allocator_free(encoder->allocator, assessment_temp_path);
429✔
7582
    sixel_allocator_free(encoder->allocator, assessment_forward_path);
429✔
7583
    if (assessment_json_owned && assessment_json_file != NULL) {
429!
7584
        (void) fclose(assessment_json_file);
3✔
7585
    }
7586
    if (assessment_target_frame != NULL) {
429!
7587
        sixel_frame_unref(assessment_target_frame);
×
7588
    }
7589
    if (assessment_expanded_frame != NULL) {
429!
7590
        sixel_frame_unref(assessment_expanded_frame);
×
7591
    }
7592
    if (assessment_source_frame != NULL) {
429✔
7593
        sixel_frame_unref(assessment_source_frame);
3✔
7594
    }
7595
    if (encoder->assessment_observer != NULL) {
429✔
7596
        sixel_assessment_unref(encoder->assessment_observer);
3✔
7597
        encoder->assessment_observer = NULL;
3✔
7598
    }
7599
    if (assessment_allocator != NULL) {
429✔
7600
        sixel_allocator_unref(assessment_allocator);
3✔
7601
    }
7602

7603
    if (encoder != NULL) {
429!
7604
        encoder->logger = NULL;
429✔
7605
        encoder->parallel_job_id = -1;
429✔
7606
    }
7607
    if (logger_prepared) {
429!
7608
        sixel_logger_close(&logger);
×
7609
    }
7610

7611
    sixel_encoder_unref(encoder);
429✔
7612

7613
    if (encode_allocator != NULL) {
429!
7614
        /*
7615
         * Release the retained allocator reference *after* dropping the
7616
         * encoder reference so that a lazily created encoder can run its
7617
         * destructor while the allocator is still alive.  This ensures that
7618
         * cleanup routines never dereference a freed allocator instance.
7619
         */
7620
        sixel_allocator_unref(encode_allocator);
429✔
7621
        encode_allocator = NULL;
429✔
7622
    }
7623

7624
    return status;
429✔
7625
}
7626

7627

7628
/* encode specified pixel data to SIXEL format
7629
 * output to encoder->outfd */
7630
SIXELAPI SIXELSTATUS
7631
sixel_encoder_encode_bytes(
×
7632
    sixel_encoder_t     /* in */    *encoder,
7633
    unsigned char       /* in */    *bytes,
7634
    int                 /* in */    width,
7635
    int                 /* in */    height,
7636
    int                 /* in */    pixelformat,
7637
    unsigned char       /* in */    *palette,
7638
    int                 /* in */    ncolors)
7639
{
7640
    SIXELSTATUS status = SIXEL_FALSE;
×
7641
    sixel_frame_t *frame = NULL;
×
7642

7643
    if (encoder == NULL || bytes == NULL) {
×
7644
        status = SIXEL_BAD_ARGUMENT;
×
7645
        goto end;
×
7646
    }
7647

7648
    status = sixel_frame_new(&frame, encoder->allocator);
×
7649
    if (SIXEL_FAILED(status)) {
×
7650
        goto end;
×
7651
    }
7652

7653
    status = sixel_frame_init(frame, bytes, width, height,
×
7654
                              pixelformat, palette, ncolors);
7655
    if (SIXEL_FAILED(status)) {
×
7656
        goto end;
×
7657
    }
7658

7659
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
7660
    if (SIXEL_FAILED(status)) {
×
7661
        goto end;
×
7662
    }
7663

7664
    status = SIXEL_OK;
7665

UNCOV
7666
end:
×
7667
    /* we need to free the frame before exiting, but we can't use the
7668
       sixel_frame_destroy function, because that will also attempt to
7669
       free the pixels and palette, which we don't own */
7670
    if (frame != NULL && encoder->allocator != NULL) {
×
7671
        sixel_allocator_free(encoder->allocator, frame);
×
7672
        sixel_allocator_unref(encoder->allocator);
×
7673
    }
7674
    return status;
×
7675
}
7676

7677

7678
/*
7679
 * Toggle source-frame capture for assessment consumers.
7680
 */
7681
SIXELAPI SIXELSTATUS
7682
sixel_encoder_enable_source_capture(
3✔
7683
    sixel_encoder_t *encoder,
7684
    int enable)
7685
{
7686
    if (encoder == NULL) {
3!
7687
        sixel_helper_set_additional_message(
×
7688
            "sixel_encoder_enable_source_capture: encoder is null.");
7689
        return SIXEL_BAD_ARGUMENT;
×
7690
    }
7691

7692
    encoder->capture_source = enable ? 1 : 0;
3✔
7693
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
3!
7694
        sixel_frame_unref(encoder->capture_source_frame);
×
7695
        encoder->capture_source_frame = NULL;
×
7696
    }
7697

7698
    return SIXEL_OK;
7699
}
7700

7701

7702
/*
7703
 * Enable or disable the quantized-frame capture facility.
7704
 *
7705
 *     capture on --> encoder keeps the latest palette-quantized frame.
7706
 *     capture off --> encoder forgets previously stored frames.
7707
 */
7708
SIXELAPI SIXELSTATUS
7709
sixel_encoder_enable_quantized_capture(
3✔
7710
    sixel_encoder_t *encoder,
7711
    int enable)
7712
{
7713
    if (encoder == NULL) {
2!
7714
        sixel_helper_set_additional_message(
×
7715
            "sixel_encoder_enable_quantized_capture: encoder is null.");
7716
        return SIXEL_BAD_ARGUMENT;
×
7717
    }
7718

7719
    encoder->capture_quantized = enable ? 1 : 0;
3✔
7720
    if (!encoder->capture_quantized) {
3!
7721
        encoder->capture_valid = 0;
×
7722
    }
7723

7724
    return SIXEL_OK;
7725
}
7726

7727

7728
/*
7729
 * Materialize the captured quantized frame as a heap-allocated
7730
 * sixel_frame_t instance for assessment consumers.
7731
 */
7732
SIXELAPI SIXELSTATUS
7733
sixel_encoder_copy_quantized_frame(
×
7734
    sixel_encoder_t   *encoder,
7735
    sixel_allocator_t *allocator,
7736
    sixel_frame_t     **ppframe)
7737
{
7738
    SIXELSTATUS status = SIXEL_FALSE;
×
7739
    sixel_frame_t *frame;
×
7740
    unsigned char *pixels;
×
7741
    unsigned char *palette;
×
7742
    size_t palette_bytes;
×
7743

7744
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
7745
        sixel_helper_set_additional_message(
×
7746
            "sixel_encoder_copy_quantized_frame: invalid argument.");
7747
        return SIXEL_BAD_ARGUMENT;
×
7748
    }
7749

7750
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
7751
        sixel_helper_set_additional_message(
×
7752
            "sixel_encoder_copy_quantized_frame: no frame captured.");
7753
        return SIXEL_RUNTIME_ERROR;
×
7754
    }
7755

7756
    *ppframe = NULL;
×
7757
    frame = NULL;
×
7758
    pixels = NULL;
×
7759
    palette = NULL;
×
7760

7761
    status = sixel_frame_new(&frame, allocator);
×
7762
    if (SIXEL_FAILED(status)) {
×
7763
        return status;
7764
    }
7765

7766
    if (encoder->capture_pixel_bytes > 0) {
×
7767
        pixels = (unsigned char *)sixel_allocator_malloc(
×
7768
            allocator, encoder->capture_pixel_bytes);
7769
        if (pixels == NULL) {
×
7770
            sixel_helper_set_additional_message(
×
7771
                "sixel_encoder_copy_quantized_frame: "
7772
                "sixel_allocator_malloc() failed.");
7773
            status = SIXEL_BAD_ALLOCATION;
×
7774
            goto cleanup;
×
7775
        }
7776
        memcpy(pixels,
×
UNCOV
7777
               encoder->capture_pixels,
×
7778
               encoder->capture_pixel_bytes);
7779
    }
7780

7781
    palette_bytes = encoder->capture_palette_size;
×
7782
    if (palette_bytes > 0) {
×
7783
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
7784
                                                          palette_bytes);
7785
        if (palette == NULL) {
×
7786
            sixel_helper_set_additional_message(
×
7787
                "sixel_encoder_copy_quantized_frame: "
7788
                "sixel_allocator_malloc() failed.");
7789
            status = SIXEL_BAD_ALLOCATION;
×
7790
            goto cleanup;
×
7791
        }
7792
        memcpy(palette,
×
UNCOV
7793
               encoder->capture_palette,
×
7794
               palette_bytes);
7795
    }
7796

7797
    status = sixel_frame_init(frame,
×
7798
                              pixels,
7799
                              encoder->capture_width,
7800
                              encoder->capture_height,
7801
                              encoder->capture_pixelformat,
7802
                              palette,
7803
                              encoder->capture_ncolors);
7804
    if (SIXEL_FAILED(status)) {
×
7805
        goto cleanup;
×
7806
    }
7807

7808
    pixels = NULL;
×
7809
    palette = NULL;
×
7810
    /*
7811
     * Capture colorspace must be preserved for assessment consumers.
7812
     * Keep access encapsulated via the public setter to avoid
7813
     * depending on frame internals.
7814
     */
7815
    sixel_frame_set_colorspace(frame, encoder->capture_colorspace);
×
7816
    *ppframe = frame;
×
7817
    return SIXEL_OK;
×
7818

UNCOV
7819
cleanup:
×
7820
    if (palette != NULL) {
×
7821
        sixel_allocator_free(allocator, palette);
×
7822
    }
7823
    if (pixels != NULL) {
×
7824
        sixel_allocator_free(allocator, pixels);
×
7825
    }
7826
    if (frame != NULL) {
×
7827
        sixel_frame_unref(frame);
×
7828
    }
7829
    return status;
7830
}
7831

7832

7833
/*
7834
 * Emit the captured palette in the requested format.
7835
 *
7836
 *   palette_output == NULL  -> skip
7837
 *   palette_output != NULL  -> materialize captured palette
7838
 */
7839
static SIXELSTATUS
7840
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
420✔
7841
{
7842
    SIXELSTATUS status;
420✔
7843
    sixel_frame_t *frame;
420✔
7844
    unsigned char const *palette;
420✔
7845
    int exported_colors;
420✔
7846
    FILE *stream;
420✔
7847
    int close_stream;
420✔
7848
    char const *path;
420✔
7849
    sixel_palette_format_t format_hint;
420✔
7850
    sixel_palette_format_t format_ext;
420✔
7851
    sixel_palette_format_t format_final;
420✔
7852
    char const *mode;
420✔
7853

7854
    status = SIXEL_OK;
420✔
7855
    frame = NULL;
420✔
7856
    palette = NULL;
420✔
7857
    exported_colors = 0;
420✔
7858
    stream = NULL;
420✔
7859
    close_stream = 0;
420✔
7860
    path = NULL;
420✔
7861
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
420✔
7862
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
420✔
7863
    format_final = SIXEL_PALETTE_FORMAT_NONE;
420✔
7864
    mode = "wb";
420✔
7865

7866
    if (encoder == NULL || encoder->palette_output == NULL) {
420!
7867
        return SIXEL_OK;
7868
    }
7869

7870
    status = sixel_encoder_copy_quantized_frame(encoder,
×
7871
                                                encoder->allocator,
7872
                                                &frame);
7873
    if (SIXEL_FAILED(status)) {
×
7874
        return status;
7875
    }
7876

7877
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
7878
    exported_colors = sixel_frame_get_ncolors(frame);
×
7879
    if (palette == NULL || exported_colors <= 0) {
×
7880
        sixel_helper_set_additional_message(
×
7881
            "sixel_encoder_emit_palette_output: palette unavailable.");
7882
        status = SIXEL_BAD_INPUT;
×
7883
        goto cleanup;
×
7884
    }
7885
    if (exported_colors > 256) {
×
7886
        exported_colors = 256;
7887
    }
7888

7889
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
7890
    if (path == NULL || *path == '\0') {
×
7891
        sixel_helper_set_additional_message(
×
7892
            "sixel_encoder_emit_palette_output: invalid path.");
7893
        status = SIXEL_BAD_ARGUMENT;
×
7894
        goto cleanup;
×
7895
    }
7896

7897
    format_ext = sixel_palette_format_from_extension(path);
×
7898
    format_final = format_hint;
×
7899
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
7900
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
7901
            if (strcmp(path, "-") == 0) {
×
7902
                sixel_helper_set_additional_message(
×
7903
                    "sixel_encoder_emit_palette_output: "
7904
                    "format required for '-'.");
7905
                status = SIXEL_BAD_ARGUMENT;
×
7906
                goto cleanup;
×
7907
            }
7908
            sixel_helper_set_additional_message(
×
7909
                "sixel_encoder_emit_palette_output: "
7910
                "unknown palette file extension.");
7911
            status = SIXEL_BAD_ARGUMENT;
×
7912
            goto cleanup;
×
7913
        }
7914
        format_final = format_ext;
7915
    }
7916
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
7917
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
7918
    }
7919

7920
    if (strcmp(path, "-") == 0) {
×
7921
        stream = stdout;
×
7922
    } else {
7923
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
7924
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
×
7925
            mode = "w";
7926
        } else {
7927
            mode = "wb";
×
7928
        }
7929
        stream = fopen(path, mode);
×
7930
        if (stream == NULL) {
×
7931
            sixel_helper_set_additional_message(
×
7932
                "sixel_encoder_emit_palette_output: failed to open file.");
7933
            status = SIXEL_LIBC_ERROR;
×
7934
            goto cleanup;
×
7935
        }
7936
        close_stream = 1;
7937
    }
7938

7939
    switch (format_final) {
×
UNCOV
7940
    case SIXEL_PALETTE_FORMAT_ACT:
×
7941
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
7942
        if (SIXEL_FAILED(status)) {
×
7943
            sixel_helper_set_additional_message(
×
7944
                "sixel_encoder_emit_palette_output: failed to write ACT.");
7945
        }
7946
        break;
UNCOV
7947
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
×
7948
        status = sixel_palette_write_pal_jasc(stream,
×
7949
                                              palette,
7950
                                              exported_colors);
7951
        if (SIXEL_FAILED(status)) {
×
7952
            sixel_helper_set_additional_message(
×
7953
                "sixel_encoder_emit_palette_output: failed to write JASC.");
7954
        }
7955
        break;
UNCOV
7956
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
×
7957
        status = sixel_palette_write_pal_riff(stream,
×
7958
                                              palette,
7959
                                              exported_colors);
7960
        if (SIXEL_FAILED(status)) {
×
7961
            sixel_helper_set_additional_message(
×
7962
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
7963
        }
7964
        break;
UNCOV
7965
    case SIXEL_PALETTE_FORMAT_GPL:
×
7966
        status = sixel_palette_write_gpl(stream,
×
7967
                                         palette,
7968
                                         exported_colors);
7969
        if (SIXEL_FAILED(status)) {
×
7970
            sixel_helper_set_additional_message(
×
7971
                "sixel_encoder_emit_palette_output: failed to write GPL.");
7972
        }
7973
        break;
UNCOV
7974
    default:
×
7975
        sixel_helper_set_additional_message(
×
7976
            "sixel_encoder_emit_palette_output: unsupported format.");
7977
        status = SIXEL_BAD_ARGUMENT;
×
7978
        break;
×
7979
    }
7980
    if (SIXEL_FAILED(status)) {
×
7981
        goto cleanup;
×
7982
    }
7983

7984
    if (close_stream) {
×
7985
        if (fclose(stream) != 0) {
×
7986
            sixel_helper_set_additional_message(
×
7987
                "sixel_encoder_emit_palette_output: fclose() failed.");
7988
            status = SIXEL_LIBC_ERROR;
×
7989
            stream = NULL;
×
7990
            goto cleanup;
×
7991
        }
7992
        stream = NULL;
7993
    } else {
7994
        if (fflush(stream) != 0) {
×
7995
            sixel_helper_set_additional_message(
×
7996
                "sixel_encoder_emit_palette_output: fflush() failed.");
7997
            status = SIXEL_LIBC_ERROR;
×
7998
            goto cleanup;
×
7999
        }
8000
    }
8001

UNCOV
8002
cleanup:
×
8003
    if (close_stream && stream != NULL) {
×
8004
        (void) fclose(stream);
×
8005
    }
8006
    if (frame != NULL) {
×
8007
        sixel_frame_unref(frame);
×
8008
    }
8009

8010
    return status;
8011
}
8012

8013

8014
/*
8015
 * Share the captured source frame with assessment consumers.
8016
 */
8017
SIXELAPI SIXELSTATUS
8018
sixel_encoder_copy_source_frame(
3✔
8019
    sixel_encoder_t *encoder,
8020
    sixel_frame_t  **ppframe)
8021
{
8022
    if (encoder == NULL || ppframe == NULL) {
3!
8023
        sixel_helper_set_additional_message(
×
8024
            "sixel_encoder_copy_source_frame: invalid argument.");
8025
        return SIXEL_BAD_ARGUMENT;
×
8026
    }
8027

8028
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
3!
8029
        sixel_helper_set_additional_message(
×
8030
            "sixel_encoder_copy_source_frame: no frame captured.");
8031
        return SIXEL_RUNTIME_ERROR;
×
8032
    }
8033

8034
    sixel_frame_ref(encoder->capture_source_frame);
3✔
8035
    *ppframe = encoder->capture_source_frame;
3✔
8036

8037
    return SIXEL_OK;
3✔
8038
}
8039

8040

8041
#if HAVE_TESTS
8042
static int
8043
test1(void)
×
8044
{
8045
    int nret = EXIT_FAILURE;
×
8046
    sixel_encoder_t *encoder = NULL;
×
8047

8048
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8049
#  pragma GCC diagnostic push
8050
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8051
#endif
8052
    encoder = sixel_encoder_create();
×
8053
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8054
#  pragma GCC diagnostic pop
8055
#endif
8056
    if (encoder == NULL) {
×
8057
        goto error;
×
8058
    }
8059
    sixel_encoder_ref(encoder);
×
8060
    sixel_encoder_unref(encoder);
×
8061
    nret = EXIT_SUCCESS;
×
8062

UNCOV
8063
error:
×
8064
    sixel_encoder_unref(encoder);
×
8065
    return nret;
×
8066
}
8067

8068

8069
static int
8070
test2(void)
×
8071
{
8072
    int nret = EXIT_FAILURE;
×
8073
    SIXELSTATUS status;
×
8074
    sixel_encoder_t *encoder = NULL;
×
8075
    sixel_frame_t *frame = NULL;
×
8076
    unsigned char *buffer;
×
8077
    int height = 0;
×
8078
    int is_animation = 0;
×
8079

8080
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8081
#  pragma GCC diagnostic push
8082
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8083
#endif
8084
    encoder = sixel_encoder_create();
×
8085
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8086
#  pragma GCC diagnostic pop
8087
#endif
8088
    if (encoder == NULL) {
×
8089
        goto error;
×
8090
    }
8091

8092
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8093
#  pragma GCC diagnostic push
8094
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8095
#endif
8096
    frame = sixel_frame_create();
×
8097
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8098
#  pragma GCC diagnostic pop
8099
#endif
8100
    if (encoder == NULL) {
×
8101
        goto error;
8102
    }
8103

8104
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
8105
    if (buffer == NULL) {
×
8106
        goto error;
×
8107
    }
8108
    status = sixel_frame_init(frame, buffer, 1, 1,
×
8109
                              SIXEL_PIXELFORMAT_RGB888,
8110
                              NULL, 0);
8111
    if (SIXEL_FAILED(status)) {
×
8112
        goto error;
×
8113
    }
8114

8115
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
8116
        is_animation = 1;
8117
    }
8118

8119
    height = sixel_frame_get_height(frame);
×
8120

8121
    status = sixel_tty_scroll(sixel_write_callback,
×
8122
                              &encoder->outfd,
×
8123
                              encoder->outfd,
8124
                              height,
8125
                              is_animation);
8126
    if (SIXEL_FAILED(status)) {
×
8127
        goto error;
×
8128
    }
8129

8130
    nret = EXIT_SUCCESS;
8131

UNCOV
8132
error:
×
8133
    sixel_encoder_unref(encoder);
×
8134
    sixel_frame_unref(frame);
×
8135
    return nret;
×
8136
}
8137

8138

8139
static int
8140
test3(void)
×
8141
{
8142
    int nret = EXIT_FAILURE;
×
8143
    int result;
×
8144

8145
    result = sixel_tty_wait_stdin(1000);
×
8146
    if (result != 0) {
×
8147
        goto error;
×
8148
    }
8149

8150
    nret = EXIT_SUCCESS;
8151

UNCOV
8152
error:
×
8153
    return nret;
×
8154
}
8155

8156

8157
static int
8158
test4(void)
×
8159
{
8160
    int nret = EXIT_FAILURE;
×
8161
    sixel_encoder_t *encoder = NULL;
×
8162
    SIXELSTATUS status;
×
8163

8164
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8165
# pragma GCC diagnostic push
8166
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8167
#endif
8168
    encoder = sixel_encoder_create();
×
8169
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8170
# pragma GCC diagnostic pop
8171
#endif
8172
    if (encoder == NULL) {
×
8173
        goto error;
×
8174
    }
8175

8176
    status = sixel_encoder_setopt(encoder,
×
8177
                                  SIXEL_OPTFLAG_LOOPMODE,
8178
                                  "force");
8179
    if (SIXEL_FAILED(status)) {
×
8180
        goto error;
×
8181
    }
8182

8183
    status = sixel_encoder_setopt(encoder,
×
8184
                                  SIXEL_OPTFLAG_PIPE_MODE,
8185
                                  "force");
8186
    if (SIXEL_FAILED(status)) {
×
8187
        goto error;
×
8188
    }
8189

8190
    nret = EXIT_SUCCESS;
8191

UNCOV
8192
error:
×
8193
    sixel_encoder_unref(encoder);
×
8194
    return nret;
×
8195
}
8196

8197

8198
static int
8199
test5(void)
×
8200
{
8201
    int nret = EXIT_FAILURE;
×
8202
    sixel_encoder_t *encoder = NULL;
×
8203
    sixel_allocator_t *allocator = NULL;
×
8204
    SIXELSTATUS status;
×
8205

8206
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
8207
    if (SIXEL_FAILED(status)) {
×
8208
        goto error;
×
8209
    }
8210

8211
    status = sixel_encoder_new(&encoder, allocator);
×
8212
    if (SIXEL_FAILED(status)) {
×
8213
        goto error;
×
8214
    }
8215

8216
    sixel_encoder_ref(encoder);
×
8217
    sixel_encoder_unref(encoder);
×
8218
    nret = EXIT_SUCCESS;
×
8219

UNCOV
8220
error:
×
8221
    sixel_encoder_unref(encoder);
×
8222
    return nret;
×
8223
}
8224

8225

8226
SIXELAPI int
8227
sixel_encoder_tests_main(void)
×
8228
{
8229
    int nret = EXIT_FAILURE;
×
8230
    size_t i;
×
UNCOV
8231
    typedef int (* testcase)(void);
×
8232

UNCOV
8233
    static testcase const testcases[] = {
×
8234
        test1,
8235
        test2,
8236
        test3,
8237
        test4,
8238
        test5
8239
    };
8240

8241
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
8242
        nret = testcases[i]();
×
8243
        if (nret != EXIT_SUCCESS) {
×
8244
            goto error;
×
8245
        }
8246
    }
8247

8248
    nret = EXIT_SUCCESS;
8249

UNCOV
8250
error:
×
8251
    return nret;
×
8252
}
8253
#endif  /* HAVE_TESTS */
8254

8255

8256
/* emacs Local Variables:      */
8257
/* emacs mode: c               */
8258
/* emacs tab-width: 4          */
8259
/* emacs indent-tabs-mode: nil */
8260
/* emacs c-basic-offset: 4     */
8261
/* emacs End:                  */
8262
/* vim: set expandtab ts=4 : */
8263
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc