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

saitoha / libsixel / 20098695301

10 Dec 2025 12:30PM UTC coverage: 44.664% (+0.2%) from 44.514%
20098695301

push

github

saitoha
python: fix python setopt flag validation

11830 of 39971 branches covered (29.6%)

16060 of 35957 relevant lines covered (44.66%)

3459040.5 hits per line

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

44.2
/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
#define SIXEL_ENCODER_LUT_POLICY_ENVVAR "SIXEL_DITHER_LOOKUP_POLICY"
128

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

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

153
#if defined(_WIN32)
154

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

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

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

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

186
#endif /* _WIN32 */
187

188

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

344

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

354
    len = strlen(s);
60✔
355

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

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

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

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

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

385
    return 1;
386
}
387

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

395
    prefer_float32 = encoder->prefer_float32;
×
396

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

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

411
    encoder->prefer_float32 = prefer_float32;
×
412

413
    return SIXEL_OK;
×
414
}
415

416

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

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

565
    status = SIXEL_OK;
566

567
end:
48✔
568
    sixel_allocator_free(allocator, buf);
48✔
569

570
    return status;
48✔
571
}
572

573

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

580
    result = (int)sixel_compat_write(*(int *)priv,
6,605✔
581
                                     data,
582
                                     (size_t)size);
583

584
    return result;
6,794✔
585
}
586

587

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

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

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

611
    return result;
72✔
612
}
613

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

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

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

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

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

682
    return written;
×
683
}
684

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

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

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

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

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

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

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

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

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

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

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

811

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

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

832
    status = SIXEL_OK;
833

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

838

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

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

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

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

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

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

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

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

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

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

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

1032
    return status;
1033
}
1034

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

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

1050
    status = SIXEL_OK;
1051

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

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

1064
    width_hint = 0;
519✔
1065
    height_hint = 0;
519✔
1066
    base = 0;
519✔
1067
    size = 0;
519✔
1068

1069
    if (encoder == NULL) {
519!
1070
        return 0;
1071
    }
1072

1073
    width_hint = encoder->pixelwidth;
519✔
1074
    height_hint = encoder->pixelheight;
519✔
1075

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

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

1102
    return (int)size;
117✔
1103
}
1104

1105

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

1115

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

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

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

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

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

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

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

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

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

1219
        break;
1220
    }
1221

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

1226

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

1230

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

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

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

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

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

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

1263
    return 1;
1264
}
1265

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

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

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

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

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

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

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

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

1352
    return spec;
1353
}
1354

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

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

1372
    return SIXEL_PALETTE_FORMAT_NONE;
1373
}
1374

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

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

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

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

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

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

1419
    return 1;
1420
}
1421

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

1434

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

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

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

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

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

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

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

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

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

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

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

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

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

1550

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

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

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

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

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

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

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

1605

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

1614

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

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

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

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

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

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

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

1649
    return SIXEL_PALETTE_FORMAT_NONE;
1650
}
1651

1652

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

1662

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

1675

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

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

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

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

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

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

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

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

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

1776

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2010

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

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

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

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

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

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

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

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

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

2145

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2347

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

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

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

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

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

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

2401
    return SIXEL_OK;
2402
}
2403

2404

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

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

2429

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

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

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

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

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

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

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

2491

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

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

2526

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2832
    *dither = callback_context.dither;
21✔
2833

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

2843
    return status;
21✔
2844
}
2845

2846

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

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

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

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

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

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

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

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

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

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

2992
    status = SIXEL_OK;
219✔
2993

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

3009

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

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

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

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

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

3045
    use_float_resize = 0;
561✔
3046

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

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

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

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

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

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

3116
    /* success */
3117
    status = SIXEL_OK;
546✔
3118

3119
end:
3120
    return status;
3121
}
3122

3123

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

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

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

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

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

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

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

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

3196
    /* success */
3197
    status = SIXEL_OK;
546✔
3198

3199
end:
546✔
3200
    return status;
546✔
3201
}
3202

3203

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

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

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

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

3249

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

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

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

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

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

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

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

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

3378
    pixbuf = sixel_frame_get_pixels(frame);
462✔
3379
    memcpy(p, pixbuf, (size_t)(width * height * depth));
462✔
3380

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

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

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

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

3428
    return status;
462✔
3429
}
3430

3431

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

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

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

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

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

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

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

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

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

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

3676
    return status;
84✔
3677
}
3678

3679

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

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

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

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

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

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

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

3776
    status = SIXEL_OK;
×
3777

3778
end:
×
3779
    return status;
×
3780
}
3781

3782

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

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

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

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

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

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

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

3893
    status = SIXEL_OK;
×
3894

3895
end:
×
3896
    return status;
×
3897
}
3898

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4353

4354
end:
546✔
4355
    if (output) {
567✔
4356
        sixel_output_unref(output);
546✔
4357
    }
4358
    if (dither) {
567✔
4359
        sixel_dither_unref(dither);
546✔
4360
    }
4361

4362
    return status;
567✔
4363
}
4364

4365

4366
/* create encoder object */
4367
SIXELAPI SIXELSTATUS
4368
sixel_encoder_new(
639✔
4369
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
4370
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
4371
                                                  default allocator */
4372
{
4373
    SIXELSTATUS status = SIXEL_FALSE;
639✔
4374
    char const *env_default_bgcolor = NULL;
639✔
4375
    char const *env_default_ncolors = NULL;
639✔
4376
    char const *env_prefer_float32 = NULL;
639✔
4377
    char const *env_lookup_policy = NULL;
639✔
4378
    int ncolors;
639✔
4379
    int prefer_float32;
639✔
4380
    int env_match_value;
639✔
4381
    sixel_option_choice_result_t match_result;
639✔
4382
    char match_detail[128];
639✔
4383

4384
    if (allocator == NULL) {
639!
4385
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
639✔
4386
        if (SIXEL_FAILED(status)) {
639!
4387
            goto end;
×
4388
        }
4389
    } else {
4390
        sixel_allocator_ref(allocator);
×
4391
    }
4392

4393
    *ppencoder
639✔
4394
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
639✔
4395
                                                    sizeof(sixel_encoder_t));
4396
    if (*ppencoder == NULL) {
639!
4397
        sixel_helper_set_additional_message(
×
4398
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
4399
        status = SIXEL_BAD_ALLOCATION;
×
4400
        sixel_allocator_unref(allocator);
×
4401
        goto end;
×
4402
    }
4403

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

4492
    prefer_float32 = 0;
639✔
4493
    env_prefer_float32 = sixel_compat_getenv(
639✔
4494
        SIXEL_ENCODER_PRECISION_ENVVAR);
4495
    /*
4496
     * $SIXEL_FLOAT32_DITHER seeds the precision preference and is later
4497
     * overridden by the precision CLI flag when provided.
4498
     */
4499
    prefer_float32 = sixel_encoder_env_prefers_float32(env_prefer_float32);
639✔
4500
    (*ppencoder)->prefer_float32 = prefer_float32;
639✔
4501

4502
    /*
4503
     * $SIXEL_DITHER_LOOKUP_POLICY mirrors the -~ flag so automated wrappers
4504
     * can seed the LUT backend before CLI overrides run.  Invalid prefixes are
4505
     * ignored to avoid hard failures when the environment is user-provided.
4506
     */
4507
    match_detail[0] = '\0';
639✔
4508
    env_lookup_policy = sixel_compat_getenv(
639✔
4509
        SIXEL_ENCODER_LUT_POLICY_ENVVAR);
4510
    if (env_lookup_policy != NULL) {
639!
4511
        match_result = sixel_option_match_choice(
×
4512
            env_lookup_policy,
4513
            g_option_choices_lut_policy,
4514
            sizeof(g_option_choices_lut_policy)
4515
            / sizeof(g_option_choices_lut_policy[0]),
4516
            &env_match_value,
4517
            match_detail,
4518
            sizeof(match_detail));
4519
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
4520
            (*ppencoder)->lut_policy = env_match_value;
×
4521
        }
4522
    }
4523

4524
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
4525
    env_default_bgcolor = sixel_compat_getenv("SIXEL_BGCOLOR");
639✔
4526
    if (env_default_bgcolor != NULL) {
639!
4527
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
4528
                                         env_default_bgcolor,
4529
                                         allocator);
4530
        if (SIXEL_FAILED(status)) {
×
4531
            goto error;
×
4532
        }
4533
    }
4534

4535
    /* evaluate environment variable ${SIXEL_COLORS} */
4536
    env_default_ncolors = sixel_compat_getenv("SIXEL_COLORS");
639✔
4537
    if (env_default_ncolors) {
639!
4538
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
4539
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
4540
            (*ppencoder)->reqcolors = ncolors;
×
4541
        }
4542
    }
4543

4544
    /* success */
4545
    status = SIXEL_OK;
639✔
4546

4547
    goto end;
639✔
4548

4549
error:
×
4550
    sixel_allocator_free(allocator, *ppencoder);
×
4551
    sixel_allocator_unref(allocator);
×
4552
    *ppencoder = NULL;
×
4553

4554
end:
639✔
4555
    return status;
639✔
4556
}
4557

4558

4559
/* create encoder object (deprecated version) */
4560
SIXELAPI /* deprecated */ sixel_encoder_t *
4561
sixel_encoder_create(void)
×
4562
{
4563
    SIXELSTATUS status = SIXEL_FALSE;
×
4564
    sixel_encoder_t *encoder = NULL;
×
4565

4566
    status = sixel_encoder_new(&encoder, NULL);
×
4567
    if (SIXEL_FAILED(status)) {
×
4568
        return NULL;
4569
    }
4570

4571
    return encoder;
×
4572
}
4573

4574

