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

saitoha / libsixel / 19785694491

29 Nov 2025 03:18PM UTC coverage: 40.91% (+0.05%) from 40.858%
19785694491

push

github

saitoha
logging: adjust loader logging placement

9993 of 36850 branches covered (27.12%)

49 of 58 new or added lines in 2 files covered. (84.48%)

4 existing lines in 1 file now uncovered.

13052 of 31904 relevant lines covered (40.91%)

1170365.88 hits per line

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

49.79
/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
    { "hkmeans", SIXEL_FINAL_MERGE_HKMEANS }
297
};
298

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

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

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

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

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

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

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

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

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

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

387

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

397
    len = strlen(s);
95✔
398

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

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

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

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

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

428
    return 1;
×
429
}
182✔
430

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

438
    prefer_float32 = encoder->prefer_float32;
×
439

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

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

454
    encoder->prefer_float32 = prefer_float32;
×
455

456
    return SIXEL_OK;
×
457
}
458

459

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

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

608
    status = SIXEL_OK;
40✔
609

610
end:
60✔
611
    sixel_allocator_free(allocator, buf);
75✔
612

613
    return status;
75✔
614
}
615

616

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

623
    result = (int)sixel_compat_write(*(int *)priv,
11,272✔
624
                                     data,
2,160✔
625
                                     (size_t)size);
2,160✔
626

627
    return result;
9,112✔
628
}
629

630

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

643
    for (i = j = 0; i < size; ++i, ++j) {
1,168,790✔
644
        hex[j] = (data[i] >> 4) & 0xf;
1,168,670✔
645
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
1,168,670!
646
        hex[++j] = data[i] & 0xf;
1,168,670✔
647
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
1,168,670✔
648
    }
233,734✔
649

650
    result = (int)sixel_compat_write(*(int *)priv,
240✔
651
                                     hex,
24✔
652
                                     (size_t)(size * 2));
120✔
653

654
    return result;
120✔
655
}
656

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

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

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

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

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

725
    return written;
×
726
}
727

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

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

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

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

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

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

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

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

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

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

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

854

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

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

875
    status = SIXEL_OK;
20✔
876

877
end:
16✔
878
    return status;
20✔
879
}
880

881

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

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

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

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

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

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

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

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

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

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

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

1075
    return status;
5✔
1076
}
1✔
1077

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

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

1093
    status = SIXEL_OK;
45✔
1094

1095
end:
36✔
1096
    return status;
45✔
1097
}
1098

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

1107
    width_hint = 0;
761✔
1108
    height_hint = 0;
761✔
1109
    base = 0;
761✔
1110
    size = 0;
761✔
1111

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

1116
    width_hint = encoder->pixelwidth;
761✔
1117
    height_hint = encoder->pixelheight;
761✔
1118

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

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

1145
    return (int)size;
155✔
1146
}
153✔
1147

1148

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

1158

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

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

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

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

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

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

1242
        sixel_dither_set_lut_policy(callback_context->dither,
42✔
1243
                                    callback_context->lut_policy);
7✔
1244

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

1259
        /* success */
1260
        status = SIXEL_OK;
35✔
1261

1262
        break;
35✔
1263
    }
7✔
1264

1265
end:
28✔
1266
    return status;
35✔
1267
}
1268

1269

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

1273

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

1281
    path_len = 0u;
105✔
1282
    ext_len = 0u;
105✔
1283
    index = 0u;
105✔
1284

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

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

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

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

1306
    return 1;
×
1307
}
21✔
1308

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

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

1338
    colon = NULL;
80✔
1339
    type_len = 0u;
80✔
1340
    index = 0u;
80✔
1341

1342
    if (format_hint != NULL) {
80✔
1343
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
35✔
1344
    }
7✔
1345
    if (spec == NULL) {
80!
1346
        return NULL;
×
1347
    }
1348

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

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

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

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

1395
    return spec;
×
1396
}
16✔
1397

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

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

1415
    return SIXEL_PALETTE_FORMAT_NONE;
35✔
1416
}
7✔
1417

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

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

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

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

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

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

1462
    return 1;
35✔
1463
}
7✔
1464

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

1477

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

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

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

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

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

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

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

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

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

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

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

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

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

1593

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

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

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

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

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

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

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

1648

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

1657

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

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

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

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

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

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

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

1692
    return SIXEL_PALETTE_FORMAT_NONE;
×
1693
}
1694

1695

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

1705

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

1718

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

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

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

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

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

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

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

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

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

1819

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2053

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

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

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

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

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

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

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

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

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

2188

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2390

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

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

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

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

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

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

2444
    return SIXEL_OK;
×
2445
}
2446

2447

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

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

2472

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

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

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

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

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

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

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

2534

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

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

2569

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

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

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

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

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

2640
    format_ext = sixel_palette_format_from_extension(path);
35✔
2641
    path_has_extension = sixel_path_has_any_extension(path);
35✔
2642

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2849
    status = sixel_loader_setopt(loader,
42✔
2850
                                 SIXEL_LOADER_OPTION_LOGGER,
2851
                                 encoder->logger);
35✔
2852
    if (SIXEL_FAILED(status)) {
35!
NEW
2853
        goto end_loader;
×
2854
    }
2855

2856
    status = sixel_loader_load_file(loader,
42✔
2857
                                    encoder->mapfile,
35✔
2858
                                    load_image_callback_for_palette);
2859
    if (status != SIXEL_OK) {
35!
2860
        goto end_loader;
×
2861
    }
2862

2863
end_loader:
28✔
2864
    sixel_loader_unref(loader);
35✔
2865

2866
    if (status != SIXEL_OK) {
35!
2867
        return status;
×
2868
    }
2869

2870
    if (! callback_context.dither) {
35!
2871
        sixel_compat_snprintf(mapfile_message,
×
2872
                              sizeof(mapfile_message),
2873
                              "sixel_prepare_specified_palette() failed.\n"
2874
                              "reason: mapfile \"%s\" is empty.",
2875
                              encoder->mapfile != NULL
×
2876
                                ? encoder->mapfile
2877
                                : "");
2878
        sixel_helper_set_additional_message(mapfile_message);
×
2879
        return SIXEL_BAD_INPUT;
×
2880
    }
2881

2882
    *dither = callback_context.dither;
35✔
2883

2884
    sixel_encoder_log_stage(encoder,
42✔
2885
                            NULL,
2886
                            "palette",
2887
                            "worker",
2888
                            "finish",
2889
                            "mapfile=%s format=%d",
2890
                            encoder->mapfile,
7✔
2891
                            format_final);
7✔
2892

2893
    return status;
35✔
2894
}
7✔
2895

2896

2897
/* create dither object from a frame */
2898
static SIXELSTATUS
2899
sixel_encoder_prepare_palette(
866✔
2900
    sixel_encoder_t *encoder,  /* encoder object */
2901
    sixel_frame_t   *frame,    /* input frame object */
2902
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2903
{
2904
    SIXELSTATUS status = SIXEL_FALSE;
866✔
2905
    int histogram_colors;
2906
    sixel_assessment_t *assessment;
2907
    int promoted_stage;
2908

2909
    assessment = NULL;
866✔
2910
    promoted_stage = 0;
866✔
2911
    if (encoder != NULL) {
866!
2912
        assessment = encoder->assessment_observer;
866✔
2913
    }
174✔
2914

2915
    switch (encoder->color_option) {
866!
2916
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
48✔
2917
        if (encoder->dither_cache) {
60!
2918
            *dither = encoder->dither_cache;
×
2919
            status = SIXEL_OK;
×
2920
        } else {
2921
            status = sixel_dither_new(dither, (-1), encoder->allocator);
60✔
2922
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
60✔
2923
        }
2924
        goto end;
60✔
2925
    case SIXEL_COLOR_OPTION_MONOCHROME:
16✔
2926
        if (encoder->dither_cache) {
20!
2927
            *dither = encoder->dither_cache;
×
2928
            status = SIXEL_OK;
×
2929
        } else {
2930
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
20✔
2931
        }
2932
        goto end;
20✔
2933
    case SIXEL_COLOR_OPTION_MAPFILE:
28✔
2934
        if (encoder->dither_cache) {
35!
2935
            *dither = encoder->dither_cache;
×
2936
            status = SIXEL_OK;
×
2937
        } else {
2938
            status = sixel_prepare_specified_palette(dither, encoder);
35✔
2939
        }
2940
        goto end;
35✔
2941
    case SIXEL_COLOR_OPTION_BUILTIN:
36✔
2942
        if (encoder->dither_cache) {
45!
2943
            *dither = encoder->dither_cache;
×
2944
            status = SIXEL_OK;
×
2945
        } else {
2946
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
45✔
2947
        }
2948
        goto end;
45✔
2949
    case SIXEL_COLOR_OPTION_DEFAULT:
706✔
2950
    default:
2951
        break;
706✔
2952
    }
2953

2954
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
706✔
2955
        if (!sixel_frame_get_palette(frame)) {
380!
2956
            status = SIXEL_LOGIC_ERROR;
×
2957
            goto end;
×
2958
        }
2959
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
456✔
2960
                                  encoder->allocator);
76✔
2961
        if (SIXEL_FAILED(status)) {
380!
2962
            goto end;
×
2963
        }
2964
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
380✔
2965
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
380✔
2966
        if (sixel_frame_get_transparent(frame) != (-1)) {
380!
2967
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2968
        }
2969
        if (*dither && encoder->dither_cache) {
380!
2970
            sixel_dither_unref(encoder->dither_cache);
×
2971
        }
2972
        goto end;
380✔
2973
    }
2974

2975
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
326!
2976
        switch (sixel_frame_get_pixelformat(frame)) {
×
2977
        case SIXEL_PIXELFORMAT_G1:
2978
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2979
            break;
×
2980
        case SIXEL_PIXELFORMAT_G2:
2981
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2982
            break;
×
2983
        case SIXEL_PIXELFORMAT_G4:
2984
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2985
            break;
×
2986
        case SIXEL_PIXELFORMAT_G8:
2987
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
2988
            break;
×
2989
        default:
2990
            *dither = NULL;
×
2991
            status = SIXEL_LOGIC_ERROR;
×
2992
            goto end;
×
2993
        }
2994
        if (*dither && encoder->dither_cache) {
×
2995
            sixel_dither_unref(encoder->dither_cache);
×
2996
        }
2997
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
2998
        status = SIXEL_OK;
×
2999
        goto end;
×
3000
    }
3001

3002
    if (encoder->dither_cache) {
326!
3003
        sixel_dither_unref(encoder->dither_cache);
×
3004
    }
3005
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
326✔
3006
    if (SIXEL_FAILED(status)) {
326!
3007
        goto end;
×
3008
    }
3009

3010
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
326✔
3011
    sixel_dither_set_sixel_reversible(*dither,
392✔
3012
                                      encoder->sixel_reversible);
66✔
3013
    sixel_dither_set_final_merge(*dither, encoder->final_merge_mode);
326✔
3014
    (*dither)->quantize_model = encoder->quantize_model;
326✔
3015

3016
    status = sixel_dither_initialize(*dither,
392✔
3017
                                     sixel_frame_get_pixels(frame),
66✔
3018
                                     sixel_frame_get_width(frame),
66✔
3019
                                     sixel_frame_get_height(frame),
66✔
3020
                                     sixel_frame_get_pixelformat(frame),
66✔
3021
                                     encoder->method_for_largest,
66✔
3022
                                     encoder->method_for_rep,
66✔
3023
                                     encoder->quality_mode);
66✔
3024
    if (SIXEL_FAILED(status)) {
326!
3025
        sixel_dither_unref(*dither);
×
3026
        goto end;
×
3027
    }
3028

3029
    if (assessment != NULL && promoted_stage == 0) {
326!
3030
        sixel_assessment_stage_transition(
5✔
3031
            assessment,
1✔
3032
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
3033
        promoted_stage = 1;
5✔
3034
    }
1✔
3035

3036
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
326✔
3037
    if (histogram_colors <= encoder->reqcolors) {
326✔
3038
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
255✔
3039
    }
43✔
3040
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
326✔
3041

3042
    status = SIXEL_OK;
326✔
3043

3044
end:
692✔
3045
    if (assessment != NULL && promoted_stage == 0) {
866!
3046
        sixel_assessment_stage_transition(
×
3047
            assessment,
3048
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
3049
        promoted_stage = 1;
×
3050
    }
3051
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
866!
3052
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
866✔
3053
        /* pass down the user's demand for an exact palette size */
3054
        (*dither)->force_palette = encoder->force_palette;
866✔
3055
    }
174✔
3056
    return status;
866✔
3057
}
3058

3059

