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

saitoha / libsixel / 19988215696

06 Dec 2025 12:05PM UTC coverage: 43.762% (-7.2%) from 50.919%
19988215696

push

github

saitoha
threading: remove sixel_threading

10728 of 38707 branches covered (27.72%)

14710 of 33614 relevant lines covered (43.76%)

2919171.27 hits per line

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

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

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

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

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

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

126
#define SIXEL_ENCODER_PRECISION_ENVVAR "SIXEL_FLOAT32_DITHER"
127

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

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

152
#if defined(_WIN32)
153

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

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

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

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

185
#endif /* _WIN32 */
186

187

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

342

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

352
    len = strlen(s);
57✔
353

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

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

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

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

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

383
    return 1;
384
}
385

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

393
    prefer_float32 = encoder->prefer_float32;
×
394

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

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

409
    encoder->prefer_float32 = prefer_float32;
×
410

411
    return SIXEL_OK;
×
412
}
413

414

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

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

563
    status = SIXEL_OK;
564

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

568
    return status;
45✔
569
}
570

571

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

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

582
    return result;
5,214✔
583
}
584

585

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

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

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

609
    return result;
72✔
610
}
611

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

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

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

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

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

680
    return written;
×
681
}
682

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

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

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

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

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

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

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

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

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

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

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

809

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

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

830
    status = SIXEL_OK;
831

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

836

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

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

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

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

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

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

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

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

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

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

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

1030
    return status;
1031
}
1032

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

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

1048
    status = SIXEL_OK;
1049

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

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

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

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

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

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

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

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

1103

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

1113

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

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

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

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

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

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

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

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

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

1217
        break;
1218
    }
1219

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

1224

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

1228

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

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

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

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

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

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

1261
    return 1;
1262
}
1263

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

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

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

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

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

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

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

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

1350
    return spec;
1351
}
1352

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

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

1370
    return SIXEL_PALETTE_FORMAT_NONE;
1371
}
1372

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

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

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

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

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

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

1417
    return 1;
1418
}
1419

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

1432

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

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

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

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

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

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

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

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

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

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

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

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

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

1548

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

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

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

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

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

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

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

1603

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

1612

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

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

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

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

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

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

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

1647
    return SIXEL_PALETTE_FORMAT_NONE;
1648
}
1649

1650

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

1660

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

1673

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

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

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

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

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

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

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

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

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

1774

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2008

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

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

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

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

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

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

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

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

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

2143

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2345

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

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

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

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

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

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

2399
    return SIXEL_OK;
2400
}
2401

2402

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

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

2427

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

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

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

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

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

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

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

2489

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

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

2524

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2830
    *dither = callback_context.dither;
21✔
2831

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

2841
    return status;
21✔
2842
}
2843

2844

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

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

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

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

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

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

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

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

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

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

2990
    status = SIXEL_OK;
195✔
2991

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

3007

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

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

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

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

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

3043
    use_float_resize = 0;
519✔
3044

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

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

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

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

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

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

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

3117
end:
3118
    return status;
3119
}
3120

3121

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

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

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

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

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

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

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

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

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

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

3201

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

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

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

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

3247

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

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

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

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

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

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

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

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

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

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

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

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

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

3426
    return status;
435✔
3427
}
3428

3429

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

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

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

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

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

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

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

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

3572
        write_started_at = 0.0;
54✔
3573
        write_finished_at = 0.0;
54✔
3574
        write_duration = 0.0;
54✔
3575
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3576
            write_started_at = sixel_assessment_timer_now();
×
3577
        }
3578
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
54✔
3579
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3580
            write_finished_at = sixel_assessment_timer_now();
×
3581
            write_duration = write_finished_at - write_started_at;
×
3582
            if (write_duration < 0.0) {
×
3583
                write_duration = 0.0;
54✔
3584
            }
3585
        }
3586
        if (nwrite < 0) {
54!
3587
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3588
            sixel_helper_set_additional_message(
×
3589
                "sixel_encoder_output_with_macro: "
3590
                "sixel_write_callback() failed.");
3591
            goto end;
×
3592
        }
3593
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
3594
            sixel_assessment_record_output_write(
×
3595
                encoder->assessment_observer,
3596
                (size_t)nwrite,
3597
                write_duration);
3598
        }
3599
    }
3600
    if (encoder->macro_number < 0) {
84✔
3601
        nwrite = sixel_compat_snprintf(
81✔
3602
            buffer,
3603
            sizeof(buffer),
3604
            "\033[%d*z",
3605
            sixel_frame_get_frame_no(frame));
3606
        if (nwrite < 0) {
81!
3607
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3608
            sixel_helper_set_additional_message(
×
3609
                "sixel_encoder_output_with_macro: command format failed.");
3610
        }
3611
        write_started_at = 0.0;
81✔
3612
        write_finished_at = 0.0;
81✔
3613
        write_duration = 0.0;
81✔
3614
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3615
            write_started_at = sixel_assessment_timer_now();
×
3616
        }