4575
/* destroy encoder object */
4576
static void
4577
sixel_encoder_destroy(sixel_encoder_t *encoder)
636✔
4578
{
4579
    sixel_allocator_t *allocator;
636✔
4580

4581
    if (encoder) {
636!
4582
        allocator = encoder->allocator;
636✔
4583
        sixel_allocator_free(allocator, encoder->mapfile);
636✔
4584
        sixel_allocator_free(allocator, encoder->palette_output);
636✔
4585
        sixel_allocator_free(allocator, encoder->loader_order);
636✔
4586
        sixel_allocator_free(allocator, encoder->bgcolor);
636✔
4587
        sixel_dither_unref(encoder->dither_cache);
636✔
4588
        if (encoder->outfd
636!
4589
            && encoder->outfd != STDOUT_FILENO
1!
4590
            && encoder->outfd != STDERR_FILENO) {
636✔
4591
            (void)sixel_compat_close(encoder->outfd);
114✔
4592
        }
4593
        if (encoder->tile_outfd >= 0
636!
4594
            && encoder->tile_outfd != encoder->outfd
×
4595
            && encoder->tile_outfd != STDOUT_FILENO
×
4596
            && encoder->tile_outfd != STDERR_FILENO) {
×
4597
            (void)sixel_compat_close(encoder->tile_outfd);
×
4598
        }
4599
        if (encoder->capture_source_frame != NULL) {
636✔
4600
            sixel_frame_unref(encoder->capture_source_frame);
3✔
4601
        }
4602
        if (encoder->clipboard_output_path != NULL) {
636!
4603
            (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4604
            encoder->clipboard_output_path = NULL;
×
4605
        }
4606
        encoder->clipboard_output_active = 0;
636✔
4607
        encoder->clipboard_output_format[0] = '\0';
636✔
4608
        sixel_allocator_free(allocator, encoder->capture_pixels);
636✔
4609
        sixel_allocator_free(allocator, encoder->capture_palette);
636✔
4610
        sixel_allocator_free(allocator, encoder->png_output_path);
636✔
4611
        sixel_allocator_free(allocator, encoder->sixel_output_path);
636✔
4612
        sixel_allocator_free(allocator, encoder);
636✔
4613
        sixel_allocator_unref(allocator);
636✔
4614
    }
4615
}
636✔
4616

4617

4618
/* increase reference count of encoder object (thread-unsafe) */
4619
SIXELAPI void
4620
sixel_encoder_ref(sixel_encoder_t *encoder)
1,458✔
4621
{
4622
    /* TODO: be thread safe */
4623
    ++encoder->ref;
1,458✔
4624
}
1,076✔
4625

4626

4627
/* decrease reference count of encoder object (thread-unsafe) */
4628
SIXELAPI void
4629
sixel_encoder_unref(sixel_encoder_t *encoder)
2,094✔
4630
{
4631
    /* TODO: be thread safe */
4632
    if (encoder != NULL && --encoder->ref == 0) {
2,094!
4633
        sixel_encoder_destroy(encoder);
636✔
4634
    }
4635
}
2,094✔
4636

4637

4638
/* set cancel state flag to encoder object */
4639
SIXELAPI SIXELSTATUS
4640
sixel_encoder_set_cancel_flag(
432✔
4641
    sixel_encoder_t /* in */ *encoder,
4642
    int             /* in */ *cancel_flag
4643
)
4644
{
4645
    SIXELSTATUS status = SIXEL_OK;
432✔
4646

4647
    encoder->cancel_flag = cancel_flag;
432✔
4648

4649
    return status;
432✔
4650
}
4651

4652

4653
/*
4654
 * parse_assessment_sections() translates a comma-separated section list into
4655
 * the bitmask understood by the assessment back-end.  The accepted grammar is
4656
 * intentionally small so that the CLI contract stays predictable:
4657
 *
4658
 *     list := section {"," section}
4659
 *     section := name | name "@" view
4660
 *
4661
 * Each name maps to a bit flag while the optional view toggles the encoded vs
4662
 * quantized quality comparison.  The helper folds case, trims ASCII
4663
 * whitespace, and rejects mixed encoded/quantized requests so that the caller
4664
 * can rely on a single coherent capture strategy.
4665
 */
4666
static int
4667
parse_assessment_sections(char const *spec,
6✔
4668
                          unsigned int *out_sections)
4669
{
4670
    char *copy;
6✔
4671
    char *cursor;
6✔
4672
    char *token;
6✔
4673
    char *context;
6✔
4674
    unsigned int sections;
6✔
4675
    unsigned int view;
6✔
4676
    int result;
6✔
4677
    size_t length;
6✔
4678
    size_t spec_len;
6✔
4679
    char *at;
6✔
4680
    char *base;
6✔
4681
    char *variant;
6✔
4682
    char *p;
6✔
4683

4684
    if (spec == NULL || out_sections == NULL) {
6!
4685
        return -1;
4686
    }
4687
    spec_len = strlen(spec);
6✔
4688
    copy = (char *)malloc(spec_len + 1u);
6✔
4689
    if (copy == NULL) {
6!
4690
        return -1;
4691
    }
4692
    memcpy(copy, spec, spec_len + 1u);
6✔
4693
    cursor = copy;
6✔
4694
    sections = 0u;
6✔
4695
    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
6✔
4696
    result = 0;
6✔
4697
    context = NULL;
6✔
4698
    while (result == 0) {
6!
4699
        token = sixel_compat_strtok(cursor, ",", &context);
12✔
4700
        if (token == NULL) {
12✔
4701
            break;
4702
        }
4703
        cursor = NULL;
6✔
4704
        while (*token == ' ' || *token == '\t') {
6!
4705
            token += 1;
×
4706
        }
4707
        length = strlen(token);
6✔
4708
        while (length > 0u &&
6!
4709
               (token[length - 1u] == ' ' || token[length - 1u] == '\t')) {
6!
4710
            token[length - 1u] = '\0';
×
4711
            length -= 1u;
×
4712
        }
4713
        if (*token == '\0') {
6!
4714
            result = -1;
4715
            break;
4716
        }
4717
        for (p = token; *p != '\0'; ++p) {
42✔
4718
            *p = (char)tolower((unsigned char)*p);
36✔
4719
        }
4720
        at = strchr(token, '@');
6✔
4721
        if (at != NULL) {
6!
4722
            *at = '\0';
×
4723
            variant = at + 1;
×
4724
            if (*variant == '\0') {
×
4725
                result = -1;
4726
                break;
4727
            }
4728
        } else {
4729
            variant = NULL;
4730
        }
4731
        base = token;
6✔
4732
        if (strcmp(base, "all") == 0) {
6!
4733
            sections |= SIXEL_ASSESSMENT_SECTION_ALL;
×
4734
            if (variant != NULL && variant[0] != '\0') {
×
4735
                if (strcmp(variant, "quantized") == 0) {
×
4736
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4737
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4738
                        result = -1;
4739
                    }
4740
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
4741
                } else if (strcmp(variant, "encoded") == 0) {
×
4742
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4743
                        result = -1;
4744
                    }
4745
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
4746
                } else {
4747
                    result = -1;
4748
                }
4749
            }
4750
        } else if (strcmp(base, "basic") == 0) {
6✔
4751
            sections |= SIXEL_ASSESSMENT_SECTION_BASIC;
3✔
4752
            if (variant != NULL) {
3!
4753
                result = -1;
4754
            }
4755
        } else if (strcmp(base, "performance") == 0) {
3!
4756
            sections |= SIXEL_ASSESSMENT_SECTION_PERFORMANCE;
×
4757
            if (variant != NULL) {
×
4758
                result = -1;
4759
            }
4760
        } else if (strcmp(base, "size") == 0) {
3!
4761
            sections |= SIXEL_ASSESSMENT_SECTION_SIZE;
×
4762
            if (variant != NULL) {
×
4763
                result = -1;
4764
            }
4765
        } else if (strcmp(base, "quality") == 0) {
3!
4766
            sections |= SIXEL_ASSESSMENT_SECTION_QUALITY;
3✔
4767
            if (variant != NULL && variant[0] != '\0') {
3!
4768
                if (strcmp(variant, "quantized") == 0) {
×
4769
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4770
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4771
                        result = -1;
4772
                    }
4773
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
4774
                } else if (strcmp(variant, "encoded") == 0) {
×
4775
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4776
                        result = -1;
4777
                    }
4778
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
4779
                } else {
4780
                    result = -1;
4781
                }
4782
            } else if (variant != NULL) {
1!
4783
                if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4784
                    result = -1;
4785
                }
4786
                view = SIXEL_ASSESSMENT_VIEW_ENCODED;
4787
            }
4788
        } else {
4789
            result = -1;
4790
        }
4791
    }
4792
    if (result == 0) {
6!
4793
        if (sections == 0u) {
6!
4794
            result = -1;
4795
        } else {
4796
            if ((sections & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u &&
6!
4797
                    view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4798
                sections |= SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4799
            }
4800
            *out_sections = sections;
6✔
4801
        }
4802
    }
4803
    free(copy);
6✔
4804
    return result;
6✔
4805
}
4806

4807

4808
static int
4809
is_png_target(char const *path)
117✔
4810
{
4811
    size_t len;
117✔
4812
    int matched;
117✔
4813

4814
    /*
4815
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
4816
     *
4817
     *   argument
4818
     *   |
4819
     *   v
4820
     *   .............. . p n g
4821
     *   ^             ^^^^^^^^^
4822
     *   |             +-- case-insensitive suffix comparison
4823
     *   +-- accepts the "png:" inline prefix used for stdout capture
4824
     */
4825

4826
    len = 0;
117✔
4827
    matched = 0;
117✔
4828

4829
    if (path == NULL) {
117!
4830
        return 0;
4831
    }
4832

4833
    if (strncmp(path, "png:", 4) == 0) {
117✔
4834
        return path[4] != '\0';
6✔
4835
    }
4836

4837
    len = strlen(path);
111✔
4838
    if (len >= 4) {
111✔
4839
        matched = (tolower((unsigned char)path[len - 4]) == '.')
108✔
4840
            && (tolower((unsigned char)path[len - 3]) == 'p')
90✔
4841
            && (tolower((unsigned char)path[len - 2]) == 'n')
3!
4842
            && (tolower((unsigned char)path[len - 1]) == 'g');
111!
4843
    }
4844

4845
    return matched;
4846
}
4847

4848

4849
static char const *
4850
png_target_payload_view(char const *argument)
9✔
4851
{
4852
    /*
4853
     * Inline PNG targets split into either a prefix/payload pair or rely on
4854
     * a simple file-name suffix:
4855
     *
4856
     *   +--------------+------------+-------------+
4857
     *   | form         | payload    | destination |
4858
     *   +--------------+------------+-------------+
4859
     *   | png:         | -          | stdout      |
4860
     *   | png:         | filename   | filesystem  |
4861
     *   | *.png        | filename   | filesystem  |
4862
     *   +--------------+------------+-------------+
4863
     *
4864
     * The caller only needs the payload column, so we expose it here.  When
4865
     * the user omits the prefix we simply echo the original pointer so the
4866
     * caller can copy the value verbatim.
4867
     */
4868
    if (argument == NULL) {
9!
4869
        return NULL;
4870
    }
4871
    if (strncmp(argument, "png:", 4) == 0) {
9✔
4872
        return argument + 4;
6✔
4873
    }
4874

4875
    return argument;
4876
}
4877

4878
static void
4879
normalise_windows_drive_path(char *path)
9✔
4880
{
4881
#if defined(_WIN32)
4882
    size_t length;
4883

4884
    /*
4885
     * MSYS-like environments forward POSIX-looking absolute paths to native
4886
     * binaries.  When a user writes "/d/..." MSYS converts the command line to
4887
     * UTF-16 and preserves the literal bytes.  The Windows CRT, however,
4888
     * expects "d:/..." or "d:\...".  The tiny state machine below rewrites the
4889
     * leading token so the runtime resolves the drive correctly:
4890
     *
4891
     *   input     normalised
4892
     *   |         |
4893
     *   v         v
4894
     *   / d / ... d : / ...
4895
     *
4896
     * The body keeps the rest of the string intact so UNC paths ("//server")
4897
     * and relative references pass through untouched.
4898
     */
4899

4900
    length = 0u;
4901

4902
    if (path == NULL) {
4903
        return;
4904
    }
4905

4906
    length = strlen(path);
4907
    if (length >= 3u
4908
            && path[0] == '/'
4909
            && ((path[1] >= 'A' && path[1] <= 'Z')
4910
                || (path[1] >= 'a' && path[1] <= 'z'))
4911
            && path[2] == '/') {
4912
        path[0] = path[1];
4913
        path[1] = ':';
4914
    }
4915
#else
4916
    (void)path;
9✔
4917
#endif
4918
}
9✔
4919

4920

4921
static int
4922
is_dev_null_path(char const *path)
×
4923
{
4924
    if (path == NULL || path[0] == '\0') {
×
4925
        return 0;
4926
    }
4927
#if defined(_WIN32)
4928
    if (_stricmp(path, "nul") == 0) {
4929
        return 1;
4930
    }
4931
#endif
4932
    return strcmp(path, "/dev/null") == 0;
×
4933
}
4934

4935

4936
static int
4937
sixel_encoder_threads_token_is_auto(char const *text)
×
4938
{
4939
    if (text == NULL) {
×
4940
        return 0;
4941
    }
4942

4943
    if ((text[0] == 'a' || text[0] == 'A') &&
×
4944
        (text[1] == 'u' || text[1] == 'U') &&
×
4945
        (text[2] == 't' || text[2] == 'T') &&
×
4946
        (text[3] == 'o' || text[3] == 'O') &&
×
4947
        text[4] == '\0') {
×
4948
        return 1;
×
4949
    }
4950

4951
    return 0;
4952
}
4953

4954
static int
4955
sixel_encoder_parse_threads_argument(char const *text, int *value)
×
4956
{
4957
    long parsed;
×
4958
    char *endptr;
×
4959

4960
    parsed = 0L;
×
4961
    endptr = NULL;
×
4962

4963
    if (text == NULL || value == NULL) {
×
4964
        return 0;
4965
    }
4966

4967
    if (sixel_encoder_threads_token_is_auto(text) != 0) {
×
4968
        *value = 0;
×
4969
        return 1;
×
4970
    }
4971

4972
    errno = 0;
×
4973
    parsed = strtol(text, &endptr, 10);
×
4974
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
×
4975
        return 0;
4976
    }
4977

4978
    if (parsed < 1L || parsed > (long)INT_MAX) {
×
4979
        return 0;
4980
    }
4981

4982
    *value = (int)parsed;
×
4983
    return 1;
×
4984
}
4985

4986
/* set an option flag to encoder object */
4987
SIXELAPI SIXELSTATUS
4988
sixel_encoder_setopt(
960✔
4989
    sixel_encoder_t /* in */ *encoder,
4990
    int             /* in */ arg,
4991
    char const      /* in */ *value)