3060
/* resize a frame with settings of specified encoder object */
3061
static SIXELSTATUS
3062
sixel_encoder_do_resize(
876✔
3063
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3064
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3065
{
3066
    SIXELSTATUS status = SIXEL_FALSE;
876✔
3067
    int src_width;
3068
    int src_height;
3069
    int dst_width;
3070
    int dst_height;
3071
    int use_float_resize;
3072

3073
    /* get frame width and height */
3074
    src_width = sixel_frame_get_width(frame);
876✔
3075
    src_height = sixel_frame_get_height(frame);
876✔
3076

3077
    if (src_width < 1) {
876✔
3078
         sixel_helper_set_additional_message(
10✔
3079
             "sixel_encoder_do_resize: "
3080
             "detected a frame with a non-positive width.");
3081
        return SIXEL_BAD_ARGUMENT;
10✔
3082
    }
3083

3084
    if (src_height < 1) {
866!
3085
         sixel_helper_set_additional_message(
×
3086
             "sixel_encoder_do_resize: "
3087
             "detected a frame with a non-positive height.");
3088
        return SIXEL_BAD_ARGUMENT;
×
3089
    }
3090

3091
    /* settings around scaling */
3092
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
866✔
3093
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
866✔
3094

3095
    use_float_resize = 0;
866✔
3096

3097
    /* if the encoder has percentwidth or percentheight property,
3098
       convert them to pixelwidth / pixelheight */
3099
    if (encoder->percentwidth > 0) {
866✔
3100
        dst_width = src_width * encoder->percentwidth / 100;
20✔
3101
    }
4✔
3102
    if (encoder->percentheight > 0) {
866✔
3103
        dst_height = src_height * encoder->percentheight / 100;
15✔
3104
    }
3✔
3105

3106
    /* if only either width or height is set, set also the other
3107
       to retain frame aspect ratio */
3108
    if (dst_width > 0 && dst_height <= 0) {
866✔
3109
        dst_height = src_height * dst_width / src_width;
70✔
3110
    }
14✔
3111
    if (dst_height > 0 && dst_width <= 0) {
866✔
3112
        dst_width = src_width * dst_height / src_height;
55✔
3113
    }
11✔
3114

3115
    sixel_encoder_log_stage(encoder,
1,040✔
3116
                            frame,
174✔
3117
                            "scale",
3118
                            "worker",
3119
                            "start",
3120
                            "src=%dx%d dst=%dx%d resample=%d",
3121
                            src_width,
174✔
3122
                            src_height,
174✔
3123
                            dst_width,
174✔
3124
                            dst_height,
174✔
3125
                            encoder->method_for_resampling);
174✔
3126

3127
    /* do resize */
3128
    if (dst_width > 0 && dst_height > 0) {
866!
3129
        if (encoder->method_for_resampling != SIXEL_RES_NEAREST) {
155✔
3130
            if (SIXEL_PIXELFORMAT_IS_FLOAT32(
125!
3131
                    encoder->working_colorspace) != 0) {
25✔
3132
                use_float_resize = 1;
×
3133
            }
3134
            if (encoder->prefer_float32 != 0) {
125!
3135
                use_float_resize = 1;
×
3136
            }
3137
        }
25✔
3138

3139
        if (use_float_resize != 0) {
155!
3140
            status = sixel_frame_resize_float32(
×
3141
                frame,
3142
                dst_width,
3143
                dst_height,
3144
                encoder->method_for_resampling);
3145
        } else {
3146
            status = sixel_frame_resize(
155✔
3147
                frame,
31✔
3148
                dst_width,
31✔
3149
                dst_height,
31✔
3150
                encoder->method_for_resampling);
31✔
3151
        }
3152
        if (SIXEL_FAILED(status)) {
155!
3153
            goto end;
×
3154
        }
3155
    }
31✔
3156

3157
    sixel_encoder_log_stage(encoder,
1,040✔
3158
                            frame,
174✔
3159
                            "scale",
3160
                            "worker",
3161
                            "finish",
3162
                            "result=%dx%d",
3163
                            sixel_frame_get_width(frame),
174✔
3164
                            sixel_frame_get_height(frame));
174✔
3165

3166
    /* success */
3167
    status = SIXEL_OK;
866✔
3168

3169
end:
692✔
3170
    return status;
866✔
3171
}
176✔
3172

3173

3174
/* clip a frame with settings of specified encoder object */
3175
static SIXELSTATUS
3176
sixel_encoder_do_clip(
866✔
3177
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3178
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3179
{
3180
    SIXELSTATUS status = SIXEL_FALSE;
866✔
3181
    int src_width;
3182
    int src_height;
3183
    int clip_x;
3184
    int clip_y;
3185
    int clip_w;
3186
    int clip_h;
3187

3188
    /* get frame width and height */
3189
    src_width = sixel_frame_get_width(frame);
866✔
3190
    src_height = sixel_frame_get_height(frame);
866✔
3191

3192
    /* settings around clipping */
3193
    clip_x = encoder->clipx;
866✔
3194
    clip_y = encoder->clipy;
866✔
3195
    clip_w = encoder->clipwidth;
866✔
3196
    clip_h = encoder->clipheight;
866✔
3197

3198
    /* adjust clipping width with comparing it to frame width */
3199
    if (clip_w + clip_x > src_width) {
866✔
3200
        if (clip_x > src_width) {
10✔
3201
            clip_w = 0;
5✔
3202
        } else {
1✔
3203
            clip_w = src_width - clip_x;
5✔
3204
        }
3205
    }
2✔
3206

3207
    /* adjust clipping height with comparing it to frame height */
3208
    if (clip_h + clip_y > src_height) {
866✔
3209
        if (clip_y > src_height) {
10✔
3210
            clip_h = 0;
5✔
3211
        } else {
1✔
3212
            clip_h = src_height - clip_y;
5✔
3213
        }
3214
    }
2✔
3215

3216
    sixel_encoder_log_stage(encoder,
1,040✔
3217
                            frame,
174✔
3218
                            "crop",
3219
                            "worker",
3220
                            "start",
3221
                            "src=%dx%d region=%dx%d@%d,%d",
3222
                            src_width,
174✔
3223
                            src_height,
174✔
3224
                            clip_w,
174✔
3225
                            clip_h,
174✔
3226
                            clip_x,
174✔
3227
                            clip_y);
174✔
3228

3229
    /* do clipping */
3230
    if (clip_w > 0 && clip_h > 0) {
866!
3231
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
20✔
3232
        if (SIXEL_FAILED(status)) {
20!
3233
            goto end;
×
3234
        }
3235
    }
4✔
3236

3237
    sixel_encoder_log_stage(encoder,
1,040✔
3238
                            frame,
174✔
3239
                            "crop",
3240
                            "worker",
3241
                            "finish",
3242
                            "result=%dx%d",
3243
                            sixel_frame_get_width(frame),
174✔
3244
                            sixel_frame_get_height(frame));
174✔
3245

3246
    /* success */
3247
    status = SIXEL_OK;
866✔
3248

3249
end:
692✔
3250
    return status;
866✔
3251
}
3252

3253

3254
static void
3255
sixel_debug_print_palette(
5✔
3256
    sixel_dither_t /* in */ *dither /* dithering object */
3257
)
3258
{
3259
    sixel_palette_t *palette_obj;
3260
    unsigned char *palette_copy;
3261
    size_t palette_count;
3262
    int i;
3263

3264
    palette_obj = NULL;
5✔
3265
    palette_copy = NULL;
5✔
3266
    palette_count = 0U;
5✔
3267
    if (dither == NULL) {
5!
3268
        return;
×
3269
    }
3270

3271
    if (SIXEL_FAILED(
5!
3272
            sixel_dither_get_quantized_palette(dither, &palette_obj))
3273
            || palette_obj == NULL) {
5!
3274
        return;
×
3275
    }
3276
    if (SIXEL_FAILED(sixel_palette_copy_entries_8bit(
5!
3277
            palette_obj,
3278
            &palette_copy,
3279
            &palette_count,
3280
            SIXEL_PIXELFORMAT_RGB888,
3281
            dither->allocator))
3282
            || palette_copy == NULL) {
5!
3283
        sixel_palette_unref(palette_obj);
×
3284
        return;
×
3285
    }
3286
    sixel_palette_unref(palette_obj);
5✔
3287

3288
    fprintf(stderr, "palette:\n");
5✔
3289
    for (i = 0; i < (int)palette_count;
80✔
3290
            ++i) {
75✔
3291
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
90✔
3292
                palette_copy[i * 3 + 0],
75✔
3293
                palette_copy[i * 3 + 1],
75✔
3294
                palette_copy[i * 3 + 2]);
75✔
3295
    }
15✔
3296
    sixel_allocator_free(dither->allocator, palette_copy);
5✔
3297
}
1✔
3298

3299

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

3328
    if (encoder == NULL) {
726!
3329
        sixel_helper_set_additional_message(
×
3330
            "sixel_encoder_output_without_macro: encoder object is null.");
3331
        status = SIXEL_BAD_ARGUMENT;
×
3332
        goto end;
×
3333
    }
3334

3335
    if (encoder->assessment_observer != NULL) {
726✔
3336
        sixel_assessment_stage_transition(
5✔
3337
            encoder->assessment_observer,
5✔
3338
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3339
    }
1✔
3340

3341
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
726✔
3342
        if (encoder->force_palette) {
566!
3343
            /* keep every slot when the user forced the palette size */
3344
            sixel_dither_set_optimize_palette(dither, 0);
×
3345
        } else {
3346
            sixel_dither_set_optimize_palette(dither, 1);
566✔
3347
        }
3348
    }
114✔
3349

3350
    pixelformat = sixel_frame_get_pixelformat(frame);
726✔
3351
    frame_colorspace = sixel_frame_get_colorspace(frame);
726✔
3352
    output->pixelformat = pixelformat;
726✔
3353
    output->source_colorspace = frame_colorspace;
726✔
3354
    output->colorspace = encoder->output_colorspace;
726✔
3355
    sixel_dither_set_pixelformat(dither, pixelformat);
726✔
3356
    depth = sixel_helper_compute_depth(pixelformat);
726✔
3357
    if (depth < 0) {
726!
3358
        status = SIXEL_LOGIC_ERROR;
×
3359
        nwrite = sixel_compat_snprintf(
×
3360
            message,
3361
            sizeof(message),
3362
            "sixel_encoder_output_without_macro: "
3363
            "sixel_helper_compute_depth(%08x) failed.",
3364
            pixelformat);
3365
        if (nwrite > 0) {
×
3366
            sixel_helper_set_additional_message(message);
×
3367
        }
3368
        goto end;
×
3369
    }
3370

3371
    width = sixel_frame_get_width(frame);
726✔
3372
    height = sixel_frame_get_height(frame);
726✔
3373
    size = (size_t)(width * height * depth);
726✔
3374

3375
    sixel_encoder_log_stage(encoder,
872✔
3376
                            frame,
146✔
3377
                            "encode",
3378
                            "worker",
3379
                            "start",
3380
                            "size=%dx%d fmt=%08x dst_cs=%d",
3381
                            width,
146✔
3382
                            height,
146✔
3383
                            pixelformat,
146✔
3384
                            output->colorspace);
146✔
3385

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

3438
    pixbuf = sixel_frame_get_pixels(frame);
726✔
3439
    memcpy(p, pixbuf, (size_t)(width * height * depth));
726✔
3440

3441
    if (encoder->cancel_flag && *encoder->cancel_flag) {
726!
3442
        goto end;
×
3443
    }
3444

3445
    if (encoder->capture_quantized) {
726✔
3446
        status = sixel_encoder_capture_quantized(encoder,
6✔
3447
                                                 dither,
1✔
3448
                                                 p,
1✔
3449
                                                 size,
1✔
3450
                                                 width,
1✔
3451
                                                 height,
1✔
3452
                                                 pixelformat,
1✔
3453
                                                 frame_colorspace,
1✔
3454
                                                 output->colorspace);
1✔
3455
        if (SIXEL_FAILED(status)) {
5!
3456
            goto end;
×
3457
        }
3458
    }
1✔
3459

3460
    if (encoder->assessment_observer != NULL) {
726✔
3461
        sixel_assessment_stage_transition(
5✔
3462
            encoder->assessment_observer,
5✔
3463
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3464
    }
1✔
3465
    status = sixel_encode(p, width, height, depth, dither, output);
726✔
3466
    if (encoder->assessment_observer != NULL) {
726✔
3467
        sixel_assessment_stage_finish(encoder->assessment_observer);
5✔
3468
    }
1✔
3469
    if (status != SIXEL_OK) {
726!
3470
        goto end;
×
3471
    }
3472

3473
end:
580✔
3474
    if (SIXEL_SUCCEEDED(status)) {
726!
3475
        sixel_encoder_log_stage(encoder,
872✔
3476
                                frame,
146✔
3477
                                "encode",
3478
                                "worker",
3479
                                "finish",
3480
                                "size=%dx%d",
3481
                                width,
146✔
3482
                                height);
146✔
3483
    }
146✔
3484
    output->pixelformat = pixelformat;
726✔
3485
    output->source_colorspace = frame_colorspace;
726✔
3486
    sixel_allocator_free(encoder->allocator, p);
726✔
3487

3488
    return status;
726✔
3489
}
3490

3491

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

3524
    if (encoder != NULL && encoder->assessment_observer != NULL) {
140!
3525
        sixel_assessment_stage_transition(
×
3526
            encoder->assessment_observer,
×
3527
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3528
    }
3529

3530
#if defined(HAVE_CLOCK)
3531
    if (output->last_clock == 0) {
140!
3532
        output->last_clock = clock();
140✔
3533
    }
28✔
3534
#elif defined(HAVE_CLOCK_WIN)
3535
    if (output->last_clock == 0) {
3536
        output->last_clock = clock_win();
3537
    }
3538
#endif
3539

3540
    width = sixel_frame_get_width(frame);
140✔
3541
    height = sixel_frame_get_height(frame);
140✔
3542
    pixelformat = sixel_frame_get_pixelformat(frame);
140✔
3543
    depth = sixel_helper_compute_depth(pixelformat);
140✔
3544
    if (depth < 0) {
140!
3545
        status = SIXEL_LOGIC_ERROR;
×
3546
        sixel_helper_set_additional_message(
×
3547
            "sixel_encoder_output_with_macro: "
3548
            "sixel_helper_compute_depth() failed.");
3549
        goto end;
×
3550
    }
3551

3552
    frame_colorspace = sixel_frame_get_colorspace(frame);
140✔
3553
    size = (size_t)width * (size_t)height * (size_t)depth;
140✔
3554
    converted = (unsigned char *)sixel_allocator_malloc(
140✔
3555
        encoder->allocator, size);
28✔
3556
    if (converted == NULL) {
140!
3557
        sixel_helper_set_additional_message(
×
3558
            "sixel_encoder_output_with_macro: "
3559
            "sixel_allocator_malloc() failed.");
3560
        status = SIXEL_BAD_ALLOCATION;
×
3561
        goto end;
×
3562
    }
3563

3564
    memcpy(converted, sixel_frame_get_pixels(frame), size);
140✔
3565
    output->pixelformat = pixelformat;
140✔
3566
    output->source_colorspace = frame_colorspace;
140✔
3567
    output->colorspace = encoder->output_colorspace;
140✔
3568

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

3619
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3620
            sixel_assessment_stage_transition(
×
3621
                encoder->assessment_observer,
×
3622
                SIXEL_ASSESSMENT_STAGE_ENCODE);
3623
        }
3624
        status = sixel_encode(converted,
108✔
3625
                              width,
18✔
3626
                              height,
18✔
3627
                              depth,
18✔
3628
                              dither,
18✔
3629
                              output);
18✔
3630
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3631
            sixel_assessment_stage_finish(
×
3632
                encoder->assessment_observer);
×
3633
        }
3634
        if (SIXEL_FAILED(status)) {
90!
3635
            goto end;
×
3636
        }
3637

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

3743
end:
40✔
3744
    output->pixelformat = pixelformat;
140✔
3745
    output->source_colorspace = frame_colorspace;
140✔
3746
    sixel_allocator_free(encoder->allocator, converted);
140✔
3747

3748
    return status;
140✔
3749
}
3750

3751

