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

saitoha / libsixel / 19714889204

26 Nov 2025 07:14PM UTC coverage: 42.522% (+1.3%) from 41.224%
19714889204

push

github

saitoha
rename sixel_parallel_logger_t -> sixel_logger_t

9688 of 33860 branches covered (28.61%)

52 of 126 new or added lines in 8 files covered. (41.27%)

6 existing lines in 2 files now uncovered.

13437 of 31600 relevant lines covered (42.52%)

958616.52 hits per line

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

49.66
/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
};
276

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

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

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

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

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

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

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

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

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

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

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

357
static int
358
sixel_encoder_pixelformat_for_colorspace(int colorspace)
21✔
359
{
360
    switch (colorspace) {
21!
361
    case SIXEL_COLORSPACE_LINEAR:
362
        return SIXEL_PIXELFORMAT_LINEARRGBFLOAT32;
×
363
    case SIXEL_COLORSPACE_OKLAB:
364
        return SIXEL_PIXELFORMAT_OKLABFLOAT32;
×
365
    default:
14✔
366
        return SIXEL_PIXELFORMAT_RGB888;
21✔
367
    }
368
}
7✔
369

370
static sixel_option_choice_t const g_option_choices_precision[] = {
371
    { "auto", SIXEL_ENCODER_PRECISION_MODE_AUTO },
372
    { "8bit", SIXEL_ENCODER_PRECISION_MODE_8BIT },
373
    { "float32", SIXEL_ENCODER_PRECISION_MODE_FLOAT32 }
374
};
375

376

377
static char *
378
arg_strdup(
57✔
379
    char const          /* in */ *s,          /* source buffer */
380
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
381
                                                 destination buffer */
382
{
383
    char *p;
384
    size_t len;
385

386
    len = strlen(s);
57✔
387

388
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
57✔
389
    if (p) {
57!
390
        (void)sixel_compat_strcpy(p, len + 1, s);
57✔
391
    }
19✔
392
    return p;
57✔
393
}
394

395
static SIXELSTATUS
396
sixel_encoder_apply_precision_override(
×
397
    sixel_encoder_precision_mode_t mode)
398
{
399
    char const *value;
400

401
    value = NULL;
×
402

403
    if (mode == SIXEL_ENCODER_PRECISION_MODE_AUTO) {
×
404
        return SIXEL_OK;
×
405
    }
406

407
    if (mode == SIXEL_ENCODER_PRECISION_MODE_FLOAT32) {
×
408
        value = "1";
×
409
    } else if (mode == SIXEL_ENCODER_PRECISION_MODE_8BIT) {
×
410
        value = "0";
×
411
    } else {
412
        sixel_helper_set_additional_message(
×
413
            "sixel_encoder_setopt: invalid precision override.");
414
        return SIXEL_BAD_ARGUMENT;
×
415
    }
416

417
    if (sixel_compat_setenv(SIXEL_ENCODER_PRECISION_ENVVAR, value) != 0) {
×
418
        sixel_helper_set_additional_message(
×
419
            "sixel_encoder_setopt: failed to set SIXEL_FLOAT32_DITHER.");
420
        return SIXEL_LIBC_ERROR;
×
421
    }
422

423
    return SIXEL_OK;
×
424
}
425

426

427
/* An clone function of XColorSpec() of xlib */
428
static SIXELSTATUS
429
sixel_parse_x_colorspec(
45✔
430
    unsigned char       /* out */ **bgcolor,     /* destination buffer */
431
    char const          /* in */  *s,            /* source buffer */
432
    sixel_allocator_t   /* in */  *allocator)    /* allocator object for
433
                                                    destination buffer */
434
{
435
    SIXELSTATUS status = SIXEL_FALSE;
45✔
436
    char *p;
437
    unsigned char components[3];
438
    int component_index = 0;
45✔
439
    unsigned long v;
440
    char *endptr;
441
    char *buf = NULL;
45✔
442
    struct color const *pcolor;
443

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

575
    status = SIXEL_OK;
24✔
576

577
end:
30✔
578
    sixel_allocator_free(allocator, buf);
45✔
579

580
    return status;
45✔
581
}
582

583

584
/* generic writer function for passing to sixel_output_new() */
585
static int
586
sixel_write_callback(char *data, int size, void *priv)
5,888✔
587
{
588
    int result;
589

590
    result = (int)sixel_compat_write(*(int *)priv,
8,048✔
591
                                     data,
2,160✔
592
                                     (size_t)size);
2,160✔
593

594
    return result;
5,888✔
595
}
596

597

598
/* the writer function with hex-encoding for passing to sixel_output_new() */
599
static int
600
sixel_hex_write_callback(
72✔
601
    char    /* in */ *data,
602
    int     /* in */ size,
603
    void    /* in */ *priv)
604
{
605
    char hex[SIXEL_OUTPUT_PACKET_SIZE * 2];
606
    int i;
607
    int j;
608
    int result;
609

610
    for (i = j = 0; i < size; ++i, ++j) {
701,274✔
611
        hex[j] = (data[i] >> 4) & 0xf;
701,202✔
612
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
701,202!
613
        hex[++j] = data[i] & 0xf;
701,202✔
614
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
701,202✔
615
    }
233,734✔
616

617
    result = (int)sixel_compat_write(*(int *)priv,
144✔
618
                                     hex,
24✔
619
                                     (size_t)(size * 2));
72✔
620

621
    return result;
72✔
622
}
623

624
typedef struct sixel_encoder_output_probe {
625
    sixel_encoder_t *encoder;
626
    sixel_write_function base_write;
627
    void *base_priv;
628
} sixel_encoder_output_probe_t;
629

630
static int
631
sixel_write_with_probe(char *data, int size, void *priv)
72✔
632
{
633
    sixel_encoder_output_probe_t *probe;
634
    int written;
635
    double started_at;
636
    double finished_at;
637
    double duration;
638

639
    probe = (sixel_encoder_output_probe_t *)priv;
72✔
640
    if (probe == NULL || probe->base_write == NULL) {
72!
641
        return 0;
×
642
    }
643
    started_at = 0.0;
72✔
644
    finished_at = 0.0;
72✔
645
    duration = 0.0;
72✔
646
    if (probe->encoder != NULL &&
72!
647
            probe->encoder->assessment_observer != NULL) {
72!
648
        started_at = sixel_assessment_timer_now();
72✔
649
    }
24✔
650
    written = probe->base_write(data, size, probe->base_priv);
72✔
651
    if (probe->encoder != NULL &&
72!
652
            probe->encoder->assessment_observer != NULL) {
72!
653
        finished_at = sixel_assessment_timer_now();
72✔
654
        duration = finished_at - started_at;
72✔
655
        if (duration < 0.0) {
72!
656
            duration = 0.0;
×
657
        }
658
    }
24✔
659
    if (written > 0 && probe->encoder != NULL &&
72!
660
            probe->encoder->assessment_observer != NULL) {
72!
661
        sixel_assessment_record_output_write(
72✔
662
            probe->encoder->assessment_observer,
72✔
663
            (size_t)written,
24✔
664
            duration);
24✔
665
    }
24✔
666
    return written;
72✔
667
}
24✔
668

669
/*
670
 * Reuse the fn_write probe for raw escape writes so that every
671
 * assessment bucket receives the same accounting.
672
 *
673
 *     encoder        probe wrapper       write(2)
674
 *     +------+    +----------------+    +---------+
675
 *     | data | -> | sixel_write_*  | -> | target  |
676
 *     +------+    +----------------+    +---------+
677
 */
678
static int
679
sixel_encoder_probe_fd_write(sixel_encoder_t *encoder,
×
680
                             char *data,
681
                             int size,
682
                             int fd)
683
{
684
    sixel_encoder_output_probe_t probe;
685
    int written;
686

687
    probe.encoder = encoder;
×
688
    probe.base_write = sixel_write_callback;
×
689
    probe.base_priv = &fd;
×
690
    written = sixel_write_with_probe(data, size, &probe);
×
691

692
    return written;
×
693
}
694

695
static void
696
sixel_encoder_log_stage(sixel_encoder_t *encoder,
6,977✔
697
                        sixel_frame_t *frame,
698
                        char const *worker,
699
                        char const *role,
700
                        char const *event,
701
                        char const *fmt,
702
                        ...)
703
{
704
    sixel_logger_t *logger;
705
    int job_id;
706
    int height;
707
    char message[256];
708
    va_list args;
709

710
    logger = NULL;
6,977✔
711
    if (encoder != NULL) {
6,977!
712
        logger = encoder->logger;
6,977✔
713
    }
2,335✔
714
    if (logger == NULL || logger->file == NULL || !logger->active) {
6,977!
715
        return;
6,977✔
716
    }
717

718
    job_id = -1;
×
719
    height = 0;
×
720
    if (frame != NULL) {
×
721
        job_id = sixel_frame_get_frame_no(frame);
×
722
        height = sixel_frame_get_height(frame);
×
723
    }
724

725
    message[0] = '\0';
×
726
#if defined(__clang__)
727
#pragma clang diagnostic push
728
#pragma clang diagnostic ignored "-Wformat-nonliteral"
729
#elif defined(__GNUC__)
730
#pragma GCC diagnostic push
731
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
732
#endif
733
    va_start(args, fmt);
×
734
    if (fmt != NULL) {
×
735
#if defined(__clang__)
736
#pragma clang diagnostic push
737
#pragma clang diagnostic ignored "-Wformat-nonliteral"
738
#elif defined(__GNUC__)
739
#pragma GCC diagnostic push
740
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
741
#endif
742
        (void)vsnprintf(message, sizeof(message), fmt, args);
×
743
#if defined(__clang__)
744
#pragma clang diagnostic pop
745
#elif defined(__GNUC__)
746
#pragma GCC diagnostic pop
747
#endif
748
    }
749
    va_end(args);
×
750
#if defined(__clang__)
751
#pragma clang diagnostic pop
752
#elif defined(__GNUC__)
753
#pragma GCC diagnostic pop
754
#endif
755

NEW
756
    sixel_logger_logf(logger,
×
757
                      role,
758
                      worker,
759
                      event,
760
                      job_id,
761
                      -1,
762
                      0,
763
                      height,
764
                      0,
765
                      height,
766
                      "%s",
767
                      message);
768
}
2,335✔
769

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

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

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

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

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

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

821

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

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

842
    status = SIXEL_OK;
12✔
843

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

848

849
static SIXELSTATUS
850
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
3✔
851
                                sixel_dither_t *dither,
852
                                unsigned char const *pixels,
853
                                size_t size,
854
                                int width,
855
                                int height,
856
                                int pixelformat,
857
                                int source_colorspace,
858
                                int colorspace)
859
{
860
    SIXELSTATUS status;
861
    int ncolors;
862
    size_t palette_bytes;
863
    unsigned char *new_pixels;
864
    unsigned char *new_palette;
865
    size_t capture_bytes;
866
    unsigned char const *capture_source;
867
    sixel_index_t *paletted_pixels;
868
    size_t quantized_pixels;
869
    sixel_allocator_t *dither_allocator;
870
    int saved_pixelformat;
871
    int restore_pixelformat;
872

873
    /*
874
     * Preserve the quantized frame for assessment observers.
875
     *
876
     *     +-----------------+     +---------------------+
877
     *     | quantized bytes | --> | encoder->capture_*  |
878
     *     +-----------------+     +---------------------+
879
     */
880

881
    status = SIXEL_OK;
3✔
882
    ncolors = 0;
3✔
883
    palette_bytes = 0;
3✔
884
    new_pixels = NULL;
3✔
885
    new_palette = NULL;
3✔
886
    capture_bytes = size;
3✔
887
    capture_source = pixels;
3✔
888
    paletted_pixels = NULL;
3✔
889
    quantized_pixels = 0;
3✔
890
    dither_allocator = NULL;
3✔
891

892
    if (encoder == NULL || pixels == NULL ||
3!
893
            (dither == NULL && size == 0)) {
1!
894
        sixel_helper_set_additional_message(
×
895
            "sixel_encoder_capture_quantized: invalid capture request.");
896
        return SIXEL_BAD_ARGUMENT;
×
897
    }
898

899
    if (!encoder->capture_quantized) {
3!
900
        return SIXEL_OK;
×
901
    }
902

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

935
    if (capture_bytes > 0) {
3!
936
        if (encoder->capture_pixels == NULL ||
3!
937
                encoder->capture_pixels_size < capture_bytes) {
×
938
            new_pixels = (unsigned char *)sixel_allocator_malloc(
3✔
939
                encoder->allocator, capture_bytes);
1✔
940
            if (new_pixels == NULL) {
3!
941
                sixel_helper_set_additional_message(
×
942
                    "sixel_encoder_capture_quantized: "
943
                    "sixel_allocator_malloc() failed.");
944
                status = SIXEL_BAD_ALLOCATION;
×
945
                goto cleanup;
×
946
            }
947
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
3✔
948
            encoder->capture_pixels = new_pixels;
3✔
949
            encoder->capture_pixels_size = capture_bytes;
3✔
950
        }
1✔
951
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
3✔
952
    }
1✔
953
    encoder->capture_pixel_bytes = capture_bytes;
3✔
954

955
    ncolors = 0;
3✔
956
    palette_bytes = 0;
3✔
957
    if (dither != NULL) {
3!
958
        sixel_palette_t *palette_obj = NULL;
3✔
959
        unsigned char *palette_copy = NULL;
3✔
960
        size_t palette_count = 0U;
3✔
961

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

1013
    encoder->capture_width = width;
3✔
1014
    encoder->capture_height = height;
3✔
1015
    if (dither != NULL) {
3!
1016
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
3✔
1017
    } else {
1✔
1018
        encoder->capture_pixelformat = pixelformat;
×
1019
    }
1020
    encoder->capture_colorspace = colorspace;
3✔
1021
    encoder->capture_palette_size = palette_bytes;
3✔
1022
    encoder->capture_ncolors = ncolors;
3✔
1023
    encoder->capture_valid = 1;
3✔
1024

1025
cleanup:
2✔
1026
    if (restore_pixelformat && dither != NULL) {
3!
1027
        /*
1028
         * Undo the normalization performed by sixel_dither_apply_palette().
1029
         *
1030
         *     RGBA8888 --capture--> RGB888 (temporary)
1031
         *          \______________________________/
1032
         *                          |
1033
         *                 restore original state for
1034
         *                 the real encoder execution.
1035
         */
1036
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
3✔
1037
    }
1✔
1038
    if (paletted_pixels != NULL && dither_allocator != NULL) {
3!
1039
        sixel_allocator_free(dither_allocator, paletted_pixels);
3✔
1040
    }
1✔
1041

1042
    return status;
3✔
1043
}
1✔
1044

1045
static SIXELSTATUS
1046
sixel_prepare_builtin_palette(
27✔
1047
    sixel_dither_t /* out */ **dither,
1048
    int            /* in */  builtin_palette)
1049
{
1050
    SIXELSTATUS status = SIXEL_FALSE;
27✔
1051

1052
    *dither = sixel_dither_get(builtin_palette);
27✔
1053
    if (*dither == NULL) {
27!
1054
        sixel_helper_set_additional_message(
×
1055
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
1056
        status = SIXEL_RUNTIME_ERROR;
×
1057
        goto end;
×
1058
    }
1059

1060
    status = SIXEL_OK;
27✔
1061

1062
end:
18✔
1063
    return status;
27✔
1064
}
1065

1066
static int
1067
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
457✔
1068
{
1069
    int width_hint;
1070
    int height_hint;
1071
    long base;
1072
    long size;
1073

1074
    width_hint = 0;
457✔
1075
    height_hint = 0;
457✔
1076
    base = 0;
457✔
1077
    size = 0;
457✔
1078

1079
    if (encoder == NULL) {
457!
1080
        return 0;
×
1081
    }
1082

1083
    width_hint = encoder->pixelwidth;
457✔
1084
    height_hint = encoder->pixelheight;
457✔
1085

1086
    /* Request extra resolution for downscaling to preserve detail. */
1087
    if (width_hint > 0 && height_hint > 0) {
457✔
1088
        /* Follow the CLI rule: double the larger axis before doubling
1089
         * again for the final request size. */
1090
        if (width_hint >= height_hint) {
9!
1091
            base = (long)width_hint;
9✔
1092
        } else {
3✔
1093
            base = (long)height_hint;
×
1094
        }
1095
        base *= 2L;
9✔
1096
    } else if (width_hint > 0) {
451✔
1097
        base = (long)width_hint;
48✔
1098
    } else if (height_hint > 0) {
416✔
1099
        base = (long)height_hint;
36✔
1100
    } else {
12✔
1101
        return 0;
364✔
1102
    }
1103

1104
    size = base * 2L;
93✔
1105
    if (size > (long)INT_MAX) {
93!
1106
        size = (long)INT_MAX;
×
1107
    }
1108
    if (size < 1L) {
93!
1109
        size = 1L;
×
1110
    }
1111

1112
    return (int)size;
93✔
1113
}
153✔
1114

1115

1116
typedef struct sixel_callback_context_for_mapfile {
1117
    int reqcolors;
1118
    sixel_dither_t *dither;
1119
    sixel_allocator_t *allocator;
1120
    int working_colorspace;
1121
    int lut_policy;
1122
} sixel_callback_context_for_mapfile_t;
1123

1124

1125
/* callback function for sixel_helper_load_image_file() */
1126
static SIXELSTATUS
1127
load_image_callback_for_palette(
21✔
1128
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1129
    void            /* in */    *data)  /* private data */
1130
{
1131
    SIXELSTATUS status = SIXEL_FALSE;
21✔
1132
    sixel_callback_context_for_mapfile_t *callback_context;
1133

1134
    /* get callback context object from the private data */
1135
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
1136

1137
    status = sixel_frame_set_pixelformat(
21✔
1138
        frame,
7✔
1139
        sixel_encoder_pixelformat_for_colorspace(
7✔
1140
            callback_context->working_colorspace));
7✔
1141
    if (SIXEL_FAILED(status)) {
21!
1142
        goto end;
×
1143
    }
1144

1145
    switch (sixel_frame_get_pixelformat(frame)) {
21!
1146
    case SIXEL_PIXELFORMAT_PAL1:
1147
    case SIXEL_PIXELFORMAT_PAL2:
1148
    case SIXEL_PIXELFORMAT_PAL4:
1149
    case SIXEL_PIXELFORMAT_PAL8:
1150
        if (sixel_frame_get_palette(frame) == NULL) {
×
1151
            status = SIXEL_LOGIC_ERROR;
×
1152
            goto end;
×
1153
        }
1154
        /* create new dither object */
1155
        status = sixel_dither_new(
×
1156
            &callback_context->dither,
1157
            sixel_frame_get_ncolors(frame),
1158
            callback_context->allocator);
1159
        if (SIXEL_FAILED(status)) {
×
1160
            goto end;
×
1161
        }
1162

1163
        sixel_dither_set_lut_policy(callback_context->dither,
×
1164
                                    callback_context->lut_policy);
1165

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

1207
        sixel_dither_set_lut_policy(callback_context->dither,
28✔
1208
                                    callback_context->lut_policy);
7✔
1209

1210
        /* create adaptive palette from given frame object */
1211
        status = sixel_dither_initialize(callback_context->dither,
28✔
1212
                                         sixel_frame_get_pixels(frame),
7✔
1213
                                         sixel_frame_get_width(frame),
7✔
1214
                                         sixel_frame_get_height(frame),
7✔
1215
                                         sixel_frame_get_pixelformat(frame),
7✔
1216
                                         SIXEL_LARGE_NORM,
1217
                                         SIXEL_REP_CENTER_BOX,
1218
                                         SIXEL_QUALITY_HIGH);
1219
        if (SIXEL_FAILED(status)) {
21!
1220
            sixel_dither_unref(callback_context->dither);
×
1221
            goto end;
×
1222
        }
1223

1224
        /* success */
1225
        status = SIXEL_OK;
21✔
1226

1227
        break;
21✔
1228
    }
7✔
1229

1230
end:
14✔
1231
    return status;
21✔
1232
}
1233

1234

1235
static SIXELSTATUS
1236
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1237

1238

1239
static int
1240
sixel_path_has_extension(char const *path, char const *extension)
63✔
1241
{
1242
    size_t path_len;
1243
    size_t ext_len;
1244
    size_t index;
1245

1246
    path_len = 0u;
63✔
1247
    ext_len = 0u;
63✔
1248
    index = 0u;
63✔
1249

1250
    if (path == NULL || extension == NULL) {
63!
1251
        return 0;
×
1252
    }
1253

1254
    path_len = strlen(path);
63✔
1255
    ext_len = strlen(extension);
63✔
1256
    if (ext_len == 0u || path_len < ext_len) {
63!
1257
        return 0;
×
1258
    }
1259

1260
    for (index = 0u; index < ext_len; ++index) {
144!
1261
        unsigned char path_ch;
1262
        unsigned char ext_ch;
1263

1264
        path_ch = (unsigned char)path[path_len - ext_len + index];
144✔
1265
        ext_ch = (unsigned char)extension[index];
144✔
1266
        if (tolower(path_ch) != tolower(ext_ch)) {
144✔
1267
            return 0;
63✔
1268
        }
1269
    }
27✔
1270

1271
    return 1;
×
1272
}
21✔
1273

1274
typedef enum sixel_palette_format {
1275
    SIXEL_PALETTE_FORMAT_NONE = 0,
1276
    SIXEL_PALETTE_FORMAT_ACT,
1277
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1278
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1279
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1280
    SIXEL_PALETTE_FORMAT_GPL
1281
} sixel_palette_format_t;
1282

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

1303
    colon = NULL;
48✔
1304
    type_len = 0u;
48✔
1305
    index = 0u;
48✔
1306

1307
    if (format_hint != NULL) {
48✔
1308
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
1309
    }
7✔
1310
    if (spec == NULL) {
48!
1311
        return NULL;
×
1312
    }
1313

1314
    colon = strchr(spec, ':');
48✔
1315
    if (colon == NULL) {
48!
1316
        return spec;
48✔
1317
    }
1318

1319
    type_len = (size_t)(colon - spec);
×
1320
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1321
        return spec;
×
1322
    }
1323

1324
    for (index = 0u; index < type_len; ++index) {
×
1325
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1326
    }
1327
    lowered[type_len] = '\0';
×
1328

1329
    if (strcmp(lowered, "act") == 0) {
×
1330
        if (format_hint != NULL) {
×
1331
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
×
1332
        }
1333
        return colon + 1;
×
1334
    }
1335
    if (strcmp(lowered, "pal") == 0) {
×
1336
        if (format_hint != NULL) {
×
1337
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1338
        }
1339
        return colon + 1;
×
1340
    }
1341
    if (strcmp(lowered, "pal-jasc") == 0) {
×
1342
        if (format_hint != NULL) {
×
1343
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1344
        }
1345
        return colon + 1;
×
1346
    }
1347
    if (strcmp(lowered, "pal-riff") == 0) {
×
1348
        if (format_hint != NULL) {
×
1349
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1350
        }
1351
        return colon + 1;
×
1352
    }
1353
    if (strcmp(lowered, "gpl") == 0) {
×
1354
        if (format_hint != NULL) {
×
1355
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
×
1356
        }
1357
        return colon + 1;
×
1358
    }
1359

1360
    return spec;
×
1361
}
16✔
1362

