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

saitoha / libsixel / 19863239119

02 Dec 2025 03:04PM UTC coverage: 40.992% (-1.6%) from 42.6%
19863239119

push

github

saitoha
fix: drcs mapping spec v3

10013 of 36855 branches covered (27.17%)

1 of 41 new or added lines in 1 file covered. (2.44%)

271 existing lines in 18 files now uncovered.

13147 of 32072 relevant lines covered (40.99%)

895662.42 hits per line

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

49.53
/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 "tty.h"
114
#include "encoder.h"
115
#include "output.h"
116
#include "logger.h"
117
#include "options.h"
118
#include "dither.h"
119
#include "rgblookup.h"
120
#include "clipboard.h"
121
#include "compat_stub.h"
122
#include "sixel_threads_config.h"
123

124
#define SIXEL_ENCODER_PRECISION_ENVVAR "SIXEL_FLOAT32_DITHER"
125

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

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

150
#if defined(_WIN32)
151

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

160
# if defined(CLOCKS_PER_SEC)
161
#  undef CLOCKS_PER_SEC
162
# endif
163
# define CLOCKS_PER_SEC 1000
164

165
# if !defined(HAVE_NANOSLEEP)
166
# define HAVE_NANOSLEEP_WIN 1
167
static int
168
nanosleep_win(
169
    struct timespec const *req,
170
    struct timespec *rem)
171
{
172
    LONGLONG nanoseconds;
173
    LARGE_INTEGER dueTime;
174
    HANDLE timer;
175

176
    if (req == NULL || req->tv_sec < 0 || req->tv_nsec < 0 ||
177
        req->tv_nsec >= 1000000000L) {
178
        errno = EINVAL;
179
        return (-1);
180
    }
181

182
    /* Convert to 100-nanosecond intervals (Windows FILETIME units) */
183
    nanoseconds = req->tv_sec * 1000000000LL + req->tv_nsec;
184
    dueTime.QuadPart = -(nanoseconds / 100); /* Negative for relative time */
185

186
    timer = CreateWaitableTimer(NULL, TRUE, NULL);
187
    if (timer == NULL) {
188
        errno = EFAULT;  /* Approximate error */
189
        return (-1);
190
    }
191

192
    if (! SetWaitableTimer(timer, &dueTime, 0, NULL, NULL, FALSE)) {
193
        (void) CloseHandle(timer);
194
        errno = EFAULT;
195
        return (-1);
196
    }
197

198
    (void) WaitForSingleObject(timer, INFINITE);
199
    (void) CloseHandle(timer);
200

201
    /* No interruption handling, so rem is unchanged */
202
    if (rem != NULL) {
203
        rem->tv_sec = 0;
204
        rem->tv_nsec = 0;
205
    }
206

207
    return (0);
208
}
209
# endif  /* HAVE_NANOSLEEP */
210

211
# if !defined(HAVE_CLOCK)
212
# define HAVE_CLOCK_WIN 1
213
static sixel_clock_t
214
clock_win(void)
215
{
216
    FILETIME ct, et, kt, ut;
217
    ULARGE_INTEGER u, k;
218

219
    if (! GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) {
220
        return (sixel_clock_t)(-1);
221
    }
222
    u.LowPart = ut.dwLowDateTime; u.HighPart = ut.dwHighDateTime;
223
    k.LowPart = kt.dwLowDateTime; k.HighPart = kt.dwHighDateTime;
224
    /* 100ns -> ms */
225
    return (sixel_clock_t)((u.QuadPart + k.QuadPart) / 10000ULL);
226
}
227
# endif  /* HAVE_CLOCK */
228

229
#endif /* _WIN32 */
230

231

232
static sixel_option_choice_t const g_option_choices_builtin_palette[] = {
233
    { "xterm16", SIXEL_BUILTIN_XTERM16 },
234
    { "xterm256", SIXEL_BUILTIN_XTERM256 },
235
    { "vt340mono", SIXEL_BUILTIN_VT340_MONO },
236
    { "vt340color", SIXEL_BUILTIN_VT340_COLOR },
237
    { "gray1", SIXEL_BUILTIN_G1 },
238
    { "gray2", SIXEL_BUILTIN_G2 },
239
    { "gray4", SIXEL_BUILTIN_G4 },
240
    { "gray8", SIXEL_BUILTIN_G8 }
241
};
242

243
static sixel_option_choice_t const g_option_choices_diffusion[] = {
244
    { "auto", SIXEL_DIFFUSE_AUTO },
245
    { "none", SIXEL_DIFFUSE_NONE },
246
    { "fs", SIXEL_DIFFUSE_FS },
247
    { "atkinson", SIXEL_DIFFUSE_ATKINSON },
248
    { "jajuni", SIXEL_DIFFUSE_JAJUNI },
249
    { "stucki", SIXEL_DIFFUSE_STUCKI },
250
    { "burkes", SIXEL_DIFFUSE_BURKES },
251
    { "sierra1", SIXEL_DIFFUSE_SIERRA1 },
252
    { "sierra2", SIXEL_DIFFUSE_SIERRA2 },
253
    { "sierra3", SIXEL_DIFFUSE_SIERRA3 },
254
    { "a_dither", SIXEL_DIFFUSE_A_DITHER },
255
    { "x_dither", SIXEL_DIFFUSE_X_DITHER },
256
    { "lso2", SIXEL_DIFFUSE_LSO2 },
257
};
258

259
static sixel_option_choice_t const g_option_choices_diffusion_scan[] = {
260
    { "auto", SIXEL_SCAN_AUTO },
261
    { "serpentine", SIXEL_SCAN_SERPENTINE },
262
    { "raster", SIXEL_SCAN_RASTER }
263
};
264

265
static sixel_option_choice_t const g_option_choices_diffusion_carry[] = {
266
    { "auto", SIXEL_CARRY_AUTO },
267
    { "direct", SIXEL_CARRY_DISABLE },
268
    { "carry", SIXEL_CARRY_ENABLE }
269
};
270

271
static sixel_option_choice_t const g_option_choices_find_largest[] = {
272
    { "auto", SIXEL_LARGE_AUTO },
273
    { "norm", SIXEL_LARGE_NORM },
274
    { "lum", SIXEL_LARGE_LUM },
275
    { "pca", SIXEL_LARGE_PCA }
276
};
277

278
static sixel_option_choice_t const g_option_choices_select_color[] = {
279
    { "auto", SIXEL_REP_AUTO },
280
    { "center", SIXEL_REP_CENTER_BOX },
281
    { "average", SIXEL_REP_AVERAGE_COLORS },
282
    { "histogram", SIXEL_REP_AVERAGE_PIXELS },
283
    { "histgram", SIXEL_REP_AVERAGE_PIXELS }
284
};
285

286
static sixel_option_choice_t const g_option_choices_quantize_model[] = {
287
    { "auto", SIXEL_QUANTIZE_MODEL_AUTO },
288
    { "heckbert", SIXEL_QUANTIZE_MODEL_MEDIANCUT },
289
    { "kmeans", SIXEL_QUANTIZE_MODEL_KMEANS }
290
};
291

292
static sixel_option_choice_t const g_option_choices_final_merge[] = {
293
    { "auto", SIXEL_FINAL_MERGE_AUTO },
294
    { "none", SIXEL_FINAL_MERGE_NONE },
295
    { "ward", SIXEL_FINAL_MERGE_WARD }
296
};
297

298
static sixel_option_choice_t const g_option_choices_resampling[] = {
299
    { "nearest", SIXEL_RES_NEAREST },
300
    { "gaussian", SIXEL_RES_GAUSSIAN },
301
    { "hanning", SIXEL_RES_HANNING },
302
    { "hamming", SIXEL_RES_HAMMING },
303
    { "bilinear", SIXEL_RES_BILINEAR },
304
    { "welsh", SIXEL_RES_WELSH },
305
    { "bicubic", SIXEL_RES_BICUBIC },
306
    { "lanczos2", SIXEL_RES_LANCZOS2 },
307
    { "lanczos3", SIXEL_RES_LANCZOS3 },
308
    { "lanczos4", SIXEL_RES_LANCZOS4 }
309
};
310

311
static sixel_option_choice_t const g_option_choices_quality[] = {
312
    { "auto", SIXEL_QUALITY_AUTO },
313
    { "high", SIXEL_QUALITY_HIGH },
314
    { "low", SIXEL_QUALITY_LOW },
315
    { "full", SIXEL_QUALITY_FULL }
316
};
317

318
static sixel_option_choice_t const g_option_choices_loopmode[] = {
319
    { "auto", SIXEL_LOOP_AUTO },
320
    { "force", SIXEL_LOOP_FORCE },
321
    { "disable", SIXEL_LOOP_DISABLE }
322
};
323

324
static sixel_option_choice_t const g_option_choices_palette_type[] = {
325
    { "auto", SIXEL_PALETTETYPE_AUTO },
326
    { "hls", SIXEL_PALETTETYPE_HLS },
327
    { "rgb", SIXEL_PALETTETYPE_RGB }
328
};
329

330
static sixel_option_choice_t const g_option_choices_encode_policy[] = {
331
    { "auto", SIXEL_ENCODEPOLICY_AUTO },
332
    { "fast", SIXEL_ENCODEPOLICY_FAST },
333
    { "size", SIXEL_ENCODEPOLICY_SIZE }
334
};
335

336
static sixel_option_choice_t const g_option_choices_lut_policy[] = {
337
    { "auto", SIXEL_LUT_POLICY_AUTO },
338
    { "5bit", SIXEL_LUT_POLICY_5BIT },
339
    { "6bit", SIXEL_LUT_POLICY_6BIT },
340
    { "none", SIXEL_LUT_POLICY_NONE },
341
    { "certlut", SIXEL_LUT_POLICY_CERTLUT }
342
};
343

344
static sixel_option_choice_t const g_option_choices_working_colorspace[] = {
345
    { "gamma", SIXEL_COLORSPACE_GAMMA },
346
    { "linear", SIXEL_COLORSPACE_LINEAR },
347
    { "oklab", SIXEL_COLORSPACE_OKLAB },
348
    { "cielab", SIXEL_COLORSPACE_CIELAB },
349
    { "din99d", SIXEL_COLORSPACE_DIN99D }
350
};
351

352
static sixel_option_choice_t const g_option_choices_output_colorspace[] = {
353
    { "gamma", SIXEL_COLORSPACE_GAMMA },
354
    { "linear", SIXEL_COLORSPACE_LINEAR },
355
    { "smpte-c", SIXEL_COLORSPACE_SMPTEC },
356
    { "smptec", SIXEL_COLORSPACE_SMPTEC }
357
};
358

359
static int
360
sixel_encoder_pixelformat_for_colorspace(int colorspace,
721✔
361
                                         int prefer_float32)
362
{
363
    switch (colorspace) {
721!
364
    case SIXEL_COLORSPACE_LINEAR:
365
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
×
366
    case SIXEL_COLORSPACE_OKLAB:
367
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
×
368
    case SIXEL_COLORSPACE_CIELAB:
369
        return SIXEL_PIXELFORMAT_CIELABFLOAT32;
×
370
    case SIXEL_COLORSPACE_DIN99D:
371
        return SIXEL_PIXELFORMAT_DIN99DFLOAT32;
×
372
    default:
540✔
373
        if (prefer_float32) {
721!
374
            return SIXEL_PIXELFORMAT_RGBFLOAT32;
×
375
        }
376
        return SIXEL_PIXELFORMAT_RGB888;
721✔
377
    }
378
}
181✔
379

380
static sixel_option_choice_t const g_option_choices_precision[] = {
381
    { "auto", SIXEL_ENCODER_PRECISION_MODE_AUTO },
382
    { "8bit", SIXEL_ENCODER_PRECISION_MODE_8BIT },
383
    { "float32", SIXEL_ENCODER_PRECISION_MODE_FLOAT32 }
384
};
385

386

387
static char *
388
arg_strdup(
76✔
389
    char const          /* in */ *s,          /* source buffer */
390
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
391
                                                 destination buffer */
392
{
393
    char *p;
394
    size_t len;
395

396
    len = strlen(s);
76✔
397

398
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
76✔
399
    if (p) {
76!
400
        (void)sixel_compat_strcpy(p, len + 1, s);
76✔
401
    }
19✔
402
    return p;
76✔
403
}
404

405
static int
406
sixel_encoder_env_prefers_float32(char const *text)
725✔
407
{
408
    char lowered[8];
409
    size_t i;
410

411
    if (text == NULL || *text == '\0') {
725!
412
        return 0;
725✔
413
    }
414

415
    for (i = 0; i < sizeof(lowered) - 1 && text[i] != '\0'; ++i) {
×
416
        lowered[i] = (char)tolower((unsigned char)text[i]);
×
417
    }
418
    lowered[i] = '\0';
×
419

420
    if (strcmp(lowered, "0") == 0
×
421
        || strcmp(lowered, "off") == 0
×
422
        || strcmp(lowered, "false") == 0
×
423
        || strcmp(lowered, "no") == 0) {
×
424
        return 0;
×
425
    }
426

427
    return 1;
×
428
}
182✔
429

430
static SIXELSTATUS
431
sixel_encoder_apply_precision_override(
×
432
    sixel_encoder_t *encoder,
433
    sixel_encoder_precision_mode_t mode)
434
{
435
    int prefer_float32;
436

437
    prefer_float32 = encoder->prefer_float32;
×
438

439
    if (mode == SIXEL_ENCODER_PRECISION_MODE_AUTO) {
×
440
        return SIXEL_OK;
×
441
    }
442

443
    if (mode == SIXEL_ENCODER_PRECISION_MODE_FLOAT32) {
×
444
        prefer_float32 = 1;
×
445
    } else if (mode == SIXEL_ENCODER_PRECISION_MODE_8BIT) {
×
446
        prefer_float32 = 0;
×
447
    } else {
448
        sixel_helper_set_additional_message(
×
449
            "sixel_encoder_setopt: invalid precision override.");
450
        return SIXEL_BAD_ARGUMENT;
×
451
    }
452

453
    encoder->prefer_float32 = prefer_float32;
×
454

455
    return SIXEL_OK;
×
456
}
457

458

459
/* An clone function of XColorSpec() of xlib */
460
static SIXELSTATUS
461
sixel_parse_x_colorspec(
60✔
462
    unsigned char       /* out */ **bgcolor,     /* destination buffer */
463
    char const          /* in */  *s,            /* source buffer */
464
    sixel_allocator_t   /* in */  *allocator)    /* allocator object for
465
                                                    destination buffer */
466
{
467
    SIXELSTATUS status = SIXEL_FALSE;
60✔
468
    char *p;
469
    unsigned char components[3];
470
    int component_index = 0;
60✔
471
    unsigned long v;
472
    char *endptr;
473
    char *buf = NULL;
60✔
474
    struct color const *pcolor;
475

476
    /* from rgb_lookup.h generated by gpref */
477
    pcolor = lookup_rgb(s, strlen(s));
60✔
478
    if (pcolor) {
60✔
479
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
4✔
480
        if (*bgcolor == NULL) {
4!
481
            sixel_helper_set_additional_message(
×
482
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
483
            status = SIXEL_BAD_ALLOCATION;
×
484
            goto end;
×
485
        }
486
        (*bgcolor)[0] = pcolor->r;
4✔
487
        (*bgcolor)[1] = pcolor->g;
4✔
488
        (*bgcolor)[2] = pcolor->b;
4✔
489
    } else if (s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && s[3] == ':') {
57!
490
        p = buf = arg_strdup(s + 4, allocator);
8✔
491
        if (buf == NULL) {
8!
492
            sixel_helper_set_additional_message(
×
493
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
494
            status = SIXEL_BAD_ALLOCATION;
×
495
            goto end;
×
496
        }
497
        while (*p) {
20!
498
            v = 0;
20✔
499
            for (endptr = p; endptr - p <= 12; ++endptr) {
48!
500
                if (*endptr >= '0' && *endptr <= '9') {
48✔
501
                    v = (v << 4) | (unsigned long)(*endptr - '0');
20✔
502
                } else if (*endptr >= 'a' && *endptr <= 'f') {
33!
503
                    v = (v << 4) | (unsigned long)(*endptr - 'a' + 10);
4✔
504
                } else if (*endptr >= 'A' && *endptr <= 'F') {
25!
505
                    v = (v << 4) | (unsigned long)(*endptr - 'A' + 10);
4✔
506
                } else {
1✔
507
                    break;
5✔
508
                }
509
            }
7✔
510
            if (endptr - p == 0) {
20!
511
                break;
×
512
            }
513
            if (endptr - p > 4) {
20!
514
                break;
×
515
            }
516
            v = v << ((4 - (endptr - p)) * 4) >> 8;
20✔
517
            components[component_index++] = (unsigned char)v;
20✔
518
            p = endptr;
20✔
519
            if (component_index == 3) {
20✔
520
                break;
4✔
521
            }
522
            if (*p == '\0') {
16✔
523
                break;
4✔
524
            }
525
            if (*p != '/') {
12!
526
                break;
×
527
            }
528
            ++p;
12✔
529
        }
530
        if (component_index != 3 || *p != '\0' || *p == '/') {
8!
531
            status = SIXEL_BAD_ARGUMENT;
4✔
532
            goto end;
4✔
533
        }
534
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
4✔
535
        if (*bgcolor == NULL) {
4!
536
            sixel_helper_set_additional_message(
×
537
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
538
            status = SIXEL_BAD_ALLOCATION;
×
539
            goto end;
×
540
        }
541
        (*bgcolor)[0] = components[0];
4✔
542
        (*bgcolor)[1] = components[1];
4✔
543
        (*bgcolor)[2] = components[2];
4✔
544
    } else if (*s == '#') {
49✔
545
        buf = arg_strdup(s + 1, allocator);
36✔
546
        if (buf == NULL) {
36!
547
            sixel_helper_set_additional_message(
×
548
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
549
            status = SIXEL_BAD_ALLOCATION;
×
550
            goto end;
×
551
        }
552
        for (p = endptr = buf; endptr - p <= 12; ++endptr) {
256✔
553
            if (*endptr >= '0' && *endptr <= '9') {
252✔
554
                *endptr -= '0';
132✔
555
            } else if (*endptr >= 'a' && *endptr <= 'f') {
153!
556
                *endptr -= 'a' - 10;
76✔
557
            } else if (*endptr >= 'A' && *endptr <= 'F') {
63✔
558
                *endptr -= 'A' - 10;
12✔
559
            } else if (*endptr == '\0') {
35✔
560
                break;
28✔
561
            } else {
562
                status = SIXEL_BAD_ARGUMENT;
4✔
563
                goto end;
4✔
564
            }
565
        }
55✔
566
        if (endptr - p > 12) {
32✔
567
            status = SIXEL_BAD_ARGUMENT;
4✔
568
            goto end;
4✔
569
        }
570
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
28✔
571
        if (*bgcolor == NULL) {
28!
572
            sixel_helper_set_additional_message(
×
573
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
574
            status = SIXEL_BAD_ALLOCATION;
×
575
            goto end;
×
576
        }
577
        switch (endptr - p) {
28✔
578
        case 3:
9✔
579
            (*bgcolor)[0] = (unsigned char)(p[0] << 4);
12✔
580
            (*bgcolor)[1] = (unsigned char)(p[1] << 4);
12✔
581
            (*bgcolor)[2] = (unsigned char)(p[2] << 4);
12✔
582
            break;
12✔
583
        case 6:
3✔
584
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
4✔
585
            (*bgcolor)[1] = (unsigned char)(p[2] << 4 | p[3]);
4✔
586
            (*bgcolor)[2] = (unsigned char)(p[4] << 4 | p[4]);
4✔
587
            break;
4✔
588
        case 9:
3✔
589
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
4✔
590
            (*bgcolor)[1] = (unsigned char)(p[3] << 4 | p[4]);
4✔
591
            (*bgcolor)[2] = (unsigned char)(p[6] << 4 | p[7]);
4✔
592
            break;
4✔
593
        case 12:
3✔
594
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
4✔
595
            (*bgcolor)[1] = (unsigned char)(p[4] << 4 | p[5]);
4✔
596
            (*bgcolor)[2] = (unsigned char)(p[8] << 4 | p[9]);
4✔
597
            break;
4✔
598
        default:
3✔
599
            status = SIXEL_BAD_ARGUMENT;
4✔
600
            goto end;
4✔
601
        }
602
    } else {
6✔
603
        status = SIXEL_BAD_ARGUMENT;
12✔
604
        goto end;
12✔
605
    }
606

607
    status = SIXEL_OK;
32✔
608

609
end:
45✔
610
    sixel_allocator_free(allocator, buf);
60✔
611

612
    return status;
60✔
613
}
614

615

616
/* generic writer function for passing to sixel_output_new() */
617
static int
618
sixel_write_callback(char *data, int size, void *priv)
7,374✔
619
{
620
    int result;
621

622
    result = (int)sixel_compat_write(*(int *)priv,
9,534✔
623
                                     data,
2,160✔
624
                                     (size_t)size);
2,160✔
625

626
    return result;
7,374✔
627
}
628

629

630
/* the writer function with hex-encoding for passing to sixel_output_new() */
631
static int
632
sixel_hex_write_callback(
96✔
633
    char    /* in */ *data,
634
    int     /* in */ size,
635
    void    /* in */ *priv)
636
{
637
    char hex[SIXEL_OUTPUT_PACKET_SIZE * 2];
638
    int i;
639
    int j;
640
    int result;
641

642
    for (i = j = 0; i < size; ++i, ++j) {
935,032✔
643
        hex[j] = (data[i] >> 4) & 0xf;
934,936✔
644
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
934,936!
645
        hex[++j] = data[i] & 0xf;
934,936✔
646
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
934,936✔
647
    }
233,734✔
648

649
    result = (int)sixel_compat_write(*(int *)priv,
192✔
650
                                     hex,
24✔
651
                                     (size_t)(size * 2));
96✔
652

653
    return result;
96✔
654
}
655

656
typedef struct sixel_encoder_output_probe {
657
    sixel_encoder_t *encoder;
658
    sixel_write_function base_write;
659
    void *base_priv;
660
} sixel_encoder_output_probe_t;
661

662
static int
663
sixel_write_with_probe(char *data, int size, void *priv)
96✔
664
{
665
    sixel_encoder_output_probe_t *probe;
666
    int written;
667
    double started_at;
668
    double finished_at;
669
    double duration;
670

671
    probe = (sixel_encoder_output_probe_t *)priv;
96✔
672
    if (probe == NULL || probe->base_write == NULL) {
96!
673
        return 0;
×
674
    }
675
    started_at = 0.0;
96✔
676
    finished_at = 0.0;
96✔
677
    duration = 0.0;
96✔
678
    if (probe->encoder != NULL &&
96!
679
            probe->encoder->assessment_observer != NULL) {
96!
680
        started_at = sixel_assessment_timer_now();
96✔
681
    }
24✔
682
    written = probe->base_write(data, size, probe->base_priv);
96✔
683
    if (probe->encoder != NULL &&
96!
684
            probe->encoder->assessment_observer != NULL) {
96!
685
        finished_at = sixel_assessment_timer_now();
96✔
686
        duration = finished_at - started_at;
96✔
687
        if (duration < 0.0) {
96!
688
            duration = 0.0;
×
689
        }
690
    }
24✔
691
    if (written > 0 && probe->encoder != NULL &&
96!
692
            probe->encoder->assessment_observer != NULL) {
96!
693
        sixel_assessment_record_output_write(
96✔
694
            probe->encoder->assessment_observer,
96✔
695
            (size_t)written,
24✔
696
            duration);
24✔
697
    }
24✔
698
    return written;
96✔
699
}
24✔
700

701
/*
702
 * Reuse the fn_write probe for raw escape writes so that every
703
 * assessment bucket receives the same accounting.
704
 *
705
 *     encoder        probe wrapper       write(2)
706
 *     +------+    +----------------+    +---------+
707
 *     | data | -> | sixel_write_*  | -> | target  |
708
 *     +------+    +----------------+    +---------+
709
 */
710
static int
711
sixel_encoder_probe_fd_write(sixel_encoder_t *encoder,
×
712
                             char *data,
713
                             int size,
714
                             int fd)
715
{
716
    sixel_encoder_output_probe_t probe;
717
    int written;
718

719
    probe.encoder = encoder;
×
720
    probe.base_write = sixel_write_callback;
×
721
    probe.base_priv = &fd;
×
722
    written = sixel_write_with_probe(data, size, &probe);
×
723

724
    return written;
×
725
}
726

727
static void
728
sixel_encoder_log_stage(sixel_encoder_t *encoder,
8,156✔
729
                        sixel_frame_t *frame,
730
                        char const *worker,
731
                        char const *role,
732
                        char const *event,
733
                        char const *fmt,
734
                        ...)
735
{
736
    sixel_logger_t *logger;
737
    int job_id;
738
    int height;
739
    char message[256];
740
    va_list args;
741

742
    logger = NULL;
8,156✔
743
    if (encoder != NULL) {
8,156!
744
        logger = encoder->logger;
8,156✔
745
    }
2,048✔
746
    if (logger == NULL || logger->file == NULL || !logger->active) {
8,156!
747
        return;
8,156✔
748
    }
749

750
    job_id = -1;
×
751
    height = 0;
×
752
    if (frame != NULL) {
×
753
        job_id = sixel_frame_get_frame_no(frame);
×
754
        height = sixel_frame_get_height(frame);
×
755
    }
756

757
    message[0] = '\0';
×
758
#if defined(__clang__)
759
#pragma clang diagnostic push
760
#pragma clang diagnostic ignored "-Wformat-nonliteral"
761
#elif defined(__GNUC__)
762
#pragma GCC diagnostic push
763
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
764
#endif
765
    va_start(args, fmt);
×
766
    if (fmt != NULL) {
×
767
#if defined(__clang__)
768
#pragma clang diagnostic push
769
#pragma clang diagnostic ignored "-Wformat-nonliteral"
770
#elif defined(__GNUC__)
771
#pragma GCC diagnostic push
772
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
773
#endif
774
        (void)vsnprintf(message, sizeof(message), fmt, args);
×
775
#if defined(__clang__)
776
#pragma clang diagnostic pop
777
#elif defined(__GNUC__)
778
#pragma GCC diagnostic pop
779
#endif
780
    }
781
    va_end(args);
×
782
#if defined(__clang__)
783
#pragma clang diagnostic pop
784
#elif defined(__GNUC__)
785
#pragma GCC diagnostic pop
786
#endif
787

788
    sixel_logger_logf(logger,
×
789
                      role,
790
                      worker,
791
                      event,
792
                      job_id,
793
                      -1,
794
                      0,
795
                      height,
796
                      0,
797
                      height,
798
                      "%s",
799
                      message);
800
}
2,048✔
801

802
static SIXELSTATUS
803
sixel_encoder_ensure_cell_size(sixel_encoder_t *encoder)
×
804
{
805
#if defined(TIOCGWINSZ)
806
    struct winsize ws;
807
    int result;
808
    int fd = 0;
×
809

810
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
811
        return SIXEL_OK;
×
812
    }
813

814
    fd = sixel_compat_open("/dev/tty", O_RDONLY);
×
815
    if (fd >= 0) {
×
816
        result = ioctl(fd, TIOCGWINSZ, &ws);
×
817
        (void)sixel_compat_close(fd);
×
818
    } else {
819
        sixel_helper_set_additional_message(
×
820
            "failed to open /dev/tty");
821
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
822
    }
823
    if (result != 0) {
×
824
        sixel_helper_set_additional_message(
×
825
            "failed to query terminal geometry with ioctl().");
826
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
827
    }
828

829
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
830
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
831
        sixel_helper_set_additional_message(
×
832
            "terminal does not report pixel cell size for drcs option.");
833
        return SIXEL_BAD_ARGUMENT;
×
834
    }
835

836
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
837
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
838
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
839
        sixel_helper_set_additional_message(
×
840
            "terminal cell size reported zero via ioctl().");
841
        return SIXEL_BAD_ARGUMENT;
×
842
    }
843

844
    return SIXEL_OK;
×
845
#else
846
    (void) encoder;
847
    sixel_helper_set_additional_message(
848
        "drcs option is not supported on this platform.");
849
    return SIXEL_NOT_IMPLEMENTED;
850
#endif
851
}
852

853

854
/* returns monochrome dithering context object */
855
static SIXELSTATUS
856
sixel_prepare_monochrome_palette(
16✔
857
    sixel_dither_t  /* out */ **dither,
858
     int            /* in */  finvert)
859
{
860
    SIXELSTATUS status = SIXEL_FALSE;
16✔
861

862
    if (finvert) {
16✔
863
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
4✔
864
    } else {
1✔
865
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
12✔
866
    }
867
    if (*dither == NULL) {
16!
868
        sixel_helper_set_additional_message(
×
869
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
870
        status = SIXEL_RUNTIME_ERROR;
×
871
        goto end;
×
872
    }
873

874
    status = SIXEL_OK;
16✔
875

876
end:
12✔
877
    return status;
16✔
878
}
879

880

881
static SIXELSTATUS
882
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
4✔
883
                                sixel_dither_t *dither,
884
                                unsigned char const *pixels,
885
                                size_t size,
886
                                int width,
887
                                int height,
888
                                int pixelformat,
889
                                int source_colorspace,
890
                                int colorspace)
891
{
892
    SIXELSTATUS status;
893
    int ncolors;
894
    size_t palette_bytes;
895
    unsigned char *new_pixels;
896
    unsigned char *new_palette;
897
    size_t capture_bytes;
898
    unsigned char const *capture_source;
899
    sixel_index_t *paletted_pixels;
900
    size_t quantized_pixels;
901
    sixel_allocator_t *dither_allocator;
902
    int saved_pixelformat;
903
    int restore_pixelformat;
904

905
    /*
906
     * Preserve the quantized frame for assessment observers.
907
     *
908
     *     +-----------------+     +---------------------+
909
     *     | quantized bytes | --> | encoder->capture_*  |
910
     *     +-----------------+     +---------------------+
911
     */
912

913
    status = SIXEL_OK;
4✔
914
    ncolors = 0;
4✔
915
    palette_bytes = 0;
4✔
916
    new_pixels = NULL;
4✔
917
    new_palette = NULL;
4✔
918
    capture_bytes = size;
4✔
919
    capture_source = pixels;
4✔
920
    paletted_pixels = NULL;
4✔
921
    quantized_pixels = 0;
4✔
922
    dither_allocator = NULL;
4✔
923

924
    if (encoder == NULL || pixels == NULL ||
4!
925
            (dither == NULL && size == 0)) {
1!
926
        sixel_helper_set_additional_message(
×
927
            "sixel_encoder_capture_quantized: invalid capture request.");
928
        return SIXEL_BAD_ARGUMENT;
×
929
    }
930

931
    if (!encoder->capture_quantized) {
4!
932
        return SIXEL_OK;
×
933
    }
934

935
    saved_pixelformat = SIXEL_PIXELFORMAT_RGB888;
4✔
936
    restore_pixelformat = 0;
4✔
937
    if (dither != NULL) {
4!
938
        dither_allocator = dither->allocator;
4✔
939
        saved_pixelformat = dither->pixelformat;
4✔
940
        restore_pixelformat = 1;
4✔
941
        if (width <= 0 || height <= 0) {
4!
942
            sixel_helper_set_additional_message(
×
943
                "sixel_encoder_capture_quantized: invalid dimensions.");
944
            status = SIXEL_BAD_ARGUMENT;
×
945
            goto cleanup;
×
946
        }
947
        quantized_pixels = (size_t)width * (size_t)height;
4✔
948
        if (height != 0 &&
4!
949
                quantized_pixels / (size_t)height != (size_t)width) {
4!
950
            sixel_helper_set_additional_message(
×
951
                "sixel_encoder_capture_quantized: image too large.");
952
            status = SIXEL_RUNTIME_ERROR;
×
953
            goto cleanup;
×
954
        }
955
        paletted_pixels = sixel_dither_apply_palette(
4✔
956
            dither, (unsigned char *)pixels, width, height);
1✔
957
        if (paletted_pixels == NULL) {
4!
958
            sixel_helper_set_additional_message(
×
959
                "sixel_encoder_capture_quantized: palette conversion failed.");
960
            status = SIXEL_RUNTIME_ERROR;
×
961
            goto cleanup;
×
962
        }
963
        capture_source = (unsigned char const *)paletted_pixels;
4✔
964
        capture_bytes = quantized_pixels;
4✔
965
    }
1✔
966

967
    if (capture_bytes > 0) {
4!
968
        if (encoder->capture_pixels == NULL ||
4!
969
                encoder->capture_pixels_size < capture_bytes) {
×
970
            new_pixels = (unsigned char *)sixel_allocator_malloc(
4✔
971
                encoder->allocator, capture_bytes);
1✔
972
            if (new_pixels == NULL) {
4!
973
                sixel_helper_set_additional_message(
×
974
                    "sixel_encoder_capture_quantized: "
975
                    "sixel_allocator_malloc() failed.");
976
                status = SIXEL_BAD_ALLOCATION;
×
977
                goto cleanup;
×
978
            }
979
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
4✔
980
            encoder->capture_pixels = new_pixels;
4✔
981
            encoder->capture_pixels_size = capture_bytes;
4✔
982
        }
1✔
983
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
4✔
984
    }
1✔
985
    encoder->capture_pixel_bytes = capture_bytes;
4✔
986

987
    ncolors = 0;
4✔
988
    palette_bytes = 0;
4✔
989
    if (dither != NULL) {
4!
990
        sixel_palette_t *palette_obj = NULL;
4✔
991
        unsigned char *palette_copy = NULL;
4✔
992
        size_t palette_count = 0U;
4✔
993

994
        status = sixel_dither_get_quantized_palette(dither, &palette_obj);
4✔
995
        if (SIXEL_SUCCEEDED(status) && palette_obj != NULL) {
4!
996
            status = sixel_palette_copy_entries_8bit(
4✔
997
                palette_obj,
1✔
998
                &palette_copy,
999
                &palette_count,
1000
                SIXEL_PIXELFORMAT_RGB888,
1001
                encoder->allocator);
1✔
1002
            sixel_palette_unref(palette_obj);
4✔
1003
            palette_obj = NULL;
4✔
1004
            if (SIXEL_SUCCEEDED(status)
5!
1005
                    && palette_copy != NULL
4!
1006
                    && palette_count > 0U) {
4!
1007
                palette_bytes = palette_count * 3U;
4✔
1008
                ncolors = (int)palette_count;
4✔
1009
                if (encoder->capture_palette == NULL
4!
1010
                        || encoder->capture_palette_size < palette_bytes) {
1!
1011
                    new_palette = (unsigned char *)sixel_allocator_malloc(
4✔
1012
                        encoder->allocator, palette_bytes);
1✔
1013
                    if (new_palette == NULL) {
4!
1014
                        sixel_helper_set_additional_message(
×
1015
                            "sixel_encoder_capture_quantized: "
1016
                            "sixel_allocator_malloc() failed.");
1017
                        status = SIXEL_BAD_ALLOCATION;
×
1018
                        sixel_allocator_free(encoder->allocator,
×
1019
                                             palette_copy);
1020
                        goto cleanup;
×
1021
                    }
1022
                    sixel_allocator_free(encoder->allocator,
5✔
1023
                                         encoder->capture_palette);
4✔
1024
                    encoder->capture_palette = new_palette;
4✔
1025
                    encoder->capture_palette_size = palette_bytes;
4✔
1026
                }
1✔
1027
                memcpy(encoder->capture_palette,
4✔
1028
                       palette_copy,
1029
                       palette_bytes);
1030
                if (source_colorspace != colorspace) {
4!
1031
                    (void)sixel_helper_convert_colorspace(
×
1032
                        encoder->capture_palette,
1033
                        palette_bytes,
1034
                        SIXEL_PIXELFORMAT_RGB888,
1035
                        source_colorspace,
1036
                        colorspace);
1037
                }
1038
            }
1✔
1039
            if (palette_copy != NULL) {
4!
1040
                sixel_allocator_free(encoder->allocator, palette_copy);
4✔
1041
            }
1✔
1042
        }
1✔
1043
    }
1✔
1044

1045
    encoder->capture_width = width;
4✔
1046
    encoder->capture_height = height;
4✔
1047
    if (dither != NULL) {
4!
1048
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
4✔
1049
    } else {
1✔
1050
        encoder->capture_pixelformat = pixelformat;
×
1051
    }
1052
    encoder->capture_colorspace = colorspace;
4✔
1053
    encoder->capture_palette_size = palette_bytes;
4✔
1054
    encoder->capture_ncolors = ncolors;
4✔
1055
    encoder->capture_valid = 1;
4✔
1056

1057
cleanup:
3✔
1058
    if (restore_pixelformat && dither != NULL) {
4!
1059
        /*
1060
         * Undo the normalization performed by sixel_dither_apply_palette().
1061
         *
1062
         *     RGBA8888 --capture--> RGB888 (temporary)
1063
         *          \______________________________/
1064
         *                          |
1065
         *                 restore original state for
1066
         *                 the real encoder execution.
1067
         */
1068
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
4✔
1069
    }
1✔
1070
    if (paletted_pixels != NULL && dither_allocator != NULL) {
4!
1071
        sixel_allocator_free(dither_allocator, paletted_pixels);
4✔
1072
    }
1✔
1073

1074
    return status;
4✔
1075
}
1✔
1076

1077
static SIXELSTATUS
1078
sixel_prepare_builtin_palette(
36✔
1079
    sixel_dither_t /* out */ **dither,
1080
    int            /* in */  builtin_palette)
1081
{
1082
    SIXELSTATUS status = SIXEL_FALSE;
36✔
1083

1084
    *dither = sixel_dither_get(builtin_palette);
36✔
1085
    if (*dither == NULL) {
36!
1086
        sixel_helper_set_additional_message(
×
1087
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
1088
        status = SIXEL_RUNTIME_ERROR;
×
1089
        goto end;
×
1090
    }
1091

1092
    status = SIXEL_OK;
36✔
1093

1094
end:
27✔
1095
    return status;
36✔
1096
}
1097

1098
static int
1099
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
609✔
1100
{
1101
    int width_hint;
1102
    int height_hint;
1103
    long base;
1104
    long size;
1105

1106
    width_hint = 0;
609✔
1107
    height_hint = 0;
609✔
1108
    base = 0;
609✔
1109
    size = 0;
609✔
1110

1111
    if (encoder == NULL) {
609!
1112
        return 0;
×
1113
    }
1114

1115
    width_hint = encoder->pixelwidth;
609✔
1116
    height_hint = encoder->pixelheight;
609✔
1117

1118
    /* Request extra resolution for downscaling to preserve detail. */
1119
    if (width_hint > 0 && height_hint > 0) {
609✔
1120
        /* Follow the CLI rule: double the larger axis before doubling
1121
         * again for the final request size. */
1122
        if (width_hint >= height_hint) {
12!
1123
            base = (long)width_hint;
12✔
1124
        } else {
3✔
1125
            base = (long)height_hint;
×
1126
        }
1127
        base *= 2L;
12✔
1128
    } else if (width_hint > 0) {
600✔
1129
        base = (long)width_hint;
64✔
1130
    } else if (height_hint > 0) {
549✔
1131
        base = (long)height_hint;
48✔
1132
    } else {
12✔
1133
        return 0;
485✔
1134
    }
1135

1136
    size = base * 2L;
124✔
1137
    if (size > (long)INT_MAX) {
124!
1138
        size = (long)INT_MAX;
×
1139
    }
1140
    if (size < 1L) {
124!
1141
        size = 1L;
×
1142
    }
1143

1144
    return (int)size;
124✔
1145
}
153✔
1146

1147

1148
typedef struct sixel_callback_context_for_mapfile {
1149
    int reqcolors;
1150
    sixel_dither_t *dither;
1151
    sixel_allocator_t *allocator;
1152
    int working_colorspace;
1153
    int lut_policy;
1154
    int prefer_float32;
1155
} sixel_callback_context_for_mapfile_t;
1156

1157

1158
/* callback function for sixel_helper_load_image_file() */
1159
static SIXELSTATUS
1160
load_image_callback_for_palette(
28✔
1161
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1162
    void            /* in */    *data)  /* private data */
1163
{
1164
    SIXELSTATUS status = SIXEL_FALSE;
28✔
1165
    sixel_callback_context_for_mapfile_t *callback_context;
1166

1167
    /* get callback context object from the private data */
1168
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
28✔
1169

1170
    status = sixel_frame_set_pixelformat(
28✔
1171
        frame,
7✔
1172
        sixel_encoder_pixelformat_for_colorspace(
7✔
1173
            callback_context->working_colorspace,
7✔
1174
            callback_context->prefer_float32));
7✔
1175
    if (SIXEL_FAILED(status)) {
28!
1176
        goto end;
×
1177
    }
1178

1179
    switch (sixel_frame_get_pixelformat(frame)) {
28!
1180
    case SIXEL_PIXELFORMAT_PAL1:
1181
    case SIXEL_PIXELFORMAT_PAL2:
1182
    case SIXEL_PIXELFORMAT_PAL4:
1183
    case SIXEL_PIXELFORMAT_PAL8:
1184
        if (sixel_frame_get_palette(frame) == NULL) {
×
1185
            status = SIXEL_LOGIC_ERROR;
×
1186
            goto end;
×
1187
        }
1188
        /* create new dither object */
1189
        status = sixel_dither_new(
×
1190
            &callback_context->dither,
1191
            sixel_frame_get_ncolors(frame),
1192
            callback_context->allocator);
1193
        if (SIXEL_FAILED(status)) {
×
1194
            goto end;
×
1195
        }
1196

1197
        sixel_dither_set_lut_policy(callback_context->dither,
×
1198
                                    callback_context->lut_policy);
1199

1200
        /* use palette which is extracted from the image */
1201
        sixel_dither_set_palette(callback_context->dither,
×
1202
                                 sixel_frame_get_palette(frame));
1203
        /* success */
1204
        status = SIXEL_OK;
×
1205
        break;
×
1206
    case SIXEL_PIXELFORMAT_G1:
1207
        /* use 1bpp grayscale builtin palette */
1208
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1209
        /* success */
1210
        status = SIXEL_OK;
×
1211
        break;
×
1212
    case SIXEL_PIXELFORMAT_G2:
1213
        /* use 2bpp grayscale builtin palette */
1214
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1215
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
1216
        /* success */
1217
        status = SIXEL_OK;
×
1218
        break;
×
1219
    case SIXEL_PIXELFORMAT_G4:
1220
        /* use 4bpp grayscale builtin palette */
1221
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
1222
        /* success */
1223
        status = SIXEL_OK;
×
1224
        break;
×
1225
    case SIXEL_PIXELFORMAT_G8:
1226
        /* use 8bpp grayscale builtin palette */
1227
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
1228
        /* success */
1229
        status = SIXEL_OK;
×
1230
        break;
×
1231
    default:
21✔
1232
        /* create new dither object */
1233
        status = sixel_dither_new(
28✔
1234
            &callback_context->dither,
7✔
1235
            callback_context->reqcolors,
7✔
1236
            callback_context->allocator);
7✔
1237
        if (SIXEL_FAILED(status)) {
28!
1238
            goto end;
×
1239
        }
1240

1241
        sixel_dither_set_lut_policy(callback_context->dither,
35✔
1242
                                    callback_context->lut_policy);
7✔
1243

1244
        /* create adaptive palette from given frame object */
1245
        status = sixel_dither_initialize(callback_context->dither,
35✔
1246
                                         sixel_frame_get_pixels(frame),
7✔
1247
                                         sixel_frame_get_width(frame),
7✔
1248
                                         sixel_frame_get_height(frame),
7✔
1249
                                         sixel_frame_get_pixelformat(frame),
7✔
1250
                                         SIXEL_LARGE_NORM,
1251
                                         SIXEL_REP_CENTER_BOX,
1252
                                         SIXEL_QUALITY_HIGH);
1253
        if (SIXEL_FAILED(status)) {
28!
1254
            sixel_dither_unref(callback_context->dither);
×
1255
            goto end;
×
1256
        }
1257

1258
        /* success */
1259
        status = SIXEL_OK;
28✔
1260

1261
        break;
28✔
1262
    }
7✔
1263

1264
end:
21✔
1265
    return status;
28✔
1266
}
1267

1268

1269
static SIXELSTATUS
1270
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1271

1272

1273
static int
1274
sixel_path_has_extension(char const *path, char const *extension)
84✔
1275
{
1276
    size_t path_len;
1277
    size_t ext_len;
1278
    size_t index;
1279

1280
    path_len = 0u;
84✔
1281
    ext_len = 0u;
84✔
1282
    index = 0u;
84✔
1283

1284
    if (path == NULL || extension == NULL) {
84!
1285
        return 0;
×
1286
    }
1287

1288
    path_len = strlen(path);
84✔
1289
    ext_len = strlen(extension);
84✔
1290
    if (ext_len == 0u || path_len < ext_len) {
84!
1291
        return 0;
×
1292
    }
1293

1294
    for (index = 0u; index < ext_len; ++index) {
192!
1295
        unsigned char path_ch;
1296
        unsigned char ext_ch;
1297

1298
        path_ch = (unsigned char)path[path_len - ext_len + index];
192✔
1299
        ext_ch = (unsigned char)extension[index];
192✔
1300
        if (tolower(path_ch) != tolower(ext_ch)) {
192✔
1301
            return 0;
84✔
1302
        }
1303
    }
27✔
1304

1305
    return 1;
×
1306
}
21✔
1307

1308
typedef enum sixel_palette_format {
1309
    SIXEL_PALETTE_FORMAT_NONE = 0,
1310
    SIXEL_PALETTE_FORMAT_ACT,
1311
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1312
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1313
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1314
    SIXEL_PALETTE_FORMAT_GPL
1315
} sixel_palette_format_t;
1316

1317
/*
1318
 * Palette specification parser
1319
 *
1320
 *   TYPE:PATH  -> explicit format prefix
1321
 *   PATH       -> rely on extension or heuristics
1322
 *
1323
 * The ASCII diagram below shows how the prefix is peeled:
1324
 *
1325
 *   [type] : [path]
1326
 *    ^-- left part selects decoder/encoder when present.
1327
 */
1328
static char const *
1329
sixel_palette_strip_prefix(char const *spec,
64✔
1330
                           sixel_palette_format_t *format_hint)
1331
{
1332
    char const *colon;
1333
    size_t type_len;
1334
    size_t index;
1335
    char lowered[16];
1336

1337
    colon = NULL;
64✔
1338
    type_len = 0u;
64✔
1339
    index = 0u;
64✔
1340

1341
    if (format_hint != NULL) {
64✔
1342
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
28✔
1343
    }
7✔
1344
    if (spec == NULL) {
64!
1345
        return NULL;
×
1346
    }
1347

1348
    colon = strchr(spec, ':');
64✔
1349
    if (colon == NULL) {
64!
1350
        return spec;
64✔
1351
    }
1352

1353
    type_len = (size_t)(colon - spec);
×
1354
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1355
        return spec;
×
1356
    }
1357

1358
    for (index = 0u; index < type_len; ++index) {
×
1359
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1360
    }
1361
    lowered[type_len] = '\0';
×
1362

1363
    if (strcmp(lowered, "act") == 0) {
×
1364
        if (format_hint != NULL) {
×
1365
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
×
1366
        }
1367
        return colon + 1;
×
1368
    }
1369
    if (strcmp(lowered, "pal") == 0) {
×
1370
        if (format_hint != NULL) {
×
1371
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1372
        }
1373
        return colon + 1;
×
1374
    }
1375
    if (strcmp(lowered, "pal-jasc") == 0) {
×
1376
        if (format_hint != NULL) {
×
1377
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1378
        }
1379
        return colon + 1;
×
1380
    }
1381
    if (strcmp(lowered, "pal-riff") == 0) {
×
1382
        if (format_hint != NULL) {
×
1383
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1384
        }
1385
        return colon + 1;
×
1386
    }
1387
    if (strcmp(lowered, "gpl") == 0) {
×
1388
        if (format_hint != NULL) {
×
1389
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
×
1390
        }
1391
        return colon + 1;
×
1392
    }
1393

1394
    return spec;
×
1395
}
16✔
1396

1397
static sixel_palette_format_t
1398
sixel_palette_format_from_extension(char const *path)
28✔
1399
{
1400
    if (path == NULL) {
28!
1401
        return SIXEL_PALETTE_FORMAT_NONE;
×
1402
    }
1403

1404
    if (sixel_path_has_extension(path, ".act")) {
28!
1405
        return SIXEL_PALETTE_FORMAT_ACT;
×
1406
    }
1407
    if (sixel_path_has_extension(path, ".pal")) {
28!
1408
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1409
    }
1410
    if (sixel_path_has_extension(path, ".gpl")) {
28!
1411
        return SIXEL_PALETTE_FORMAT_GPL;
×
1412
    }
1413

1414
    return SIXEL_PALETTE_FORMAT_NONE;
28✔
1415
}
7✔
1416

1417
static int
1418
sixel_path_has_any_extension(char const *path)
28✔
1419
{
1420
    char const *slash_forward;
1421
#if defined(_WIN32)
1422
    char const *slash_backward;
1423
#endif
1424
    char const *start;
1425
    char const *dot;
1426

1427
    slash_forward = NULL;
28✔
1428
#if defined(_WIN32)
1429
    slash_backward = NULL;
1430
#endif
1431
    start = path;
28✔
1432
    dot = NULL;
28✔
1433

1434
    if (path == NULL) {
28!
1435
        return 0;
×
1436
    }
1437

1438
    slash_forward = strrchr(path, '/');
28✔
1439
#if defined(_WIN32)
1440
    slash_backward = strrchr(path, '\\');
1441
    if (slash_backward != NULL &&
1442
            (slash_forward == NULL || slash_backward > slash_forward)) {
1443
        slash_forward = slash_backward;
1444
    }
1445
#endif
1446
    if (slash_forward == NULL) {
28!
1447
        start = path;
×
1448
    } else {
1449
        start = slash_forward + 1;
28✔
1450
    }
1451

1452
    dot = strrchr(start, '.');
28✔
1453
    if (dot == NULL) {
28!
1454
        return 0;
×
1455
    }
1456

1457
    if (dot[1] == '\0') {
28!
1458
        return 0;
×
1459
    }
1460

1461
    return 1;
28✔
1462
}
7✔
1463

1464
static int
1465
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1466
{
1467
    if (data == NULL || size < 3u) {
×
1468
        return 0;
×
1469
    }
1470
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
1471
        return 1;
×
1472
    }
1473
    return 0;
×
1474
}
1475

1476

1477
/*
1478
 * Materialize palette bytes from a stream.
1479
 *
1480
 * The flow looks like:
1481
 *
1482
 *   stream --> [scratch buffer] --> [resizable heap buffer]
1483
 *                  ^ looped read        ^ returned payload
1484
 */
1485
static SIXELSTATUS
1486
sixel_palette_read_stream(FILE *stream,
×
1487
                          sixel_allocator_t *allocator,
1488
                          unsigned char **pdata,
1489
                          size_t *psize)
1490
{
1491
    SIXELSTATUS status;
1492
    unsigned char *buffer;
1493
    unsigned char *grown;
1494
    size_t capacity;
1495
    size_t used;
1496
    size_t read_bytes;
1497
    size_t needed;
1498
    size_t new_capacity;
1499
    unsigned char scratch[4096];
1500

1501
    status = SIXEL_FALSE;
×
1502
    buffer = NULL;
×
1503
    grown = NULL;
×
1504
    capacity = 0u;
×
1505
    used = 0u;
×
1506
    read_bytes = 0u;
×
1507
    needed = 0u;
×
1508
    new_capacity = 0u;
×
1509

1510
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1511
        sixel_helper_set_additional_message(
×
1512
            "sixel_palette_read_stream: invalid argument.");
1513
        return SIXEL_BAD_ARGUMENT;
×
1514
    }
1515

1516
    *pdata = NULL;
×
1517
    *psize = 0u;
×
1518

1519
    while (1) {
1520
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1521
        if (read_bytes == 0u) {
×
1522
            if (ferror(stream)) {
×
1523
                sixel_helper_set_additional_message(
×
1524
                    "sixel_palette_read_stream: fread() failed.");
1525
                status = SIXEL_LIBC_ERROR;
×
1526
                goto cleanup;
×
1527
            }
1528
            break;
×
1529
        }
1530

1531
        if (used > SIZE_MAX - read_bytes) {
×
1532
            sixel_helper_set_additional_message(
×
1533
                "sixel_palette_read_stream: size overflow.");
1534
            status = SIXEL_BAD_ALLOCATION;
×
1535
            goto cleanup;
×
1536
        }
1537
        needed = used + read_bytes;
×
1538

1539
        if (needed > capacity) {
×
1540
            new_capacity = capacity;
×
1541
            if (new_capacity == 0u) {
×
1542
                new_capacity = 4096u;
×
1543
            }
1544
            while (needed > new_capacity) {
×
1545
                if (new_capacity > SIZE_MAX / 2u) {
×
1546
                    sixel_helper_set_additional_message(
×
1547
                        "sixel_palette_read_stream: size overflow.");
1548
                    status = SIXEL_BAD_ALLOCATION;
×
1549
                    goto cleanup;
×
1550
                }
1551
                new_capacity *= 2u;
×
1552
            }
1553

1554
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1555
                                                             new_capacity);
1556
            if (grown == NULL) {
×
1557
                sixel_helper_set_additional_message(
×
1558
                    "sixel_palette_read_stream: allocation failed.");
1559
                status = SIXEL_BAD_ALLOCATION;
×
1560
                goto cleanup;
×
1561
            }
1562

1563
            if (buffer != NULL) {
×
1564
                memcpy(grown, buffer, used);
×
1565
                sixel_allocator_free(allocator, buffer);
×
1566
            }
1567

1568
            buffer = grown;
×
1569
            grown = NULL;
×
1570
            capacity = new_capacity;
×
1571
        }
1572

1573
        memcpy(buffer + used, scratch, read_bytes);
×
1574
        used += read_bytes;
×
1575
    }
1576

1577
    *pdata = buffer;
×
1578
    *psize = used;
×
1579
    status = SIXEL_OK;
×
1580
    return status;
×
1581

1582
cleanup:
1583
    if (grown != NULL) {
×
1584
        sixel_allocator_free(allocator, grown);
×
1585
    }
1586
    if (buffer != NULL) {
×
1587
        sixel_allocator_free(allocator, buffer);
×
1588
    }
1589
    return status;
×
1590
}
1591