3617
        nwrite = sixel_write_callback(buffer,
162✔
3618
                                      (int)strlen(buffer),
81✔
3619
                                      &encoder->outfd);
3620
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3621
            write_finished_at = sixel_assessment_timer_now();
×
3622
            write_duration = write_finished_at - write_started_at;
×
3623
            if (write_duration < 0.0) {
×
3624
                write_duration = 0.0;
81✔
3625
            }
3626
        }
3627
        if (nwrite < 0) {
81!
3628
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3629
            sixel_helper_set_additional_message(
×
3630
                "sixel_encoder_output_with_macro: "
3631
                "sixel_write_callback() failed.");
3632
            goto end;
×
3633
        }
3634
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
3635
            sixel_assessment_record_output_write(
×
3636
                encoder->assessment_observer,
3637
                (size_t)nwrite,
3638
                write_duration);
3639
        }
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
3647
                             / 100000);
3648
#  else
3649
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
54✔
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
                remaining_delay =
54✔
3663
                    (unsigned int)(1000 * 10 * delay - dulation);
54✔
3664
                sixel_sleep(remaining_delay);
54✔
3665
            }
3666
        }
3667
    }
3668

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

3674
    return status;
84✔
3675
}
3676

3677

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

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

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

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

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

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

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

3774
    status = SIXEL_OK;
×
3775

3776
end:
×
3777
    return status;
×
3778
}
3779

3780

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

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

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

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

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

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

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

3891
    status = SIXEL_OK;
×
3892

3893
end:
×
3894
    return status;
×
3895
}
3896

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4351

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

4360
    return status;
525✔
4361
}
4362

4363

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

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

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

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

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

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

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

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

4519
    goto end;
537✔
4520

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

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

4530

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

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

4543
    return encoder;
×
4544
}
4545

4546

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

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

4589

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

4598

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

4609

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

4619
    encoder->cancel_flag = cancel_flag;
432✔
4620

4621
    return status;
432✔
4622
}
4623

4624

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

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

4779

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

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

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

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

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

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

4817
    return matched;
4818
}
4819

4820

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

4847
    return argument;
4848
}
4849

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

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

4872
    length = 0u;
4873

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

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

4892

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

4907

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

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

4923
    return 0;
4924
}
4925

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

6272
    status = SIXEL_OK;
6273

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

6280
    return status;
717✔
6281
}
6282

6283

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

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

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

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

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

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

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

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

6374
    return SIXEL_OK;
6375
}
6376

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

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

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

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

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

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

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

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

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

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

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

6479
    return template_path;
6480
}
6481

6482

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

6492

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

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

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

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

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

6520

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

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

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

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

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

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

6581
    template_path = NULL;
×
6582
    status = SIXEL_OK;
×
6583

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

6592
    return status;
×
6593
}
6594

6595

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

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

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

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

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

6634
    return SIXEL_OK;
6635
}
6636

6637

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

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

6655
    *data = NULL;
×
6656
    *size = 0u;
×
6657

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

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

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

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

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

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

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

6725
    *data = buffer;
×
6726
    *size = read_size;
×
6727

6728
    return SIXEL_OK;
×
6729
}
6730

6731

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

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

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

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

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

6756
    status = sixel_decoder_decode(decoder);
9✔
6757

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

6761
    return status;
9✔
6762
}
6763

6764

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

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

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

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

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

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

7021
    }
7022

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

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

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

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

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

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

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

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

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

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

7134
reload:
330✔
7135

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

7512
        clipboard_output_data = NULL;
×
7513
        clipboard_output_size = 0u;
×
7514

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

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

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

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

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

7610
    sixel_encoder_unref(encoder);
429✔
7611

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

7623
    return status;
429✔
7624
}
7625

7626

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

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

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

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

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

7663
    status = SIXEL_OK;
7664

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

7676

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

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

7697
    return SIXEL_OK;
7698
}
7699

7700

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

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

7723
    return SIXEL_OK;
7724
}
7725

7726

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

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

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

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

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

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

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

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

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

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

7831

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

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

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

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

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

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

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

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

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

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

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

8009
    return status;
8010
}
8011

8012

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

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

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

8036
    return SIXEL_OK;
3✔
8037
}
8038

8039

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

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

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

8067

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

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

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

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

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

8118
    height = sixel_frame_get_height(frame);
×
8119

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

8129
    nret = EXIT_SUCCESS;
8130

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

8137

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

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

8149
    nret = EXIT_SUCCESS;
8150

8151
error:
×
8152
    return nret;
×
8153
}
8154

8155

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

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

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

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

8189
    nret = EXIT_SUCCESS;
8190

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

8196

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

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

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

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

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

8224

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

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

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

8247
    nret = EXIT_SUCCESS;
8248

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

8254

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