1363
static sixel_palette_format_t
1364
sixel_palette_format_from_extension(char const *path)
21✔
1365
{
1366
    if (path == NULL) {
21!
1367
        return SIXEL_PALETTE_FORMAT_NONE;
×
1368
    }
1369

1370
    if (sixel_path_has_extension(path, ".act")) {
21!
1371
        return SIXEL_PALETTE_FORMAT_ACT;
×
1372
    }
1373
    if (sixel_path_has_extension(path, ".pal")) {
21!
1374
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1375
    }
1376
    if (sixel_path_has_extension(path, ".gpl")) {
21!
1377
        return SIXEL_PALETTE_FORMAT_GPL;
×
1378
    }
1379

1380
    return SIXEL_PALETTE_FORMAT_NONE;
21✔
1381
}
7✔
1382

1383
static int
1384
sixel_path_has_any_extension(char const *path)
21✔
1385
{
1386
    char const *slash_forward;
1387
#if defined(_WIN32)
1388
    char const *slash_backward;
1389
#endif
1390
    char const *start;
1391
    char const *dot;
1392

1393
    slash_forward = NULL;
21✔
1394
#if defined(_WIN32)
1395
    slash_backward = NULL;
1396
#endif
1397
    start = path;
21✔
1398
    dot = NULL;
21✔
1399

1400
    if (path == NULL) {
21!
1401
        return 0;
×
1402
    }
1403

1404
    slash_forward = strrchr(path, '/');
21✔
1405
#if defined(_WIN32)
1406
    slash_backward = strrchr(path, '\\');
1407
    if (slash_backward != NULL &&
1408
            (slash_forward == NULL || slash_backward > slash_forward)) {
1409
        slash_forward = slash_backward;
1410
    }
1411
#endif
1412
    if (slash_forward == NULL) {
21!
1413
        start = path;
×
1414
    } else {
1415
        start = slash_forward + 1;
21✔
1416
    }
1417

1418
    dot = strrchr(start, '.');
21✔
1419
    if (dot == NULL) {
21!
1420
        return 0;
×
1421
    }
1422

1423
    if (dot[1] == '\0') {
21!
1424
        return 0;
×
1425
    }
1426

1427
    return 1;
21✔
1428
}
7✔
1429

1430
static int
1431
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1432
{
1433
    if (data == NULL || size < 3u) {
×
1434
        return 0;
×
1435
    }
1436
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
1437
        return 1;
×
1438
    }
1439
    return 0;
×
1440
}
1441

1442

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

1467
    status = SIXEL_FALSE;
×
1468
    buffer = NULL;
×
1469
    grown = NULL;
×
1470
    capacity = 0u;
×
1471
    used = 0u;
×
1472
    read_bytes = 0u;
×
1473
    needed = 0u;
×
1474
    new_capacity = 0u;
×
1475

1476
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1477
        sixel_helper_set_additional_message(
×
1478
            "sixel_palette_read_stream: invalid argument.");
1479
        return SIXEL_BAD_ARGUMENT;
×
1480
    }
1481

1482
    *pdata = NULL;
×
1483
    *psize = 0u;
×
1484

1485
    while (1) {
1486
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1487
        if (read_bytes == 0u) {
×
1488
            if (ferror(stream)) {
×
1489
                sixel_helper_set_additional_message(
×
1490
                    "sixel_palette_read_stream: fread() failed.");
1491
                status = SIXEL_LIBC_ERROR;
×
1492
                goto cleanup;
×
1493
            }
1494
            break;
×
1495
        }
1496

1497
        if (used > SIZE_MAX - read_bytes) {
×
1498
            sixel_helper_set_additional_message(
×
1499
                "sixel_palette_read_stream: size overflow.");
1500
            status = SIXEL_BAD_ALLOCATION;
×
1501
            goto cleanup;
×
1502
        }
1503
        needed = used + read_bytes;
×
1504

1505
        if (needed > capacity) {
×
1506
            new_capacity = capacity;
×
1507
            if (new_capacity == 0u) {
×
1508
                new_capacity = 4096u;
×
1509
            }
1510
            while (needed > new_capacity) {
×
1511
                if (new_capacity > SIZE_MAX / 2u) {
×
1512
                    sixel_helper_set_additional_message(
×
1513
                        "sixel_palette_read_stream: size overflow.");
1514
                    status = SIXEL_BAD_ALLOCATION;
×
1515
                    goto cleanup;
×
1516
                }
1517
                new_capacity *= 2u;
×
1518
            }
1519

1520
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1521
                                                             new_capacity);
1522
            if (grown == NULL) {
×
1523
                sixel_helper_set_additional_message(
×
1524
                    "sixel_palette_read_stream: allocation failed.");
1525
                status = SIXEL_BAD_ALLOCATION;
×
1526
                goto cleanup;
×
1527
            }
1528

1529
            if (buffer != NULL) {
×
1530
                memcpy(grown, buffer, used);
×
1531
                sixel_allocator_free(allocator, buffer);
×
1532
            }
1533

1534
            buffer = grown;
×
1535
            grown = NULL;
×
1536
            capacity = new_capacity;
×
1537
        }
1538

1539
        memcpy(buffer + used, scratch, read_bytes);
×
1540
        used += read_bytes;
×
1541
    }
1542

1543
    *pdata = buffer;
×
1544
    *psize = used;
×
1545
    status = SIXEL_OK;
×
1546
    return status;
×
1547

1548
cleanup:
1549
    if (grown != NULL) {
×
1550
        sixel_allocator_free(allocator, grown);
×
1551
    }
1552
    if (buffer != NULL) {
×
1553
        sixel_allocator_free(allocator, buffer);
×
1554
    }
1555
    return status;
×
1556
}
1557

1558

1559
static SIXELSTATUS
1560
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
×
1561
{
1562
    int error_value;
1563
    char error_message[256];
1564
#if HAVE_SYS_STAT_H
1565
    struct stat path_stat;
1566
#endif
1567

1568
    if (pstream == NULL || pclose == NULL || path == NULL) {
×
1569
        sixel_helper_set_additional_message(
×
1570
            "sixel_palette_open_read: invalid argument.");
1571
        return SIXEL_BAD_ARGUMENT;
×
1572
    }
1573

1574
    error_value = 0;
×
1575
    error_message[0] = '\0';
×
1576

1577
    if (strcmp(path, "-") == 0) {
×
1578
        *pstream = stdin;
×
1579
        *pclose = 0;
×
1580
        return SIXEL_OK;
×
1581
    }
1582

1583
#if HAVE_SYS_STAT_H
1584
    if (stat(path, &path_stat) == 0 && S_ISDIR(path_stat.st_mode)) {
×
1585
        sixel_compat_snprintf(error_message,
×
1586
                              sizeof(error_message),
1587
                              "sixel_palette_open_read: mapfile \"%s\" "
1588
                              "is a directory.",
1589
                              path);
1590
        sixel_helper_set_additional_message(error_message);
×
1591
        return SIXEL_BAD_INPUT;
×
1592
    }
1593
#endif
1594

1595
    errno = 0;
×
1596
    *pstream = fopen(path, "rb");
×
1597
    if (*pstream == NULL) {
×
1598
        error_value = errno;
×
1599
        sixel_compat_snprintf(error_message,
×
1600
                              sizeof(error_message),
1601
                              "sixel_palette_open_read: failed to open "
1602
                              "\"%s\": %s.",
1603
                              path,
1604
                              strerror(error_value));
1605
        sixel_helper_set_additional_message(error_message);
×
1606
        return SIXEL_LIBC_ERROR;
×
1607
    }
1608

1609
    *pclose = 1;
×
1610
    return SIXEL_OK;
×
1611
}
1612

1613

1614
static void
1615
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1616
{
1617
    if (close_stream && stream != NULL) {
×
1618
        (void) fclose(stream);
×
1619
    }
1620
}
×
1621

1622

1623
static sixel_palette_format_t
1624
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1625
{
1626
    size_t offset;
1627
    size_t data_size;
1628

1629
    offset = 0u;
×
1630
    data_size = size;
×
1631

1632
    if (data == NULL || size == 0u) {
×
1633
        return SIXEL_PALETTE_FORMAT_NONE;
×
1634
    }
1635

1636
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
1637
        return SIXEL_PALETTE_FORMAT_ACT;
×
1638
    }
1639

1640
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
1641
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
1642
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1643
    }
1644

1645
    if (sixel_palette_has_utf8_bom(data, size)) {
×
1646
        offset = 3u;
×
1647
        data_size = size - 3u;
×
1648
    }
1649

1650
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
×
1651
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1652
    }
1653
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
×
1654
        return SIXEL_PALETTE_FORMAT_GPL;
×
1655
    }
1656

1657
    return SIXEL_PALETTE_FORMAT_NONE;
×
1658
}
1659

1660

1661
static unsigned int
1662
sixel_palette_read_le16(unsigned char const *ptr)
×
1663
{
1664
    if (ptr == NULL) {
×
1665
        return 0u;
×
1666
    }
1667
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1668
}
1669

1670

1671
static unsigned int
1672
sixel_palette_read_le32(unsigned char const *ptr)
×
1673
{
1674
    if (ptr == NULL) {
×
1675
        return 0u;
×
1676
    }
1677
    return ((unsigned int)ptr[0])
×
1678
        | ((unsigned int)ptr[1] << 8)
×
1679
        | ((unsigned int)ptr[2] << 16)
×
1680
        | ((unsigned int)ptr[3] << 24);
×
1681
}
1682

1683

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

1708
    status = SIXEL_FALSE;
×
1709
    local = NULL;
×
1710
    palette_start = data;
×
1711
    trailer = NULL;
×
1712
    palette_obj = NULL;
×
1713
    exported_colors = 0;
×
1714
    start_index = 0;
×
1715

1716
    if (encoder == NULL || dither == NULL) {
×
1717
        sixel_helper_set_additional_message(
×
1718
            "sixel_palette_parse_act: invalid argument.");
1719
        return SIXEL_BAD_ARGUMENT;
×
1720
    }
1721
    if (data == NULL || size < 256u * 3u) {
×
1722
        sixel_helper_set_additional_message(
×
1723
            "sixel_palette_parse_act: truncated ACT palette.");
1724
        return SIXEL_BAD_INPUT;
×
1725
    }
1726

1727
    if (size == 256u * 3u) {
×
1728
        exported_colors = 256;
×
1729
        start_index = 0;
×
1730
    } else if (size == 256u * 3u + 4u) {
×
1731
        trailer = data + 256u * 3u;
×
1732
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
×
1733
                                | (unsigned int)trailer[1]);
×
1734
        start_index = (int)(((unsigned int)trailer[2] << 8)
×
1735
                            | (unsigned int)trailer[3]);
×
1736
    } else {
1737
        sixel_helper_set_additional_message(
×
1738
            "sixel_palette_parse_act: invalid ACT length.");
1739
        return SIXEL_BAD_INPUT;
×
1740
    }
1741

1742
    if (start_index < 0 || start_index >= 256) {
×
1743
        sixel_helper_set_additional_message(
×
1744
            "sixel_palette_parse_act: ACT start index out of range.");
1745
        return SIXEL_BAD_INPUT;
×
1746
    }
1747
    if (exported_colors <= 0 || exported_colors > 256) {
×
1748
        exported_colors = 256;
×
1749
    }
1750
    if (start_index + exported_colors > 256) {
×
1751
        sixel_helper_set_additional_message(
×
1752
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
1753
        return SIXEL_BAD_INPUT;
×
1754
    }
1755

1756
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
1757
    if (SIXEL_FAILED(status)) {
×
1758
        return status;
×
1759
    }
1760

1761
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1762

1763
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
1764
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
1765
        sixel_dither_unref(local);
×
1766
        return status;
×
1767
    }
1768
    status = sixel_palette_set_entries(
×
1769
        palette_obj,
1770
        palette_start + (size_t)start_index * 3u,
×
1771
        (unsigned int)exported_colors,
1772
        3,
1773
        encoder->allocator);
1774
    sixel_palette_unref(palette_obj);
×
1775
    if (SIXEL_FAILED(status)) {
×
1776
        sixel_dither_unref(local);
×
1777
        return status;
×
1778
    }
1779

1780
    *dither = local;
×
1781
    return SIXEL_OK;
×
1782
}
1783

1784

1785
static SIXELSTATUS
1786
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1787
                             size_t size,
1788
                             sixel_encoder_t *encoder,
1789
                             sixel_dither_t **dither)
1790
{
1791
    SIXELSTATUS status;
1792
    char *text;
1793
    size_t index;
1794
    size_t offset;
1795
    char *cursor;
1796
    char *line;
1797
    char *line_end;
1798
    int stage;
1799
    int exported_colors;
1800
    int parsed_colors;
1801
    sixel_dither_t *local;
1802
    sixel_palette_t *palette_obj;
1803
    unsigned char *palette_buffer;
1804
    long component;
1805
    char *parse_end;
1806
    int value_index;
1807
    int values[3];
1808
    char tail;
1809

1810
    status = SIXEL_FALSE;
×
1811
    text = NULL;
×
1812
    index = 0u;
×
1813
    offset = 0u;
×
1814
    cursor = NULL;
×
1815
    line = NULL;
×
1816
    line_end = NULL;
×
1817
    stage = 0;
×
1818
    exported_colors = 0;
×
1819
    parsed_colors = 0;
×
1820
    local = NULL;
×
1821
    palette_obj = NULL;
×
1822
    palette_buffer = NULL;
×
1823
    component = 0;
×
1824
    parse_end = NULL;
×
1825
    value_index = 0;
×
1826
    values[0] = 0;
×
1827
    values[1] = 0;
×
1828
    values[2] = 0;
×
1829

1830
    if (encoder == NULL || dither == NULL) {
×
1831
        sixel_helper_set_additional_message(
×
1832
            "sixel_palette_parse_pal_jasc: invalid argument.");
1833
        return SIXEL_BAD_ARGUMENT;
×
1834
    }
1835
    if (data == NULL || size == 0u) {
×
1836
        sixel_helper_set_additional_message(
×
1837
            "sixel_palette_parse_pal_jasc: empty palette.");
1838
        return SIXEL_BAD_INPUT;
×
1839
    }
1840

1841
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
1842
    if (text == NULL) {
×
1843
        sixel_helper_set_additional_message(
×
1844
            "sixel_palette_parse_pal_jasc: allocation failed.");
1845
        return SIXEL_BAD_ALLOCATION;
×
1846
    }
1847
    memcpy(text, data, size);
×
1848
    text[size] = '\0';
×
1849

1850
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1851
        offset = 3u;
×
1852
    }
1853
    cursor = text + offset;
×
1854

1855
    while (*cursor != '\0') {
×
1856
        line = cursor;
×
1857
        line_end = cursor;
×
1858
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1859
            ++line_end;
×
1860
        }
1861
        if (*line_end != '\0') {
×
1862
            *line_end = '\0';
×
1863
            cursor = line_end + 1;
×
1864
        } else {
1865
            cursor = line_end;
×
1866
        }
1867
        while (*cursor == '\n' || *cursor == '\r') {
×
1868
            ++cursor;
×
1869
        }
1870

1871
        while (*line == ' ' || *line == '\t') {
×
1872
            ++line;
×
1873
        }
1874
        index = strlen(line);
×
1875
        while (index > 0u) {
×
1876
            tail = line[index - 1];
×
1877
            if (tail != ' ' && tail != '\t') {
×
1878
                break;
×
1879
            }
1880
            line[index - 1] = '\0';
×
1881
            --index;
×
1882
        }
1883
        if (*line == '\0') {
×
1884
            continue;
×
1885
        }
1886
        if (*line == '#') {
×
1887
            continue;
×
1888
        }
1889

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

1938
        value_index = 0;
×
1939
        while (value_index < 3) {
×
1940
            component = strtol(line, &parse_end, 10);
×
1941
            if (parse_end == line || component < 0L || component > 255L) {
×
1942
                sixel_helper_set_additional_message(
×
1943
                    "sixel_palette_parse_pal_jasc: invalid component.");
1944
                status = SIXEL_BAD_INPUT;
×
1945
                goto cleanup;
×
1946
            }
1947
            values[value_index] = (int)component;
×
1948
            ++value_index;
×
1949
            line = parse_end;
×
1950
            while (*line == ' ' || *line == '\t') {
×
1951
                ++line;
×
1952
            }
1953
        }
1954

1955
        if (parsed_colors >= exported_colors) {
×
1956
            sixel_helper_set_additional_message(
×
1957
                "sixel_palette_parse_pal_jasc: excess entries.");
1958
            status = SIXEL_BAD_INPUT;
×
1959
            goto cleanup;
×
1960
        }
1961

1962
        palette_buffer[parsed_colors * 3 + 0] =
×
1963
            (unsigned char)values[0];
×
1964
        palette_buffer[parsed_colors * 3 + 1] =
×
1965
            (unsigned char)values[1];
×
1966
        palette_buffer[parsed_colors * 3 + 2] =
×
1967
            (unsigned char)values[2];
×
1968
        ++parsed_colors;
×
1969
    }
1970

1971
    if (stage < 3) {
×
1972
        sixel_helper_set_additional_message(
×
1973
            "sixel_palette_parse_pal_jasc: incomplete header.");
1974
        status = SIXEL_BAD_INPUT;
×
1975
        goto cleanup;
×
1976
    }
1977
    if (parsed_colors != exported_colors) {
×
1978
        sixel_helper_set_additional_message(
×
1979
            "sixel_palette_parse_pal_jasc: color count mismatch.");
1980
        status = SIXEL_BAD_INPUT;
×
1981
        goto cleanup;
×
1982
    }
1983

1984
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
1985
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
1986
        goto cleanup;
×
1987
    }
1988
    status = sixel_palette_set_entries(palette_obj,
×
1989
                                       palette_buffer,
1990
                                       (unsigned int)exported_colors,
1991
                                       3,
1992
                                       encoder->allocator);
1993
    sixel_palette_unref(palette_obj);
×
1994
    palette_obj = NULL;
×
1995
    if (SIXEL_FAILED(status)) {
×
1996
        goto cleanup;
×
1997
    }
1998

1999
    *dither = local;
×
2000
    status = SIXEL_OK;
×
2001

2002
cleanup:
2003
    if (palette_obj != NULL) {
×
2004
        sixel_palette_unref(palette_obj);
×
2005
    }
2006
    if (SIXEL_FAILED(status) && local != NULL) {
×
2007
        sixel_dither_unref(local);
×
2008
    }
2009
    if (palette_buffer != NULL) {
×
2010
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
2011
    }
2012
    if (text != NULL) {
×
2013
        sixel_allocator_free(encoder->allocator, text);
×
2014
    }
2015
    return status;
×
2016
}
2017

2018

2019
static SIXELSTATUS
2020
sixel_palette_parse_pal_riff(unsigned char const *data,
×
2021
                             size_t size,
2022
                             sixel_encoder_t *encoder,
2023
                             sixel_dither_t **dither)