1592

1593
static SIXELSTATUS
1594
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
×
1595
{
1596
    int error_value;
1597
    char error_message[256];
1598
#if HAVE_SYS_STAT_H
1599
    struct stat path_stat;
1600
#endif
1601

1602
    if (pstream == NULL || pclose == NULL || path == NULL) {
×
1603
        sixel_helper_set_additional_message(
×
1604
            "sixel_palette_open_read: invalid argument.");
1605
        return SIXEL_BAD_ARGUMENT;
×
1606
    }
1607

1608
    error_value = 0;
×
1609
    error_message[0] = '\0';
×
1610

1611
    if (strcmp(path, "-") == 0) {
×
1612
        *pstream = stdin;
×
1613
        *pclose = 0;
×
1614
        return SIXEL_OK;
×
1615
    }
1616

1617
#if HAVE_SYS_STAT_H
1618
    if (stat(path, &path_stat) == 0 && S_ISDIR(path_stat.st_mode)) {
×
1619
        sixel_compat_snprintf(error_message,
×
1620
                              sizeof(error_message),
1621
                              "sixel_palette_open_read: mapfile \"%s\" "
1622
                              "is a directory.",
1623
                              path);
1624
        sixel_helper_set_additional_message(error_message);
×
1625
        return SIXEL_BAD_INPUT;
×
1626
    }
1627
#endif
1628

1629
    errno = 0;
×
1630
    *pstream = fopen(path, "rb");
×
1631
    if (*pstream == NULL) {
×
1632
        error_value = errno;
×
1633
        sixel_compat_snprintf(error_message,
×
1634
                              sizeof(error_message),
1635
                              "sixel_palette_open_read: failed to open "
1636
                              "\"%s\": %s.",
1637
                              path,
1638
                              strerror(error_value));
1639
        sixel_helper_set_additional_message(error_message);
×
1640
        return SIXEL_LIBC_ERROR;
×
1641
    }
1642

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

1647

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

1656

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

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

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

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

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

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

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

1691
    return SIXEL_PALETTE_FORMAT_NONE;
×
1692
}
1693

1694

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

1704

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

1717

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

1742
    status = SIXEL_FALSE;
×
1743
    local = NULL;
×
1744
    palette_start = data;
×
1745
    trailer = NULL;
×
1746
    palette_obj = NULL;
×
1747
    exported_colors = 0;
×
1748
    start_index = 0;
×
1749

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

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

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

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

1795
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1796

1797
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
1798
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
1799
        sixel_dither_unref(local);
×
1800
        return status;
×
1801
    }
1802
    status = sixel_palette_set_entries(
×
1803
        palette_obj,
1804
        palette_start + (size_t)start_index * 3u,
×
1805
        (unsigned int)exported_colors,
1806
        3,
1807
        encoder->allocator);
1808
    sixel_palette_unref(palette_obj);
×
1809
    if (SIXEL_FAILED(status)) {
×
1810
        sixel_dither_unref(local);
×
1811
        return status;
×
1812
    }
1813

1814
    *dither = local;
×
1815
    return SIXEL_OK;
×
1816
}
1817

1818

1819
static SIXELSTATUS
1820
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1821
                             size_t size,
1822
                             sixel_encoder_t *encoder,
1823
                             sixel_dither_t **dither)
1824
{
1825
    SIXELSTATUS status;
1826
    char *text;
1827
    size_t index;
1828
    size_t offset;
1829
    char *cursor;
1830
    char *line;
1831
    char *line_end;
1832
    int stage;
1833
    int exported_colors;
1834
    int parsed_colors;
1835
    sixel_dither_t *local;
1836
    sixel_palette_t *palette_obj;
1837
    unsigned char *palette_buffer;
1838
    long component;
1839
    char *parse_end;
1840
    int value_index;
1841
    int values[3];
1842
    char tail;
1843

1844
    status = SIXEL_FALSE;
×
1845
    text = NULL;
×
1846
    index = 0u;
×
1847
    offset = 0u;
×
1848
    cursor = NULL;
×
1849
    line = NULL;
×
1850
    line_end = NULL;
×
1851
    stage = 0;
×
1852
    exported_colors = 0;
×
1853
    parsed_colors = 0;
×
1854
    local = NULL;
×
1855
    palette_obj = NULL;
×
1856
    palette_buffer = NULL;
×
1857
    component = 0;
×
1858
    parse_end = NULL;
×
1859
    value_index = 0;
×
1860
    values[0] = 0;
×
1861
    values[1] = 0;
×
1862
    values[2] = 0;
×
1863

1864
    if (encoder == NULL || dither == NULL) {
×
1865
        sixel_helper_set_additional_message(
×
1866
            "sixel_palette_parse_pal_jasc: invalid argument.");
1867
        return SIXEL_BAD_ARGUMENT;
×
1868
    }
1869
    if (data == NULL || size == 0u) {
×
1870
        sixel_helper_set_additional_message(
×
1871
            "sixel_palette_parse_pal_jasc: empty palette.");
1872
        return SIXEL_BAD_INPUT;
×
1873
    }
1874

1875
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
1876
    if (text == NULL) {
×
1877
        sixel_helper_set_additional_message(
×
1878
            "sixel_palette_parse_pal_jasc: allocation failed.");
1879
        return SIXEL_BAD_ALLOCATION;
×
1880
    }
1881
    memcpy(text, data, size);
×
1882
    text[size] = '\0';
×
1883

1884
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1885
        offset = 3u;
×
1886
    }
1887
    cursor = text + offset;
×
1888

1889
    while (*cursor != '\0') {
×
1890
        line = cursor;
×
1891
        line_end = cursor;
×
1892
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1893
            ++line_end;
×
1894
        }
1895
        if (*line_end != '\0') {
×
1896
            *line_end = '\0';
×
1897
            cursor = line_end + 1;
×
1898
        } else {
1899
            cursor = line_end;
×
1900
        }
1901
        while (*cursor == '\n' || *cursor == '\r') {
×
1902
            ++cursor;
×
1903
        }
1904

1905
        while (*line == ' ' || *line == '\t') {
×
1906
            ++line;
×
1907
        }
1908
        index = strlen(line);
×
1909
        while (index > 0u) {
×
1910
            tail = line[index - 1];
×
1911
            if (tail != ' ' && tail != '\t') {
×
1912
                break;
×
1913
            }
1914
            line[index - 1] = '\0';
×
1915
            --index;
×
1916
        }
1917
        if (*line == '\0') {
×
1918
            continue;
×
1919
        }
1920
        if (*line == '#') {
×
1921
            continue;
×
1922
        }
1923

1924
        if (stage == 0) {
×
1925
            if (strcmp(line, "JASC-PAL") != 0) {
×
1926
                sixel_helper_set_additional_message(
×
1927
                    "sixel_palette_parse_pal_jasc: missing header.");
1928
                status = SIXEL_BAD_INPUT;
×
1929
                goto cleanup;
×
1930
            }
1931
            stage = 1;
×
1932
            continue;
×
1933
        }
1934
        if (stage == 1) {
×
1935
            stage = 2;
×
1936
            continue;
×
1937
        }
1938
        if (stage == 2) {
×
1939
            component = strtol(line, &parse_end, 10);
×
1940
            if (parse_end == line || component <= 0L || component > 256L) {
×
1941
                sixel_helper_set_additional_message(
×
1942
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1943
                status = SIXEL_BAD_INPUT;
×
1944
                goto cleanup;
×
1945
            }
1946
            exported_colors = (int)component;
×
1947
            if (exported_colors <= 0) {
×
1948
                sixel_helper_set_additional_message(
×
1949
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1950
                status = SIXEL_BAD_INPUT;
×
1951
                goto cleanup;
×
1952
            }
1953
            palette_buffer = (unsigned char *)sixel_allocator_malloc(
×
1954
                encoder->allocator,
1955
                (size_t)exported_colors * 3u);
×
1956
            if (palette_buffer == NULL) {
×
1957
                sixel_helper_set_additional_message(
×
1958
                    "sixel_palette_parse_pal_jasc: allocation failed.");
1959
                status = SIXEL_BAD_ALLOCATION;
×
1960
                goto cleanup;
×
1961
            }
1962
            status = sixel_dither_new(&local, exported_colors,
×
1963
                                      encoder->allocator);
1964
            if (SIXEL_FAILED(status)) {
×
1965
                goto cleanup;
×
1966
            }
1967
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1968
            stage = 3;
×
1969
            continue;
×
1970
        }
1971

1972
        value_index = 0;
×
1973
        while (value_index < 3) {
×
1974
            component = strtol(line, &parse_end, 10);
×
1975
            if (parse_end == line || component < 0L || component > 255L) {
×
1976
                sixel_helper_set_additional_message(
×
1977
                    "sixel_palette_parse_pal_jasc: invalid component.");
1978
                status = SIXEL_BAD_INPUT;
×
1979
                goto cleanup;
×
1980
            }
1981
            values[value_index] = (int)component;
×
1982
            ++value_index;
×
1983
            line = parse_end;
×
1984
            while (*line == ' ' || *line == '\t') {
×
1985
                ++line;
×
1986
            }
1987
        }
1988

1989
        if (parsed_colors >= exported_colors) {
×
1990
            sixel_helper_set_additional_message(
×
1991
                "sixel_palette_parse_pal_jasc: excess entries.");
1992
            status = SIXEL_BAD_INPUT;
×
1993
            goto cleanup;
×
1994
        }
1995

1996
        palette_buffer[parsed_colors * 3 + 0] =
×
1997
            (unsigned char)values[0];
×
1998
        palette_buffer[parsed_colors * 3 + 1] =
×
1999
            (unsigned char)values[1];
×
2000
        palette_buffer[parsed_colors * 3 + 2] =
×
2001
            (unsigned char)values[2];
×
2002
        ++parsed_colors;
×
2003
    }
2004

2005
    if (stage < 3) {
×
2006
        sixel_helper_set_additional_message(
×
2007
            "sixel_palette_parse_pal_jasc: incomplete header.");
2008
        status = SIXEL_BAD_INPUT;
×
2009
        goto cleanup;
×
2010
    }
2011
    if (parsed_colors != exported_colors) {
×
2012
        sixel_helper_set_additional_message(
×
2013
            "sixel_palette_parse_pal_jasc: color count mismatch.");
2014
        status = SIXEL_BAD_INPUT;
×
2015
        goto cleanup;
×
2016
    }
2017

2018
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2019
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2020
        goto cleanup;
×
2021
    }
2022
    status = sixel_palette_set_entries(palette_obj,
×
2023
                                       palette_buffer,
2024
                                       (unsigned int)exported_colors,
2025
                                       3,
2026
                                       encoder->allocator);
2027
    sixel_palette_unref(palette_obj);
×
2028
    palette_obj = NULL;
×
2029
    if (SIXEL_FAILED(status)) {
×
2030
        goto cleanup;
×
2031
    }
2032

2033
    *dither = local;
×
2034
    status = SIXEL_OK;
×
2035

2036
cleanup:
2037
    if (palette_obj != NULL) {
×
2038
        sixel_palette_unref(palette_obj);
×
2039
    }
2040
    if (SIXEL_FAILED(status) && local != NULL) {
×
2041
        sixel_dither_unref(local);
×
2042
    }
2043
    if (palette_buffer != NULL) {
×
2044
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
2045
    }
2046
    if (text != NULL) {
×
2047
        sixel_allocator_free(encoder->allocator, text);
×
2048
    }
2049
    return status;
×
2050
}
2051

2052

2053
static SIXELSTATUS
2054
sixel_palette_parse_pal_riff(unsigned char const *data,
×
2055
                             size_t size,
2056
                             sixel_encoder_t *encoder,
2057
                             sixel_dither_t **dither)
2058
{
2059
    SIXELSTATUS status;
2060
    size_t offset;
2061
    size_t chunk_size;
2062
    sixel_dither_t *local;
2063
    sixel_palette_t *palette_obj;
2064
    unsigned char const *chunk;
2065
    unsigned char *palette_buffer;
2066
    unsigned int entry_count;
2067
    unsigned int version;
2068
    unsigned int index;
2069
    size_t palette_offset;
2070

2071
    status = SIXEL_FALSE;
×
2072
    offset = 0u;
×
2073
    chunk_size = 0u;
×
2074
    local = NULL;
×
2075
    chunk = NULL;
×
2076
    palette_obj = NULL;
×
2077
    palette_buffer = NULL;
×
2078
    entry_count = 0u;
×
2079
    version = 0u;
×
2080
    index = 0u;
×
2081
    palette_offset = 0u;
×
2082

2083
    if (encoder == NULL || dither == NULL) {
×
2084
        sixel_helper_set_additional_message(
×
2085
            "sixel_palette_parse_pal_riff: invalid argument.");
2086
        return SIXEL_BAD_ARGUMENT;
×
2087
    }
2088
    if (data == NULL || size < 12u) {
×
2089
        sixel_helper_set_additional_message(
×
2090
            "sixel_palette_parse_pal_riff: truncated palette.");
2091
        return SIXEL_BAD_INPUT;
×
2092
    }
2093
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
×
2094
        sixel_helper_set_additional_message(
×
2095
            "sixel_palette_parse_pal_riff: missing RIFF header.");
2096
        return SIXEL_BAD_INPUT;
×
2097
    }