4992
{
4993
    SIXELSTATUS status = SIXEL_FALSE;
960✔
4994
    int number;
960✔
4995
    int parsed;
960✔
4996
    char unit[32];
960✔
4997
    char lowered[16];
960✔
4998
    size_t len;
960✔
4999
    size_t i;
960✔
5000
    long parsed_reqcolors;
960✔
5001
    char *endptr;
960✔
5002
    int forced_palette;
960✔
5003
    char *opt_copy;
960✔
5004
    char const *drcs_arg_delim;
960✔
5005
    char const *drcs_arg_charset;
960✔
5006
    char const *drcs_arg_second_delim;
960✔
5007
    char const *drcs_arg_path;
960✔
5008
    size_t drcs_arg_path_length;
960✔
5009
    size_t drcs_segment_length;
960✔
5010
    char drcs_segment[32];
960✔
5011
    int drcs_mmv_value;
960✔
5012
    long drcs_charset_value;
960✔
5013
    unsigned int drcs_charset_limit;
960✔
5014
    sixel_option_choice_result_t match_result;
960✔
5015
    int match_value;
960✔
5016
    char match_detail[128];
960✔
5017
    char match_message[256];
960✔
5018
    int png_argument_has_prefix = 0;
960✔
5019
    char const *png_path_view = NULL;
960✔
5020
    size_t png_path_length;
960✔
5021
    char cell_message[256];
960✔
5022
    char const *cell_detail;
960✔
5023
    unsigned int path_flags;
960✔
5024
    char const *mapfile_view;
960✔
5025
    int path_check;
960✔
5026

5027
    sixel_encoder_ref(encoder);
960✔
5028
    opt_copy = NULL;
960✔
5029
    path_flags = 0u;
960✔
5030
    mapfile_view = NULL;
960✔
5031
    path_check = 0;
960✔
5032

5033
    switch(arg) {
960!
5034
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
117✔
5035
        if (*value == '\0') {
117!
5036
            sixel_helper_set_additional_message(
×
5037
                "no file name specified.");
5038
            status = SIXEL_BAD_ARGUMENT;
×
5039
            goto end;
×
5040
        }
5041
        if (is_png_target(value)) {
117✔
5042
            encoder->output_is_png = 1;
9✔
5043
            png_argument_has_prefix =
18✔
5044
                (value != NULL)
5045
                && (strncmp(value, "png:", 4) == 0);
9!
5046
            png_path_view = png_target_payload_view(value);
9✔
5047
            if (png_argument_has_prefix
9!
5048
                    && (png_path_view == NULL
6!
5049
                        || png_path_view[0] == '\0')) {
6!
5050
                sixel_helper_set_additional_message(
×
5051
                    "sixel_encoder_setopt: missing target after the \"png:\" "
5052
                    "prefix. use png:- or png:<path> with a non-empty payload."
5053
                );
5054
                status = SIXEL_BAD_ARGUMENT;
×
5055
                goto end;
×
5056
            }
5057
            encoder->output_png_to_stdout =
12✔
5058
                (png_path_view != NULL)
5059
                && (strcmp(png_path_view, "-") == 0);
9!
5060
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
9✔
5061
            encoder->png_output_path = NULL;
9✔
5062
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
9✔
5063
            encoder->sixel_output_path = NULL;
9✔
5064
            if (! encoder->output_png_to_stdout) {
9!
5065
                /*
5066
                 * +-----------------------------------------+
5067
                 * |  PNG target normalization               |
5068
                 * +-----------------------------------------+
5069
                 * |  Raw input  |  Stored file path         |
5070
                 * |-------------+---------------------------|
5071
                 * |  png:-      |  "-" (stdout sentinel)    |
5072
                 * |  png:/foo   |  "/foo"                   |
5073
                 * +-----------------------------------------+
5074
                 * Strip the "png:" prefix so the decoder can
5075
                 * pass the true filesystem path to libpng
5076
                 * while the CLI retains its shorthand.
5077
                 */
5078
                png_path_view = value;
9✔
5079
                if (strncmp(value, "png:", 4) == 0) {
9✔
5080
                    png_path_view = value + 4;
6✔
5081
                }
5082
                if (png_path_view[0] == '\0') {
9!
5083
                    sixel_helper_set_additional_message(
×
5084
                        "sixel_encoder_setopt: PNG output path is empty.");
5085
                    status = SIXEL_BAD_ARGUMENT;
×
5086
                    goto end;
×
5087
                }
5088
                png_path_length = strlen(png_path_view);
9✔
5089
                encoder->png_output_path =
18✔
5090
                    (char *)sixel_allocator_malloc(
9✔
5091
                        encoder->allocator, png_path_length + 1u);
5092
                if (encoder->png_output_path == NULL) {
9!
5093
                    sixel_helper_set_additional_message(
×
5094
                        "sixel_encoder_setopt: sixel_allocator_malloc() "
5095
                        "failed for PNG output path.");
5096
                    status = SIXEL_BAD_ALLOCATION;
×
5097
                    goto end;
×
5098
                }
5099
                if (png_path_view != NULL) {
9!
5100
                    (void)sixel_compat_strcpy(encoder->png_output_path,
9✔
5101
                                              png_path_length + 1u,
5102
                                              png_path_view);
5103
                } else {
5104
                    encoder->png_output_path[0] = '\0';
5105
                }
5106
                normalise_windows_drive_path(encoder->png_output_path);
9✔
5107
            }
5108
        } else {
5109
            encoder->output_is_png = 0;
108✔
5110
            encoder->output_png_to_stdout = 0;
108✔
5111
            png_argument_has_prefix = 0;
108✔
5112
            png_path_view = NULL;
108✔
5113
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
108✔
5114
            encoder->png_output_path = NULL;
108✔
5115
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
108✔
5116
            encoder->sixel_output_path = NULL;
108✔
5117
            if (encoder->clipboard_output_path != NULL) {
108!
5118
                (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
5119
                sixel_allocator_free(encoder->allocator,
×
5120
                                     encoder->clipboard_output_path);
×
5121
                encoder->clipboard_output_path = NULL;
×
5122
            }
5123
            encoder->clipboard_output_active = 0;
108✔
5124
            encoder->clipboard_output_format[0] = '\0';
108✔
5125
            {
5126
                sixel_clipboard_spec_t clipboard_spec;
108✔
5127
                SIXELSTATUS clip_status;
108✔
5128
                char *spool_path;
108✔
5129
                int spool_fd;
108✔
5130

5131
                clipboard_spec.is_clipboard = 0;
108✔
5132
                clipboard_spec.format[0] = '\0';
108✔
5133
                clip_status = SIXEL_OK;
108✔
5134
                spool_path = NULL;
108✔
5135
                spool_fd = (-1);
108✔
5136

5137
                if (sixel_clipboard_parse_spec(value, &clipboard_spec)
108!
5138
                        && clipboard_spec.is_clipboard) {
×
5139
                    clip_status = clipboard_create_spool(
×
5140
                        encoder->allocator,
5141
                        "clipboard-out",
5142
                        &spool_path,
5143
                        &spool_fd);
5144
                    if (SIXEL_FAILED(clip_status)) {
×
5145
                        status = clip_status;
×
5146
                        goto end;
×
5147
                    }
5148
                    clipboard_select_format(
×
5149
                        encoder->clipboard_output_format,
×
5150
                        sizeof(encoder->clipboard_output_format),
5151
                        clipboard_spec.format,
5152
                        "sixel");
5153
                    if (encoder->outfd
×
5154
                            && encoder->outfd != STDOUT_FILENO
1!
5155
                            && encoder->outfd != STDERR_FILENO) {
×
5156
                        (void)sixel_compat_close(encoder->outfd);
×
5157
                    }
5158
                    encoder->outfd = spool_fd;
×
5159
                    spool_fd = (-1);
×
5160
                    encoder->sixel_output_path = spool_path;
×
5161
                    encoder->clipboard_output_path = spool_path;
×
5162
                    spool_path = NULL;
×
5163
                    encoder->clipboard_output_active = 1;
×
5164
                    break;
×
5165
                }
5166

5167
                if (spool_fd >= 0) {
108!
5168
                    (void)sixel_compat_close(spool_fd);
5169
                }
5170
                if (spool_path != NULL) {
108!
5171
                    sixel_allocator_free(encoder->allocator, spool_path);
5172
                }
5173
            }
2!
5174
            if (strcmp(value, "-") != 0) {
108!
5175
                encoder->sixel_output_path = (char *)sixel_allocator_malloc(
216✔
5176
                    encoder->allocator, strlen(value) + 1);
108✔
5177
                if (encoder->sixel_output_path == NULL) {
108!
5178
                    sixel_helper_set_additional_message(
×
5179
                        "sixel_encoder_setopt: malloc() failed for output path.");
5180
                    status = SIXEL_BAD_ALLOCATION;
×
5181
                    goto end;
×
5182
                }
5183
                (void)sixel_compat_strcpy(encoder->sixel_output_path,
108✔
5184
                                          strlen(value) + 1,
108✔
5185
                                          value);
5186
            }
5187
        }
5188

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

6152
            if (len >= sizeof(lowered)) {
×
6153
                sixel_helper_set_additional_message(
×
6154
                    "specified working colorspace name is too long.");
6155
                status = SIXEL_BAD_ARGUMENT;
×
6156
                goto end;
×
6157
            }
6158
            for (i = 0; i < len; ++i) {
×
6159
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6160
            }
6161
            lowered[len] = '\0';
×
6162

6163
            match_result = sixel_option_match_choice(
×
6164
                lowered,
6165
                g_option_choices_working_colorspace,
6166
                sizeof(g_option_choices_working_colorspace) /
6167
                sizeof(g_option_choices_working_colorspace[0]),
6168
                &match_value,
6169
                match_detail,
6170
                sizeof(match_detail));
6171
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6172
                encoder->working_colorspace = match_value;
×
6173
            } else {
6174
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6175
                    sixel_option_report_ambiguous_prefix(value,
×
6176
                        match_detail,
6177
                        match_message,
6178
                        sizeof(match_message));
6179
                } else {
6180
                    sixel_option_report_invalid_choice(
×
6181
                        "unsupported working colorspace specified.",
6182
                        match_detail,
6183
                        match_message,
6184
                        sizeof(match_message));
6185
                }
6186
                status = SIXEL_BAD_ARGUMENT;
×
6187
                goto end;
×
6188
            }
6189
        }
6190
        break;
×
6191
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
×
6192
        if (value == NULL) {
×
6193
            sixel_helper_set_additional_message(
×
6194
                "output-colorspace requires an argument.");
6195
            status = SIXEL_BAD_ARGUMENT;
×
6196
            goto end;
×
6197
        } else {
6198
            len = strlen(value);
×
6199

6200
            if (len >= sizeof(lowered)) {
×
6201
                sixel_helper_set_additional_message(
×
6202
                    "specified output colorspace name is too long.");
6203
                status = SIXEL_BAD_ARGUMENT;
×
6204
                goto end;
×
6205
            }
6206
            for (i = 0; i < len; ++i) {
×
6207
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6208
            }
6209
            lowered[len] = '\0';
×
6210

6211
            match_result = sixel_option_match_choice(
×
6212
                lowered,
6213
                g_option_choices_output_colorspace,
6214
                sizeof(g_option_choices_output_colorspace) /
6215
                sizeof(g_option_choices_output_colorspace[0]),
6216
                &match_value,
6217
                match_detail,
6218
                sizeof(match_detail));
6219
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6220
                encoder->output_colorspace = match_value;
×
6221
            } else {
6222
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6223
                    sixel_option_report_ambiguous_prefix(value,
×
6224
                        match_detail,
6225
                        match_message,
6226
                        sizeof(match_message));
6227
                } else {
6228
                    sixel_option_report_invalid_choice(
×
6229
                        "unsupported output colorspace specified.",
6230
                        match_detail,
6231
                        match_message,
6232
                        sizeof(match_message));
6233
                }
6234
                status = SIXEL_BAD_ARGUMENT;
×
6235
                goto end;
×
6236
            }
6237
        }
6238
        break;
×
6239
    case SIXEL_OPTFLAG_ORMODE:  /* O */
×
6240
        encoder->ormode = 1;
×
6241
        break;
×
6242
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6243
        encoder->complexion = atoi(value);
9✔
6244
        if (encoder->complexion < 1) {
9✔
6245
            sixel_helper_set_additional_message(
3✔
6246
                "complexion parameter must be 1 or more.");
6247
            status = SIXEL_BAD_ARGUMENT;
3✔
6248
            goto end;
3✔
6249
        }
6250
        break;
6251
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
×
6252
        encoder->pipe_mode = 1;
×
6253
        break;
×
6254
    case '?':  /* unknown option */
×
6255
    default:
6256
        /* exit if unknown options are specified */
6257
        sixel_helper_set_additional_message(
×
6258
            "unknown option is specified.");
6259
        status = SIXEL_BAD_ARGUMENT;
×
6260
        goto end;
×
6261
    }
6262

6263
    /* detects arguments conflictions */
6264
    if (encoder->reqcolors != (-1)) {
882✔
6265
        switch (encoder->color_option) {
114!
6266
        case SIXEL_COLOR_OPTION_MAPFILE:
×
6267
            sixel_helper_set_additional_message(
×
6268
                "option -p, --colors conflicts with -m, --mapfile.");
6269
            status = SIXEL_BAD_ARGUMENT;
×
6270
            goto end;
×
6271
        case SIXEL_COLOR_OPTION_MONOCHROME:
3✔
6272
            sixel_helper_set_additional_message(
3✔
6273
                "option -e, --monochrome conflicts with -p, --colors.");
6274
            status = SIXEL_BAD_ARGUMENT;
3✔
6275
            goto end;
3✔
6276
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
3✔
6277
            sixel_helper_set_additional_message(
3✔
6278
                "option -p, --colors conflicts with -I, --high-color.");
6279
            status = SIXEL_BAD_ARGUMENT;
3✔
6280
            goto end;
3✔
6281
        case SIXEL_COLOR_OPTION_BUILTIN:
3✔
6282
            sixel_helper_set_additional_message(
3✔
6283
                "option -p, --colors conflicts with -b, --builtin-palette.");
6284
            status = SIXEL_BAD_ARGUMENT;
3✔
6285
            goto end;
3✔
6286
        default:
6287
            break;
6288
        }
6289
    }
6290

6291
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
6292
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
873✔
6293
        sixel_helper_set_additional_message(
3✔
6294
            "option -8 --8bit-mode conflicts"
6295
            " with -P, --penetrate.");
6296
        status = SIXEL_BAD_ARGUMENT;
3✔
6297
        goto end;
3✔
6298
    }
6299

6300
    status = SIXEL_OK;
6301

6302
end:
960✔
6303
    if (opt_copy != NULL) {
960!
6304
        sixel_allocator_free(encoder->allocator, opt_copy);
6305
    }
6306
    sixel_encoder_unref(encoder);
960✔
6307

6308
    return status;
960✔
6309
}
6310

6311

6312
/* called when image loader component load a image frame */
6313
static SIXELSTATUS
6314
load_image_callback(sixel_frame_t *frame, void *data)
564✔
6315
{
6316
    sixel_encoder_t *encoder;
564✔
6317

6318
    encoder = (sixel_encoder_t *)data;
564✔
6319
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
564!
6320
        sixel_frame_ref(frame);
3✔
6321
        encoder->capture_source_frame = frame;
3✔
6322
    }