3752
static SIXELSTATUS
3753
sixel_encoder_emit_iso2022_chars(
×
3754
    sixel_encoder_t *encoder,
3755
    sixel_frame_t *frame
3756
)
3757
{
3758
    char *buf_p, *buf;
3759
    int col, row;
3760
    int charset;
3761
    int is_96cs;
3762
    unsigned int charset_no;
3763
    unsigned int code;
3764
    int num_cols, num_rows;
3765
    SIXELSTATUS status;
3766
    size_t alloc_size;
3767
    int nwrite;
3768
    int target_fd;
3769
    int chunk_size;
3770

3771
    charset_no = encoder->drcs_charset_no;
×
3772
    if (charset_no == 0u) {
×
3773
        charset_no = 1u;
×
3774
    }
3775
    if (encoder->drcs_mmv == 0) {
×
3776
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3777
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3778
    } else if (encoder->drcs_mmv == 1) {
×
3779
        is_96cs = 0;
×
3780
        charset = (int)(charset_no + 0x3fu);
×
3781
    } else {
3782
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3783
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3784
    }
3785
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3786
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3787
             / encoder->cell_width;
×
3788
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3789
             / encoder->cell_height;
×
3790

3791
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
3792
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
3793
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3794
    if (buf == NULL) {
×
3795
        sixel_helper_set_additional_message(
×
3796
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
3797
        status = SIXEL_BAD_ALLOCATION;
×
3798
        goto end;
×
3799
    }
3800

3801
    code = 0x20;
×
3802
    *(buf_p++) = '\016';  /* SI */
×
3803
    *(buf_p++) = '\033';
×
3804
    *(buf_p++) = ')';
×
3805
    *(buf_p++) = ' ';
×
3806
    *(buf_p++) = charset;
×
3807
    for(row = 0; row < num_rows; row++) {
×
3808
        for(col = 0; col < num_cols; col++) {
×
3809
            if ((code & 0x7f) == 0x0) {
×
3810
                if (charset == 0x7e) {
×
3811
                    is_96cs = 1 - is_96cs;
×
3812
                    charset = '0';
×
3813
                } else {
3814
                    charset++;
×
3815
                }
3816
                code = 0x20;
×
3817
                *(buf_p++) = '\033';
×
3818
                *(buf_p++) = is_96cs ? '-': ')';
×
3819
                *(buf_p++) = ' ';
×
3820
                *(buf_p++) = charset;
×
3821
            }
3822
            *(buf_p++) = code++;
×
3823
        }
3824
        *(buf_p++) = '\n';
×
3825
    }
3826
    *(buf_p++) = '\017';  /* SO */
×
3827

3828
    if (encoder->tile_outfd >= 0) {
×
3829
        target_fd = encoder->tile_outfd;
×
3830
    } else {
3831
        target_fd = encoder->outfd;
×
3832
    }
3833

3834
    chunk_size = (int)(buf_p - buf);
×
3835
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3836
                                          buf,
3837
                                          chunk_size,
3838
                                          target_fd);
3839
    if (nwrite != chunk_size) {
×
3840
        sixel_helper_set_additional_message(
×
3841
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3842
        status = SIXEL_RUNTIME_ERROR;
×
3843
        goto end;
×
3844
    }
3845

3846
    sixel_allocator_free(encoder->allocator, buf);
×
3847

3848
    status = SIXEL_OK;
×
3849

3850
end:
3851
    return status;
×
3852
}
3853

3854

3855
/*
3856
 * This routine is derived from mlterm's drcssixel.c
3857
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
3858
 * The original implementation is credited to Araki Ken.
3859
 * Adjusted here to integrate with libsixel's encoder pipeline.
3860
 */
3861
static SIXELSTATUS
3862
sixel_encoder_emit_drcsmmv2_chars(
×
3863
    sixel_encoder_t *encoder,
3864
    sixel_frame_t *frame
3865
)
3866
{
3867
    char *buf_p, *buf;
3868
    int col, row;
3869
    int charset;
3870
    int is_96cs;
3871
    unsigned int charset_no;
3872
    unsigned int code;
3873
    int num_cols, num_rows;
3874
    SIXELSTATUS status;
3875
    size_t alloc_size;
3876
    int nwrite;
3877
    int target_fd;
3878
    int chunk_size;
3879

3880
    charset_no = encoder->drcs_charset_no;
×
3881
    if (charset_no == 0u) {
×
3882
        charset_no = 1u;
×
3883
    }
3884
    if (encoder->drcs_mmv == 0) {
×
3885
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3886
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3887
    } else if (encoder->drcs_mmv == 1) {
×
3888
        is_96cs = 0;
×
3889
        charset = (int)(charset_no + 0x3fu);
×
3890
    } else {
3891
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3892
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3893
    }
3894
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3895
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3896
             / encoder->cell_width;
×
3897
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3898
             / encoder->cell_height;
×
3899

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

3910
    for(row = 0; row < num_rows; row++) {
×
3911
        for(col = 0; col < num_cols; col++) {
×
3912
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
3913
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
3914
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
3915
            *(buf_p++) = (code & 0x3f) | 0x80;
×
3916
            code++;
×
3917
            if ((code & 0x7f) == 0x0) {
×
3918
                if (charset == 0x7e) {
×
3919
                    is_96cs = 1 - is_96cs;
×
3920
                    charset = '0';
×
3921
                } else {
3922
                    charset++;
×
3923
                }
3924
                code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3925
            }
3926
        }
3927
        *(buf_p++) = '\n';
×
3928
    }
3929

3930
    if (charset == 0x7e) {
×
3931
        is_96cs = 1 - is_96cs;
×
3932
    } else {
3933
        charset = '0';
×
3934
        charset++;
×
3935
    }
3936
    if (encoder->tile_outfd >= 0) {
×
3937
        target_fd = encoder->tile_outfd;
×
3938
    } else {
3939
        target_fd = encoder->outfd;
×
3940
    }
3941

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

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

3956
    status = SIXEL_OK;
×
3957

3958
end:
3959
    return status;
×
3960
}
3961

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

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

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

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

4139
    if (assessment != NULL) {
866✔
4140
        sixel_assessment_stage_transition(
5✔
4141
            assessment,
1✔
4142
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
4143
    }
1✔
4144

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

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

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

4174
    if (assessment != NULL) {
866✔
4175
        sixel_assessment_stage_transition(
5✔
4176
            assessment,
1✔
4177
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
4178
    }
1✔
4179

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

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

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

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

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

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

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

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

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

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

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

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

4305
    if (encoder->fdrcs) {  /* -@ option */
866!
4306
        if (encoder->drcs_mmv == 0) {
×
4307
            drcs_is_96cs_param =
×
4308
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
4309
            drcs_designate_char =
×
4310
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
4311
        } else if (encoder->drcs_mmv == 1) {
×
4312
            drcs_is_96cs_param = 0;
×
4313
            drcs_designate_char =
×
4314
                (int)(encoder->drcs_charset_no + 0x3fu);
×
4315
        } else {
4316
            drcs_is_96cs_param =
×
4317
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
4318
            drcs_designate_char =
×
4319
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
4320
        }
4321
        nwrite = sprintf(buf,
×
4322
                         "%s%s1;0;0;%d;1;3;%d;%d{ %c",
4323
                         (encoder->drcs_mmv > 0) ? (
×
4324
                             encoder->f8bit ? "\233?8800h": "\033[?8800h"
×
4325
                         ): "",
4326
                         encoder->f8bit ? "\220": "\033P",
×
4327
                         encoder->cell_width,
4328
                         encoder->cell_height,
4329
                         drcs_is_96cs_param,
4330
                         drcs_designate_char);
4331
        if (nwrite < 0) {
×
4332
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4333
            sixel_helper_set_additional_message(
×
4334
                "sixel_encoder_encode_frame: command format failed.");
4335
            goto end;
×
4336
        }
4337
        nwrite = write_callback(buf, nwrite, write_priv);
×
4338
        if (nwrite < 0) {
×
4339
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4340
            sixel_helper_set_additional_message(
×
4341
                "sixel_encoder_encode_frame: write() failed.");
4342
            goto end;
×
4343
        }
4344
    }
4345

4346
    /* output sixel: junction of multi-frame processing strategy */
4347
    if (encoder->fuse_macro) {  /* -u option */
866✔
4348
        /* use macro */
4349
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
135✔
4350
    } else if (encoder->macro_number >= 0) { /* -n option */
758✔
4351
        /* use macro */
4352
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
5✔
4353
    } else {
1✔
4354
        /* do not use macro */
4355
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
726✔
4356
    }
4357
    if (SIXEL_FAILED(status)) {
866!
4358
        goto end;
×
4359
    }
4360

4361
    if (encoder->cancel_flag && *encoder->cancel_flag) {
866!
4362
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
4363
        if (nwrite < 0) {
×
4364
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4365
            sixel_helper_set_additional_message(
×
4366
                "sixel_encoder_encode_frame: write_callback() failed.");
4367
            goto end;
×
4368
        }
4369
        status = SIXEL_INTERRUPTED;
×
4370
    }
4371
    if (SIXEL_FAILED(status)) {
866!
4372
        goto end;
×
4373
    }
4374

4375
    if (encoder->fdrcs) {  /* -@ option */
866!
4376
        if (encoder->f8bit) {
×
4377
            nwrite = write_callback("\234", 1, write_priv);
×
4378
        } else {
4379
            nwrite = write_callback("\033\\", 2, write_priv);
×
4380
        }
4381
        if (nwrite < 0) {
×
4382
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4383
            sixel_helper_set_additional_message(
×
4384
                "sixel_encoder_encode_frame: write_callback() failed.");
4385
            goto end;
×
4386
        }
4387

4388
        if (encoder->tile_outfd >= 0) {
×
4389
            if (encoder->drcs_mmv == 0) {
×
4390
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
4391
                if (SIXEL_FAILED(status)) {
×
4392
                    goto end;
×
4393
                }
4394
            } else {
4395
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
4396
                if (SIXEL_FAILED(status)) {
×
4397
                    goto end;
×
4398
                }
4399
            }
4400
        }
4401
    }
4402

4403

4404
end:
692✔
4405
    if (output) {
876✔
4406
        sixel_output_unref(output);
866✔
4407
    }
174✔
4408
    if (dither) {
876✔
4409
        sixel_dither_unref(dither);
866✔
4410
    }
174✔
4411

4412
    return status;
876✔
4413
}
4414

4415

4416
/* create encoder object */
4417
SIXELAPI SIXELSTATUS
4418
sixel_encoder_new(
906✔
4419
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
4420
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
4421
                                                  default allocator */
4422
{
4423
    SIXELSTATUS status = SIXEL_FALSE;
906✔
4424
    char const *env_default_bgcolor = NULL;
906✔
4425
    char const *env_default_ncolors = NULL;
906✔
4426
    char const *env_prefer_float32 = NULL;
906✔
4427
    int ncolors;
4428
    int prefer_float32;
4429

4430
    if (allocator == NULL) {
906!
4431
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
906✔
4432
        if (SIXEL_FAILED(status)) {
906!
4433
            goto end;
×
4434
        }
4435
    } else {
182✔
4436
        sixel_allocator_ref(allocator);
×
4437
    }
4438

4439
    *ppencoder
182✔
4440
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
1,088✔
4441
                                                    sizeof(sixel_encoder_t));
4442
    if (*ppencoder == NULL) {
906!
4443
        sixel_helper_set_additional_message(
×
4444
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
4445
        status = SIXEL_BAD_ALLOCATION;
×
4446
        sixel_allocator_unref(allocator);
×
4447
        goto end;
×
4448
    }
4449

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

4538
    prefer_float32 = 0;
906✔
4539
    env_prefer_float32 = sixel_compat_getenv(
906✔
4540
        SIXEL_ENCODER_PRECISION_ENVVAR);
4541
    /*
4542
     * $SIXEL_FLOAT32_DITHER seeds the precision preference and is later
4543
     * overridden by the precision CLI flag when provided.
4544
     */
4545
    prefer_float32 = sixel_encoder_env_prefers_float32(env_prefer_float32);
906✔
4546
    (*ppencoder)->prefer_float32 = prefer_float32;
906✔
4547

4548
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
4549
    env_default_bgcolor = sixel_compat_getenv("SIXEL_BGCOLOR");
906✔
4550
    if (env_default_bgcolor != NULL) {
906!
4551
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
4552
                                         env_default_bgcolor,
4553
                                         allocator);
4554
        if (SIXEL_FAILED(status)) {
×
4555
            goto error;
×
4556
        }
4557
    }
4558

4559
    /* evaluate environment variable ${SIXEL_COLORS} */
4560
    env_default_ncolors = sixel_compat_getenv("SIXEL_COLORS");
906✔
4561
    if (env_default_ncolors) {
906!
4562
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
4563
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
4564
            (*ppencoder)->reqcolors = ncolors;
×
4565
        }
4566
    }
4567

4568
    /* success */
4569
    status = SIXEL_OK;
906✔
4570

4571
    goto end;
906✔
4572

4573
error:
4574
    sixel_allocator_free(allocator, *ppencoder);
×
4575
    sixel_allocator_unref(allocator);
×
4576
    *ppencoder = NULL;
×
4577

4578
end:
724✔
4579
    return status;
906✔
4580
}
4581

4582

4583
/* create encoder object (deprecated version) */
4584
SIXELAPI /* deprecated */ sixel_encoder_t *
4585
sixel_encoder_create(void)
×
4586
{
4587
    SIXELSTATUS status = SIXEL_FALSE;
×
4588
    sixel_encoder_t *encoder = NULL;
×
4589

4590
    status = sixel_encoder_new(&encoder, NULL);
×
4591
    if (SIXEL_FAILED(status)) {
×
4592
        return NULL;
×
4593
    }
4594

4595
    return encoder;
×
4596
}
4597

4598

4599
/* destroy encoder object */
4600
static void
4601
sixel_encoder_destroy(sixel_encoder_t *encoder)
906✔
4602
{
4603
    sixel_allocator_t *allocator;
4604

4605
    if (encoder) {
906!
4606
        allocator = encoder->allocator;
906✔
4607
        sixel_allocator_free(allocator, encoder->mapfile);
906✔
4608
        sixel_allocator_free(allocator, encoder->palette_output);
906✔
4609
        sixel_allocator_free(allocator, encoder->loader_order);
906✔
4610
        sixel_allocator_free(allocator, encoder->bgcolor);
906✔
4611
        sixel_dither_unref(encoder->dither_cache);
906✔
4612
        if (encoder->outfd
916!
4613
            && encoder->outfd != STDOUT_FILENO
906!
4614
            && encoder->outfd != STDERR_FILENO) {
222!
4615
            (void)sixel_compat_close(encoder->outfd);
50✔
4616
        }
10✔
4617
        if (encoder->tile_outfd >= 0
906!
4618
            && encoder->tile_outfd != encoder->outfd
182!
4619
            && encoder->tile_outfd != STDOUT_FILENO
×
4620
            && encoder->tile_outfd != STDERR_FILENO) {
×
4621
            (void)sixel_compat_close(encoder->tile_outfd);
×
4622
        }
4623
        if (encoder->capture_source_frame != NULL) {
906✔
4624
            sixel_frame_unref(encoder->capture_source_frame);
5✔
4625
        }
1✔
4626
        if (encoder->clipboard_output_path != NULL) {
906!
4627
            (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4628
            encoder->clipboard_output_path = NULL;
×
4629
        }
4630
        encoder->clipboard_output_active = 0;
906✔
4631
        encoder->clipboard_output_format[0] = '\0';
906✔
4632
        sixel_allocator_free(allocator, encoder->capture_pixels);
906✔
4633
        sixel_allocator_free(allocator, encoder->capture_palette);
906✔
4634
        sixel_allocator_free(allocator, encoder->png_output_path);
906✔
4635
        sixel_allocator_free(allocator, encoder->sixel_output_path);
906✔
4636
        sixel_allocator_free(allocator, encoder);
906✔
4637
        sixel_allocator_unref(allocator);
906✔
4638
    }
182✔
4639
}
906✔
4640

4641

4642
/* increase reference count of encoder object (thread-unsafe) */
4643
SIXELAPI void
4644
sixel_encoder_ref(sixel_encoder_t *encoder)
1,922✔
4645
{
4646
    /* TODO: be thread safe */
4647
    ++encoder->ref;
1,922✔
4648
}
1,922✔
4649

4650

4651
/* decrease reference count of encoder object (thread-unsafe) */
4652
SIXELAPI void
4653
sixel_encoder_unref(sixel_encoder_t *encoder)
2,828✔
4654
{
4655
    /* TODO: be thread safe */
4656
    if (encoder != NULL && --encoder->ref == 0) {
2,828!
4657
        sixel_encoder_destroy(encoder);
906✔
4658
    }
182✔
4659
}
2,828✔
4660

4661

4662
/* set cancel state flag to encoder object */
4663
SIXELAPI SIXELSTATUS
4664
sixel_encoder_set_cancel_flag(
731✔
4665
    sixel_encoder_t /* in */ *encoder,
4666
    int             /* in */ *cancel_flag
4667
)
4668
{
4669
    SIXELSTATUS status = SIXEL_OK;
731✔
4670

4671
    encoder->cancel_flag = cancel_flag;
731✔
4672

4673
    return status;
731✔
4674
}
4675

4676

4677
/*
4678
 * parse_assessment_sections() translates a comma-separated section list into
4679
 * the bitmask understood by the assessment back-end.  The accepted grammar is
4680
 * intentionally small so that the CLI contract stays predictable:
4681
 *
4682
 *     list := section {"," section}
4683
 *     section := name | name "@" view
4684
 *
4685
 * Each name maps to a bit flag while the optional view toggles the encoded vs
4686
 * quantized quality comparison.  The helper folds case, trims ASCII
4687
 * whitespace, and rejects mixed encoded/quantized requests so that the caller
4688
 * can rely on a single coherent capture strategy.
4689
 */
4690
static int
4691
parse_assessment_sections(char const *spec,
10✔
4692
                          unsigned int *out_sections)
4693
{
4694
    char *copy;
4695
    char *cursor;
4696
    char *token;
4697
    char *context;
4698
    unsigned int sections;
4699
    unsigned int view;
4700
    int result;
4701
    size_t length;
4702
    size_t spec_len;
4703
    char *at;
4704
    char *base;
4705
    char *variant;
4706
    char *p;
4707

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

4831

4832
static int
4833
is_png_target(char const *path)
51✔
4834
{
4835
    size_t len;
4836
    int matched;
4837

4838
    /*
4839
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
4840
     *
4841
     *   argument
4842
     *   |
4843
     *   v
4844
     *   .............. . p n g
4845
     *   ^             ^^^^^^^^^
4846
     *   |             +-- case-insensitive suffix comparison
4847
     *   +-- accepts the "png:" inline prefix used for stdout capture
4848
     */
4849

4850
    len = 0;
51✔
4851
    matched = 0;
51✔
4852

4853
    if (path == NULL) {
51!
4854
        return 0;
×
4855
    }
4856

4857
    if (strncmp(path, "png:", 4) == 0) {
51✔
4858
        return path[4] != '\0';
10✔
4859
    }
4860

4861
    len = strlen(path);
41✔
4862
    if (len >= 4) {
41✔
4863
        matched = (tolower((unsigned char)path[len - 4]) == '.')
37✔
4864
            && (tolower((unsigned char)path[len - 3]) == 'p')
12!
4865
            && (tolower((unsigned char)path[len - 2]) == 'n')
5!
4866
            && (tolower((unsigned char)path[len - 1]) == 'g');
40!
4867
    }
8✔
4868

4869
    return matched;
41✔
4870
}
11✔
4871

4872

4873
static char const *
4874
png_target_payload_view(char const *argument)
15✔
4875
{
4876
    /*
4877
     * Inline PNG targets split into either a prefix/payload pair or rely on
4878
     * a simple file-name suffix:
4879
     *
4880
     *   +--------------+------------+-------------+
4881
     *   | form         | payload    | destination |
4882
     *   +--------------+------------+-------------+
4883
     *   | png:         | -          | stdout      |
4884
     *   | png:         | filename   | filesystem  |
4885
     *   | *.png        | filename   | filesystem  |
4886
     *   +--------------+------------+-------------+
4887
     *
4888
     * The caller only needs the payload column, so we expose it here.  When
4889
     * the user omits the prefix we simply echo the original pointer so the
4890
     * caller can copy the value verbatim.
4891
     */
4892
    if (argument == NULL) {
15!
4893
        return NULL;
×
4894
    }
4895
    if (strncmp(argument, "png:", 4) == 0) {
15✔
4896
        return argument + 4;
10✔
4897
    }
4898

4899
    return argument;
5✔
4900
}
3✔
4901

4902
static void
4903
normalise_windows_drive_path(char *path)
15✔
4904
{
4905
#if defined(_WIN32)
4906
    size_t length;
4907

4908
    /*
4909
     * MSYS-like environments forward POSIX-looking absolute paths to native
4910
     * binaries.  When a user writes "/d/..." MSYS converts the command line to
4911
     * UTF-16 and preserves the literal bytes.  The Windows CRT, however,
4912
     * expects "d:/..." or "d:\...".  The tiny state machine below rewrites the
4913
     * leading token so the runtime resolves the drive correctly:
4914
     *
4915
     *   input     normalised
4916
     *   |         |
4917
     *   v         v
4918
     *   / d / ... d : / ...
4919
     *
4920
     * The body keeps the rest of the string intact so UNC paths ("//server")
4921
     * and relative references pass through untouched.
4922
     */
4923

4924
    length = 0u;
4925

4926
    if (path == NULL) {
4927
        return;
4928
    }
4929

4930
    length = strlen(path);
4931
    if (length >= 3u
4932
            && path[0] == '/'
4933
            && ((path[1] >= 'A' && path[1] <= 'Z')
4934
                || (path[1] >= 'a' && path[1] <= 'z'))
4935
            && path[2] == '/') {
4936
        path[0] = path[1];
4937
        path[1] = ':';
4938
    }
4939
#else
4940
    (void)path;
3✔
4941
#endif
4942
}
15✔
4943

4944

4945
static int
4946
is_dev_null_path(char const *path)
×
4947
{
4948
    if (path == NULL || path[0] == '\0') {
×
4949
        return 0;
×
4950
    }
4951
#if defined(_WIN32)
4952
    if (_stricmp(path, "nul") == 0) {
4953
        return 1;
4954
    }
4955
#endif
4956
    return strcmp(path, "/dev/null") == 0;
×
4957
}
4958

4959

4960
static int
4961
sixel_encoder_threads_token_is_auto(char const *text)
×
4962
{
4963
    if (text == NULL) {
×
4964
        return 0;
×
4965
    }
4966

4967
    if ((text[0] == 'a' || text[0] == 'A') &&
×
4968
        (text[1] == 'u' || text[1] == 'U') &&
×
4969
        (text[2] == 't' || text[2] == 'T') &&
×
4970
        (text[3] == 'o' || text[3] == 'O') &&
×
4971
        text[4] == '\0') {
×
4972
        return 1;
×
4973
    }
4974

4975
    return 0;
×
4976
}
4977

4978
static int
4979
sixel_encoder_parse_threads_argument(char const *text, int *value)
×
4980
{
4981
    long parsed;
4982
    char *endptr;
4983

4984
    parsed = 0L;
×
4985
    endptr = NULL;
×
4986

4987
    if (text == NULL || value == NULL) {
×
4988
        return 0;
×
4989
    }
4990

4991
    if (sixel_encoder_threads_token_is_auto(text) != 0) {
×
4992
        *value = 0;
×
4993
        return 1;
×
4994
    }
4995

4996
    errno = 0;
×
4997
    parsed = strtol(text, &endptr, 10);
×
4998
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
×
4999
        return 0;
×
5000
    }
5001

5002
    if (parsed < 1L || parsed > (long)INT_MAX) {
×
5003
        return 0;
×
5004
    }
5005

5006
    *value = (int)parsed;
×
5007
    return 1;
×
5008
}
5009

5010
/* set an option flag to encoder object */
5011
SIXELAPI SIXELSTATUS
5012
sixel_encoder_setopt(
1,196✔
5013
    sixel_encoder_t /* in */ *encoder,
5014
    int             /* in */ arg,
5015
    char const      /* in */ *value)
5016
{
5017
    SIXELSTATUS status = SIXEL_FALSE;
1,196✔
5018
    int number;
5019
    int parsed;
5020
    char unit[32];
5021
    char lowered[16];
5022
    size_t len;
5023
    size_t i;
5024
    long parsed_reqcolors;
5025
    char *endptr;
5026
    int forced_palette;
5027
    char *opt_copy;
5028
    char const *drcs_arg_delim;
5029
    char const *drcs_arg_charset;
5030
    char const *drcs_arg_second_delim;
5031
    char const *drcs_arg_path;
5032
    size_t drcs_arg_path_length;
5033
    size_t drcs_segment_length;
5034
    char drcs_segment[32];
5035
    int drcs_mmv_value;
5036
    long drcs_charset_value;
5037
    unsigned int drcs_charset_limit;
5038
    sixel_option_choice_result_t match_result;
5039
    int match_value;
5040
    char match_detail[128];
5041
    char match_message[256];
5042
    int png_argument_has_prefix = 0;
1,196✔
5043
    char const *png_path_view = NULL;
1,196✔
5044
    size_t png_path_length;
5045
    char cell_message[256];
5046
    char const *cell_detail;
5047
    unsigned int path_flags;
5048
    char const *mapfile_view;
5049
    int path_check;
5050

5051
    sixel_encoder_ref(encoder);
1,196✔
5052
    opt_copy = NULL;
1,196✔
5053
    path_flags = 0u;
1,196✔
5054
    mapfile_view = NULL;
1,196✔
5055
    path_check = 0;
1,196✔
5056

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

5155
                clipboard_spec.is_clipboard = 0;
36✔
5156
                clipboard_spec.format[0] = '\0';
36✔
5157
                clip_status = SIXEL_OK;
36✔
5158
                spool_path = NULL;
36✔
5159
                spool_fd = (-1);
36✔
5160

5161
                if (sixel_clipboard_parse_spec(value, &clipboard_spec)
36!
5162
                        && clipboard_spec.is_clipboard) {
8!
5163
                    clip_status = clipboard_create_spool(
1✔
5164
                        encoder->allocator,
1✔
5165
                        "clipboard-out",
5166
                        &spool_path,
5167
                        &spool_fd);
5168
                    if (SIXEL_FAILED(clip_status)) {
1!
5169
                        status = clip_status;
×
5170
                        goto end;
×
5171
                    }
5172
                    clipboard_select_format(
1✔
5173
                        encoder->clipboard_output_format,
1✔
5174
                        sizeof(encoder->clipboard_output_format),
5175
                        clipboard_spec.format,
1✔
5176
                        "sixel");
5177
                    if (encoder->outfd
1!
5178
                            && encoder->outfd != STDOUT_FILENO
1!
5179
                            && encoder->outfd != STDERR_FILENO) {
1!
5180
                        (void)sixel_compat_close(encoder->outfd);
×
5181
                    }
5182
                    encoder->outfd = spool_fd;
1✔
5183
                    spool_fd = (-1);
1✔
5184
                    encoder->sixel_output_path = spool_path;
1✔
5185
                    encoder->clipboard_output_path = spool_path;
1✔
5186
                    spool_path = NULL;
1✔
5187
                    encoder->clipboard_output_active = 1;
1✔
5188
                    break;
1✔
5189
                }
5190

5191
                if (spool_fd >= 0) {
35!
5192
                    (void)sixel_compat_close(spool_fd);
×
5193
                }
5194
                if (spool_path != NULL) {
35!
5195
                    sixel_allocator_free(encoder->allocator, spool_path);
×
5196
                }
5197
            }
5198
            if (strcmp(value, "-") != 0) {
35!
5199
                encoder->sixel_output_path = (char *)sixel_allocator_malloc(
63✔
5200
                    encoder->allocator, strlen(value) + 1);
35✔
5201
                if (encoder->sixel_output_path == NULL) {
35!
5202
                    sixel_helper_set_additional_message(
×
5203
                        "sixel_encoder_setopt: malloc() failed for output path.");
5204
                    status = SIXEL_BAD_ALLOCATION;
×
5205
                    goto end;
×
5206
                }
5207
                (void)sixel_compat_strcpy(encoder->sixel_output_path,
42✔
5208
                                          strlen(value) + 1,
35✔
5209
                                          value);
7✔
5210
            }
7✔
5211
        }
5212

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

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

6185
            match_result = sixel_option_match_choice(
×
6186
                lowered,
6187
                g_option_choices_working_colorspace,
6188
                sizeof(g_option_choices_working_colorspace) /
6189
                sizeof(g_option_choices_working_colorspace[0]),
6190
                &match_value,
6191
                match_detail,
6192
                sizeof(match_detail));
6193
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6194
                encoder->working_colorspace = match_value;
×
6195
            } else {
6196
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6197
                    sixel_option_report_ambiguous_prefix(value,
×
6198
                        match_detail,
6199
                        match_message,
6200
                        sizeof(match_message));
6201
                } else {
6202
                    sixel_option_report_invalid_choice(
×
6203
                        "unsupported working colorspace specified.",
6204
                        match_detail,
6205
                        match_message,
6206
                        sizeof(match_message));
6207
                }