2098

2099
    offset = 12u;
×
2100
    while (offset + 8u <= size) {
×
2101
        chunk = data + offset;
×
2102
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
×
2103
        if (offset + 8u + chunk_size > size) {
×
2104
            sixel_helper_set_additional_message(
×
2105
                "sixel_palette_parse_pal_riff: chunk extends past end.");
2106
            return SIXEL_BAD_INPUT;
×
2107
        }
2108
        if (memcmp(chunk, "data", 4) == 0) {
×
2109
            break;
×
2110
        }
2111
        offset += 8u + ((chunk_size + 1u) & ~1u);
×
2112
    }
2113

2114
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
2115
        sixel_helper_set_additional_message(
×
2116
            "sixel_palette_parse_pal_riff: missing data chunk.");
2117
        return SIXEL_BAD_INPUT;
×
2118
    }
2119

2120
    if (chunk_size < 4u) {
×
2121
        sixel_helper_set_additional_message(
×
2122
            "sixel_palette_parse_pal_riff: data chunk too small.");
2123
        return SIXEL_BAD_INPUT;
×
2124
    }
2125
    version = sixel_palette_read_le16(chunk + 8);
×
2126
    (void)version;
2127
    entry_count = sixel_palette_read_le16(chunk + 10);
×
2128
    if (entry_count == 0u || entry_count > 256u) {
×
2129
        sixel_helper_set_additional_message(
×
2130
            "sixel_palette_parse_pal_riff: invalid entry count.");
2131
        return SIXEL_BAD_INPUT;
×
2132
    }
2133
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
×
2134
        sixel_helper_set_additional_message(
×
2135
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
2136
        return SIXEL_BAD_INPUT;
×
2137
    }
2138

2139
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
×
2140
    if (SIXEL_FAILED(status)) {
×
2141
        return status;
×
2142
    }
2143
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2144
    palette_buffer = (unsigned char *)sixel_allocator_malloc(
×
2145
        encoder->allocator,
2146
        (size_t)entry_count * 3u);
×
2147
    if (palette_buffer == NULL) {
×
2148
        sixel_helper_set_additional_message(
×
2149
            "sixel_palette_parse_pal_riff: allocation failed.");
2150
        sixel_dither_unref(local);
×
2151
        return SIXEL_BAD_ALLOCATION;
×
2152
    }
2153
    palette_offset = 12u;
×
2154
    for (index = 0u; index < entry_count; ++index) {
×
2155
        palette_buffer[index * 3u + 0u] =
×
2156
            chunk[palette_offset + index * 4u + 0u];
×
2157
        palette_buffer[index * 3u + 1u] =
×
2158
            chunk[palette_offset + index * 4u + 1u];
×
2159
        palette_buffer[index * 3u + 2u] =
×
2160
            chunk[palette_offset + index * 4u + 2u];
×
2161
    }
2162

2163
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2164
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2165
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
2166
        sixel_dither_unref(local);
×
2167
        return status;
×
2168
    }
2169
    status = sixel_palette_set_entries(palette_obj,
×
2170
                                       palette_buffer,
2171
                                       (unsigned int)entry_count,
2172
                                       3,
2173
                                       encoder->allocator);
2174
    sixel_palette_unref(palette_obj);
×
2175
    palette_obj = NULL;
×
2176
    sixel_allocator_free(encoder->allocator, palette_buffer);
×
2177
    palette_buffer = NULL;
×
2178
    if (SIXEL_FAILED(status)) {
×
2179
        sixel_dither_unref(local);
×
2180
        return status;
×
2181
    }
2182

2183
    *dither = local;
×
2184
    return SIXEL_OK;
×
2185
}
2186

2187

2188
static SIXELSTATUS
2189
sixel_palette_parse_gpl(unsigned char const *data,
×
2190
                        size_t size,
2191
                        sixel_encoder_t *encoder,
2192
                        sixel_dither_t **dither)
2193
{
2194
    SIXELSTATUS status;
2195
    char *text;
2196
    size_t offset;
2197
    char *cursor;
2198
    char *line;
2199
    char *line_end;
2200
    size_t index;
2201
    int header_seen;
2202
    int parsed_colors;
2203
    unsigned char palette_bytes[256 * 3];
2204
    long component;
2205
    char *parse_end;
2206
    int value_index;
2207
    int values[3];
2208
    sixel_dither_t *local;
2209
    sixel_palette_t *palette_obj;
2210
    char tail;
2211

2212
    status = SIXEL_FALSE;
×
2213
    text = NULL;
×
2214
    offset = 0u;
×
2215
    cursor = NULL;
×
2216
    line = NULL;
×
2217
    line_end = NULL;
×
2218
    index = 0u;
×
2219
    header_seen = 0;
×
2220
    parsed_colors = 0;
×
2221
    component = 0;
×
2222
    parse_end = NULL;
×
2223
    value_index = 0;
×
2224
    values[0] = 0;
×
2225
    values[1] = 0;
×
2226
    values[2] = 0;
×
2227
    local = NULL;
×
2228
    palette_obj = NULL;
×
2229

2230
    if (encoder == NULL || dither == NULL) {
×
2231
        sixel_helper_set_additional_message(
×
2232
            "sixel_palette_parse_gpl: invalid argument.");
2233
        return SIXEL_BAD_ARGUMENT;
×
2234
    }
2235
    if (data == NULL || size == 0u) {
×
2236
        sixel_helper_set_additional_message(
×
2237
            "sixel_palette_parse_gpl: empty palette.");
2238
        return SIXEL_BAD_INPUT;
×
2239
    }
2240

2241
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
2242
    if (text == NULL) {
×
2243
        sixel_helper_set_additional_message(
×
2244
            "sixel_palette_parse_gpl: allocation failed.");
2245
        return SIXEL_BAD_ALLOCATION;
×
2246
    }
2247
    memcpy(text, data, size);
×
2248
    text[size] = '\0';
×
2249

2250
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2251
        offset = 3u;
×
2252
    }
2253
    cursor = text + offset;
×
2254

2255
    while (*cursor != '\0') {
×
2256
        line = cursor;
×
2257
        line_end = cursor;
×
2258
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2259
            ++line_end;
×
2260
        }
2261
        if (*line_end != '\0') {
×
2262
            *line_end = '\0';
×
2263
            cursor = line_end + 1;
×
2264
        } else {
2265
            cursor = line_end;
×
2266
        }
2267
        while (*cursor == '\n' || *cursor == '\r') {
×
2268
            ++cursor;
×
2269
        }
2270

2271
        while (*line == ' ' || *line == '\t') {
×
2272
            ++line;
×
2273
        }
2274
        index = strlen(line);
×
2275
        while (index > 0u) {
×
2276
            tail = line[index - 1];
×
2277
            if (tail != ' ' && tail != '\t') {
×
2278
                break;
×
2279
            }
2280
            line[index - 1] = '\0';
×
2281
            --index;
×
2282
        }
2283
        if (*line == '\0') {
×
2284
            continue;
×
2285
        }
2286
        if (*line == '#') {
×
2287
            continue;
×
2288
        }
2289
        if (strncmp(line, "Name:", 5) == 0) {
×
2290
            continue;
×
2291
        }
2292
        if (strncmp(line, "Columns:", 8) == 0) {
×
2293
            continue;
×
2294
        }
2295

2296
        if (!header_seen) {
×
2297
            if (strcmp(line, "GIMP Palette") != 0) {
×
2298
                sixel_helper_set_additional_message(
×
2299
                    "sixel_palette_parse_gpl: missing header.");
2300
                status = SIXEL_BAD_INPUT;
×
2301
                goto cleanup;
×
2302
            }
2303
            header_seen = 1;
×
2304
            continue;
×
2305
        }
2306

2307
        if (parsed_colors >= 256) {
×
2308
            sixel_helper_set_additional_message(
×
2309
                "sixel_palette_parse_gpl: too many colors.");
2310
            status = SIXEL_BAD_INPUT;
×
2311
            goto cleanup;
×
2312
        }
2313

2314
        value_index = 0;
×
2315
        while (value_index < 3) {
×
2316
            component = strtol(line, &parse_end, 10);
×
2317
            if (parse_end == line || component < 0L || component > 255L) {
×
2318
                sixel_helper_set_additional_message(
×
2319
                    "sixel_palette_parse_gpl: invalid component.");
2320
                status = SIXEL_BAD_INPUT;
×
2321
                goto cleanup;
×
2322
            }
2323
            values[value_index] = (int)component;
×
2324
            ++value_index;
×
2325
            line = parse_end;
×
2326
            while (*line == ' ' || *line == '\t') {
×
2327
                ++line;
×
2328
            }
2329
        }
2330

2331
        palette_bytes[parsed_colors * 3 + 0] =
×
2332
            (unsigned char)values[0];
×
2333
        palette_bytes[parsed_colors * 3 + 1] =
×
2334
            (unsigned char)values[1];
×
2335
        palette_bytes[parsed_colors * 3 + 2] =
×
2336
            (unsigned char)values[2];
×
2337
        ++parsed_colors;
×
2338
    }
2339

2340
    if (!header_seen) {
×
2341
        sixel_helper_set_additional_message(
×
2342
            "sixel_palette_parse_gpl: header missing.");
2343
        status = SIXEL_BAD_INPUT;
×
2344
        goto cleanup;
×
2345
    }
2346
    if (parsed_colors <= 0) {
×
2347
        sixel_helper_set_additional_message(
×
2348
            "sixel_palette_parse_gpl: no colors parsed.");
2349
        status = SIXEL_BAD_INPUT;
×
2350
        goto cleanup;
×
2351
    }
2352

2353
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2354
    if (SIXEL_FAILED(status)) {
×
2355
        goto cleanup;
×
2356
    }
2357
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2358
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2359
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2360
        goto cleanup;
×
2361
    }
2362
    status = sixel_palette_set_entries(palette_obj,
×
2363
                                       palette_bytes,
2364
                                       (unsigned int)parsed_colors,
2365
                                       3,
2366
                                       encoder->allocator);
2367
    sixel_palette_unref(palette_obj);
×
2368
    palette_obj = NULL;
×
2369
    if (SIXEL_FAILED(status)) {
×
2370
        goto cleanup;
×
2371
    }
2372

2373
    *dither = local;
×
2374
    status = SIXEL_OK;
×
2375

2376
cleanup:
2377
    if (palette_obj != NULL) {
×
2378
        sixel_palette_unref(palette_obj);
×
2379
    }
2380
    if (SIXEL_FAILED(status) && local != NULL) {
×
2381
        sixel_dither_unref(local);
×
2382
    }
2383
    if (text != NULL) {
×
2384
        sixel_allocator_free(encoder->allocator, text);
×
2385
    }
2386
    return status;
×
2387
}
2388

2389

2390
/*
2391
 * Palette exporters
2392
 *
2393
 *   +----------+-------------------------+
2394
 *   | format   | emission strategy       |
2395
 *   +----------+-------------------------+
2396
 *   | ACT      | fixed 256 entries + EOF |
2397
 *   | PAL JASC | textual lines           |
2398
 *   | PAL RIFF | RIFF container          |
2399
 *   | GPL      | textual lines           |
2400
 *   +----------+-------------------------+
2401
 */
2402
static SIXELSTATUS
2403
sixel_palette_write_act(FILE *stream,
×
2404
                        unsigned char const *palette,
2405
                        int exported_colors)
2406
{
2407
    SIXELSTATUS status;
2408
    unsigned char act_table[256 * 3];
2409
    unsigned char trailer[4];
2410
    size_t exported_bytes;
2411

2412
    status = SIXEL_FALSE;
×
2413
    exported_bytes = 0u;
×
2414

2415
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2416
        return SIXEL_BAD_ARGUMENT;
×
2417
    }
2418
    if (exported_colors > 256) {
×
2419
        exported_colors = 256;
×
2420
    }
2421

2422
    memset(act_table, 0, sizeof(act_table));
×
2423
    exported_bytes = (size_t)exported_colors * 3u;
×
2424
    memcpy(act_table, palette, exported_bytes);
×
2425

2426
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2427
                                 & 0xffu);
2428
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2429
    trailer[2] = 0u;
×
2430
    trailer[3] = 0u;
×
2431

2432
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2433
            != sizeof(act_table)) {
2434
        status = SIXEL_LIBC_ERROR;
×
2435
        return status;
×
2436
    }
2437
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2438
            != sizeof(trailer)) {
2439
        status = SIXEL_LIBC_ERROR;
×
2440
        return status;
×
2441
    }
2442

2443
    return SIXEL_OK;
×
2444
}
2445

2446

2447
static SIXELSTATUS
2448
sixel_palette_write_pal_jasc(FILE *stream,
×
2449
                             unsigned char const *palette,
2450
                             int exported_colors)
2451
{
2452
    int index;
2453

2454
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2455
        return SIXEL_BAD_ARGUMENT;
×
2456
    }
2457
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
2458
        return SIXEL_LIBC_ERROR;
×
2459
    }
2460
    for (index = 0; index < exported_colors; ++index) {
×
2461
        if (fprintf(stream, "%d %d %d\n",
×
2462
                    (int)palette[index * 3 + 0],
×
2463
                    (int)palette[index * 3 + 1],
×
2464
                    (int)palette[index * 3 + 2]) < 0) {
×
2465
            return SIXEL_LIBC_ERROR;
×
2466
        }
2467
    }
2468
    return SIXEL_OK;
×
2469
}
2470

2471

2472
static SIXELSTATUS
2473
sixel_palette_write_pal_riff(FILE *stream,
×
2474
                             unsigned char const *palette,
2475
                             int exported_colors)
2476
{
2477
    unsigned char header[12];
2478
    unsigned char chunk[8];
2479
    unsigned char log_palette[4 + 256 * 4];
2480
    unsigned int data_size;
2481
    unsigned int riff_size;
2482
    int index;
2483

2484
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2485
        return SIXEL_BAD_ARGUMENT;
×
2486
    }
2487
    if (exported_colors > 256) {
×
2488
        exported_colors = 256;
×
2489
    }
2490

2491
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2492
    riff_size = 4u + 8u + data_size;
×
2493

2494
    memcpy(header, "RIFF", 4);
×
2495
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2496
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2497
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2498
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2499
    memcpy(header + 8, "PAL ", 4);
×
2500

2501
    memcpy(chunk, "data", 4);
×
2502
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2503
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2504
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2505
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2506

2507
    memset(log_palette, 0, sizeof(log_palette));
×
2508
    log_palette[0] = 0x00;
×
2509
    log_palette[1] = 0x03;
×
2510
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
2511
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
2512
    for (index = 0; index < exported_colors; ++index) {
×
2513
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
2514
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
2515
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
2516
        log_palette[4 + index * 4 + 3] = 0u;
×
2517
    }
2518

2519
    if (fwrite(header, 1, sizeof(header), stream)
×
2520
            != sizeof(header)) {
2521
        return SIXEL_LIBC_ERROR;
×
2522
    }
2523
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2524
        return SIXEL_LIBC_ERROR;
×
2525
    }
2526
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2527
            != (size_t)data_size) {
×
2528
        return SIXEL_LIBC_ERROR;
×
2529
    }
2530
    return SIXEL_OK;
×
2531
}
2532

2533

2534
static SIXELSTATUS
2535
sixel_palette_write_gpl(FILE *stream,
×
2536
                        unsigned char const *palette,
2537
                        int exported_colors)
2538
{
2539
    int index;
2540

2541
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2542
        return SIXEL_BAD_ARGUMENT;
×
2543
    }
2544
    if (fprintf(stream, "GIMP Palette\n") < 0) {
×
2545
        return SIXEL_LIBC_ERROR;
×
2546
    }
2547
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
×
2548
        return SIXEL_LIBC_ERROR;
×
2549
    }
2550
    if (fprintf(stream, "Columns: 16\n") < 0) {
×
2551
        return SIXEL_LIBC_ERROR;
×
2552
    }
2553
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
×
2554
        return SIXEL_LIBC_ERROR;
×
2555
    }
2556
    for (index = 0; index < exported_colors; ++index) {
×
2557
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
×
2558
                    (int)palette[index * 3 + 0],
×
2559
                    (int)palette[index * 3 + 1],
×
2560
                    (int)palette[index * 3 + 2],
×
2561
                    index) < 0) {
2562
            return SIXEL_LIBC_ERROR;
×
2563
        }
2564
    }
2565
    return SIXEL_OK;
×
2566
}
2567

2568

2569
/* create palette from specified map file */
2570
static SIXELSTATUS
2571
sixel_prepare_specified_palette(
28✔
2572
    sixel_dither_t  /* out */   **dither,
2573
    sixel_encoder_t /* in */    *encoder)
2574
{
2575
    SIXELSTATUS status;
2576
    sixel_callback_context_for_mapfile_t callback_context;
2577
    sixel_loader_t *loader;
2578
    int fstatic;
2579
    int fuse_palette;
2580
    int reqcolors;
2581
    int loop_override;
2582
    char const *path;
2583
    sixel_palette_format_t format_hint;
2584
    sixel_palette_format_t format_ext;
2585
    sixel_palette_format_t format_final;
2586
    sixel_palette_format_t format_detected;
2587
    FILE *stream;
2588
    int close_stream;
2589
    unsigned char *buffer;
2590
    size_t buffer_size;
2591
    int palette_request;
2592
    int need_detection;
2593
    int treat_as_image;
2594
    int path_has_extension;
2595
    char mapfile_message[256];
2596

2597
    status = SIXEL_FALSE;
28✔
2598
    loader = NULL;
28✔
2599
    fstatic = 1;
28✔
2600
    fuse_palette = 1;
28✔
2601
    reqcolors = SIXEL_PALETTE_MAX;
28✔
2602
    loop_override = SIXEL_LOOP_DISABLE;
28✔
2603
    path = NULL;
28✔
2604
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
28✔
2605
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
28✔
2606
    format_final = SIXEL_PALETTE_FORMAT_NONE;
28✔
2607
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
28✔
2608
    stream = NULL;
28✔
2609
    close_stream = 0;
28✔
2610
    buffer = NULL;
28✔
2611
    buffer_size = 0u;
28✔
2612
    palette_request = 0;
28✔
2613
    need_detection = 0;
28✔
2614
    treat_as_image = 0;
28✔
2615
    path_has_extension = 0;
28✔
2616
    mapfile_message[0] = '\0';
28✔
2617

2618
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
28!
2619
        sixel_helper_set_additional_message(
×
2620
            "sixel_prepare_specified_palette: invalid mapfile path.");
2621
        return SIXEL_BAD_ARGUMENT;
×
2622
    }
2623

2624
    sixel_encoder_log_stage(encoder,
35✔
2625
                            NULL,
2626
                            "palette",
2627
                            "worker",
2628
                            "start",
2629
                            "mapfile=%s",
2630
                            encoder->mapfile);
7✔
2631

2632
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
28✔
2633
    if (path == NULL || *path == '\0') {
28!
2634
        sixel_helper_set_additional_message(
×
2635
            "sixel_prepare_specified_palette: empty mapfile path.");
2636
        return SIXEL_BAD_ARGUMENT;
×
2637
    }
2638

2639
    format_ext = sixel_palette_format_from_extension(path);
28✔
2640
    path_has_extension = sixel_path_has_any_extension(path);
28✔
2641

2642
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
28!
2643
        palette_request = 1;
×
2644
        format_final = format_hint;
×
2645
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
28!
2646
        palette_request = 1;
×
2647
        format_final = format_ext;
×
2648
    } else if (!path_has_extension) {
28!
2649
        palette_request = 1;
×
2650
        need_detection = 1;
×
2651
    } else {
2652
        treat_as_image = 1;
28✔
2653
    }
2654

2655
    if (palette_request) {
28!
2656
        status = sixel_palette_open_read(path, &stream, &close_stream);
×
2657
        if (SIXEL_FAILED(status)) {
×
2658
            goto palette_cleanup;
×
2659
        }
2660
        status = sixel_palette_read_stream(stream,
×
2661
                                           encoder->allocator,
2662
                                           &buffer,
2663
                                           &buffer_size);
2664
        if (close_stream) {
×
2665
            sixel_palette_close_stream(stream, close_stream);
×
2666
            stream = NULL;
×
2667
            close_stream = 0;
×
2668
        }
2669
        if (SIXEL_FAILED(status)) {
×
2670
            goto palette_cleanup;
×
2671
        }
2672
        if (buffer_size == 0u) {
×
2673
            sixel_compat_snprintf(mapfile_message,
×
2674
                                  sizeof(mapfile_message),
2675
                                  "sixel_prepare_specified_palette: "
2676
                                  "mapfile \"%s\" is empty.",
2677
                                  path != NULL ? path : "");
×
2678
            sixel_helper_set_additional_message(mapfile_message);
×
2679
            status = SIXEL_BAD_INPUT;
×
2680
            goto palette_cleanup;
×
2681
        }
2682

2683
        if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
2684
            format_detected = sixel_palette_guess_format(buffer,
×
2685
                                                         buffer_size);
2686
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2687
                sixel_helper_set_additional_message(
×
2688
                    "sixel_prepare_specified_palette: "
2689
                    "unable to detect palette format.");
2690
                status = SIXEL_BAD_INPUT;
×
2691
                goto palette_cleanup;
×
2692
            }
2693
            format_final = format_detected;
×
2694
        } else if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
2695
            format_detected = sixel_palette_guess_format(buffer,
×
2696
                                                         buffer_size);
2697
            if (format_detected == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
2698
                    format_detected == SIXEL_PALETTE_FORMAT_PAL_RIFF) {
2699
                format_final = format_detected;
×
2700
            } else {
2701
                sixel_helper_set_additional_message(
×
2702
                    "sixel_prepare_specified_palette: "
2703
                    "ambiguous .pal content.");
2704
                status = SIXEL_BAD_INPUT;
×
2705
                goto palette_cleanup;
×
2706
            }
2707
        } else if (need_detection) {
×
2708
            format_detected = sixel_palette_guess_format(buffer,
×
2709
                                                         buffer_size);
2710
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2711
                sixel_helper_set_additional_message(
×
2712
                    "sixel_prepare_specified_palette: "
2713
                    "unable to detect palette format.");
2714
                status = SIXEL_BAD_INPUT;
×
2715
                goto palette_cleanup;
×
2716
            }
2717
            format_final = format_detected;
×
2718
        }
2719

2720
        switch (format_final) {
×
2721
        case SIXEL_PALETTE_FORMAT_ACT:
2722
            status = sixel_palette_parse_act(buffer,
×
2723
                                             buffer_size,
2724
                                             encoder,
2725
                                             dither);
2726
            break;
×
2727
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
2728
            status = sixel_palette_parse_pal_jasc(buffer,
×
2729
                                                  buffer_size,
2730
                                                  encoder,
2731
                                                  dither);
2732
            break;
×
2733
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
2734
            status = sixel_palette_parse_pal_riff(buffer,
×
2735
                                                  buffer_size,
2736
                                                  encoder,
2737
                                                  dither);
2738
            break;
×
2739
        case SIXEL_PALETTE_FORMAT_GPL:
2740
            status = sixel_palette_parse_gpl(buffer,
×
2741
                                             buffer_size,
2742
                                             encoder,
2743
                                             dither);
2744
            break;
×
2745
        default:
2746
            sixel_helper_set_additional_message(
×
2747
                "sixel_prepare_specified_palette: "
2748
                "unsupported palette format.");
2749
            status = SIXEL_BAD_INPUT;
×
2750
            break;
×
2751
        }
2752

2753
palette_cleanup:
2754
        if (buffer != NULL) {
×
2755
            sixel_allocator_free(encoder->allocator, buffer);
×
2756
            buffer = NULL;
×
2757
        }
2758
        if (stream != NULL) {
×
2759
            sixel_palette_close_stream(stream, close_stream);
×
2760
            stream = NULL;
×
2761
        }
2762
        if (SIXEL_SUCCEEDED(status)) {
×
2763
            return status;
×
2764
        }
2765
        if (!treat_as_image) {
×
2766
            return status;
×
2767
        }
2768
    }
2769

2770
    callback_context.reqcolors = encoder->reqcolors;
28✔
2771
    callback_context.dither = NULL;
28✔
2772
    callback_context.allocator = encoder->allocator;
28✔
2773
    callback_context.working_colorspace = encoder->working_colorspace;
28✔
2774
    callback_context.lut_policy = encoder->lut_policy;
28✔
2775
    callback_context.prefer_float32 = encoder->prefer_float32;
28✔
2776

2777
    sixel_helper_set_loader_trace(encoder->verbose);
28✔
2778
    sixel_helper_set_thumbnail_size_hint(
28✔
2779
        sixel_encoder_thumbnail_hint(encoder));
7✔
2780
    status = sixel_loader_new(&loader, encoder->allocator);
28✔
2781
    if (SIXEL_FAILED(status)) {
28!
2782
        goto end_loader;
×
2783
    }
2784

2785
    status = sixel_loader_setopt(loader,
28✔
2786
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
2787
                                 &fstatic);
2788
    if (SIXEL_FAILED(status)) {
28!
2789
        goto end_loader;
×
2790
    }
2791

2792
    status = sixel_loader_setopt(loader,
28✔
2793
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2794
                                 &fuse_palette);
2795
    if (SIXEL_FAILED(status)) {
28!
2796
        goto end_loader;
×
2797
    }
2798

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

2806
    status = sixel_loader_setopt(loader,
35✔
2807
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2808
                                 encoder->bgcolor);
28✔
2809
    if (SIXEL_FAILED(status)) {
28!
2810
        goto end_loader;
×
2811
    }
2812

2813
    status = sixel_loader_setopt(loader,
28✔
2814
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
2815
                                 &loop_override);
2816
    if (SIXEL_FAILED(status)) {
28!
2817
        goto end_loader;
×
2818
    }
2819

2820
    status = sixel_loader_setopt(loader,
35✔
2821
                                 SIXEL_LOADER_OPTION_INSECURE,
2822
                                 &encoder->finsecure);
28✔
2823
    if (SIXEL_FAILED(status)) {
28!
2824
        goto end_loader;
×
2825
    }
2826

2827
    status = sixel_loader_setopt(loader,
35✔
2828
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2829
                                 encoder->cancel_flag);
28✔
2830
    if (SIXEL_FAILED(status)) {
28!
2831
        goto end_loader;
×
2832
    }
2833

2834
    status = sixel_loader_setopt(loader,
35✔
2835
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
2836
                                 encoder->loader_order);
28✔
2837
    if (SIXEL_FAILED(status)) {
28!
2838
        goto end_loader;
×
2839
    }
2840

2841
    status = sixel_loader_setopt(loader,
28✔
2842
                                 SIXEL_LOADER_OPTION_CONTEXT,
2843
                                 &callback_context);
2844
    if (SIXEL_FAILED(status)) {
28!
2845
        goto end_loader;
×
2846
    }
2847

2848
    status = sixel_loader_load_file(loader,
35✔
2849
                                    encoder->mapfile,
28✔
2850
                                    load_image_callback_for_palette);
2851
    if (status != SIXEL_OK) {
28!
2852
        goto end_loader;
×
2853
    }
2854

2855
end_loader:
21✔
2856
    sixel_loader_unref(loader);
28✔
2857

2858
    if (status != SIXEL_OK) {
28!
2859
        return status;
×
2860
    }
2861

2862
    if (! callback_context.dither) {
28!
2863
        sixel_compat_snprintf(mapfile_message,
×
2864
                              sizeof(mapfile_message),
2865
                              "sixel_prepare_specified_palette() failed.\n"
2866
                              "reason: mapfile \"%s\" is empty.",
2867
                              encoder->mapfile != NULL
×
2868
                                ? encoder->mapfile
2869
                                : "");
2870
        sixel_helper_set_additional_message(mapfile_message);
×
2871
        return SIXEL_BAD_INPUT;
×
2872
    }
2873

2874
    *dither = callback_context.dither;
28✔
2875

2876
    sixel_encoder_log_stage(encoder,
35✔
2877
                            NULL,
2878
                            "palette",
2879
                            "worker",
2880
                            "finish",
2881
                            "mapfile=%s format=%d",
2882
                            encoder->mapfile,
7✔
2883
                            format_final);
7✔
2884

2885
    return status;
28✔
2886
}
7✔
2887

2888

2889
/* create dither object from a frame */
2890
static SIXELSTATUS
2891
sixel_encoder_prepare_palette(
693✔
2892
    sixel_encoder_t *encoder,  /* encoder object */
2893
    sixel_frame_t   *frame,    /* input frame object */
2894
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2895
{
2896
    SIXELSTATUS status = SIXEL_FALSE;
693✔
2897
    int histogram_colors;
2898
    sixel_assessment_t *assessment;
2899
    int promoted_stage;
2900

2901
    assessment = NULL;
693✔
2902
    promoted_stage = 0;
693✔
2903
    if (encoder != NULL) {
693!
2904
        assessment = encoder->assessment_observer;
693✔
2905
    }
174✔
2906

2907
    switch (encoder->color_option) {
693!
2908
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
36✔
2909
        if (encoder->dither_cache) {
48!
2910
            *dither = encoder->dither_cache;
×
2911
            status = SIXEL_OK;
×
2912
        } else {
2913
            status = sixel_dither_new(dither, (-1), encoder->allocator);
48✔
2914
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
48✔
2915
        }
2916
        goto end;
48✔
2917
    case SIXEL_COLOR_OPTION_MONOCHROME:
12✔
2918
        if (encoder->dither_cache) {
16!
2919
            *dither = encoder->dither_cache;
×
2920
            status = SIXEL_OK;
×
2921
        } else {
2922
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
16✔
2923
        }
2924
        goto end;
16✔
2925
    case SIXEL_COLOR_OPTION_MAPFILE:
21✔
2926
        if (encoder->dither_cache) {
28!
2927
            *dither = encoder->dither_cache;
×
2928
            status = SIXEL_OK;
×
2929
        } else {
2930
            status = sixel_prepare_specified_palette(dither, encoder);
28✔
2931
        }
2932
        goto end;
28✔
2933
    case SIXEL_COLOR_OPTION_BUILTIN:
27✔
2934
        if (encoder->dither_cache) {
36!
2935
            *dither = encoder->dither_cache;
×
2936
            status = SIXEL_OK;
×
2937
        } else {
2938
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
36✔
2939
        }
2940
        goto end;
36✔
2941
    case SIXEL_COLOR_OPTION_DEFAULT:
565✔
2942
    default:
2943
        break;
565✔
2944
    }
2945

2946
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
565✔
2947
        if (!sixel_frame_get_palette(frame)) {
304!
2948
            status = SIXEL_LOGIC_ERROR;
×
2949
            goto end;
×
2950
        }
2951
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
380✔
2952
                                  encoder->allocator);
76✔
2953
        if (SIXEL_FAILED(status)) {
304!
2954
            goto end;
×
2955
        }
2956
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
304✔
2957
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
304✔
2958
        if (sixel_frame_get_transparent(frame) != (-1)) {
304!
2959
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2960
        }
2961
        if (*dither && encoder->dither_cache) {
304!
2962
            sixel_dither_unref(encoder->dither_cache);
×
2963
        }
2964
        goto end;
304✔
2965
    }
2966

2967
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
261!
2968
        switch (sixel_frame_get_pixelformat(frame)) {
×
2969
        case SIXEL_PIXELFORMAT_G1:
2970
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2971
            break;
×
2972
        case SIXEL_PIXELFORMAT_G2:
2973
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2974
            break;
×
2975
        case SIXEL_PIXELFORMAT_G4:
2976
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2977
            break;
×
2978
        case SIXEL_PIXELFORMAT_G8:
2979
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
2980
            break;
×
2981
        default:
2982
            *dither = NULL;
×
2983
            status = SIXEL_LOGIC_ERROR;
×
2984
            goto end;
×
2985
        }
2986
        if (*dither && encoder->dither_cache) {
×
2987
            sixel_dither_unref(encoder->dither_cache);
×
2988
        }
2989
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
2990
        status = SIXEL_OK;
×
2991
        goto end;
×
2992
    }
2993

2994
    if (encoder->dither_cache) {
261!
2995
        sixel_dither_unref(encoder->dither_cache);
×
2996
    }
2997
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
261✔
2998
    if (SIXEL_FAILED(status)) {
261!
2999
        goto end;
×
3000
    }
3001

3002
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
261✔
3003
    sixel_dither_set_sixel_reversible(*dither,
327✔
3004
                                      encoder->sixel_reversible);
66✔
3005
    sixel_dither_set_final_merge(*dither, encoder->final_merge_mode);
261✔
3006
    (*dither)->quantize_model = encoder->quantize_model;
261✔
3007

3008
    status = sixel_dither_initialize(*dither,
327✔
3009
                                     sixel_frame_get_pixels(frame),
66✔
3010
                                     sixel_frame_get_width(frame),
66✔
3011
                                     sixel_frame_get_height(frame),
66✔
3012
                                     sixel_frame_get_pixelformat(frame),
66✔
3013
                                     encoder->method_for_largest,
66✔
3014
                                     encoder->method_for_rep,
66✔
3015
                                     encoder->quality_mode);
66✔
3016
    if (SIXEL_FAILED(status)) {
261!
3017
        sixel_dither_unref(*dither);
×
3018
        goto end;
×
3019
    }
3020