6323

6324
    return sixel_encoder_encode_frame(encoder, frame, NULL);
564✔
6325
}
6326

6327
/*
6328
 * Build a tee for encoded-assessment output:
6329
 *
6330
 *     +-------------+     +-------------------+     +------------+
6331
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
6332
 *     +-------------+     +-------------------+     +------------+
6333
 *
6334
 * The tee sink can be stdout or a user-provided file such as /dev/null.
6335
 */
6336
static SIXELSTATUS
6337
copy_file_to_stream(char const *path,
×
6338
                    FILE *stream,
6339
                    sixel_assessment_t *assessment)
6340
{
6341
    FILE *source;
×
6342
    unsigned char buffer[4096];
×
6343
    size_t nread;
×
6344
    size_t nwritten;
×
6345
    double started_at;
×
6346
    double finished_at;
×
6347
    double duration;
×
6348

6349
    source = NULL;
×
6350
    nread = 0;
×
6351
    nwritten = 0;
×
6352
    started_at = 0.0;
×
6353
    finished_at = 0.0;
×
6354
    duration = 0.0;
×
6355

6356
    source = sixel_compat_fopen(path, "rb");
×
6357
    if (source == NULL) {
×
6358
        sixel_helper_set_additional_message(
×
6359
            "copy_file_to_stream: failed to open assessment staging file.");
6360
        return SIXEL_LIBC_ERROR;
×
6361
    }
6362

6363
    for (;;) {
×
6364
        nread = fread(buffer, 1, sizeof(buffer), source);
×
6365
        if (nread == 0) {
×
6366
            if (ferror(source)) {
×
6367
                sixel_helper_set_additional_message(
×
6368
                    "copy_file_to_stream: failed while reading assessment staging file.");
6369
                (void) fclose(source);
×
6370
                return SIXEL_LIBC_ERROR;
×
6371
            }
6372
            break;
×
6373
        }
6374
        if (assessment != NULL) {
×
6375
            started_at = sixel_assessment_timer_now();
×
6376
        }
6377
        nwritten = fwrite(buffer, 1, nread, stream);
×
6378
        if (nwritten != nread) {
×
6379
            sixel_helper_set_additional_message(
×
6380
                "img2sixel: failed while copying assessment staging file.");
6381
            (void) fclose(source);
×
6382
            return SIXEL_LIBC_ERROR;
×
6383
        }
6384
        if (assessment != NULL) {
×
6385
            finished_at = sixel_assessment_timer_now();
×
6386
            duration = finished_at - started_at;
×
6387
            if (duration < 0.0) {
×
6388
                duration = 0.0;
6389
            }
6390
            sixel_assessment_record_output_write(assessment,
×
6391
                                                 nwritten,
6392
                                                 duration);
6393
        }
6394
    }
6395

6396
    if (fclose(source) != 0) {
×
6397
        sixel_helper_set_additional_message(
×
6398
            "img2sixel: failed to close assessment staging file.");
6399
        return SIXEL_LIBC_ERROR;
×
6400
    }
6401

6402
    return SIXEL_OK;
6403
}
6404

6405
typedef struct assessment_json_sink {
6406
    FILE *stream;
6407
    int failed;
6408
} assessment_json_sink_t;
6409

6410
static void
6411
assessment_json_callback(char const *chunk,
42✔
6412
                         size_t length,
6413
                         void *user_data)
6414
{
6415
    assessment_json_sink_t *sink;
42✔
6416

6417
    sink = (assessment_json_sink_t *)user_data;
42✔
6418
    if (sink == NULL || sink->stream == NULL) {
42!
6419
        return;
6420
    }
6421
    if (sink->failed) {
42!
6422
        return;
6423
    }
6424
    if (fwrite(chunk, 1, length, sink->stream) != length) {
42!
6425
        sink->failed = 1;
×
6426
    }
6427
}
1!
6428

6429
static char *
6430
create_temp_template_with_prefix(sixel_allocator_t *allocator,
9✔
6431
                                 char const *prefix,
6432
                                 size_t *capacity_out)
6433
{
6434
    char const *tmpdir;
9✔
6435
    size_t tmpdir_len;
9✔
6436
    size_t prefix_len;
9✔
6437
    size_t suffix_len;
9✔
6438
    size_t template_len;
9✔
6439
    char *template_path;
9✔
6440
    int needs_separator;
9✔
6441
    size_t maximum_tmpdir_len;
9✔
6442

6443
    tmpdir = sixel_compat_getenv("TMPDIR");
9✔
6444
#if defined(_WIN32)
6445
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6446
        tmpdir = sixel_compat_getenv("TEMP");
6447
    }
6448
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6449
        tmpdir = sixel_compat_getenv("TMP");
6450
    }
6451
#endif
6452
    if (tmpdir == NULL || tmpdir[0] == '\0') {
9!
6453
#if defined(_WIN32)
6454
        tmpdir = ".";
6455
#else
6456
        tmpdir = "/tmp";
9✔
6457
#endif
6458
    }
6459

6460
    tmpdir_len = strlen(tmpdir);
9✔
6461
    prefix_len = 0u;
9✔
6462
    suffix_len = 0u;
9✔
6463
    if (prefix == NULL) {
9!
6464
        return NULL;
6465
    }
6466

6467
    prefix_len = strlen(prefix);
9✔
6468
    suffix_len = prefix_len + strlen("-XXXXXX");
9✔
6469
    maximum_tmpdir_len = (size_t)INT_MAX;
9✔
6470

6471
    if (maximum_tmpdir_len <= suffix_len + 2) {
9!
6472
        return NULL;
6473
    }
6474
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
9!
6475
        return NULL;
6476
    }
6477
    needs_separator = 1;
9✔
6478
    if (tmpdir_len > 0) {
9!
6479
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
9!
6480
            needs_separator = 0;
9✔
6481
        }
6482
    }
6483

6484
    template_len = tmpdir_len + suffix_len + 2;
9✔
6485
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
9✔
6486
    if (template_path == NULL) {
9!
6487
        return NULL;
6488
    }
6489

6490
    if (needs_separator) {
9!
6491
#if defined(_WIN32)
6492
        (void) snprintf(template_path, template_len,
6493
                        "%s\\%s-XXXXXX", tmpdir, prefix);
6494
#else
6495
        (void) snprintf(template_path, template_len,
9✔
6496
                        "%s/%s-XXXXXX", tmpdir, prefix);
6497
#endif
6498
    } else {
6499
        (void) snprintf(template_path, template_len,
×
6500
                        "%s%s-XXXXXX", tmpdir, prefix);
6501
    }
6502

6503
    if (capacity_out != NULL) {
9!
6504
        *capacity_out = template_len;
9✔
6505
    }
6506

6507
    return template_path;
6508
}
6509

6510

6511
static char *
6512
create_temp_template(sixel_allocator_t *allocator,
9✔
6513
                     size_t *capacity_out)
6514
{
6515
    return create_temp_template_with_prefix(allocator,
9✔
6516
                                            "img2sixel",
6517
                                            capacity_out);
6518
}
6519

6520

6521
static void
6522
clipboard_select_format(char *dest,
×
6523
                        size_t dest_size,
6524
                        char const *format,
6525
                        char const *fallback)
6526
{
6527
    char const *source;
×
6528
    size_t limit;
×
6529

6530
    if (dest == NULL || dest_size == 0u) {
×
6531
        return;
6532
    }
6533

6534
    source = fallback;
×
6535
    if (format != NULL && format[0] != '\0') {
×
6536
        source = format;
×
6537
    }
6538

6539
    limit = dest_size - 1u;
×
6540
    if (limit == 0u) {
×
6541
        dest[0] = '\0';
×
6542
        return;
×
6543
    }
6544

6545
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
×
6546
}
1!
6547

6548

6549
static SIXELSTATUS
6550
clipboard_create_spool(sixel_allocator_t *allocator,
×
6551
                       char const *prefix,
6552
                       char **path_out,
6553
                       int *fd_out)
6554
{
6555
    SIXELSTATUS status;
×
6556
    char *template_path;
×
6557
    size_t template_capacity;
×
6558
    int open_flags;
×
6559
    int fd;
×
6560
    char *tmpname_result;
×
6561

6562
    status = SIXEL_FALSE;
×
6563
    template_path = NULL;
×
6564
    template_capacity = 0u;
×
6565
    open_flags = 0;
×
6566
    fd = (-1);
×
6567
    tmpname_result = NULL;
×
6568

6569
    template_path = create_temp_template_with_prefix(allocator,
×
6570
                                                     prefix,
6571
                                                     &template_capacity);
6572
    if (template_path == NULL) {
×
6573
        sixel_helper_set_additional_message(
×
6574
            "clipboard: failed to allocate spool template.");
6575
        status = SIXEL_BAD_ALLOCATION;
×
6576
        goto end;
×
6577
    }
6578

6579
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
×
6580
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
6581
        tmpname_result = tmpnam(template_path);
×
6582
        if (tmpname_result == NULL) {
×
6583
            sixel_helper_set_additional_message(
×
6584
                "clipboard: failed to reserve spool template.");
6585
            status = SIXEL_LIBC_ERROR;
×
6586
            goto end;
×
6587
        }
6588
        template_capacity = strlen(template_path) + 1u;
×
6589
    }
6590

6591
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
×
6592
#if defined(O_EXCL)
6593
    open_flags |= O_EXCL;
×
6594
#endif
6595
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
×
6596
    if (fd < 0) {
×
6597
        sixel_helper_set_additional_message(
×
6598
            "clipboard: failed to open spool file.");
6599
        status = SIXEL_LIBC_ERROR;
×
6600
        goto end;
×
6601
    }
6602

6603
    *path_out = template_path;
×
6604
    if (fd_out != NULL) {
×
6605
        *fd_out = fd;
×
6606
        fd = (-1);
×
6607
    }
6608

6609
    template_path = NULL;
×
6610
    status = SIXEL_OK;
×
6611

6612
end:
×
6613
    if (fd >= 0) {
×
6614
        (void)sixel_compat_close(fd);
×
6615
    }
6616
    if (template_path != NULL) {
×
6617
        sixel_allocator_free(allocator, template_path);
×
6618
    }
6619

6620
    return status;
×
6621
}
6622

6623

6624
static SIXELSTATUS
6625
clipboard_write_file(char const *path,
×
6626
                     unsigned char const *data,
6627
                     size_t size)
6628
{
6629
    FILE *stream;
×
6630
    size_t written;
×
6631

6632
    if (path == NULL) {
×
6633
        sixel_helper_set_additional_message(
×
6634
            "clipboard: spool path is null.");
6635
        return SIXEL_BAD_ARGUMENT;
×
6636
    }
6637

6638
    stream = sixel_compat_fopen(path, "wb");
×
6639
    if (stream == NULL) {
×
6640
        sixel_helper_set_additional_message(
×
6641
            "clipboard: failed to open spool file for write.");
6642
        return SIXEL_LIBC_ERROR;
×
6643
    }
6644

6645
    written = 0u;
×
6646
    if (size > 0u && data != NULL) {
×
6647
        written = fwrite(data, 1u, size, stream);
×
6648
        if (written != size) {
×
6649
            (void)fclose(stream);
×
6650
            sixel_helper_set_additional_message(
×
6651
                "clipboard: failed to write spool payload.");
6652
            return SIXEL_LIBC_ERROR;
×
6653
        }
6654
    }
6655

6656
    if (fclose(stream) != 0) {
×
6657
        sixel_helper_set_additional_message(
×
6658
            "clipboard: failed to close spool file after write.");
6659
        return SIXEL_LIBC_ERROR;
×
6660
    }
6661

6662
    return SIXEL_OK;
6663
}
6664

6665

6666
static SIXELSTATUS
6667
clipboard_read_file(char const *path,
×
6668
                    unsigned char **data,
6669
                    size_t *size)