6208
                status = SIXEL_BAD_ARGUMENT;
×
6209
                goto end;
×
6210
            }
6211
        }
6212
        break;
×
6213
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
6214
        if (value == NULL) {
×
6215
            sixel_helper_set_additional_message(
×
6216
                "output-colorspace requires an argument.");
6217
            status = SIXEL_BAD_ARGUMENT;
×
6218
            goto end;
×
6219
        } else {
6220
            len = strlen(value);
×
6221

6222
            if (len >= sizeof(lowered)) {
×
6223
                sixel_helper_set_additional_message(
×
6224
                    "specified output colorspace name is too long.");
6225
                status = SIXEL_BAD_ARGUMENT;
×
6226
                goto end;
×
6227
            }
6228
            for (i = 0; i < len; ++i) {
×
6229
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6230
            }
6231
            lowered[len] = '\0';
×
6232

6233
            match_result = sixel_option_match_choice(
×
6234
                lowered,
6235
                g_option_choices_output_colorspace,
6236
                sizeof(g_option_choices_output_colorspace) /
6237
                sizeof(g_option_choices_output_colorspace[0]),
6238
                &match_value,
6239
                match_detail,
6240
                sizeof(match_detail));
6241
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6242
                encoder->output_colorspace = match_value;
×
6243
            } else {
6244
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6245
                    sixel_option_report_ambiguous_prefix(value,
×
6246
                        match_detail,
6247
                        match_message,
6248
                        sizeof(match_message));
6249
                } else {
6250
                    sixel_option_report_invalid_choice(
×
6251
                        "unsupported output colorspace specified.",
6252
                        match_detail,
6253
                        match_message,
6254
                        sizeof(match_message));
6255
                }
6256
                status = SIXEL_BAD_ARGUMENT;
×
6257
                goto end;
×
6258
            }
6259
        }
6260
        break;
×
6261
    case SIXEL_OPTFLAG_ORMODE:  /* O */
6262
        encoder->ormode = 1;
×
6263
        break;
×
6264
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
12✔
6265
        encoder->complexion = atoi(value);