3021
    if (assessment != NULL && promoted_stage == 0) {
261!
3022
        sixel_assessment_stage_transition(
4✔
3023
            assessment,
1✔
3024
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
3025
        promoted_stage = 1;
4✔
3026
    }
1✔
3027

3028
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
261✔
3029
    if (histogram_colors <= encoder->reqcolors) {
261✔
3030
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
202✔
3031
    }
43✔
3032
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
261✔
3033

3034
    status = SIXEL_OK;
261✔
3035

3036
end:
519✔
3037
    if (assessment != NULL && promoted_stage == 0) {
693!
3038
        sixel_assessment_stage_transition(
×
3039
            assessment,
3040
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
3041
        promoted_stage = 1;
×
3042
    }
3043
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
693!
3044
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
693✔
3045
        /* pass down the user's demand for an exact palette size */
3046
        (*dither)->force_palette = encoder->force_palette;
693✔
3047
    }
174✔
3048
    return status;
693✔
3049
}
3050

3051

3052
/* resize a frame with settings of specified encoder object */
3053
static SIXELSTATUS
3054
sixel_encoder_do_resize(
701✔
3055
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3056
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3057
{
3058
    SIXELSTATUS status = SIXEL_FALSE;
701✔
3059
    int src_width;
3060
    int src_height;
3061
    int dst_width;
3062
    int dst_height;
3063
    int use_float_resize;
3064

3065
    /* get frame width and height */
3066
    src_width = sixel_frame_get_width(frame);
701✔
3067
    src_height = sixel_frame_get_height(frame);
701✔
3068

3069
    if (src_width < 1) {
701✔
3070
         sixel_helper_set_additional_message(
8✔
3071
             "sixel_encoder_do_resize: "
3072
             "detected a frame with a non-positive width.");
3073
        return SIXEL_BAD_ARGUMENT;
8✔
3074
    }
3075

3076
    if (src_height < 1) {
693!
3077
         sixel_helper_set_additional_message(
×
3078
             "sixel_encoder_do_resize: "
3079
             "detected a frame with a non-positive height.");
3080
        return SIXEL_BAD_ARGUMENT;
×
3081
    }
3082

3083
    /* settings around scaling */
3084
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
693✔
3085
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
693✔
3086

3087
    use_float_resize = 0;
693✔
3088

3089
    /* if the encoder has percentwidth or percentheight property,
3090
       convert them to pixelwidth / pixelheight */
3091
    if (encoder->percentwidth > 0) {
693✔
3092
        dst_width = src_width * encoder->percentwidth / 100;
16✔
3093
    }
4✔
3094
    if (encoder->percentheight > 0) {
693✔
3095
        dst_height = src_height * encoder->percentheight / 100;
12✔
3096
    }
3✔
3097

3098
    /* if only either width or height is set, set also the other
3099
       to retain frame aspect ratio */
3100
    if (dst_width > 0 && dst_height <= 0) {
693✔
3101
        dst_height = src_height * dst_width / src_width;
56✔
3102
    }
14✔
3103
    if (dst_height > 0 && dst_width <= 0) {
693✔
3104
        dst_width = src_width * dst_height / src_height;
44✔
3105
    }
11✔
3106

3107
    sixel_encoder_log_stage(encoder,
867✔
3108
                            frame,
174✔
3109
                            "scale",
3110
                            "worker",
3111
                            "start",
3112
                            "src=%dx%d dst=%dx%d resample=%d",
3113
                            src_width,
174✔
3114
                            src_height,
174✔
3115
                            dst_width,
174✔
3116
                            dst_height,
174✔
3117
                            encoder->method_for_resampling);
174✔
3118

3119
    /* do resize */
3120
    if (dst_width > 0 && dst_height > 0) {
693!
3121
        if (encoder->method_for_resampling != SIXEL_RES_NEAREST) {
124✔
3122
            if (SIXEL_PIXELFORMAT_IS_FLOAT32(
100!
3123
                    encoder->working_colorspace) != 0) {
25✔
3124
                use_float_resize = 1;
×
3125
            }
3126
            if (encoder->prefer_float32 != 0) {
100!
3127
                use_float_resize = 1;
×
3128
            }
3129
        }
25✔
3130

3131
        if (use_float_resize != 0) {
124!
3132
            status = sixel_frame_resize_float32(
×
3133
                frame,
3134
                dst_width,
3135
                dst_height,
3136
                encoder->method_for_resampling);
3137
        } else {
3138
            status = sixel_frame_resize(
124✔
3139
                frame,
31✔
3140
                dst_width,
31✔
3141
                dst_height,
31✔
3142
                encoder->method_for_resampling);
31✔
3143
        }
3144
        if (SIXEL_FAILED(status)) {
124!
3145
            goto end;
×
3146
        }
3147
    }
31✔
3148

3149
    sixel_encoder_log_stage(encoder,
867✔
3150
                            frame,
174✔
3151
                            "scale",
3152
                            "worker",
3153
                            "finish",
3154
                            "result=%dx%d",
3155
                            sixel_frame_get_width(frame),
174✔
3156
                            sixel_frame_get_height(frame));
174✔
3157

3158
    /* success */
3159
    status = SIXEL_OK;
693✔
3160

3161
end:
519✔
3162
    return status;
693✔
3163
}
176✔
3164

3165

3166
/* clip a frame with settings of specified encoder object */
3167
static SIXELSTATUS
3168
sixel_encoder_do_clip(
693✔
3169
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3170
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3171
{
3172
    SIXELSTATUS status = SIXEL_FALSE;
693✔
3173
    int src_width;
3174
    int src_height;
3175
    int clip_x;
3176
    int clip_y;
3177
    int clip_w;
3178
    int clip_h;
3179

3180
    /* get frame width and height */
3181
    src_width = sixel_frame_get_width(frame);
693✔
3182
    src_height = sixel_frame_get_height(frame);
693✔
3183

3184
    /* settings around clipping */
3185
    clip_x = encoder->clipx;
693✔
3186
    clip_y = encoder->clipy;
693✔
3187
    clip_w = encoder->clipwidth;
693✔
3188
    clip_h = encoder->clipheight;
693✔
3189

3190
    /* adjust clipping width with comparing it to frame width */
3191
    if (clip_w + clip_x > src_width) {
693✔
3192
        if (clip_x > src_width) {
8✔
3193
            clip_w = 0;
4✔
3194
        } else {
1✔
3195
            clip_w = src_width - clip_x;
4✔
3196
        }
3197
    }
2✔
3198

3199
    /* adjust clipping height with comparing it to frame height */
3200
    if (clip_h + clip_y > src_height) {
693✔
3201
        if (clip_y > src_height) {
8✔
3202
            clip_h = 0;
4✔
3203
        } else {
1✔
3204
            clip_h = src_height - clip_y;
4✔
3205
        }
3206
    }
2✔
3207

3208
    sixel_encoder_log_stage(encoder,
867✔
3209
                            frame,
174✔
3210
                            "crop",
3211
                            "worker",
3212
                            "start",
3213
                            "src=%dx%d region=%dx%d@%d,%d",
3214
                            src_width,
174✔
3215
                            src_height,
174✔
3216
                            clip_w,
174✔
3217
                            clip_h,
174✔
3218
                            clip_x,
174✔
3219
                            clip_y);
174✔
3220

3221
    /* do clipping */
3222
    if (clip_w > 0 && clip_h > 0) {
693!
3223
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
16✔
3224
        if (SIXEL_FAILED(status)) {
16!
3225
            goto end;
×
3226
        }
3227
    }
4✔
3228

3229
    sixel_encoder_log_stage(encoder,
867✔
3230
                            frame,
174✔
3231
                            "crop",
3232
                            "worker",
3233
                            "finish",
3234
                            "result=%dx%d",
3235
                            sixel_frame_get_width(frame),
174✔
3236
                            sixel_frame_get_height(frame));
174✔
3237

3238
    /* success */
3239
    status = SIXEL_OK;
693✔
3240

3241
end:
519✔
3242
    return status;
693✔
3243
}
3244

3245

3246
static void
3247
sixel_debug_print_palette(
4✔
3248
    sixel_dither_t /* in */ *dither /* dithering object */
3249
)
3250
{
3251
    sixel_palette_t *palette_obj;
3252
    unsigned char *palette_copy;
3253
    size_t palette_count;
3254
    int i;
3255

3256
    palette_obj = NULL;
4✔
3257
    palette_copy = NULL;
4✔
3258
    palette_count = 0U;
4✔
3259
    if (dither == NULL) {
4!
3260
        return;
×
3261
    }
3262

3263
    if (SIXEL_FAILED(
4!
3264
            sixel_dither_get_quantized_palette(dither, &palette_obj))
3265
            || palette_obj == NULL) {
4!
3266
        return;
×
3267
    }
3268
    if (SIXEL_FAILED(sixel_palette_copy_entries_8bit(
4!
3269
            palette_obj,
3270
            &palette_copy,
3271
            &palette_count,
3272
            SIXEL_PIXELFORMAT_RGB888,
3273
            dither->allocator))
3274
            || palette_copy == NULL) {
4!
3275
        sixel_palette_unref(palette_obj);
×
3276
        return;
×
3277
    }
3278
    sixel_palette_unref(palette_obj);
4✔
3279

3280
    fprintf(stderr, "palette:\n");
4✔
3281
    for (i = 0; i < (int)palette_count;
64✔
3282
            ++i) {
60✔
3283
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
75✔
3284
                palette_copy[i * 3 + 0],
60✔
3285
                palette_copy[i * 3 + 1],
60✔
3286
                palette_copy[i * 3 + 2]);
60✔
3287
    }
15✔
3288
    sixel_allocator_free(dither->allocator, palette_copy);
4✔
3289
}
1✔
3290

3291

3292
static SIXELSTATUS
3293
sixel_encoder_output_without_macro(
581✔
3294
    sixel_frame_t       /* in */ *frame,
3295
    sixel_dither_t      /* in */ *dither,
3296
    sixel_output_t      /* in */ *output,
3297
    sixel_encoder_t     /* in */ *encoder)
3298
{
3299
    SIXELSTATUS status = SIXEL_OK;
581✔
3300
    static unsigned char *p;
3301
    int depth;
3302
    enum { message_buffer_size = 2048 };
3303
    char message[message_buffer_size];
3304
    int nwrite;
3305
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3306
    int dulation;
3307
    int delay;
3308
    struct timespec tv;
3309
#endif
3310
    unsigned char *pixbuf;
3311
    int width;
3312
    int height;
3313
    int pixelformat = 0;
581✔
3314
    size_t size;
3315
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
581✔
3316
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3317
    sixel_clock_t last_clock;
3318
#endif
3319

3320
    if (encoder == NULL) {
581!
3321
        sixel_helper_set_additional_message(
×
3322
            "sixel_encoder_output_without_macro: encoder object is null.");
3323
        status = SIXEL_BAD_ARGUMENT;
×
3324
        goto end;
×
3325
    }
3326

3327
    if (encoder->assessment_observer != NULL) {
581✔
3328
        sixel_assessment_stage_transition(
4✔
3329
            encoder->assessment_observer,
4✔
3330
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3331
    }
1✔
3332

3333
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
581✔
3334
        if (encoder->force_palette) {
453!
3335
            /* keep every slot when the user forced the palette size */
3336
            sixel_dither_set_optimize_palette(dither, 0);
×
3337
        } else {
3338
            sixel_dither_set_optimize_palette(dither, 1);
453✔
3339
        }
3340
    }
114✔
3341

3342
    pixelformat = sixel_frame_get_pixelformat(frame);
581✔
3343
    frame_colorspace = sixel_frame_get_colorspace(frame);
581✔
3344
    output->pixelformat = pixelformat;
581✔
3345
    output->source_colorspace = frame_colorspace;
581✔
3346
    output->colorspace = encoder->output_colorspace;
581✔
3347
    sixel_dither_set_pixelformat(dither, pixelformat);
581✔
3348
    depth = sixel_helper_compute_depth(pixelformat);
581✔
3349
    if (depth < 0) {
581!
3350
        status = SIXEL_LOGIC_ERROR;
×
3351
        nwrite = sixel_compat_snprintf(
×
3352
            message,
3353
            sizeof(message),
3354
            "sixel_encoder_output_without_macro: "
3355
            "sixel_helper_compute_depth(%08x) failed.",
3356
            pixelformat);
3357
        if (nwrite > 0) {
×
3358
            sixel_helper_set_additional_message(message);
×
3359
        }
3360
        goto end;
×
3361
    }
3362

3363
    width = sixel_frame_get_width(frame);
581✔
3364
    height = sixel_frame_get_height(frame);
581✔
3365
    size = (size_t)(width * height * depth);
581✔
3366

3367
    sixel_encoder_log_stage(encoder,
727✔
3368
                            frame,
146✔
3369
                            "encode",
3370
                            "worker",
3371
                            "start",
3372
                            "size=%dx%d fmt=%08x dst_cs=%d",
3373
                            width,
146✔
3374
                            height,
146✔
3375
                            pixelformat,
146✔
3376
                            output->colorspace);
146✔
3377

3378
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
581✔
3379
    if (p == NULL) {
581!
3380
        sixel_helper_set_additional_message(
×
3381
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
3382
        status = SIXEL_BAD_ALLOCATION;
×
3383
        goto end;
×
3384
    }
3385
#if defined(HAVE_CLOCK)
3386
    if (output->last_clock == 0) {
581!
3387
        output->last_clock = clock();
581✔
3388
    }
146✔
3389
#elif defined(HAVE_CLOCK_WIN)
3390
    if (output->last_clock == 0) {
3391
        output->last_clock = clock_win();
3392
    }
3393
#endif
3394
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3395
    delay = sixel_frame_get_delay(frame);
581✔
3396
    if (delay > 0 && !encoder->fignore_delay) {
581✔
3397
# if defined(HAVE_CLOCK)
3398
        last_clock = clock();
4✔
3399
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3400
#  if defined(__APPLE__)
3401
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
3402
                          / 100000);
1✔
3403
#  else
3404
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3✔
3405
                          / CLOCKS_PER_SEC);
3406
#  endif
3407
        output->last_clock = last_clock;
4✔
3408
# elif defined(HAVE_CLOCK_WIN)
3409
        last_clock = clock_win();
3410
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3411
                          / CLOCKS_PER_SEC);
3412
        output->last_clock = last_clock;
3413
# else
3414
        dulation = 0;
3415
# endif
3416
        if (dulation < 1000 * 10 * delay) {
4!
3417
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3418
            tv.tv_sec = 0;
4✔
3419
            tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
4✔
3420
#  if defined(HAVE_NANOSLEEP)
3421
            nanosleep(&tv, NULL);
4✔
3422
#  else
3423
            nanosleep_win(&tv, NULL);
3424
#  endif
3425
# endif
3426
        }
1✔
3427
    }
1✔
3428
#endif
3429

3430
    pixbuf = sixel_frame_get_pixels(frame);
581✔
3431
    memcpy(p, pixbuf, (size_t)(width * height * depth));
581✔
3432

3433
    if (encoder->cancel_flag && *encoder->cancel_flag) {
581!
3434
        goto end;
×
3435
    }
3436

3437
    if (encoder->capture_quantized) {
581✔
3438
        status = sixel_encoder_capture_quantized(encoder,
5✔
3439
                                                 dither,
1✔
3440
                                                 p,
1✔
3441
                                                 size,
1✔
3442
                                                 width,
1✔
3443
                                                 height,
1✔
3444
                                                 pixelformat,
1✔
3445
                                                 frame_colorspace,
1✔
3446
                                                 output->colorspace);
1✔
3447
        if (SIXEL_FAILED(status)) {
4!
3448
            goto end;
×
3449
        }
3450
    }
1✔
3451

3452
    if (encoder->assessment_observer != NULL) {
581✔
3453
        sixel_assessment_stage_transition(
4✔
3454
            encoder->assessment_observer,
4✔
3455
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3456
    }
1✔
3457
    status = sixel_encode(p, width, height, depth, dither, output);
581✔
3458
    if (encoder->assessment_observer != NULL) {
581✔
3459
        sixel_assessment_stage_finish(encoder->assessment_observer);
4✔
3460
    }
1✔
3461
    if (status != SIXEL_OK) {
581!
3462
        goto end;
×
3463
    }
3464

3465
end:
435✔
3466
    if (SIXEL_SUCCEEDED(status)) {
581!
3467
        sixel_encoder_log_stage(encoder,
727✔
3468
                                frame,
146✔
3469
                                "encode",
3470
                                "worker",
3471
                                "finish",
3472
                                "size=%dx%d",
3473
                                width,
146✔
3474
                                height);
146✔
3475
    }
146✔
3476
    output->pixelformat = pixelformat;
581✔
3477
    output->source_colorspace = frame_colorspace;
581✔
3478
    sixel_allocator_free(encoder->allocator, p);
581✔
3479

3480
    return status;
581✔
3481
}
3482

3483

3484
static SIXELSTATUS
3485
sixel_encoder_output_with_macro(
112✔
3486
    sixel_frame_t   /* in */ *frame,
3487
    sixel_dither_t  /* in */ *dither,
3488
    sixel_output_t  /* in */ *output,
3489
    sixel_encoder_t /* in */ *encoder)
3490
{
3491
    SIXELSTATUS status = SIXEL_OK;
112✔
3492
    enum { message_buffer_size = 256 };
3493
    char buffer[message_buffer_size];
3494
    int nwrite;
3495
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3496
    int dulation;
3497
    struct timespec tv;
3498
#endif
3499
    int width;
3500
    int height;
3501
    int pixelformat;
3502
    int depth;
3503
    size_t size = 0;
112✔
3504
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
112✔
3505
    unsigned char *converted = NULL;
112✔
3506
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3507
    int delay;
3508
#endif
3509
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3510
    sixel_clock_t last_clock;
3511
#endif
3512
    double write_started_at;
3513
    double write_finished_at;
3514
    double write_duration;
3515

3516
    if (encoder != NULL && encoder->assessment_observer != NULL) {
112!
3517
        sixel_assessment_stage_transition(
×
3518
            encoder->assessment_observer,
×
3519
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3520
    }
3521

3522
#if defined(HAVE_CLOCK)
3523
    if (output->last_clock == 0) {
112!
3524
        output->last_clock = clock();
112✔
3525
    }
28✔
3526
#elif defined(HAVE_CLOCK_WIN)
3527
    if (output->last_clock == 0) {
3528
        output->last_clock = clock_win();
3529
    }
3530
#endif
3531

3532
    width = sixel_frame_get_width(frame);
112✔
3533
    height = sixel_frame_get_height(frame);
112✔
3534
    pixelformat = sixel_frame_get_pixelformat(frame);
112✔
3535
    depth = sixel_helper_compute_depth(pixelformat);
112✔
3536
    if (depth < 0) {
112!
3537
        status = SIXEL_LOGIC_ERROR;
×
3538
        sixel_helper_set_additional_message(
×
3539
            "sixel_encoder_output_with_macro: "
3540
            "sixel_helper_compute_depth() failed.");
3541
        goto end;
×
3542
    }
3543

3544
    frame_colorspace = sixel_frame_get_colorspace(frame);
112✔
3545
    size = (size_t)width * (size_t)height * (size_t)depth;
112✔
3546
    converted = (unsigned char *)sixel_allocator_malloc(
112✔
3547
        encoder->allocator, size);
28✔
3548
    if (converted == NULL) {
112!
3549
        sixel_helper_set_additional_message(
×
3550
            "sixel_encoder_output_with_macro: "
3551
            "sixel_allocator_malloc() failed.");
3552
        status = SIXEL_BAD_ALLOCATION;
×
3553
        goto end;
×
3554
    }
3555

3556
    memcpy(converted, sixel_frame_get_pixels(frame), size);
112✔
3557
    output->pixelformat = pixelformat;
112✔
3558
    output->source_colorspace = frame_colorspace;
112✔
3559
    output->colorspace = encoder->output_colorspace;
112✔
3560

3561
    if (sixel_frame_get_loop_no(frame) == 0) {
112✔
3562
        if (encoder->macro_number >= 0) {
72!
3563
            nwrite = sixel_compat_snprintf(
×
3564
                buffer,
3565
                sizeof(buffer),
3566
                "\033P%d;0;1!z",
3567
                encoder->macro_number);
3568
        } else {
3569
            nwrite = sixel_compat_snprintf(
72✔
3570
                buffer,
18✔
3571
                sizeof(buffer),
3572
                "\033P%d;0;1!z",
3573
                sixel_frame_get_frame_no(frame));
18✔
3574
        }
3575
        if (nwrite < 0) {
72!
3576
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3577
            sixel_helper_set_additional_message(
×
3578
                "sixel_encoder_output_with_macro: command format failed.");
3579
            goto end;
×
3580
        }
3581
        write_started_at = 0.0;
72✔
3582
        write_finished_at = 0.0;
72✔
3583
        write_duration = 0.0;
72✔
3584
        if (encoder != NULL && encoder->assessment_observer != NULL) {
72!
3585
            write_started_at = sixel_assessment_timer_now();
×
3586
        }
3587
        nwrite = sixel_write_callback(buffer,
90✔
3588
                                      (int)strlen(buffer),
72✔
3589
                                      &encoder->outfd);
72✔
3590
        if (encoder != NULL && encoder->assessment_observer != NULL) {
72!
3591
            write_finished_at = sixel_assessment_timer_now();
×
3592
            write_duration = write_finished_at - write_started_at;
×
3593
            if (write_duration < 0.0) {
×
3594
                write_duration = 0.0;
×
3595
            }
3596
        }
3597
        if (nwrite < 0) {
72!
3598
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3599
            sixel_helper_set_additional_message(
×
3600
                "sixel_encoder_output_with_macro: "
3601
                "sixel_write_callback() failed.");
3602
            goto end;
×
3603
        }
3604
        if (encoder != NULL && encoder->assessment_observer != NULL) {
72!
3605
            sixel_assessment_record_output_write(
×
3606
                encoder->assessment_observer,
×
3607
                (size_t)nwrite,
3608
                write_duration);
3609
        }
3610

3611
        if (encoder != NULL && encoder->assessment_observer != NULL) {
72!
3612
            sixel_assessment_stage_transition(
×
3613
                encoder->assessment_observer,
×
3614
                SIXEL_ASSESSMENT_STAGE_ENCODE);
3615
        }
3616
        status = sixel_encode(converted,
90✔
3617
                              width,
18✔
3618
                              height,
18✔
3619
                              depth,
18✔
3620
                              dither,
18✔
3621
                              output);
18✔
3622
        if (encoder != NULL && encoder->assessment_observer != NULL) {
72!
3623
            sixel_assessment_stage_finish(
×
3624
                encoder->assessment_observer);
×
3625
        }
3626
        if (SIXEL_FAILED(status)) {
72!
3627
            goto end;
×
3628
        }
3629

3630
        write_started_at = 0.0;
72✔
3631
        write_finished_at = 0.0;
72✔
3632
        write_duration = 0.0;
72✔
3633
        if (encoder != NULL && encoder->assessment_observer != NULL) {
72!
3634
            write_started_at = sixel_assessment_timer_now();
×
3635
        }
3636
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
72✔
3637
        if (encoder != NULL && encoder->assessment_observer != NULL) {
72!
3638
            write_finished_at = sixel_assessment_timer_now();
×
3639
            write_duration = write_finished_at - write_started_at;
×
3640
            if (write_duration < 0.0) {
×
3641
                write_duration = 0.0;
×
3642
            }
3643
        }
3644
        if (nwrite < 0) {
72!
3645
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3646
            sixel_helper_set_additional_message(
×
3647
                "sixel_encoder_output_with_macro: "
3648
                "sixel_write_callback() failed.");
3649
            goto end;
×
3650
        }
3651
        if (encoder != NULL && encoder->assessment_observer != NULL) {
72!
3652
            sixel_assessment_record_output_write(
×
3653
                encoder->assessment_observer,
×
3654
                (size_t)nwrite,
3655
                write_duration);
3656
        }
3657
    }
18✔
3658
    if (encoder->macro_number < 0) {
139✔
3659
        nwrite = sixel_compat_snprintf(
108✔
3660
            buffer,
27✔
3661
            sizeof(buffer),
3662
            "\033[%d*z",
3663
            sixel_frame_get_frame_no(frame));
27✔
3664
        if (nwrite < 0) {
108!
3665
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3666
            sixel_helper_set_additional_message(
×
3667
                "sixel_encoder_output_with_macro: command format failed.");
3668
        }
3669
        write_started_at = 0.0;
108✔
3670
        write_finished_at = 0.0;
108✔
3671
        write_duration = 0.0;
108✔
3672
        if (encoder != NULL && encoder->assessment_observer != NULL) {
108!
3673
            write_started_at = sixel_assessment_timer_now();
×
3674
        }
3675
        nwrite = sixel_write_callback(buffer,
135✔
3676
                                      (int)strlen(buffer),
108✔
3677
                                      &encoder->outfd);
108✔
3678
        if (encoder != NULL && encoder->assessment_observer != NULL) {
108!
3679
            write_finished_at = sixel_assessment_timer_now();
×
3680
            write_duration = write_finished_at - write_started_at;
×
3681
            if (write_duration < 0.0) {
×
3682
                write_duration = 0.0;
×
3683
            }
3684
        }
3685
        if (nwrite < 0) {
108!
3686
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3687
            sixel_helper_set_additional_message(
×
3688
                "sixel_encoder_output_with_macro: "
3689
                "sixel_write_callback() failed.");
3690
            goto end;
×
3691
        }
3692
        if (encoder != NULL && encoder->assessment_observer != NULL) {
108!
3693
            sixel_assessment_record_output_write(
×
3694
                encoder->assessment_observer,
×
3695
                (size_t)nwrite,
3696
                write_duration);
3697
        }
3698
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3699
        delay = sixel_frame_get_delay(frame);
108✔
3700
        if (delay > 0 && !encoder->fignore_delay) {
108!
3701
# if defined(HAVE_CLOCK)
3702
            last_clock = clock();
72✔
3703
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3704
#  if defined(__APPLE__)
3705
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
3706
                             / 100000);
18✔
3707
#  else
3708
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
54✔
3709
                             / CLOCKS_PER_SEC);
3710
#  endif
3711
            output->last_clock = last_clock;
72✔
3712
# elif defined(HAVE_CLOCK_WIN)
3713
            last_clock = clock_win();
3714
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3715
                             / CLOCKS_PER_SEC);
3716
            output->last_clock = last_clock;
3717
# else
3718
            dulation = 0;
3719
# endif
3720
            if (dulation < 1000 * 10 * delay) {
72!
3721
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3722
                tv.tv_sec = 0;
72✔
3723
                tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
72✔
3724
#  if defined(HAVE_NANOSLEEP)
3725
                nanosleep(&tv, NULL);
72✔
3726
#  else
3727
                nanosleep_win(&tv, NULL);
3728
#  endif
3729
# endif
3730
            }
18✔
3731
        }
18✔
3732
#endif
3733
    }
27✔
3734

3735
end:
30✔
3736
    output->pixelformat = pixelformat;
112✔
3737
    output->source_colorspace = frame_colorspace;
112✔
3738
    sixel_allocator_free(encoder->allocator, converted);
112✔
3739

3740
    return status;
112✔
3741
}
3742

3743