2024
{
2025
    SIXELSTATUS status;
2026
    size_t offset;
2027
    size_t chunk_size;
2028
    sixel_dither_t *local;
2029
    sixel_palette_t *palette_obj;
2030
    unsigned char const *chunk;
2031
    unsigned char *palette_buffer;
2032
    unsigned int entry_count;
2033
    unsigned int version;
2034
    unsigned int index;
2035
    size_t palette_offset;
2036

2037
    status = SIXEL_FALSE;
×
2038
    offset = 0u;
×
2039
    chunk_size = 0u;
×
2040
    local = NULL;
×
2041
    chunk = NULL;
×
2042
    palette_obj = NULL;
×
2043
    palette_buffer = NULL;
×
2044
    entry_count = 0u;
×
2045
    version = 0u;
×
2046
    index = 0u;
×
2047
    palette_offset = 0u;
×
2048

2049
    if (encoder == NULL || dither == NULL) {
×
2050
        sixel_helper_set_additional_message(
×
2051
            "sixel_palette_parse_pal_riff: invalid argument.");
2052
        return SIXEL_BAD_ARGUMENT;
×
2053
    }
2054
    if (data == NULL || size < 12u) {
×
2055
        sixel_helper_set_additional_message(
×
2056
            "sixel_palette_parse_pal_riff: truncated palette.");
2057
        return SIXEL_BAD_INPUT;
×
2058
    }
2059
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
×
2060
        sixel_helper_set_additional_message(
×
2061
            "sixel_palette_parse_pal_riff: missing RIFF header.");
2062
        return SIXEL_BAD_INPUT;
×
2063
    }
2064

2065
    offset = 12u;
×
2066
    while (offset + 8u <= size) {
×
2067
        chunk = data + offset;
×
2068
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
×
2069
        if (offset + 8u + chunk_size > size) {
×
2070
            sixel_helper_set_additional_message(
×
2071
                "sixel_palette_parse_pal_riff: chunk extends past end.");
2072
            return SIXEL_BAD_INPUT;
×
2073
        }
2074
        if (memcmp(chunk, "data", 4) == 0) {
×
2075
            break;
×
2076
        }
2077
        offset += 8u + ((chunk_size + 1u) & ~1u);
×
2078
    }
2079

2080
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
2081
        sixel_helper_set_additional_message(
×
2082
            "sixel_palette_parse_pal_riff: missing data chunk.");
2083
        return SIXEL_BAD_INPUT;
×
2084
    }
2085

2086
    if (chunk_size < 4u) {
×
2087
        sixel_helper_set_additional_message(
×
2088
            "sixel_palette_parse_pal_riff: data chunk too small.");
2089
        return SIXEL_BAD_INPUT;
×
2090
    }
2091
    version = sixel_palette_read_le16(chunk + 8);
×
2092
    (void)version;
2093
    entry_count = sixel_palette_read_le16(chunk + 10);
×
2094
    if (entry_count == 0u || entry_count > 256u) {
×
2095
        sixel_helper_set_additional_message(
×
2096
            "sixel_palette_parse_pal_riff: invalid entry count.");
2097
        return SIXEL_BAD_INPUT;
×
2098
    }
2099
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
×
2100
        sixel_helper_set_additional_message(
×
2101
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
2102
        return SIXEL_BAD_INPUT;
×
2103
    }
2104

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

2129
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2130
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2131
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
2132
        sixel_dither_unref(local);
×
2133
        return status;
×
2134
    }
2135
    status = sixel_palette_set_entries(palette_obj,
×
2136
                                       palette_buffer,
2137
                                       (unsigned int)entry_count,
2138
                                       3,
2139
                                       encoder->allocator);
2140
    sixel_palette_unref(palette_obj);
×
2141
    palette_obj = NULL;
×
2142
    sixel_allocator_free(encoder->allocator, palette_buffer);
×
2143
    palette_buffer = NULL;
×
2144
    if (SIXEL_FAILED(status)) {
×
2145
        sixel_dither_unref(local);
×
2146
        return status;
×
2147
    }
2148

2149
    *dither = local;
×
2150
    return SIXEL_OK;
×
2151
}
2152

2153

2154
static SIXELSTATUS
2155
sixel_palette_parse_gpl(unsigned char const *data,
×
2156
                        size_t size,
2157
                        sixel_encoder_t *encoder,
2158
                        sixel_dither_t **dither)
2159
{
2160
    SIXELSTATUS status;
2161
    char *text;
2162
    size_t offset;
2163
    char *cursor;
2164
    char *line;
2165
    char *line_end;
2166
    size_t index;
2167
    int header_seen;
2168
    int parsed_colors;
2169
    unsigned char palette_bytes[256 * 3];
2170
    long component;
2171
    char *parse_end;
2172
    int value_index;
2173
    int values[3];
2174
    sixel_dither_t *local;
2175
    sixel_palette_t *palette_obj;
2176
    char tail;
2177

2178
    status = SIXEL_FALSE;
×
2179
    text = NULL;
×
2180
    offset = 0u;
×
2181
    cursor = NULL;
×
2182
    line = NULL;
×
2183
    line_end = NULL;
×
2184
    index = 0u;
×
2185
    header_seen = 0;
×
2186
    parsed_colors = 0;
×
2187
    component = 0;
×
2188
    parse_end = NULL;
×
2189
    value_index = 0;
×
2190
    values[0] = 0;
×
2191
    values[1] = 0;
×
2192
    values[2] = 0;
×
2193
    local = NULL;
×
2194
    palette_obj = NULL;
×
2195

2196
    if (encoder == NULL || dither == NULL) {
×
2197
        sixel_helper_set_additional_message(
×
2198
            "sixel_palette_parse_gpl: invalid argument.");
2199
        return SIXEL_BAD_ARGUMENT;
×
2200
    }
2201
    if (data == NULL || size == 0u) {
×
2202
        sixel_helper_set_additional_message(
×
2203
            "sixel_palette_parse_gpl: empty palette.");
2204
        return SIXEL_BAD_INPUT;
×
2205
    }
2206

2207
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
2208
    if (text == NULL) {
×
2209
        sixel_helper_set_additional_message(
×
2210
            "sixel_palette_parse_gpl: allocation failed.");
2211
        return SIXEL_BAD_ALLOCATION;
×
2212
    }
2213
    memcpy(text, data, size);
×
2214
    text[size] = '\0';
×
2215

2216
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2217
        offset = 3u;
×
2218
    }
2219
    cursor = text + offset;
×
2220

2221
    while (*cursor != '\0') {
×
2222
        line = cursor;
×
2223
        line_end = cursor;
×
2224
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2225
            ++line_end;
×
2226
        }
2227
        if (*line_end != '\0') {
×
2228
            *line_end = '\0';
×
2229
            cursor = line_end + 1;
×
2230
        } else {
2231
            cursor = line_end;
×
2232
        }
2233
        while (*cursor == '\n' || *cursor == '\r') {
×
2234
            ++cursor;
×
2235
        }
2236

2237
        while (*line == ' ' || *line == '\t') {
×
2238
            ++line;
×
2239
        }
2240
        index = strlen(line);
×
2241
        while (index > 0u) {
×
2242
            tail = line[index - 1];
×
2243
            if (tail != ' ' && tail != '\t') {
×
2244
                break;
×
2245
            }
2246
            line[index - 1] = '\0';
×
2247
            --index;
×
2248
        }
2249
        if (*line == '\0') {
×
2250
            continue;
×
2251
        }
2252
        if (*line == '#') {
×
2253
            continue;
×
2254
        }
2255
        if (strncmp(line, "Name:", 5) == 0) {
×
2256
            continue;
×
2257
        }
2258
        if (strncmp(line, "Columns:", 8) == 0) {
×
2259
            continue;
×
2260
        }
2261

2262
        if (!header_seen) {
×
2263
            if (strcmp(line, "GIMP Palette") != 0) {
×
2264
                sixel_helper_set_additional_message(
×
2265
                    "sixel_palette_parse_gpl: missing header.");
2266
                status = SIXEL_BAD_INPUT;
×
2267
                goto cleanup;
×
2268
            }
2269
            header_seen = 1;
×
2270
            continue;
×
2271
        }
2272

2273
        if (parsed_colors >= 256) {
×
2274
            sixel_helper_set_additional_message(
×
2275
                "sixel_palette_parse_gpl: too many colors.");
2276
            status = SIXEL_BAD_INPUT;
×
2277
            goto cleanup;
×
2278
        }
2279

2280
        value_index = 0;
×
2281
        while (value_index < 3) {
×
2282
            component = strtol(line, &parse_end, 10);
×
2283
            if (parse_end == line || component < 0L || component > 255L) {
×
2284
                sixel_helper_set_additional_message(
×
2285
                    "sixel_palette_parse_gpl: invalid component.");
2286
                status = SIXEL_BAD_INPUT;
×
2287
                goto cleanup;
×
2288
            }
2289
            values[value_index] = (int)component;
×
2290
            ++value_index;
×
2291
            line = parse_end;
×
2292
            while (*line == ' ' || *line == '\t') {
×
2293
                ++line;
×
2294
            }
2295
        }
2296

2297
        palette_bytes[parsed_colors * 3 + 0] =
×
2298
            (unsigned char)values[0];
×
2299
        palette_bytes[parsed_colors * 3 + 1] =
×
2300
            (unsigned char)values[1];
×
2301
        palette_bytes[parsed_colors * 3 + 2] =
×
2302
            (unsigned char)values[2];
×
2303
        ++parsed_colors;
×
2304
    }
2305

2306
    if (!header_seen) {
×
2307
        sixel_helper_set_additional_message(
×
2308
            "sixel_palette_parse_gpl: header missing.");
2309
        status = SIXEL_BAD_INPUT;
×
2310
        goto cleanup;
×
2311
    }
2312
    if (parsed_colors <= 0) {
×
2313
        sixel_helper_set_additional_message(
×
2314
            "sixel_palette_parse_gpl: no colors parsed.");
2315
        status = SIXEL_BAD_INPUT;
×
2316
        goto cleanup;
×
2317
    }
2318

2319
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2320
    if (SIXEL_FAILED(status)) {
×
2321
        goto cleanup;
×
2322
    }
2323
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2324
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2325
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2326
        goto cleanup;
×
2327
    }
2328
    status = sixel_palette_set_entries(palette_obj,
×
2329
                                       palette_bytes,
2330
                                       (unsigned int)parsed_colors,
2331
                                       3,
2332
                                       encoder->allocator);
2333
    sixel_palette_unref(palette_obj);
×
2334
    palette_obj = NULL;
×
2335
    if (SIXEL_FAILED(status)) {
×
2336
        goto cleanup;
×
2337
    }
2338

2339
    *dither = local;
×
2340
    status = SIXEL_OK;
×
2341

2342
cleanup:
2343
    if (palette_obj != NULL) {
×
2344
        sixel_palette_unref(palette_obj);
×
2345
    }
2346
    if (SIXEL_FAILED(status) && local != NULL) {
×
2347
        sixel_dither_unref(local);
×
2348
    }
2349
    if (text != NULL) {
×
2350
        sixel_allocator_free(encoder->allocator, text);
×
2351
    }
2352
    return status;
×
2353
}
2354

2355

2356
/*
2357
 * Palette exporters
2358
 *
2359
 *   +----------+-------------------------+
2360
 *   | format   | emission strategy       |
2361
 *   +----------+-------------------------+
2362
 *   | ACT      | fixed 256 entries + EOF |
2363
 *   | PAL JASC | textual lines           |
2364
 *   | PAL RIFF | RIFF container          |
2365
 *   | GPL      | textual lines           |
2366
 *   +----------+-------------------------+
2367
 */
2368
static SIXELSTATUS
2369
sixel_palette_write_act(FILE *stream,
×
2370
                        unsigned char const *palette,
2371
                        int exported_colors)
2372
{
2373
    SIXELSTATUS status;
2374
    unsigned char act_table[256 * 3];
2375
    unsigned char trailer[4];
2376
    size_t exported_bytes;
2377

2378
    status = SIXEL_FALSE;
×
2379
    exported_bytes = 0u;
×
2380

2381
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2382
        return SIXEL_BAD_ARGUMENT;
×
2383
    }
2384
    if (exported_colors > 256) {
×
2385
        exported_colors = 256;
×
2386
    }
2387

2388
    memset(act_table, 0, sizeof(act_table));
×
2389
    exported_bytes = (size_t)exported_colors * 3u;
×
2390
    memcpy(act_table, palette, exported_bytes);
×
2391

2392
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2393
                                 & 0xffu);
2394
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2395
    trailer[2] = 0u;
×
2396
    trailer[3] = 0u;
×
2397

2398
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2399
            != sizeof(act_table)) {
2400
        status = SIXEL_LIBC_ERROR;
×
2401
        return status;
×
2402
    }
2403
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2404
            != sizeof(trailer)) {
2405
        status = SIXEL_LIBC_ERROR;
×
2406
        return status;
×
2407
    }
2408

2409
    return SIXEL_OK;
×
2410
}
2411

2412

2413
static SIXELSTATUS
2414
sixel_palette_write_pal_jasc(FILE *stream,
×
2415
                             unsigned char const *palette,
2416
                             int exported_colors)
2417
{
2418
    int index;
2419

2420
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2421
        return SIXEL_BAD_ARGUMENT;
×
2422
    }
2423
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
2424
        return SIXEL_LIBC_ERROR;
×
2425
    }
2426
    for (index = 0; index < exported_colors; ++index) {
×
2427
        if (fprintf(stream, "%d %d %d\n",
×
2428
                    (int)palette[index * 3 + 0],
×
2429
                    (int)palette[index * 3 + 1],
×
2430
                    (int)palette[index * 3 + 2]) < 0) {
×
2431
            return SIXEL_LIBC_ERROR;
×
2432
        }
2433
    }
2434
    return SIXEL_OK;
×
2435
}
2436

2437

2438
static SIXELSTATUS
2439
sixel_palette_write_pal_riff(FILE *stream,
×
2440
                             unsigned char const *palette,
2441
                             int exported_colors)
2442
{
2443
    unsigned char header[12];
2444
    unsigned char chunk[8];
2445
    unsigned char log_palette[4 + 256 * 4];
2446
    unsigned int data_size;
2447
    unsigned int riff_size;
2448
    int index;
2449

2450
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2451
        return SIXEL_BAD_ARGUMENT;
×
2452
    }
2453
    if (exported_colors > 256) {
×
2454
        exported_colors = 256;
×
2455
    }
2456

2457
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2458
    riff_size = 4u + 8u + data_size;
×
2459

2460
    memcpy(header, "RIFF", 4);
×
2461
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2462
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2463
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2464
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2465
    memcpy(header + 8, "PAL ", 4);
×
2466

2467
    memcpy(chunk, "data", 4);
×
2468
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2469
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2470
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2471
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2472

2473
    memset(log_palette, 0, sizeof(log_palette));
×
2474
    log_palette[0] = 0x00;
×
2475
    log_palette[1] = 0x03;
×
2476
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
2477
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
2478
    for (index = 0; index < exported_colors; ++index) {
×
2479
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
2480
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
2481
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
2482
        log_palette[4 + index * 4 + 3] = 0u;
×
2483
    }
2484

2485
    if (fwrite(header, 1, sizeof(header), stream)
×
2486
            != sizeof(header)) {
2487
        return SIXEL_LIBC_ERROR;
×
2488
    }
2489
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2490
        return SIXEL_LIBC_ERROR;
×
2491
    }
2492
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2493
            != (size_t)data_size) {
×
2494
        return SIXEL_LIBC_ERROR;
×
2495
    }
2496
    return SIXEL_OK;
×
2497
}
2498

2499

2500
static SIXELSTATUS
2501
sixel_palette_write_gpl(FILE *stream,
×
2502
                        unsigned char const *palette,
2503
                        int exported_colors)
2504
{
2505
    int index;
2506

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

2534

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

2563
    status = SIXEL_FALSE;
21✔
2564
    loader = NULL;
21✔
2565
    fstatic = 1;
21✔
2566
    fuse_palette = 1;
21✔
2567
    reqcolors = SIXEL_PALETTE_MAX;
21✔
2568
    loop_override = SIXEL_LOOP_DISABLE;
21✔
2569
    path = NULL;
21✔
2570
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
2571
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
21✔
2572
    format_final = SIXEL_PALETTE_FORMAT_NONE;
21✔
2573
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
21✔
2574
    stream = NULL;
21✔
2575
    close_stream = 0;
21✔
2576
    buffer = NULL;
21✔
2577
    buffer_size = 0u;
21✔
2578
    palette_request = 0;
21✔
2579
    need_detection = 0;
21✔
2580
    treat_as_image = 0;
21✔
2581
    path_has_extension = 0;
21✔
2582
    mapfile_message[0] = '\0';
21✔
2583

2584
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
21!
2585
        sixel_helper_set_additional_message(
×
2586
            "sixel_prepare_specified_palette: invalid mapfile path.");
2587
        return SIXEL_BAD_ARGUMENT;
×
2588
    }
2589

2590
    sixel_encoder_log_stage(encoder,
28✔
2591
                            NULL,
2592
                            "palette",
2593
                            "worker",
2594
                            "start",
2595
                            "mapfile=%s",
2596
                            encoder->mapfile);
7✔
2597

2598
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
21✔
2599
    if (path == NULL || *path == '\0') {
21!
2600
        sixel_helper_set_additional_message(
×
2601
            "sixel_prepare_specified_palette: empty mapfile path.");
2602
        return SIXEL_BAD_ARGUMENT;
×
2603
    }
2604

2605
    format_ext = sixel_palette_format_from_extension(path);
21✔
2606
    path_has_extension = sixel_path_has_any_extension(path);
21✔
2607

2608
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
21!
2609
        palette_request = 1;
×
2610
        format_final = format_hint;
×
2611
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
21!
2612
        palette_request = 1;
×
2613
        format_final = format_ext;
×
2614
    } else if (!path_has_extension) {
21!
2615
        palette_request = 1;
×
2616
        need_detection = 1;
×
2617
    } else {
2618
        treat_as_image = 1;
21✔
2619
    }
2620

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

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

2686
        switch (format_final) {
×
2687
        case SIXEL_PALETTE_FORMAT_ACT:
2688
            status = sixel_palette_parse_act(buffer,
×
2689
                                             buffer_size,
2690
                                             encoder,
2691
                                             dither);
2692
            break;
×
2693
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
2694
            status = sixel_palette_parse_pal_jasc(buffer,
×
2695
                                                  buffer_size,
2696
                                                  encoder,
2697
                                                  dither);
2698
            break;
×
2699
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
2700
            status = sixel_palette_parse_pal_riff(buffer,
×
2701
                                                  buffer_size,
2702
                                                  encoder,
2703
                                                  dither);
2704
            break;
×
2705
        case SIXEL_PALETTE_FORMAT_GPL:
2706
            status = sixel_palette_parse_gpl(buffer,
×
2707
                                             buffer_size,
2708
                                             encoder,
2709
                                             dither);
2710
            break;
×
2711
        default:
2712
            sixel_helper_set_additional_message(
×
2713
                "sixel_prepare_specified_palette: "
2714
                "unsupported palette format.");
2715
            status = SIXEL_BAD_INPUT;
×
2716
            break;
×
2717
        }
2718

2719
palette_cleanup:
2720
        if (buffer != NULL) {
×
2721
            sixel_allocator_free(encoder->allocator, buffer);
×
2722
            buffer = NULL;
×
2723
        }
2724
        if (stream != NULL) {
×
2725
            sixel_palette_close_stream(stream, close_stream);
×
2726
            stream = NULL;
×
2727
        }
2728
        if (SIXEL_SUCCEEDED(status)) {
×
2729
            return status;
×
2730
        }
2731
        if (!treat_as_image) {
×
2732
            return status;
×
2733
        }
2734
    }
2735

2736
    callback_context.reqcolors = encoder->reqcolors;
21✔
2737
    callback_context.dither = NULL;
21✔
2738
    callback_context.allocator = encoder->allocator;
21✔
2739
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
2740
    callback_context.lut_policy = encoder->lut_policy;
21✔
2741

2742
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
2743
    sixel_helper_set_thumbnail_size_hint(
21✔
2744
        sixel_encoder_thumbnail_hint(encoder));
7✔
2745
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
2746
    if (SIXEL_FAILED(status)) {
21!
2747
        goto end_loader;
×
2748
    }
2749

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

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

2764
    status = sixel_loader_setopt(loader,
21✔
2765
                                 SIXEL_LOADER_OPTION_REQCOLORS,
2766
                                 &reqcolors);
2767
    if (SIXEL_FAILED(status)) {
21!
2768
        goto end_loader;
×
2769
    }
2770

2771
    status = sixel_loader_setopt(loader,
28✔
2772
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2773
                                 encoder->bgcolor);
21✔
2774
    if (SIXEL_FAILED(status)) {
21!
2775
        goto end_loader;
×
2776
    }
2777

2778
    status = sixel_loader_setopt(loader,
21✔
2779
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
2780
                                 &loop_override);
2781
    if (SIXEL_FAILED(status)) {
21!
2782
        goto end_loader;
×
2783
    }
2784

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

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

2799
    status = sixel_loader_setopt(loader,
28✔
2800
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
2801
                                 encoder->loader_order);
21✔
2802
    if (SIXEL_FAILED(status)) {
21!
2803
        goto end_loader;
×
2804
    }
2805

2806
    status = sixel_loader_setopt(loader,
21✔
2807
                                 SIXEL_LOADER_OPTION_CONTEXT,
2808
                                 &callback_context);
2809
    if (SIXEL_FAILED(status)) {
21!
2810
        goto end_loader;
×
2811
    }
2812

2813
    status = sixel_loader_load_file(loader,
28✔
2814
                                    encoder->mapfile,
21✔
2815
                                    load_image_callback_for_palette);
2816
    if (status != SIXEL_OK) {
21!
2817
        goto end_loader;
×
2818
    }