15✔
6266
        if (encoder->complexion < 1) {
15✔
6267
            sixel_helper_set_additional_message(
5✔
6268
                "complexion parameter must be 1 or more.");
6269
            status = SIXEL_BAD_ARGUMENT;
5✔
6270
            goto end;
5✔
6271
        }
6272
        break;
10✔
6273
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
6274
        encoder->pipe_mode = 1;
×
6275
        break;
×
6276
    case '?':  /* unknown option */
×
6277
    default:
6278
        /* exit if unknown options are specified */
6279
        sixel_helper_set_additional_message(
×
6280
            "unknown option is specified.");
6281
        status = SIXEL_BAD_ARGUMENT;
×
6282
        goto end;
×
6283
    }
6284

6285
    /* detects arguments conflictions */
6286
    if (encoder->reqcolors != (-1)) {
1,066✔
6287
        switch (encoder->color_option) {
165!
6288
        case SIXEL_COLOR_OPTION_MAPFILE:
6289
            sixel_helper_set_additional_message(
×
6290
                "option -p, --colors conflicts with -m, --mapfile.");
6291
            status = SIXEL_BAD_ARGUMENT;
×
6292
            goto end;
×
6293
        case SIXEL_COLOR_OPTION_MONOCHROME:
4✔
6294
            sixel_helper_set_additional_message(
5✔
6295
                "option -e, --monochrome conflicts with -p, --colors.");
6296
            status = SIXEL_BAD_ARGUMENT;
5✔
6297
            goto end;
5✔
6298
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
4✔
6299
            sixel_helper_set_additional_message(
5✔
6300
                "option -p, --colors conflicts with -I, --high-color.");
6301
            status = SIXEL_BAD_ARGUMENT;
5✔
6302
            goto end;
5✔
6303
        case SIXEL_COLOR_OPTION_BUILTIN:
4✔
6304
            sixel_helper_set_additional_message(
5✔
6305
                "option -p, --colors conflicts with -b, --builtin-palette.");
6306
            status = SIXEL_BAD_ARGUMENT;
5✔
6307
            goto end;
5✔
6308
        default:
120✔
6309
            break;
150✔
6310
        }
6311
    }
30✔
6312

6313
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
6314
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
1,051✔
6315
        sixel_helper_set_additional_message(
5✔
6316
            "option -8 --8bit-mode conflicts"
6317
            " with -P, --penetrate.");
6318
        status = SIXEL_BAD_ARGUMENT;
5✔
6319
        goto end;
5✔
6320
    }
6321

6322
    status = SIXEL_OK;
1,046✔
6323

6324
end:
956✔
6325
    if (opt_copy != NULL) {
1,196!
6326
        sixel_allocator_free(encoder->allocator, opt_copy);
×
6327
    }
6328
    sixel_encoder_unref(encoder);
1,196✔
6329

6330
    return status;
1,196✔
6331
}
6332

6333

6334
/* called when image loader component load a image frame */
6335
static SIXELSTATUS
6336
load_image_callback(sixel_frame_t *frame, void *data)
876✔
6337
{
6338
    sixel_encoder_t *encoder;
6339

6340
    encoder = (sixel_encoder_t *)data;
876✔
6341
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
876!
6342
        sixel_frame_ref(frame);
5✔
6343
        encoder->capture_source_frame = frame;
5✔
6344
    }
1✔
6345

6346
    return sixel_encoder_encode_frame(encoder, frame, NULL);
876✔
6347
}
6348

6349
/*
6350
 * Build a tee for encoded-assessment output:
6351
 *
6352
 *     +-------------+     +-------------------+     +------------+
6353
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
6354
 *     +-------------+     +-------------------+     +------------+
6355
 *
6356
 * The tee sink can be stdout or a user-provided file such as /dev/null.
6357
 */
6358
static SIXELSTATUS
6359
copy_file_to_stream(char const *path,
×
6360
                    FILE *stream,
6361
                    sixel_assessment_t *assessment)
6362
{
6363
    FILE *source;
6364
    unsigned char buffer[4096];
6365
    size_t nread;
6366
    size_t nwritten;
6367
    double started_at;
6368
    double finished_at;
6369
    double duration;
6370

6371
    source = NULL;
×
6372
    nread = 0;
×
6373
    nwritten = 0;
×
6374
    started_at = 0.0;
×
6375
    finished_at = 0.0;
×
6376
    duration = 0.0;
×
6377

6378
    source = sixel_compat_fopen(path, "rb");
×
6379
    if (source == NULL) {
×
6380
        sixel_helper_set_additional_message(
×
6381
            "copy_file_to_stream: failed to open assessment staging file.");
6382
        return SIXEL_LIBC_ERROR;
×
6383
    }
6384

6385
    for (;;) {
6386
        nread = fread(buffer, 1, sizeof(buffer), source);
×
6387
        if (nread == 0) {
×
6388
            if (ferror(source)) {
×
6389
                sixel_helper_set_additional_message(
×
6390
                    "copy_file_to_stream: failed while reading assessment staging file.");
6391
                (void) fclose(source);
×
6392
                return SIXEL_LIBC_ERROR;
×
6393
            }
6394
            break;
×
6395
        }
6396
        if (assessment != NULL) {
×
6397
            started_at = sixel_assessment_timer_now();
×
6398
        }
6399
        nwritten = fwrite(buffer, 1, nread, stream);
×
6400
        if (nwritten != nread) {
×
6401
            sixel_helper_set_additional_message(
×
6402
                "img2sixel: failed while copying assessment staging file.");
6403
            (void) fclose(source);
×
6404
            return SIXEL_LIBC_ERROR;
×
6405
        }
6406
        if (assessment != NULL) {
×
6407
            finished_at = sixel_assessment_timer_now();
×
6408
            duration = finished_at - started_at;
×
6409
            if (duration < 0.0) {
×
6410
                duration = 0.0;
×
6411
            }
6412
            sixel_assessment_record_output_write(assessment,
×
6413
                                                 nwritten,
6414
                                                 duration);
6415
        }
6416
    }
6417

6418
    if (fclose(source) != 0) {
×
6419
        sixel_helper_set_additional_message(
×
6420
            "img2sixel: failed to close assessment staging file.");
6421
        return SIXEL_LIBC_ERROR;
×
6422
    }
6423

6424
    return SIXEL_OK;
×
6425
}
6426

6427
typedef struct assessment_json_sink {
6428
    FILE *stream;
6429
    int failed;
6430
} assessment_json_sink_t;
6431

6432
static void
6433
assessment_json_callback(char const *chunk,
70✔
6434
                         size_t length,
6435
                         void *user_data)
6436
{
6437
    assessment_json_sink_t *sink;
6438

6439
    sink = (assessment_json_sink_t *)user_data;
70✔
6440
    if (sink == NULL || sink->stream == NULL) {
70!
6441
        return;
×
6442
    }
6443
    if (sink->failed) {
70!
6444
        return;
×
6445
    }
6446
    if (fwrite(chunk, 1, length, sink->stream) != length) {
70!
6447
        sink->failed = 1;
×
6448
    }
6449
}
14✔
6450

6451
static char *
6452
create_temp_template_with_prefix(sixel_allocator_t *allocator,
17✔
6453
                                 char const *prefix,
6454
                                 size_t *capacity_out)
6455
{
6456
    char const *tmpdir;
6457
    size_t tmpdir_len;
6458
    size_t prefix_len;
6459
    size_t suffix_len;
6460
    size_t template_len;
6461
    char *template_path;
6462
    int needs_separator;
6463
    size_t maximum_tmpdir_len;
6464

6465
    tmpdir = sixel_compat_getenv("TMPDIR");
17✔
6466
#if defined(_WIN32)
6467
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6468
        tmpdir = sixel_compat_getenv("TEMP");
6469
    }
6470
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6471
        tmpdir = sixel_compat_getenv("TMP");
6472
    }
6473
#endif
6474
    if (tmpdir == NULL || tmpdir[0] == '\0') {
17!
6475
#if defined(_WIN32)
6476
        tmpdir = ".";
6477
#else
6478
        tmpdir = "/tmp";
12✔
6479
#endif
6480
    }
6481

6482
    tmpdir_len = strlen(tmpdir);
17✔
6483
    prefix_len = 0u;
17✔
6484
    suffix_len = 0u;
17✔
6485
    if (prefix == NULL) {
17!
6486
        return NULL;
×
6487
    }
6488

6489
    prefix_len = strlen(prefix);
17✔
6490
    suffix_len = prefix_len + strlen("-XXXXXX");
17✔
6491
    maximum_tmpdir_len = (size_t)INT_MAX;
17✔
6492

6493
    if (maximum_tmpdir_len <= suffix_len + 2) {
17!
6494
        return NULL;
×
6495
    }
6496
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
17!
6497
        return NULL;
×
6498
    }
6499
    needs_separator = 1;
17✔
6500
    if (tmpdir_len > 0) {
17!
6501
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
17!
6502
            needs_separator = 0;
5✔
6503
        }
5✔
6504
    }
5✔
6505

6506
    template_len = tmpdir_len + suffix_len + 2;
17✔
6507
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
17✔
6508
    if (template_path == NULL) {
17!
6509
        return NULL;
×
6510
    }
6511

6512
    if (needs_separator) {
17!
6513
#if defined(_WIN32)
6514
        (void) snprintf(template_path, template_len,
6515
                        "%s\\%s-XXXXXX", tmpdir, prefix);
6516
#else
6517
        (void) snprintf(template_path, template_len,
12✔
6518
                        "%s/%s-XXXXXX", tmpdir, prefix);
6519
#endif
6520
    } else {
6521
        (void) snprintf(template_path, template_len,
5✔
6522
                        "%s%s-XXXXXX", tmpdir, prefix);
6523
    }
6524

6525
    if (capacity_out != NULL) {
17!
6526
        *capacity_out = template_len;
17✔
6527
    }
5✔
6528

6529
    return template_path;
17✔
6530
}
5✔
6531

6532

6533
static char *
6534
create_temp_template(sixel_allocator_t *allocator,
15✔
6535
                     size_t *capacity_out)
6536
{
6537
    return create_temp_template_with_prefix(allocator,
18✔
6538
                                            "img2sixel",
6539
                                            capacity_out);
3✔
6540
}
6541

6542

6543
static void
6544
clipboard_select_format(char *dest,
2✔
6545
                        size_t dest_size,
6546
                        char const *format,
6547
                        char const *fallback)
6548
{
6549
    char const *source;
6550
    size_t limit;
6551

6552
    if (dest == NULL || dest_size == 0u) {
2!
6553
        return;
×
6554
    }
6555

6556
    source = fallback;
2✔
6557
    if (format != NULL && format[0] != '\0') {
2!
6558
        source = format;
×
6559
    }
6560

6561
    limit = dest_size - 1u;
2✔
6562
    if (limit == 0u) {
2!
6563
        dest[0] = '\0';
×
6564
        return;
×
6565
    }
6566

6567
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
2✔
6568
}
2✔
6569

6570

6571
static SIXELSTATUS
6572
clipboard_create_spool(sixel_allocator_t *allocator,
2✔
6573
                       char const *prefix,
6574
                       char **path_out,
6575
                       int *fd_out)
6576
{
6577
    SIXELSTATUS status;
6578
    char *template_path;
6579
    size_t template_capacity;
6580
    int open_flags;
6581
    int fd;
6582
    char *tmpname_result;
6583

6584
    status = SIXEL_FALSE;
2✔
6585
    template_path = NULL;
2✔
6586
    template_capacity = 0u;
2✔
6587
    open_flags = 0;
2✔
6588
    fd = (-1);
2✔
6589
    tmpname_result = NULL;
2✔
6590

6591
    template_path = create_temp_template_with_prefix(allocator,
4✔
6592
                                                     prefix,
2✔
6593
                                                     &template_capacity);
6594
    if (template_path == NULL) {
2!
6595
        sixel_helper_set_additional_message(
×
6596
            "clipboard: failed to allocate spool template.");
6597
        status = SIXEL_BAD_ALLOCATION;
×
6598
        goto end;
×
6599
    }
6600

6601
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
2!
6602
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
6603
        tmpname_result = tmpnam(template_path);
×
6604
        if (tmpname_result == NULL) {
×
6605
            sixel_helper_set_additional_message(
×
6606
                "clipboard: failed to reserve spool template.");
6607
            status = SIXEL_LIBC_ERROR;
×
6608
            goto end;
×
6609
        }
6610
        template_capacity = strlen(template_path) + 1u;
×
6611
    }
6612

6613
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
2✔
6614
#if defined(O_EXCL)
6615
    open_flags |= O_EXCL;
2✔
6616
#endif
6617
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
2✔
6618
    if (fd < 0) {
2!
6619
        sixel_helper_set_additional_message(
×
6620
            "clipboard: failed to open spool file.");
6621
        status = SIXEL_LIBC_ERROR;
×
6622
        goto end;
×
6623
    }
6624

6625
    *path_out = template_path;
2✔
6626
    if (fd_out != NULL) {
2!
6627
        *fd_out = fd;
1✔
6628
        fd = (-1);
1✔
6629
    }
1✔
6630

6631
    template_path = NULL;
2✔
6632
    status = SIXEL_OK;
2✔
6633

6634
end:
6635
    if (fd >= 0) {
2!
6636
        (void)sixel_compat_close(fd);
1✔
6637
    }
1✔
6638
    if (template_path != NULL) {
2!
6639
        sixel_allocator_free(allocator, template_path);
×
6640
    }
6641

6642
    return status;
2✔
6643
}
6644

6645

6646
static SIXELSTATUS
6647
clipboard_write_file(char const *path,
1✔
6648
                     unsigned char const *data,
6649
                     size_t size)
6650
{
6651
    FILE *stream;
6652
    size_t written;
6653

6654
    if (path == NULL) {
1!
6655
        sixel_helper_set_additional_message(
×
6656
            "clipboard: spool path is null.");
6657
        return SIXEL_BAD_ARGUMENT;
×
6658
    }
6659

6660
    stream = sixel_compat_fopen(path, "wb");
1✔
6661
    if (stream == NULL) {
1!
6662
        sixel_helper_set_additional_message(
×
6663
            "clipboard: failed to open spool file for write.");
6664
        return SIXEL_LIBC_ERROR;
×
6665
    }
6666

6667
    written = 0u;
1✔
6668
    if (size > 0u && data != NULL) {
1!
6669
        written = fwrite(data, 1u, size, stream);
1✔
6670
        if (written != size) {
1!
6671
            (void)fclose(stream);
×
6672
            sixel_helper_set_additional_message(
×
6673
                "clipboard: failed to write spool payload.");
6674
            return SIXEL_LIBC_ERROR;
×
6675
        }
6676
    }
1✔
6677

6678
    if (fclose(stream) != 0) {
1!
6679
        sixel_helper_set_additional_message(
×
6680
            "clipboard: failed to close spool file after write.");
6681
        return SIXEL_LIBC_ERROR;
×
6682
    }
6683

6684
    return SIXEL_OK;
1✔
6685
}
1✔
6686

6687