3744
static SIXELSTATUS
3745
sixel_encoder_emit_iso2022_chars(
×
3746
    sixel_encoder_t *encoder,
3747
    sixel_frame_t *frame
3748
)
3749
{
3750
    char *buf_p, *buf;
3751
    int col, row;
3752
    int charset;
3753
    int is_96cs;
3754
    unsigned int charset_no;
3755
    unsigned int code;
3756
    int num_cols, num_rows;
3757
    SIXELSTATUS status;
3758
    size_t alloc_size;
3759
    int nwrite;
3760
    int target_fd;
3761
    int chunk_size;
3762

3763
    charset_no = encoder->drcs_charset_no;
×
3764
    if (charset_no == 0u) {
×
3765
        charset_no = 1u;
×
3766
    }
3767
    if (encoder->drcs_mmv == 0) {
×
3768
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3769
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3770
    } else if (encoder->drcs_mmv == 1) {
×
3771
        is_96cs = 0;
×
3772
        charset = (int)(charset_no + 0x3fu);
×
3773
    } else {
3774
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3775
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3776
    }
3777
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3778
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3779
             / encoder->cell_width;
×
3780
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3781
             / encoder->cell_height;
×
3782

3783
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
3784
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
3785
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3786
    if (buf == NULL) {
×
3787
        sixel_helper_set_additional_message(
×
3788
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
3789
        status = SIXEL_BAD_ALLOCATION;
×
3790
        goto end;
×
3791
    }
3792

3793
    code = 0x20;
×
3794
    *(buf_p++) = '\016';  /* SI */
×
3795
    *(buf_p++) = '\033';
×
3796
    *(buf_p++) = ')';
×
3797
    *(buf_p++) = ' ';
×
3798
    *(buf_p++) = charset;
×
3799
    for(row = 0; row < num_rows; row++) {
×
3800
        for(col = 0; col < num_cols; col++) {
×
3801
            if ((code & 0x7f) == 0x0) {
×
3802
                if (charset == 0x7e) {
×
3803
                    is_96cs = 1 - is_96cs;
×
3804
                    charset = '0';
×
3805
                } else {
3806
                    charset++;
×
3807
                }
3808
                code = 0x20;
×
3809
                *(buf_p++) = '\033';
×
3810
                *(buf_p++) = is_96cs ? '-': ')';
×
3811
                *(buf_p++) = ' ';
×
3812
                *(buf_p++) = charset;
×
3813
            }
3814
            *(buf_p++) = code++;
×
3815
        }
3816
        *(buf_p++) = '\n';
×
3817
    }
3818
    *(buf_p++) = '\017';  /* SO */
×
3819

3820
    if (encoder->tile_outfd >= 0) {
×
3821
        target_fd = encoder->tile_outfd;
×
3822
    } else {
3823
        target_fd = encoder->outfd;
×
3824
    }
3825

3826
    chunk_size = (int)(buf_p - buf);
×
3827
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3828
                                          buf,
3829
                                          chunk_size,
3830
                                          target_fd);
3831
    if (nwrite != chunk_size) {
×
3832
        sixel_helper_set_additional_message(
×
3833
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3834
        status = SIXEL_RUNTIME_ERROR;
×
3835
        goto end;
×
3836
    }
3837

3838
    sixel_allocator_free(encoder->allocator, buf);
×
3839

3840
    status = SIXEL_OK;
×
3841

3842
end:
3843
    return status;
×
3844
}
3845

3846

3847
/*
3848
 * This routine is derived from mlterm's drcssixel.c
3849
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
3850
 * The original implementation is credited to Araki Ken.
3851
 * Adjusted here to integrate with libsixel's encoder pipeline.
3852
 */
3853
static SIXELSTATUS
3854
sixel_encoder_emit_drcsmmv2_chars(
×
3855
    sixel_encoder_t *encoder,
3856
    sixel_frame_t *frame
3857
)
3858
{
3859
    char *buf_p, *buf;
3860
    int col, row;
NEW
3861
    char ibytes[3] = { 0x20, 0x00, 0x00 };
×
3862
    int is_96cs;
3863
    unsigned int charset_no;
3864
    unsigned int code;
3865
    int num_cols, num_rows;
3866
    SIXELSTATUS status;
3867
    size_t alloc_size;
3868
    int nwrite;
3869
    int target_fd;
3870
    int chunk_size;
3871
    int fill;
3872

3873
    charset_no = encoder->drcs_charset_no;
×
3874
    if (charset_no == 0u) {
×
3875
        charset_no = 1u;
×
3876
    }
3877
    if (encoder->drcs_mmv == 0) {
×
3878
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
NEW
3879
        ibytes[1] = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
NEW
3880
        fill = 0;
×
3881
    } else if (encoder->drcs_mmv == 1) {
×
3882
        is_96cs = 0;
×
NEW
3883
        ibytes[1] = (int)(charset_no + 0x3fu);
×
NEW
3884
        fill = 0;
×
NEW
3885
    } else if (encoder->drcs_mmv == 2) {
×
3886
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
NEW
3887
        ibytes[1] = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
NEW
3888
        fill = 0;
×
3889
    } else {  /* v3 */
NEW
3890
        is_96cs = 0;
×
NEW
3891
        ibytes[1] = (int)(((charset_no - 1u) / 63u) + 0x20u);
×
NEW
3892
        ibytes[2] = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
NEW
3893
        fill = 1;
×
3894
    }
NEW
3895
    if (fill) {
×
NEW
3896
        code = 0x100000 + (charset_no - 1u) * 94;
×
3897
    } else {
NEW
3898
        code = 0x100020 + (is_96cs ? 0x80 : 0) + ibytes[1] * 0x100;
×
3899
    }
3900
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3901
             / encoder->cell_width;
×
3902
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3903
             / encoder->cell_height;
×
3904

3905
    /* cols x rows x 4(out of BMP) + rows(LFs) */
3906
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
3907
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3908
    if (buf == NULL) {
×
3909
        sixel_helper_set_additional_message(
×
3910
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
3911
        status = SIXEL_BAD_ALLOCATION;
×
3912
        goto end;
×
3913
    }
3914

NEW
3915
    for (row = 0; row < num_rows; row++) {
×
NEW
3916
        for (col = 0; col < num_cols; col++) {
×
3917
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
3918
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
3919
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
3920
            *(buf_p++) = (code & 0x3f) | 0x80;
×
3921
            code++;
×
NEW
3922
            if (! fill) {
×
NEW
3923
                if ((code & 0x7f) == 0x0) {
×
NEW
3924
                    if (ibytes[1] == 0x7e) {
×
NEW
3925
                        is_96cs = 1 - is_96cs;
×
NEW
3926
                        ibytes[1] = '0';
×
3927
                    } else {
NEW
3928
                        ibytes[1]++;
×
3929
                    }
NEW
3930
                    code = 0x100020 + (is_96cs ? 0x80 : 0) + ibytes[1] * 0x100;
×
3931
                }
3932
            }
3933
        }
3934
        *(buf_p++) = '\n';
×
3935
    }
3936

3937
    if (encoder->tile_outfd >= 0) {
×
3938
        target_fd = encoder->tile_outfd;
×
3939
    } else {
3940
        target_fd = encoder->outfd;
×
3941
    }
3942

3943
    chunk_size = (int)(buf_p - buf);
×
3944
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3945
                                          buf,
3946
                                          chunk_size,
3947
                                          target_fd);
3948
    if (nwrite != chunk_size) {
×
3949
        sixel_helper_set_additional_message(
×
3950
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
3951
        status = SIXEL_RUNTIME_ERROR;
×
3952
        goto end;
×
3953
    }
3954

3955
    sixel_allocator_free(encoder->allocator, buf);
×
3956

3957
    status = SIXEL_OK;
×
3958

3959
end:
3960
    return status;
×
3961
}
3962

3963
static SIXELSTATUS
3964
sixel_encoder_encode_frame(
701✔
3965
    sixel_encoder_t *encoder,
3966
    sixel_frame_t   *frame,
3967
    sixel_output_t  *output)
3968
{
3969
    SIXELSTATUS status = SIXEL_FALSE;
701✔
3970
    sixel_dither_t *dither = NULL;
701✔
3971
    int height;
3972
    int is_animation = 0;
701✔
3973
    int nwrite;
3974
    int drcs_is_96cs_param;
3975
    char drcs_designate_str[4] = { 0x20, 0x20, 0x40, 0x00 };
701✔
3976
    char buf[256];
3977
    sixel_write_function fn_write;
3978
    sixel_write_function write_callback;
3979
    sixel_write_function scroll_callback;
3980
    void *write_priv;
3981
    void *scroll_priv;
3982
    sixel_encoder_output_probe_t probe;
3983
    sixel_encoder_output_probe_t scroll_probe;
3984
    sixel_assessment_t *assessment;
3985
    int width_before;
3986
    int height_before;
3987
    int width_after;
3988
    int height_after;
3989
    int target_pixelformat;
3990
    int frame_colorspace;
3991

3992
    fn_write = sixel_write_callback;
701✔
3993
    write_callback = sixel_write_callback;
701✔
3994
    scroll_callback = sixel_write_callback;
701✔
3995
    write_priv = &encoder->outfd;
701✔
3996
    scroll_priv = &encoder->outfd;
701✔
3997
    probe.encoder = NULL;
701✔
3998
    probe.base_write = NULL;
701✔
3999
    probe.base_priv = NULL;
701✔
4000
    scroll_probe.encoder = NULL;
701✔
4001
    scroll_probe.base_write = NULL;
701✔
4002
    scroll_probe.base_priv = NULL;
701✔
4003
    assessment = NULL;
701✔
4004
    if (encoder != NULL) {
701!
4005
        assessment = encoder->assessment_observer;
701✔
4006
    }
176✔
4007
    if (assessment != NULL) {
701✔
4008
        if (encoder->clipfirst) {
4!
4009
            sixel_assessment_stage_transition(
×
4010
                assessment,
4011
                SIXEL_ASSESSMENT_STAGE_CROP);
4012
        } else {
4013
            sixel_assessment_stage_transition(
4✔
4014
                assessment,
1✔
4015
                SIXEL_ASSESSMENT_STAGE_SCALE);
4016
        }
4017
    }
1✔
4018

4019
    /*
4020
     *  Geometry timeline:
4021
     *
4022
     *      +-------+    +------+    +---------------+
4023
     *      | scale | -> | crop | -> | color convert |
4024
     *      +-------+    +------+    +---------------+
4025
     *
4026
     *  The order of the first two blocks mirrors `-c`, so we hop between
4027
     *  SCALE and CROP depending on `clipfirst`.
4028
     */
4029

4030
    if (encoder->clipfirst) {
701✔
4031
        width_before = sixel_frame_get_width(frame);
8✔
4032
        height_before = sixel_frame_get_height(frame);
8✔
4033
        sixel_encoder_log_stage(encoder,
10✔
4034
                                frame,
2✔
4035
                                "crop",
4036
                                "worker",
4037
                                "start",
4038
                                "size=%dx%d",
4039
                                width_before,
2✔
4040
                                height_before);
2✔
4041
        status = sixel_encoder_do_clip(encoder, frame);
8✔
4042
        if (SIXEL_FAILED(status)) {
8!
4043
            goto end;
×
4044
        }
4045
        width_after = sixel_frame_get_width(frame);
8✔
4046
        height_after = sixel_frame_get_height(frame);
8✔
4047
        sixel_encoder_log_stage(encoder,
10✔
4048
                                frame,
2✔
4049
                                "crop",
4050
                                "worker",
4051
                                "finish",
4052
                                "result=%dx%d",
4053
                                width_after,
2✔
4054
                                height_after);
2✔
4055
        if (assessment != NULL) {
8!
4056
            sixel_assessment_stage_transition(
×
4057
                assessment,
4058
                SIXEL_ASSESSMENT_STAGE_SCALE);
4059
        }
4060
        width_before = sixel_frame_get_width(frame);
8✔
4061
        height_before = sixel_frame_get_height(frame);
8✔
4062
        sixel_encoder_log_stage(encoder,
10✔
4063
                                frame,
2✔
4064
                                "scale",
4065
                                "worker",
4066
                                "start",
4067
                                "size=%dx%d",
4068
                                width_before,
2✔
4069
                                height_before);
2✔
4070
        status = sixel_encoder_do_resize(encoder, frame);
8✔
4071
        if (SIXEL_FAILED(status)) {
8!
4072
            goto end;
×
4073
        }
4074
        width_after = sixel_frame_get_width(frame);
8✔
4075
        height_after = sixel_frame_get_height(frame);
8✔
4076
        sixel_encoder_log_stage(encoder,
10✔
4077
                                frame,
2✔
4078
                                "scale",
4079
                                "worker",
4080
                                "finish",
4081
                                "result=%dx%d",
4082
                                width_after,
2✔
4083
                                height_after);
2✔
4084
    } else {
2✔
4085
        width_before = sixel_frame_get_width(frame);
693✔
4086
        height_before = sixel_frame_get_height(frame);
693✔
4087
        sixel_encoder_log_stage(encoder,
867✔
4088
                                frame,
174✔
4089
                                "scale",
4090
                                "worker",
4091
                                "start",
4092
                                "size=%dx%d",
4093
                                width_before,
174✔
4094
                                height_before);
174✔
4095
        status = sixel_encoder_do_resize(encoder, frame);
693✔
4096
        if (SIXEL_FAILED(status)) {
693✔
4097
            goto end;
8✔
4098
        }
4099
        width_after = sixel_frame_get_width(frame);
685✔
4100
        height_after = sixel_frame_get_height(frame);
685✔
4101
        sixel_encoder_log_stage(encoder,
857✔
4102
                                frame,
172✔
4103
                                "scale",
4104
                                "worker",
4105
                                "finish",
4106
                                "result=%dx%d",
4107
                                width_after,
172✔
4108
                                height_after);
172✔
4109
        if (assessment != NULL) {
685✔
4110
            sixel_assessment_stage_transition(
4✔
4111
                assessment,
1✔
4112
                SIXEL_ASSESSMENT_STAGE_CROP);
4113
        }
1✔
4114
        width_before = sixel_frame_get_width(frame);
685✔
4115
        height_before = sixel_frame_get_height(frame);
685✔
4116
        sixel_encoder_log_stage(encoder,
857✔
4117
                                frame,
172✔
4118
                                "crop",
4119
                                "worker",
4120
                                "start",
4121
                                "size=%dx%d",
4122
                                width_before,
172✔
4123
                                height_before);
172✔
4124
        status = sixel_encoder_do_clip(encoder, frame);
685✔
4125
        if (SIXEL_FAILED(status)) {
685!
4126
            goto end;
×
4127
        }
4128
        width_after = sixel_frame_get_width(frame);
685✔
4129
        height_after = sixel_frame_get_height(frame);
685✔
4130
        sixel_encoder_log_stage(encoder,
857✔
4131
                                frame,
172✔
4132
                                "crop",
4133
                                "worker",
4134
                                "finish",
4135
                                "result=%dx%d",
4136
                                width_after,
172✔
4137
                                height_after);
172✔
4138
    }
4139

4140
    if (assessment != NULL) {
693✔
4141
        sixel_assessment_stage_transition(
4✔
4142
            assessment,
1✔
4143
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
4144
    }
1✔
4145

4146
    frame_colorspace = sixel_frame_get_colorspace(frame);
693✔
4147
    target_pixelformat = sixel_encoder_pixelformat_for_colorspace(
693✔
4148
        encoder->working_colorspace,
174✔
4149
        encoder->prefer_float32);
174✔
4150

4151
    /*
4152
     * Promote to float formats when the user opts in via the environment or
4153
     * the precision CLI flag. The selection mirrors the requested working
4154
     * colorspace to avoid losing detail before palette generation.
4155
     */
4156
    sixel_encoder_log_stage(encoder,
867✔
4157
                            frame,
174✔
4158
                            "colorspace",
4159
                            "worker",
4160
                            "start",
4161
                            "current=%d target=%d",
4162
                            frame_colorspace,
174✔
4163
                            encoder->working_colorspace);
174✔
4164

4165
    if (encoder->working_colorspace != SIXEL_COLORSPACE_GAMMA
693!
4166
        || encoder->prefer_float32 != 0) {
693!
4167
        status = sixel_frame_set_pixelformat(
×
4168
            frame,
4169
            target_pixelformat);
4170
        if (SIXEL_FAILED(status)) {
×
4171
            goto end;
×
4172
        }
4173
    }
4174

4175
    if (assessment != NULL) {
693✔
4176
        sixel_assessment_stage_transition(
4✔
4177
            assessment,
1✔
4178
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
4179
    }
1✔
4180

4181
    sixel_encoder_log_stage(encoder,
867✔
4182
                            frame,
174✔
4183
                            "colorspace",
4184
                            "worker",
4185
                            "finish",
4186
                            "result=%d",
4187
                            sixel_frame_get_colorspace(frame));
174✔
4188

4189
    /* prepare dither context */
4190
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
693✔
4191
    if (status != SIXEL_OK) {
693!
4192
        dither = NULL;
×
4193
        goto end;
×
4194
    }
4195

4196
    if (encoder->dither_cache != NULL) {
693!
4197
        encoder->dither_cache = dither;
×
4198
        sixel_dither_ref(dither);
×
4199
    }
4200

4201
    if (encoder->fdrcs) {
693!
4202
        status = sixel_encoder_ensure_cell_size(encoder);
×
4203
        if (SIXEL_FAILED(status)) {
×
4204
            goto end;
×
4205
        }
4206
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
4207
            sixel_helper_set_additional_message(
×
4208
                "drcs option cannot be used together with macro output.");
4209
            status = SIXEL_BAD_ARGUMENT;
×
4210
            goto end;
×
4211
        }
4212
    }
4213

4214
    /* evaluate -v option: print palette */
4215
    if (encoder->verbose) {
693✔
4216
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
12✔
4217
            sixel_debug_print_palette(dither);
4✔
4218
        }
1✔
4219
    }
3✔
4220

4221
    /* evaluate -d option: set method for diffusion */
4222
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
693✔
4223
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
693✔
4224
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
693✔
4225

4226
    /* evaluate -C option: set complexion score */
4227
    if (encoder->complexion > 1) {
693✔
4228
        sixel_dither_set_complexion_score(dither, encoder->complexion);
8✔
4229
    }
2✔
4230

4231
    if (output) {
693!
4232
        sixel_output_ref(output);
×
4233
        if (encoder->assessment_observer != NULL) {
×
4234
            probe.encoder = encoder;
×
4235
            probe.base_write = fn_write;
×
4236
            probe.base_priv = &encoder->outfd;
×
4237
            write_callback = sixel_write_with_probe;
×
4238
            write_priv = &probe;
×
4239
        }
4240
    } else {
4241
        /* create output context */
4242
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
693✔
4243
            /* -u or -n option */
4244
            fn_write = sixel_hex_write_callback;
112✔
4245
        } else {
28✔
4246
            fn_write = sixel_write_callback;
581✔
4247
        }
4248
        write_callback = fn_write;
693✔
4249
        write_priv = &encoder->outfd;
693✔
4250
        if (encoder->assessment_observer != NULL) {
693✔
4251
            probe.encoder = encoder;
4✔
4252
            probe.base_write = fn_write;
4✔
4253
            probe.base_priv = &encoder->outfd;
4✔
4254
            write_callback = sixel_write_with_probe;
4✔
4255
            write_priv = &probe;
4✔
4256
        }
1✔
4257
        status = sixel_output_new(&output,
693✔
4258
                                  write_callback,
174✔
4259
                                  write_priv,
174✔
4260
                                  encoder->allocator);
174✔
4261
        if (SIXEL_FAILED(status)) {
693!
4262
            goto end;
×
4263
        }
4264
    }
4265

4266
    if (encoder->fdrcs) {
693!
4267
        sixel_output_set_skip_dcs_envelope(output, 1);
×
4268
        sixel_output_set_skip_header(output, 1);
×
4269
    }
4270

4271
    sixel_output_set_8bit_availability(output, encoder->f8bit);
693✔
4272
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
693✔
4273
    sixel_output_set_palette_type(output, encoder->palette_type);
693✔
4274
    sixel_output_set_penetrate_multiplexer(
693✔
4275
        output, encoder->penetrate_multiplexer);
174✔
4276
    sixel_output_set_encode_policy(output, encoder->encode_policy);
693✔
4277
    sixel_output_set_ormode(output, encoder->ormode);
693✔
4278

4279
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
693✔
4280
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
144✔
4281
            is_animation = 1;
132✔
4282
        }
33✔
4283
        height = sixel_frame_get_height(frame);
144✔
4284
        if (encoder->assessment_observer != NULL) {
144!
4285
            scroll_probe.encoder = encoder;
×
4286
            scroll_probe.base_write = sixel_write_callback;
×
4287
            scroll_probe.base_priv = &encoder->outfd;
×
4288
            scroll_callback = sixel_write_with_probe;
×
4289
            scroll_priv = &scroll_probe;
×
4290
        } else {
4291
            scroll_callback = sixel_write_callback;
144✔
4292
            scroll_priv = &encoder->outfd;
144✔
4293
        }
4294
        (void) sixel_tty_scroll(scroll_callback,
180✔
4295
                                scroll_priv,
36✔
4296
                                encoder->outfd,
36✔
4297
                                height,
36✔
4298
                                is_animation);
36✔
4299
    }
36✔
4300

4301
    if (encoder->cancel_flag && *encoder->cancel_flag) {
693!
4302
        status = SIXEL_INTERRUPTED;
×
4303
        goto end;
×
4304
    }
4305

4306
    if (encoder->fdrcs) {  /* -@ option */
693!
4307
        if (encoder->drcs_mmv == 0) {
×
4308
            drcs_is_96cs_param =
×
4309
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
NEW
4310
            drcs_designate_str[1] =
×
4311
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
NEW
4312
            drcs_designate_str[2] = 0x00;
×
4313
        } else if (encoder->drcs_mmv == 1) {
×
4314
            drcs_is_96cs_param = 0;
×
NEW
4315
            drcs_designate_str[1] =
×
4316
                (int)(encoder->drcs_charset_no + 0x3fu);
×
NEW
4317
            drcs_designate_str[2] = 0x00;
×
NEW
4318
        } else if (encoder->drcs_mmv == 2) {
×
4319
            drcs_is_96cs_param =
×
4320
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
NEW
4321
            drcs_designate_str[1] =
×
4322
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
NEW
4323
            drcs_designate_str[2] = 0x00;
×
4324
        } else {
NEW
4325
            drcs_is_96cs_param = 0;
×
NEW
4326
            drcs_designate_str[1] =
×
NEW
4327
                (int)(((encoder->drcs_charset_no - 1u) / 63u) + 0x20u);
×
NEW
4328
            drcs_designate_str[2] =
×
NEW
4329
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
NEW
4330
            drcs_designate_str[3] = 0x00;
×
4331
        }
4332
        nwrite = sprintf(buf,
×
4333
                         "%s%sh%s1;0;0;%d;1;3;%d;%d{%s",
4334
                         (encoder->drcs_mmv > 0) ? (
×
4335
                             encoder->f8bit ? "\233?8800": "\033[?8800"
×
4336
                         ): "",
4337
                         (encoder->drcs_mmv >= 3) ? (
×
4338
                             encoder->f8bit ? ";8801": ";8801"
4339
                         ): "",
4340
                         encoder->f8bit ? "\220": "\033P",
×
4341
                         encoder->cell_width,
4342
                         encoder->cell_height,
4343
                         drcs_is_96cs_param,
4344
                         drcs_designate_str);
4345
        if (nwrite < 0) {
×
4346
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4347
            sixel_helper_set_additional_message(
×
4348
                "sixel_encoder_encode_frame: command format failed.");
4349
            goto end;
×
4350
        }
4351
        nwrite = write_callback(buf, nwrite, write_priv);
×
4352
        if (nwrite < 0) {
×
4353
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4354
            sixel_helper_set_additional_message(
×
4355
                "sixel_encoder_encode_frame: write() failed.");
4356
            goto end;
×
4357
        }
4358
    }
4359

4360
    /* output sixel: junction of multi-frame processing strategy */
4361
    if (encoder->fuse_macro) {  /* -u option */
693✔
4362
        /* use macro */
4363
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
108✔
4364
    } else if (encoder->macro_number >= 0) { /* -n option */
612✔
4365
        /* use macro */
4366
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
4✔
4367
    } else {
1✔
4368
        /* do not use macro */
4369
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
581✔
4370
    }
4371
    if (SIXEL_FAILED(status)) {
693!
4372
        goto end;
×
4373
    }
4374

4375
    if (encoder->cancel_flag && *encoder->cancel_flag) {
693!
4376
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
4377
        if (nwrite < 0) {
×
4378
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4379
            sixel_helper_set_additional_message(
×
4380
                "sixel_encoder_encode_frame: write_callback() failed.");
4381
            goto end;
×
4382
        }
4383
        status = SIXEL_INTERRUPTED;
×
4384
    }
4385
    if (SIXEL_FAILED(status)) {
693!
4386
        goto end;
×
4387
    }
4388

4389
    if (encoder->fdrcs) {  /* -@ option */
693!
4390
        if (encoder->f8bit) {
×
4391
            nwrite = write_callback("\234", 1, write_priv);
×
4392
        } else {
4393
            nwrite = write_callback("\033\\", 2, write_priv);
×
4394
        }
4395
        if (nwrite < 0) {
×
4396
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4397
            sixel_helper_set_additional_message(
×
4398
                "sixel_encoder_encode_frame: write_callback() failed.");
4399
            goto end;
×
4400
        }
4401

4402
        if (encoder->tile_outfd >= 0) {
×
4403
            if (encoder->drcs_mmv == 0) {
×
4404
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
4405
                if (SIXEL_FAILED(status)) {
×
4406
                    goto end;
×
4407
                }
4408
            } else {
4409
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
4410
                if (SIXEL_FAILED(status)) {
×
4411
                    goto end;
×
4412
                }
4413
            }
4414
        }
4415
    }
4416

4417

4418
end:
519✔
4419
    if (output) {
701✔
4420
        sixel_output_unref(output);
693✔
4421
    }
174✔
4422
    if (dither) {
701✔
4423
        sixel_dither_unref(dither);
693✔
4424
    }
174✔
4425

4426
    return status;
701✔
4427
}
4428

4429

4430
/* create encoder object */
4431
SIXELAPI SIXELSTATUS
4432
sixel_encoder_new(
725✔
4433
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
4434
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
4435
                                                  default allocator */
4436
{
4437
    SIXELSTATUS status = SIXEL_FALSE;
725✔
4438
    char const *env_default_bgcolor = NULL;
725✔
4439
    char const *env_default_ncolors = NULL;
725✔
4440
    char const *env_prefer_float32 = NULL;
725✔
4441
    int ncolors;
4442
    int prefer_float32;
4443

4444
    if (allocator == NULL) {
725!
4445
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
725✔
4446
        if (SIXEL_FAILED(status)) {
725!
4447
            goto end;
×
4448
        }
4449
    } else {
182✔
4450
        sixel_allocator_ref(allocator);
×
4451
    }
4452

4453
    *ppencoder
182✔
4454
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
907✔
4455
                                                    sizeof(sixel_encoder_t));
4456
    if (*ppencoder == NULL) {
725!
4457
        sixel_helper_set_additional_message(
×
4458
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
4459
        status = SIXEL_BAD_ALLOCATION;
×
4460
        sixel_allocator_unref(allocator);
×
4461
        goto end;
×
4462
    }
4463

4464
    (*ppencoder)->ref                   = 1;
725✔
4465
    (*ppencoder)->reqcolors             = (-1);
725✔
4466
    (*ppencoder)->force_palette         = 0;
725✔
4467
    (*ppencoder)->mapfile               = NULL;
725✔
4468
    (*ppencoder)->palette_output        = NULL;
725✔
4469
    (*ppencoder)->loader_order          = NULL;
725✔
4470
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
725✔
4471
    (*ppencoder)->builtin_palette       = 0;
725✔
4472
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
725✔
4473
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
725✔
4474
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
725✔
4475
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
725✔
4476
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
725✔
4477
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
725✔
4478
    (*ppencoder)->quantize_model        = SIXEL_QUANTIZE_MODEL_AUTO;
725✔
4479
    (*ppencoder)->final_merge_mode      = SIXEL_FINAL_MERGE_AUTO;
725✔
4480
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
725✔
4481
    (*ppencoder)->sixel_reversible      = 0;
725✔
4482
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
725✔
4483
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
725✔
4484
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
725✔
4485
    (*ppencoder)->f8bit                 = 0;
725✔
4486
    (*ppencoder)->has_gri_arg_limit     = 0;
725✔
4487
    (*ppencoder)->finvert               = 0;
725✔
4488
    (*ppencoder)->fuse_macro            = 0;
725✔
4489
    (*ppencoder)->fdrcs                 = 0;
725✔
4490
    (*ppencoder)->fignore_delay         = 0;
725✔
4491
    (*ppencoder)->complexion            = 1;
725✔
4492
    (*ppencoder)->fstatic               = 0;
725✔
4493
    (*ppencoder)->cell_width            = 0;
725✔
4494
    (*ppencoder)->cell_height           = 0;
725✔
4495
    (*ppencoder)->pixelwidth            = (-1);
725✔
4496
    (*ppencoder)->pixelheight           = (-1);
725✔
4497
    (*ppencoder)->percentwidth          = (-1);
725✔
4498
    (*ppencoder)->percentheight         = (-1);
725✔
4499
    (*ppencoder)->clipx                 = 0;
725✔
4500
    (*ppencoder)->clipy                 = 0;
725✔
4501
    (*ppencoder)->clipwidth             = 0;
725✔
4502
    (*ppencoder)->clipheight            = 0;
725✔
4503
    (*ppencoder)->clipfirst             = 0;
725✔
4504
    (*ppencoder)->macro_number          = (-1);
725✔
4505
    (*ppencoder)->verbose               = 0;
725✔
4506
    (*ppencoder)->penetrate_multiplexer = 0;
725✔
4507
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
725✔
4508
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
725✔
4509
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
725✔
4510
    (*ppencoder)->prefer_float32        = 0;
725✔
4511
    (*ppencoder)->ormode                = 0;
725✔
4512
    (*ppencoder)->pipe_mode             = 0;
725✔
4513
    (*ppencoder)->bgcolor               = NULL;
725✔
4514
    (*ppencoder)->outfd                 = STDOUT_FILENO;
725✔
4515
    (*ppencoder)->tile_outfd            = (-1);
725✔
4516
    (*ppencoder)->finsecure             = 0;
725✔
4517
    (*ppencoder)->cancel_flag           = NULL;
725✔
4518
    (*ppencoder)->dither_cache          = NULL;
725✔
4519
    (*ppencoder)->drcs_charset_no       = 1u;
725✔
4520
    (*ppencoder)->drcs_mmv              = 2;
725✔
4521
    (*ppencoder)->capture_quantized     = 0;
725✔
4522
    (*ppencoder)->capture_source        = 0;
725✔
4523
    (*ppencoder)->capture_pixels        = NULL;
725✔
4524
    (*ppencoder)->capture_pixels_size   = 0;
725✔
4525
    (*ppencoder)->capture_palette       = NULL;
725✔
4526
    (*ppencoder)->capture_palette_size  = 0;
725✔
4527
    (*ppencoder)->capture_pixel_bytes   = 0;
725✔
4528
    (*ppencoder)->capture_width         = 0;
725✔
4529
    (*ppencoder)->capture_height        = 0;
725✔
4530
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
725✔
4531
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
725✔
4532
    (*ppencoder)->capture_ncolors       = 0;
725✔
4533
    (*ppencoder)->capture_valid         = 0;
725✔
4534
    (*ppencoder)->capture_source_frame  = NULL;
725✔
4535
    (*ppencoder)->assessment_observer   = NULL;
725✔
4536
    (*ppencoder)->assessment_json_path  = NULL;
725✔
4537
    (*ppencoder)->assessment_sections   = SIXEL_ASSESSMENT_SECTION_NONE;
725✔
4538
    (*ppencoder)->last_loader_name[0]   = '\0';
725✔
4539
    (*ppencoder)->last_source_path[0]   = '\0';
725✔
4540
    (*ppencoder)->last_input_bytes      = 0u;
725✔
4541
    (*ppencoder)->output_is_png         = 0;
725✔
4542
    (*ppencoder)->output_png_to_stdout  = 0;
725✔
4543
    (*ppencoder)->png_output_path       = NULL;
725✔
4544
    (*ppencoder)->sixel_output_path     = NULL;
725✔
4545
    (*ppencoder)->clipboard_output_active = 0;
725✔
4546
    (*ppencoder)->clipboard_output_format[0] = '\0';
725✔
4547
    (*ppencoder)->clipboard_output_path = NULL;
725✔
4548
    (*ppencoder)->logger                = NULL;
725✔
4549
    (*ppencoder)->parallel_job_id       = -1;
725✔
4550
    (*ppencoder)->allocator             = allocator;
725✔
4551

4552
    prefer_float32 = 0;
725✔
4553
    env_prefer_float32 = sixel_compat_getenv(
725✔
4554
        SIXEL_ENCODER_PRECISION_ENVVAR);
4555
    /*
4556
     * $SIXEL_FLOAT32_DITHER seeds the precision preference and is later
4557
     * overridden by the precision CLI flag when provided.
4558
     */
4559
    prefer_float32 = sixel_encoder_env_prefers_float32(env_prefer_float32);
725✔
4560
    (*ppencoder)->prefer_float32 = prefer_float32;
725✔
4561

4562
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
4563
    env_default_bgcolor = sixel_compat_getenv("SIXEL_BGCOLOR");
725✔
4564
    if (env_default_bgcolor != NULL) {
725!
4565
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
4566
                                         env_default_bgcolor,
4567
                                         allocator);
4568
        if (SIXEL_FAILED(status)) {
×
4569
            goto error;
×
4570
        }
4571
    }
4572

4573
    /* evaluate environment variable ${SIXEL_COLORS} */
4574
    env_default_ncolors = sixel_compat_getenv("SIXEL_COLORS");
725✔
4575
    if (env_default_ncolors) {
725!
4576
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
4577
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
4578
            (*ppencoder)->reqcolors = ncolors;
×
4579
        }
4580
    }
4581

4582
    /* success */
4583
    status = SIXEL_OK;
725✔
4584

4585
    goto end;
725✔
4586

4587
error:
4588
    sixel_allocator_free(allocator, *ppencoder);
×
4589
    sixel_allocator_unref(allocator);
×
4590
    *ppencoder = NULL;
×
4591

4592
end:
543✔
4593
    return status;
725✔
4594
}
4595

4596

4597
/* create encoder object (deprecated version) */
4598
SIXELAPI /* deprecated */ sixel_encoder_t *
4599
sixel_encoder_create(void)
×
4600
{
4601
    SIXELSTATUS status = SIXEL_FALSE;
×
4602
    sixel_encoder_t *encoder = NULL;
×
4603

4604
    status = sixel_encoder_new(&encoder, NULL);
×
4605
    if (SIXEL_FAILED(status)) {
×
4606
        return NULL;
×
4607
    }
4608

4609
    return encoder;
×
4610
}
4611

4612

4613
/* destroy encoder object */
4614
static void
4615
sixel_encoder_destroy(sixel_encoder_t *encoder)
725✔
4616
{
4617
    sixel_allocator_t *allocator;
4618

4619
    if (encoder) {
725!
4620
        allocator = encoder->allocator;
725✔
4621
        sixel_allocator_free(allocator, encoder->mapfile);
725✔
4622
        sixel_allocator_free(allocator, encoder->palette_output);
725✔
4623
        sixel_allocator_free(allocator, encoder->loader_order);
725✔
4624
        sixel_allocator_free(allocator, encoder->bgcolor);
725✔
4625
        sixel_dither_unref(encoder->dither_cache);
725✔
4626
        if (encoder->outfd
735!
4627
            && encoder->outfd != STDOUT_FILENO
725!
4628
            && encoder->outfd != STDERR_FILENO) {
212!
4629
            (void)sixel_compat_close(encoder->outfd);
40✔
4630
        }
10✔
4631
        if (encoder->tile_outfd >= 0
725!
4632
            && encoder->tile_outfd != encoder->outfd
182!
4633
            && encoder->tile_outfd != STDOUT_FILENO
×
4634
            && encoder->tile_outfd != STDERR_FILENO) {
×
4635
            (void)sixel_compat_close(encoder->tile_outfd);
×
4636
        }
4637
        if (encoder->capture_source_frame != NULL) {
725✔
4638
            sixel_frame_unref(encoder->capture_source_frame);
4✔
4639
        }
1✔
4640
        if (encoder->clipboard_output_path != NULL) {
725!
4641
            (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4642
            encoder->clipboard_output_path = NULL;
×
4643
        }
4644
        encoder->clipboard_output_active = 0;
725✔
4645
        encoder->clipboard_output_format[0] = '\0';
725✔
4646
        sixel_allocator_free(allocator, encoder->capture_pixels);
725✔
4647
        sixel_allocator_free(allocator, encoder->capture_palette);
725✔
4648
        sixel_allocator_free(allocator, encoder->png_output_path);
725✔
4649
        sixel_allocator_free(allocator, encoder->sixel_output_path);
725✔
4650
        sixel_allocator_free(allocator, encoder);
725✔
4651
        sixel_allocator_unref(allocator);
725✔
4652
    }
182✔
4653
}
725✔
4654

4655

4656
/* increase reference count of encoder object (thread-unsafe) */
4657
SIXELAPI void
4658
sixel_encoder_ref(sixel_encoder_t *encoder)
1,538✔
4659
{
4660
    /* TODO: be thread safe */
4661
    ++encoder->ref;
1,538✔
4662
}
1,538✔
4663

4664

4665
/* decrease reference count of encoder object (thread-unsafe) */
4666
SIXELAPI void
4667
sixel_encoder_unref(sixel_encoder_t *encoder)
2,263✔
4668
{
4669
    /* TODO: be thread safe */
4670
    if (encoder != NULL && --encoder->ref == 0) {
2,263!
4671
        sixel_encoder_destroy(encoder);
725✔
4672
    }
182✔
4673
}
2,263✔
4674

4675

4676
/* set cancel state flag to encoder object */
4677
SIXELAPI SIXELSTATUS
4678
sixel_encoder_set_cancel_flag(
585✔
4679
    sixel_encoder_t /* in */ *encoder,
4680
    int             /* in */ *cancel_flag
4681
)
4682
{
4683
    SIXELSTATUS status = SIXEL_OK;
585✔
4684

4685
    encoder->cancel_flag = cancel_flag;
585✔
4686

4687
    return status;
585✔
4688
}
4689

4690

4691
/*
4692
 * parse_assessment_sections() translates a comma-separated section list into
4693
 * the bitmask understood by the assessment back-end.  The accepted grammar is
4694
 * intentionally small so that the CLI contract stays predictable:
4695
 *
4696
 *     list := section {"," section}
4697
 *     section := name | name "@" view
4698
 *
4699
 * Each name maps to a bit flag while the optional view toggles the encoded vs
4700
 * quantized quality comparison.  The helper folds case, trims ASCII
4701
 * whitespace, and rejects mixed encoded/quantized requests so that the caller
4702
 * can rely on a single coherent capture strategy.
4703
 */
4704
static int
4705
parse_assessment_sections(char const *spec,
8✔
4706
                          unsigned int *out_sections)
4707
{
4708
    char *copy;
4709
    char *cursor;
4710
    char *token;
4711
    char *context;
4712
    unsigned int sections;
4713
    unsigned int view;
4714
    int result;
4715
    size_t length;
4716
    size_t spec_len;
4717
    char *at;
4718
    char *base;
4719
    char *variant;
4720
    char *p;
4721

4722
    if (spec == NULL || out_sections == NULL) {
8!
4723
        return -1;
×
4724
    }
4725
    spec_len = strlen(spec);
8✔
4726
    copy = (char *)malloc(spec_len + 1u);
8✔
4727
    if (copy == NULL) {
8!
4728
        return -1;
×
4729
    }
4730
    memcpy(copy, spec, spec_len + 1u);
8✔
4731
    cursor = copy;
8✔
4732
    sections = 0u;
8✔
4733
    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
8✔
4734
    result = 0;
8✔
4735
    context = NULL;
8✔
4736
    while (result == 0) {
16!
4737
        token = sixel_compat_strtok(cursor, ",", &context);
16✔
4738
        if (token == NULL) {
16✔
4739
            break;
8✔
4740
        }
4741
        cursor = NULL;
8✔
4742
        while (*token == ' ' || *token == '\t') {
8!
4743
            token += 1;
×
4744
        }
4745
        length = strlen(token);
8✔
4746
        while (length > 0u &&
10!
4747
               (token[length - 1u] == ' ' || token[length - 1u] == '\t')) {
8!
4748
            token[length - 1u] = '\0';
×
4749
            length -= 1u;
×
4750
        }
4751
        if (*token == '\0') {
8!
4752
            result = -1;
×
4753
            break;
×
4754
        }
4755
        for (p = token; *p != '\0'; ++p) {
56✔
4756
            *p = (char)tolower((unsigned char)*p);
48✔
4757
        }
12✔
4758
        at = strchr(token, '@');
8✔
4759
        if (at != NULL) {
8!
4760
            *at = '\0';
×
4761
            variant = at + 1;
×
4762
            if (*variant == '\0') {
×
4763
                result = -1;
×
4764
                break;
×
4765
            }
4766
        } else {
4767
            variant = NULL;
8✔
4768
        }
4769
        base = token;
8✔
4770
        if (strcmp(base, "all") == 0) {
8!
4771
            sections |= SIXEL_ASSESSMENT_SECTION_ALL;
×
4772
            if (variant != NULL && variant[0] != '\0') {
×
4773
                if (strcmp(variant, "quantized") == 0) {
×
4774
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4775
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4776
                        result = -1;
×
4777
                    }
4778
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4779
                } else if (strcmp(variant, "encoded") == 0) {
×
4780
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4781
                        result = -1;
×
4782
                    }
4783
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4784
                } else {
4785
                    result = -1;
×
4786
                }
4787
            }
4788
        } else if (strcmp(base, "basic") == 0) {
8✔
4789
            sections |= SIXEL_ASSESSMENT_SECTION_BASIC;
4✔
4790
            if (variant != NULL) {
4!
4791
                result = -1;
×
4792
            }
4793
        } else if (strcmp(base, "performance") == 0) {
5!
4794
            sections |= SIXEL_ASSESSMENT_SECTION_PERFORMANCE;
×
4795
            if (variant != NULL) {
×
4796
                result = -1;
×
4797
            }
4798
        } else if (strcmp(base, "size") == 0) {
4!
4799
            sections |= SIXEL_ASSESSMENT_SECTION_SIZE;
×
4800
            if (variant != NULL) {
×
4801
                result = -1;
×
4802
            }
4803
        } else if (strcmp(base, "quality") == 0) {
4!
4804
            sections |= SIXEL_ASSESSMENT_SECTION_QUALITY;
4✔
4805
            if (variant != NULL && variant[0] != '\0') {
4!
4806
                if (strcmp(variant, "quantized") == 0) {
×
4807
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
4808
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
4809
                        result = -1;
×
4810
                    }
4811
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4812
                } else if (strcmp(variant, "encoded") == 0) {
×
4813
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4814
                        result = -1;
×
4815
                    }
4816
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4817
                } else {
4818
                    result = -1;
×
4819
                }
4820
            } else if (variant != NULL) {
4!
4821
                if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
4822
                    result = -1;
×
4823
                }