2819

2820
end_loader:
14✔
2821
    sixel_loader_unref(loader);
21✔
2822

2823
    if (status != SIXEL_OK) {
21!
2824
        return status;
×
2825
    }
2826

2827
    if (! callback_context.dither) {
21!
2828
        sixel_compat_snprintf(mapfile_message,
×
2829
                              sizeof(mapfile_message),
2830
                              "sixel_prepare_specified_palette() failed.\n"
2831
                              "reason: mapfile \"%s\" is empty.",
2832
                              encoder->mapfile != NULL
×
2833
                                ? encoder->mapfile
2834
                                : "");
2835
        sixel_helper_set_additional_message(mapfile_message);
×
2836
        return SIXEL_BAD_INPUT;
×
2837
    }
2838

2839
    *dither = callback_context.dither;
21✔
2840

2841
    sixel_encoder_log_stage(encoder,
28✔
2842
                            NULL,
2843
                            "palette",
2844
                            "worker",
2845
                            "finish",
2846
                            "mapfile=%s format=%d",
2847
                            encoder->mapfile,
7✔
2848
                            format_final);
7✔
2849

2850
    return status;
21✔
2851
}
7✔
2852

2853

2854
/* create dither object from a frame */
2855
static SIXELSTATUS
2856
sixel_encoder_prepare_palette(
520✔
2857
    sixel_encoder_t *encoder,  /* encoder object */
2858
    sixel_frame_t   *frame,    /* input frame object */
2859
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2860
{
2861
    SIXELSTATUS status = SIXEL_FALSE;
520✔
2862
    int histogram_colors;
2863
    sixel_assessment_t *assessment;
2864
    int promoted_stage;
2865

2866
    assessment = NULL;
520✔
2867
    promoted_stage = 0;
520✔
2868
    if (encoder != NULL) {
520!
2869
        assessment = encoder->assessment_observer;
520✔
2870
    }
174✔
2871

2872
    switch (encoder->color_option) {
520!
2873
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
24✔
2874
        if (encoder->dither_cache) {
36!
2875
            *dither = encoder->dither_cache;
×
2876
            status = SIXEL_OK;
×
2877
        } else {
2878
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
2879
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
2880
        }
2881
        goto end;
36✔
2882
    case SIXEL_COLOR_OPTION_MONOCHROME:
8✔
2883
        if (encoder->dither_cache) {
12!
2884
            *dither = encoder->dither_cache;
×
2885
            status = SIXEL_OK;
×
2886
        } else {
2887
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
2888
        }
2889
        goto end;
12✔
2890
    case SIXEL_COLOR_OPTION_MAPFILE:
14✔
2891
        if (encoder->dither_cache) {
21!
2892
            *dither = encoder->dither_cache;
×
2893
            status = SIXEL_OK;
×
2894
        } else {
2895
            status = sixel_prepare_specified_palette(dither, encoder);
21✔
2896
        }
2897
        goto end;
21✔
2898
    case SIXEL_COLOR_OPTION_BUILTIN:
18✔
2899
        if (encoder->dither_cache) {
27!
2900
            *dither = encoder->dither_cache;
×
2901
            status = SIXEL_OK;
×
2902
        } else {
2903
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
2904
        }
2905
        goto end;
27✔
2906
    case SIXEL_COLOR_OPTION_DEFAULT:
424✔
2907
    default:
2908
        break;
424✔
2909
    }
2910

2911
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
424✔
2912
        if (!sixel_frame_get_palette(frame)) {
228!
2913
            status = SIXEL_LOGIC_ERROR;
×
2914
            goto end;
×
2915
        }
2916
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
304✔
2917
                                  encoder->allocator);
76✔
2918
        if (SIXEL_FAILED(status)) {
228!
2919
            goto end;
×
2920
        }
2921
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
228✔
2922
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
228✔
2923
        if (sixel_frame_get_transparent(frame) != (-1)) {
228!
2924
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2925
        }
2926
        if (*dither && encoder->dither_cache) {
228!
2927
            sixel_dither_unref(encoder->dither_cache);
×
2928
        }
2929
        goto end;
228✔
2930
    }
2931

2932
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
196!
2933
        switch (sixel_frame_get_pixelformat(frame)) {
×
2934
        case SIXEL_PIXELFORMAT_G1:
2935
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2936
            break;
×
2937
        case SIXEL_PIXELFORMAT_G2:
2938
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2939
            break;
×
2940
        case SIXEL_PIXELFORMAT_G4:
2941
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2942
            break;
×
2943
        case SIXEL_PIXELFORMAT_G8:
2944
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
2945
            break;
×
2946
        default:
2947
            *dither = NULL;
×
2948
            status = SIXEL_LOGIC_ERROR;
×
2949
            goto end;
×
2950
        }
2951
        if (*dither && encoder->dither_cache) {
×
2952
            sixel_dither_unref(encoder->dither_cache);
×
2953
        }
2954
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
2955
        status = SIXEL_OK;
×
2956
        goto end;
×
2957
    }
2958

2959
    if (encoder->dither_cache) {
196!
2960
        sixel_dither_unref(encoder->dither_cache);
×
2961
    }
2962
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
196✔
2963
    if (SIXEL_FAILED(status)) {
196!
2964
        goto end;
×
2965
    }
2966

2967
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
196✔
2968
    sixel_dither_set_sixel_reversible(*dither,
262✔
2969
                                      encoder->sixel_reversible);
66✔
2970
    sixel_dither_set_final_merge(*dither, encoder->final_merge_mode);
196✔
2971
    (*dither)->quantize_model = encoder->quantize_model;
196✔
2972

2973
    status = sixel_dither_initialize(*dither,
262✔
2974
                                     sixel_frame_get_pixels(frame),
66✔
2975
                                     sixel_frame_get_width(frame),
66✔
2976
                                     sixel_frame_get_height(frame),
66✔
2977
                                     sixel_frame_get_pixelformat(frame),
66✔
2978
                                     encoder->method_for_largest,
66✔
2979
                                     encoder->method_for_rep,
66✔
2980
                                     encoder->quality_mode);
66✔
2981
    if (SIXEL_FAILED(status)) {
196!
2982
        sixel_dither_unref(*dither);
×
2983
        goto end;
×
2984
    }
2985

2986
    if (assessment != NULL && promoted_stage == 0) {
196!
2987
        sixel_assessment_stage_transition(
3✔
2988
            assessment,
1✔
2989
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2990
        promoted_stage = 1;
3✔
2991
    }
1✔
2992

2993
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
196✔
2994
    if (histogram_colors <= encoder->reqcolors) {
196✔
2995
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
127✔
2996
    }
43✔
2997
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
196✔
2998

2999
    status = SIXEL_OK;
196✔
3000

3001
end:
346✔
3002
    if (assessment != NULL && promoted_stage == 0) {
520!
3003
        sixel_assessment_stage_transition(
×
3004
            assessment,
3005
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
3006
        promoted_stage = 1;
×
3007
    }
3008
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
520!
3009
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
520✔
3010
        /* pass down the user's demand for an exact palette size */
3011
        (*dither)->force_palette = encoder->force_palette;
520✔
3012
    }
174✔
3013
    return status;
520✔
3014
}
3015

3016

3017
/* resize a frame with settings of specified encoder object */
3018
static SIXELSTATUS
3019
sixel_encoder_do_resize(
526✔
3020
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3021
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3022
{
3023
    SIXELSTATUS status = SIXEL_FALSE;
526✔
3024
    int src_width;
3025
    int src_height;
3026
    int dst_width;
3027
    int dst_height;
3028

3029
    /* get frame width and height */
3030
    src_width = sixel_frame_get_width(frame);
526✔
3031
    src_height = sixel_frame_get_height(frame);
526✔
3032

3033
    if (src_width < 1) {
526✔
3034
         sixel_helper_set_additional_message(
6✔
3035
             "sixel_encoder_do_resize: "
3036
             "detected a frame with a non-positive width.");
3037
        return SIXEL_BAD_ARGUMENT;
6✔
3038
    }
3039

3040
    if (src_height < 1) {
520!
3041
         sixel_helper_set_additional_message(
×
3042
             "sixel_encoder_do_resize: "
3043
             "detected a frame with a non-positive height.");
3044
        return SIXEL_BAD_ARGUMENT;
×
3045
    }
3046

3047
    /* settings around scaling */
3048
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
520✔
3049
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
520✔
3050

3051
    /* if the encoder has percentwidth or percentheight property,
3052
       convert them to pixelwidth / pixelheight */
3053
    if (encoder->percentwidth > 0) {
520✔
3054
        dst_width = src_width * encoder->percentwidth / 100;
12✔
3055
    }
4✔
3056
    if (encoder->percentheight > 0) {
520✔
3057
        dst_height = src_height * encoder->percentheight / 100;
9✔
3058
    }
3✔
3059

3060
    /* if only either width or height is set, set also the other
3061
       to retain frame aspect ratio */
3062
    if (dst_width > 0 && dst_height <= 0) {
520✔
3063
        dst_height = src_height * dst_width / src_width;
42✔
3064
    }
14✔
3065
    if (dst_height > 0 && dst_width <= 0) {
520✔
3066
        dst_width = src_width * dst_height / src_height;
33✔
3067
    }
11✔
3068

3069
    sixel_encoder_log_stage(encoder,
694✔
3070
                            frame,
174✔
3071
                            "scale",
3072
                            "worker",
3073
                            "start",
3074
                            "src=%dx%d dst=%dx%d resample=%d",
3075
                            src_width,
174✔
3076
                            src_height,
174✔
3077
                            dst_width,
174✔
3078
                            dst_height,
174✔
3079
                            encoder->method_for_resampling);
174✔
3080

3081
    /* do resize */
3082
    if (dst_width > 0 && dst_height > 0) {
520!
3083
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
3084
                                    encoder->method_for_resampling);
31✔
3085
        if (SIXEL_FAILED(status)) {
93!
3086
            goto end;
×
3087
        }
3088
    }
31✔
3089

3090
    sixel_encoder_log_stage(encoder,
694✔
3091
                            frame,
174✔
3092
                            "scale",
3093
                            "worker",
3094
                            "finish",
3095
                            "result=%dx%d",
3096
                            sixel_frame_get_width(frame),
174✔
3097
                            sixel_frame_get_height(frame));
174✔
3098

3099
    /* success */
3100
    status = SIXEL_OK;
520✔
3101

3102
end:
346✔
3103
    return status;
520✔
3104
}
176✔
3105

3106

3107
/* clip a frame with settings of specified encoder object */
3108
static SIXELSTATUS
3109
sixel_encoder_do_clip(
520✔
3110
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3111
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3112
{
3113
    SIXELSTATUS status = SIXEL_FALSE;
520✔
3114
    int src_width;
3115
    int src_height;
3116
    int clip_x;
3117
    int clip_y;
3118
    int clip_w;
3119
    int clip_h;
3120

3121
    /* get frame width and height */
3122
    src_width = sixel_frame_get_width(frame);
520✔
3123
    src_height = sixel_frame_get_height(frame);
520✔
3124

3125
    /* settings around clipping */
3126
    clip_x = encoder->clipx;
520✔
3127
    clip_y = encoder->clipy;
520✔
3128
    clip_w = encoder->clipwidth;
520✔
3129
    clip_h = encoder->clipheight;
520✔
3130

3131
    /* adjust clipping width with comparing it to frame width */
3132
    if (clip_w + clip_x > src_width) {
520✔
3133
        if (clip_x > src_width) {
6✔
3134
            clip_w = 0;
3✔
3135
        } else {
1✔
3136
            clip_w = src_width - clip_x;
3✔
3137
        }
3138
    }
2✔
3139

3140
    /* adjust clipping height with comparing it to frame height */
3141
    if (clip_h + clip_y > src_height) {
520✔
3142
        if (clip_y > src_height) {
6✔
3143
            clip_h = 0;
3✔
3144
        } else {
1✔
3145
            clip_h = src_height - clip_y;
3✔
3146
        }
3147
    }
2✔
3148

3149
    sixel_encoder_log_stage(encoder,
694✔
3150
                            frame,
174✔
3151
                            "crop",
3152
                            "worker",
3153
                            "start",
3154
                            "src=%dx%d region=%dx%d@%d,%d",
3155
                            src_width,
174✔
3156
                            src_height,
174✔
3157
                            clip_w,
174✔
3158
                            clip_h,
174✔
3159
                            clip_x,
174✔
3160
                            clip_y);
174✔
3161

3162
    /* do clipping */
3163
    if (clip_w > 0 && clip_h > 0) {
520!
3164
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
12✔
3165
        if (SIXEL_FAILED(status)) {
12!
3166
            goto end;
×
3167
        }
3168
    }
4✔
3169

3170
    sixel_encoder_log_stage(encoder,
694✔
3171
                            frame,
174✔
3172
                            "crop",
3173
                            "worker",
3174
                            "finish",
3175
                            "result=%dx%d",
3176
                            sixel_frame_get_width(frame),
174✔
3177
                            sixel_frame_get_height(frame));
174✔
3178

3179
    /* success */
3180
    status = SIXEL_OK;
520✔
3181

3182
end:
346✔
3183
    return status;
520✔
3184
}
3185

3186

3187
static void
3188
sixel_debug_print_palette(
3✔
3189
    sixel_dither_t /* in */ *dither /* dithering object */
3190
)
3191
{
3192
    sixel_palette_t *palette_obj;
3193
    unsigned char *palette_copy;
3194
    size_t palette_count;
3195
    int i;
3196

3197
    palette_obj = NULL;
3✔
3198
    palette_copy = NULL;
3✔
3199
    palette_count = 0U;
3✔
3200
    if (dither == NULL) {
3!
3201
        return;
×
3202
    }
3203

3204
    if (SIXEL_FAILED(
3!
3205
            sixel_dither_get_quantized_palette(dither, &palette_obj))
3206
            || palette_obj == NULL) {
3!
3207
        return;
×
3208
    }
3209
    if (SIXEL_FAILED(sixel_palette_copy_entries_8bit(
3!
3210
            palette_obj,
3211
            &palette_copy,
3212
            &palette_count,
3213
            SIXEL_PIXELFORMAT_RGB888,
3214
            dither->allocator))
3215
            || palette_copy == NULL) {
3!
3216
        sixel_palette_unref(palette_obj);
×
3217
        return;
×
3218
    }
3219
    sixel_palette_unref(palette_obj);
3✔
3220

3221
    fprintf(stderr, "palette:\n");
3✔
3222
    for (i = 0; i < (int)palette_count;
48✔
3223
            ++i) {
45✔
3224
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
60✔
3225
                palette_copy[i * 3 + 0],
45✔
3226
                palette_copy[i * 3 + 1],
45✔
3227
                palette_copy[i * 3 + 2]);
45✔
3228
    }
15✔
3229
    sixel_allocator_free(dither->allocator, palette_copy);
3✔
3230
}
1✔
3231

3232

3233
static SIXELSTATUS
3234
sixel_encoder_output_without_macro(
436✔
3235
    sixel_frame_t       /* in */ *frame,
3236
    sixel_dither_t      /* in */ *dither,
3237
    sixel_output_t      /* in */ *output,
3238
    sixel_encoder_t     /* in */ *encoder)
3239
{
3240
    SIXELSTATUS status = SIXEL_OK;
436✔
3241
    static unsigned char *p;
3242
    int depth;
3243
    enum { message_buffer_size = 2048 };
3244
    char message[message_buffer_size];
3245
    int nwrite;
3246
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3247
    int dulation;
3248
    int delay;
3249
    struct timespec tv;
3250
#endif
3251
    unsigned char *pixbuf;
3252
    int width;
3253
    int height;
3254
    int pixelformat = 0;
436✔
3255
    size_t size;
3256
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
436✔
3257
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3258
    sixel_clock_t last_clock;
3259
#endif
3260

3261
    if (encoder == NULL) {
436!
3262
        sixel_helper_set_additional_message(
×
3263
            "sixel_encoder_output_without_macro: encoder object is null.");
3264
        status = SIXEL_BAD_ARGUMENT;
×
3265
        goto end;
×
3266
    }
3267

3268
    if (encoder->assessment_observer != NULL) {
436✔
3269
        sixel_assessment_stage_transition(
3✔
3270
            encoder->assessment_observer,
3✔
3271
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3272
    }
1✔
3273

3274
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
436✔
3275
        if (encoder->force_palette) {
340!
3276
            /* keep every slot when the user forced the palette size */
3277
            sixel_dither_set_optimize_palette(dither, 0);
×
3278
        } else {
3279
            sixel_dither_set_optimize_palette(dither, 1);
340✔
3280
        }
3281
    }
114✔
3282

3283
    pixelformat = sixel_frame_get_pixelformat(frame);
436✔
3284
    frame_colorspace = sixel_frame_get_colorspace(frame);
436✔
3285
    output->pixelformat = pixelformat;
436✔
3286
    output->source_colorspace = frame_colorspace;
436✔
3287
    output->colorspace = encoder->output_colorspace;
436✔
3288
    sixel_dither_set_pixelformat(dither, pixelformat);
436✔
3289
    depth = sixel_helper_compute_depth(pixelformat);
436✔
3290
    if (depth < 0) {
436!
3291
        status = SIXEL_LOGIC_ERROR;
×
3292
        nwrite = sixel_compat_snprintf(
×
3293
            message,
3294
            sizeof(message),
3295
            "sixel_encoder_output_without_macro: "
3296
            "sixel_helper_compute_depth(%08x) failed.",
3297
            pixelformat);
3298
        if (nwrite > 0) {
×
3299
            sixel_helper_set_additional_message(message);
×
3300
        }
3301
        goto end;
×
3302
    }
3303

3304
    width = sixel_frame_get_width(frame);
436✔
3305
    height = sixel_frame_get_height(frame);
436✔
3306
    size = (size_t)(width * height * depth);
436✔
3307

3308
    sixel_encoder_log_stage(encoder,
582✔
3309
                            frame,
146✔
3310
                            "encode",
3311
                            "worker",
3312
                            "start",
3313
                            "size=%dx%d fmt=%08x dst_cs=%d",
3314
                            width,
146✔
3315
                            height,
146✔
3316
                            pixelformat,
146✔
3317
                            output->colorspace);
146✔
3318

3319
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
436✔
3320
    if (p == NULL) {
436!
3321
        sixel_helper_set_additional_message(
×
3322
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
3323
        status = SIXEL_BAD_ALLOCATION;
×
3324
        goto end;
×
3325
    }
3326
#if defined(HAVE_CLOCK)
3327
    if (output->last_clock == 0) {
436!
3328
        output->last_clock = clock();
436✔
3329
    }
146✔
3330
#elif defined(HAVE_CLOCK_WIN)
3331
    if (output->last_clock == 0) {
3332
        output->last_clock = clock_win();
3333
    }
3334
#endif
3335
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3336
    delay = sixel_frame_get_delay(frame);
436✔
3337
    if (delay > 0 && !encoder->fignore_delay) {
436✔
3338
# if defined(HAVE_CLOCK)
3339
        last_clock = clock();
3✔
3340
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3341
#  if defined(__APPLE__)
3342
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
3343
                          / 100000);
1✔
3344
#  else
3345
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
3346
                          / CLOCKS_PER_SEC);
3347
#  endif
3348
        output->last_clock = last_clock;
3✔
3349
# elif defined(HAVE_CLOCK_WIN)
3350
        last_clock = clock_win();
3351
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3352
                          / CLOCKS_PER_SEC);
3353
        output->last_clock = last_clock;
3354
# else
3355
        dulation = 0;
3356
# endif
3357
        if (dulation < 1000 * 10 * delay) {
3!
3358
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3359
            tv.tv_sec = 0;
3✔
3360
            tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
3✔
3361
#  if defined(HAVE_NANOSLEEP)
3362
            nanosleep(&tv, NULL);
3✔
3363
#  else
3364
            nanosleep_win(&tv, NULL);
3365
#  endif
3366
# endif
3367
        }
1✔
3368
    }
1✔
3369
#endif
3370

3371
    pixbuf = sixel_frame_get_pixels(frame);
436✔
3372
    memcpy(p, pixbuf, (size_t)(width * height * depth));
436✔
3373

3374
    if (encoder->cancel_flag && *encoder->cancel_flag) {
436!
3375
        goto end;
×
3376
    }
3377

3378
    if (encoder->capture_quantized) {
436✔
3379
        status = sixel_encoder_capture_quantized(encoder,
4✔
3380
                                                 dither,
1✔
3381
                                                 p,
1✔
3382
                                                 size,
1✔
3383
                                                 width,
1✔
3384
                                                 height,
1✔
3385
                                                 pixelformat,
1✔
3386
                                                 frame_colorspace,
1✔
3387
                                                 output->colorspace);
1✔
3388
        if (SIXEL_FAILED(status)) {
3!
3389
            goto end;
×
3390
        }
3391
    }
1✔
3392