6688
static SIXELSTATUS
6689
clipboard_read_file(char const *path,
1✔
6690
                    unsigned char **data,
6691
                    size_t *size)
6692
{
6693
    FILE *stream;
6694
    long seek_result;
6695
    long file_size;
6696
    unsigned char *buffer;
6697
    size_t read_size;
6698

6699
    if (data == NULL || size == NULL) {
1!
6700
        sixel_helper_set_additional_message(
×
6701
            "clipboard: read buffer pointers are null.");
6702
        return SIXEL_BAD_ARGUMENT;
×
6703
    }
6704

6705
    *data = NULL;
1✔
6706
    *size = 0u;
1✔
6707

6708
    if (path == NULL) {
1!
6709
        sixel_helper_set_additional_message(
×
6710
            "clipboard: spool path is null.");
6711
        return SIXEL_BAD_ARGUMENT;
×
6712
    }
6713

6714
    stream = sixel_compat_fopen(path, "rb");
1✔
6715
    if (stream == NULL) {
1!
6716
        sixel_helper_set_additional_message(
×
6717
            "clipboard: failed to open spool file for read.");
6718
        return SIXEL_LIBC_ERROR;
×
6719
    }
6720

6721
    seek_result = fseek(stream, 0L, SEEK_END);
1✔
6722
    if (seek_result != 0) {
1!
6723
        (void)fclose(stream);
×
6724
        sixel_helper_set_additional_message(
×
6725
            "clipboard: failed to seek spool file.");
6726
        return SIXEL_LIBC_ERROR;
×
6727
    }
6728

6729
    file_size = ftell(stream);
1✔
6730
    if (file_size < 0) {
1!
6731
        (void)fclose(stream);
×
6732
        sixel_helper_set_additional_message(
×
6733
            "clipboard: failed to determine spool size.");
6734
        return SIXEL_LIBC_ERROR;
×
6735
    }
6736

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

6745
    if (file_size == 0) {
1!
6746
        buffer = NULL;
×
6747
        read_size = 0u;
×
6748
    } else {
6749
        buffer = (unsigned char *)malloc((size_t)file_size);
1✔
6750
        if (buffer == NULL) {
1!
6751
            (void)fclose(stream);
×
6752
            sixel_helper_set_additional_message(
×
6753
                "clipboard: malloc() failed for spool payload.");
6754
            return SIXEL_BAD_ALLOCATION;
×
6755
        }
6756
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
1✔
6757
        if (read_size != (size_t)file_size) {
1!
6758
            free(buffer);
×
6759
            (void)fclose(stream);
×
6760
            sixel_helper_set_additional_message(
×
6761
                "clipboard: failed to read spool payload.");
6762
            return SIXEL_LIBC_ERROR;
×
6763
        }
6764
    }
6765

6766
    if (fclose(stream) != 0) {
1!
6767
        if (buffer != NULL) {
×
6768
            free(buffer);
×
6769
        }
6770
        sixel_helper_set_additional_message(
×
6771
            "clipboard: failed to close spool file after read.");
6772
        return SIXEL_LIBC_ERROR;
×
6773
    }
6774

6775
    *data = buffer;
1✔
6776
    *size = read_size;
1✔
6777

6778
    return SIXEL_OK;
1✔
6779
}
1✔
6780

6781

6782
static SIXELSTATUS
6783
write_png_from_sixel(char const *sixel_path, char const *output_path)
15✔
6784
{
6785
    SIXELSTATUS status;
6786
    sixel_decoder_t *decoder;
6787

6788
    status = SIXEL_FALSE;
15✔
6789
    decoder = NULL;
15✔
6790

6791
    status = sixel_decoder_new(&decoder, NULL);
15✔
6792
    if (SIXEL_FAILED(status)) {
15!
6793
        goto end;
×
6794
    }
6795

6796
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
15✔
6797
    if (SIXEL_FAILED(status)) {
15!
6798
        goto end;
×
6799
    }
6800

6801
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
15✔
6802
    if (SIXEL_FAILED(status)) {
15!
6803
        goto end;
×
6804
    }
6805

6806
    status = sixel_decoder_decode(decoder);
15✔
6807

6808
end:
12✔
6809
    sixel_decoder_unref(decoder);
15✔
6810

6811
    return status;
15✔
6812
}
6813

6814

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

6871
    clipboard_input_format[0] = '\0';
726✔
6872
    clipboard_input_path = NULL;
726✔
6873
    clipboard_blob = NULL;
726✔
6874
    clipboard_blob_size = 0u;
726✔
6875
    clipboard_status = SIXEL_OK;
726✔
6876
    effective_filename = filename;
726✔
6877
    path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
726✔
6878
        SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
6879
        SIXEL_OPTION_PATH_ALLOW_REMOTE;
6880
    path_check = 0;
726✔
6881
    logger_prepared = 0;
726✔
6882
    sixel_logger_init(&logger);
726✔
6883
    sixel_logger_prepare_env(&logger);
726✔
6884
    logger_prepared = logger.active;
726✔
6885
    if (encoder != NULL) {
726!
6886
        encoder->logger = &logger;
726✔
6887
        encoder->parallel_job_id = -1;
726✔
6888
    }
146✔
6889

6890
    if (filename != NULL) {
726✔
6891
        path_check = sixel_option_validate_filesystem_path(
491✔
6892
            filename,
99✔
6893
            filename,
99✔
6894
            path_flags);
99✔
6895
        if (path_check != 0) {
491!
6896
            status = SIXEL_BAD_ARGUMENT;
×
6897
            goto end;
×
6898
        }
6899
    }
99✔
6900

6901
    if (encoder != NULL) {
726!
6902
        encode_allocator = encoder->allocator;
726✔
6903
        if (encode_allocator != NULL) {
726!
6904
            /*
6905
             * Hold a reference until cleanup so worker side-effects or loader
6906
             * destruction cannot release the allocator before sequential
6907
             * teardown finishes using it.
6908
             */
6909
            sixel_allocator_ref(encode_allocator);
726✔
6910
        }
146✔
6911
    }
146✔
6912

6913
    clipboard_spec.is_clipboard = 0;
726✔
6914
    clipboard_spec.format[0] = '\0';
726✔
6915
    if (effective_filename != NULL
727!
6916
            && sixel_clipboard_parse_spec(effective_filename,
538!
6917
                                          &clipboard_spec)
6918
            && clipboard_spec.is_clipboard) {
99!
6919
        clipboard_select_format(clipboard_input_format,
2✔
6920
                                sizeof(clipboard_input_format),
6921
                                clipboard_spec.format,
1✔
6922
                                "sixel");
6923
        clipboard_status = sixel_clipboard_read(
1✔
6924
            clipboard_input_format,
1✔
6925
            &clipboard_blob,
6926
            &clipboard_blob_size,
6927
            encoder->allocator);
1✔
6928
        if (SIXEL_FAILED(clipboard_status)) {
1!
6929
            status = clipboard_status;
×
6930
            goto end;
×
6931
        }
6932
        clipboard_status = clipboard_create_spool(
1✔
6933
            encoder->allocator,
1✔
6934
            "clipboard-in",
6935
            &clipboard_input_path,
6936
            NULL);
6937
        if (SIXEL_FAILED(clipboard_status)) {
1!
6938
            status = clipboard_status;
×
6939
            goto end;
×
6940
        }
6941
        clipboard_status = clipboard_write_file(
1✔
6942
            clipboard_input_path,
1✔
6943
            clipboard_blob,
1✔
6944
            clipboard_blob_size);
1✔
6945
        if (SIXEL_FAILED(clipboard_status)) {
1!
6946
            status = clipboard_status;
×
6947
            goto end;
×
6948
        }
6949
        if (clipboard_blob != NULL) {
1!
6950
            free(clipboard_blob);
1✔
6951
            clipboard_blob = NULL;
1✔
6952
        }
1✔
6953
        effective_filename = clipboard_input_path;
1✔
6954
    }
1✔
6955

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

7071
    }
1✔
7072

7073
    if (encoder->output_is_png) {
726✔
7074
        png_temp_capacity = 0u;
15✔
7075
        png_tmpnam_result = NULL;
15✔
7076
        png_temp_path = create_temp_template(encoder->allocator,
15✔
7077
                                             &png_temp_capacity);
7078
        if (png_temp_path == NULL) {
15!
7079
            sixel_helper_set_additional_message(
×
7080
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
7081
            status = SIXEL_BAD_ALLOCATION;
×
7082
            goto end;
×
7083
        }
7084
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
15!
7085
            /* Fall back to tmpnam() when mktemp variants are unavailable. */
7086
            png_tmpnam_result = tmpnam(png_temp_path);
×
7087
            if (png_tmpnam_result == NULL) {
×
7088
                sixel_helper_set_additional_message(
×
7089
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
7090
                status = SIXEL_RUNTIME_ERROR;
×
7091
                goto end;
×
7092
            }
7093
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
7094
        }
7095
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
15!
7096
            (void)sixel_compat_close(encoder->outfd);
15✔
7097
        }
3✔
7098
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
15✔
7099
#if defined(O_EXCL)
7100
        png_open_flags |= O_EXCL;
15✔
7101
#endif
7102
        encoder->outfd = sixel_compat_open(png_temp_path,
18✔
7103
                                           png_open_flags,
3✔
7104
                                           S_IRUSR | S_IWUSR);
7105
        if (encoder->outfd < 0) {
15!
7106
            sixel_helper_set_additional_message(
×
7107
                "sixel_encoder_encode: failed to create the PNG target file.");
7108
            status = SIXEL_LIBC_ERROR;
×
7109
            goto end;
×
7110
        }
7111
    }
3✔
7112

7113
    if (encoder == NULL) {
726!
7114
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7115
#  pragma GCC diagnostic push
7116
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7117
#endif
7118
        encoder = sixel_encoder_create();
×
7119
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7120
#  pragma GCC diagnostic pop
7121
#endif
7122
        if (encoder == NULL) {
×
7123
            sixel_helper_set_additional_message(
×
7124
                "sixel_encoder_encode: sixel_encoder_create() failed.");
7125
            status = SIXEL_BAD_ALLOCATION;
×
7126
            goto end;
×
7127
        }
7128
    } else {
7129
        sixel_encoder_ref(encoder);
726✔
7130
    }
7131

7132
    if (encode_allocator == NULL && encoder != NULL) {
726!
7133
        encode_allocator = encoder->allocator;
×
7134
        if (encode_allocator != NULL) {
×
7135
            /* Ensure the allocator stays valid after lazy encoder creation. */
7136
            sixel_allocator_ref(encode_allocator);
×
7137
        }
7138
    }
7139

7140
    if (encoder->assessment_observer != NULL) {
726✔
7141
        sixel_assessment_stage_transition(
5✔
7142
            encoder->assessment_observer,
5✔
7143
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
7144
    }
1✔
7145
    encoder->last_loader_name[0] = '\0';
726✔
7146
    encoder->last_source_path[0] = '\0';
726✔
7147
    encoder->last_input_bytes = 0u;
726✔
7148

7149
    /* if required color is not set, set the max value */
7150
    if (encoder->reqcolors == (-1)) {
726✔
7151
        encoder->reqcolors = SIXEL_PALETTE_MAX;
696✔
7152
    }
140✔
7153

7154
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
726!
7155
        sixel_frame_unref(encoder->capture_source_frame);
×
7156
        encoder->capture_source_frame = NULL;
×
7157
    }
7158

7159
    /* if required color is less then 2, set the min value */
7160
    if (encoder->reqcolors < 2) {
726✔
7161
        encoder->reqcolors = SIXEL_PALETTE_MIN;
5✔
7162
    }
1✔
7163

7164
    /* if color space option is not set, choose RGB color space */
7165
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
726✔
7166
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
696✔
7167
    }
140✔
7168

7169
    /* if color option is not default value, prohibit to read
7170
       the file as a paletted image */
7171
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
726✔
7172
        fuse_palette = 0;
160✔
7173
    }
32✔
7174

7175
    /* if scaling options are set, prohibit to read the file as
7176
       a paletted image */
7177
    if (encoder->percentwidth > 0 ||
849✔
7178
        encoder->percentheight > 0 ||
706✔
7179
        encoder->pixelwidth > 0 ||
696✔
7180
        encoder->pixelheight > 0) {
644✔
7181
        fuse_palette = 0;
165✔
7182
    }
33✔
7183

7184
reload:
580✔
7185

7186
    sixel_helper_set_loader_trace(encoder->verbose);
726✔
7187
    sixel_helper_set_thumbnail_size_hint(
726✔
7188
        sixel_encoder_thumbnail_hint(encoder));
146✔
7189

7190
    status = sixel_loader_new(&loader, encoder->allocator);
726✔
7191
    if (SIXEL_FAILED(status)) {
726!
7192
        goto load_end;
×
7193
    }
7194

7195
    status = sixel_loader_setopt(loader,
872✔
7196
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
7197
                                 &encoder->fstatic);
726✔
7198
    if (SIXEL_FAILED(status)) {
726!
7199
        goto load_end;
×
7200
    }
7201

7202
    status = sixel_loader_setopt(loader,
726✔
7203
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
7204
                                 &fuse_palette);
7205
    if (SIXEL_FAILED(status)) {
726!
7206
        goto load_end;
×
7207
    }
7208

7209
    status = sixel_loader_setopt(loader,
872✔
7210
                                 SIXEL_LOADER_OPTION_REQCOLORS,
7211
                                 &encoder->reqcolors);
726✔
7212
    if (SIXEL_FAILED(status)) {
726!
7213
        goto load_end;
×
7214
    }
7215

7216
    status = sixel_loader_setopt(loader,
872✔
7217
                                 SIXEL_LOADER_OPTION_BGCOLOR,
7218
                                 encoder->bgcolor);
726✔
7219
    if (SIXEL_FAILED(status)) {
726!
7220
        goto load_end;
×
7221
    }
7222

7223
    status = sixel_loader_setopt(loader,
872✔
7224
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
7225
                                 &encoder->loop_mode);
726✔
7226
    if (SIXEL_FAILED(status)) {
726!
7227
        goto load_end;
×
7228
    }
7229

7230
    status = sixel_loader_setopt(loader,
872✔
7231
                                 SIXEL_LOADER_OPTION_INSECURE,
7232
                                 &encoder->finsecure);
726✔
7233
    if (SIXEL_FAILED(status)) {
726!
7234
        goto load_end;
×
7235
    }
7236

7237
    status = sixel_loader_setopt(loader,
872✔
7238
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
7239
                                 encoder->cancel_flag);
726✔
7240
    if (SIXEL_FAILED(status)) {
726!
7241
        goto load_end;
×
7242
    }
7243

7244
    status = sixel_loader_setopt(loader,
872✔
7245
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
7246
                                 encoder->loader_order);
726✔
7247
    if (SIXEL_FAILED(status)) {
726!
7248
        goto load_end;
×
7249
    }
7250

7251
    status = sixel_loader_setopt(loader,
872✔
7252
                                 SIXEL_LOADER_OPTION_CONTEXT,
7253
                                 encoder);
146✔
7254
    if (SIXEL_FAILED(status)) {
726!
7255
        goto load_end;
×
7256
    }