6670
{
6671
    FILE *stream;
×
6672
    long seek_result;
×
6673
    long file_size;
×
6674
    unsigned char *buffer;
×
6675
    size_t read_size;
×
6676

6677
    if (data == NULL || size == NULL) {
×
6678
        sixel_helper_set_additional_message(
×
6679
            "clipboard: read buffer pointers are null.");
6680
        return SIXEL_BAD_ARGUMENT;
×
6681
    }
6682

6683
    *data = NULL;
×
6684
    *size = 0u;
×
6685

6686
    if (path == NULL) {
×
6687
        sixel_helper_set_additional_message(
×
6688
            "clipboard: spool path is null.");
6689
        return SIXEL_BAD_ARGUMENT;
×
6690
    }
6691

6692
    stream = sixel_compat_fopen(path, "rb");
×
6693
    if (stream == NULL) {
×
6694
        sixel_helper_set_additional_message(
×
6695
            "clipboard: failed to open spool file for read.");
6696
        return SIXEL_LIBC_ERROR;
×
6697
    }
6698

6699
    seek_result = fseek(stream, 0L, SEEK_END);
×
6700
    if (seek_result != 0) {
×
6701
        (void)fclose(stream);
×
6702
        sixel_helper_set_additional_message(
×
6703
            "clipboard: failed to seek spool file.");
6704
        return SIXEL_LIBC_ERROR;
×
6705
    }
6706

6707
    file_size = ftell(stream);
×
6708
    if (file_size < 0) {
×
6709
        (void)fclose(stream);
×
6710
        sixel_helper_set_additional_message(
×
6711
            "clipboard: failed to determine spool size.");
6712
        return SIXEL_LIBC_ERROR;
×
6713
    }
6714

6715
    seek_result = fseek(stream, 0L, SEEK_SET);
×
6716
    if (seek_result != 0) {
×
6717
        (void)fclose(stream);
×
6718
        sixel_helper_set_additional_message(
×
6719
            "clipboard: failed to rewind spool file.");
6720
        return SIXEL_LIBC_ERROR;
×
6721
    }
6722

6723
    if (file_size == 0) {
×
6724
        buffer = NULL;
6725
        read_size = 0u;
6726
    } else {
6727
        buffer = (unsigned char *)malloc((size_t)file_size);
×
6728
        if (buffer == NULL) {
×
6729
            (void)fclose(stream);
×
6730
            sixel_helper_set_additional_message(
×
6731
                "clipboard: malloc() failed for spool payload.");
6732
            return SIXEL_BAD_ALLOCATION;
×
6733
        }
6734
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
×
6735
        if (read_size != (size_t)file_size) {
×
6736
            free(buffer);
×
6737
            (void)fclose(stream);
×
6738
            sixel_helper_set_additional_message(
×
6739
                "clipboard: failed to read spool payload.");
6740
            return SIXEL_LIBC_ERROR;
×
6741
        }
6742
    }
6743

6744
    if (fclose(stream) != 0) {
×
6745
        if (buffer != NULL) {
×
6746
            free(buffer);
×
6747
        }
6748
        sixel_helper_set_additional_message(
×
6749
            "clipboard: failed to close spool file after read.");
6750
        return SIXEL_LIBC_ERROR;
×
6751
    }
6752

6753
    *data = buffer;
×
6754
    *size = read_size;
×
6755

6756
    return SIXEL_OK;
×
6757
}
6758

6759

6760
static SIXELSTATUS
6761
write_png_from_sixel(char const *sixel_path, char const *output_path)
9✔
6762
{
6763
    SIXELSTATUS status;
9✔
6764
    sixel_decoder_t *decoder;
9✔
6765

6766
    status = SIXEL_FALSE;
9✔
6767
    decoder = NULL;
9✔
6768

6769
    status = sixel_decoder_new(&decoder, NULL);
9✔
6770
    if (SIXEL_FAILED(status)) {
9!
6771
        goto end;
×
6772
    }
6773

6774
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
9✔
6775
    if (SIXEL_FAILED(status)) {
9!
6776
        goto end;
×
6777
    }
6778

6779
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
9✔
6780
    if (SIXEL_FAILED(status)) {
9!
6781
        goto end;
×
6782
    }
6783

6784
    status = sixel_decoder_decode(decoder);
9✔
6785

6786
end:
9✔
6787
    sixel_decoder_unref(decoder);
9✔
6788

6789
    return status;
9✔
6790
}
6791

6792

6793
/* load source data from specified file and encode it to SIXEL format
6794
 * output to encoder->outfd */
6795
SIXELAPI SIXELSTATUS
6796
sixel_encoder_encode(
498✔
6797
    sixel_encoder_t *encoder,   /* encoder object */
6798
    char const      *filename)  /* input filename */
6799
{
6800
    SIXELSTATUS status = SIXEL_FALSE;
498✔
6801
    SIXELSTATUS palette_status = SIXEL_OK;
498✔
6802
    int fuse_palette = 1;
498✔
6803
    sixel_loader_t *loader = NULL;
498✔
6804
    sixel_allocator_t *assessment_allocator = NULL;
498✔
6805
    sixel_allocator_t *encode_allocator = NULL;
498✔
6806
    sixel_frame_t *assessment_source_frame = NULL;
498✔
6807
    sixel_frame_t *assessment_target_frame = NULL;
498✔
6808
    sixel_frame_t *assessment_expanded_frame = NULL;
498✔
6809
    unsigned int assessment_section_mask =
498✔
6810
        encoder->assessment_sections & SIXEL_ASSESSMENT_SECTION_MASK;
498✔
6811
    int assessment_need_source_capture = 0;
498✔
6812
    int assessment_need_quantized_capture = 0;
498✔
6813
    int assessment_need_quality = 0;
498✔
6814
    int assessment_quality_quantized = 0;
498✔
6815
    assessment_json_sink_t assessment_sink;
498✔
6816
    FILE *assessment_json_file = NULL;
498✔
6817
    FILE *assessment_forward_stream = NULL;
498✔
6818
    int assessment_json_owned = 0;
498✔
6819
    char *assessment_temp_path = NULL;
498✔
6820
    size_t assessment_temp_capacity = 0u;
498✔
6821
    char *assessment_tmpnam_result = NULL;
498✔
6822
    sixel_assessment_spool_mode_t assessment_spool_mode
498✔
6823
        = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
6824
    char *assessment_forward_path = NULL;
498✔
6825
    size_t assessment_output_bytes;
498✔
6826
#if HAVE_SYS_STAT_H
6827
    struct stat assessment_stat;
498✔
6828
    int assessment_stat_result;
498✔
6829
    char const *assessment_size_path = NULL;
498✔
6830
#endif
6831
    char const *png_final_path = NULL;
498✔
6832
    char *png_temp_path = NULL;
498✔
6833
    size_t png_temp_capacity = 0u;
498✔
6834
    char *png_tmpnam_result = NULL;
498✔
6835
    int png_open_flags = 0;
498✔
6836
    int spool_required;
498✔
6837
    sixel_clipboard_spec_t clipboard_spec;
498✔
6838
    char clipboard_input_format[32];
498✔
6839
    char *clipboard_input_path;
498✔
6840
    unsigned char *clipboard_blob;
498✔
6841
    size_t clipboard_blob_size;
498✔
6842
    SIXELSTATUS clipboard_status;
498✔
6843
    char const *effective_filename;
498✔
6844
    unsigned int path_flags;
498✔
6845
    int path_check;
498✔
6846
    sixel_logger_t logger;
498✔
6847
    int logger_prepared;
498✔
6848

6849
    clipboard_input_format[0] = '\0';
498✔
6850
    clipboard_input_path = NULL;
498✔
6851
    clipboard_blob = NULL;
498✔
6852
    clipboard_blob_size = 0u;
498✔
6853
    clipboard_status = SIXEL_OK;
498✔
6854
    effective_filename = filename;
498✔
6855
    path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
498✔
6856
        SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
6857
        SIXEL_OPTION_PATH_ALLOW_REMOTE;
6858
    path_check = 0;
498✔
6859
    logger_prepared = 0;
498✔
6860
    sixel_logger_init(&logger);
498✔
6861
    sixel_logger_prepare_env(&logger);
498✔
6862
    logger_prepared = logger.active;
498✔
6863
    if (encoder != NULL) {
498!
6864
        encoder->logger = &logger;
498✔
6865
        encoder->parallel_job_id = -1;
498✔
6866
    }
6867

6868
    if (filename != NULL) {
498✔
6869
        path_check = sixel_option_validate_filesystem_path(
357✔
6870
            filename,
6871
            filename,
6872
            path_flags);
6873
        if (path_check != 0) {
357!
6874
            status = SIXEL_BAD_ARGUMENT;
×
6875
            goto end;
×
6876
        }
6877
    }
6878

6879
    if (encoder != NULL) {
498!
6880
        encode_allocator = encoder->allocator;
498✔
6881
        if (encode_allocator != NULL) {
498!
6882
            /*
6883
             * Hold a reference until cleanup so worker side-effects or loader
6884
             * destruction cannot release the allocator before sequential
6885
             * teardown finishes using it.
6886
             */
6887
            sixel_allocator_ref(encode_allocator);
498✔
6888
        }
6889
    }
6890

6891
    clipboard_spec.is_clipboard = 0;
498✔
6892
    clipboard_spec.format[0] = '\0';
498✔
6893
    if (effective_filename != NULL
498!
6894
            && sixel_clipboard_parse_spec(effective_filename,
357!
6895
                                          &clipboard_spec)
6896
            && clipboard_spec.is_clipboard) {
×
6897
        clipboard_select_format(clipboard_input_format,
×
6898
                                sizeof(clipboard_input_format),
6899
                                clipboard_spec.format,
6900
                                "sixel");
6901
        clipboard_status = sixel_clipboard_read(
×
6902
            clipboard_input_format,
6903
            &clipboard_blob,
6904
            &clipboard_blob_size,
6905
            encoder->allocator);
6906
        if (SIXEL_FAILED(clipboard_status)) {
×
6907
            status = clipboard_status;
×
6908
            goto end;
×
6909
        }
6910
        clipboard_status = clipboard_create_spool(
×
6911
            encoder->allocator,
6912
            "clipboard-in",
6913
            &clipboard_input_path,
6914
            NULL);
6915
        if (SIXEL_FAILED(clipboard_status)) {
×
6916
            status = clipboard_status;
×
6917
            goto end;
×
6918
        }
6919
        clipboard_status = clipboard_write_file(
×
6920
            clipboard_input_path,
6921
            clipboard_blob,
6922
            clipboard_blob_size);
6923
        if (SIXEL_FAILED(clipboard_status)) {
×
6924
            status = clipboard_status;
×
6925
            goto end;
×
6926
        }
6927
        if (clipboard_blob != NULL) {
×
6928
            free(clipboard_blob);
×
6929
            clipboard_blob = NULL;
×
6930
        }
6931
        effective_filename = clipboard_input_path;
6932
    }
6933

6934
    if (assessment_section_mask != SIXEL_ASSESSMENT_SECTION_NONE) {
498✔
6935
        status = sixel_allocator_new(&assessment_allocator,
3✔
6936
                                     malloc,
6937
                                     calloc,
6938
                                     realloc,
6939
                                     free);
6940
        if (SIXEL_FAILED(status) || assessment_allocator == NULL) {
3!
6941
            goto end;
×
6942
        }
6943
        status = sixel_assessment_new(&encoder->assessment_observer,
3✔
6944
                                       assessment_allocator);
6945
        if (SIXEL_FAILED(status) || encoder->assessment_observer == NULL) {
3!
6946
            goto end;
×
6947
        }
6948
        sixel_assessment_select_sections(encoder->assessment_observer,
3✔
6949
                                         encoder->assessment_sections);
6950
        sixel_assessment_attach_encoder(encoder->assessment_observer,
3✔
6951
                                        encoder);
6952
        assessment_need_quality =
3✔
6953
            (assessment_section_mask & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u;
3✔
6954
        assessment_quality_quantized =
3✔
6955
            (encoder->assessment_sections & SIXEL_ASSESSMENT_VIEW_QUANTIZED) != 0u;
3✔
6956
        assessment_need_quantized_capture =
6✔
6957
            ((assessment_section_mask &
3✔
6958
              (SIXEL_ASSESSMENT_SECTION_BASIC |
6959
               SIXEL_ASSESSMENT_SECTION_SIZE)) != 0u) ||
3!
6960
            assessment_quality_quantized;
6961
        assessment_need_source_capture =
3✔
6962
            (assessment_section_mask &
6963
             (SIXEL_ASSESSMENT_SECTION_BASIC |
6964
              SIXEL_ASSESSMENT_SECTION_PERFORMANCE |
6965
              SIXEL_ASSESSMENT_SECTION_SIZE |
6966
              SIXEL_ASSESSMENT_SECTION_QUALITY)) != 0u;
6967
        if (assessment_need_quality && !assessment_quality_quantized &&
3!
6968
                encoder->output_is_png) {
×
6969
            sixel_helper_set_additional_message(
×
6970
                "sixel_encoder_setopt: encoded quality assessment requires SIXEL output.");
6971
            status = SIXEL_BAD_ARGUMENT;
×
6972
            goto end;
×
6973
        }
6974
        status = sixel_encoder_enable_source_capture(encoder,
3✔
6975
                                                     assessment_need_source_capture);
6976
        if (SIXEL_FAILED(status)) {
3!
6977
            goto end;
×
6978
        }
6979
        status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_STATIC, NULL);
3✔
6980
        if (SIXEL_FAILED(status)) {
3!
6981
            goto end;
×
6982
        }
6983
        if (assessment_need_quantized_capture) {
3!
6984
            status = sixel_encoder_enable_quantized_capture(encoder, 1);
3✔
6985
            if (SIXEL_FAILED(status)) {
3!
6986
                goto end;
6987
            }
6988
        }
6989
        assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
3✔
6990
        spool_required = 0;
3✔
6991
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
6992
            if (encoder->sixel_output_path == NULL) {
×
6993
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
6994
                spool_required = 1;
6995
            } else if (strcmp(encoder->sixel_output_path, "-") == 0) {
×
6996
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
6997
                spool_required = 1;
×
6998
                free(encoder->sixel_output_path);
×
6999
                encoder->sixel_output_path = NULL;
×
7000
            } else if (is_dev_null_path(encoder->sixel_output_path)) {
×
7001
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_PATH;
×
7002
                spool_required = 1;
×
7003
                assessment_forward_path = encoder->sixel_output_path;
×
7004
                encoder->sixel_output_path = NULL;
×
7005
            }
7006
        }
7007
        if (spool_required) {
×
7008
            assessment_temp_capacity = 0u;
×
7009
            assessment_tmpnam_result = NULL;
×
7010
            assessment_temp_path = create_temp_template(encoder->allocator,
×
7011
                                                        &assessment_temp_capacity);
7012
            if (assessment_temp_path == NULL) {
×
7013
                sixel_helper_set_additional_message(
×
7014
                    "sixel_encoder_encode: sixel_allocator_malloc() "
7015
                    "failed for assessment staging path.");
7016
                status = SIXEL_BAD_ALLOCATION;
×
7017
                goto end;
×
7018
            }
7019
            if (sixel_compat_mktemp(assessment_temp_path,
×
7020
                                    assessment_temp_capacity) != 0) {
7021
                /* Fall back to tmpnam() when mktemp variants are unavailable. */
7022
                assessment_tmpnam_result = tmpnam(assessment_temp_path);
×
7023
                if (assessment_tmpnam_result == NULL) {
×
7024
                    sixel_helper_set_additional_message(
×
7025
                        "sixel_encoder_encode: mktemp() failed for assessment staging file.");
7026
                    status = SIXEL_RUNTIME_ERROR;
×
7027
                    goto end;
×
7028
                }
7029
                assessment_temp_capacity = strlen(assessment_temp_path) + 1u;
×
7030
            }
7031
            status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_OUTFILE,
×
7032
                                          assessment_temp_path);
7033
            if (SIXEL_FAILED(status)) {
×
7034
                goto end;
×
7035
            }
7036
            encoder->sixel_output_path = (char *)sixel_allocator_malloc(
×
7037
                encoder->allocator, strlen(assessment_temp_path) + 1);
×
7038
            if (encoder->sixel_output_path == NULL) {
×
7039
                sixel_helper_set_additional_message(
×
7040
                    "sixel_encoder_encode: malloc() failed for assessment staging name.");
7041
                status = SIXEL_BAD_ALLOCATION;
×
7042
                goto end;
×
7043
            }
7044
            (void)sixel_compat_strcpy(encoder->sixel_output_path,
×
7045
                                      strlen(assessment_temp_path) + 1,
×
7046
                                      assessment_temp_path);
7047
        }