3393
    if (encoder->assessment_observer != NULL) {
436✔
3394
        sixel_assessment_stage_transition(
3✔
3395
            encoder->assessment_observer,
3✔
3396
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3397
    }
1✔
3398
    status = sixel_encode(p, width, height, depth, dither, output);
436✔
3399
    if (encoder->assessment_observer != NULL) {
436✔
3400
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
3401
    }
1✔
3402
    if (status != SIXEL_OK) {
436!
3403
        goto end;
×
3404
    }
3405

3406
end:
290✔
3407
    if (SIXEL_SUCCEEDED(status)) {
436!
3408
        sixel_encoder_log_stage(encoder,
582✔
3409
                                frame,
146✔
3410
                                "encode",
3411
                                "worker",
3412
                                "finish",
3413
                                "size=%dx%d",
3414
                                width,
146✔
3415
                                height);
146✔
3416
    }
146✔
3417
    output->pixelformat = pixelformat;
436✔
3418
    output->source_colorspace = frame_colorspace;
436✔
3419
    sixel_allocator_free(encoder->allocator, p);
436✔
3420

3421
    return status;
436✔
3422
}
3423

3424

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

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

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

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

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

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

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

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

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

3676
end:
20✔
3677
    output->pixelformat = pixelformat;
84✔
3678
    output->source_colorspace = frame_colorspace;
84✔
3679
    sixel_allocator_free(encoder->allocator, converted);
84✔
3680

3681
    return status;
84✔
3682
}
3683

3684

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

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

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

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

3761
    if (encoder->tile_outfd >= 0) {
×
3762
        target_fd = encoder->tile_outfd;
×
3763
    } else {
3764
        target_fd = encoder->outfd;
×
3765
    }
3766

3767
    chunk_size = (int)(buf_p - buf);
×
3768
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3769
                                          buf,
3770
                                          chunk_size,
3771
                                          target_fd);
3772
    if (nwrite != chunk_size) {
×
3773
        sixel_helper_set_additional_message(
×
3774
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3775
        status = SIXEL_RUNTIME_ERROR;
×
3776
        goto end;
×
3777
    }
3778

3779
    sixel_allocator_free(encoder->allocator, buf);
×
3780

3781
    status = SIXEL_OK;
×
3782

3783
end:
3784
    return status;
×
3785
}
3786

3787

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

3813
    charset_no = encoder->drcs_charset_no;
×
3814
    if (charset_no == 0u) {
×
3815
        charset_no = 1u;
×
3816
    }
3817
    if (encoder->drcs_mmv == 0) {
×
3818
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3819
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3820
    } else if (encoder->drcs_mmv == 1) {
×
3821
        is_96cs = 0;
×
3822
        charset = (int)(charset_no + 0x3fu);
×
3823
    } else {
3824
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3825
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3826
    }
3827
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3828
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3829
             / encoder->cell_width;
×
3830
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3831
             / encoder->cell_height;
×
3832

3833
    /* cols x rows x 4(out of BMP) + rows(LFs) */
3834
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
3835
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3836
    if (buf == NULL) {
×
3837
        sixel_helper_set_additional_message(
×
3838
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
3839
        status = SIXEL_BAD_ALLOCATION;
×
3840
        goto end;
×
3841
    }
3842

3843
    for(row = 0; row < num_rows; row++) {
×
3844
        for(col = 0; col < num_cols; col++) {
×
3845
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
3846
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
3847
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
3848
            *(buf_p++) = (code & 0x3f) | 0x80;
×
3849
            code++;
×
3850
            if ((code & 0x7f) == 0x0) {
×
3851
                if (charset == 0x7e) {
×
3852
                    is_96cs = 1 - is_96cs;
×
3853
                    charset = '0';
×
3854
                } else {
3855
                    charset++;
×
3856
                }
3857
                code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3858
            }
3859
        }
3860
        *(buf_p++) = '\n';
×
3861
    }
3862

3863
    if (charset == 0x7e) {
×
3864
        is_96cs = 1 - is_96cs;
×
3865
    } else {
3866
        charset = '0';
×
3867
        charset++;
×
3868
    }
3869
    if (encoder->tile_outfd >= 0) {
×
3870
        target_fd = encoder->tile_outfd;
×
3871
    } else {
3872
        target_fd = encoder->outfd;
×
3873
    }
3874

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

3887
    sixel_allocator_free(encoder->allocator, buf);
×
3888

3889
    status = SIXEL_OK;
×
3890

3891
end:
3892
    return status;
×
3893
}
3894

3895
static SIXELSTATUS
3896
sixel_encoder_encode_frame(
526✔
3897
    sixel_encoder_t *encoder,
3898
    sixel_frame_t   *frame,
3899
    sixel_output_t  *output)
3900
{
3901
    SIXELSTATUS status = SIXEL_FALSE;
526✔
3902
    sixel_dither_t *dither = NULL;
526✔
3903
    int height;
3904
    int is_animation = 0;
526✔
3905
    int nwrite;
3906
    int drcs_is_96cs_param;
3907
    int drcs_designate_char;
3908
    char buf[256];
3909
    sixel_write_function fn_write;
3910
    sixel_write_function write_callback;
3911
    sixel_write_function scroll_callback;
3912
    void *write_priv;
3913
    void *scroll_priv;
3914
    sixel_encoder_output_probe_t probe;
3915
    sixel_encoder_output_probe_t scroll_probe;
3916
    sixel_assessment_t *assessment;
3917
    int width_before;
3918
    int height_before;
3919
    int width_after;
3920
    int height_after;
3921

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

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

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

4070
    if (assessment != NULL) {
520✔
4071
        sixel_assessment_stage_transition(
3✔
4072
            assessment,
1✔
4073
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
4074
    }
1✔
4075
 
4076
    sixel_encoder_log_stage(encoder,
694✔
4077
                            frame,
174✔
4078
                            "colorspace",
4079
                            "worker",
4080
                            "start",
4081
                            "current=%d target=%d",
4082
                            sixel_frame_get_colorspace(frame),
174✔
4083
                            encoder->working_colorspace);
174✔
4084

4085
    if (encoder->working_colorspace != SIXEL_COLORSPACE_GAMMA) {
520!
4086
        status = sixel_frame_set_pixelformat(
×
4087
            frame,
4088
            sixel_encoder_pixelformat_for_colorspace(
4089
                encoder->working_colorspace));
4090
        if (SIXEL_FAILED(status)) {
×
4091
            goto end;
×
4092
        }
4093
    }
4094

4095
    if (assessment != NULL) {
520✔
4096
        sixel_assessment_stage_transition(
3✔
4097
            assessment,
1✔
4098
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
4099
    }
1✔
4100

4101
    sixel_encoder_log_stage(encoder,
694✔
4102
                            frame,
174✔
4103
                            "colorspace",
4104
                            "worker",
4105
                            "finish",
4106
                            "result=%d",
4107
                            sixel_frame_get_colorspace(frame));
174✔
4108

4109
    /* prepare dither context */
4110
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
520✔
4111
    if (status != SIXEL_OK) {
520!
4112
        dither = NULL;
×
4113
        goto end;
×
4114
    }
4115

4116
    if (encoder->dither_cache != NULL) {
520!
4117
        encoder->dither_cache = dither;
×
4118
        sixel_dither_ref(dither);
×
4119
    }
4120

4121
    if (encoder->fdrcs) {
520!
4122
        status = sixel_encoder_ensure_cell_size(encoder);
×
4123
        if (SIXEL_FAILED(status)) {
×
4124
            goto end;
×
4125
        }
4126
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
4127
            sixel_helper_set_additional_message(
×
4128
                "drcs option cannot be used together with macro output.");
4129
            status = SIXEL_BAD_ARGUMENT;
×
4130
            goto end;
×
4131
        }
4132
    }
4133

4134
    /* evaluate -v option: print palette */
4135
    if (encoder->verbose) {
520✔
4136
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
4137
            sixel_debug_print_palette(dither);
3✔
4138
        }
1✔
4139
    }
3✔
4140

4141
    /* evaluate -d option: set method for diffusion */
4142
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
520✔
4143
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
520✔
4144
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
520✔
4145

4146
    /* evaluate -C option: set complexion score */
4147
    if (encoder->complexion > 1) {
520✔
4148
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
4149
    }
2✔
4150

4151
    if (output) {
520!
4152
        sixel_output_ref(output);
×
4153
        if (encoder->assessment_observer != NULL) {
×
4154
            probe.encoder = encoder;
×
4155
            probe.base_write = fn_write;
×
4156
            probe.base_priv = &encoder->outfd;
×
4157
            write_callback = sixel_write_with_probe;
×
4158
            write_priv = &probe;
×
4159
        }
4160
    } else {
4161
        /* create output context */
4162
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
520✔
4163
            /* -u or -n option */
4164
            fn_write = sixel_hex_write_callback;
84✔
4165
        } else {
28✔
4166
            fn_write = sixel_write_callback;
436✔
4167
        }
4168
        write_callback = fn_write;
520✔
4169
        write_priv = &encoder->outfd;
520✔
4170
        if (encoder->assessment_observer != NULL) {
520✔
4171
            probe.encoder = encoder;
3✔
4172
            probe.base_write = fn_write;
3✔
4173
            probe.base_priv = &encoder->outfd;
3✔
4174
            write_callback = sixel_write_with_probe;
3✔
4175
            write_priv = &probe;
3✔
4176
        }
1✔
4177
        status = sixel_output_new(&output,
520✔
4178
                                  write_callback,
174✔
4179
                                  write_priv,
174✔
4180
                                  encoder->allocator);
174✔
4181
        if (SIXEL_FAILED(status)) {
520!
4182
            goto end;
×
4183
        }
4184
    }
4185

4186
    if (encoder->fdrcs) {
520!
4187
        sixel_output_set_skip_dcs_envelope(output, 1);
×
4188
        sixel_output_set_skip_header(output, 1);
×
4189
    }
4190

4191
    sixel_output_set_8bit_availability(output, encoder->f8bit);
520✔
4192
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
520✔
4193
    sixel_output_set_palette_type(output, encoder->palette_type);
520✔
4194
    sixel_output_set_penetrate_multiplexer(
520✔
4195
        output, encoder->penetrate_multiplexer);
174✔
4196
    sixel_output_set_encode_policy(output, encoder->encode_policy);
520✔
4197
    sixel_output_set_ormode(output, encoder->ormode);
520✔
4198

4199
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
520✔
4200
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
108✔
4201
            is_animation = 1;
99✔
4202
        }
33✔
4203
        height = sixel_frame_get_height(frame);
108✔
4204
        if (encoder->assessment_observer != NULL) {
108!
4205
            scroll_probe.encoder = encoder;
×
4206
            scroll_probe.base_write = sixel_write_callback;
×
4207
            scroll_probe.base_priv = &encoder->outfd;
×
4208
            scroll_callback = sixel_write_with_probe;
×
4209
            scroll_priv = &scroll_probe;
×
4210
        } else {
4211
            scroll_callback = sixel_write_callback;
108✔
4212
            scroll_priv = &encoder->outfd;
108✔
4213
        }
4214
        (void) sixel_tty_scroll(scroll_callback,
144✔
4215
                                scroll_priv,
36✔
4216
                                encoder->outfd,
36✔
4217
                                height,
36✔
4218
                                is_animation);
36✔
4219
    }
36✔
4220

4221
    if (encoder->cancel_flag && *encoder->cancel_flag) {
520!
4222
        status = SIXEL_INTERRUPTED;
×
4223
        goto end;
×
4224
    }
4225

4226
    if (encoder->fdrcs) {  /* -@ option */
520!
4227
        if (encoder->drcs_mmv == 0) {
×
4228
            drcs_is_96cs_param =
×
4229
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
4230
            drcs_designate_char =
×
4231
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
4232
        } else if (encoder->drcs_mmv == 1) {
×
4233
            drcs_is_96cs_param = 0;
×
4234
            drcs_designate_char =
×
4235
                (int)(encoder->drcs_charset_no + 0x3fu);
×
4236
        } else {
4237
            drcs_is_96cs_param =
×
4238
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
4239
            drcs_designate_char =
×
4240
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
4241
        }
4242
        nwrite = sprintf(buf,
×
4243
                         "%s%s1;0;0;%d;1;3;%d;%d{ %c",
4244
                         (encoder->drcs_mmv > 0) ? (
×
4245
                             encoder->f8bit ? "\233?8800h": "\033[?8800h"
×
4246
                         ): "",
4247
                         encoder->f8bit ? "\220": "\033P",
×
4248
                         encoder->cell_width,
4249
                         encoder->cell_height,
4250
                         drcs_is_96cs_param,
4251
                         drcs_designate_char);
4252
        if (nwrite < 0) {
×
4253
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4254
            sixel_helper_set_additional_message(
×
4255
                "sixel_encoder_encode_frame: command format failed.");
4256
            goto end;
×
4257
        }
4258
        nwrite = write_callback(buf, nwrite, write_priv);
×
4259
        if (nwrite < 0) {
×
4260
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4261
            sixel_helper_set_additional_message(
×
4262
                "sixel_encoder_encode_frame: write() failed.");
4263
            goto end;
×
4264
        }
4265
    }
4266

4267
    /* output sixel: junction of multi-frame processing strategy */
4268
    if (encoder->fuse_macro) {  /* -u option */
520✔
4269
        /* use macro */
4270
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
81✔
4271
    } else if (encoder->macro_number >= 0) { /* -n option */
466✔
4272
        /* use macro */
4273
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
4274
    } else {
1✔
4275
        /* do not use macro */
4276
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
436✔
4277
    }
4278
    if (SIXEL_FAILED(status)) {
520!
4279
        goto end;
×
4280
    }
4281

4282
    if (encoder->cancel_flag && *encoder->cancel_flag) {
520!
4283
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
4284
        if (nwrite < 0) {
×
4285
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4286
            sixel_helper_set_additional_message(
×
4287
                "sixel_encoder_encode_frame: write_callback() failed.");
4288
            goto end;
×
4289
        }
4290
        status = SIXEL_INTERRUPTED;
×
4291
    }
4292
    if (SIXEL_FAILED(status)) {
520!
4293
        goto end;
×
4294
    }
4295

4296
    if (encoder->fdrcs) {  /* -@ option */
520!
4297
        if (encoder->f8bit) {
×
4298
            nwrite = write_callback("\234", 1, write_priv);
×
4299
        } else {
4300
            nwrite = write_callback("\033\\", 2, write_priv);
×
4301
        }
4302
        if (nwrite < 0) {
×
4303
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4304
            sixel_helper_set_additional_message(
×
4305
                "sixel_encoder_encode_frame: write_callback() failed.");
4306
            goto end;
×
4307
        }
4308

4309
        if (encoder->tile_outfd >= 0) {
×
4310
            if (encoder->drcs_mmv == 0) {
×
4311
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
4312
                if (SIXEL_FAILED(status)) {
×
4313
                    goto end;
×
4314
                }
4315
            } else {
4316
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
4317
                if (SIXEL_FAILED(status)) {
×
4318
                    goto end;
×
4319
                }
4320
            }
4321
        }
4322
    }
4323

4324

4325
end:
346✔
4326
    if (output) {
526✔
4327
        sixel_output_unref(output);
520✔
4328
    }
174✔
4329
    if (dither) {
526✔
4330
        sixel_dither_unref(dither);
520✔
4331
    }
174✔
4332

4333
    return status;
526✔
4334
}
4335

4336

4337
/* create encoder object */
4338
SIXELAPI SIXELSTATUS
4339
sixel_encoder_new(
544✔
4340
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
4341
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
4342
                                                  default allocator */
4343
{
4344
    SIXELSTATUS status = SIXEL_FALSE;
544✔
4345
    char const *env_default_bgcolor = NULL;
544✔
4346
    char const *env_default_ncolors = NULL;
544✔
4347
    int ncolors;
4348
#if HAVE__DUPENV_S
4349
    errno_t e;
4350
    size_t len;
4351
#endif  /* HAVE__DUPENV_S */
4352

4353
    if (allocator == NULL) {
544!
4354
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
544✔
4355
        if (SIXEL_FAILED(status)) {
544!
4356
            goto end;
×
4357
        }
4358
    } else {
182✔
4359
        sixel_allocator_ref(allocator);
×
4360
    }
4361

4362
    *ppencoder
182✔
4363
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
726✔
4364
                                                    sizeof(sixel_encoder_t));
4365
    if (*ppencoder == NULL) {
544!
4366
        sixel_helper_set_additional_message(
×
4367
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
4368
        status = SIXEL_BAD_ALLOCATION;
×
4369
        sixel_allocator_unref(allocator);
×
4370
        goto end;
×
4371
    }
4372

4373
    (*ppencoder)->ref                   = 1;
544✔
4374
    (*ppencoder)->reqcolors             = (-1);
544✔
4375
    (*ppencoder)->force_palette         = 0;
544✔
4376
    (*ppencoder)->mapfile               = NULL;
544✔
4377
    (*ppencoder)->palette_output        = NULL;
544✔
4378
    (*ppencoder)->loader_order          = NULL;
544✔
4379
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
544✔
4380
    (*ppencoder)->builtin_palette       = 0;
544✔
4381
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
544✔
4382
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
544✔
4383
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
544✔
4384
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
544✔
4385
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
544✔
4386
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
544✔
4387
    (*ppencoder)->quantize_model        = SIXEL_QUANTIZE_MODEL_AUTO;
544✔
4388
    (*ppencoder)->final_merge_mode      = SIXEL_FINAL_MERGE_AUTO;
544✔
4389
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
544✔
4390
    (*ppencoder)->sixel_reversible      = 0;
544✔
4391
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
544✔
4392
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
544✔
4393
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
544✔
4394
    (*ppencoder)->f8bit                 = 0;
544✔
4395
    (*ppencoder)->has_gri_arg_limit     = 0;
544✔
4396
    (*ppencoder)->finvert               = 0;
544✔
4397
    (*ppencoder)->fuse_macro            = 0;
544✔
4398
    (*ppencoder)->fdrcs                 = 0;
544✔
4399
    (*ppencoder)->fignore_delay         = 0;
544✔
4400
    (*ppencoder)->complexion            = 1;
544✔
4401
    (*ppencoder)->fstatic               = 0;
544✔
4402
    (*ppencoder)->cell_width            = 0;
544✔
4403
    (*ppencoder)->cell_height           = 0;
544✔
4404
    (*ppencoder)->pixelwidth            = (-1);
544✔
4405
    (*ppencoder)->pixelheight           = (-1);
544✔
4406
    (*ppencoder)->percentwidth          = (-1);
544✔
4407
    (*ppencoder)->percentheight         = (-1);
544✔
4408
    (*ppencoder)->clipx                 = 0;
544✔
4409
    (*ppencoder)->clipy                 = 0;
544✔
4410
    (*ppencoder)->clipwidth             = 0;
544✔
4411
    (*ppencoder)->clipheight            = 0;
544✔
4412
    (*ppencoder)->clipfirst             = 0;
544✔
4413
    (*ppencoder)->macro_number          = (-1);
544✔
4414
    (*ppencoder)->verbose               = 0;
544✔
4415
    (*ppencoder)->penetrate_multiplexer = 0;
544✔
4416
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
544✔
4417
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
544✔
4418
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
544✔
4419
    (*ppencoder)->ormode                = 0;
544✔
4420
    (*ppencoder)->pipe_mode             = 0;
544✔
4421
    (*ppencoder)->bgcolor               = NULL;
544✔
4422
    (*ppencoder)->outfd                 = STDOUT_FILENO;
544✔
4423
    (*ppencoder)->tile_outfd            = (-1);
544✔
4424
    (*ppencoder)->finsecure             = 0;
544✔
4425
    (*ppencoder)->cancel_flag           = NULL;
544✔
4426
    (*ppencoder)->dither_cache          = NULL;
544✔
4427
    (*ppencoder)->drcs_charset_no       = 1u;
544✔
4428
    (*ppencoder)->drcs_mmv              = 2;
544✔
4429
    (*ppencoder)->capture_quantized     = 0;
544✔
4430
    (*ppencoder)->capture_source        = 0;
544✔
4431
    (*ppencoder)->capture_pixels        = NULL;
544✔
4432
    (*ppencoder)->capture_pixels_size   = 0;
544✔
4433
    (*ppencoder)->capture_palette       = NULL;
544✔
4434
    (*ppencoder)->capture_palette_size  = 0;
544✔
4435
    (*ppencoder)->capture_pixel_bytes   = 0;
544✔
4436
    (*ppencoder)->capture_width         = 0;
544✔
4437
    (*ppencoder)->capture_height        = 0;
544✔
4438
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
544✔
4439
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
544✔
4440
    (*ppencoder)->capture_ncolors       = 0;
544✔
4441
    (*ppencoder)->capture_valid         = 0;
544✔
4442
    (*ppencoder)->capture_source_frame  = NULL;
544✔
4443
    (*ppencoder)->assessment_observer   = NULL;
544✔
4444
    (*ppencoder)->assessment_json_path  = NULL;
544✔
4445
    (*ppencoder)->assessment_sections   = SIXEL_ASSESSMENT_SECTION_NONE;
544✔
4446
    (*ppencoder)->last_loader_name[0]   = '\0';
544✔
4447
    (*ppencoder)->last_source_path[0]   = '\0';
544✔
4448
    (*ppencoder)->last_input_bytes      = 0u;
544✔
4449
    (*ppencoder)->output_is_png         = 0;
544✔
4450
    (*ppencoder)->output_png_to_stdout  = 0;
544✔
4451
    (*ppencoder)->png_output_path       = NULL;
544✔
4452
    (*ppencoder)->sixel_output_path     = NULL;
544✔
4453
    (*ppencoder)->clipboard_output_active = 0;
544✔
4454
    (*ppencoder)->clipboard_output_format[0] = '\0';
544✔
4455
    (*ppencoder)->clipboard_output_path = NULL;
544✔
4456
    (*ppencoder)->logger                = NULL;
544✔
4457
    (*ppencoder)->parallel_job_id       = -1;
544✔
4458
    (*ppencoder)->allocator             = allocator;
544✔
4459

4460
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
4461
#if HAVE__DUPENV_S
4462
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_BGCOLOR");
4463
    if (e != (0)) {
4464
        sixel_helper_set_additional_message(
4465
            "failed to get environment variable $SIXEL_BGCOLOR.");
4466
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4467
    }
4468
#else
4469
    env_default_bgcolor = sixel_compat_getenv("SIXEL_BGCOLOR");
544✔
4470
#endif  /* HAVE__DUPENV_S */
4471
    if (env_default_bgcolor != NULL) {
544!
4472
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
4473
                                         env_default_bgcolor,
4474
                                         allocator);
4475
        if (SIXEL_FAILED(status)) {
×
4476
            goto error;
×
4477
        }
4478
    }
4479

4480
    /* evaluate environment variable ${SIXEL_COLORS} */
4481
#if HAVE__DUPENV_S
4482
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_COLORS");
4483
    if (e != (0)) {
4484
        sixel_helper_set_additional_message(
4485
            "failed to get environment variable $SIXEL_COLORS.");
4486
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4487
    }
4488
#else
4489
    env_default_ncolors = sixel_compat_getenv("SIXEL_COLORS");
544✔
4490
#endif  /* HAVE__DUPENV_S */
4491
    if (env_default_ncolors) {
544!
4492
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
4493
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
4494
            (*ppencoder)->reqcolors = ncolors;
×
4495
        }
4496
    }