7257

7258
    status = sixel_loader_setopt(loader,
872✔
7259
                                 SIXEL_LOADER_OPTION_LOGGER,
7260
                                 encoder->logger);
726✔
7261
    if (SIXEL_FAILED(status)) {
726!
NEW
7262
        goto load_end;
×
7263
    }
7264

7265
    /*
7266
     * Wire the optional assessment observer into the loader.
7267
     *
7268
     * The observer travels separately from the callback context so mapfile
7269
     * palette probes and other callbacks can keep using arbitrary structs.
7270
     */
7271
    status = sixel_loader_setopt(loader,
872✔
7272
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
7273
                                 encoder->assessment_observer);
726✔
7274
    if (SIXEL_FAILED(status)) {
726!
7275
        goto load_end;
×
7276
    }
7277

7278
    status = sixel_loader_load_file(loader,
872✔
7279
                                    effective_filename,
146✔
7280
                                    load_image_callback);
7281
    if (status != SIXEL_OK) {
726✔
7282
        goto load_end;
25✔
7283
    }
7284
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
701✔
7285
    if (sixel_loader_get_last_success_name(loader) != NULL) {
701!
7286
        (void)snprintf(encoder->last_loader_name,
701✔
7287
                       sizeof(encoder->last_loader_name),
7288
                       "%s",
7289
                       sixel_loader_get_last_success_name(loader));
7290
    } else {
141✔
7291
        encoder->last_loader_name[0] = '\0';
×
7292
    }
7293
    if (sixel_loader_get_last_source_path(loader) != NULL) {
701✔
7294
        (void)snprintf(encoder->last_source_path,
471✔
7295
                       sizeof(encoder->last_source_path),
7296
                       "%s",
7297
                       sixel_loader_get_last_source_path(loader));
7298
    } else {
95✔
7299
        encoder->last_source_path[0] = '\0';
230✔
7300
    }
7301
    if (encoder->assessment_observer != NULL) {
702✔
7302
        sixel_assessment_record_loader(encoder->assessment_observer,
6✔
7303
                                       encoder->last_source_path,
5✔
7304
                                       encoder->last_loader_name,
5✔
7305
                                       encoder->last_input_bytes);
1✔
7306
    }
1✔
7307

7308
load_end:
556✔
7309
    sixel_loader_unref(loader);
726✔
7310
    loader = NULL;
726✔
7311

7312
    if (status != SIXEL_OK) {
726✔
7313
        goto end;
25✔
7314
    }
7315

7316
    palette_status = sixel_encoder_emit_palette_output(encoder);
701✔
7317
    if (SIXEL_FAILED(palette_status)) {
701!
7318
        status = palette_status;
×
7319
        goto end;
×
7320
    }
7321

7322
    if (encoder->pipe_mode) {
701!
7323
#if HAVE_CLEARERR
7324
        clearerr(stdin);
×
7325
#endif  /* HAVE_FSEEK */
7326
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
7327
            status = sixel_tty_wait_stdin(1000000);
×
7328
            if (SIXEL_FAILED(status)) {
×
7329
                goto end;
×
7330
            }
7331
            if (status != SIXEL_OK) {
×
7332
                break;
×
7333
            }
7334
        }
7335
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
7336
            goto reload;
×
7337
        }
7338
    }
7339

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

7550
    if (encoder->output_is_png) {
701✔
7551
        png_final_path = encoder->output_png_to_stdout ? "-" : encoder->png_output_path;
15!
7552
        if (! encoder->output_png_to_stdout && png_final_path == NULL) {
15!
7553
            sixel_helper_set_additional_message(
×
7554
                "sixel_encoder_encode: missing PNG output path.");
7555
            status = SIXEL_RUNTIME_ERROR;
×
7556
            goto end;
×
7557
        }
7558
        status = write_png_from_sixel(png_temp_path, png_final_path);
15✔
7559
        if (SIXEL_FAILED(status)) {
15!
7560
            goto end;
×
7561
        }
7562
    }
3✔
7563

7564
    if (encoder->clipboard_output_active
701!
7565
            && encoder->clipboard_output_path != NULL) {
142!
7566
        unsigned char *clipboard_output_data;
7567
        size_t clipboard_output_size;
7568

7569
        clipboard_output_data = NULL;
1✔
7570
        clipboard_output_size = 0u;
1✔
7571

7572
        if (encoder->outfd
2!
7573
                && encoder->outfd != STDOUT_FILENO
1!
7574
                && encoder->outfd != STDERR_FILENO) {
1!
7575
            (void)sixel_compat_close(encoder->outfd);
1✔
7576
            encoder->outfd = STDOUT_FILENO;
1✔
7577
        }
1✔
7578

7579
        clipboard_status = clipboard_read_file(
1✔
7580
            encoder->clipboard_output_path,
1✔
7581
            &clipboard_output_data,
7582
            &clipboard_output_size);
7583
        if (SIXEL_SUCCEEDED(clipboard_status)) {
1!
7584
            clipboard_status = sixel_clipboard_write(
1✔
7585
                encoder->clipboard_output_format,
1✔
7586
                clipboard_output_data,
1✔
7587
                clipboard_output_size);
1✔
7588
        }
1✔
7589
        if (clipboard_output_data != NULL) {
1!
7590
            free(clipboard_output_data);
1✔
7591
        }
1✔
7592
        if (SIXEL_FAILED(clipboard_status)) {
1!
7593
            status = clipboard_status;
×
7594
            goto end;
×
7595
        }
7596
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
1✔
7597
        sixel_allocator_free(encoder->allocator,
2✔
7598
                             encoder->clipboard_output_path);
1✔
7599
        encoder->clipboard_output_path = NULL;
1✔
7600
        encoder->sixel_output_path = NULL;
1✔
7601
        encoder->clipboard_output_active = 0;
1✔
7602
        encoder->clipboard_output_format[0] = '\0';
1✔
7603
    }
1✔
7604

7605
    /* the status may not be SIXEL_OK */
7606

7607
end:
560✔
7608
    if (png_temp_path != NULL) {
726✔
7609
        (void)sixel_compat_unlink(png_temp_path);
15✔
7610
    }
3✔
7611
    sixel_allocator_free(encoder->allocator, png_temp_path);
726✔
7612
    if (clipboard_input_path != NULL) {
726!
7613
        (void)sixel_compat_unlink(clipboard_input_path);
1✔
7614
        sixel_allocator_free(encoder->allocator, clipboard_input_path);
1✔
7615
    }
1✔
7616
    if (clipboard_blob != NULL) {
726!
7617
        free(clipboard_blob);
×
7618
    }
7619
    if (encoder->clipboard_output_path != NULL) {
726!
7620
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
7621
        sixel_allocator_free(encoder->allocator,
×
7622
                             encoder->clipboard_output_path);
×
7623
        encoder->clipboard_output_path = NULL;
×
7624
        encoder->sixel_output_path = NULL;
×
7625
        encoder->clipboard_output_active = 0;
×
7626
        encoder->clipboard_output_format[0] = '\0';
×
7627
    }
7628
    sixel_allocator_free(encoder->allocator, encoder->png_output_path);
726✔
7629
    encoder->png_output_path = NULL;
726✔
7630
    if (assessment_forward_stream != NULL) {
726!
7631
        (void) fclose(assessment_forward_stream);
×
7632
    }
7633
    if (assessment_temp_path != NULL &&
726!
7634
            assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
7635
        (void)sixel_compat_unlink(assessment_temp_path);
×
7636
    }
7637
    sixel_allocator_free(encoder->allocator, assessment_temp_path);
726✔
7638
    sixel_allocator_free(encoder->allocator, assessment_forward_path);
726✔
7639
    if (assessment_json_owned && assessment_json_file != NULL) {
726!
7640
        (void) fclose(assessment_json_file);
5✔
7641
    }
1✔
7642
    if (assessment_target_frame != NULL) {
726!
7643
        sixel_frame_unref(assessment_target_frame);
×
7644
    }
7645
    if (assessment_expanded_frame != NULL) {
726!
7646
        sixel_frame_unref(assessment_expanded_frame);
×
7647
    }
7648
    if (assessment_source_frame != NULL) {
726✔
7649
        sixel_frame_unref(assessment_source_frame);
5✔
7650
    }
1✔
7651
    if (encoder->assessment_observer != NULL) {
726✔
7652
        sixel_assessment_unref(encoder->assessment_observer);
5✔
7653
        encoder->assessment_observer = NULL;
5✔
7654
    }
1✔
7655
    if (assessment_allocator != NULL) {
726✔
7656
        sixel_allocator_unref(assessment_allocator);
5✔
7657
    }
1✔
7658

7659
    if (encoder != NULL) {
726!
7660
        encoder->logger = NULL;
726✔
7661
        encoder->parallel_job_id = -1;
726✔
7662
    }
146✔
7663
    if (logger_prepared) {
726!
7664
        sixel_logger_close(&logger);
×
7665
    }
7666

7667
    sixel_encoder_unref(encoder);
726✔
7668

7669
    if (encode_allocator != NULL) {
726!
7670
        /*
7671
         * Release the retained allocator reference *after* dropping the
7672
         * encoder reference so that a lazily created encoder can run its
7673
         * destructor while the allocator is still alive.  This ensures that
7674
         * cleanup routines never dereference a freed allocator instance.
7675
         */
7676
        sixel_allocator_unref(encode_allocator);
726✔
7677
        encode_allocator = NULL;
726✔
7678
    }
146✔
7679

7680
    return status;
726✔
7681
}
7682

7683

7684
/* encode specified pixel data to SIXEL format
7685
 * output to encoder->outfd */
7686
SIXELAPI SIXELSTATUS
7687
sixel_encoder_encode_bytes(
×
7688
    sixel_encoder_t     /* in */    *encoder,
7689
    unsigned char       /* in */    *bytes,
7690
    int                 /* in */    width,
7691
    int                 /* in */    height,
7692
    int                 /* in */    pixelformat,
7693
    unsigned char       /* in */    *palette,
7694
    int                 /* in */    ncolors)
7695
{
7696
    SIXELSTATUS status = SIXEL_FALSE;
×
7697
    sixel_frame_t *frame = NULL;
×
7698

7699
    if (encoder == NULL || bytes == NULL) {
×
7700
        status = SIXEL_BAD_ARGUMENT;
×
7701
        goto end;
×
7702
    }
7703

7704
    status = sixel_frame_new(&frame, encoder->allocator);
×
7705
    if (SIXEL_FAILED(status)) {
×
7706
        goto end;
×
7707
    }
7708

7709
    status = sixel_frame_init(frame, bytes, width, height,
×
7710
                              pixelformat, palette, ncolors);
7711
    if (SIXEL_FAILED(status)) {
×
7712
        goto end;
×
7713
    }
7714

7715
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
7716
    if (SIXEL_FAILED(status)) {
×
7717
        goto end;
×
7718
    }
7719

7720
    status = SIXEL_OK;
×
7721

7722
end:
7723
    /* we need to free the frame before exiting, but we can't use the
7724
       sixel_frame_destroy function, because that will also attempt to
7725
       free the pixels and palette, which we don't own */
7726
    if (frame != NULL && encoder->allocator != NULL) {
×
7727
        sixel_allocator_free(encoder->allocator, frame);
×
7728
        sixel_allocator_unref(encoder->allocator);
×
7729
    }
7730
    return status;
×
7731
}
7732

7733

7734
/*
7735
 * Toggle source-frame capture for assessment consumers.
7736
 */
7737
SIXELAPI SIXELSTATUS
7738
sixel_encoder_enable_source_capture(
5✔
7739
    sixel_encoder_t *encoder,
7740
    int enable)
7741
{
7742
    if (encoder == NULL) {
5!
7743
        sixel_helper_set_additional_message(
×
7744
            "sixel_encoder_enable_source_capture: encoder is null.");
7745
        return SIXEL_BAD_ARGUMENT;
×
7746
    }
7747

7748
    encoder->capture_source = enable ? 1 : 0;
5✔
7749
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
5!
7750
        sixel_frame_unref(encoder->capture_source_frame);
×
7751
        encoder->capture_source_frame = NULL;
×
7752
    }
7753

7754
    return SIXEL_OK;
5✔
7755
}
1✔
7756

7757

7758
/*
7759
 * Enable or disable the quantized-frame capture facility.
7760
 *
7761
 *     capture on --> encoder keeps the latest palette-quantized frame.
7762
 *     capture off --> encoder forgets previously stored frames.
7763
 */
7764
SIXELAPI SIXELSTATUS
7765
sixel_encoder_enable_quantized_capture(
5✔
7766
    sixel_encoder_t *encoder,
7767
    int enable)
7768
{
7769
    if (encoder == NULL) {
5!
7770
        sixel_helper_set_additional_message(
×
7771
            "sixel_encoder_enable_quantized_capture: encoder is null.");
7772
        return SIXEL_BAD_ARGUMENT;
×
7773
    }
7774

7775
    encoder->capture_quantized = enable ? 1 : 0;
5✔
7776
    if (!encoder->capture_quantized) {
5!
7777
        encoder->capture_valid = 0;
×
7778
    }
7779

7780
    return SIXEL_OK;
5✔
7781
}
1✔
7782

7783

7784
/*
7785
 * Materialize the captured quantized frame as a heap-allocated
7786
 * sixel_frame_t instance for assessment consumers.
7787
 */
7788
SIXELAPI SIXELSTATUS
7789
sixel_encoder_copy_quantized_frame(
×
7790
    sixel_encoder_t   *encoder,
7791
    sixel_allocator_t *allocator,
7792
    sixel_frame_t     **ppframe)
7793
{
7794
    SIXELSTATUS status = SIXEL_FALSE;
×
7795
    sixel_frame_t *frame;
7796
    unsigned char *pixels;
7797
    unsigned char *palette;
7798
    size_t palette_bytes;
7799

7800
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
7801
        sixel_helper_set_additional_message(
×
7802
            "sixel_encoder_copy_quantized_frame: invalid argument.");
7803
        return SIXEL_BAD_ARGUMENT;
×
7804
    }
7805

7806
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
7807
        sixel_helper_set_additional_message(
×
7808
            "sixel_encoder_copy_quantized_frame: no frame captured.");
7809
        return SIXEL_RUNTIME_ERROR;
×
7810
    }
7811

7812
    *ppframe = NULL;
×
7813
    frame = NULL;
×
7814
    pixels = NULL;
×
7815
    palette = NULL;
×
7816

7817
    status = sixel_frame_new(&frame, allocator);
×
7818
    if (SIXEL_FAILED(status)) {
×
7819
        return status;
×
7820
    }
7821

7822
    if (encoder->capture_pixel_bytes > 0) {
×
7823
        pixels = (unsigned char *)sixel_allocator_malloc(
×
7824
            allocator, encoder->capture_pixel_bytes);
7825
        if (pixels == NULL) {
×
7826
            sixel_helper_set_additional_message(
×
7827
                "sixel_encoder_copy_quantized_frame: "
7828
                "sixel_allocator_malloc() failed.");
7829
            status = SIXEL_BAD_ALLOCATION;
×
7830
            goto cleanup;
×
7831
        }
7832
        memcpy(pixels,
×
7833
               encoder->capture_pixels,
7834
               encoder->capture_pixel_bytes);
7835
    }