4824
                view = SIXEL_ASSESSMENT_VIEW_ENCODED;
×
4825
            }
4826
        } else {
1✔
4827
            result = -1;
×
4828
        }
4829
    }
4830
    if (result == 0) {
8!
4831
        if (sections == 0u) {
8!
4832
            result = -1;
×
4833
        } else {
4834
            if ((sections & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u &&
8!
4835
                    view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
1✔
4836
                sections |= SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
4837
            }
4838
            *out_sections = sections;
8✔
4839
        }
4840
    }
2✔
4841
    free(copy);
8✔
4842
    return result;
8✔
4843
}
2✔
4844

4845

4846
static int
4847
is_png_target(char const *path)
41✔
4848
{
4849
    size_t len;
4850
    int matched;
4851

4852
    /*
4853
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
4854
     *
4855
     *   argument
4856
     *   |
4857
     *   v
4858
     *   .............. . p n g
4859
     *   ^             ^^^^^^^^^
4860
     *   |             +-- case-insensitive suffix comparison
4861
     *   +-- accepts the "png:" inline prefix used for stdout capture
4862
     */
4863

4864
    len = 0;
41✔
4865
    matched = 0;
41✔
4866

4867
    if (path == NULL) {
41!
4868
        return 0;
×
4869
    }
4870

4871
    if (strncmp(path, "png:", 4) == 0) {
41✔
4872
        return path[4] != '\0';
8✔
4873
    }
4874

4875
    len = strlen(path);
33✔
4876
    if (len >= 4) {
33✔
4877
        matched = (tolower((unsigned char)path[len - 4]) == '.')
30✔
4878
            && (tolower((unsigned char)path[len - 3]) == 'p')
11!
4879
            && (tolower((unsigned char)path[len - 2]) == 'n')
4!
4880
            && (tolower((unsigned char)path[len - 1]) == 'g');
32!
4881
    }
8✔
4882

4883
    return matched;
33✔
4884
}
11✔
4885

4886

4887
static char const *
4888
png_target_payload_view(char const *argument)
12✔
4889
{
4890
    /*
4891
     * Inline PNG targets split into either a prefix/payload pair or rely on
4892
     * a simple file-name suffix:
4893
     *
4894
     *   +--------------+------------+-------------+
4895
     *   | form         | payload    | destination |
4896
     *   +--------------+------------+-------------+
4897
     *   | png:         | -          | stdout      |
4898
     *   | png:         | filename   | filesystem  |
4899
     *   | *.png        | filename   | filesystem  |
4900
     *   +--------------+------------+-------------+
4901
     *
4902
     * The caller only needs the payload column, so we expose it here.  When
4903
     * the user omits the prefix we simply echo the original pointer so the
4904
     * caller can copy the value verbatim.
4905
     */
4906
    if (argument == NULL) {
12!
4907
        return NULL;
×
4908
    }
4909
    if (strncmp(argument, "png:", 4) == 0) {
12✔
4910
        return argument + 4;
8✔
4911
    }
4912

4913
    return argument;
4✔
4914
}
3✔
4915

4916
static void
4917
normalise_windows_drive_path(char *path)
12✔
4918
{
4919
#if defined(_WIN32)
4920
    size_t length;
4921

4922
    /*
4923
     * MSYS-like environments forward POSIX-looking absolute paths to native
4924
     * binaries.  When a user writes "/d/..." MSYS converts the command line to
4925
     * UTF-16 and preserves the literal bytes.  The Windows CRT, however,
4926
     * expects "d:/..." or "d:\...".  The tiny state machine below rewrites the
4927
     * leading token so the runtime resolves the drive correctly:
4928
     *
4929
     *   input     normalised
4930
     *   |         |
4931
     *   v         v
4932
     *   / d / ... d : / ...
4933
     *
4934
     * The body keeps the rest of the string intact so UNC paths ("//server")
4935
     * and relative references pass through untouched.
4936
     */
4937

4938
    length = 0u;
4939

4940
    if (path == NULL) {
4941
        return;
4942
    }
4943

4944
    length = strlen(path);
4945
    if (length >= 3u
4946
            && path[0] == '/'
4947
            && ((path[1] >= 'A' && path[1] <= 'Z')
4948
                || (path[1] >= 'a' && path[1] <= 'z'))
4949
            && path[2] == '/') {
4950
        path[0] = path[1];
4951
        path[1] = ':';
4952
    }
4953
#else
4954
    (void)path;
3✔
4955
#endif
4956
}
12✔
4957

4958

4959
static int
4960
is_dev_null_path(char const *path)
×
4961
{
4962
    if (path == NULL || path[0] == '\0') {
×
4963
        return 0;
×
4964
    }
4965
#if defined(_WIN32)
4966
    if (_stricmp(path, "nul") == 0) {
4967
        return 1;
4968
    }
4969
#endif
4970
    return strcmp(path, "/dev/null") == 0;
×
4971
}
4972

4973

4974
static int
4975
sixel_encoder_threads_token_is_auto(char const *text)
×
4976
{
4977
    if (text == NULL) {
×
4978
        return 0;
×
4979
    }
4980

4981
    if ((text[0] == 'a' || text[0] == 'A') &&
×
4982
        (text[1] == 'u' || text[1] == 'U') &&
×
4983
        (text[2] == 't' || text[2] == 'T') &&
×
4984
        (text[3] == 'o' || text[3] == 'O') &&
×
4985
        text[4] == '\0') {
×
4986
        return 1;
×
4987
    }
4988

4989
    return 0;
×
4990
}
4991

4992
static int
4993
sixel_encoder_parse_threads_argument(char const *text, int *value)
×
4994
{
4995
    long parsed;
4996
    char *endptr;
4997

4998
    parsed = 0L;
×
4999
    endptr = NULL;
×
5000

5001
    if (text == NULL || value == NULL) {
×
5002
        return 0;
×
5003
    }
5004

5005
    if (sixel_encoder_threads_token_is_auto(text) != 0) {
×
5006
        *value = 0;
×
5007
        return 1;
×
5008
    }
5009

5010
    errno = 0;
×
5011
    parsed = strtol(text, &endptr, 10);
×
5012
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
×
5013
        return 0;
×
5014
    }
5015

5016
    if (parsed < 1L || parsed > (long)INT_MAX) {
×
5017
        return 0;
×
5018
    }
5019

5020
    *value = (int)parsed;
×
5021
    return 1;
×
5022
}
5023

5024
/* set an option flag to encoder object */
5025
SIXELAPI SIXELSTATUS
5026
sixel_encoder_setopt(
957✔
5027
    sixel_encoder_t /* in */ *encoder,
5028
    int             /* in */ arg,
5029
    char const      /* in */ *value)
5030
{
5031
    SIXELSTATUS status = SIXEL_FALSE;
957✔
5032
    int number;
5033
    int parsed;
5034
    char unit[32];
5035
    char lowered[16];
5036
    size_t len;
5037
    size_t i;
5038
    long parsed_reqcolors;
5039
    char *endptr;
5040
    int forced_palette;
5041
    char *opt_copy;
5042
    char const *drcs_arg_delim;
5043
    char const *drcs_arg_charset;
5044
    char const *drcs_arg_second_delim;
5045
    char const *drcs_arg_path;
5046
    size_t drcs_arg_path_length;
5047
    size_t drcs_segment_length;
5048
    char drcs_segment[32];
5049
    int drcs_mmv_value;
5050
    long drcs_charset_value;
5051
    unsigned int drcs_charset_limit;
5052
    sixel_option_choice_result_t match_result;
5053
    int match_value;
5054
    char match_detail[128];
5055
    char match_message[256];
5056
    int png_argument_has_prefix = 0;
957✔
5057
    char const *png_path_view = NULL;
957✔
5058
    size_t png_path_length;
5059
    char cell_message[256];
5060
    char const *cell_detail;
5061
    unsigned int path_flags;
5062
    char const *mapfile_view;
5063
    int path_check;
5064

5065
    sixel_encoder_ref(encoder);
957✔
5066
    opt_copy = NULL;
957✔
5067
    path_flags = 0u;
957✔
5068
    mapfile_view = NULL;
957✔
5069
    path_check = 0;
957✔
5070

5071
    switch(arg) {
957!
5072
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
30✔
5073
        if (*value == '\0') {
41!
5074
            sixel_helper_set_additional_message(
×
5075
                "no file name specified.");
5076
            status = SIXEL_BAD_ARGUMENT;
×
5077
            goto end;
×
5078
        }
5079
        if (is_png_target(value)) {
41✔
5080
            encoder->output_is_png = 1;
12✔
5081
            png_argument_has_prefix =
12✔
5082
                (value != NULL)
3✔
5083
                && (strncmp(value, "png:", 4) == 0);
12!
5084
            png_path_view = png_target_payload_view(value);
12✔
5085
            if (png_argument_has_prefix
14!
5086
                    && (png_path_view == NULL
9!
5087
                        || png_path_view[0] == '\0')) {
8!
5088
                sixel_helper_set_additional_message(
×
5089
                    "sixel_encoder_setopt: missing target after the \"png:\" "
5090
                    "prefix. use png:- or png:<path> with a non-empty payload."
5091
                );
5092
                status = SIXEL_BAD_ARGUMENT;
×
5093
                goto end;
×
5094
            }
5095
            encoder->output_png_to_stdout =
12✔
5096
                (png_path_view != NULL)
3✔
5097
                && (strcmp(png_path_view, "-") == 0);
12!
5098
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
12✔
5099
            encoder->png_output_path = NULL;
12✔
5100
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
12✔
5101
            encoder->sixel_output_path = NULL;
12✔
5102
            if (! encoder->output_png_to_stdout) {
12!
5103
                /*
5104
                 * +-----------------------------------------+
5105
                 * |  PNG target normalization               |
5106
                 * +-----------------------------------------+
5107
                 * |  Raw input  |  Stored file path         |
5108
                 * |-------------+---------------------------|
5109
                 * |  png:-      |  "-" (stdout sentinel)    |
5110
                 * |  png:/foo   |  "/foo"                   |
5111
                 * +-----------------------------------------+
5112
                 * Strip the "png:" prefix so the decoder can
5113
                 * pass the true filesystem path to libpng
5114
                 * while the CLI retains its shorthand.
5115
                 */
5116
                png_path_view = value;
12✔
5117
                if (strncmp(value, "png:", 4) == 0) {
12✔
5118
                    png_path_view = value + 4;
8✔
5119
                }
2✔
5120
                if (png_path_view[0] == '\0') {
12!
5121
                    sixel_helper_set_additional_message(
×
5122
                        "sixel_encoder_setopt: PNG output path is empty.");
5123
                    status = SIXEL_BAD_ARGUMENT;
×
5124
                    goto end;
×
5125
                }
5126
                png_path_length = strlen(png_path_view);
12✔
5127
                encoder->png_output_path =
12✔
5128
                    (char *)sixel_allocator_malloc(
12✔
5129
                        encoder->allocator, png_path_length + 1u);
3✔
5130
                if (encoder->png_output_path == NULL) {
12!
5131
                    sixel_helper_set_additional_message(
×
5132
                        "sixel_encoder_setopt: sixel_allocator_malloc() "
5133
                        "failed for PNG output path.");
5134
                    status = SIXEL_BAD_ALLOCATION;
×
5135
                    goto end;
×
5136
                }
5137
                if (png_path_view != NULL) {
12!
5138
                    (void)sixel_compat_strcpy(encoder->png_output_path,
15✔
5139
                                              png_path_length + 1u,
3✔
5140
                                              png_path_view);
3✔
5141
                } else {
3✔
5142
                    encoder->png_output_path[0] = '\0';
×
5143
                }
5144
                normalise_windows_drive_path(encoder->png_output_path);
12✔
5145
            }
3✔
5146
        } else {
3✔
5147
            encoder->output_is_png = 0;
29✔
5148
            encoder->output_png_to_stdout = 0;
29✔
5149
            png_argument_has_prefix = 0;
29✔
5150
            png_path_view = NULL;
29✔
5151
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
29✔
5152
            encoder->png_output_path = NULL;
29✔
5153
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
29✔
5154
            encoder->sixel_output_path = NULL;
29✔
5155
            if (encoder->clipboard_output_path != NULL) {
29!
5156
                (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
5157
                sixel_allocator_free(encoder->allocator,
×
5158
                                     encoder->clipboard_output_path);
×
5159
                encoder->clipboard_output_path = NULL;
×
5160
            }
5161
            encoder->clipboard_output_active = 0;
29✔
5162
            encoder->clipboard_output_format[0] = '\0';
29✔
5163
            {
5164
                sixel_clipboard_spec_t clipboard_spec;
5165
                SIXELSTATUS clip_status;
5166
                char *spool_path;
5167
                int spool_fd;
5168

5169
                clipboard_spec.is_clipboard = 0;
29✔
5170
                clipboard_spec.format[0] = '\0';
29✔
5171
                clip_status = SIXEL_OK;
29✔
5172
                spool_path = NULL;
29✔
5173
                spool_fd = (-1);
29✔
5174

5175
                if (sixel_clipboard_parse_spec(value, &clipboard_spec)
29!
5176
                        && clipboard_spec.is_clipboard) {
8!
5177
                    clip_status = clipboard_create_spool(
1✔
5178
                        encoder->allocator,
1✔
5179
                        "clipboard-out",
5180
                        &spool_path,
5181
                        &spool_fd);
5182
                    if (SIXEL_FAILED(clip_status)) {
1!
5183
                        status = clip_status;
×
5184
                        goto end;
×
5185
                    }
5186
                    clipboard_select_format(
1✔
5187
                        encoder->clipboard_output_format,
1✔
5188
                        sizeof(encoder->clipboard_output_format),
5189
                        clipboard_spec.format,
1✔
5190
                        "sixel");
5191
                    if (encoder->outfd
1!
5192
                            && encoder->outfd != STDOUT_FILENO
1!
5193
                            && encoder->outfd != STDERR_FILENO) {
1!
5194
                        (void)sixel_compat_close(encoder->outfd);
×
5195
                    }
5196
                    encoder->outfd = spool_fd;
1✔
5197
                    spool_fd = (-1);
1✔
5198
                    encoder->sixel_output_path = spool_path;
1✔
5199
                    encoder->clipboard_output_path = spool_path;
1✔
5200
                    spool_path = NULL;
1✔
5201
                    encoder->clipboard_output_active = 1;
1✔
5202
                    break;
1✔
5203
                }
5204

5205
                if (spool_fd >= 0) {
28!
5206
                    (void)sixel_compat_close(spool_fd);
×
5207
                }
5208
                if (spool_path != NULL) {
28!
5209
                    sixel_allocator_free(encoder->allocator, spool_path);
×
5210
                }
5211
            }
5212
            if (strcmp(value, "-") != 0) {
28!
5213
                encoder->sixel_output_path = (char *)sixel_allocator_malloc(
49✔
5214
                    encoder->allocator, strlen(value) + 1);
28✔
5215
                if (encoder->sixel_output_path == NULL) {
28!
5216
                    sixel_helper_set_additional_message(
×
5217
                        "sixel_encoder_setopt: malloc() failed for output path.");
5218
                    status = SIXEL_BAD_ALLOCATION;
×
5219
                    goto end;
×
5220
                }
5221
                (void)sixel_compat_strcpy(encoder->sixel_output_path,
35✔
5222
                                          strlen(value) + 1,
28✔
5223
                                          value);
7✔
5224
            }
7✔
5225
        }
5226

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

6190
            if (len >= sizeof(lowered)) {
×
6191
                sixel_helper_set_additional_message(
×
6192
                    "specified working colorspace name is too long.");
6193
                status = SIXEL_BAD_ARGUMENT;
×
6194
                goto end;
×
6195
            }
6196
            for (i = 0; i < len; ++i) {
×
6197
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6198
            }
6199
            lowered[len] = '\0';
×
6200

6201
            match_result = sixel_option_match_choice(
×
6202
                lowered,
6203
                g_option_choices_working_colorspace,
6204
                sizeof(g_option_choices_working_colorspace) /
6205
                sizeof(g_option_choices_working_colorspace[0]),
6206
                &match_value,
6207
                match_detail,
6208
                sizeof(match_detail));
6209
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6210
                encoder->working_colorspace = match_value;
×
6211
            } else {
6212
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6213
                    sixel_option_report_ambiguous_prefix(value,
×
6214
                        match_detail,
6215
                        match_message,
6216
                        sizeof(match_message));
6217
                } else {
6218
                    sixel_option_report_invalid_choice(
×
6219
                        "unsupported working colorspace specified.",
6220
                        match_detail,
6221
                        match_message,
6222
                        sizeof(match_message));
6223
                }
6224
                status = SIXEL_BAD_ARGUMENT;
×
6225
                goto end;
×
6226
            }
6227
        }
6228
        break;
×
6229
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
6230
        if (value == NULL) {
×
6231
            sixel_helper_set_additional_message(
×
6232
                "output-colorspace requires an argument.");
6233
            status = SIXEL_BAD_ARGUMENT;
×
6234
            goto end;
×
6235
        } else {
6236
            len = strlen(value);
×
6237

6238
            if (len >= sizeof(lowered)) {
×
6239
                sixel_helper_set_additional_message(
×
6240
                    "specified output colorspace name is too long.");
6241
                status = SIXEL_BAD_ARGUMENT;
×
6242
                goto end;
×
6243
            }
6244
            for (i = 0; i < len; ++i) {
×
6245
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6246
            }
6247
            lowered[len] = '\0';
×
6248

6249
            match_result = sixel_option_match_choice(
×
6250
                lowered,
6251
                g_option_choices_output_colorspace,
6252
                sizeof(g_option_choices_output_colorspace) /
6253
                sizeof(g_option_choices_output_colorspace[0]),
6254
                &match_value,
6255
                match_detail,
6256
                sizeof(match_detail));
6257
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6258
                encoder->output_colorspace = match_value;
×
6259
            } else {
6260
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6261
                    sixel_option_report_ambiguous_prefix(value,
×
6262
                        match_detail,
6263
                        match_message,
6264
                        sizeof(match_message));
6265
                } else {
6266
                    sixel_option_report_invalid_choice(
×
6267
                        "unsupported output colorspace specified.",
6268
                        match_detail,
6269
                        match_message,
6270
                        sizeof(match_message));
6271
                }
6272
                status = SIXEL_BAD_ARGUMENT;
×
6273
                goto end;
×
6274
            }
6275
        }
6276
        break;
×
6277
    case SIXEL_OPTFLAG_ORMODE:  /* O */
6278
        encoder->ormode = 1;
×
6279
        break;
×
6280
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
9✔
6281
        encoder->complexion = atoi(value);
12✔
6282
        if (encoder->complexion < 1) {
12✔
6283
            sixel_helper_set_additional_message(
4✔
6284
                "complexion parameter must be 1 or more.");
6285
            status = SIXEL_BAD_ARGUMENT;
4✔
6286
            goto end;
4✔
6287
        }
6288
        break;
8✔
6289
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
6290
        encoder->pipe_mode = 1;
×
6291
        break;
×
6292
    case '?':  /* unknown option */
×
6293
    default:
6294
        /* exit if unknown options are specified */
6295
        sixel_helper_set_additional_message(
×
6296
            "unknown option is specified.");
6297
        status = SIXEL_BAD_ARGUMENT;
×
6298
        goto end;
×
6299
    }
6300

6301
    /* detects arguments conflictions */
6302
    if (encoder->reqcolors != (-1)) {
853✔
6303
        switch (encoder->color_option) {
132!
6304
        case SIXEL_COLOR_OPTION_MAPFILE:
6305
            sixel_helper_set_additional_message(
×
6306
                "option -p, --colors conflicts with -m, --mapfile.");
6307
            status = SIXEL_BAD_ARGUMENT;
×
6308
            goto end;
×
6309
        case SIXEL_COLOR_OPTION_MONOCHROME:
3✔
6310
            sixel_helper_set_additional_message(
4✔
6311
                "option -e, --monochrome conflicts with -p, --colors.");
6312
            status = SIXEL_BAD_ARGUMENT;
4✔
6313
            goto end;
4✔
6314
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
3✔
6315
            sixel_helper_set_additional_message(
4✔
6316
                "option -p, --colors conflicts with -I, --high-color.");
6317
            status = SIXEL_BAD_ARGUMENT;
4✔
6318
            goto end;
4✔
6319
        case SIXEL_COLOR_OPTION_BUILTIN:
3✔
6320
            sixel_helper_set_additional_message(
4✔
6321
                "option -p, --colors conflicts with -b, --builtin-palette.");
6322
            status = SIXEL_BAD_ARGUMENT;
4✔
6323
            goto end;
4✔
6324
        default:
90✔
6325
            break;
120✔
6326
        }
6327
    }
30✔
6328

6329
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
6330
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
841✔
6331
        sixel_helper_set_additional_message(
4✔
6332
            "option -8 --8bit-mode conflicts"
6333
            " with -P, --penetrate.");
6334
        status = SIXEL_BAD_ARGUMENT;
4✔
6335
        goto end;
4✔
6336
    }
6337

6338
    status = SIXEL_OK;
837✔
6339

6340
end:
717✔
6341
    if (opt_copy != NULL) {
957!
6342
        sixel_allocator_free(encoder->allocator, opt_copy);
×
6343
    }
6344
    sixel_encoder_unref(encoder);
957✔
6345

6346
    return status;
957✔
6347
}
6348

6349

6350
/* called when image loader component load a image frame */
6351
static SIXELSTATUS
6352
load_image_callback(sixel_frame_t *frame, void *data)
701✔
6353
{
6354
    sixel_encoder_t *encoder;
6355

6356
    encoder = (sixel_encoder_t *)data;
701✔
6357
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
701!
6358
        sixel_frame_ref(frame);
4✔
6359
        encoder->capture_source_frame = frame;
4✔
6360
    }
1✔
6361

6362
    return sixel_encoder_encode_frame(encoder, frame, NULL);
701✔
6363
}
6364

6365
/*
6366
 * Build a tee for encoded-assessment output:
6367
 *
6368
 *     +-------------+     +-------------------+     +------------+
6369
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
6370
 *     +-------------+     +-------------------+     +------------+
6371
 *
6372
 * The tee sink can be stdout or a user-provided file such as /dev/null.
6373
 */
6374
static SIXELSTATUS
6375
copy_file_to_stream(char const *path,
×
6376
                    FILE *stream,
6377
                    sixel_assessment_t *assessment)
6378
{
6379
    FILE *source;
6380
    unsigned char buffer[4096];
6381
    size_t nread;
6382
    size_t nwritten;
6383
    double started_at;
6384
    double finished_at;
6385
    double duration;
6386

6387
    source = NULL;
×
6388
    nread = 0;
×
6389
    nwritten = 0;
×
6390
    started_at = 0.0;
×
6391
    finished_at = 0.0;
×
6392
    duration = 0.0;
×
6393

6394
    source = sixel_compat_fopen(path, "rb");
×
6395
    if (source == NULL) {
×
6396
        sixel_helper_set_additional_message(
×
6397
            "copy_file_to_stream: failed to open assessment staging file.");
6398
        return SIXEL_LIBC_ERROR;
×
6399
    }
6400

6401
    for (;;) {
6402
        nread = fread(buffer, 1, sizeof(buffer), source);
×
6403
        if (nread == 0) {
×
6404
            if (ferror(source)) {
×
6405
                sixel_helper_set_additional_message(
×
6406
                    "copy_file_to_stream: failed while reading assessment staging file.");
6407
                (void) fclose(source);
×
6408
                return SIXEL_LIBC_ERROR;
×
6409
            }
6410
            break;
×
6411
        }
6412
        if (assessment != NULL) {
×
6413
            started_at = sixel_assessment_timer_now();
×
6414
        }
6415
        nwritten = fwrite(buffer, 1, nread, stream);
×
6416
        if (nwritten != nread) {
×
6417
            sixel_helper_set_additional_message(
×
6418
                "img2sixel: failed while copying assessment staging file.");
6419
            (void) fclose(source);
×
6420
            return SIXEL_LIBC_ERROR;
×
6421
        }
6422
        if (assessment != NULL) {
×
6423
            finished_at = sixel_assessment_timer_now();
×
6424
            duration = finished_at - started_at;
×
6425
            if (duration < 0.0) {
×
6426
                duration = 0.0;
×
6427
            }
6428
            sixel_assessment_record_output_write(assessment,
×
6429
                                                 nwritten,
6430
                                                 duration);
6431
        }
6432
    }
6433

6434
    if (fclose(source) != 0) {
×
6435
        sixel_helper_set_additional_message(
×
6436
            "img2sixel: failed to close assessment staging file.");
6437
        return SIXEL_LIBC_ERROR;
×
6438
    }
6439

6440
    return SIXEL_OK;
×
6441
}
6442

6443
typedef struct assessment_json_sink {
6444
    FILE *stream;
6445
    int failed;
6446
} assessment_json_sink_t;
6447

6448
static void
6449
assessment_json_callback(char const *chunk,
56✔
6450
                         size_t length,
6451
                         void *user_data)
6452
{
6453
    assessment_json_sink_t *sink;
6454

6455
    sink = (assessment_json_sink_t *)user_data;
56✔
6456
    if (sink == NULL || sink->stream == NULL) {
56!
6457
        return;
×
6458
    }
6459
    if (sink->failed) {
56!
6460
        return;
×
6461
    }
6462
    if (fwrite(chunk, 1, length, sink->stream) != length) {
56!
6463
        sink->failed = 1;
×
6464
    }
6465
}
14✔
6466

6467
static char *
6468
create_temp_template_with_prefix(sixel_allocator_t *allocator,
14✔
6469
                                 char const *prefix,
6470
                                 size_t *capacity_out)
6471
{
6472
    char const *tmpdir;
6473
    size_t tmpdir_len;
6474
    size_t prefix_len;
6475
    size_t suffix_len;
6476
    size_t template_len;
6477
    char *template_path;
6478
    int needs_separator;
6479
    size_t maximum_tmpdir_len;
6480

6481
    tmpdir = sixel_compat_getenv("TMPDIR");
14✔
6482
#if defined(_WIN32)
6483
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6484
        tmpdir = sixel_compat_getenv("TEMP");
6485
    }
6486
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6487
        tmpdir = sixel_compat_getenv("TMP");
6488
    }
6489
#endif
6490
    if (tmpdir == NULL || tmpdir[0] == '\0') {
14!
6491
#if defined(_WIN32)
6492
        tmpdir = ".";
6493
#else
6494
        tmpdir = "/tmp";
9✔
6495
#endif
6496
    }
6497

6498
    tmpdir_len = strlen(tmpdir);
14✔
6499
    prefix_len = 0u;
14✔
6500
    suffix_len = 0u;
14✔
6501
    if (prefix == NULL) {
14!
6502
        return NULL;
×
6503
    }
6504

6505
    prefix_len = strlen(prefix);
14✔
6506
    suffix_len = prefix_len + strlen("-XXXXXX");
14✔
6507
    maximum_tmpdir_len = (size_t)INT_MAX;
14✔
6508

6509
    if (maximum_tmpdir_len <= suffix_len + 2) {
14!
6510
        return NULL;
×
6511
    }
6512
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
14!
6513
        return NULL;
×
6514
    }
6515
    needs_separator = 1;
14✔
6516
    if (tmpdir_len > 0) {
14!
6517
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
14!
6518
            needs_separator = 0;
5✔
6519
        }
5✔
6520
    }
5✔
6521

6522
    template_len = tmpdir_len + suffix_len + 2;
14✔
6523
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
14✔
6524
    if (template_path == NULL) {
14!
6525
        return NULL;
×
6526
    }
6527

6528
    if (needs_separator) {
14!
6529
#if defined(_WIN32)
6530
        (void) snprintf(template_path, template_len,
6531
                        "%s\\%s-XXXXXX", tmpdir, prefix);
6532
#else
6533
        (void) snprintf(template_path, template_len,
9✔
6534
                        "%s/%s-XXXXXX", tmpdir, prefix);
6535
#endif
6536
    } else {
6537
        (void) snprintf(template_path, template_len,
5✔
6538
                        "%s%s-XXXXXX", tmpdir, prefix);
6539
    }
6540

6541
    if (capacity_out != NULL) {
14!
6542
        *capacity_out = template_len;
14✔
6543
    }
5✔
6544

6545
    return template_path;
14✔
6546
}
5✔
6547

6548

6549
static char *
6550
create_temp_template(sixel_allocator_t *allocator,
12✔
6551
                     size_t *capacity_out)
6552
{
6553
    return create_temp_template_with_prefix(allocator,
15✔
6554
                                            "img2sixel",
6555
                                            capacity_out);
3✔
6556
}
6557

6558

6559
static void
6560
clipboard_select_format(char *dest,
2✔
6561
                        size_t dest_size,
6562
                        char const *format,
6563
                        char const *fallback)
6564
{
6565
    char const *source;
6566
    size_t limit;
6567

6568
    if (dest == NULL || dest_size == 0u) {
2!
6569
        return;
×
6570
    }
6571

6572
    source = fallback;
2✔
6573
    if (format != NULL && format[0] != '\0') {
2!
6574
        source = format;
×
6575
    }
6576

6577
    limit = dest_size - 1u;
2✔
6578
    if (limit == 0u) {
2!
6579
        dest[0] = '\0';
×
6580
        return;
×
6581
    }
6582

6583
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
2✔
6584
}
2✔
6585

6586

6587
static SIXELSTATUS
6588
clipboard_create_spool(sixel_allocator_t *allocator,
2✔
6589
                       char const *prefix,
6590
                       char **path_out,
6591
                       int *fd_out)
6592
{
6593
    SIXELSTATUS status;
6594
    char *template_path;
6595
    size_t template_capacity;
6596
    int open_flags;
6597
    int fd;
6598
    char *tmpname_result;
6599

6600
    status = SIXEL_FALSE;
2✔
6601
    template_path = NULL;
2✔
6602
    template_capacity = 0u;
2✔
6603
    open_flags = 0;
2✔
6604
    fd = (-1);
2✔
6605
    tmpname_result = NULL;
2✔
6606

6607
    template_path = create_temp_template_with_prefix(allocator,
4✔
6608
                                                     prefix,
2✔
6609
                                                     &template_capacity);
6610
    if (template_path == NULL) {
2!
6611
        sixel_helper_set_additional_message(
×
6612
            "clipboard: failed to allocate spool template.");
6613
        status = SIXEL_BAD_ALLOCATION;
×
6614
        goto end;
×
6615
    }
6616

6617
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
2!
6618
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
6619
        tmpname_result = tmpnam(template_path);
×
6620
        if (tmpname_result == NULL) {
×
6621
            sixel_helper_set_additional_message(
×
6622
                "clipboard: failed to reserve spool template.");
6623
            status = SIXEL_LIBC_ERROR;
×
6624
            goto end;
×
6625
        }
6626
        template_capacity = strlen(template_path) + 1u;
×
6627
    }