4497

4498
    /* success */
4499
    status = SIXEL_OK;
544✔
4500

4501
    goto end;
544✔
4502

4503
error:
4504
    sixel_allocator_free(allocator, *ppencoder);
×
4505
    sixel_allocator_unref(allocator);
×
4506
    *ppencoder = NULL;
×
4507

4508
end:
362✔
4509
#if HAVE__DUPENV_S
4510
    free(env_default_bgcolor);
4511
    free(env_default_ncolors);
4512
#endif  /* HAVE__DUPENV_S */
4513
    return status;
544✔
4514
}
4515

4516

4517
/* create encoder object (deprecated version) */
4518
SIXELAPI /* deprecated */ sixel_encoder_t *
4519
sixel_encoder_create(void)
×
4520
{
4521
    SIXELSTATUS status = SIXEL_FALSE;
×
4522
    sixel_encoder_t *encoder = NULL;
×
4523

4524
    status = sixel_encoder_new(&encoder, NULL);
×
4525
    if (SIXEL_FAILED(status)) {
×
4526
        return NULL;
×
4527
    }
4528

4529
    return encoder;
×
4530
}
4531

4532

4533
/* destroy encoder object */
4534
static void
4535
sixel_encoder_destroy(sixel_encoder_t *encoder)
544✔
4536
{
4537
    sixel_allocator_t *allocator;
4538

4539
    if (encoder) {
544!
4540
        allocator = encoder->allocator;
544✔
4541
        sixel_allocator_free(allocator, encoder->mapfile);
544✔
4542
        sixel_allocator_free(allocator, encoder->palette_output);
544✔
4543
        sixel_allocator_free(allocator, encoder->loader_order);
544✔
4544
        sixel_allocator_free(allocator, encoder->bgcolor);
544✔
4545
        sixel_dither_unref(encoder->dither_cache);
544✔
4546
        if (encoder->outfd
554!
4547
            && encoder->outfd != STDOUT_FILENO
544!
4548
            && encoder->outfd != STDERR_FILENO) {
202!
4549
            (void)sixel_compat_close(encoder->outfd);
30✔
4550
        }
10✔
4551
        if (encoder->tile_outfd >= 0
544!
4552
            && encoder->tile_outfd != encoder->outfd
182!
4553
            && encoder->tile_outfd != STDOUT_FILENO
×
4554
            && encoder->tile_outfd != STDERR_FILENO) {
×
4555
            (void)sixel_compat_close(encoder->tile_outfd);
×
4556
        }
4557
        if (encoder->capture_source_frame != NULL) {
544✔
4558
            sixel_frame_unref(encoder->capture_source_frame);
3✔
4559
        }
1✔
4560
        if (encoder->clipboard_output_path != NULL) {
544!
4561
            (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
4562
            encoder->clipboard_output_path = NULL;
×
4563
        }
4564
        encoder->clipboard_output_active = 0;
544✔
4565
        encoder->clipboard_output_format[0] = '\0';
544✔
4566
        sixel_allocator_free(allocator, encoder->capture_pixels);
544✔
4567
        sixel_allocator_free(allocator, encoder->capture_palette);
544✔
4568
        sixel_allocator_free(allocator, encoder->png_output_path);
544✔
4569
        sixel_allocator_free(allocator, encoder->sixel_output_path);
544✔
4570
        sixel_allocator_free(allocator, encoder);
544✔
4571
        sixel_allocator_unref(allocator);
544✔
4572
    }
182✔
4573
}
544✔
4574

4575

4576
/* increase reference count of encoder object (thread-unsafe) */
4577
SIXELAPI void
4578
sixel_encoder_ref(sixel_encoder_t *encoder)
1,154✔
4579
{
4580
    /* TODO: be thread safe */
4581
    ++encoder->ref;
1,154✔
4582
}
1,154✔
4583

4584

4585
/* decrease reference count of encoder object (thread-unsafe) */
4586
SIXELAPI void
4587
sixel_encoder_unref(sixel_encoder_t *encoder)
1,698✔
4588
{
4589
    /* TODO: be thread safe */
4590
    if (encoder != NULL && --encoder->ref == 0) {
1,698!
4591
        sixel_encoder_destroy(encoder);
544✔
4592
    }
182✔
4593
}
1,698✔
4594

4595

4596
/* set cancel state flag to encoder object */
4597
SIXELAPI SIXELSTATUS
4598
sixel_encoder_set_cancel_flag(
439✔
4599
    sixel_encoder_t /* in */ *encoder,
4600
    int             /* in */ *cancel_flag
4601
)
4602
{
4603
    SIXELSTATUS status = SIXEL_OK;
439✔
4604

4605
    encoder->cancel_flag = cancel_flag;
439✔
4606

4607
    return status;
439✔
4608
}
4609

4610

4611
/*
4612
 * parse_assessment_sections() translates a comma-separated section list into
4613
 * the bitmask understood by the assessment back-end.  The accepted grammar is
4614
 * intentionally small so that the CLI contract stays predictable:
4615
 *
4616
 *     list := section {"," section}
4617
 *     section := name | name "@" view
4618
 *
4619
 * Each name maps to a bit flag while the optional view toggles the encoded vs
4620
 * quantized quality comparison.  The helper folds case, trims ASCII
4621
 * whitespace, and rejects mixed encoded/quantized requests so that the caller
4622
 * can rely on a single coherent capture strategy.
4623
 */
4624
static int
4625
parse_assessment_sections(char const *spec,
6✔
4626
                          unsigned int *out_sections)
4627
{
4628
    char *copy;
4629
    char *cursor;
4630
    char *token;
4631
    char *context;
4632
    unsigned int sections;
4633
    unsigned int view;
4634
    int result;
4635
    size_t length;
4636
    size_t spec_len;
4637
    char *at;
4638
    char *base;
4639
    char *variant;
4640
    char *p;
4641

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

4765

4766
static int
4767
is_png_target(char const *path)
31✔
4768
{
4769
    size_t len;
4770
    int matched;
4771

4772
    /*
4773
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
4774
     *
4775
     *   argument
4776
     *   |
4777
     *   v
4778
     *   .............. . p n g
4779
     *   ^             ^^^^^^^^^
4780
     *   |             +-- case-insensitive suffix comparison
4781
     *   +-- accepts the "png:" inline prefix used for stdout capture
4782
     */
4783

4784
    len = 0;
31✔
4785
    matched = 0;
31✔
4786

4787
    if (path == NULL) {
31!
4788
        return 0;
×
4789
    }
4790

4791
    if (strncmp(path, "png:", 4) == 0) {
31✔
4792
        return path[4] != '\0';
6✔
4793
    }
4794

4795
    len = strlen(path);
25✔
4796
    if (len >= 4) {
25✔
4797
        matched = (tolower((unsigned char)path[len - 4]) == '.')
23✔
4798
            && (tolower((unsigned char)path[len - 3]) == 'p')
10!
4799
            && (tolower((unsigned char)path[len - 2]) == 'n')
3!
4800
            && (tolower((unsigned char)path[len - 1]) == 'g');
24!
4801
    }
8✔
4802

4803
    return matched;
25✔
4804
}
11✔
4805

4806

4807
static char const *
4808
png_target_payload_view(char const *argument)
9✔
4809
{
4810
    /*
4811
     * Inline PNG targets split into either a prefix/payload pair or rely on
4812
     * a simple file-name suffix:
4813
     *
4814
     *   +--------------+------------+-------------+
4815
     *   | form         | payload    | destination |
4816
     *   +--------------+------------+-------------+
4817
     *   | png:         | -          | stdout      |
4818
     *   | png:         | filename   | filesystem  |
4819
     *   | *.png        | filename   | filesystem  |
4820
     *   +--------------+------------+-------------+
4821
     *
4822
     * The caller only needs the payload column, so we expose it here.  When
4823
     * the user omits the prefix we simply echo the original pointer so the
4824
     * caller can copy the value verbatim.
4825
     */
4826
    if (argument == NULL) {
9!
4827
        return NULL;
×
4828
    }
4829
    if (strncmp(argument, "png:", 4) == 0) {
9✔
4830
        return argument + 4;
6✔
4831
    }
4832

4833
    return argument;
3✔
4834
}
3✔
4835

4836
static void
4837
normalise_windows_drive_path(char *path)
9✔
4838
{
4839
#if defined(_WIN32)
4840
    size_t length;
4841

4842
    /*
4843
     * MSYS-like environments forward POSIX-looking absolute paths to native
4844
     * binaries.  When a user writes "/d/..." MSYS converts the command line to
4845
     * UTF-16 and preserves the literal bytes.  The Windows CRT, however,
4846
     * expects "d:/..." or "d:\...".  The tiny state machine below rewrites the
4847
     * leading token so the runtime resolves the drive correctly:
4848
     *
4849
     *   input     normalised
4850
     *   |         |
4851
     *   v         v
4852
     *   / d / ... d : / ...
4853
     *
4854
     * The body keeps the rest of the string intact so UNC paths ("//server")
4855
     * and relative references pass through untouched.
4856
     */
4857

4858
    length = 0u;
4859

4860
    if (path == NULL) {
4861
        return;
4862
    }
4863

4864
    length = strlen(path);
4865
    if (length >= 3u
4866
            && path[0] == '/'
4867
            && ((path[1] >= 'A' && path[1] <= 'Z')
4868
                || (path[1] >= 'a' && path[1] <= 'z'))
4869
            && path[2] == '/') {
4870
        path[0] = path[1];
4871
        path[1] = ':';
4872
    }
4873
#else
4874
    (void)path;
3✔
4875
#endif
4876
}
9✔
4877

4878

4879
static int
4880
is_dev_null_path(char const *path)
×
4881
{
4882
    if (path == NULL || path[0] == '\0') {
×
4883
        return 0;
×
4884
    }
4885
#if defined(_WIN32)
4886
    if (_stricmp(path, "nul") == 0) {
4887
        return 1;
4888
    }
4889
#endif
4890
    return strcmp(path, "/dev/null") == 0;
×
4891
}
4892

4893

4894
static int
4895
sixel_encoder_threads_token_is_auto(char const *text)
×
4896
{
4897
    if (text == NULL) {
×
4898
        return 0;
×
4899
    }
4900

4901
    if ((text[0] == 'a' || text[0] == 'A') &&
×
4902
        (text[1] == 'u' || text[1] == 'U') &&
×
4903
        (text[2] == 't' || text[2] == 'T') &&
×
4904
        (text[3] == 'o' || text[3] == 'O') &&
×
4905
        text[4] == '\0') {
×
4906
        return 1;
×
4907
    }
4908

4909
    return 0;
×
4910
}
4911

4912
static int
4913
sixel_encoder_parse_threads_argument(char const *text, int *value)
×
4914
{
4915
    long parsed;
4916
    char *endptr;
4917

4918
    parsed = 0L;
×
4919
    endptr = NULL;
×
4920

4921
    if (text == NULL || value == NULL) {
×
4922
        return 0;
×
4923
    }
4924

4925
    if (sixel_encoder_threads_token_is_auto(text) != 0) {
×
4926
        *value = 0;
×
4927
        return 1;
×
4928
    }
4929

4930
    errno = 0;
×
4931
    parsed = strtol(text, &endptr, 10);
×
4932
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
×
4933
        return 0;
×
4934
    }
4935

4936
    if (parsed < 1L || parsed > (long)INT_MAX) {
×
4937
        return 0;
×
4938
    }
4939

4940
    *value = (int)parsed;
×
4941
    return 1;
×
4942
}
4943

4944
/* set an option flag to encoder object */
4945
SIXELAPI SIXELSTATUS
4946
sixel_encoder_setopt(
718✔
4947
    sixel_encoder_t /* in */ *encoder,
4948
    int             /* in */ arg,
4949
    char const      /* in */ *value)
4950
{
4951
    SIXELSTATUS status = SIXEL_FALSE;
718✔
4952
    int number;
4953
    int parsed;
4954
    char unit[32];
4955
    char lowered[16];
4956
    size_t len;
4957
    size_t i;
4958
    long parsed_reqcolors;
4959
    char *endptr;
4960
    int forced_palette;
4961
    char *opt_copy;
4962
    char const *drcs_arg_delim;
4963
    char const *drcs_arg_charset;
4964
    char const *drcs_arg_second_delim;
4965
    char const *drcs_arg_path;
4966
    size_t drcs_arg_path_length;
4967
    size_t drcs_segment_length;
4968
    char drcs_segment[32];
4969
    int drcs_mmv_value;
4970
    long drcs_charset_value;
4971
    unsigned int drcs_charset_limit;
4972
    sixel_option_choice_result_t match_result;
4973
    int match_value;
4974
    char match_detail[128];
4975
    char match_message[256];
4976
    int png_argument_has_prefix = 0;
718✔
4977
    char const *png_path_view = NULL;
718✔
4978
    size_t png_path_length;
4979
    char cell_message[256];
4980
    char const *cell_detail;
4981
    unsigned int path_flags;
4982
    char const *mapfile_view;
4983
    int path_check;
4984

4985
    sixel_encoder_ref(encoder);
718✔
4986
    opt_copy = NULL;
718✔
4987
    path_flags = 0u;
718✔
4988
    mapfile_view = NULL;
718✔
4989
    path_check = 0;
718✔
4990

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

5089
                clipboard_spec.is_clipboard = 0;
22✔
5090
                clipboard_spec.format[0] = '\0';
22✔
5091
                clip_status = SIXEL_OK;
22✔
5092
                spool_path = NULL;
22✔
5093
                spool_fd = (-1);
22✔
5094

5095
                if (sixel_clipboard_parse_spec(value, &clipboard_spec)
22!
5096
                        && clipboard_spec.is_clipboard) {
8!
5097
                    clip_status = clipboard_create_spool(
1✔
5098
                        encoder->allocator,
1✔
5099
                        "clipboard-out",
5100
                        &spool_path,
5101
                        &spool_fd);
5102
                    if (SIXEL_FAILED(clip_status)) {
1!
5103
                        status = clip_status;
×
5104
                        goto end;
×
5105
                    }
5106
                    clipboard_select_format(
1✔
5107
                        encoder->clipboard_output_format,
1✔
5108
                        sizeof(encoder->clipboard_output_format),
5109
                        clipboard_spec.format,
1✔
5110
                        "sixel");
5111
                    if (encoder->outfd
1!
5112
                            && encoder->outfd != STDOUT_FILENO
1!
5113
                            && encoder->outfd != STDERR_FILENO) {
1!
5114
                        (void)sixel_compat_close(encoder->outfd);
×
5115
                    }
5116
                    encoder->outfd = spool_fd;
1✔
5117
                    spool_fd = (-1);
1✔
5118
                    encoder->sixel_output_path = spool_path;
1✔
5119
                    encoder->clipboard_output_path = spool_path;
1✔
5120
                    spool_path = NULL;
1✔
5121
                    encoder->clipboard_output_active = 1;
1✔
5122
                    break;
1✔
5123
                }
5124

5125
                if (spool_fd >= 0) {
21!
5126
                    (void)sixel_compat_close(spool_fd);
×
5127
                }
5128
                if (spool_path != NULL) {
21!
5129
                    sixel_allocator_free(encoder->allocator, spool_path);
×
5130
                }
5131
            }
5132
            if (strcmp(value, "-") != 0) {
21!
5133
                encoder->sixel_output_path = (char *)sixel_allocator_malloc(
35✔
5134
                    encoder->allocator, strlen(value) + 1);
21✔
5135
                if (encoder->sixel_output_path == NULL) {
21!
5136
                    sixel_helper_set_additional_message(
×
5137
                        "sixel_encoder_setopt: malloc() failed for output path.");
5138
                    status = SIXEL_BAD_ALLOCATION;
×
5139
                    goto end;
×
5140
                }
5141
                (void)sixel_compat_strcpy(encoder->sixel_output_path,
28✔
5142
                                          strlen(value) + 1,
21✔
5143
                                          value);
7✔
5144
            }
7✔
5145
        }
5146

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

6107
            if (len >= sizeof(lowered)) {
×
6108
                sixel_helper_set_additional_message(
×
6109
                    "specified working colorspace name is too long.");
6110
                status = SIXEL_BAD_ARGUMENT;
×
6111
                goto end;
×
6112
            }
6113
            for (i = 0; i < len; ++i) {
×
6114
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6115
            }
6116
            lowered[len] = '\0';
×
6117

6118
            match_result = sixel_option_match_choice(
×
6119
                lowered,
6120
                g_option_choices_working_colorspace,
6121
                sizeof(g_option_choices_working_colorspace) /
6122
                sizeof(g_option_choices_working_colorspace[0]),
6123
                &match_value,
6124
                match_detail,
6125
                sizeof(match_detail));
6126
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6127
                encoder->working_colorspace = match_value;
×
6128
            } else {
6129
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6130
                    sixel_option_report_ambiguous_prefix(value,
×
6131
                        match_detail,
6132
                        match_message,
6133
                        sizeof(match_message));
6134
                } else {
6135
                    sixel_option_report_invalid_choice(
×
6136
                        "unsupported working colorspace specified.",
6137
                        match_detail,
6138
                        match_message,
6139
                        sizeof(match_message));
6140
                }
6141
                status = SIXEL_BAD_ARGUMENT;
×
6142
                goto end;
×
6143
            }
6144
        }
6145
        break;
×
6146
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
6147
        if (value == NULL) {
×
6148
            sixel_helper_set_additional_message(
×
6149
                "output-colorspace requires an argument.");
6150
            status = SIXEL_BAD_ARGUMENT;
×
6151
            goto end;
×
6152
        } else {
6153
            len = strlen(value);
×
6154

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

6166
            match_result = sixel_option_match_choice(
×
6167
                lowered,
6168
                g_option_choices_output_colorspace,
6169
                sizeof(g_option_choices_output_colorspace) /
6170
                sizeof(g_option_choices_output_colorspace[0]),
6171
                &match_value,
6172
                match_detail,
6173
                sizeof(match_detail));
6174
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6175
                encoder->output_colorspace = match_value;
×
6176
            } else {
6177
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6178
                    sixel_option_report_ambiguous_prefix(value,
×
6179
                        match_detail,
6180
                        match_message,
6181
                        sizeof(match_message));
6182
                } else {
6183
                    sixel_option_report_invalid_choice(
×
6184
                        "unsupported output colorspace specified.",
6185
                        match_detail,
6186
                        match_message,
6187
                        sizeof(match_message));
6188
                }
6189
                status = SIXEL_BAD_ARGUMENT;
×
6190
                goto end;
×
6191
            }
6192
        }
6193
        break;
×
6194
    case SIXEL_OPTFLAG_ORMODE:  /* O */
6195
        encoder->ormode = 1;
×
6196
        break;
×
6197
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
6198
        encoder->complexion = atoi(value);
9✔
6199
        if (encoder->complexion < 1) {
9✔
6200
            sixel_helper_set_additional_message(
3✔
6201
                "complexion parameter must be 1 or more.");
6202
            status = SIXEL_BAD_ARGUMENT;
3✔
6203
            goto end;
3✔
6204
        }
6205
        break;
6✔
6206
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
6207
        encoder->pipe_mode = 1;
×
6208
        break;
×
6209
    case '?':  /* unknown option */
×
6210
    default:
6211
        /* exit if unknown options are specified */
6212
        sixel_helper_set_additional_message(
×
6213
            "unknown option is specified.");
6214
        status = SIXEL_BAD_ARGUMENT;
×
6215
        goto end;
×
6216
    }
6217

6218
    /* detects arguments conflictions */