7048

7049
    }
7050

7051
    if (encoder->output_is_png) {
498✔
7052
        png_temp_capacity = 0u;
9✔
7053
        png_tmpnam_result = NULL;
9✔
7054
        png_temp_path = create_temp_template(encoder->allocator,
9✔
7055
                                             &png_temp_capacity);
7056
        if (png_temp_path == NULL) {
9!
7057
            sixel_helper_set_additional_message(
×
7058
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
7059
            status = SIXEL_BAD_ALLOCATION;
×
7060
            goto end;
×
7061
        }
7062
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
9!
7063
            /* Fall back to tmpnam() when mktemp variants are unavailable. */
7064
            png_tmpnam_result = tmpnam(png_temp_path);
×
7065
            if (png_tmpnam_result == NULL) {
×
7066
                sixel_helper_set_additional_message(
×
7067
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
7068
                status = SIXEL_RUNTIME_ERROR;
×
7069
                goto end;
×
7070
            }
7071
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
7072
        }
7073
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
9!
7074
            (void)sixel_compat_close(encoder->outfd);
3✔
7075
        }
7076
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
9✔
7077
#if defined(O_EXCL)
7078
        png_open_flags |= O_EXCL;
9✔
7079
#endif
7080
        encoder->outfd = sixel_compat_open(png_temp_path,
9✔
7081
                                           png_open_flags,
7082
                                           S_IRUSR | S_IWUSR);
7083
        if (encoder->outfd < 0) {
9!
7084
            sixel_helper_set_additional_message(
×
7085
                "sixel_encoder_encode: failed to create the PNG target file.");
7086
            status = SIXEL_LIBC_ERROR;
×
7087
            goto end;
×
7088
        }
7089
    }
7090

7091
    if (encoder == NULL) {
1!
7092
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7093
#  pragma GCC diagnostic push
7094
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7095
#endif
7096
        encoder = sixel_encoder_create();
7097
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7098
#  pragma GCC diagnostic pop
7099
#endif
7100
        if (encoder == NULL) {
×
7101
            sixel_helper_set_additional_message(
7102
                "sixel_encoder_encode: sixel_encoder_create() failed.");
7103
            status = SIXEL_BAD_ALLOCATION;
7104
            goto end;
7105
        }
7106
    } else {
7107
        sixel_encoder_ref(encoder);
498✔
7108
    }
7109

7110
    if (encode_allocator == NULL && encoder != NULL) {
498!
7111
        encode_allocator = encoder->allocator;
×
7112
        if (encode_allocator != NULL) {
×
7113
            /* Ensure the allocator stays valid after lazy encoder creation. */
7114
            sixel_allocator_ref(encode_allocator);
×
7115
        }
7116
    }
7117

7118
    if (encoder->assessment_observer != NULL) {
498✔
7119
        sixel_assessment_stage_transition(
3✔
7120
            encoder->assessment_observer,
7121
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
7122
    }
7123
    encoder->last_loader_name[0] = '\0';
498✔
7124
    encoder->last_source_path[0] = '\0';
498✔
7125
    encoder->last_input_bytes = 0u;
498✔
7126

7127
    /* if required color is not set, set the max value */
7128
    if (encoder->reqcolors == (-1)) {
498✔
7129
        encoder->reqcolors = SIXEL_PALETTE_MAX;
477✔
7130
    }
7131

7132
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
498!
7133
        sixel_frame_unref(encoder->capture_source_frame);
×
7134
        encoder->capture_source_frame = NULL;
×
7135
    }
7136

7137
    /* if required color is less then 2, set the min value */
7138
    if (encoder->reqcolors < 2) {
498✔
7139
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
7140
    }
7141

7142
    /* if color space option is not set, choose RGB color space */
7143
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
498✔
7144
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
477✔
7145
    }
7146

7147
    /* if color option is not default value, prohibit to read
7148
       the file as a paletted image */
7149
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
498✔
7150
        fuse_palette = 0;
96✔
7151
    }
7152

7153
    /* if scaling options are set, prohibit to read the file as
7154
       a paletted image */
7155
    if (encoder->percentwidth > 0 ||
498✔
7156
        encoder->percentheight > 0 ||
486✔
7157
        encoder->pixelwidth > 0 ||
480✔
7158
        encoder->pixelheight > 0) {
405✔
7159
        fuse_palette = 0;
123✔
7160
    }
7161

7162
reload:
375✔
7163

7164
    sixel_helper_set_loader_trace(encoder->verbose);
498✔
7165
    sixel_helper_set_thumbnail_size_hint(
498✔
7166
        sixel_encoder_thumbnail_hint(encoder));
7167

7168
    status = sixel_loader_new(&loader, encoder->allocator);
498✔
7169
    if (SIXEL_FAILED(status)) {
498!
7170
        goto load_end;
×
7171
    }
7172

7173
    status = sixel_loader_setopt(loader,
996✔
7174
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
7175
                                 &encoder->fstatic);
498✔
7176
    if (SIXEL_FAILED(status)) {
498!
7177
        goto load_end;
×
7178
    }
7179

7180
    status = sixel_loader_setopt(loader,
498✔
7181
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
7182
                                 &fuse_palette);
7183
    if (SIXEL_FAILED(status)) {
498!
7184
        goto load_end;
×
7185
    }
7186

7187
    status = sixel_loader_setopt(loader,
996✔
7188
                                 SIXEL_LOADER_OPTION_REQCOLORS,
7189
                                 &encoder->reqcolors);
498✔
7190
    if (SIXEL_FAILED(status)) {
498!
7191
        goto load_end;
×
7192
    }
7193

7194
    status = sixel_loader_setopt(loader,
996✔
7195
                                 SIXEL_LOADER_OPTION_BGCOLOR,
7196
                                 encoder->bgcolor);
498✔
7197
    if (SIXEL_FAILED(status)) {
498!
7198
        goto load_end;
×
7199
    }
7200

7201
    status = sixel_loader_setopt(loader,
996✔
7202
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
7203
                                 &encoder->loop_mode);
498✔
7204
    if (SIXEL_FAILED(status)) {
498!
7205
        goto load_end;
×
7206
    }
7207

7208
    status = sixel_loader_setopt(loader,
996✔
7209
                                 SIXEL_LOADER_OPTION_INSECURE,
7210
                                 &encoder->finsecure);
498✔
7211
    if (SIXEL_FAILED(status)) {
498!
7212
        goto load_end;
×
7213
    }
7214

7215
    status = sixel_loader_setopt(loader,
996✔
7216
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
7217
                                 encoder->cancel_flag);
498✔
7218
    if (SIXEL_FAILED(status)) {
498!
7219
        goto load_end;
×
7220
    }
7221

7222
    status = sixel_loader_setopt(loader,
996✔
7223
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
7224
                                 encoder->loader_order);
498✔
7225
    if (SIXEL_FAILED(status)) {
498!
7226
        goto load_end;
×
7227
    }
7228

7229
    status = sixel_loader_setopt(loader,
498✔
7230
                                 SIXEL_LOADER_OPTION_CONTEXT,
7231
                                 encoder);
7232
    if (SIXEL_FAILED(status)) {
498!
7233
        goto load_end;
×
7234
    }
7235

7236
    /*
7237
     * Wire the optional assessment observer into the loader.
7238
     *
7239
     * The observer travels separately from the callback context so mapfile
7240
     * palette probes and other callbacks can keep using arbitrary structs.
7241
     */
7242
    status = sixel_loader_setopt(loader,
996✔
7243
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
7244
                                 encoder->assessment_observer);
498✔
7245
    if (SIXEL_FAILED(status)) {
498!
7246
        goto load_end;
×
7247
    }
7248

7249
    status = sixel_loader_load_file(loader,
498✔
7250
                                    effective_filename,
7251
                                    load_image_callback);
7252
    if (status != SIXEL_OK) {
498✔
7253
        goto load_end;
54✔
7254
    }
7255
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
444✔
7256
    if (sixel_loader_get_last_success_name(loader) != NULL) {
444!
7257
        (void)snprintf(encoder->last_loader_name,
444✔
7258
                       sizeof(encoder->last_loader_name),
7259
                       "%s",
7260
                       sixel_loader_get_last_success_name(loader));
7261
    } else {
7262
        encoder->last_loader_name[0] = '\0';
×
7263
    }
7264
    if (sixel_loader_get_last_source_path(loader) != NULL) {
444✔
7265
        (void)snprintf(encoder->last_source_path,
306✔
7266
                       sizeof(encoder->last_source_path),
7267
                       "%s",
7268
                       sixel_loader_get_last_source_path(loader));
7269
    } else {
7270
        encoder->last_source_path[0] = '\0';
138✔
7271
    }
7272
    if (encoder->assessment_observer != NULL) {
444✔
7273
        sixel_assessment_record_loader(encoder->assessment_observer,
3✔
7274
                                       encoder->last_source_path,
3✔
7275
                                       encoder->last_loader_name,
3✔
7276
                                       encoder->last_input_bytes);
7277
    }
7278

7279
load_end:
441✔
7280
    sixel_loader_unref(loader);
498✔
7281
    loader = NULL;
498✔
7282

7283
    if (status != SIXEL_OK) {
498✔
7284
        goto end;
54✔
7285
    }
7286

7287
    palette_status = sixel_encoder_emit_palette_output(encoder);
444✔
7288
    if (SIXEL_FAILED(palette_status)) {
444!
7289
        status = palette_status;
×
7290
        goto end;
×
7291
    }
7292

7293
    if (encoder->pipe_mode) {
444!
7294
#if HAVE_CLEARERR
7295
        clearerr(stdin);
×
7296
#endif  /* HAVE_FSEEK */
7297
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
7298
            status = sixel_tty_wait_stdin(1000000);
×
7299
            if (SIXEL_FAILED(status)) {
×
7300
                goto end;
×
7301
            }
7302
            if (status != SIXEL_OK) {
×
7303
                break;
7304
            }
7305
        }
7306
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
7307
            goto reload;
×
7308
        }
7309
    }
7310

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

7521
    if (encoder->output_is_png) {
444✔
7522
        png_final_path = encoder->output_png_to_stdout ? "-" : encoder->png_output_path;
9!
7523
        if (! encoder->output_png_to_stdout && png_final_path == NULL) {
9!
7524
            sixel_helper_set_additional_message(
×
7525
                "sixel_encoder_encode: missing PNG output path.");
7526
            status = SIXEL_RUNTIME_ERROR;
×
7527
            goto end;
×
7528
        }
7529
        status = write_png_from_sixel(png_temp_path, png_final_path);
9✔
7530
        if (SIXEL_FAILED(status)) {
9!
7531
            goto end;
×
7532
        }
7533
    }
7534