6628

6629
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
2✔
6630
#if defined(O_EXCL)
6631
    open_flags |= O_EXCL;
2✔
6632
#endif
6633
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
2✔
6634
    if (fd < 0) {
2!
6635
        sixel_helper_set_additional_message(
×
6636
            "clipboard: failed to open spool file.");
6637
        status = SIXEL_LIBC_ERROR;
×
6638
        goto end;
×
6639
    }
6640

6641
    *path_out = template_path;
2✔
6642
    if (fd_out != NULL) {
2!
6643
        *fd_out = fd;
1✔
6644
        fd = (-1);
1✔
6645
    }
1✔
6646

6647
    template_path = NULL;
2✔
6648
    status = SIXEL_OK;
2✔
6649

6650
end:
6651
    if (fd >= 0) {
2!
6652
        (void)sixel_compat_close(fd);
1✔
6653
    }
1✔
6654
    if (template_path != NULL) {
2!
6655
        sixel_allocator_free(allocator, template_path);
×
6656
    }
6657

6658
    return status;
2✔
6659
}
6660

6661

6662
static SIXELSTATUS
6663
clipboard_write_file(char const *path,
1✔
6664
                     unsigned char const *data,
6665
                     size_t size)
6666
{
6667
    FILE *stream;
6668
    size_t written;
6669

6670
    if (path == NULL) {
1!
6671
        sixel_helper_set_additional_message(
×
6672
            "clipboard: spool path is null.");
6673
        return SIXEL_BAD_ARGUMENT;
×
6674
    }
6675

6676
    stream = sixel_compat_fopen(path, "wb");
1✔
6677
    if (stream == NULL) {
1!
6678
        sixel_helper_set_additional_message(
×
6679
            "clipboard: failed to open spool file for write.");
6680
        return SIXEL_LIBC_ERROR;
×
6681
    }
6682

6683
    written = 0u;
1✔
6684
    if (size > 0u && data != NULL) {
1!
6685
        written = fwrite(data, 1u, size, stream);
1✔
6686
        if (written != size) {
1!
6687
            (void)fclose(stream);
×
6688
            sixel_helper_set_additional_message(
×
6689
                "clipboard: failed to write spool payload.");
6690
            return SIXEL_LIBC_ERROR;
×
6691
        }
6692
    }
1✔
6693

6694
    if (fclose(stream) != 0) {
1!
6695
        sixel_helper_set_additional_message(
×
6696
            "clipboard: failed to close spool file after write.");
6697
        return SIXEL_LIBC_ERROR;
×
6698
    }
6699

6700
    return SIXEL_OK;
1✔
6701
}
1✔
6702

6703

6704
static SIXELSTATUS
6705
clipboard_read_file(char const *path,
1✔
6706
                    unsigned char **data,
6707
                    size_t *size)
6708
{
6709
    FILE *stream;
6710
    long seek_result;
6711
    long file_size;
6712
    unsigned char *buffer;
6713
    size_t read_size;
6714

6715
    if (data == NULL || size == NULL) {
1!
6716
        sixel_helper_set_additional_message(
×
6717
            "clipboard: read buffer pointers are null.");
6718
        return SIXEL_BAD_ARGUMENT;
×
6719
    }
6720

6721
    *data = NULL;
1✔
6722
    *size = 0u;
1✔
6723

6724
    if (path == NULL) {
1!
6725
        sixel_helper_set_additional_message(
×
6726
            "clipboard: spool path is null.");
6727
        return SIXEL_BAD_ARGUMENT;
×
6728
    }
6729

6730
    stream = sixel_compat_fopen(path, "rb");
1✔
6731
    if (stream == NULL) {
1!
6732
        sixel_helper_set_additional_message(
×
6733
            "clipboard: failed to open spool file for read.");
6734
        return SIXEL_LIBC_ERROR;
×
6735
    }
6736

6737
    seek_result = fseek(stream, 0L, SEEK_END);
1✔
6738
    if (seek_result != 0) {
1!
6739
        (void)fclose(stream);
×
6740
        sixel_helper_set_additional_message(
×
6741
            "clipboard: failed to seek spool file.");
6742
        return SIXEL_LIBC_ERROR;
×
6743
    }
6744

6745
    file_size = ftell(stream);
1✔
6746
    if (file_size < 0) {
1!
6747
        (void)fclose(stream);
×
6748
        sixel_helper_set_additional_message(
×
6749
            "clipboard: failed to determine spool size.");
6750
        return SIXEL_LIBC_ERROR;
×
6751
    }
6752

6753
    seek_result = fseek(stream, 0L, SEEK_SET);
1✔
6754
    if (seek_result != 0) {
1!
6755
        (void)fclose(stream);
×
6756
        sixel_helper_set_additional_message(
×
6757
            "clipboard: failed to rewind spool file.");
6758
        return SIXEL_LIBC_ERROR;
×
6759
    }
6760

6761
    if (file_size == 0) {
1!
6762
        buffer = NULL;
×
6763
        read_size = 0u;
×
6764
    } else {
6765
        buffer = (unsigned char *)malloc((size_t)file_size);
1✔
6766
        if (buffer == NULL) {
1!
6767
            (void)fclose(stream);
×
6768
            sixel_helper_set_additional_message(
×
6769
                "clipboard: malloc() failed for spool payload.");
6770
            return SIXEL_BAD_ALLOCATION;
×
6771
        }
6772
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
1✔
6773
        if (read_size != (size_t)file_size) {
1!
6774
            free(buffer);
×
6775
            (void)fclose(stream);
×
6776
            sixel_helper_set_additional_message(
×
6777
                "clipboard: failed to read spool payload.");
6778
            return SIXEL_LIBC_ERROR;
×
6779
        }
6780
    }
6781

6782
    if (fclose(stream) != 0) {
1!
6783
        if (buffer != NULL) {
×
6784
            free(buffer);
×
6785
        }
6786
        sixel_helper_set_additional_message(
×
6787
            "clipboard: failed to close spool file after read.");
6788
        return SIXEL_LIBC_ERROR;
×
6789
    }
6790

6791
    *data = buffer;
1✔
6792
    *size = read_size;
1✔
6793

6794
    return SIXEL_OK;
1✔
6795
}
1✔
6796

6797

6798
static SIXELSTATUS
6799
write_png_from_sixel(char const *sixel_path, char const *output_path)
12✔
6800
{
6801
    SIXELSTATUS status;
6802
    sixel_decoder_t *decoder;
6803

6804
    status = SIXEL_FALSE;
12✔
6805
    decoder = NULL;
12✔
6806

6807
    status = sixel_decoder_new(&decoder, NULL);
12✔
6808
    if (SIXEL_FAILED(status)) {
12!
6809
        goto end;
×
6810
    }
6811

6812
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
12✔
6813
    if (SIXEL_FAILED(status)) {
12!
6814
        goto end;
×
6815
    }
6816

6817
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
12✔
6818
    if (SIXEL_FAILED(status)) {
12!
6819
        goto end;
×
6820
    }
6821

6822
    status = sixel_decoder_decode(decoder);
12✔
6823

6824
end:
9✔
6825
    sixel_decoder_unref(decoder);
12✔
6826

6827
    return status;
12✔
6828
}
6829

6830

6831
/* load source data from specified file and encode it to SIXEL format
6832
 * output to encoder->outfd */
6833
SIXELAPI SIXELSTATUS
6834
sixel_encoder_encode(
581✔
6835
    sixel_encoder_t *encoder,   /* encoder object */
6836
    char const      *filename)  /* input filename */
6837
{
6838
    SIXELSTATUS status = SIXEL_FALSE;
581✔
6839
    SIXELSTATUS palette_status = SIXEL_OK;
581✔
6840
    int fuse_palette = 1;
581✔
6841
    sixel_loader_t *loader = NULL;
581✔
6842
    sixel_allocator_t *assessment_allocator = NULL;
581✔
6843
    sixel_allocator_t *encode_allocator = NULL;
581✔
6844
    sixel_frame_t *assessment_source_frame = NULL;
581✔
6845
    sixel_frame_t *assessment_target_frame = NULL;
581✔
6846
    sixel_frame_t *assessment_expanded_frame = NULL;
581✔
6847
    unsigned int assessment_section_mask =
581✔
6848
        encoder->assessment_sections & SIXEL_ASSESSMENT_SECTION_MASK;
581✔
6849
    int assessment_need_source_capture = 0;
581✔
6850
    int assessment_need_quantized_capture = 0;
581✔
6851
    int assessment_need_quality = 0;
581✔
6852
    int assessment_quality_quantized = 0;
581✔
6853
    assessment_json_sink_t assessment_sink;
6854
    FILE *assessment_json_file = NULL;
581✔
6855
    FILE *assessment_forward_stream = NULL;
581✔
6856
    int assessment_json_owned = 0;
581✔
6857
    char *assessment_temp_path = NULL;
581✔
6858
    size_t assessment_temp_capacity = 0u;
581✔
6859
    char *assessment_tmpnam_result = NULL;
581✔
6860
    sixel_assessment_spool_mode_t assessment_spool_mode
581✔
6861
        = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
6862
    char *assessment_forward_path = NULL;
581✔
6863
    size_t assessment_output_bytes;
6864
#if HAVE_SYS_STAT_H
6865
    struct stat assessment_stat;
6866
    int assessment_stat_result;
6867
    char const *assessment_size_path = NULL;
581✔
6868
#endif
6869
    char const *png_final_path = NULL;
581✔
6870
    char *png_temp_path = NULL;
581✔
6871
    size_t png_temp_capacity = 0u;
581✔
6872
    char *png_tmpnam_result = NULL;
581✔
6873
    int png_open_flags = 0;
581✔
6874
    int spool_required;
6875
    sixel_clipboard_spec_t clipboard_spec;
6876
    char clipboard_input_format[32];
6877
    char *clipboard_input_path;
6878
    unsigned char *clipboard_blob;
6879
    size_t clipboard_blob_size;
6880
    SIXELSTATUS clipboard_status;
6881
    char const *effective_filename;
6882
    unsigned int path_flags;
6883
    int path_check;
6884
    sixel_logger_t logger;
6885
    int logger_prepared;
6886

6887
    clipboard_input_format[0] = '\0';
581✔
6888
    clipboard_input_path = NULL;
581✔
6889
    clipboard_blob = NULL;
581✔
6890
    clipboard_blob_size = 0u;
581✔
6891
    clipboard_status = SIXEL_OK;
581✔
6892
    effective_filename = filename;
581✔
6893
    path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
581✔
6894
        SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
6895
        SIXEL_OPTION_PATH_ALLOW_REMOTE;
6896
    path_check = 0;
581✔
6897
    logger_prepared = 0;
581✔
6898
    sixel_logger_init(&logger);
581✔
6899
    sixel_logger_prepare_env(&logger);
581✔
6900
    logger_prepared = logger.active;
581✔
6901
    if (encoder != NULL) {
581!
6902
        encoder->logger = &logger;
581✔
6903
        encoder->parallel_job_id = -1;
581✔
6904
    }
146✔
6905

6906
    if (filename != NULL) {
581✔
6907
        path_check = sixel_option_validate_filesystem_path(
393✔
6908
            filename,
99✔
6909
            filename,
99✔
6910
            path_flags);
99✔
6911
        if (path_check != 0) {
393!
6912
            status = SIXEL_BAD_ARGUMENT;
×
6913
            goto end;
×
6914
        }
6915
    }
99✔
6916

6917
    if (encoder != NULL) {
581!
6918
        encode_allocator = encoder->allocator;
581✔
6919
        if (encode_allocator != NULL) {
581!
6920
            /*
6921
             * Hold a reference until cleanup so worker side-effects or loader
6922
             * destruction cannot release the allocator before sequential
6923
             * teardown finishes using it.
6924
             */
6925
            sixel_allocator_ref(encode_allocator);
581✔
6926
        }
146✔
6927
    }
146✔
6928

6929
    clipboard_spec.is_clipboard = 0;
581✔
6930
    clipboard_spec.format[0] = '\0';
581✔
6931
    if (effective_filename != NULL
582!
6932
            && sixel_clipboard_parse_spec(effective_filename,
440!
6933
                                          &clipboard_spec)
6934
            && clipboard_spec.is_clipboard) {
99!
6935
        clipboard_select_format(clipboard_input_format,
2✔
6936
                                sizeof(clipboard_input_format),
6937
                                clipboard_spec.format,
1✔
6938
                                "sixel");
6939
        clipboard_status = sixel_clipboard_read(
1✔
6940
            clipboard_input_format,
1✔
6941
            &clipboard_blob,
6942
            &clipboard_blob_size,
6943
            encoder->allocator);
1✔
6944
        if (SIXEL_FAILED(clipboard_status)) {
1!
6945
            status = clipboard_status;
×
6946
            goto end;
×
6947
        }
6948
        clipboard_status = clipboard_create_spool(
1✔
6949
            encoder->allocator,
1✔
6950
            "clipboard-in",
6951
            &clipboard_input_path,
6952
            NULL);
6953
        if (SIXEL_FAILED(clipboard_status)) {
1!
6954
            status = clipboard_status;
×
6955
            goto end;
×
6956
        }
6957
        clipboard_status = clipboard_write_file(
1✔
6958
            clipboard_input_path,
1✔
6959
            clipboard_blob,
1✔
6960
            clipboard_blob_size);
1✔
6961
        if (SIXEL_FAILED(clipboard_status)) {
1!
6962
            status = clipboard_status;
×
6963
            goto end;
×
6964
        }
6965
        if (clipboard_blob != NULL) {
1!
6966
            free(clipboard_blob);
1✔
6967
            clipboard_blob = NULL;
1✔
6968
        }
1✔
6969
        effective_filename = clipboard_input_path;
1✔
6970
    }
1✔
6971

6972
    if (assessment_section_mask != SIXEL_ASSESSMENT_SECTION_NONE) {
581✔
6973
        status = sixel_allocator_new(&assessment_allocator,
4✔
6974
                                     malloc,
6975
                                     calloc,
6976
                                     realloc,
6977
                                     free);
6978
        if (SIXEL_FAILED(status) || assessment_allocator == NULL) {
4!
6979
            goto end;
×
6980
        }
6981
        status = sixel_assessment_new(&encoder->assessment_observer,
5✔
6982
                                       assessment_allocator);
1✔
6983
        if (SIXEL_FAILED(status) || encoder->assessment_observer == NULL) {
4!
6984
            goto end;
×
6985
        }
6986
        sixel_assessment_select_sections(encoder->assessment_observer,
5✔
6987
                                         encoder->assessment_sections);
1✔
6988
        sixel_assessment_attach_encoder(encoder->assessment_observer,
5✔
6989
                                        encoder);
1✔
6990
        assessment_need_quality =
4✔
6991
            (assessment_section_mask & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u;
4✔
6992
        assessment_quality_quantized =
4✔
6993
            (encoder->assessment_sections & SIXEL_ASSESSMENT_VIEW_QUANTIZED) != 0u;
4✔
6994
        assessment_need_quantized_capture =
4✔
6995
            ((assessment_section_mask &
4✔
6996
              (SIXEL_ASSESSMENT_SECTION_BASIC |
6997
               SIXEL_ASSESSMENT_SECTION_SIZE)) != 0u) ||
4!
6998
            assessment_quality_quantized;
6999
        assessment_need_source_capture =
4✔
7000
            (assessment_section_mask &
4✔
7001
             (SIXEL_ASSESSMENT_SECTION_BASIC |
7002
              SIXEL_ASSESSMENT_SECTION_PERFORMANCE |
7003
              SIXEL_ASSESSMENT_SECTION_SIZE |
7004
              SIXEL_ASSESSMENT_SECTION_QUALITY)) != 0u;
4✔
7005
        if (assessment_need_quality && !assessment_quality_quantized &&
4!
7006
                encoder->output_is_png) {
×
7007
            sixel_helper_set_additional_message(
×
7008
                "sixel_encoder_setopt: encoded quality assessment requires SIXEL output.");
7009
            status = SIXEL_BAD_ARGUMENT;
×
7010
            goto end;
×
7011
        }
7012
        status = sixel_encoder_enable_source_capture(encoder,
5✔
7013
                                                     assessment_need_source_capture);
1✔
7014
        if (SIXEL_FAILED(status)) {
4!
7015
            goto end;
×
7016
        }
7017
        status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_STATIC, NULL);
4✔
7018
        if (SIXEL_FAILED(status)) {
4!
7019
            goto end;
×
7020
        }
7021
        if (assessment_need_quantized_capture) {
4!
7022
            status = sixel_encoder_enable_quantized_capture(encoder, 1);
4✔
7023
            if (SIXEL_FAILED(status)) {
4!
7024
                goto end;
×
7025
            }
7026
        }
1✔
7027
        assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
4✔
7028
        spool_required = 0;
4✔
7029
        if (assessment_need_quality && !assessment_quality_quantized) {
4!
7030
            if (encoder->sixel_output_path == NULL) {
×
7031
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
7032
                spool_required = 1;
×
7033
            } else if (strcmp(encoder->sixel_output_path, "-") == 0) {
×
7034
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
7035
                spool_required = 1;
×
7036
                free(encoder->sixel_output_path);
×
7037
                encoder->sixel_output_path = NULL;
×
7038
            } else if (is_dev_null_path(encoder->sixel_output_path)) {
×
7039
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_PATH;
×
7040
                spool_required = 1;
×
7041
                assessment_forward_path = encoder->sixel_output_path;
×
7042
                encoder->sixel_output_path = NULL;
×
7043
            }
7044
        }
7045
        if (spool_required) {
4!
7046
            assessment_temp_capacity = 0u;
×
7047
            assessment_tmpnam_result = NULL;
×
7048
            assessment_temp_path = create_temp_template(encoder->allocator,
×
7049
                                                        &assessment_temp_capacity);
7050
            if (assessment_temp_path == NULL) {
×
7051
                sixel_helper_set_additional_message(
×
7052
                    "sixel_encoder_encode: sixel_allocator_malloc() "
7053
                    "failed for assessment staging path.");
7054
                status = SIXEL_BAD_ALLOCATION;
×
7055
                goto end;
×
7056
            }
7057
            if (sixel_compat_mktemp(assessment_temp_path,
×
7058
                                    assessment_temp_capacity) != 0) {
7059
                /* Fall back to tmpnam() when mktemp variants are unavailable. */
7060
                assessment_tmpnam_result = tmpnam(assessment_temp_path);
×
7061
                if (assessment_tmpnam_result == NULL) {
×
7062
                    sixel_helper_set_additional_message(
×
7063
                        "sixel_encoder_encode: mktemp() failed for assessment staging file.");
7064
                    status = SIXEL_RUNTIME_ERROR;
×
7065
                    goto end;
×
7066
                }
7067
                assessment_temp_capacity = strlen(assessment_temp_path) + 1u;
×
7068
            }
7069
            status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_OUTFILE,
×
7070
                                          assessment_temp_path);
7071
            if (SIXEL_FAILED(status)) {
×
7072
                goto end;
×
7073
            }
7074
            encoder->sixel_output_path = (char *)sixel_allocator_malloc(
×
7075
                encoder->allocator, strlen(assessment_temp_path) + 1);
×
7076
            if (encoder->sixel_output_path == NULL) {
×
7077
                sixel_helper_set_additional_message(
×
7078
                    "sixel_encoder_encode: malloc() failed for assessment staging name.");
7079
                status = SIXEL_BAD_ALLOCATION;
×
7080
                goto end;
×
7081
            }
7082
            (void)sixel_compat_strcpy(encoder->sixel_output_path,
×
7083
                                      strlen(assessment_temp_path) + 1,
×
7084
                                      assessment_temp_path);
7085
        }
7086

7087
    }
1✔
7088

7089
    if (encoder->output_is_png) {
581✔
7090
        png_temp_capacity = 0u;
12✔
7091
        png_tmpnam_result = NULL;
12✔
7092
        png_temp_path = create_temp_template(encoder->allocator,
12✔
7093
                                             &png_temp_capacity);
7094
        if (png_temp_path == NULL) {
12!
7095
            sixel_helper_set_additional_message(
×
7096
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
7097
            status = SIXEL_BAD_ALLOCATION;
×
7098
            goto end;
×
7099
        }
7100
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
12!
7101
            /* Fall back to tmpnam() when mktemp variants are unavailable. */
7102
            png_tmpnam_result = tmpnam(png_temp_path);
×
7103
            if (png_tmpnam_result == NULL) {
×
7104
                sixel_helper_set_additional_message(
×
7105
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
7106
                status = SIXEL_RUNTIME_ERROR;
×
7107
                goto end;
×
7108
            }
7109
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
7110
        }
7111
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
12!
7112
            (void)sixel_compat_close(encoder->outfd);
12✔
7113
        }
3✔
7114
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
12✔
7115
#if defined(O_EXCL)
7116
        png_open_flags |= O_EXCL;
12✔
7117
#endif
7118
        encoder->outfd = sixel_compat_open(png_temp_path,
15✔
7119
                                           png_open_flags,
3✔
7120
                                           S_IRUSR | S_IWUSR);
7121
        if (encoder->outfd < 0) {
12!
7122
            sixel_helper_set_additional_message(
×
7123
                "sixel_encoder_encode: failed to create the PNG target file.");
7124
            status = SIXEL_LIBC_ERROR;
×
7125
            goto end;
×
7126
        }
7127
    }
3✔
7128

7129
    if (encoder == NULL) {
581!
7130
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7131
#  pragma GCC diagnostic push
7132
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7133
#endif
7134
        encoder = sixel_encoder_create();
×
7135
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7136
#  pragma GCC diagnostic pop
7137
#endif
7138
        if (encoder == NULL) {
×
7139
            sixel_helper_set_additional_message(
×
7140
                "sixel_encoder_encode: sixel_encoder_create() failed.");
7141
            status = SIXEL_BAD_ALLOCATION;
×
7142
            goto end;
×
7143
        }
7144
    } else {
7145
        sixel_encoder_ref(encoder);
581✔
7146
    }
7147

7148
    if (encode_allocator == NULL && encoder != NULL) {
581!
7149
        encode_allocator = encoder->allocator;
×
7150
        if (encode_allocator != NULL) {
×
7151
            /* Ensure the allocator stays valid after lazy encoder creation. */
7152
            sixel_allocator_ref(encode_allocator);
×
7153
        }
7154
    }
7155

7156
    if (encoder->assessment_observer != NULL) {
581✔
7157
        sixel_assessment_stage_transition(
4✔
7158
            encoder->assessment_observer,
4✔
7159
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
7160
    }
1✔
7161
    encoder->last_loader_name[0] = '\0';
581✔
7162
    encoder->last_source_path[0] = '\0';
581✔
7163
    encoder->last_input_bytes = 0u;
581✔
7164

7165
    /* if required color is not set, set the max value */
7166
    if (encoder->reqcolors == (-1)) {
581✔
7167
        encoder->reqcolors = SIXEL_PALETTE_MAX;
557✔
7168
    }
140✔
7169

7170
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
581!
7171
        sixel_frame_unref(encoder->capture_source_frame);
×
7172
        encoder->capture_source_frame = NULL;
×
7173
    }
7174

7175
    /* if required color is less then 2, set the min value */
7176
    if (encoder->reqcolors < 2) {
581✔
7177
        encoder->reqcolors = SIXEL_PALETTE_MIN;
4✔
7178
    }
1✔
7179

7180
    /* if color space option is not set, choose RGB color space */
7181
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
581✔
7182
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
557✔
7183
    }
140✔
7184

7185
    /* if color option is not default value, prohibit to read
7186
       the file as a paletted image */
7187
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
581✔
7188
        fuse_palette = 0;
128✔
7189
    }
32✔
7190

7191
    /* if scaling options are set, prohibit to read the file as
7192
       a paletted image */
7193
    if (encoder->percentwidth > 0 ||
704✔
7194
        encoder->percentheight > 0 ||
565✔
7195
        encoder->pixelwidth > 0 ||
557✔
7196
        encoder->pixelheight > 0) {
522✔
7197
        fuse_palette = 0;
132✔
7198
    }
33✔
7199

7200
reload:
435✔
7201

7202
    sixel_helper_set_loader_trace(encoder->verbose);
581✔
7203
    sixel_helper_set_thumbnail_size_hint(
581✔
7204
        sixel_encoder_thumbnail_hint(encoder));
146✔
7205

7206
    status = sixel_loader_new(&loader, encoder->allocator);
581✔
7207
    if (SIXEL_FAILED(status)) {
581!
7208
        goto load_end;
×
7209
    }
7210

7211
    status = sixel_loader_setopt(loader,
727✔
7212
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
7213
                                 &encoder->fstatic);
581✔
7214
    if (SIXEL_FAILED(status)) {
581!
7215
        goto load_end;
×
7216
    }
7217

7218
    status = sixel_loader_setopt(loader,
581✔
7219
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
7220
                                 &fuse_palette);
7221
    if (SIXEL_FAILED(status)) {
581!
7222
        goto load_end;
×
7223
    }
7224

7225
    status = sixel_loader_setopt(loader,
727✔
7226
                                 SIXEL_LOADER_OPTION_REQCOLORS,
7227
                                 &encoder->reqcolors);
581✔
7228
    if (SIXEL_FAILED(status)) {
581!
7229
        goto load_end;
×
7230
    }
7231

7232
    status = sixel_loader_setopt(loader,
727✔
7233
                                 SIXEL_LOADER_OPTION_BGCOLOR,
7234
                                 encoder->bgcolor);
581✔
7235
    if (SIXEL_FAILED(status)) {
581!
7236
        goto load_end;
×
7237
    }
7238

7239
    status = sixel_loader_setopt(loader,
727✔
7240
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
7241
                                 &encoder->loop_mode);
581✔
7242
    if (SIXEL_FAILED(status)) {
581!
7243
        goto load_end;
×
7244
    }
7245

7246
    status = sixel_loader_setopt(loader,
727✔
7247
                                 SIXEL_LOADER_OPTION_INSECURE,
7248
                                 &encoder->finsecure);
581✔
7249
    if (SIXEL_FAILED(status)) {
581!
7250
        goto load_end;
×
7251
    }
7252

7253
    status = sixel_loader_setopt(loader,
727✔
7254
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
7255
                                 encoder->cancel_flag);
581✔
7256
    if (SIXEL_FAILED(status)) {
581!
7257
        goto load_end;
×
7258
    }
7259

7260
    status = sixel_loader_setopt(loader,
727✔
7261
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
7262
                                 encoder->loader_order);
581✔
7263
    if (SIXEL_FAILED(status)) {
581!
7264
        goto load_end;
×
7265
    }
7266

7267
    status = sixel_loader_setopt(loader,
727✔
7268
                                 SIXEL_LOADER_OPTION_CONTEXT,
7269
                                 encoder);
146✔
7270
    if (SIXEL_FAILED(status)) {
581!
7271
        goto load_end;
×
7272
    }
7273

7274
    /*
7275
     * Wire the optional assessment observer into the loader.
7276
     *
7277
     * The observer travels separately from the callback context so mapfile
7278
     * palette probes and other callbacks can keep using arbitrary structs.
7279
     */
7280
    status = sixel_loader_setopt(loader,
727✔
7281
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
7282
                                 encoder->assessment_observer);
581✔
7283
    if (SIXEL_FAILED(status)) {
581!
7284
        goto load_end;
×
7285
    }
7286

7287
    status = sixel_loader_load_file(loader,
727✔
7288
                                    effective_filename,
146✔
7289
                                    load_image_callback);
7290
    if (status != SIXEL_OK) {
581✔
7291
        goto load_end;
20✔
7292
    }
7293
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
561✔
7294
    if (sixel_loader_get_last_success_name(loader) != NULL) {
561!
7295
        (void)snprintf(encoder->last_loader_name,
561✔
7296
                       sizeof(encoder->last_loader_name),
7297
                       "%s",
7298
                       sixel_loader_get_last_success_name(loader));
7299
    } else {
141✔
7300
        encoder->last_loader_name[0] = '\0';
×
7301
    }
7302
    if (sixel_loader_get_last_source_path(loader) != NULL) {
561✔
7303
        (void)snprintf(encoder->last_source_path,
377✔
7304
                       sizeof(encoder->last_source_path),
7305
                       "%s",
7306
                       sixel_loader_get_last_source_path(loader));
7307
    } else {
95✔
7308
        encoder->last_source_path[0] = '\0';
184✔
7309
    }
7310
    if (encoder->assessment_observer != NULL) {
562✔
7311
        sixel_assessment_record_loader(encoder->assessment_observer,
5✔
7312
                                       encoder->last_source_path,
4✔
7313
                                       encoder->last_loader_name,
4✔
7314
                                       encoder->last_input_bytes);
1✔
7315
    }
1✔
7316

7317
load_end:
417✔
7318
    sixel_loader_unref(loader);
581✔
7319
    loader = NULL;
581✔
7320

7321
    if (status != SIXEL_OK) {
581✔
7322
        goto end;
20✔
7323
    }
7324

7325
    palette_status = sixel_encoder_emit_palette_output(encoder);
561✔
7326
    if (SIXEL_FAILED(palette_status)) {
561!
7327
        status = palette_status;
×
7328
        goto end;
×
7329
    }
7330

7331
    if (encoder->pipe_mode) {
561!
7332
#if HAVE_CLEARERR
7333
        clearerr(stdin);
×
7334
#endif  /* HAVE_FSEEK */
7335
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
7336
            status = sixel_tty_wait_stdin(1000000);
×
7337
            if (SIXEL_FAILED(status)) {
×
7338
                goto end;
×
7339
            }
7340
            if (status != SIXEL_OK) {
×
7341
                break;
×
7342
            }
7343
        }
7344
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
7345
            goto reload;
×
7346
        }
7347
    }
7348

7349
    if (encoder->assessment_observer) {
561✔
7350
        if (assessment_allocator == NULL || encoder->assessment_observer == NULL) {
4!
7351
            status = SIXEL_RUNTIME_ERROR;
×
7352
            goto end;
×
7353
        }
7354
        if (assessment_need_source_capture) {
4!
7355
            status = sixel_encoder_copy_source_frame(encoder,
4✔
7356
                                                     &assessment_source_frame);
7357
            if (SIXEL_FAILED(status)) {
4!
7358
                goto end;
×
7359
            }
7360
            sixel_assessment_record_source_frame(encoder->assessment_observer,
5✔
7361
                                                 assessment_source_frame);
1✔
7362
        }
1✔
7363
        if (assessment_need_quality) {
4!
7364
            if (assessment_quality_quantized) {
×
7365
                status = sixel_encoder_copy_quantized_frame(
×
7366
                    encoder, assessment_allocator, &assessment_target_frame);
7367
                if (SIXEL_FAILED(status)) {
×
7368
                    goto end;
×
7369
                }
7370
                status = sixel_assessment_expand_quantized_frame(
×
7371
                    assessment_target_frame,
7372
                    assessment_allocator,
7373
                    &assessment_expanded_frame);
7374
                if (SIXEL_FAILED(status)) {
×
7375
                    goto end;
×
7376
                }
7377
                sixel_frame_unref(assessment_target_frame);
×
7378
                assessment_target_frame = assessment_expanded_frame;
×
7379
                assessment_expanded_frame = NULL;
×
7380
                sixel_assessment_record_quantized_capture(
×
7381
                    encoder->assessment_observer, encoder);
×
7382
            } else {
7383
                status = sixel_assessment_load_single_frame(
×
7384
                    encoder->sixel_output_path,
×
7385
                    assessment_allocator,
7386
                    &assessment_target_frame);
7387
                if (SIXEL_FAILED(status)) {
×
7388
                    goto end;
×
7389
                }
7390
            }
7391
            if (!assessment_quality_quantized &&
×
7392
                    assessment_need_quantized_capture) {
7393
                sixel_assessment_record_quantized_capture(
×
7394
                    encoder->assessment_observer, encoder);
×
7395
            }
7396
        } else if (assessment_need_quantized_capture) {
4!
7397
            sixel_assessment_record_quantized_capture(
4✔
7398
                encoder->assessment_observer, encoder);
4✔
7399
        }
1✔
7400
        if (encoder->assessment_observer != NULL &&
4!
7401
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
1✔
7402
            sixel_assessment_stage_transition(
×
7403
                encoder->assessment_observer,
×
7404
                SIXEL_ASSESSMENT_STAGE_OUTPUT);
7405
        }
7406
        if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
4!
7407
            status = copy_file_to_stream(assessment_temp_path,
×
7408
                                         stdout,
7409
                                         encoder->assessment_observer);
×
7410
            if (SIXEL_FAILED(status)) {
×
7411
                goto end;
×
7412
            }
7413
        } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
4!
7414
            if (assessment_forward_path == NULL) {
×
7415
                sixel_helper_set_additional_message(
×
7416
                    "sixel_encoder_encode: missing assessment spool target.");
7417
                status = SIXEL_RUNTIME_ERROR;
×
7418
                goto end;
×
7419
            }
7420
            assessment_forward_stream = sixel_compat_fopen(
×
7421
                assessment_forward_path,
7422
                "wb");