6219
    if (encoder->reqcolors != (-1)) {
640✔
6220
        switch (encoder->color_option) {
99!
6221
        case SIXEL_COLOR_OPTION_MAPFILE:
6222
            sixel_helper_set_additional_message(
×
6223
                "option -p, --colors conflicts with -m, --mapfile.");
6224
            status = SIXEL_BAD_ARGUMENT;
×
6225
            goto end;
×
6226
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
6227
            sixel_helper_set_additional_message(
3✔
6228
                "option -e, --monochrome conflicts with -p, --colors.");
6229
            status = SIXEL_BAD_ARGUMENT;
3✔
6230
            goto end;
3✔
6231
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
6232
            sixel_helper_set_additional_message(
3✔
6233
                "option -p, --colors conflicts with -I, --high-color.");
6234
            status = SIXEL_BAD_ARGUMENT;
3✔
6235
            goto end;
3✔
6236
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
6237
            sixel_helper_set_additional_message(
3✔
6238
                "option -p, --colors conflicts with -b, --builtin-palette.");
6239
            status = SIXEL_BAD_ARGUMENT;
3✔
6240
            goto end;
3✔
6241
        default:
60✔
6242
            break;
90✔
6243
        }
6244
    }
30✔
6245

6246
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
6247
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
631✔
6248
        sixel_helper_set_additional_message(
3✔
6249
            "option -8 --8bit-mode conflicts"
6250
            " with -P, --penetrate.");
6251
        status = SIXEL_BAD_ARGUMENT;
3✔
6252
        goto end;
3✔
6253
    }
6254

6255
    status = SIXEL_OK;
628✔
6256

6257
end:
478✔
6258
    if (opt_copy != NULL) {
718!
6259
        sixel_allocator_free(encoder->allocator, opt_copy);
×
6260
    }
6261
    sixel_encoder_unref(encoder);
718✔
6262

6263
    return status;
718✔
6264
}
6265

6266

6267
/* called when image loader component load a image frame */
6268
static SIXELSTATUS
6269
load_image_callback(sixel_frame_t *frame, void *data)
526✔
6270
{
6271
    sixel_encoder_t *encoder;
6272

6273
    encoder = (sixel_encoder_t *)data;
526✔
6274
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
526!
6275
        sixel_frame_ref(frame);
3✔
6276
        encoder->capture_source_frame = frame;
3✔
6277
    }
1✔
6278

6279
    return sixel_encoder_encode_frame(encoder, frame, NULL);
526✔
6280
}
6281

6282
/*
6283
 * Build a tee for encoded-assessment output:
6284
 *
6285
 *     +-------------+     +-------------------+     +------------+
6286
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
6287
 *     +-------------+     +-------------------+     +------------+
6288
 *
6289
 * The tee sink can be stdout or a user-provided file such as /dev/null.
6290
 */
6291
static SIXELSTATUS
6292
copy_file_to_stream(char const *path,
×
6293
                    FILE *stream,
6294
                    sixel_assessment_t *assessment)
6295
{
6296
    FILE *source;
6297
    unsigned char buffer[4096];
6298
    size_t nread;
6299
    size_t nwritten;
6300
    double started_at;
6301
    double finished_at;
6302
    double duration;
6303

6304
    source = NULL;
×
6305
    nread = 0;
×
6306
    nwritten = 0;
×
6307
    started_at = 0.0;
×
6308
    finished_at = 0.0;
×
6309
    duration = 0.0;
×
6310

6311
    source = sixel_compat_fopen(path, "rb");
×
6312
    if (source == NULL) {
×
6313
        sixel_helper_set_additional_message(
×
6314
            "copy_file_to_stream: failed to open assessment staging file.");
6315
        return SIXEL_LIBC_ERROR;
×
6316
    }
6317

6318
    for (;;) {
6319
        nread = fread(buffer, 1, sizeof(buffer), source);
×
6320
        if (nread == 0) {
×
6321
            if (ferror(source)) {
×
6322
                sixel_helper_set_additional_message(
×
6323
                    "copy_file_to_stream: failed while reading assessment staging file.");
6324
                (void) fclose(source);
×
6325
                return SIXEL_LIBC_ERROR;
×
6326
            }
6327
            break;
×
6328
        }
6329
        if (assessment != NULL) {
×
6330
            started_at = sixel_assessment_timer_now();
×
6331
        }
6332
        nwritten = fwrite(buffer, 1, nread, stream);
×
6333
        if (nwritten != nread) {
×
6334
            sixel_helper_set_additional_message(
×
6335
                "img2sixel: failed while copying assessment staging file.");
6336
            (void) fclose(source);
×
6337
            return SIXEL_LIBC_ERROR;
×
6338
        }
6339
        if (assessment != NULL) {
×
6340
            finished_at = sixel_assessment_timer_now();
×
6341
            duration = finished_at - started_at;
×
6342
            if (duration < 0.0) {
×
6343
                duration = 0.0;
×
6344
            }
6345
            sixel_assessment_record_output_write(assessment,
×
6346
                                                 nwritten,
6347
                                                 duration);
6348
        }
6349
    }
6350

6351
    if (fclose(source) != 0) {
×
6352
        sixel_helper_set_additional_message(
×
6353
            "img2sixel: failed to close assessment staging file.");
6354
        return SIXEL_LIBC_ERROR;
×
6355
    }
6356

6357
    return SIXEL_OK;
×
6358
}
6359

6360
typedef struct assessment_json_sink {
6361
    FILE *stream;
6362
    int failed;
6363
} assessment_json_sink_t;
6364

6365
static void
6366
assessment_json_callback(char const *chunk,
42✔
6367
                         size_t length,
6368
                         void *user_data)
6369
{
6370
    assessment_json_sink_t *sink;
6371

6372
    sink = (assessment_json_sink_t *)user_data;
42✔
6373
    if (sink == NULL || sink->stream == NULL) {
42!
6374
        return;
×
6375
    }
6376
    if (sink->failed) {
42!
6377
        return;
×
6378
    }
6379
    if (fwrite(chunk, 1, length, sink->stream) != length) {
42!
6380
        sink->failed = 1;
×
6381
    }
6382
}
14✔
6383

6384
static char *
6385
create_temp_template_with_prefix(sixel_allocator_t *allocator,
11✔
6386
                                 char const *prefix,
6387
                                 size_t *capacity_out)
6388
{
6389
    char const *tmpdir;
6390
    size_t tmpdir_len;
6391
    size_t prefix_len;
6392
    size_t suffix_len;
6393
    size_t template_len;
6394
    char *template_path;
6395
    int needs_separator;
6396
    size_t maximum_tmpdir_len;
6397

6398
    tmpdir = sixel_compat_getenv("TMPDIR");
11✔
6399
#if defined(_WIN32)
6400
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6401
        tmpdir = sixel_compat_getenv("TEMP");
6402
    }
6403
    if (tmpdir == NULL || tmpdir[0] == '\0') {
6404
        tmpdir = sixel_compat_getenv("TMP");
6405
    }
6406
#endif
6407
    if (tmpdir == NULL || tmpdir[0] == '\0') {
11!
6408
#if defined(_WIN32)
6409
        tmpdir = ".";
6410
#else
6411
        tmpdir = "/tmp";
6✔
6412
#endif
6413
    }
6414

6415
    tmpdir_len = strlen(tmpdir);
11✔
6416
    prefix_len = 0u;
11✔
6417
    suffix_len = 0u;
11✔
6418
    if (prefix == NULL) {
11!
6419
        return NULL;
×
6420
    }
6421

6422
    prefix_len = strlen(prefix);
11✔
6423
    suffix_len = prefix_len + strlen("-XXXXXX");
11✔
6424
    maximum_tmpdir_len = (size_t)INT_MAX;
11✔
6425

6426
    if (maximum_tmpdir_len <= suffix_len + 2) {
11!
6427
        return NULL;
×
6428
    }
6429
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
11!
6430
        return NULL;
×
6431
    }
6432
    needs_separator = 1;
11✔
6433
    if (tmpdir_len > 0) {
11!
6434
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
11!
6435
            needs_separator = 0;
5✔
6436
        }
5✔
6437
    }
5✔
6438

6439
    template_len = tmpdir_len + suffix_len + 2;
11✔
6440
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
11✔
6441
    if (template_path == NULL) {
11!
6442
        return NULL;
×
6443
    }
6444

6445
    if (needs_separator) {
11!
6446
#if defined(_WIN32)
6447
        (void) snprintf(template_path, template_len,
6448
                        "%s\\%s-XXXXXX", tmpdir, prefix);
6449
#else
6450
        (void) snprintf(template_path, template_len,
6✔
6451
                        "%s/%s-XXXXXX", tmpdir, prefix);
6452
#endif
6453
    } else {
6454
        (void) snprintf(template_path, template_len,
5✔
6455
                        "%s%s-XXXXXX", tmpdir, prefix);
6456
    }
6457

6458
    if (capacity_out != NULL) {
11!
6459
        *capacity_out = template_len;
11✔
6460
    }
5✔
6461

6462
    return template_path;
11✔
6463
}
5✔
6464

6465

6466
static char *
6467
create_temp_template(sixel_allocator_t *allocator,
9✔
6468
                     size_t *capacity_out)
6469
{
6470
    return create_temp_template_with_prefix(allocator,
12✔
6471
                                            "img2sixel",
6472
                                            capacity_out);
3✔
6473
}
6474

6475

6476
static void
6477
clipboard_select_format(char *dest,
2✔
6478
                        size_t dest_size,
6479
                        char const *format,
6480
                        char const *fallback)
6481
{
6482
    char const *source;
6483
    size_t limit;
6484

6485
    if (dest == NULL || dest_size == 0u) {
2!
6486
        return;
×
6487
    }
6488

6489
    source = fallback;
2✔
6490
    if (format != NULL && format[0] != '\0') {
2!
6491
        source = format;
×
6492
    }
6493

6494
    limit = dest_size - 1u;
2✔
6495
    if (limit == 0u) {
2!
6496
        dest[0] = '\0';
×
6497
        return;
×
6498
    }
6499

6500
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
2✔
6501
}
2✔
6502

6503

6504
static SIXELSTATUS
6505
clipboard_create_spool(sixel_allocator_t *allocator,
2✔
6506
                       char const *prefix,
6507
                       char **path_out,
6508
                       int *fd_out)
6509
{
6510
    SIXELSTATUS status;
6511
    char *template_path;
6512
    size_t template_capacity;
6513
    int open_flags;
6514
    int fd;
6515
    char *tmpname_result;
6516

6517
    status = SIXEL_FALSE;
2✔
6518
    template_path = NULL;
2✔
6519
    template_capacity = 0u;
2✔
6520
    open_flags = 0;
2✔
6521
    fd = (-1);
2✔
6522
    tmpname_result = NULL;
2✔
6523

6524
    template_path = create_temp_template_with_prefix(allocator,
4✔
6525
                                                     prefix,
2✔
6526
                                                     &template_capacity);
6527
    if (template_path == NULL) {
2!
6528
        sixel_helper_set_additional_message(
×
6529
            "clipboard: failed to allocate spool template.");
6530
        status = SIXEL_BAD_ALLOCATION;
×
6531
        goto end;
×
6532
    }
6533

6534
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
2!
6535
        /* Fall back to tmpnam() when mktemp variants are unavailable. */
6536
        tmpname_result = tmpnam(template_path);
×
6537
        if (tmpname_result == NULL) {
×
6538
            sixel_helper_set_additional_message(
×
6539
                "clipboard: failed to reserve spool template.");
6540
            status = SIXEL_LIBC_ERROR;
×
6541
            goto end;
×
6542
        }
6543
        template_capacity = strlen(template_path) + 1u;
×
6544
    }
6545

6546
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
2✔
6547
#if defined(O_EXCL)
6548
    open_flags |= O_EXCL;
2✔
6549
#endif
6550
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
2✔
6551
    if (fd < 0) {
2!
6552
        sixel_helper_set_additional_message(
×
6553
            "clipboard: failed to open spool file.");
6554
        status = SIXEL_LIBC_ERROR;
×
6555
        goto end;
×
6556
    }
6557

6558
    *path_out = template_path;
2✔
6559
    if (fd_out != NULL) {
2!
6560
        *fd_out = fd;
1✔
6561
        fd = (-1);
1✔
6562
    }
1✔
6563

6564
    template_path = NULL;
2✔
6565
    status = SIXEL_OK;
2✔
6566

6567
end:
6568
    if (fd >= 0) {
2!
6569
        (void)sixel_compat_close(fd);
1✔
6570
    }
1✔
6571
    if (template_path != NULL) {
2!
6572
        sixel_allocator_free(allocator, template_path);
×
6573
    }
6574

6575
    return status;
2✔
6576
}
6577

6578

6579
static SIXELSTATUS
6580
clipboard_write_file(char const *path,
1✔
6581
                     unsigned char const *data,
6582
                     size_t size)
6583
{
6584
    FILE *stream;
6585
    size_t written;
6586

6587
    if (path == NULL) {
1!
6588
        sixel_helper_set_additional_message(
×
6589
            "clipboard: spool path is null.");
6590
        return SIXEL_BAD_ARGUMENT;
×
6591
    }
6592

6593
    stream = sixel_compat_fopen(path, "wb");
1✔
6594
    if (stream == NULL) {
1!
6595
        sixel_helper_set_additional_message(
×
6596
            "clipboard: failed to open spool file for write.");
6597
        return SIXEL_LIBC_ERROR;
×
6598
    }
6599

6600
    written = 0u;
1✔
6601
    if (size > 0u && data != NULL) {
1!
6602
        written = fwrite(data, 1u, size, stream);
1✔
6603
        if (written != size) {
1!
6604
            (void)fclose(stream);
×
6605
            sixel_helper_set_additional_message(
×
6606
                "clipboard: failed to write spool payload.");
6607
            return SIXEL_LIBC_ERROR;
×
6608
        }
6609
    }
1✔
6610

6611
    if (fclose(stream) != 0) {
1!
6612
        sixel_helper_set_additional_message(
×
6613
            "clipboard: failed to close spool file after write.");
6614
        return SIXEL_LIBC_ERROR;
×
6615
    }
6616

6617
    return SIXEL_OK;
1✔
6618
}
1✔
6619

6620

6621
static SIXELSTATUS
6622
clipboard_read_file(char const *path,
1✔
6623
                    unsigned char **data,
6624
                    size_t *size)
6625
{
6626
    FILE *stream;
6627
    long seek_result;
6628
    long file_size;
6629
    unsigned char *buffer;
6630
    size_t read_size;
6631

6632
    if (data == NULL || size == NULL) {
1!
6633
        sixel_helper_set_additional_message(
×
6634
            "clipboard: read buffer pointers are null.");
6635
        return SIXEL_BAD_ARGUMENT;
×
6636
    }
6637

6638
    *data = NULL;
1✔
6639
    *size = 0u;
1✔
6640

6641
    if (path == NULL) {
1!
6642
        sixel_helper_set_additional_message(
×
6643
            "clipboard: spool path is null.");
6644
        return SIXEL_BAD_ARGUMENT;
×
6645
    }
6646

6647
    stream = sixel_compat_fopen(path, "rb");
1✔
6648
    if (stream == NULL) {
1!
6649
        sixel_helper_set_additional_message(
×
6650
            "clipboard: failed to open spool file for read.");
6651
        return SIXEL_LIBC_ERROR;
×
6652
    }
6653

6654
    seek_result = fseek(stream, 0L, SEEK_END);
1✔
6655
    if (seek_result != 0) {
1!
6656
        (void)fclose(stream);
×
6657
        sixel_helper_set_additional_message(
×
6658
            "clipboard: failed to seek spool file.");
6659
        return SIXEL_LIBC_ERROR;
×
6660
    }
6661

6662
    file_size = ftell(stream);
1✔
6663
    if (file_size < 0) {
1!
6664
        (void)fclose(stream);
×
6665
        sixel_helper_set_additional_message(
×
6666
            "clipboard: failed to determine spool size.");
6667
        return SIXEL_LIBC_ERROR;
×
6668
    }
6669

6670
    seek_result = fseek(stream, 0L, SEEK_SET);
1✔
6671
    if (seek_result != 0) {
1!
6672
        (void)fclose(stream);
×
6673
        sixel_helper_set_additional_message(
×
6674
            "clipboard: failed to rewind spool file.");
6675
        return SIXEL_LIBC_ERROR;
×
6676
    }
6677

6678
    if (file_size == 0) {
1!
6679
        buffer = NULL;
×
6680
        read_size = 0u;
×
6681
    } else {
6682
        buffer = (unsigned char *)malloc((size_t)file_size);
1✔
6683
        if (buffer == NULL) {
1!
6684
            (void)fclose(stream);
×
6685
            sixel_helper_set_additional_message(
×
6686
                "clipboard: malloc() failed for spool payload.");
6687
            return SIXEL_BAD_ALLOCATION;
×
6688
        }
6689
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
1✔
6690
        if (read_size != (size_t)file_size) {
1!
6691
            free(buffer);
×
6692
            (void)fclose(stream);
×
6693
            sixel_helper_set_additional_message(
×
6694
                "clipboard: failed to read spool payload.");
6695
            return SIXEL_LIBC_ERROR;
×
6696
        }
6697
    }
6698

6699
    if (fclose(stream) != 0) {
1!
6700
        if (buffer != NULL) {
×
6701
            free(buffer);
×
6702
        }
6703
        sixel_helper_set_additional_message(
×
6704
            "clipboard: failed to close spool file after read.");
6705
        return SIXEL_LIBC_ERROR;
×
6706
    }
6707

6708
    *data = buffer;
1✔
6709
    *size = read_size;
1✔
6710

6711
    return SIXEL_OK;
1✔
6712
}
1✔
6713

6714

6715
static SIXELSTATUS
6716
write_png_from_sixel(char const *sixel_path, char const *output_path)
9✔
6717
{
6718
    SIXELSTATUS status;
6719
    sixel_decoder_t *decoder;
6720

6721
    status = SIXEL_FALSE;
9✔
6722
    decoder = NULL;
9✔
6723

6724
    status = sixel_decoder_new(&decoder, NULL);
9✔
6725
    if (SIXEL_FAILED(status)) {
9!
6726
        goto end;
×
6727
    }
6728

6729
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
9✔
6730
    if (SIXEL_FAILED(status)) {
9!
6731
        goto end;
×
6732
    }
6733

6734
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
9✔
6735
    if (SIXEL_FAILED(status)) {
9!
6736
        goto end;
×
6737
    }
6738

6739
    status = sixel_decoder_decode(decoder);
9✔
6740

6741
end:
6✔
6742
    sixel_decoder_unref(decoder);
9✔
6743

6744
    return status;
9✔
6745
}
6746

6747

6748
/* load source data from specified file and encode it to SIXEL format
6749
 * output to encoder->outfd */
6750
SIXELAPI SIXELSTATUS
6751
sixel_encoder_encode(
436✔
6752
    sixel_encoder_t *encoder,   /* encoder object */
6753
    char const      *filename)  /* input filename */