7535
    if (encoder->clipboard_output_active
444!
7536
            && encoder->clipboard_output_path != NULL) {
×
7537
        unsigned char *clipboard_output_data;
×
7538
        size_t clipboard_output_size;
×
7539

7540
        clipboard_output_data = NULL;
×
7541
        clipboard_output_size = 0u;
×
7542

7543
        if (encoder->outfd
×
7544
                && encoder->outfd != STDOUT_FILENO
1!
7545
                && encoder->outfd != STDERR_FILENO) {
×
7546
            (void)sixel_compat_close(encoder->outfd);
×
7547
            encoder->outfd = STDOUT_FILENO;
×
7548
        }
7549

7550
        clipboard_status = clipboard_read_file(
×
7551
            encoder->clipboard_output_path,
×
7552
            &clipboard_output_data,
7553
            &clipboard_output_size);
7554
        if (SIXEL_SUCCEEDED(clipboard_status)) {
×
7555
            clipboard_status = sixel_clipboard_write(
×
7556
                encoder->clipboard_output_format,
×
7557
                clipboard_output_data,
7558
                clipboard_output_size);
7559
        }
7560
        if (clipboard_output_data != NULL) {
×
7561
            free(clipboard_output_data);
×
7562
        }
7563
        if (SIXEL_FAILED(clipboard_status)) {
×
7564
            status = clipboard_status;
×
7565
            goto end;
×
7566
        }
7567
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
7568
        sixel_allocator_free(encoder->allocator,
×
7569
                             encoder->clipboard_output_path);
×
7570
        encoder->clipboard_output_path = NULL;
×
7571
        encoder->sixel_output_path = NULL;
×
7572
        encoder->clipboard_output_active = 0;
×
7573
        encoder->clipboard_output_format[0] = '\0';
×
7574
    }
1!
7575

7576
    /* the status may not be SIXEL_OK */
7577

7578
end:
444✔
7579
    if (png_temp_path != NULL) {
498✔
7580
        (void)sixel_compat_unlink(png_temp_path);
9✔
7581
    }
7582
    sixel_allocator_free(encoder->allocator, png_temp_path);
498✔
7583
    if (clipboard_input_path != NULL) {
498!
7584
        (void)sixel_compat_unlink(clipboard_input_path);
×
7585
        sixel_allocator_free(encoder->allocator, clipboard_input_path);
×
7586
    }
7587
    if (clipboard_blob != NULL) {
498!
7588
        free(clipboard_blob);
×
7589
    }
7590
    if (encoder->clipboard_output_path != NULL) {
498!
7591
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
7592
        sixel_allocator_free(encoder->allocator,
×
7593
                             encoder->clipboard_output_path);
×
7594
        encoder->clipboard_output_path = NULL;
×
7595
        encoder->sixel_output_path = NULL;
×
7596
        encoder->clipboard_output_active = 0;
×
7597
        encoder->clipboard_output_format[0] = '\0';
×
7598
    }
7599
    sixel_allocator_free(encoder->allocator, encoder->png_output_path);
498✔
7600
    encoder->png_output_path = NULL;
498✔
7601
    if (assessment_forward_stream != NULL) {
498!
7602
        (void) fclose(assessment_forward_stream);
7603
    }
7604
    if (assessment_temp_path != NULL &&
498!
7605
            assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
498!
7606
        (void)sixel_compat_unlink(assessment_temp_path);
×
7607
    }
7608
    sixel_allocator_free(encoder->allocator, assessment_temp_path);
498✔
7609
    sixel_allocator_free(encoder->allocator, assessment_forward_path);
498✔
7610
    if (assessment_json_owned && assessment_json_file != NULL) {
498!
7611
        (void) fclose(assessment_json_file);
3✔
7612
    }
7613
    if (assessment_target_frame != NULL) {
498!
7614
        sixel_frame_unref(assessment_target_frame);
×
7615
    }
7616
    if (assessment_expanded_frame != NULL) {
498!
7617
        sixel_frame_unref(assessment_expanded_frame);
×
7618
    }
7619
    if (assessment_source_frame != NULL) {
498✔
7620
        sixel_frame_unref(assessment_source_frame);
3✔
7621
    }
7622
    if (encoder->assessment_observer != NULL) {
498✔
7623
        sixel_assessment_unref(encoder->assessment_observer);
3✔
7624
        encoder->assessment_observer = NULL;
3✔
7625
    }
7626
    if (assessment_allocator != NULL) {
498✔
7627
        sixel_allocator_unref(assessment_allocator);
3✔
7628
    }
7629

7630
    if (encoder != NULL) {
498!
7631
        encoder->logger = NULL;
498✔
7632
        encoder->parallel_job_id = -1;
498✔
7633
    }
7634
    if (logger_prepared) {
498!
7635
        sixel_logger_close(&logger);
×
7636
    }
7637

7638
    sixel_encoder_unref(encoder);
498✔
7639

7640
    if (encode_allocator != NULL) {
498!
7641
        /*
7642
         * Release the retained allocator reference *after* dropping the
7643
         * encoder reference so that a lazily created encoder can run its
7644
         * destructor while the allocator is still alive.  This ensures that
7645
         * cleanup routines never dereference a freed allocator instance.
7646
         */
7647
        sixel_allocator_unref(encode_allocator);
498✔
7648
        encode_allocator = NULL;
498✔
7649
    }
7650

7651
    return status;
498✔
7652
}
7653

7654

7655
/* encode specified pixel data to SIXEL format
7656
 * output to encoder->outfd */
7657
SIXELAPI SIXELSTATUS
7658
sixel_encoder_encode_bytes(
3✔
7659
    sixel_encoder_t     /* in */    *encoder,
7660
    unsigned char       /* in */    *bytes,
7661
    int                 /* in */    width,
7662
    int                 /* in */    height,
7663
    int                 /* in */    pixelformat,
7664
    unsigned char       /* in */    *palette,
7665
    int                 /* in */    ncolors)
7666
{
7667
    SIXELSTATUS status = SIXEL_FALSE;
3✔
7668
    sixel_frame_t *frame = NULL;
3✔
7669

7670
    if (encoder == NULL || bytes == NULL) {
3!
7671
        status = SIXEL_BAD_ARGUMENT;
×
7672
        goto end;
×
7673
    }
7674

7675
    status = sixel_frame_new(&frame, encoder->allocator);
3✔
7676
    if (SIXEL_FAILED(status)) {
3!
7677
        goto end;
×
7678
    }
7679

7680
    status = sixel_frame_init(frame, bytes, width, height,
3✔
7681
                              pixelformat, palette, ncolors);
7682
    if (SIXEL_FAILED(status)) {
3!
7683
        goto end;
×
7684
    }
7685

7686
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
3✔
7687
    if (SIXEL_FAILED(status)) {
3!
7688
        goto end;
×
7689
    }
7690

7691
    status = SIXEL_OK;
7692

7693
end:
3✔
7694
    /* we need to free the frame before exiting, but we can't use the
7695
       sixel_frame_destroy function, because that will also attempt to
7696
       free the pixels and palette, which we don't own */
7697
    if (frame != NULL && encoder->allocator != NULL) {
3!
7698
        sixel_allocator_free(encoder->allocator, frame);
3✔
7699
        sixel_allocator_unref(encoder->allocator);
3✔
7700
    }
7701
    return status;
3✔
7702
}
7703

7704

7705
/*
7706
 * Toggle source-frame capture for assessment consumers.
7707
 */
7708
SIXELAPI SIXELSTATUS
7709
sixel_encoder_enable_source_capture(
3✔
7710
    sixel_encoder_t *encoder,
7711
    int enable)
7712
{
7713
    if (encoder == NULL) {
3!
7714
        sixel_helper_set_additional_message(
×
7715
            "sixel_encoder_enable_source_capture: encoder is null.");
7716
        return SIXEL_BAD_ARGUMENT;
×
7717
    }
7718

7719
    encoder->capture_source = enable ? 1 : 0;
3✔
7720
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
3!
7721
        sixel_frame_unref(encoder->capture_source_frame);
×
7722
        encoder->capture_source_frame = NULL;
×
7723
    }
7724

7725
    return SIXEL_OK;
7726
}
7727

7728

7729
/*
7730
 * Enable or disable the quantized-frame capture facility.
7731
 *
7732
 *     capture on --> encoder keeps the latest palette-quantized frame.
7733
 *     capture off --> encoder forgets previously stored frames.
7734
 */
7735
SIXELAPI SIXELSTATUS
7736
sixel_encoder_enable_quantized_capture(
3✔
7737
    sixel_encoder_t *encoder,
7738
    int enable)
7739
{
7740
    if (encoder == NULL) {
2!
7741
        sixel_helper_set_additional_message(
×
7742
            "sixel_encoder_enable_quantized_capture: encoder is null.");
7743
        return SIXEL_BAD_ARGUMENT;
×
7744
    }
7745

7746
    encoder->capture_quantized = enable ? 1 : 0;
3✔
7747
    if (!encoder->capture_quantized) {
3!
7748
        encoder->capture_valid = 0;
×
7749
    }
7750

7751
    return SIXEL_OK;
7752
}
7753

7754

7755
/*
7756
 * Materialize the captured quantized frame as a heap-allocated
7757
 * sixel_frame_t instance for assessment consumers.
7758
 */
7759
SIXELAPI SIXELSTATUS
7760
sixel_encoder_copy_quantized_frame(
×
7761
    sixel_encoder_t   *encoder,
7762
    sixel_allocator_t *allocator,
7763
    sixel_frame_t     **ppframe)
7764
{
7765
    SIXELSTATUS status = SIXEL_FALSE;
×
7766
    sixel_frame_t *frame;
×
7767
    unsigned char *pixels;
×
7768
    unsigned char *palette;
×
7769
    size_t palette_bytes;
×
7770

7771
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
7772
        sixel_helper_set_additional_message(
×
7773
            "sixel_encoder_copy_quantized_frame: invalid argument.");
7774
        return SIXEL_BAD_ARGUMENT;
×
7775
    }
7776

7777
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
7778
        sixel_helper_set_additional_message(
×
7779
            "sixel_encoder_copy_quantized_frame: no frame captured.");
7780
        return SIXEL_RUNTIME_ERROR;
×
7781
    }
7782

7783
    *ppframe = NULL;
×
7784
    frame = NULL;
×
7785
    pixels = NULL;
×
7786
    palette = NULL;
×
7787

7788
    status = sixel_frame_new(&frame, allocator);
×
7789
    if (SIXEL_FAILED(status)) {
×
7790
        return status;
7791
    }
7792

7793
    if (encoder->capture_pixel_bytes > 0) {
×
7794
        pixels = (unsigned char *)sixel_allocator_malloc(
×
7795
            allocator, encoder->capture_pixel_bytes);
7796
        if (pixels == NULL) {
×
7797
            sixel_helper_set_additional_message(
×
7798
                "sixel_encoder_copy_quantized_frame: "
7799
                "sixel_allocator_malloc() failed.");
7800
            status = SIXEL_BAD_ALLOCATION;
×
7801
            goto cleanup;
×
7802
        }
7803
        memcpy(pixels,
×
7804
               encoder->capture_pixels,
×
7805
               encoder->capture_pixel_bytes);
7806
    }
7807

7808
    palette_bytes = encoder->capture_palette_size;
×
7809
    if (palette_bytes > 0) {
×
7810
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
7811
                                                          palette_bytes);
7812
        if (palette == NULL) {
×
7813
            sixel_helper_set_additional_message(
×
7814
                "sixel_encoder_copy_quantized_frame: "
7815
                "sixel_allocator_malloc() failed.");
7816
            status = SIXEL_BAD_ALLOCATION;
×
7817
            goto cleanup;
×
7818
        }
7819
        memcpy(palette,
×
7820
               encoder->capture_palette,
×
7821
               palette_bytes);
7822
    }
7823

7824
    status = sixel_frame_init(frame,
×
7825
                              pixels,
7826
                              encoder->capture_width,
7827
                              encoder->capture_height,
7828
                              encoder->capture_pixelformat,
7829
                              palette,
7830
                              encoder->capture_ncolors);
7831
    if (SIXEL_FAILED(status)) {
×
7832
        goto cleanup;
×
7833
    }
7834

7835
    pixels = NULL;
×
7836
    palette = NULL;
×
7837
    /*
7838
     * Capture colorspace must be preserved for assessment consumers.
7839
     * Keep access encapsulated via the public setter to avoid
7840
     * depending on frame internals.
7841
     */
7842
    sixel_frame_set_colorspace(frame, encoder->capture_colorspace);
×
7843
    *ppframe = frame;
×
7844
    return SIXEL_OK;
×
7845

7846
cleanup:
×
7847
    if (palette != NULL) {
×
7848
        sixel_allocator_free(allocator, palette);
×
7849
    }
7850
    if (pixels != NULL) {
×
7851
        sixel_allocator_free(allocator, pixels);
×
7852
    }
7853
    if (frame != NULL) {
×
7854
        sixel_frame_unref(frame);
×
7855
    }
7856
    return status;
7857
}
7858

7859

7860
/*
7861
 * Emit the captured palette in the requested format.
7862
 *
7863
 *   palette_output == NULL  -> skip
7864
 *   palette_output != NULL  -> materialize captured palette
7865
 */