7836

7837
    palette_bytes = encoder->capture_palette_size;
×
7838
    if (palette_bytes > 0) {
×
7839
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
7840
                                                          palette_bytes);
7841
        if (palette == NULL) {
×
7842
            sixel_helper_set_additional_message(
×
7843
                "sixel_encoder_copy_quantized_frame: "
7844
                "sixel_allocator_malloc() failed.");
7845
            status = SIXEL_BAD_ALLOCATION;
×
7846
            goto cleanup;
×
7847
        }
7848
        memcpy(palette,
×
7849
               encoder->capture_palette,
7850
               palette_bytes);
7851
    }
7852

7853
    status = sixel_frame_init(frame,
×
7854
                              pixels,
7855
                              encoder->capture_width,
7856
                              encoder->capture_height,
7857
                              encoder->capture_pixelformat,
7858
                              palette,
7859
                              encoder->capture_ncolors);
7860
    if (SIXEL_FAILED(status)) {
×
7861
        goto cleanup;
×
7862
    }
7863

7864
    pixels = NULL;
×
7865
    palette = NULL;
×
7866
    /*
7867
     * Capture colorspace must be preserved for assessment consumers.
7868
     * Keep access encapsulated via the public setter to avoid
7869
     * depending on frame internals.
7870
     */
7871
    sixel_frame_set_colorspace(frame, encoder->capture_colorspace);
×
7872
    *ppframe = frame;
×
7873
    return SIXEL_OK;
×
7874

7875
cleanup:
7876
    if (palette != NULL) {
×
7877
        sixel_allocator_free(allocator, palette);
×
7878
    }
7879
    if (pixels != NULL) {
×
7880
        sixel_allocator_free(allocator, pixels);
×
7881
    }
7882
    if (frame != NULL) {
×
7883
        sixel_frame_unref(frame);
×
7884
    }
7885
    return status;
×
7886
}
7887

7888

7889
/*
7890
 * Emit the captured palette in the requested format.
7891
 *
7892
 *   palette_output == NULL  -> skip
7893
 *   palette_output != NULL  -> materialize captured palette
7894
 */
7895
static SIXELSTATUS
7896
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
701✔
7897
{
7898
    SIXELSTATUS status;
7899
    sixel_frame_t *frame;
7900
    unsigned char const *palette;
7901
    int exported_colors;
7902
    FILE *stream;
7903
    int close_stream;
7904
    char const *path;
7905
    sixel_palette_format_t format_hint;
7906
    sixel_palette_format_t format_ext;
7907
    sixel_palette_format_t format_final;
7908
    char const *mode;
7909

7910
    status = SIXEL_OK;
701✔
7911
    frame = NULL;
701✔
7912
    palette = NULL;
701✔
7913
    exported_colors = 0;
701✔
7914
    stream = NULL;
701✔
7915
    close_stream = 0;
701✔
7916
    path = NULL;
701✔
7917
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
701✔
7918
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
701✔
7919
    format_final = SIXEL_PALETTE_FORMAT_NONE;
701✔
7920
    mode = "wb";
701✔
7921

7922
    if (encoder == NULL || encoder->palette_output == NULL) {
701!
7923
        return SIXEL_OK;
701✔
7924
    }
7925

7926
    status = sixel_encoder_copy_quantized_frame(encoder,
×
7927
                                                encoder->allocator,
7928
                                                &frame);
7929
    if (SIXEL_FAILED(status)) {
×
7930
        return status;
×
7931
    }
7932

7933
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
7934
    exported_colors = sixel_frame_get_ncolors(frame);
×
7935
    if (palette == NULL || exported_colors <= 0) {
×
7936
        sixel_helper_set_additional_message(
×
7937
            "sixel_encoder_emit_palette_output: palette unavailable.");
7938
        status = SIXEL_BAD_INPUT;
×
7939
        goto cleanup;
×
7940
    }
7941
    if (exported_colors > 256) {
×
7942
        exported_colors = 256;
×
7943
    }
7944

7945
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
7946
    if (path == NULL || *path == '\0') {
×
7947
        sixel_helper_set_additional_message(
×
7948
            "sixel_encoder_emit_palette_output: invalid path.");
7949
        status = SIXEL_BAD_ARGUMENT;
×
7950
        goto cleanup;
×
7951
    }
7952

7953
    format_ext = sixel_palette_format_from_extension(path);
×
7954
    format_final = format_hint;
×
7955
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
7956
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
7957
            if (strcmp(path, "-") == 0) {
×
7958
                sixel_helper_set_additional_message(
×
7959
                    "sixel_encoder_emit_palette_output: "
7960
                    "format required for '-'.");
7961
                status = SIXEL_BAD_ARGUMENT;
×
7962
                goto cleanup;
×
7963
            }
7964
            sixel_helper_set_additional_message(
×
7965
                "sixel_encoder_emit_palette_output: "
7966
                "unknown palette file extension.");
7967
            status = SIXEL_BAD_ARGUMENT;
×
7968
            goto cleanup;
×
7969
        }
7970
        format_final = format_ext;
×
7971
    }
7972
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
7973
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
7974
    }
7975

7976
    if (strcmp(path, "-") == 0) {
×
7977
        stream = stdout;
×
7978
    } else {
7979
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
7980
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
7981
            mode = "w";
×
7982
        } else {
7983
            mode = "wb";
×
7984
        }
7985
        stream = fopen(path, mode);
×
7986
        if (stream == NULL) {
×
7987
            sixel_helper_set_additional_message(
×
7988
                "sixel_encoder_emit_palette_output: failed to open file.");
7989
            status = SIXEL_LIBC_ERROR;
×
7990
            goto cleanup;
×
7991
        }
7992
        close_stream = 1;
×
7993
    }
7994

7995
    switch (format_final) {
×
7996
    case SIXEL_PALETTE_FORMAT_ACT:
7997
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
7998
        if (SIXEL_FAILED(status)) {
×
7999
            sixel_helper_set_additional_message(
×
8000
                "sixel_encoder_emit_palette_output: failed to write ACT.");
8001
        }
8002
        break;
×
8003
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
8004
        status = sixel_palette_write_pal_jasc(stream,
×
8005
                                              palette,
8006
                                              exported_colors);
8007
        if (SIXEL_FAILED(status)) {
×
8008
            sixel_helper_set_additional_message(
×
8009
                "sixel_encoder_emit_palette_output: failed to write JASC.");
8010
        }
8011
        break;
×
8012
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
8013
        status = sixel_palette_write_pal_riff(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 RIFF.");
8019
        }
8020
        break;
×
8021
    case SIXEL_PALETTE_FORMAT_GPL:
8022
        status = sixel_palette_write_gpl(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 GPL.");
8028
        }
8029
        break;
×
8030
    default:
8031
        sixel_helper_set_additional_message(
×
8032
            "sixel_encoder_emit_palette_output: unsupported format.");
8033
        status = SIXEL_BAD_ARGUMENT;
×
8034
        break;
×
8035
    }
8036
    if (SIXEL_FAILED(status)) {
×
8037
        goto cleanup;
×
8038
    }
8039

8040
    if (close_stream) {
×
8041
        if (fclose(stream) != 0) {
×
8042
            sixel_helper_set_additional_message(
×
8043
                "sixel_encoder_emit_palette_output: fclose() failed.");
8044
            status = SIXEL_LIBC_ERROR;
×
8045
            stream = NULL;
×
8046
            goto cleanup;
×
8047
        }
8048
        stream = NULL;
×
8049
    } else {
8050
        if (fflush(stream) != 0) {
×
8051
            sixel_helper_set_additional_message(
×
8052
                "sixel_encoder_emit_palette_output: fflush() failed.");
8053
            status = SIXEL_LIBC_ERROR;
×
8054
            goto cleanup;
×
8055
        }
8056
    }
8057

8058
cleanup:
8059
    if (close_stream && stream != NULL) {
×
8060
        (void) fclose(stream);
×
8061
    }
8062
    if (frame != NULL) {
×
8063
        sixel_frame_unref(frame);
×
8064
    }
8065

8066
    return status;
×
8067
}
141✔
8068

8069

8070
/*
8071
 * Share the captured source frame with assessment consumers.
8072
 */
8073
SIXELAPI SIXELSTATUS
8074
sixel_encoder_copy_source_frame(
5✔
8075
    sixel_encoder_t *encoder,
8076
    sixel_frame_t  **ppframe)
8077
{
8078
    if (encoder == NULL || ppframe == NULL) {
5!
8079
        sixel_helper_set_additional_message(
×
8080
            "sixel_encoder_copy_source_frame: invalid argument.");
8081
        return SIXEL_BAD_ARGUMENT;
×
8082
    }
8083

8084
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
5!
8085
        sixel_helper_set_additional_message(
×
8086
            "sixel_encoder_copy_source_frame: no frame captured.");
8087
        return SIXEL_RUNTIME_ERROR;
×
8088
    }
8089

8090
    sixel_frame_ref(encoder->capture_source_frame);
5✔
8091
    *ppframe = encoder->capture_source_frame;
5✔
8092

8093
    return SIXEL_OK;
5✔
8094
}
1✔
8095

8096

8097
#if HAVE_TESTS
8098
static int
8099
test1(void)
×
8100
{
8101
    int nret = EXIT_FAILURE;
×
8102
    sixel_encoder_t *encoder = NULL;
×
8103

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

8119
error:
8120
    sixel_encoder_unref(encoder);
×
8121
    return nret;
×
8122
}
8123

8124

8125
static int
8126
test2(void)
×
8127
{
8128
    int nret = EXIT_FAILURE;
×
8129
    SIXELSTATUS status;
8130
    sixel_encoder_t *encoder = NULL;
×
8131
    sixel_frame_t *frame = NULL;
×
8132
    unsigned char *buffer;
8133
    int height = 0;
×
8134
    int is_animation = 0;
×
8135

8136
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8137
#  pragma GCC diagnostic push
8138
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8139
#endif
8140
    encoder = sixel_encoder_create();
×
8141
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8142
#  pragma GCC diagnostic pop
8143
#endif
8144
    if (encoder == NULL) {
×
8145
        goto error;
×
8146
    }
8147

8148
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8149
#  pragma GCC diagnostic push
8150
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8151
#endif
8152
    frame = sixel_frame_create();
×
8153
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8154
#  pragma GCC diagnostic pop
8155
#endif
8156
    if (encoder == NULL) {
×
8157
        goto error;
×
8158
    }
8159

8160
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
8161
    if (buffer == NULL) {
×
8162
        goto error;
×
8163
    }
8164
    status = sixel_frame_init(frame, buffer, 1, 1,
×
8165
                              SIXEL_PIXELFORMAT_RGB888,
8166
                              NULL, 0);
8167
    if (SIXEL_FAILED(status)) {
×
8168
        goto error;
×
8169
    }
8170

8171
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
8172
        is_animation = 1;
×
8173
    }
8174

8175
    height = sixel_frame_get_height(frame);
×
8176

8177
    status = sixel_tty_scroll(sixel_write_callback,
×
8178
                              &encoder->outfd,
×
8179
                              encoder->outfd,
8180
                              height,
8181
                              is_animation);
8182
    if (SIXEL_FAILED(status)) {
×
8183
        goto error;
×
8184
    }
8185

8186
    nret = EXIT_SUCCESS;
×
8187

8188
error:
8189
    sixel_encoder_unref(encoder);
×
8190
    sixel_frame_unref(frame);
×
8191
    return nret;
×
8192
}
8193

8194

8195
static int
8196
test3(void)
×
8197
{
8198
    int nret = EXIT_FAILURE;
×
8199
    int result;
8200

8201
    result = sixel_tty_wait_stdin(1000);
×
8202
    if (result != 0) {
×
8203
        goto error;
×
8204
    }
8205

8206
    nret = EXIT_SUCCESS;
×
8207

8208
error:
8209
    return nret;
×
8210
}
8211

8212

8213
static int
8214
test4(void)
×
8215
{
8216
    int nret = EXIT_FAILURE;
×
8217
    sixel_encoder_t *encoder = NULL;
×
8218
    SIXELSTATUS status;
8219

8220
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8221
# pragma GCC diagnostic push
8222
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8223
#endif
8224
    encoder = sixel_encoder_create();
×
8225
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8226
# pragma GCC diagnostic pop
8227
#endif
8228
    if (encoder == NULL) {
×
8229
        goto error;
×
8230
    }
8231

8232
    status = sixel_encoder_setopt(encoder,
×
8233
                                  SIXEL_OPTFLAG_LOOPMODE,
8234
                                  "force");
8235
    if (SIXEL_FAILED(status)) {
×
8236
        goto error;
×
8237
    }
8238

8239
    status = sixel_encoder_setopt(encoder,
×
8240
                                  SIXEL_OPTFLAG_PIPE_MODE,
8241
                                  "force");
8242
    if (SIXEL_FAILED(status)) {
×
8243
        goto error;
×
8244
    }
8245

8246
    nret = EXIT_SUCCESS;
×
8247

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

8253

8254
static int
8255
test5(void)
×
8256
{
8257
    int nret = EXIT_FAILURE;
×
8258
    sixel_encoder_t *encoder = NULL;
×
8259
    sixel_allocator_t *allocator = NULL;
×
8260
    SIXELSTATUS status;
8261

8262
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
8263
    if (SIXEL_FAILED(status)) {
×
8264
        goto error;
×
8265
    }
8266

8267
    status = sixel_encoder_new(&encoder, allocator);
×
8268
    if (SIXEL_FAILED(status)) {
×
8269
        goto error;
×
8270
    }
8271

8272
    sixel_encoder_ref(encoder);
×
8273
    sixel_encoder_unref(encoder);
×
8274
    nret = EXIT_SUCCESS;
×
8275

8276
error:
8277
    sixel_encoder_unref(encoder);
×
8278
    return nret;
×
8279
}
8280

8281

8282
SIXELAPI int
8283
sixel_encoder_tests_main(void)
×
8284
{
8285
    int nret = EXIT_FAILURE;
×
8286
    size_t i;
8287
    typedef int (* testcase)(void);
8288

8289
    static testcase const testcases[] = {
8290
        test1,
8291
        test2,
8292
        test3,
8293
        test4,
8294
        test5
8295
    };
8296

8297
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
8298
        nret = testcases[i]();
×
8299
        if (nret != EXIT_SUCCESS) {
×
8300
            goto error;
×
8301
        }
8302
    }
8303

8304
    nret = EXIT_SUCCESS;
×
8305

8306
error:
8307
    return nret;
×
8308
}
8309
#endif  /* HAVE_TESTS */
8310

8311

8312
/* emacs Local Variables:      */
8313
/* emacs mode: c               */
8314
/* emacs tab-width: 4          */
8315
/* emacs indent-tabs-mode: nil */
8316
/* emacs c-basic-offset: 4     */
8317
/* emacs End:                  */
8318
/* vim: set expandtab ts=4 : */
8319
/* 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