7423
            if (assessment_forward_stream == NULL) {
×
7424
                sixel_helper_set_additional_message(
×
7425
                    "sixel_encoder_encode: failed to open assessment spool sink.");
7426
                status = SIXEL_LIBC_ERROR;
×
7427
                goto end;
×
7428
            }
7429
            status = copy_file_to_stream(assessment_temp_path,
×
7430
                                         assessment_forward_stream,
7431
                                         encoder->assessment_observer);
×
7432
            if (fclose(assessment_forward_stream) != 0) {
×
7433
                if (SIXEL_SUCCEEDED(status)) {
×
7434
                    sixel_helper_set_additional_message(
×
7435
                        "img2sixel: failed to close assessment spool sink.");
7436
                    status = SIXEL_LIBC_ERROR;
×
7437
                }
7438
            }
7439
            assessment_forward_stream = NULL;
×
7440
            if (SIXEL_FAILED(status)) {
×
7441
                goto end;
×
7442
            }
7443
        }
7444
        if (encoder->assessment_observer != NULL &&
4!
7445
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
1✔
7446
            sixel_assessment_stage_finish(encoder->assessment_observer);
×
7447
        }
7448
#if HAVE_SYS_STAT_H
7449
        assessment_output_bytes = 0u;
4✔
7450
        assessment_size_path = NULL;
4✔
7451
        if (assessment_need_quality && !assessment_quality_quantized) {
4!
7452
            if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
×
7453
                    assessment_spool_mode ==
7454
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
7455
                assessment_size_path = assessment_temp_path;
×
7456
            } else if (encoder->sixel_output_path != NULL &&
×
7457
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
7458
                assessment_size_path = encoder->sixel_output_path;
×
7459
            }
7460
        } else {
7461
            if (encoder->sixel_output_path != NULL &&
4!
7462
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
7463
                assessment_size_path = encoder->sixel_output_path;
×
7464
            } else if (assessment_spool_mode ==
4!
7465
                    SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
4!
7466
                    assessment_spool_mode ==
1✔
7467
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
7468
                assessment_size_path = assessment_temp_path;
×
7469
            }
7470
        }
7471
        if (assessment_size_path != NULL) {
4!
7472
            assessment_stat_result = stat(assessment_size_path,
×
7473
                                          &assessment_stat);
7474
            if (assessment_stat_result == 0 &&
×
7475
                    assessment_stat.st_size >= 0) {
×
7476
                assessment_output_bytes =
×
7477
                    (size_t)assessment_stat.st_size;
×
7478
            }
7479
        }
7480
#else
7481
        assessment_output_bytes = 0u;
7482
#endif
7483
        sixel_assessment_record_output_size(encoder->assessment_observer,
5✔
7484
                                            assessment_output_bytes);
1✔
7485
        if (assessment_need_quality) {
4!
7486
            status = sixel_assessment_analyze(encoder->assessment_observer,
×
7487
                                              assessment_source_frame,
7488
                                              assessment_target_frame);
7489
            if (SIXEL_FAILED(status)) {
×
7490
                goto end;
×
7491
            }
7492
        }
7493
        sixel_assessment_stage_finish(encoder->assessment_observer);
4✔
7494
        if (encoder->assessment_json_path != NULL &&
4!
7495
                strcmp(encoder->assessment_json_path, "-") != 0) {
4!
7496
            assessment_json_file = sixel_compat_fopen(
4✔
7497
                encoder->assessment_json_path,
1✔
7498
                "wb");
7499
            if (assessment_json_file == NULL) {
4!
7500
                sixel_helper_set_additional_message(
×
7501
                    "sixel_encoder_encode: failed to open assessment JSON file.");
7502
                status = SIXEL_LIBC_ERROR;
×
7503
                goto end;
×
7504
            }
7505
            assessment_json_owned = 1;
4✔
7506
            assessment_sink.stream = assessment_json_file;
4✔
7507
        } else {
1✔
7508
            assessment_sink.stream = stdout;
×
7509
        }
7510
        assessment_sink.failed = 0;
4✔
7511
        status = sixel_assessment_get_json(encoder->assessment_observer,
5✔
7512
                                           encoder->assessment_sections,
1✔
7513
                                           assessment_json_callback,
7514
                                           &assessment_sink);
7515
        if (SIXEL_FAILED(status) || assessment_sink.failed) {
4!
7516
            sixel_helper_set_additional_message(
×
7517
                "sixel_encoder_encode: failed to emit assessment JSON.");
7518
            goto end;
×
7519
        }
7520
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
558!
7521
        status = copy_file_to_stream(assessment_temp_path,
×
7522
                                     stdout,
7523
                                     encoder->assessment_observer);
×
7524
        if (SIXEL_FAILED(status)) {
×
7525
            goto end;
×
7526
        }
7527
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
557!
7528
        if (assessment_forward_path == NULL) {
×
7529
            sixel_helper_set_additional_message(
×
7530
                "img2sixel: missing assessment spool target.");
7531
            status = SIXEL_RUNTIME_ERROR;
×
7532
            goto end;
×
7533
        }
7534
        assessment_forward_stream = sixel_compat_fopen(
×
7535
            assessment_forward_path,
7536
            "wb");
7537
        if (assessment_forward_stream == NULL) {
×
7538
            sixel_helper_set_additional_message(
×
7539
                "img2sixel: failed to open assessment spool sink.");
7540
            status = SIXEL_LIBC_ERROR;
×
7541
            goto end;
×
7542
        }
7543
        status = copy_file_to_stream(assessment_temp_path,
×
7544
                                     assessment_forward_stream,
7545
                                     encoder->assessment_observer);
×
7546
        if (fclose(assessment_forward_stream) != 0) {
×
7547
            if (SIXEL_SUCCEEDED(status)) {
×
7548
                sixel_helper_set_additional_message(
×
7549
                    "img2sixel: failed to close assessment spool sink.");
7550
                status = SIXEL_LIBC_ERROR;
×
7551
            }
7552
        }
7553
        assessment_forward_stream = NULL;
×
7554
        if (SIXEL_FAILED(status)) {
×
7555
            goto end;
×
7556
        }
7557
    }
7558

7559
    if (encoder->output_is_png) {
561✔
7560
        png_final_path = encoder->output_png_to_stdout ? "-" : encoder->png_output_path;
12!
7561
        if (! encoder->output_png_to_stdout && png_final_path == NULL) {
12!
7562
            sixel_helper_set_additional_message(
×
7563
                "sixel_encoder_encode: missing PNG output path.");
7564
            status = SIXEL_RUNTIME_ERROR;
×
7565
            goto end;
×
7566
        }
7567
        status = write_png_from_sixel(png_temp_path, png_final_path);
12✔
7568
        if (SIXEL_FAILED(status)) {
12!
7569
            goto end;
×
7570
        }
7571
    }
3✔
7572

7573
    if (encoder->clipboard_output_active
561!
7574
            && encoder->clipboard_output_path != NULL) {
142!
7575
        unsigned char *clipboard_output_data;
7576
        size_t clipboard_output_size;
7577

7578
        clipboard_output_data = NULL;
1✔
7579
        clipboard_output_size = 0u;
1✔
7580

7581
        if (encoder->outfd
2!
7582
                && encoder->outfd != STDOUT_FILENO
1!
7583
                && encoder->outfd != STDERR_FILENO) {
1!
7584
            (void)sixel_compat_close(encoder->outfd);
1✔
7585
            encoder->outfd = STDOUT_FILENO;
1✔
7586
        }
1✔
7587

7588
        clipboard_status = clipboard_read_file(
1✔
7589
            encoder->clipboard_output_path,
1✔
7590
            &clipboard_output_data,
7591
            &clipboard_output_size);
7592
        if (SIXEL_SUCCEEDED(clipboard_status)) {
1!
7593
            clipboard_status = sixel_clipboard_write(
1✔
7594
                encoder->clipboard_output_format,
1✔
7595
                clipboard_output_data,
1✔
7596
                clipboard_output_size);
1✔
7597
        }
1✔
7598
        if (clipboard_output_data != NULL) {
1!
7599
            free(clipboard_output_data);
1✔
7600
        }
1✔
7601
        if (SIXEL_FAILED(clipboard_status)) {
1!
7602
            status = clipboard_status;
×
7603
            goto end;
×
7604
        }
7605
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
1✔
7606
        sixel_allocator_free(encoder->allocator,
2✔
7607
                             encoder->clipboard_output_path);
1✔
7608
        encoder->clipboard_output_path = NULL;
1✔
7609
        encoder->sixel_output_path = NULL;
1✔
7610
        encoder->clipboard_output_active = 0;
1✔
7611
        encoder->clipboard_output_format[0] = '\0';
1✔
7612
    }
1✔
7613

7614
    /* the status may not be SIXEL_OK */
7615

7616
end:
420✔
7617
    if (png_temp_path != NULL) {
581✔
7618
        (void)sixel_compat_unlink(png_temp_path);
12✔
7619
    }
3✔
7620
    sixel_allocator_free(encoder->allocator, png_temp_path);
581✔
7621
    if (clipboard_input_path != NULL) {
581!
7622
        (void)sixel_compat_unlink(clipboard_input_path);
1✔
7623
        sixel_allocator_free(encoder->allocator, clipboard_input_path);
1✔
7624
    }
1✔
7625
    if (clipboard_blob != NULL) {
581!
7626
        free(clipboard_blob);
×
7627
    }
7628
    if (encoder->clipboard_output_path != NULL) {
581!
7629
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
7630
        sixel_allocator_free(encoder->allocator,
×
7631
                             encoder->clipboard_output_path);
×
7632
        encoder->clipboard_output_path = NULL;
×
7633
        encoder->sixel_output_path = NULL;
×
7634
        encoder->clipboard_output_active = 0;
×
7635
        encoder->clipboard_output_format[0] = '\0';
×
7636
    }
7637
    sixel_allocator_free(encoder->allocator, encoder->png_output_path);
581✔
7638
    encoder->png_output_path = NULL;
581✔
7639
    if (assessment_forward_stream != NULL) {
581!
7640
        (void) fclose(assessment_forward_stream);
×
7641
    }
7642
    if (assessment_temp_path != NULL &&
581!
7643
            assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
7644
        (void)sixel_compat_unlink(assessment_temp_path);
×
7645
    }
7646
    sixel_allocator_free(encoder->allocator, assessment_temp_path);
581✔
7647
    sixel_allocator_free(encoder->allocator, assessment_forward_path);
581✔
7648
    if (assessment_json_owned && assessment_json_file != NULL) {
581!
7649
        (void) fclose(assessment_json_file);
4✔
7650
    }
1✔
7651
    if (assessment_target_frame != NULL) {
581!
7652
        sixel_frame_unref(assessment_target_frame);
×
7653
    }
7654
    if (assessment_expanded_frame != NULL) {
581!
7655
        sixel_frame_unref(assessment_expanded_frame);
×
7656
    }
7657
    if (assessment_source_frame != NULL) {
581✔
7658
        sixel_frame_unref(assessment_source_frame);
4✔
7659
    }
1✔
7660
    if (encoder->assessment_observer != NULL) {
581✔
7661
        sixel_assessment_unref(encoder->assessment_observer);
4✔
7662
        encoder->assessment_observer = NULL;
4✔
7663
    }
1✔
7664
    if (assessment_allocator != NULL) {
581✔
7665
        sixel_allocator_unref(assessment_allocator);
4✔
7666
    }
1✔
7667

7668
    if (encoder != NULL) {
581!
7669
        encoder->logger = NULL;
581✔
7670
        encoder->parallel_job_id = -1;
581✔
7671
    }
146✔
7672
    if (logger_prepared) {
581!
7673
        sixel_logger_close(&logger);
×
7674
    }
7675

7676
    sixel_encoder_unref(encoder);
581✔
7677

7678
    if (encode_allocator != NULL) {
581!
7679
        /*
7680
         * Release the retained allocator reference *after* dropping the
7681
         * encoder reference so that a lazily created encoder can run its
7682
         * destructor while the allocator is still alive.  This ensures that
7683
         * cleanup routines never dereference a freed allocator instance.
7684
         */
7685
        sixel_allocator_unref(encode_allocator);
581✔
7686
        encode_allocator = NULL;
581✔
7687
    }
146✔
7688

7689
    return status;
581✔
7690
}
7691

7692

7693
/* encode specified pixel data to SIXEL format
7694
 * output to encoder->outfd */
7695
SIXELAPI SIXELSTATUS
7696
sixel_encoder_encode_bytes(
×
7697
    sixel_encoder_t     /* in */    *encoder,
7698
    unsigned char       /* in */    *bytes,
7699
    int                 /* in */    width,
7700
    int                 /* in */    height,
7701
    int                 /* in */    pixelformat,
7702
    unsigned char       /* in */    *palette,
7703
    int                 /* in */    ncolors)
7704
{
7705
    SIXELSTATUS status = SIXEL_FALSE;
×
7706
    sixel_frame_t *frame = NULL;
×
7707

7708
    if (encoder == NULL || bytes == NULL) {
×
7709
        status = SIXEL_BAD_ARGUMENT;
×
7710
        goto end;
×
7711
    }
7712

7713
    status = sixel_frame_new(&frame, encoder->allocator);
×
7714
    if (SIXEL_FAILED(status)) {
×
7715
        goto end;
×
7716
    }
7717

7718
    status = sixel_frame_init(frame, bytes, width, height,
×
7719
                              pixelformat, palette, ncolors);
7720
    if (SIXEL_FAILED(status)) {
×
7721
        goto end;
×
7722
    }
7723

7724
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
7725
    if (SIXEL_FAILED(status)) {
×
7726
        goto end;
×
7727
    }
7728

7729
    status = SIXEL_OK;
×
7730

7731
end:
7732
    /* we need to free the frame before exiting, but we can't use the
7733
       sixel_frame_destroy function, because that will also attempt to
7734
       free the pixels and palette, which we don't own */
7735
    if (frame != NULL && encoder->allocator != NULL) {
×
7736
        sixel_allocator_free(encoder->allocator, frame);
×
7737
        sixel_allocator_unref(encoder->allocator);
×
7738
    }
7739
    return status;
×
7740
}
7741

7742

7743
/*
7744
 * Toggle source-frame capture for assessment consumers.
7745
 */
7746
SIXELAPI SIXELSTATUS
7747
sixel_encoder_enable_source_capture(
4✔
7748
    sixel_encoder_t *encoder,
7749
    int enable)
7750
{
7751
    if (encoder == NULL) {
4!
7752
        sixel_helper_set_additional_message(
×
7753
            "sixel_encoder_enable_source_capture: encoder is null.");
7754
        return SIXEL_BAD_ARGUMENT;
×
7755
    }
7756

7757
    encoder->capture_source = enable ? 1 : 0;
4✔
7758
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
4!
7759
        sixel_frame_unref(encoder->capture_source_frame);
×
7760
        encoder->capture_source_frame = NULL;
×
7761
    }
7762

7763
    return SIXEL_OK;
4✔
7764
}
1✔
7765

7766

7767
/*
7768
 * Enable or disable the quantized-frame capture facility.
7769
 *
7770
 *     capture on --> encoder keeps the latest palette-quantized frame.
7771
 *     capture off --> encoder forgets previously stored frames.
7772
 */
7773
SIXELAPI SIXELSTATUS
7774
sixel_encoder_enable_quantized_capture(
4✔
7775
    sixel_encoder_t *encoder,
7776
    int enable)
7777
{
7778
    if (encoder == NULL) {
4!
7779
        sixel_helper_set_additional_message(
×
7780
            "sixel_encoder_enable_quantized_capture: encoder is null.");
7781
        return SIXEL_BAD_ARGUMENT;
×
7782
    }
7783

7784
    encoder->capture_quantized = enable ? 1 : 0;
4✔
7785
    if (!encoder->capture_quantized) {
4!
7786
        encoder->capture_valid = 0;
×
7787
    }
7788

7789
    return SIXEL_OK;
4✔
7790
}
1✔
7791

7792

7793
/*
7794
 * Materialize the captured quantized frame as a heap-allocated
7795
 * sixel_frame_t instance for assessment consumers.
7796
 */
7797
SIXELAPI SIXELSTATUS
7798
sixel_encoder_copy_quantized_frame(
×
7799
    sixel_encoder_t   *encoder,
7800
    sixel_allocator_t *allocator,
7801
    sixel_frame_t     **ppframe)
7802
{
7803
    SIXELSTATUS status = SIXEL_FALSE;
×
7804
    sixel_frame_t *frame;
7805
    unsigned char *pixels;
7806
    unsigned char *palette;
7807
    size_t palette_bytes;
7808

7809
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
7810
        sixel_helper_set_additional_message(
×
7811
            "sixel_encoder_copy_quantized_frame: invalid argument.");
7812
        return SIXEL_BAD_ARGUMENT;
×
7813
    }
7814

7815
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
7816
        sixel_helper_set_additional_message(
×
7817
            "sixel_encoder_copy_quantized_frame: no frame captured.");
7818
        return SIXEL_RUNTIME_ERROR;
×
7819
    }
7820

7821
    *ppframe = NULL;
×
7822
    frame = NULL;
×
7823
    pixels = NULL;
×
7824
    palette = NULL;
×
7825

7826
    status = sixel_frame_new(&frame, allocator);
×
7827
    if (SIXEL_FAILED(status)) {
×
7828
        return status;
×
7829
    }
7830

7831
    if (encoder->capture_pixel_bytes > 0) {
×
7832
        pixels = (unsigned char *)sixel_allocator_malloc(
×
7833
            allocator, encoder->capture_pixel_bytes);
7834
        if (pixels == NULL) {
×
7835
            sixel_helper_set_additional_message(
×
7836
                "sixel_encoder_copy_quantized_frame: "
7837
                "sixel_allocator_malloc() failed.");
7838
            status = SIXEL_BAD_ALLOCATION;
×
7839
            goto cleanup;
×
7840
        }
7841
        memcpy(pixels,
×
7842
               encoder->capture_pixels,
7843
               encoder->capture_pixel_bytes);
7844
    }
7845

7846
    palette_bytes = encoder->capture_palette_size;
×
7847
    if (palette_bytes > 0) {
×
7848
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
7849
                                                          palette_bytes);
7850
        if (palette == NULL) {
×
7851
            sixel_helper_set_additional_message(
×
7852
                "sixel_encoder_copy_quantized_frame: "
7853
                "sixel_allocator_malloc() failed.");
7854
            status = SIXEL_BAD_ALLOCATION;
×
7855
            goto cleanup;
×
7856
        }
7857
        memcpy(palette,
×
7858
               encoder->capture_palette,
7859
               palette_bytes);
7860
    }
7861

7862
    status = sixel_frame_init(frame,
×
7863
                              pixels,
7864
                              encoder->capture_width,
7865
                              encoder->capture_height,
7866
                              encoder->capture_pixelformat,
7867
                              palette,
7868
                              encoder->capture_ncolors);
7869
    if (SIXEL_FAILED(status)) {
×
7870
        goto cleanup;
×
7871
    }
7872

7873
    pixels = NULL;
×
7874
    palette = NULL;
×
7875
    /*
7876
     * Capture colorspace must be preserved for assessment consumers.
7877
     * Keep access encapsulated via the public setter to avoid
7878
     * depending on frame internals.
7879
     */
7880
    sixel_frame_set_colorspace(frame, encoder->capture_colorspace);
×
7881
    *ppframe = frame;
×
7882
    return SIXEL_OK;
×
7883

7884
cleanup:
7885
    if (palette != NULL) {
×
7886
        sixel_allocator_free(allocator, palette);
×
7887
    }
7888
    if (pixels != NULL) {
×
7889
        sixel_allocator_free(allocator, pixels);
×
7890
    }
7891
    if (frame != NULL) {
×
7892
        sixel_frame_unref(frame);
×
7893
    }
7894
    return status;
×
7895
}
7896

7897

7898
/*
7899
 * Emit the captured palette in the requested format.
7900
 *
7901
 *   palette_output == NULL  -> skip
7902
 *   palette_output != NULL  -> materialize captured palette
7903
 */
7904
static SIXELSTATUS
7905
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
561✔
7906
{
7907
    SIXELSTATUS status;
7908
    sixel_frame_t *frame;
7909
    unsigned char const *palette;
7910
    int exported_colors;
7911
    FILE *stream;
7912
    int close_stream;
7913
    char const *path;
7914
    sixel_palette_format_t format_hint;
7915
    sixel_palette_format_t format_ext;
7916
    sixel_palette_format_t format_final;
7917
    char const *mode;
7918

7919
    status = SIXEL_OK;
561✔
7920
    frame = NULL;
561✔
7921
    palette = NULL;
561✔
7922
    exported_colors = 0;
561✔
7923
    stream = NULL;
561✔
7924
    close_stream = 0;
561✔
7925
    path = NULL;
561✔
7926
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
561✔
7927
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
561✔
7928
    format_final = SIXEL_PALETTE_FORMAT_NONE;
561✔
7929
    mode = "wb";
561✔
7930

7931
    if (encoder == NULL || encoder->palette_output == NULL) {
561!
7932
        return SIXEL_OK;
561✔
7933
    }
7934

7935
    status = sixel_encoder_copy_quantized_frame(encoder,
×
7936
                                                encoder->allocator,
7937
                                                &frame);
7938
    if (SIXEL_FAILED(status)) {
×
7939
        return status;
×
7940
    }
7941

7942
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
7943
    exported_colors = sixel_frame_get_ncolors(frame);
×
7944
    if (palette == NULL || exported_colors <= 0) {
×
7945
        sixel_helper_set_additional_message(
×
7946
            "sixel_encoder_emit_palette_output: palette unavailable.");
7947
        status = SIXEL_BAD_INPUT;
×
7948
        goto cleanup;
×
7949
    }
7950
    if (exported_colors > 256) {
×
7951
        exported_colors = 256;
×
7952
    }
7953

7954
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
7955
    if (path == NULL || *path == '\0') {
×
7956
        sixel_helper_set_additional_message(
×
7957
            "sixel_encoder_emit_palette_output: invalid path.");
7958
        status = SIXEL_BAD_ARGUMENT;
×
7959
        goto cleanup;
×
7960
    }
7961

7962
    format_ext = sixel_palette_format_from_extension(path);
×
7963
    format_final = format_hint;
×
7964
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
7965
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
7966
            if (strcmp(path, "-") == 0) {
×
7967
                sixel_helper_set_additional_message(
×
7968
                    "sixel_encoder_emit_palette_output: "
7969
                    "format required for '-'.");
7970
                status = SIXEL_BAD_ARGUMENT;
×
7971
                goto cleanup;
×
7972
            }
7973
            sixel_helper_set_additional_message(
×
7974
                "sixel_encoder_emit_palette_output: "
7975
                "unknown palette file extension.");
7976
            status = SIXEL_BAD_ARGUMENT;
×
7977
            goto cleanup;
×
7978
        }
7979
        format_final = format_ext;
×
7980
    }
7981
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
7982
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
7983
    }
7984

7985
    if (strcmp(path, "-") == 0) {
×
7986
        stream = stdout;
×
7987
    } else {
7988
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
7989
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
7990
            mode = "w";
×
7991
        } else {
7992
            mode = "wb";
×
7993
        }
7994
        stream = fopen(path, mode);
×
7995
        if (stream == NULL) {
×
7996
            sixel_helper_set_additional_message(
×
7997
                "sixel_encoder_emit_palette_output: failed to open file.");
7998
            status = SIXEL_LIBC_ERROR;
×
7999
            goto cleanup;
×
8000
        }
8001
        close_stream = 1;
×
8002
    }
8003

8004
    switch (format_final) {
×
8005
    case SIXEL_PALETTE_FORMAT_ACT:
8006
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
8007
        if (SIXEL_FAILED(status)) {
×
8008
            sixel_helper_set_additional_message(
×
8009
                "sixel_encoder_emit_palette_output: failed to write ACT.");
8010
        }
8011
        break;
×
8012
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
8013
        status = sixel_palette_write_pal_jasc(stream,
×
8014
                                              palette,
8015
                                              exported_colors);
8016
        if (SIXEL_FAILED(status)) {
×
8017
            sixel_helper_set_additional_message(
×
8018
                "sixel_encoder_emit_palette_output: failed to write JASC.");
8019
        }
8020
        break;
×
8021
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
8022
        status = sixel_palette_write_pal_riff(stream,
×
8023
                                              palette,
8024
                                              exported_colors);
8025
        if (SIXEL_FAILED(status)) {
×
8026
            sixel_helper_set_additional_message(
×
8027
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
8028
        }
8029
        break;
×
8030
    case SIXEL_PALETTE_FORMAT_GPL:
8031
        status = sixel_palette_write_gpl(stream,
×
8032
                                         palette,
8033
                                         exported_colors);
8034
        if (SIXEL_FAILED(status)) {
×
8035
            sixel_helper_set_additional_message(
×
8036
                "sixel_encoder_emit_palette_output: failed to write GPL.");
8037
        }
8038
        break;
×
8039
    default:
8040
        sixel_helper_set_additional_message(
×
8041
            "sixel_encoder_emit_palette_output: unsupported format.");
8042
        status = SIXEL_BAD_ARGUMENT;
×
8043
        break;
×
8044
    }
8045
    if (SIXEL_FAILED(status)) {
×
8046
        goto cleanup;
×
8047
    }
8048

8049
    if (close_stream) {
×
8050
        if (fclose(stream) != 0) {
×
8051
            sixel_helper_set_additional_message(
×
8052
                "sixel_encoder_emit_palette_output: fclose() failed.");
8053
            status = SIXEL_LIBC_ERROR;
×
8054
            stream = NULL;
×
8055
            goto cleanup;
×
8056
        }
8057
        stream = NULL;
×
8058
    } else {
8059
        if (fflush(stream) != 0) {
×
8060
            sixel_helper_set_additional_message(
×
8061
                "sixel_encoder_emit_palette_output: fflush() failed.");
8062
            status = SIXEL_LIBC_ERROR;
×
8063
            goto cleanup;
×
8064
        }
8065
    }
8066

8067
cleanup:
8068
    if (close_stream && stream != NULL) {
×
8069
        (void) fclose(stream);
×
8070
    }
8071
    if (frame != NULL) {
×
8072
        sixel_frame_unref(frame);
×
8073
    }
8074

8075
    return status;
×
8076
}
141✔
8077

8078

8079
/*
8080
 * Share the captured source frame with assessment consumers.
8081
 */
8082
SIXELAPI SIXELSTATUS
8083
sixel_encoder_copy_source_frame(
4✔
8084
    sixel_encoder_t *encoder,
8085
    sixel_frame_t  **ppframe)
8086
{
8087
    if (encoder == NULL || ppframe == NULL) {
4!
8088
        sixel_helper_set_additional_message(
×
8089
            "sixel_encoder_copy_source_frame: invalid argument.");
8090
        return SIXEL_BAD_ARGUMENT;
×
8091
    }
8092

8093
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
4!
8094
        sixel_helper_set_additional_message(
×
8095
            "sixel_encoder_copy_source_frame: no frame captured.");
8096
        return SIXEL_RUNTIME_ERROR;
×
8097
    }
8098

8099
    sixel_frame_ref(encoder->capture_source_frame);
4✔
8100
    *ppframe = encoder->capture_source_frame;
4✔
8101

8102
    return SIXEL_OK;
4✔
8103
}
1✔
8104

8105

8106
#if HAVE_TESTS
8107
static int
8108
test1(void)
×
8109
{
8110
    int nret = EXIT_FAILURE;
×
8111
    sixel_encoder_t *encoder = NULL;
×
8112

8113
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8114
#  pragma GCC diagnostic push
8115
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8116
#endif
8117
    encoder = sixel_encoder_create();
×
8118
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8119
#  pragma GCC diagnostic pop
8120
#endif
8121
    if (encoder == NULL) {
×
8122
        goto error;
×
8123
    }
8124
    sixel_encoder_ref(encoder);
×
8125
    sixel_encoder_unref(encoder);
×
8126
    nret = EXIT_SUCCESS;
×
8127

8128
error:
8129
    sixel_encoder_unref(encoder);
×
8130
    return nret;
×
8131
}
8132

8133

8134
static int
8135
test2(void)
×
8136
{
8137
    int nret = EXIT_FAILURE;
×
8138
    SIXELSTATUS status;
8139
    sixel_encoder_t *encoder = NULL;
×
8140
    sixel_frame_t *frame = NULL;
×
8141
    unsigned char *buffer;
8142
    int height = 0;
×
8143
    int is_animation = 0;
×
8144

8145
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8146
#  pragma GCC diagnostic push
8147
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8148
#endif
8149
    encoder = sixel_encoder_create();
×
8150
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8151
#  pragma GCC diagnostic pop
8152
#endif
8153
    if (encoder == NULL) {
×
8154
        goto error;
×
8155
    }
8156

8157
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8158
#  pragma GCC diagnostic push
8159
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8160
#endif
8161
    frame = sixel_frame_create();
×
8162
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8163
#  pragma GCC diagnostic pop
8164
#endif
8165
    if (encoder == NULL) {
×
8166
        goto error;
×
8167
    }
8168

8169
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
8170
    if (buffer == NULL) {
×
8171
        goto error;
×
8172
    }
8173
    status = sixel_frame_init(frame, buffer, 1, 1,
×
8174
                              SIXEL_PIXELFORMAT_RGB888,
8175
                              NULL, 0);
8176
    if (SIXEL_FAILED(status)) {
×
8177
        goto error;
×
8178
    }
8179

8180
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
8181
        is_animation = 1;
×
8182
    }
8183

8184
    height = sixel_frame_get_height(frame);
×
8185

8186
    status = sixel_tty_scroll(sixel_write_callback,
×
8187
                              &encoder->outfd,
×
8188
                              encoder->outfd,
8189
                              height,
8190
                              is_animation);
8191
    if (SIXEL_FAILED(status)) {
×
8192
        goto error;
×
8193
    }
8194

8195
    nret = EXIT_SUCCESS;
×
8196

8197
error:
8198
    sixel_encoder_unref(encoder);
×
8199
    sixel_frame_unref(frame);
×
8200
    return nret;
×
8201
}
8202

8203

8204
static int
8205
test3(void)
×
8206
{
8207
    int nret = EXIT_FAILURE;
×
8208
    int result;
8209

8210
    result = sixel_tty_wait_stdin(1000);
×
8211
    if (result != 0) {
×
8212
        goto error;
×
8213
    }
8214

8215
    nret = EXIT_SUCCESS;
×
8216

8217
error:
8218
    return nret;
×
8219
}
8220

8221

8222
static int
8223
test4(void)
×
8224
{
8225
    int nret = EXIT_FAILURE;
×
8226
    sixel_encoder_t *encoder = NULL;
×
8227
    SIXELSTATUS status;
8228

8229
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8230
# pragma GCC diagnostic push
8231
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8232
#endif
8233
    encoder = sixel_encoder_create();
×
8234
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8235
# pragma GCC diagnostic pop
8236
#endif
8237
    if (encoder == NULL) {
×
8238
        goto error;
×
8239
    }
8240

8241
    status = sixel_encoder_setopt(encoder,
×
8242
                                  SIXEL_OPTFLAG_LOOPMODE,
8243
                                  "force");
8244
    if (SIXEL_FAILED(status)) {
×
8245
        goto error;
×
8246
    }
8247

8248
    status = sixel_encoder_setopt(encoder,
×
8249
                                  SIXEL_OPTFLAG_PIPE_MODE,
8250
                                  "force");
8251
    if (SIXEL_FAILED(status)) {
×
8252
        goto error;
×
8253
    }
8254

8255
    nret = EXIT_SUCCESS;
×
8256

8257
error:
8258
    sixel_encoder_unref(encoder);
×
8259
    return nret;
×
8260
}
8261

8262

8263
static int
8264
test5(void)
×
8265
{
8266
    int nret = EXIT_FAILURE;
×
8267
    sixel_encoder_t *encoder = NULL;
×
8268
    sixel_allocator_t *allocator = NULL;
×
8269
    SIXELSTATUS status;
8270

8271
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
8272
    if (SIXEL_FAILED(status)) {
×
8273
        goto error;
×
8274
    }
8275

8276
    status = sixel_encoder_new(&encoder, allocator);
×
8277
    if (SIXEL_FAILED(status)) {
×
8278
        goto error;
×
8279
    }
8280

8281
    sixel_encoder_ref(encoder);
×
8282
    sixel_encoder_unref(encoder);
×
8283
    nret = EXIT_SUCCESS;
×
8284

8285
error:
8286
    sixel_encoder_unref(encoder);
×
8287
    return nret;
×
8288
}
8289

8290

8291
SIXELAPI int
8292
sixel_encoder_tests_main(void)
×
8293
{
8294
    int nret = EXIT_FAILURE;
×
8295
    size_t i;
8296
    typedef int (* testcase)(void);
8297

8298
    static testcase const testcases[] = {
8299
        test1,
8300
        test2,
8301
        test3,
8302
        test4,
8303
        test5
8304
    };
8305

8306
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
8307
        nret = testcases[i]();
×
8308
        if (nret != EXIT_SUCCESS) {
×
8309
            goto error;
×
8310
        }
8311
    }
8312

8313
    nret = EXIT_SUCCESS;
×
8314

8315
error:
8316
    return nret;
×
8317
}
8318
#endif  /* HAVE_TESTS */
8319

8320

8321
/* emacs Local Variables:      */
8322
/* emacs mode: c               */
8323
/* emacs tab-width: 4          */
8324
/* emacs indent-tabs-mode: nil */
8325
/* emacs c-basic-offset: 4     */
8326
/* emacs End:                  */
8327
/* vim: set expandtab ts=4 : */
8328
/* 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