7866
static SIXELSTATUS
7867
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
444✔
7868
{
7869
    SIXELSTATUS status;
444✔
7870
    sixel_frame_t *frame;
444✔
7871
    unsigned char const *palette;
444✔
7872
    int exported_colors;
444✔
7873
    FILE *stream;
444✔
7874
    int close_stream;
444✔
7875
    char const *path;
444✔
7876
    sixel_palette_format_t format_hint;
444✔
7877
    sixel_palette_format_t format_ext;
444✔
7878
    sixel_palette_format_t format_final;
444✔
7879
    char const *mode;
444✔
7880

7881
    status = SIXEL_OK;
444✔
7882
    frame = NULL;
444✔
7883
    palette = NULL;
444✔
7884
    exported_colors = 0;
444✔
7885
    stream = NULL;
444✔
7886
    close_stream = 0;
444✔
7887
    path = NULL;
444✔
7888
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
444✔
7889
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
444✔
7890
    format_final = SIXEL_PALETTE_FORMAT_NONE;
444✔
7891
    mode = "wb";
444✔
7892

7893
    if (encoder == NULL || encoder->palette_output == NULL) {
444!
7894
        return SIXEL_OK;
7895
    }
7896

7897
    status = sixel_encoder_copy_quantized_frame(encoder,
×
7898
                                                encoder->allocator,
7899
                                                &frame);
7900
    if (SIXEL_FAILED(status)) {
×
7901
        return status;
7902
    }
7903

7904
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
7905
    exported_colors = sixel_frame_get_ncolors(frame);
×
7906
    if (palette == NULL || exported_colors <= 0) {
×
7907
        sixel_helper_set_additional_message(
×
7908
            "sixel_encoder_emit_palette_output: palette unavailable.");
7909
        status = SIXEL_BAD_INPUT;
×
7910
        goto cleanup;
×
7911
    }
7912
    if (exported_colors > 256) {
×
7913
        exported_colors = 256;
7914
    }
7915

7916
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
7917
    if (path == NULL || *path == '\0') {
×
7918
        sixel_helper_set_additional_message(
×
7919
            "sixel_encoder_emit_palette_output: invalid path.");
7920
        status = SIXEL_BAD_ARGUMENT;
×
7921
        goto cleanup;
×
7922
    }
7923

7924
    format_ext = sixel_palette_format_from_extension(path);
×
7925
    format_final = format_hint;
×
7926
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
7927
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
7928
            if (strcmp(path, "-") == 0) {
×
7929
                sixel_helper_set_additional_message(
×
7930
                    "sixel_encoder_emit_palette_output: "
7931
                    "format required for '-'.");
7932
                status = SIXEL_BAD_ARGUMENT;
×
7933
                goto cleanup;
×
7934
            }
7935
            sixel_helper_set_additional_message(
×
7936
                "sixel_encoder_emit_palette_output: "
7937
                "unknown palette file extension.");
7938
            status = SIXEL_BAD_ARGUMENT;
×
7939
            goto cleanup;
×
7940
        }
7941
        format_final = format_ext;
7942
    }
7943
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
7944
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
7945
    }
7946

7947
    if (strcmp(path, "-") == 0) {
×
7948
        stream = stdout;
×
7949
    } else {
7950
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
7951
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
×
7952
            mode = "w";
7953
        } else {
7954
            mode = "wb";
×
7955
        }
7956
        stream = fopen(path, mode);
×
7957
        if (stream == NULL) {
×
7958
            sixel_helper_set_additional_message(
×
7959
                "sixel_encoder_emit_palette_output: failed to open file.");
7960
            status = SIXEL_LIBC_ERROR;
×
7961
            goto cleanup;
×
7962
        }
7963
        close_stream = 1;
7964
    }
7965

7966
    switch (format_final) {
×
7967
    case SIXEL_PALETTE_FORMAT_ACT:
×
7968
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
7969
        if (SIXEL_FAILED(status)) {
×
7970
            sixel_helper_set_additional_message(
×
7971
                "sixel_encoder_emit_palette_output: failed to write ACT.");
7972
        }
7973
        break;
7974
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
×
7975
        status = sixel_palette_write_pal_jasc(stream,
×
7976
                                              palette,
7977
                                              exported_colors);
7978
        if (SIXEL_FAILED(status)) {
×
7979
            sixel_helper_set_additional_message(
×
7980
                "sixel_encoder_emit_palette_output: failed to write JASC.");
7981
        }
7982
        break;
7983
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
×
7984
        status = sixel_palette_write_pal_riff(stream,
×
7985
                                              palette,
7986
                                              exported_colors);
7987
        if (SIXEL_FAILED(status)) {
×
7988
            sixel_helper_set_additional_message(
×
7989
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
7990
        }
7991
        break;
7992
    case SIXEL_PALETTE_FORMAT_GPL:
×
7993
        status = sixel_palette_write_gpl(stream,
×
7994
                                         palette,
7995
                                         exported_colors);
7996
        if (SIXEL_FAILED(status)) {
×
7997
            sixel_helper_set_additional_message(
×
7998
                "sixel_encoder_emit_palette_output: failed to write GPL.");
7999
        }
8000
        break;
8001
    default:
×
8002
        sixel_helper_set_additional_message(
×
8003
            "sixel_encoder_emit_palette_output: unsupported format.");
8004
        status = SIXEL_BAD_ARGUMENT;
×
8005
        break;
×
8006
    }
8007
    if (SIXEL_FAILED(status)) {
×
8008
        goto cleanup;
×
8009
    }
8010

8011
    if (close_stream) {
×
8012
        if (fclose(stream) != 0) {
×
8013
            sixel_helper_set_additional_message(
×
8014
                "sixel_encoder_emit_palette_output: fclose() failed.");
8015
            status = SIXEL_LIBC_ERROR;
×
8016
            stream = NULL;
×
8017
            goto cleanup;
×
8018
        }
8019
        stream = NULL;
8020
    } else {
8021
        if (fflush(stream) != 0) {
×
8022
            sixel_helper_set_additional_message(
×
8023
                "sixel_encoder_emit_palette_output: fflush() failed.");
8024
            status = SIXEL_LIBC_ERROR;
×
8025
            goto cleanup;
×
8026
        }
8027
    }
8028

8029
cleanup:
×
8030
    if (close_stream && stream != NULL) {
×
8031
        (void) fclose(stream);
×
8032
    }
8033
    if (frame != NULL) {
×
8034
        sixel_frame_unref(frame);
×
8035
    }
8036

8037
    return status;
8038
}
8039

8040

8041
/*
8042
 * Share the captured source frame with assessment consumers.
8043
 */
8044
SIXELAPI SIXELSTATUS
8045
sixel_encoder_copy_source_frame(
3✔
8046
    sixel_encoder_t *encoder,
8047
    sixel_frame_t  **ppframe)
8048
{
8049
    if (encoder == NULL || ppframe == NULL) {
3!
8050
        sixel_helper_set_additional_message(
×
8051
            "sixel_encoder_copy_source_frame: invalid argument.");
8052
        return SIXEL_BAD_ARGUMENT;
×
8053
    }
8054

8055
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
3!
8056
        sixel_helper_set_additional_message(
×
8057
            "sixel_encoder_copy_source_frame: no frame captured.");
8058
        return SIXEL_RUNTIME_ERROR;
×
8059
    }
8060

8061
    sixel_frame_ref(encoder->capture_source_frame);
3✔
8062
    *ppframe = encoder->capture_source_frame;
3✔
8063

8064
    return SIXEL_OK;
3✔
8065
}
8066

8067

8068
#if HAVE_TESTS
8069
static int
8070
test1(void)
×
8071
{
8072
    int nret = EXIT_FAILURE;
×
8073
    sixel_encoder_t *encoder = NULL;
×
8074

8075
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8076
#  pragma GCC diagnostic push
8077
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8078
#endif
8079
    encoder = sixel_encoder_create();
×
8080
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8081
#  pragma GCC diagnostic pop
8082
#endif
8083
    if (encoder == NULL) {
×
8084
        goto error;
×
8085
    }
8086
    sixel_encoder_ref(encoder);
×
8087
    sixel_encoder_unref(encoder);
×
8088
    nret = EXIT_SUCCESS;
×
8089

8090
error:
×
8091
    sixel_encoder_unref(encoder);
×
8092
    return nret;
×
8093
}
8094

8095

8096
static int
8097
test2(void)
×
8098
{
8099
    int nret = EXIT_FAILURE;
×
8100
    SIXELSTATUS status;
×
8101
    sixel_encoder_t *encoder = NULL;
×
8102
    sixel_frame_t *frame = NULL;
×
8103
    unsigned char *buffer;
×
8104
    int height = 0;
×
8105
    int is_animation = 0;
×
8106

8107
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8108
#  pragma GCC diagnostic push
8109
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8110
#endif
8111
    encoder = sixel_encoder_create();
×
8112
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8113
#  pragma GCC diagnostic pop
8114
#endif
8115
    if (encoder == NULL) {
×
8116
        goto error;
×
8117
    }
8118

8119
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8120
#  pragma GCC diagnostic push
8121
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8122
#endif
8123
    frame = sixel_frame_create();
×
8124
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8125
#  pragma GCC diagnostic pop
8126
#endif
8127
    if (encoder == NULL) {
×
8128
        goto error;
8129
    }
8130

8131
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
8132
    if (buffer == NULL) {
×
8133
        goto error;
×
8134
    }
8135
    status = sixel_frame_init(frame, buffer, 1, 1,
×
8136
                              SIXEL_PIXELFORMAT_RGB888,
8137
                              NULL, 0);
8138
    if (SIXEL_FAILED(status)) {
×
8139
        goto error;
×
8140
    }
8141

8142
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
8143
        is_animation = 1;
8144
    }
8145

8146
    height = sixel_frame_get_height(frame);
×
8147

8148
    status = sixel_tty_scroll(sixel_write_callback,
×
8149
                              &encoder->outfd,
×
8150
                              encoder->outfd,
8151
                              height,
8152
                              is_animation);
8153
    if (SIXEL_FAILED(status)) {
×
8154
        goto error;
×
8155
    }
8156

8157
    nret = EXIT_SUCCESS;
8158

8159
error:
×
8160
    sixel_encoder_unref(encoder);
×
8161
    sixel_frame_unref(frame);
×
8162
    return nret;
×
8163
}
8164

8165

8166
static int
8167
test3(void)
×
8168
{
8169
    int nret = EXIT_FAILURE;
×
8170
    int result;
×
8171

8172
    result = sixel_tty_wait_stdin(1000);
×
8173
    if (result != 0) {
×
8174
        goto error;
×
8175
    }
8176

8177
    nret = EXIT_SUCCESS;
8178

8179
error:
×
8180
    return nret;
×
8181
}
8182

8183

8184
static int
8185
test4(void)
×
8186
{
8187
    int nret = EXIT_FAILURE;
×
8188
    sixel_encoder_t *encoder = NULL;
×
8189
    SIXELSTATUS status;
×
8190

8191
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8192
# pragma GCC diagnostic push
8193
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8194
#endif
8195
    encoder = sixel_encoder_create();
×
8196
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8197
# pragma GCC diagnostic pop
8198
#endif
8199
    if (encoder == NULL) {
×
8200
        goto error;
×
8201
    }
8202

8203
    status = sixel_encoder_setopt(encoder,
×
8204
                                  SIXEL_OPTFLAG_LOOPMODE,
8205
                                  "force");
8206
    if (SIXEL_FAILED(status)) {
×
8207
        goto error;
×
8208
    }
8209

8210
    status = sixel_encoder_setopt(encoder,
×
8211
                                  SIXEL_OPTFLAG_PIPE_MODE,
8212
                                  "force");
8213
    if (SIXEL_FAILED(status)) {
×
8214
        goto error;
×
8215
    }
8216

8217
    nret = EXIT_SUCCESS;
8218

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

8224

8225
static int
8226
test5(void)
×
8227
{
8228
    int nret = EXIT_FAILURE;
×
8229
    sixel_encoder_t *encoder = NULL;
×
8230
    sixel_allocator_t *allocator = NULL;
×
8231
    SIXELSTATUS status;
×
8232

8233
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
8234
    if (SIXEL_FAILED(status)) {
×
8235
        goto error;
×
8236
    }
8237

8238
    status = sixel_encoder_new(&encoder, allocator);
×
8239
    if (SIXEL_FAILED(status)) {
×
8240
        goto error;
×
8241
    }
8242

8243
    sixel_encoder_ref(encoder);
×
8244
    sixel_encoder_unref(encoder);
×
8245
    nret = EXIT_SUCCESS;
×
8246

8247
error:
×
8248
    sixel_encoder_unref(encoder);
×
8249
    return nret;
×
8250
}
8251

8252

8253
SIXELAPI int
8254
sixel_encoder_tests_main(void)
×
8255
{
8256
    int nret = EXIT_FAILURE;
×
8257
    size_t i;
×
8258
    typedef int (* testcase)(void);
×
8259

8260
    static testcase const testcases[] = {
×
8261
        test1,
8262
        test2,
8263
        test3,
8264
        test4,
8265
        test5
8266
    };
8267

8268
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
8269
        nret = testcases[i]();
×
8270
        if (nret != EXIT_SUCCESS) {
×
8271
            goto error;
×
8272
        }
8273
    }
8274

8275
    nret = EXIT_SUCCESS;
8276

8277
error:
×
8278
    return nret;
×
8279
}
8280
#endif  /* HAVE_TESTS */
8281

8282

8283
/* emacs Local Variables:      */
8284
/* emacs mode: c               */
8285
/* emacs tab-width: 4          */
8286
/* emacs indent-tabs-mode: nil */
8287
/* emacs c-basic-offset: 4     */
8288
/* emacs End:                  */
8289
/* vim: set expandtab ts=4 : */
8290
/* 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