6754
{
6755
    SIXELSTATUS status = SIXEL_FALSE;
436✔
6756
    SIXELSTATUS palette_status = SIXEL_OK;
436✔
6757
    int fuse_palette = 1;
436✔
6758
    sixel_loader_t *loader = NULL;
436✔
6759
    sixel_allocator_t *assessment_allocator = NULL;
436✔
6760
    sixel_allocator_t *encode_allocator = NULL;
436✔
6761
    sixel_frame_t *assessment_source_frame = NULL;
436✔
6762
    sixel_frame_t *assessment_target_frame = NULL;
436✔
6763
    sixel_frame_t *assessment_expanded_frame = NULL;
436✔
6764
    unsigned int assessment_section_mask =
436✔
6765
        encoder->assessment_sections & SIXEL_ASSESSMENT_SECTION_MASK;
436✔
6766
    int assessment_need_source_capture = 0;
436✔
6767
    int assessment_need_quantized_capture = 0;
436✔
6768
    int assessment_need_quality = 0;
436✔
6769
    int assessment_quality_quantized = 0;
436✔
6770
    assessment_json_sink_t assessment_sink;
6771
    FILE *assessment_json_file = NULL;
436✔
6772
    FILE *assessment_forward_stream = NULL;
436✔
6773
    int assessment_json_owned = 0;
436✔
6774
    char *assessment_temp_path = NULL;
436✔
6775
    size_t assessment_temp_capacity = 0u;
436✔
6776
    char *assessment_tmpnam_result = NULL;
436✔
6777
    sixel_assessment_spool_mode_t assessment_spool_mode
436✔
6778
        = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
6779
    char *assessment_forward_path = NULL;
436✔
6780
    size_t assessment_output_bytes;
6781
#if HAVE_SYS_STAT_H
6782
    struct stat assessment_stat;
6783
    int assessment_stat_result;
6784
    char const *assessment_size_path = NULL;
436✔
6785
#endif
6786
    char const *png_final_path = NULL;
436✔
6787
    char *png_temp_path = NULL;
436✔
6788
    size_t png_temp_capacity = 0u;
436✔
6789
    char *png_tmpnam_result = NULL;
436✔
6790
    int png_open_flags = 0;
436✔
6791
    int spool_required;
6792
    sixel_clipboard_spec_t clipboard_spec;
6793
    char clipboard_input_format[32];
6794
    char *clipboard_input_path;
6795
    unsigned char *clipboard_blob;
6796
    size_t clipboard_blob_size;
6797
    SIXELSTATUS clipboard_status;
6798
    char const *effective_filename;
6799
    unsigned int path_flags;
6800
    int path_check;
6801
    sixel_logger_t logger;
6802
    int logger_prepared;
6803

6804
    clipboard_input_format[0] = '\0';
436✔
6805
    clipboard_input_path = NULL;
436✔
6806
    clipboard_blob = NULL;
436✔
6807
    clipboard_blob_size = 0u;
436✔
6808
    clipboard_status = SIXEL_OK;
436✔
6809
    effective_filename = filename;
436✔
6810
    path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
436✔
6811
        SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
6812
        SIXEL_OPTION_PATH_ALLOW_REMOTE;
6813
    path_check = 0;
436✔
6814
    logger_prepared = 0;
436✔
6815
    sixel_logger_init(&logger);
436✔
6816
    sixel_logger_prepare_env(&logger);
436✔
6817
    logger_prepared = logger.active;
436✔
6818
    if (encoder != NULL) {
436!
6819
        encoder->logger = &logger;
436✔
6820
        encoder->parallel_job_id = -1;
436✔
6821
    }
146✔
6822

6823
    if (filename != NULL) {
436✔
6824
        path_check = sixel_option_validate_filesystem_path(
295✔
6825
            filename,
99✔
6826
            filename,
99✔
6827
            path_flags);
99✔
6828
        if (path_check != 0) {
295!
6829
            status = SIXEL_BAD_ARGUMENT;
×
6830
            goto end;
×
6831
        }
6832
    }
99✔
6833

6834
    if (encoder != NULL) {
436!
6835
        encode_allocator = encoder->allocator;
436✔
6836
        if (encode_allocator != NULL) {
436!
6837
            /*
6838
             * Hold a reference until cleanup so worker side-effects or loader
6839
             * destruction cannot release the allocator before sequential
6840
             * teardown finishes using it.
6841
             */
6842
            sixel_allocator_ref(encode_allocator);
436✔
6843
        }
146✔
6844
    }
146✔
6845

6846
    clipboard_spec.is_clipboard = 0;
436✔
6847
    clipboard_spec.format[0] = '\0';
436✔
6848
    if (effective_filename != NULL
437!
6849
            && sixel_clipboard_parse_spec(effective_filename,
342!
6850
                                          &clipboard_spec)
6851
            && clipboard_spec.is_clipboard) {
99!
6852
        clipboard_select_format(clipboard_input_format,
2✔
6853
                                sizeof(clipboard_input_format),
6854
                                clipboard_spec.format,
1✔
6855
                                "sixel");
6856
        clipboard_status = sixel_clipboard_read(
1✔
6857
            clipboard_input_format,
1✔
6858
            &clipboard_blob,
6859
            &clipboard_blob_size,
6860
            encoder->allocator);
1✔
6861
        if (SIXEL_FAILED(clipboard_status)) {
1!
6862
            status = clipboard_status;
×
6863
            goto end;
×
6864
        }
6865
        clipboard_status = clipboard_create_spool(
1✔
6866
            encoder->allocator,
1✔
6867
            "clipboard-in",
6868
            &clipboard_input_path,
6869
            NULL);
6870
        if (SIXEL_FAILED(clipboard_status)) {
1!
6871
            status = clipboard_status;
×
6872
            goto end;
×
6873
        }
6874
        clipboard_status = clipboard_write_file(
1✔
6875
            clipboard_input_path,
1✔
6876
            clipboard_blob,
1✔
6877
            clipboard_blob_size);
1✔
6878
        if (SIXEL_FAILED(clipboard_status)) {
1!
6879
            status = clipboard_status;
×
6880
            goto end;
×
6881
        }
6882
        if (clipboard_blob != NULL) {
1!
6883
            free(clipboard_blob);
1✔
6884
            clipboard_blob = NULL;
1✔
6885
        }
1✔
6886
        effective_filename = clipboard_input_path;
1✔
6887
    }
1✔
6888

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

7004
    }
1✔
7005

7006
    if (encoder->output_is_png) {
436✔
7007
        png_temp_capacity = 0u;
9✔
7008
        png_tmpnam_result = NULL;
9✔
7009
        png_temp_path = create_temp_template(encoder->allocator,
9✔
7010
                                             &png_temp_capacity);
7011
        if (png_temp_path == NULL) {
9!
7012
            sixel_helper_set_additional_message(
×
7013
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
7014
            status = SIXEL_BAD_ALLOCATION;
×
7015
            goto end;
×
7016
        }
7017
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
9!
7018
            /* Fall back to tmpnam() when mktemp variants are unavailable. */
7019
            png_tmpnam_result = tmpnam(png_temp_path);
×
7020
            if (png_tmpnam_result == NULL) {
×
7021
                sixel_helper_set_additional_message(
×
7022
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
7023
                status = SIXEL_RUNTIME_ERROR;
×
7024
                goto end;
×
7025
            }
7026
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
7027
        }
7028
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
9!
7029
            (void)sixel_compat_close(encoder->outfd);
9✔
7030
        }
3✔
7031
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
9✔
7032
#if defined(O_EXCL)
7033
        png_open_flags |= O_EXCL;
9✔
7034
#endif
7035
        encoder->outfd = sixel_compat_open(png_temp_path,
12✔
7036
                                           png_open_flags,
3✔
7037
                                           S_IRUSR | S_IWUSR);
7038
        if (encoder->outfd < 0) {
9!
7039
            sixel_helper_set_additional_message(
×
7040
                "sixel_encoder_encode: failed to create the PNG target file.");
7041
            status = SIXEL_LIBC_ERROR;
×
7042
            goto end;
×
7043
        }
7044
    }
3✔
7045

7046
    if (encoder == NULL) {
436!
7047
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7048
#  pragma GCC diagnostic push
7049
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7050
#endif
7051
        encoder = sixel_encoder_create();
×
7052
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7053
#  pragma GCC diagnostic pop
7054
#endif
7055
        if (encoder == NULL) {
×
7056
            sixel_helper_set_additional_message(
×
7057
                "sixel_encoder_encode: sixel_encoder_create() failed.");
7058
            status = SIXEL_BAD_ALLOCATION;
×
7059
            goto end;
×
7060
        }
7061
    } else {
7062
        sixel_encoder_ref(encoder);
436✔
7063
    }
7064

7065
    if (encode_allocator == NULL && encoder != NULL) {
436!
7066
        encode_allocator = encoder->allocator;
×
7067
        if (encode_allocator != NULL) {
×
7068
            /* Ensure the allocator stays valid after lazy encoder creation. */
7069
            sixel_allocator_ref(encode_allocator);
×
7070
        }
7071
    }
7072

7073
    if (encoder->assessment_observer != NULL) {
436✔
7074
        sixel_assessment_stage_transition(
3✔
7075
            encoder->assessment_observer,
3✔
7076
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
7077
    }
1✔
7078
    encoder->last_loader_name[0] = '\0';
436✔
7079
    encoder->last_source_path[0] = '\0';
436✔
7080
    encoder->last_input_bytes = 0u;
436✔
7081

7082
    /* if required color is not set, set the max value */
7083
    if (encoder->reqcolors == (-1)) {
436✔
7084
        encoder->reqcolors = SIXEL_PALETTE_MAX;
418✔
7085
    }
140✔
7086

7087
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
436!
7088
        sixel_frame_unref(encoder->capture_source_frame);
×
7089
        encoder->capture_source_frame = NULL;
×
7090
    }
7091

7092
    /* if required color is less then 2, set the min value */
7093
    if (encoder->reqcolors < 2) {
436✔
7094
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
7095
    }
1✔
7096

7097
    /* if color space option is not set, choose RGB color space */
7098
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
436✔
7099
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
418✔
7100
    }
140✔
7101

7102
    /* if color option is not default value, prohibit to read
7103
       the file as a paletted image */
7104
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
436✔
7105
        fuse_palette = 0;
96✔
7106
    }
32✔
7107

7108
    /* if scaling options are set, prohibit to read the file as
7109
       a paletted image */
7110
    if (encoder->percentwidth > 0 ||
559✔
7111
        encoder->percentheight > 0 ||
424✔
7112
        encoder->pixelwidth > 0 ||
418✔
7113
        encoder->pixelheight > 0) {
400✔
7114
        fuse_palette = 0;
99✔
7115
    }
33✔
7116

7117
reload:
290✔
7118

7119
    sixel_helper_set_loader_trace(encoder->verbose);
436✔
7120
    sixel_helper_set_thumbnail_size_hint(
436✔
7121
        sixel_encoder_thumbnail_hint(encoder));
146✔
7122

7123
    status = sixel_loader_new(&loader, encoder->allocator);
436✔
7124
    if (SIXEL_FAILED(status)) {
436!
7125
        goto load_end;
×
7126
    }
7127

7128
    status = sixel_loader_setopt(loader,
582✔
7129
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
7130
                                 &encoder->fstatic);
436✔
7131
    if (SIXEL_FAILED(status)) {
436!
7132
        goto load_end;
×
7133
    }
7134

7135
    status = sixel_loader_setopt(loader,
436✔
7136
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
7137
                                 &fuse_palette);
7138
    if (SIXEL_FAILED(status)) {
436!
7139
        goto load_end;
×
7140
    }
7141

7142
    status = sixel_loader_setopt(loader,
582✔
7143
                                 SIXEL_LOADER_OPTION_REQCOLORS,
7144
                                 &encoder->reqcolors);
436✔
7145
    if (SIXEL_FAILED(status)) {
436!
7146
        goto load_end;
×
7147
    }
7148

7149
    status = sixel_loader_setopt(loader,
582✔
7150
                                 SIXEL_LOADER_OPTION_BGCOLOR,
7151
                                 encoder->bgcolor);
436✔
7152
    if (SIXEL_FAILED(status)) {
436!
7153
        goto load_end;
×
7154
    }
7155

7156
    status = sixel_loader_setopt(loader,
582✔
7157
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
7158
                                 &encoder->loop_mode);
436✔
7159
    if (SIXEL_FAILED(status)) {
436!
7160
        goto load_end;
×
7161
    }
7162

7163
    status = sixel_loader_setopt(loader,
582✔
7164
                                 SIXEL_LOADER_OPTION_INSECURE,
7165
                                 &encoder->finsecure);
436✔
7166
    if (SIXEL_FAILED(status)) {
436!
7167
        goto load_end;
×
7168
    }
7169

7170
    status = sixel_loader_setopt(loader,
582✔
7171
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
7172
                                 encoder->cancel_flag);
436✔
7173
    if (SIXEL_FAILED(status)) {
436!
7174
        goto load_end;
×
7175
    }
7176

7177
    status = sixel_loader_setopt(loader,
582✔
7178
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
7179
                                 encoder->loader_order);
436✔
7180
    if (SIXEL_FAILED(status)) {
436!
7181
        goto load_end;
×
7182
    }
7183

7184
    status = sixel_loader_setopt(loader,
582✔
7185
                                 SIXEL_LOADER_OPTION_CONTEXT,
7186
                                 encoder);
146✔
7187
    if (SIXEL_FAILED(status)) {
436!
7188
        goto load_end;
×
7189
    }
7190

7191
    /*
7192
     * Wire the optional assessment observer into the loader.
7193
     *
7194
     * The observer travels separately from the callback context so mapfile
7195
     * palette probes and other callbacks can keep using arbitrary structs.
7196
     */
7197
    status = sixel_loader_setopt(loader,
582✔
7198
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
7199
                                 encoder->assessment_observer);
436✔
7200
    if (SIXEL_FAILED(status)) {
436!
7201
        goto load_end;
×
7202
    }
7203

7204
    sixel_encoder_log_stage(encoder,
582✔
7205
                            NULL,
7206
                            "loader",
7207
                            "worker",
7208
                            "start",
7209
                            "path=%s",
7210
                            effective_filename != NULL
146✔
7211
                                ? effective_filename
99✔
7212
                                : "");
7213

7214
    status = sixel_loader_load_file(loader,
582✔
7215
                                    effective_filename,
146✔
7216
                                    load_image_callback);
7217
    if (status != SIXEL_OK) {
436✔
7218
        goto load_end;
15✔
7219
    }
7220
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
421✔
7221
    if (sixel_loader_get_last_success_name(loader) != NULL) {
421!
7222
        (void)snprintf(encoder->last_loader_name,
421✔
7223
                       sizeof(encoder->last_loader_name),
7224
                       "%s",
7225
                       sixel_loader_get_last_success_name(loader));
7226
    } else {
141✔
7227
        encoder->last_loader_name[0] = '\0';
×
7228
    }
7229
    if (sixel_loader_get_last_source_path(loader) != NULL) {
421✔
7230
        (void)snprintf(encoder->last_source_path,
283✔
7231
                       sizeof(encoder->last_source_path),
7232
                       "%s",
7233
                       sixel_loader_get_last_source_path(loader));
7234
    } else {
95✔
7235
        encoder->last_source_path[0] = '\0';
138✔
7236
    }
7237
    sixel_encoder_log_stage(encoder,
562✔
7238
                            NULL,
7239
                            "loader",
7240
                            "worker",
7241
                            "finish",
7242
                            "path=%s loader=%s bytes=%zu",
7243
                            effective_filename != NULL
141✔
7244
                                ? effective_filename
95✔
7245
                                : "",
7246
                            encoder->last_loader_name,
421✔
7247
                            encoder->last_input_bytes);
141✔
7248
    if (encoder->assessment_observer != NULL) {
422✔
7249
        sixel_assessment_record_loader(encoder->assessment_observer,
4✔
7250
                                       encoder->last_source_path,
3✔
7251
                                       encoder->last_loader_name,
3✔
7252
                                       encoder->last_input_bytes);
1✔
7253
    }
1✔
7254

7255
load_end:
278✔
7256
    sixel_loader_unref(loader);
436✔
7257
    loader = NULL;
436✔
7258

7259
    if (status != SIXEL_OK) {
436✔
7260
        goto end;
15✔
7261
    }
7262

7263
    palette_status = sixel_encoder_emit_palette_output(encoder);
421✔
7264
    if (SIXEL_FAILED(palette_status)) {
421!
7265
        status = palette_status;
×
7266
        goto end;
×
7267
    }
7268

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

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

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

7511
    if (encoder->clipboard_output_active
421!
7512
            && encoder->clipboard_output_path != NULL) {
142!
7513
        unsigned char *clipboard_output_data;
7514
        size_t clipboard_output_size;
7515

7516
        clipboard_output_data = NULL;
1✔
7517
        clipboard_output_size = 0u;
1✔
7518

7519
        if (encoder->outfd
2!
7520
                && encoder->outfd != STDOUT_FILENO
1!
7521
                && encoder->outfd != STDERR_FILENO) {
1!
7522
            (void)sixel_compat_close(encoder->outfd);
1✔
7523
            encoder->outfd = STDOUT_FILENO;
1✔
7524
        }
1✔
7525

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

7552
    /* the status may not be SIXEL_OK */
7553

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

7606
    if (encoder != NULL) {
436!
7607
        encoder->logger = NULL;
436✔
7608
        encoder->parallel_job_id = -1;
436✔
7609
    }
146✔
7610
    if (logger_prepared) {
436!
NEW
7611
        sixel_logger_close(&logger);
×
7612
    }
7613

7614
    sixel_encoder_unref(encoder);
436✔
7615

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

7627
    return status;
436✔
7628
}
7629

7630

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

7646
    if (encoder == NULL || bytes == NULL) {
×
7647
        status = SIXEL_BAD_ARGUMENT;
×
7648
        goto end;
×
7649
    }
7650

7651
    status = sixel_frame_new(&frame, encoder->allocator);
×
7652
    if (SIXEL_FAILED(status)) {
×
7653
        goto end;
×
7654
    }
7655

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

7662
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
7663
    if (SIXEL_FAILED(status)) {
×
7664
        goto end;
×
7665
    }
7666

7667
    status = SIXEL_OK;
×
7668

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

7680

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

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

7701
    return SIXEL_OK;
3✔
7702
}
1✔
7703

7704

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

7722
    encoder->capture_quantized = enable ? 1 : 0;
3✔
7723
    if (!encoder->capture_quantized) {
3!
7724
        encoder->capture_valid = 0;
×
7725
    }
7726

7727
    return SIXEL_OK;
3✔
7728
}
1✔
7729

7730

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

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

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

7759
    *ppframe = NULL;
×
7760
    frame = NULL;
×
7761
    pixels = NULL;
×
7762
    palette = NULL;
×
7763

7764
    status = sixel_frame_new(&frame, allocator);
×
7765
    if (SIXEL_FAILED(status)) {
×
7766
        return status;
×
7767
    }
7768

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

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

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

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

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

7835

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

7857
    status = SIXEL_OK;
421✔
7858
    frame = NULL;
421✔
7859
    palette = NULL;
421✔
7860
    exported_colors = 0;
421✔
7861
    stream = NULL;
421✔
7862
    close_stream = 0;
421✔
7863
    path = NULL;
421✔
7864
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
421✔
7865
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
421✔
7866
    format_final = SIXEL_PALETTE_FORMAT_NONE;
421✔
7867
    mode = "wb";
421✔
7868

7869
    if (encoder == NULL || encoder->palette_output == NULL) {
421!
7870
        return SIXEL_OK;
421✔
7871
    }
7872

7873
    status = sixel_encoder_copy_quantized_frame(encoder,
×
7874
                                                encoder->allocator,
7875
                                                &frame);
7876
    if (SIXEL_FAILED(status)) {
×
7877
        return status;
×
7878
    }
7879

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

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

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

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

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

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

8005
cleanup:
8006
    if (close_stream && stream != NULL) {
×
8007
        (void) fclose(stream);
×
8008
    }
8009
    if (frame != NULL) {
×
8010
        sixel_frame_unref(frame);
×
8011
    }
8012

8013
    return status;
×
8014
}
141✔
8015

8016

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

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

8037
    sixel_frame_ref(encoder->capture_source_frame);
3✔
8038
    *ppframe = encoder->capture_source_frame;
3✔
8039

8040
    return SIXEL_OK;
3✔
8041
}
1✔
8042

8043

8044
#if HAVE_TESTS
8045
static int
8046
test1(void)
×
8047
{
8048
    int nret = EXIT_FAILURE;
×
8049
    sixel_encoder_t *encoder = NULL;
×
8050

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

8066
error:
8067
    sixel_encoder_unref(encoder);
×
8068
    return nret;
×
8069
}
8070

8071

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

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

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

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

8118
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
8119
        is_animation = 1;
×
8120
    }
8121

8122
    height = sixel_frame_get_height(frame);
×
8123

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

8133
    nret = EXIT_SUCCESS;
×
8134

8135
error:
8136
    sixel_encoder_unref(encoder);
×
8137
    sixel_frame_unref(frame);
×
8138
    return nret;
×
8139
}
8140

8141

8142
static int
8143
test3(void)
×
8144
{
8145
    int nret = EXIT_FAILURE;
×
8146
    int result;
8147

8148
    result = sixel_tty_wait_stdin(1000);
×
8149
    if (result != 0) {
×
8150
        goto error;
×
8151
    }
8152

8153
    nret = EXIT_SUCCESS;
×
8154

8155
error:
8156
    return nret;
×
8157
}
8158

8159

8160
static int
8161
test4(void)
×
8162
{
8163
    int nret = EXIT_FAILURE;
×
8164
    sixel_encoder_t *encoder = NULL;
×
8165
    SIXELSTATUS status;
8166

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

8179
    status = sixel_encoder_setopt(encoder,
×
8180
                                  SIXEL_OPTFLAG_LOOPMODE,
8181
                                  "force");
8182
    if (SIXEL_FAILED(status)) {
×
8183
        goto error;
×
8184
    }
8185

8186
    status = sixel_encoder_setopt(encoder,
×
8187
                                  SIXEL_OPTFLAG_PIPE_MODE,
8188
                                  "force");
8189
    if (SIXEL_FAILED(status)) {
×
8190
        goto error;
×
8191
    }
8192

8193
    nret = EXIT_SUCCESS;
×
8194

8195
error:
8196
    sixel_encoder_unref(encoder);
×
8197
    return nret;
×
8198
}
8199

8200

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

8209
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
8210
    if (SIXEL_FAILED(status)) {
×
8211
        goto error;
×
8212
    }
8213

8214
    status = sixel_encoder_new(&encoder, allocator);
×
8215
    if (SIXEL_FAILED(status)) {
×
8216
        goto error;
×
8217
    }
8218

8219
    sixel_encoder_ref(encoder);
×
8220
    sixel_encoder_unref(encoder);
×
8221
    nret = EXIT_SUCCESS;
×
8222

8223
error:
8224
    sixel_encoder_unref(encoder);
×
8225
    return nret;
×
8226
}
8227

8228

8229
SIXELAPI int
8230
sixel_encoder_tests_main(void)
×
8231
{
8232
    int nret = EXIT_FAILURE;
×
8233
    size_t i;
8234
    typedef int (* testcase)(void);
8235

8236
    static testcase const testcases[] = {
8237
        test1,
8238
        test2,
8239
        test3,
8240
        test4,
8241
        test5
8242
    };
8243

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

8251
    nret = EXIT_SUCCESS;
×
8252

8253
error:
8254
    return nret;
×
8255
}
8256
#endif  /* HAVE_TESTS */
8257

8258

8259
/* emacs Local Variables:      */
8260
/* emacs mode: c               */
8261
/* emacs tab-width: 4          */
8262
/* emacs indent-tabs-mode: nil */
8263
/* emacs c-basic-offset: 4     */
8264
/* emacs End:                  */
8265
/* vim: set expandtab ts=4 : */
8266
/* 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