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

saitoha / libsixel / 20208829175

14 Dec 2025 11:18AM UTC coverage: 43.574% (-7.0%) from 50.557%
20208829175

push

github

saitoha
wic: add wic_sources to src/probe.c

11725 of 42140 branches covered (27.82%)

16506 of 37880 relevant lines covered (43.57%)

3210498.67 hits per line

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

47.11
/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 "frame.h"
117
#include "output.h"
118
#include "logger.h"
119
#include "options.h"
120
#include "dither.h"
121
#include "rgblookup.h"
122
#include "clipboard.h"
123
#include "compat_stub.h"
124
#include "sleep.h"
125
#include "threading.h"
126

127
#define SIXEL_ENCODER_PRECISION_ENVVAR "SIXEL_FLOAT32_DITHER"
128
#define SIXEL_ENCODER_LUT_POLICY_ENVVAR "SIXEL_DITHER_LOOKUP_POLICY"
129
#define SIXEL_ENCODER_SAMPLE_TARGET_ENVVAR \
130
    "SIXEL_PALETTE_SAMPLE_TARGET"
131

132
typedef enum sixel_encoder_precision_mode {
133
    SIXEL_ENCODER_PRECISION_MODE_AUTO = 0,
134
    SIXEL_ENCODER_PRECISION_MODE_8BIT,
135
    SIXEL_ENCODER_PRECISION_MODE_FLOAT32
136
} sixel_encoder_precision_mode_t;
137

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

156
typedef struct sixel_palette_async_job {
157
    sixel_thread_t thread;
158
    sixel_mutex_t mutex;
159
    sixel_cond_t cond;
160
    sixel_frame_t *sample_frame;
161
    sixel_allocator_t *allocator;
162
    sixel_dither_t *dither;
163
    SIXELSTATUS status;
164
    int target_pixelformat;
165
    int reqcolors;
166
    int method_for_largest;
167
    int method_for_rep;
168
    int quality_mode;
169
    int lut_policy;
170
    int final_merge_mode;
171
    int sixel_reversible;
172
    int quantize_model;
173
    int force_palette;
174
    int started;
175
    int finished;
176
} sixel_palette_async_job_t;
177

178
static SIXELSTATUS sixel_encoder_palette_job_init(
179
    sixel_palette_async_job_t *job,
180
    sixel_allocator_t *allocator);
181
static void sixel_encoding_planner_init(sixel_encoding_planner_t *planner);
182
static void sixel_encoding_planner_plan(sixel_encoding_planner_t *planner,
183
                                        sixel_encoder_t *encoder,
184
                                        sixel_frame_t *frame);
185
static int sixel_encoder_parse_sample_target(char const *text,
186
                                             size_t *value_out);
187
static void sixel_encoder_palette_job_dispose(sixel_palette_async_job_t *job);
188
static SIXELSTATUS sixel_encoder_palette_job_launch(
189
    sixel_palette_async_job_t *job,
190
    sixel_frame_t *frame,
191
    int target_pixelformat,
192
    sixel_encoder_t *encoder);
193
static SIXELSTATUS sixel_encoder_palette_job_wait(
194
    sixel_palette_async_job_t *job,
195
    sixel_dither_t **dither_out);
196

197
#if defined(_WIN32)
198

199
# include <windows.h>
200
# if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
201
#  include <io.h>
202
# endif
203
# if defined(_MSC_VER)
204
#   include <time.h>
205
# endif
206

207
# if defined(CLOCKS_PER_SEC)
208
#  undef CLOCKS_PER_SEC
209
# endif
210
# define CLOCKS_PER_SEC 1000
211

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

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

230
#endif /* _WIN32 */
231

232

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

385
static sixel_option_choice_t const g_option_choices_precision[] = {
386
    { "auto", SIXEL_ENCODER_PRECISION_MODE_AUTO },
387
    { "8bit", SIXEL_ENCODER_PRECISION_MODE_8BIT },
388
    { "float32", SIXEL_ENCODER_PRECISION_MODE_FLOAT32 }
389
};
390

391

392
static char *
393
arg_strdup(
60✔
394
    char const          /* in */ *s,          /* source buffer */
395
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
396
                                                 destination buffer */
397
{
398
    char *p;
60✔
399
    size_t len;
60✔
400

401
    len = strlen(s);
60✔
402

403
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
60✔
404
    if (p) {
60!
405
        (void)sixel_compat_strcpy(p, len + 1, s);
60✔
406
    }
407
    return p;
60✔
408
}
409

410
static int
411
sixel_encoder_env_prefers_float32(char const *text)
576✔
412
{
413
    char lowered[8];
576✔
414
    size_t i;
576✔
415

416
    if (text == NULL || *text == '\0') {
576!
417
        return 0;
418
    }
419

420
    for (i = 0; i < sizeof(lowered) - 1 && text[i] != '\0'; ++i) {
×
421
        lowered[i] = (char)tolower((unsigned char)text[i]);
×
422
    }
423
    lowered[i] = '\0';
×
424

425
    if (strcmp(lowered, "0") == 0
×
426
        || strcmp(lowered, "off") == 0
×
427
        || strcmp(lowered, "false") == 0
×
428
        || strcmp(lowered, "no") == 0) {
×
429
        return 0;
430
    }
431

432
    return 1;
433
}
434

435
static SIXELSTATUS
436
sixel_encoder_apply_precision_override(
×
437
    sixel_encoder_t *encoder,
438
    sixel_encoder_precision_mode_t mode)
439
{
440
    int prefer_float32;
×
441

442
    prefer_float32 = encoder->prefer_float32;
×
443

444
    if (mode == SIXEL_ENCODER_PRECISION_MODE_AUTO) {
×
445
        return SIXEL_OK;
446
    }
447

448
    if (mode == SIXEL_ENCODER_PRECISION_MODE_FLOAT32) {
×
449
        prefer_float32 = 1;
450
    } else if (mode == SIXEL_ENCODER_PRECISION_MODE_8BIT) {
×
451
        prefer_float32 = 0;
452
    } else {
453
        sixel_helper_set_additional_message(
×
454
            "sixel_encoder_setopt: invalid precision override.");
455
        return SIXEL_BAD_ARGUMENT;
×
456
    }
457

458
    encoder->prefer_float32 = prefer_float32;
×
459

460
    return SIXEL_OK;
×
461
}
462

463

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

482
    /* from rgb_lookup.h generated by gpref */
483
    name_length = strlen(s);
48✔
484
    if (name_length > (size_t)UINT_MAX) {
48!
485
        sixel_helper_set_additional_message(
×
486
            "sixel_parse_x_colorspec: color name is too long.");
487
        status = SIXEL_BAD_ARGUMENT;
×
488
        goto end;
×
489
    }
490
    pcolor = lookup_rgb(s, (unsigned int)name_length);
48✔
491
    if (pcolor) {
48✔
492
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
493
        if (*bgcolor == NULL) {
3!
494
            sixel_helper_set_additional_message(
×
495
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
496
            status = SIXEL_BAD_ALLOCATION;
×
497
            goto end;
×
498
        }
499
        (*bgcolor)[0] = pcolor->r;
3✔
500
        (*bgcolor)[1] = pcolor->g;
3✔
501
        (*bgcolor)[2] = pcolor->b;
3✔
502
    } else if (s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && s[3] == ':') {
45!
503
        p = buf = arg_strdup(s + 4, allocator);
6✔
504
        if (buf == NULL) {
6!
505
            sixel_helper_set_additional_message(
×
506
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
507
            status = SIXEL_BAD_ALLOCATION;
×
508
            goto end;
×
509
        }
510
        while (*p) {
15!
511
            v = 0;
512
            for (endptr = p; endptr - p <= 12; ++endptr) {
36!
513
                if (*endptr >= '0' && *endptr <= '9') {
36✔
514
                    v = (v << 4) | (unsigned long)(*endptr - '0');
15✔
515
                } else if (*endptr >= 'a' && *endptr <= 'f') {
21!
516
                    v = (v << 4) | (unsigned long)(*endptr - 'a' + 10);
3✔
517
                } else if (*endptr >= 'A' && *endptr <= 'F') {
18!
518
                    v = (v << 4) | (unsigned long)(*endptr - 'A' + 10);
3✔
519
                } else {
520
                    break;
521
                }
522
            }
523
            if (endptr - p == 0) {
15!
524
                break;
525
            }
526
            if (endptr - p > 4) {
15!
527
                break;
528
            }
529
            v = v << ((4 - (endptr - p)) * 4) >> 8;
15✔
530
            components[component_index++] = (unsigned char)v;
15✔
531
            p = endptr;
15✔
532
            if (component_index == 3) {
15✔
533
                break;
534
            }
535
            if (*p == '\0') {
12✔
536
                break;
537
            }
538
            if (*p != '/') {
9!
539
                break;
540
            }
541
            ++p;
9✔
542
        }
543
        if (component_index != 3 || *p != '\0' || *p == '/') {
6!
544
            status = SIXEL_BAD_ARGUMENT;
3✔
545
            goto end;
3✔
546
        }
547
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
548
        if (*bgcolor == NULL) {
3!
549
            sixel_helper_set_additional_message(
×
550
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
551
            status = SIXEL_BAD_ALLOCATION;
×
552
            goto end;
×
553
        }
554
        (*bgcolor)[0] = components[0];
3✔
555
        (*bgcolor)[1] = components[1];
3✔
556
        (*bgcolor)[2] = components[2];
3✔
557
    } else if (*s == '#') {
39✔
558
        buf = arg_strdup(s + 1, allocator);
30✔
559
        if (buf == NULL) {
30!
560
            sixel_helper_set_additional_message(
×
561
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
562
            status = SIXEL_BAD_ALLOCATION;
×
563
            goto end;
×
564
        }
565
        for (p = endptr = buf; endptr - p <= 12; ++endptr) {
213✔
566
            if (*endptr >= '0' && *endptr <= '9') {
210✔
567
                *endptr -= '0';
117✔
568
            } else if (*endptr >= 'a' && *endptr <= 'f') {
93!
569
                *endptr -= 'a' - 10;
57✔
570
            } else if (*endptr >= 'A' && *endptr <= 'F') {
36✔
571
                *endptr -= 'A' - 10;
9✔
572
            } else if (*endptr == '\0') {
27✔
573
                break;
574
            } else {
575
                status = SIXEL_BAD_ARGUMENT;
3✔
576
                goto end;
3✔
577
            }
578
        }
579
        if (endptr - p > 12) {
27✔
580
            status = SIXEL_BAD_ARGUMENT;
3✔
581
            goto end;
3✔
582
        }
583
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
24✔
584
        if (*bgcolor == NULL) {
24!
585
            sixel_helper_set_additional_message(
×
586
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
587
            status = SIXEL_BAD_ALLOCATION;
×
588
            goto end;
×
589
        }
590
        switch (endptr - p) {
24✔
591
        case 3:
9✔
592
            (*bgcolor)[0] = (unsigned char)(p[0] << 4);
9✔
593
            (*bgcolor)[1] = (unsigned char)(p[1] << 4);
9✔
594
            (*bgcolor)[2] = (unsigned char)(p[2] << 4);
9✔
595
            break;
9✔
596
        case 6:
6✔
597
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
6✔
598
            (*bgcolor)[1] = (unsigned char)(p[2] << 4 | p[3]);
6✔
599
            (*bgcolor)[2] = (unsigned char)(p[4] << 4 | p[4]);
6✔
600
            break;
6✔
601
        case 9:
3✔
602
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
603
            (*bgcolor)[1] = (unsigned char)(p[3] << 4 | p[4]);
3✔
604
            (*bgcolor)[2] = (unsigned char)(p[6] << 4 | p[7]);
3✔
605
            break;
3✔
606
        case 12:
3✔
607
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
608
            (*bgcolor)[1] = (unsigned char)(p[4] << 4 | p[5]);
3✔
609
            (*bgcolor)[2] = (unsigned char)(p[8] << 4 | p[9]);
3✔
610
            break;
3✔
611
        default:
3✔
612
            status = SIXEL_BAD_ARGUMENT;
3✔
613
            goto end;
3✔
614
        }
615
    } else {
616
        status = SIXEL_BAD_ARGUMENT;
9✔
617
        goto end;
9✔
618
    }
619

620
    status = SIXEL_OK;
621

622
end:
48✔
623
    sixel_allocator_free(allocator, buf);
48✔
624

625
    return status;
48✔
626
}
627

628

629
/* generic writer function for passing to sixel_output_new() */
630
static int
631
sixel_write_callback(char *data, int size, void *priv)
7,331✔
632
{
633
    int result;
7,331✔
634

635
    result = (int)sixel_compat_write(*(int *)priv,
7,142✔
636
                                     data,
637
                                     (size_t)size);
638

639
    return result;
7,331✔
640
}
641

642

643
/* the writer function with hex-encoding for passing to sixel_output_new() */
644
static int
645
sixel_hex_write_callback(
72✔
646
    char    /* in */ *data,
647
    int     /* in */ size,
648
    void    /* in */ *priv)
649
{
650
    char hex[SIXEL_OUTPUT_PACKET_SIZE * 2];
72✔
651
    int i;
72✔
652
    int j;
72✔
653
    int result;
72✔
654

655
    for (i = j = 0; i < size; ++i, ++j) {
701,274✔
656
        hex[j] = (data[i] >> 4) & 0xf;
701,202✔
657
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
701,202!
658
        hex[++j] = data[i] & 0xf;
701,202✔
659
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
957,426✔
660
    }
661

662
    result = (int)sixel_compat_write(*(int *)priv,
144✔
663
                                     hex,
664
                                     (size_t)(size * 2));
72✔
665

666
    return result;
72✔
667
}
668

669
typedef struct sixel_encoder_output_probe {
670
    sixel_encoder_t *encoder;
671
    sixel_write_function base_write;
672
    void *base_priv;
673
} sixel_encoder_output_probe_t;
674

675
static int
676
sixel_write_with_probe(char *data, int size, void *priv)
72✔
677
{
678
    sixel_encoder_output_probe_t *probe;
72✔
679
    int written;
72✔
680
    double started_at;
72✔
681
    double finished_at;
72✔
682
    double duration;
72✔
683

684
    probe = (sixel_encoder_output_probe_t *)priv;
72✔
685
    if (probe == NULL || probe->base_write == NULL) {
72!
686
        return 0;
687
    }
688
    started_at = 0.0;
72✔
689
    finished_at = 0.0;
72✔
690
    duration = 0.0;
72✔
691
    if (probe->encoder != NULL &&
72!
692
            probe->encoder->assessment_observer != NULL) {
72!
693
        started_at = sixel_assessment_timer_now();
72✔
694
    }
695
    written = probe->base_write(data, size, probe->base_priv);
72✔
696
    if (probe->encoder != NULL &&
72!
697
            probe->encoder->assessment_observer != NULL) {
72!
698
        finished_at = sixel_assessment_timer_now();
72✔
699
        duration = finished_at - started_at;
72✔
700
        if (duration < 0.0) {
72!
701
            duration = 0.0;
×
702
        }
703
    }
704
    if (written > 0 && probe->encoder != NULL &&
72!
705
            probe->encoder->assessment_observer != NULL) {
72!
706
        sixel_assessment_record_output_write(
72✔
707
            probe->encoder->assessment_observer,
708
            (size_t)written,
709
            duration);
710
    }
711
    return written;
712
}
713

714
/*
715
 * Reuse the fn_write probe for raw escape writes so that every
716
 * assessment bucket receives the same accounting.
717
 *
718
 *     encoder        probe wrapper       write(2)
719
 *     +------+    +----------------+    +---------+
720
 *     | data | -> | sixel_write_*  | -> | target  |
721
 *     +------+    +----------------+    +---------+
722
 */
723
static int
724
sixel_encoder_probe_fd_write(sixel_encoder_t *encoder,
×
725
                             char *data,
726
                             int size,
727
                             int fd)
728
{
729
    sixel_encoder_output_probe_t probe;
×
730
    int written;
×
731

732
    probe.encoder = encoder;
×
733
    probe.base_write = sixel_write_callback;
×
734
    probe.base_priv = &fd;
×
735
    written = sixel_write_with_probe(data, size, &probe);
×
736

737
    return written;
×
738
}
739

740
static void
741
sixel_encoder_log_stage(sixel_encoder_t *encoder,
6,432✔
742
                        sixel_frame_t *frame,
743
                        char const *worker,
744
                        char const *role,
745
                        char const *event,
746
                        char const *fmt,
747
                        ...)
748
{
749
    sixel_logger_t *logger;
6,432✔
750
    int job_id;
6,432✔
751
    int height;
6,432✔
752
    char message[256];
6,432✔
753
    va_list args;
6,432✔
754

755
    logger = NULL;
6,432✔
756
    if (encoder != NULL) {
6,432!
757
        logger = encoder->logger;
6,432✔
758
    }
759
    if (logger == NULL || logger->file == NULL || !logger->active) {
6,432!
760
        return;
6,432✔
761
    }
762

763
    job_id = -1;
×
764
    height = 0;
×
765
    if (frame != NULL) {
×
766
        job_id = sixel_frame_get_frame_no(frame);
×
767
        height = sixel_frame_get_height(frame);
×
768
    }
769

770
    message[0] = '\0';
×
771
#if defined(__clang__)
772
#pragma clang diagnostic push
773
#pragma clang diagnostic ignored "-Wformat-nonliteral"
774
#elif defined(__GNUC__)
775
#pragma GCC diagnostic push
776
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
777
#endif
778
    va_start(args, fmt);
×
779
    if (fmt != NULL) {
×
780
#if defined(__clang__)
781
#pragma clang diagnostic push
782
#pragma clang diagnostic ignored "-Wformat-nonliteral"
783
#elif defined(__GNUC__)
784
#pragma GCC diagnostic push
785
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
786
#endif
787
        (void)vsnprintf(message, sizeof(message), fmt, args);
×
788
#if defined(__clang__)
789
#pragma clang diagnostic pop
790
#elif defined(__GNUC__)
791
#pragma GCC diagnostic pop
792
#endif
793
    }
794
    va_end(args);
×
795
#if defined(__clang__)
796
#pragma clang diagnostic pop
797
#elif defined(__GNUC__)
798
#pragma GCC diagnostic pop
799
#endif
800

801
    sixel_logger_logf(logger,
×
802
                      role,
803
                      worker,
804
                      event,
805
                      job_id,
806
                      -1,
807
                      0,
808
                      height,
809
                      0,
810
                      height,
811
                      "%s",
812
                      message);
813
}
1!
814

815
static SIXELSTATUS
816
sixel_encoder_ensure_cell_size(sixel_encoder_t *encoder)
×
817
{
818
#if defined(TIOCGWINSZ)
819
    struct winsize ws;
×
820
    int result;
×
821
    int fd = 0;
×
822

823
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
824
        return SIXEL_OK;
825
    }
826

827
    fd = sixel_compat_open("/dev/tty", O_RDONLY);
×
828
    if (fd >= 0) {
×
829
        result = ioctl(fd, TIOCGWINSZ, &ws);
×
830
        (void)sixel_compat_close(fd);
×
831
    } else {
832
        sixel_helper_set_additional_message(
×
833
            "failed to open /dev/tty");
834
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
835
    }
836
    if (result != 0) {
×
837
        sixel_helper_set_additional_message(
×
838
            "failed to query terminal geometry with ioctl().");
839
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
840
    }
841

842
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
843
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
844
        sixel_helper_set_additional_message(
×
845
            "terminal does not report pixel cell size for drcs option.");
846
        return SIXEL_BAD_ARGUMENT;
×
847
    }
848

849
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
850
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
851
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
852
        sixel_helper_set_additional_message(
×
853
            "terminal cell size reported zero via ioctl().");
854
        return SIXEL_BAD_ARGUMENT;
×
855
    }
856

857
    return SIXEL_OK;
858
#else
859
    (void) encoder;
860
    sixel_helper_set_additional_message(
861
        "drcs option is not supported on this platform.");
862
    return SIXEL_NOT_IMPLEMENTED;
863
#endif
864
}
865

866

867
/* returns monochrome dithering context object */
868
static SIXELSTATUS
869
sixel_prepare_monochrome_palette(
12✔
870
    sixel_dither_t  /* out */ **dither,
871
     int            /* in */  finvert)
872
{
873
    SIXELSTATUS status = SIXEL_FALSE;
12✔
874

875
    if (finvert) {
12✔
876
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
3✔
877
    } else {
878
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
9✔
879
    }
880
    if (*dither == NULL) {
12!
881
        sixel_helper_set_additional_message(
×
882
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
883
        status = SIXEL_RUNTIME_ERROR;
×
884
        goto end;
×
885
    }
886

887
    status = SIXEL_OK;
888

889
end:
12✔
890
    return status;
12✔
891
}
892

893

894
static SIXELSTATUS
895
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
3✔
896
                                sixel_dither_t *dither,
897
                                unsigned char const *pixels,
898
                                size_t size,
899
                                int width,
900
                                int height,
901
                                int pixelformat,
902
                                int source_colorspace,
903
                                int colorspace)
904
{
905
    SIXELSTATUS status;
3✔
906
    int ncolors;
3✔
907
    size_t palette_bytes;
3✔
908
    unsigned char *new_pixels;
3✔
909
    unsigned char *new_palette;
3✔
910
    size_t capture_bytes;
3✔
911
    unsigned char const *capture_source;
3✔
912
    sixel_index_t *paletted_pixels;
3✔
913
    size_t quantized_pixels;
3✔
914
    sixel_allocator_t *dither_allocator;
3✔
915
    int saved_pixelformat;
3✔
916
    int restore_pixelformat;
3✔
917

918
    /*
919
     * Preserve the quantized frame for assessment observers.
920
     *
921
     *     +-----------------+     +---------------------+
922
     *     | quantized bytes | --> | encoder->capture_*  |
923
     *     +-----------------+     +---------------------+
924
     */
925

926
    status = SIXEL_OK;
3✔
927
    ncolors = 0;
3✔
928
    palette_bytes = 0;
3✔
929
    new_pixels = NULL;
3✔
930
    new_palette = NULL;
3✔
931
    capture_bytes = size;
3✔
932
    capture_source = pixels;
3✔
933
    paletted_pixels = NULL;
3✔
934
    quantized_pixels = 0;
3✔
935
    dither_allocator = NULL;
3✔
936

937
    if (encoder == NULL || pixels == NULL ||
3!
938
            (dither == NULL && size == 0)) {
3!
939
        sixel_helper_set_additional_message(
×
940
            "sixel_encoder_capture_quantized: invalid capture request.");
941
        return SIXEL_BAD_ARGUMENT;
×
942
    }
943

944
    if (!encoder->capture_quantized) {
3!
945
        return SIXEL_OK;
946
    }
947

948
    saved_pixelformat = SIXEL_PIXELFORMAT_RGB888;
3✔
949
    restore_pixelformat = 0;
3✔
950
    if (dither != NULL) {
3!
951
        dither_allocator = dither->allocator;
3✔
952
        saved_pixelformat = dither->pixelformat;
3✔
953
        restore_pixelformat = 1;
3✔
954
        if (width <= 0 || height <= 0) {
3!
955
            sixel_helper_set_additional_message(
×
956
                "sixel_encoder_capture_quantized: invalid dimensions.");
957
            status = SIXEL_BAD_ARGUMENT;
×
958
            goto cleanup;
×
959
        }
960
        quantized_pixels = (size_t)width * (size_t)height;
3✔
961
        if (height != 0 &&
3!
962
                quantized_pixels / (size_t)height != (size_t)width) {
3!
963
            sixel_helper_set_additional_message(
×
964
                "sixel_encoder_capture_quantized: image too large.");
965
            status = SIXEL_RUNTIME_ERROR;
×
966
            goto cleanup;
×
967
        }
968
        paletted_pixels = sixel_dither_apply_palette(
3✔
969
            dither, (unsigned char *)pixels, width, height);
970
        if (paletted_pixels == NULL) {
3!
971
            sixel_helper_set_additional_message(
×
972
                "sixel_encoder_capture_quantized: palette conversion failed.");
973
            status = SIXEL_RUNTIME_ERROR;
×
974
            goto cleanup;
×
975
        }
976
        capture_source = (unsigned char const *)paletted_pixels;
977
        capture_bytes = quantized_pixels;
978
    }
979

980
    if (capture_bytes > 0) {
3!
981
        if (encoder->capture_pixels == NULL ||
3!
982
                encoder->capture_pixels_size < capture_bytes) {
×
983
            new_pixels = (unsigned char *)sixel_allocator_malloc(
3✔
984
                encoder->allocator, capture_bytes);
985
            if (new_pixels == NULL) {
3!
986
                sixel_helper_set_additional_message(
×
987
                    "sixel_encoder_capture_quantized: "
988
                    "sixel_allocator_malloc() failed.");
989
                status = SIXEL_BAD_ALLOCATION;
×
990
                goto cleanup;
×
991
            }
992
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
3✔
993
            encoder->capture_pixels = new_pixels;
3✔
994
            encoder->capture_pixels_size = capture_bytes;
3✔
995
        }
996
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
3✔
997
    }
998
    encoder->capture_pixel_bytes = capture_bytes;
3✔
999

1000
    ncolors = 0;
3✔
1001
    palette_bytes = 0;
3✔
1002
    if (dither != NULL) {
3!
1003
        sixel_palette_t *palette_obj = NULL;
3✔
1004
        unsigned char *palette_copy = NULL;
3✔
1005
        size_t palette_count = 0U;
3✔
1006

1007
        status = sixel_dither_get_quantized_palette(dither, &palette_obj);
3✔
1008
        if (SIXEL_SUCCEEDED(status) && palette_obj != NULL) {
3!
1009
            status = sixel_palette_copy_entries_8bit(
3✔
1010
                palette_obj,
1011
                &palette_copy,
1012
                &palette_count,
1013
                SIXEL_PIXELFORMAT_RGB888,
1014
                encoder->allocator);
1015
            sixel_palette_unref(palette_obj);
3✔
1016
            palette_obj = NULL;
3✔
1017
            if (SIXEL_SUCCEEDED(status)
3!
1018
                    && palette_copy != NULL
3!
1019
                    && palette_count > 0U) {
3!
1020
                palette_bytes = palette_count * 3U;
3✔
1021
                ncolors = (int)palette_count;
3✔
1022
                if (encoder->capture_palette == NULL
3!
1023
                        || encoder->capture_palette_size < palette_bytes) {
×
1024
                    new_palette = (unsigned char *)sixel_allocator_malloc(
3✔
1025
                        encoder->allocator, palette_bytes);
1026
                    if (new_palette == NULL) {
3!
1027
                        sixel_helper_set_additional_message(
×
1028
                            "sixel_encoder_capture_quantized: "
1029
                            "sixel_allocator_malloc() failed.");
1030
                        status = SIXEL_BAD_ALLOCATION;
×
1031
                        sixel_allocator_free(encoder->allocator,
×
1032
                                             palette_copy);
1033
                        goto cleanup;
×
1034
                    }
1035
                    sixel_allocator_free(encoder->allocator,
3✔
1036
                                         encoder->capture_palette);
3✔
1037
                    encoder->capture_palette = new_palette;
3✔
1038
                    encoder->capture_palette_size = palette_bytes;
3✔
1039
                }
1040
                memcpy(encoder->capture_palette,
3!
1041
                       palette_copy,
1042
                       palette_bytes);
1043
                if (source_colorspace != colorspace) {
3!
1044
                    (void)sixel_helper_convert_colorspace(
×
1045
                        encoder->capture_palette,
1046
                        palette_bytes,
1047
                        SIXEL_PIXELFORMAT_RGB888,
1048
                        source_colorspace,
1049
                        colorspace);
1050
                }
1051
            }
1052
            if (palette_copy != NULL) {
3!
1053
                sixel_allocator_free(encoder->allocator, palette_copy);
3✔
1054
            }
1055
        }
1056
    }
1!
1057

1058
    encoder->capture_width = width;
3✔
1059
    encoder->capture_height = height;
3✔
1060
    if (dither != NULL) {
3!
1061
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
3✔
1062
    } else {
1063
        encoder->capture_pixelformat = pixelformat;
×
1064
    }
1065
    encoder->capture_colorspace = colorspace;
3✔
1066
    encoder->capture_palette_size = palette_bytes;
3✔
1067
    encoder->capture_ncolors = ncolors;
3✔
1068
    encoder->capture_valid = 1;
3✔
1069

1070
cleanup:
3✔
1071
    if (restore_pixelformat && dither != NULL) {
3!
1072
        /*
1073
         * Undo the normalization performed by sixel_dither_apply_palette().
1074
         *
1075
         *     RGBA8888 --capture--> RGB888 (temporary)
1076
         *          \______________________________/
1077
         *                          |
1078
         *                 restore original state for
1079
         *                 the real encoder execution.
1080
         */
1081
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
3✔
1082
    }
1083
    if (paletted_pixels != NULL && dither_allocator != NULL) {
3!
1084
        sixel_allocator_free(dither_allocator, paletted_pixels);
3✔
1085
    }
1086

1087
    return status;
1088
}
1089

1090
static SIXELSTATUS
1091
sixel_prepare_builtin_palette(
27✔
1092
    sixel_dither_t /* out */ **dither,
1093
    int            /* in */  builtin_palette)
1094
{
1095
    SIXELSTATUS status = SIXEL_FALSE;
27✔
1096

1097
    *dither = sixel_dither_get(builtin_palette);
27✔
1098
    if (*dither == NULL) {
27!
1099
        sixel_helper_set_additional_message(
×
1100
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
1101
        status = SIXEL_RUNTIME_ERROR;
×
1102
        goto end;
×
1103
    }
1104

1105
    status = SIXEL_OK;
1106

1107
end:
27✔
1108
    return status;
27✔
1109
}
1110

1111
static int
1112
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
480✔
1113
{
1114
    int width_hint;
480✔
1115
    int height_hint;
480✔
1116
    long base;
480✔
1117
    long size;
480✔
1118

1119
    width_hint = 0;
480✔
1120
    height_hint = 0;
480✔
1121
    base = 0;
480✔
1122
    size = 0;
480✔
1123

1124
    if (encoder == NULL) {
480!
1125
        return 0;
1126
    }
1127

1128
    width_hint = encoder->pixelwidth;
480✔
1129
    height_hint = encoder->pixelheight;
480✔
1130

1131
    /* Request extra resolution for downscaling to preserve detail. */
1132
    if (width_hint > 0 && height_hint > 0) {
480✔
1133
        /* Follow the CLI rule: double the larger axis before doubling
1134
         * again for the final request size. */
1135
        if (width_hint >= height_hint) {
15!
1136
            base = (long)width_hint;
1137
        } else {
1138
            base = (long)height_hint;
1139
        }
1140
        base *= 2L;
15✔
1141
    } else if (width_hint > 0) {
465✔
1142
        base = (long)width_hint;
51✔
1143
    } else if (height_hint > 0) {
414✔
1144
        base = (long)height_hint;
36✔
1145
    } else {
1146
        return 0;
1147
    }
1148

1149
    size = base * 2L;
102✔
1150
    if (size > (long)INT_MAX) {
102!
1151
        size = (long)INT_MAX;
1152
    }
1153
    if (size < 1L) {
1!
1154
        size = 1L;
1155
    }
1156

1157
    return (int)size;
102✔
1158
}
1159

1160

1161
typedef struct sixel_callback_context_for_mapfile {
1162
    int reqcolors;
1163
    sixel_dither_t *dither;
1164
    sixel_allocator_t *allocator;
1165
    int working_colorspace;
1166
    int lut_policy;
1167
    int prefer_float32;
1168
} sixel_callback_context_for_mapfile_t;
1169

1170

1171
/* callback function for sixel_helper_load_image_file() */
1172
static SIXELSTATUS
1173
load_image_callback_for_palette(
21✔
1174
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1175
    void            /* in */    *data)  /* private data */
1176
{
1177
    SIXELSTATUS status = SIXEL_FALSE;
21✔
1178
    sixel_callback_context_for_mapfile_t *callback_context;
21✔
1179

1180
    /* get callback context object from the private data */
1181
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
1182

1183
    status = sixel_frame_set_pixelformat(
21✔
1184
        frame,
1185
        sixel_encoder_pixelformat_for_colorspace(
1186
            callback_context->working_colorspace,
1187
            callback_context->prefer_float32));
1188
    if (SIXEL_FAILED(status)) {
21!
1189
        goto end;
×
1190
    }
1191

1192
    switch (sixel_frame_get_pixelformat(frame)) {
21!
1193
    case SIXEL_PIXELFORMAT_PAL1:
×
1194
    case SIXEL_PIXELFORMAT_PAL2:
1195
    case SIXEL_PIXELFORMAT_PAL4:
1196
    case SIXEL_PIXELFORMAT_PAL8:
1197
        if (sixel_frame_get_palette(frame) == NULL) {
×
1198
            status = SIXEL_LOGIC_ERROR;
×
1199
            goto end;
×
1200
        }
1201
        /* create new dither object */
1202
        status = sixel_dither_new(
×
1203
            &callback_context->dither,
1204
            sixel_frame_get_ncolors(frame),
1205
            callback_context->allocator);
1206
        if (SIXEL_FAILED(status)) {
×
1207
            goto end;
×
1208
        }
1209

1210
        sixel_dither_set_lut_policy(callback_context->dither,
×
1211
                                    callback_context->lut_policy);
1212

1213
        /* use palette which is extracted from the image */
1214
        sixel_dither_set_palette(callback_context->dither,
×
1215
                                 sixel_frame_get_palette(frame));
1216
        /* success */
1217
        status = SIXEL_OK;
×
1218
        break;
×
1219
    case SIXEL_PIXELFORMAT_G1:
×
1220
        /* use 1bpp grayscale builtin palette */
1221
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1222
        /* success */
1223
        status = SIXEL_OK;
×
1224
        break;
×
1225
    case SIXEL_PIXELFORMAT_G2:
×
1226
        /* use 2bpp grayscale builtin palette */
1227
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1228
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
1229
        /* success */
1230
        status = SIXEL_OK;
×
1231
        break;
×
1232
    case SIXEL_PIXELFORMAT_G4:
×
1233
        /* use 4bpp grayscale builtin palette */
1234
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
1235
        /* success */
1236
        status = SIXEL_OK;
×
1237
        break;
×
1238
    case SIXEL_PIXELFORMAT_G8:
×
1239
        /* use 8bpp grayscale builtin palette */
1240
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
1241
        /* success */
1242
        status = SIXEL_OK;
×
1243
        break;
×
1244
    default:
21✔
1245
        /* create new dither object */
1246
        status = sixel_dither_new(
21✔
1247
            &callback_context->dither,
1248
            callback_context->reqcolors,
1249
            callback_context->allocator);
1250
        if (SIXEL_FAILED(status)) {
21!
1251
            goto end;
×
1252
        }
1253

1254
        sixel_dither_set_lut_policy(callback_context->dither,
21✔
1255
                                    callback_context->lut_policy);
1256

1257
        /* create adaptive palette from given frame object */
1258
        status = sixel_dither_initialize(callback_context->dither,
21✔
1259
                                         sixel_frame_get_pixels(frame),
1260
                                         sixel_frame_get_width(frame),
1261
                                         sixel_frame_get_height(frame),
1262
                                         sixel_frame_get_pixelformat(frame),
1263
                                         SIXEL_LARGE_NORM,
1264
                                         SIXEL_REP_CENTER_BOX,
1265
                                         SIXEL_QUALITY_HIGH);
1266
        if (SIXEL_FAILED(status)) {
21!
1267
            sixel_dither_unref(callback_context->dither);
×
1268
            goto end;
×
1269
        }
1270

1271
        /* success */
1272
        status = SIXEL_OK;
1273

1274
        break;
1275
    }
1276

1277
end:
21✔
1278
    return status;
21✔
1279
}
1280

1281

1282
static SIXELSTATUS
1283
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1284

1285

1286
static int
1287
sixel_path_has_extension(char const *path, char const *extension)
63✔
1288
{
1289
    size_t path_len;
63✔
1290
    size_t ext_len;
63✔
1291
    size_t index;
63✔
1292

1293
    path_len = 0u;
63✔
1294
    ext_len = 0u;
63✔
1295
    index = 0u;
63✔
1296

1297
    if (path == NULL || extension == NULL) {
63!
1298
        return 0;
1299
    }
1300

1301
    path_len = strlen(path);
63✔
1302
    ext_len = strlen(extension);
63✔
1303
    if (ext_len == 0u || path_len < ext_len) {
63!
1304
        return 0;
1305
    }
1306

1307
    for (index = 0u; index < ext_len; ++index) {
144!
1308
        unsigned char path_ch;
144✔
1309
        unsigned char ext_ch;
144✔
1310

1311
        path_ch = (unsigned char)path[path_len - ext_len + index];
144✔
1312
        ext_ch = (unsigned char)extension[index];
144✔
1313
        if (tolower(path_ch) != tolower(ext_ch)) {
144✔
1314
            return 0;
1315
        }
1316
    }
2✔
1317

1318
    return 1;
1319
}
1320

1321
typedef enum sixel_palette_format {
1322
    SIXEL_PALETTE_FORMAT_NONE = 0,
1323
    SIXEL_PALETTE_FORMAT_ACT,
1324
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1325
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1326
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1327
    SIXEL_PALETTE_FORMAT_GPL
1328
} sixel_palette_format_t;
1329

1330
/*
1331
 * Palette specification parser
1332
 *
1333
 *   TYPE:PATH  -> explicit format prefix
1334
 *   PATH       -> rely on extension or heuristics
1335
 *
1336
 * The ASCII diagram below shows how the prefix is peeled:
1337
 *
1338
 *   [type] : [path]
1339
 *    ^-- left part selects decoder/encoder when present.
1340
 */
1341
static char const *
1342
sixel_palette_strip_prefix(char const *spec,
48✔
1343
                           sixel_palette_format_t *format_hint)
1344
{
1345
    char const *colon;
48✔
1346
    size_t type_len;
48✔
1347
    size_t index;
48✔
1348
    char lowered[16];
48✔
1349

1350
    colon = NULL;
48✔
1351
    type_len = 0u;
48✔
1352
    index = 0u;
48✔
1353

1354
    if (format_hint != NULL) {
48✔
1355
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
1356
    }
1357
    if (spec == NULL) {
48!
1358
        return NULL;
1359
    }
1360

1361
    colon = strchr(spec, ':');
48✔
1362
    if (colon == NULL) {
48!
1363
        return spec;
1364
    }
1365

1366
    type_len = (size_t)(colon - spec);
×
1367
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1368
        return spec;
1369
    }
1370

1371
    for (index = 0u; index < type_len; ++index) {
×
1372
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1373
    }
1374
    lowered[type_len] = '\0';
×
1375

1376
    if (strcmp(lowered, "act") == 0) {
×
1377
        if (format_hint != NULL) {
×
1378
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
×
1379
        }
1380
        return colon + 1;
×
1381
    }
1382
    if (strcmp(lowered, "pal") == 0) {
×
1383
        if (format_hint != NULL) {
×
1384
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1385
        }
1386
        return colon + 1;
×
1387
    }
1388
    if (strcmp(lowered, "pal-jasc") == 0) {
×
1389
        if (format_hint != NULL) {
×
1390
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1391
        }
1392
        return colon + 1;
×
1393
    }
1394
    if (strcmp(lowered, "pal-riff") == 0) {
×
1395
        if (format_hint != NULL) {
×
1396
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1397
        }
1398
        return colon + 1;
×
1399
    }
1400
    if (strcmp(lowered, "gpl") == 0) {
×
1401
        if (format_hint != NULL) {
×
1402
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
×
1403
        }
1404
        return colon + 1;
×
1405
    }
1406

1407
    return spec;
1408
}
1409

1410
static sixel_palette_format_t
1411
sixel_palette_format_from_extension(char const *path)
21✔
1412
{
1413
    if (path == NULL) {
21!
1414
        return SIXEL_PALETTE_FORMAT_NONE;
1415
    }
1416

1417
    if (sixel_path_has_extension(path, ".act")) {
21!
1418
        return SIXEL_PALETTE_FORMAT_ACT;
1419
    }
1420
    if (sixel_path_has_extension(path, ".pal")) {
21!
1421
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
1422
    }
1423
    if (sixel_path_has_extension(path, ".gpl")) {
21!
1424
        return SIXEL_PALETTE_FORMAT_GPL;
×
1425
    }
1426

1427
    return SIXEL_PALETTE_FORMAT_NONE;
1428
}
1429

1430
static int
1431
sixel_path_has_any_extension(char const *path)
21✔
1432
{
1433
    char const *slash_forward;
21✔
1434
#if defined(_WIN32)
1435
    char const *slash_backward;
1436
#endif
1437
    char const *start;
21✔
1438
    char const *dot;
21✔
1439

1440
    slash_forward = NULL;
21✔
1441
#if defined(_WIN32)
1442
    slash_backward = NULL;
1443
#endif
1444
    start = path;
21✔
1445
    dot = NULL;
21✔
1446

1447
    if (path == NULL) {
21!
1448
        return 0;
1449
    }
1450

1451
    slash_forward = strrchr(path, '/');
21✔
1452
#if defined(_WIN32)
1453
    slash_backward = strrchr(path, '\\');
1454
    if (slash_backward != NULL &&
1455
            (slash_forward == NULL || slash_backward > slash_forward)) {
1456
        slash_forward = slash_backward;
1457
    }
1458
#endif
1459
    if (slash_forward == NULL) {
21!
1460
        start = path;
1461
    } else {
1462
        start = slash_forward + 1;
21✔
1463
    }
1464

1465
    dot = strrchr(start, '.');
21✔
1466
    if (dot == NULL) {
21!
1467
        return 0;
1468
    }
1469

1470
    if (dot[1] == '\0') {
21!
1471
        return 0;
×
1472
    }
1473

1474
    return 1;
1475
}
1476

1477
static int
1478
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1479
{
1480
    if (data == NULL || size < 3u) {
×
1481
        return 0;
1482
    }
1483
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
1484
        return 1;
×
1485
    }
1486
    return 0;
1487
}
1488

1489

1490
/*
1491
 * Materialize palette bytes from a stream.
1492
 *
1493
 * The flow looks like:
1494
 *
1495
 *   stream --> [scratch buffer] --> [resizable heap buffer]
1496
 *                  ^ looped read        ^ returned payload
1497
 */
1498
static SIXELSTATUS
1499
sixel_palette_read_stream(FILE *stream,
×
1500
                          sixel_allocator_t *allocator,
1501
                          unsigned char **pdata,
1502
                          size_t *psize)
1503
{
1504
    SIXELSTATUS status;
×
1505
    unsigned char *buffer;
×
1506
    unsigned char *grown;
×
1507
    size_t capacity;
×
1508
    size_t used;
×
1509
    size_t read_bytes;
×
1510
    size_t needed;
×
1511
    size_t new_capacity;
×
1512
    unsigned char scratch[4096];
×
1513

1514
    status = SIXEL_FALSE;
×
1515
    buffer = NULL;
×
1516
    grown = NULL;
×
1517
    capacity = 0u;
×
1518
    used = 0u;
×
1519
    read_bytes = 0u;
×
1520
    needed = 0u;
×
1521
    new_capacity = 0u;
×
1522

1523
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1524
        sixel_helper_set_additional_message(
×
1525
            "sixel_palette_read_stream: invalid argument.");
1526
        return SIXEL_BAD_ARGUMENT;
×
1527
    }
1528

1529
    *pdata = NULL;
×
1530
    *psize = 0u;
×
1531

1532
    while (1) {
×
1533
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1534
        if (read_bytes == 0u) {
×
1535
            if (ferror(stream)) {
×
1536
                sixel_helper_set_additional_message(
×
1537
                    "sixel_palette_read_stream: fread() failed.");
1538
                status = SIXEL_LIBC_ERROR;
×
1539
                goto cleanup;
×
1540
            }
1541
            break;
×
1542
        }
1543

1544
        if (used > SIZE_MAX - read_bytes) {
×
1545
            sixel_helper_set_additional_message(
×
1546
                "sixel_palette_read_stream: size overflow.");
1547
            status = SIXEL_BAD_ALLOCATION;
×
1548
            goto cleanup;
×
1549
        }
1550
        needed = used + read_bytes;
×
1551

1552
        if (needed > capacity) {
×
1553
            new_capacity = capacity;
×
1554
            if (new_capacity == 0u) {
×
1555
                new_capacity = 4096u;
×
1556
            }
1557
            while (needed > new_capacity) {
×
1558
                if (new_capacity > SIZE_MAX / 2u) {
×
1559
                    sixel_helper_set_additional_message(
×
1560
                        "sixel_palette_read_stream: size overflow.");
1561
                    status = SIXEL_BAD_ALLOCATION;
×
1562
                    goto cleanup;
×
1563
                }
1564
                new_capacity *= 2u;
×
1565
            }
1566

1567
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1568
                                                             new_capacity);
1569
            if (grown == NULL) {
×
1570
                sixel_helper_set_additional_message(
×
1571
                    "sixel_palette_read_stream: allocation failed.");
1572
                status = SIXEL_BAD_ALLOCATION;
×
1573
                goto cleanup;
×
1574
            }
1575

1576
            if (buffer != NULL) {
×
1577
                memcpy(grown, buffer, used);
×
1578
                sixel_allocator_free(allocator, buffer);
×
1579
            }
1580

1581
            buffer = grown;
1582
            grown = NULL;
1583
            capacity = new_capacity;
1584
        }
1585

1586
        memcpy(buffer + used, scratch, read_bytes);
×
1587
        used += read_bytes;
×
1588
    }
1589

1590
    *pdata = buffer;
×
1591
    *psize = used;
×
1592
    status = SIXEL_OK;
×
1593
    return status;
×
1594

1595
cleanup:
×
1596
    if (grown != NULL) {
×
1597
        sixel_allocator_free(allocator, grown);
1598
    }
1599
    if (buffer != NULL) {
×
1600
        sixel_allocator_free(allocator, buffer);
×
1601
    }
1602
    return status;
1603
}
1604

1605

1606
static SIXELSTATUS
1607
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
×
1608
{
1609
    int error_value;
×
1610
    char error_message[256];
×
1611
    char strerror_buffer[128];
×
1612
#if HAVE_SYS_STAT_H
1613
    struct stat path_stat;
×
1614
#endif
1615

1616
    if (pstream == NULL || pclose == NULL || path == NULL) {
×
1617
        sixel_helper_set_additional_message(
×
1618
            "sixel_palette_open_read: invalid argument.");
1619
        return SIXEL_BAD_ARGUMENT;
×
1620
    }
1621

1622
    error_value = 0;
×
1623
    error_message[0] = '\0';
×
1624

1625
    if (strcmp(path, "-") == 0) {
×
1626
        *pstream = stdin;
×
1627
        *pclose = 0;
×
1628
        return SIXEL_OK;
×
1629
    }
1630

1631
#if HAVE_SYS_STAT_H
1632
    if (stat(path, &path_stat) == 0 && S_ISDIR(path_stat.st_mode)) {
×
1633
        sixel_compat_snprintf(error_message,
×
1634
                              sizeof(error_message),
1635
                              "sixel_palette_open_read: mapfile \"%s\" "
1636
                              "is a directory.",
1637
                              path);
1638
        sixel_helper_set_additional_message(error_message);
×
1639
        return SIXEL_BAD_INPUT;
×
1640
    }
1641
#endif
1642

1643
    errno = 0;
×
1644
    *pstream = sixel_compat_fopen(path, "rb");
×
1645
    if (*pstream == NULL) {
×
1646
        error_value = errno;
×
1647
        sixel_compat_snprintf(error_message,
×
1648
                              sizeof(error_message),
1649
                              "sixel_palette_open_read: failed to open "
1650
                              "\"%s\": %s.",
1651
                              path,
1652
                              sixel_compat_strerror(error_value,
1653
                                                    strerror_buffer,
1654
                                                    sizeof(strerror_buffer)));
1655
        sixel_helper_set_additional_message(error_message);
×
1656
        return SIXEL_LIBC_ERROR;
×
1657
    }
1658

1659
    *pclose = 1;
×
1660
    return SIXEL_OK;
×
1661
}
1662

1663

1664
static void
1665
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1666
{
1667
    if (close_stream && stream != NULL) {
×
1668
        (void) fclose(stream);
×
1669
    }
1670
}
×
1671

1672

1673
static sixel_palette_format_t
1674
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1675
{
1676
    size_t offset;
×
1677
    size_t data_size;
×
1678

1679
    offset = 0u;
×
1680
    data_size = size;
×
1681

1682
    if (data == NULL || size == 0u) {
×
1683
        return SIXEL_PALETTE_FORMAT_NONE;
1684
    }
1685

1686
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
1687
        return SIXEL_PALETTE_FORMAT_ACT;
1688
    }
1689

1690
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
1691
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
1692
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
1693
    }
1694

1695
    if (sixel_palette_has_utf8_bom(data, size)) {
×
1696
        offset = 3u;
×
1697
        data_size = size - 3u;
×
1698
    }
1699

1700
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
×
1701
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
1702
    }
1703
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
×
1704
        return SIXEL_PALETTE_FORMAT_GPL;
×
1705
    }
1706

1707
    return SIXEL_PALETTE_FORMAT_NONE;
1708
}
1709

1710

1711
static unsigned int
1712
sixel_palette_read_le16(unsigned char const *ptr)
×
1713
{
1714
    if (ptr == NULL) {
×
1715
        return 0u;
1716
    }
1717
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1718
}
1719

1720

1721
static unsigned int
1722
sixel_palette_read_le32(unsigned char const *ptr)
×
1723
{
1724
    if (ptr == NULL) {
×
1725
        return 0u;
1726
    }
1727
    return ((unsigned int)ptr[0])
×
1728
        | ((unsigned int)ptr[1] << 8)
×
1729
        | ((unsigned int)ptr[2] << 16)
×
1730
        | ((unsigned int)ptr[3] << 24);
×
1731
}
1732

1733

1734
/*
1735
 * Adobe Color Table (*.act) reader
1736
 *
1737
 *   +-----------+---------------------------+
1738
 *   | section   | bytes                     |
1739
 *   +-----------+---------------------------+
1740
 *   | palette   | 256 entries * 3 RGB bytes |
1741
 *   | trailer   | optional count/start pair |
1742
 *   +-----------+---------------------------+
1743
 */
1744
static SIXELSTATUS
1745
sixel_palette_parse_act(unsigned char const *data,
×
1746
                        size_t size,
1747
                        sixel_encoder_t *encoder,
1748
                        sixel_dither_t **dither)
1749
{
1750
    SIXELSTATUS status;
×
1751
    sixel_dither_t *local;
×
1752
    unsigned char const *palette_start;
×
1753
    unsigned char const *trailer;
×
1754
    sixel_palette_t *palette_obj;
×
1755
    int exported_colors;
×
1756
    int start_index;
×
1757

1758
    status = SIXEL_FALSE;
×
1759
    local = NULL;
×
1760
    palette_start = data;
×
1761
    trailer = NULL;
×
1762
    palette_obj = NULL;
×
1763
    exported_colors = 0;
×
1764
    start_index = 0;
×
1765

1766
    if (encoder == NULL || dither == NULL) {
×
1767
        sixel_helper_set_additional_message(
×
1768
            "sixel_palette_parse_act: invalid argument.");
1769
        return SIXEL_BAD_ARGUMENT;
×
1770
    }
1771
    if (data == NULL || size < 256u * 3u) {
×
1772
        sixel_helper_set_additional_message(
×
1773
            "sixel_palette_parse_act: truncated ACT palette.");
1774
        return SIXEL_BAD_INPUT;
×
1775
    }
1776

1777
    if (size == 256u * 3u) {
×
1778
        exported_colors = 256;
1779
        start_index = 0;
1780
    } else if (size == 256u * 3u + 4u) {
×
1781
        trailer = data + 256u * 3u;
×
1782
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
×
1783
                                | (unsigned int)trailer[1]);
×
1784
        start_index = (int)(((unsigned int)trailer[2] << 8)
×
1785
                            | (unsigned int)trailer[3]);
×
1786
    } else {
1787
        sixel_helper_set_additional_message(
×
1788
            "sixel_palette_parse_act: invalid ACT length.");
1789
        return SIXEL_BAD_INPUT;
×
1790
    }
1791

1792
    if (start_index < 0 || start_index >= 256) {
×
1793
        sixel_helper_set_additional_message(
×
1794
            "sixel_palette_parse_act: ACT start index out of range.");
1795
        return SIXEL_BAD_INPUT;
×
1796
    }
1797
    if (exported_colors <= 0 || exported_colors > 256) {
×
1798
        exported_colors = 256;
×
1799
    }
1800
    if (start_index + exported_colors > 256) {
×
1801
        sixel_helper_set_additional_message(
×
1802
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
1803
        return SIXEL_BAD_INPUT;
×
1804
    }
1805

1806
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
1807
    if (SIXEL_FAILED(status)) {
×
1808
        return status;
1809
    }
1810

1811
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1812

1813
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
1814
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
1815
        sixel_dither_unref(local);
×
1816
        return status;
×
1817
    }
1818
    status = sixel_palette_set_entries(
×
1819
        palette_obj,
1820
        palette_start + (size_t)start_index * 3u,
×
1821
        (unsigned int)exported_colors,
1822
        3,
1823
        encoder->allocator);
1824
    sixel_palette_unref(palette_obj);
×
1825
    if (SIXEL_FAILED(status)) {
×
1826
        sixel_dither_unref(local);
×
1827
        return status;
×
1828
    }
1829

1830
    *dither = local;
×
1831
    return SIXEL_OK;
×
1832
}
1833

1834

1835
static SIXELSTATUS
1836
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1837
                             size_t size,
1838
                             sixel_encoder_t *encoder,
1839
                             sixel_dither_t **dither)
1840
{
1841
    SIXELSTATUS status;
×
1842
    char *text;
×
1843
    size_t index;
×
1844
    size_t offset;
×
1845
    char *cursor;
×
1846
    char *line;
×
1847
    char *line_end;
×
1848
    int stage;
×
1849
    int exported_colors;
×
1850
    int parsed_colors;
×
1851
    sixel_dither_t *local;
×
1852
    sixel_palette_t *palette_obj;
×
1853
    unsigned char *palette_buffer;
×
1854
    long component;
×
1855
    char *parse_end;
×
1856
    int value_index;
×
1857
    int values[3];
×
1858
    char tail;
×
1859

1860
    status = SIXEL_FALSE;
×
1861
    text = NULL;
×
1862
    index = 0u;
×
1863
    offset = 0u;
×
1864
    cursor = NULL;
×
1865
    line = NULL;
×
1866
    line_end = NULL;
×
1867
    stage = 0;
×
1868
    exported_colors = 0;
×
1869
    parsed_colors = 0;
×
1870
    local = NULL;
×
1871
    palette_obj = NULL;
×
1872
    palette_buffer = NULL;
×
1873
    component = 0;
×
1874
    parse_end = NULL;
×
1875
    value_index = 0;
×
1876
    values[0] = 0;
×
1877
    values[1] = 0;
×
1878
    values[2] = 0;
×
1879

1880
    if (encoder == NULL || dither == NULL) {
×
1881
        sixel_helper_set_additional_message(
×
1882
            "sixel_palette_parse_pal_jasc: invalid argument.");
1883
        return SIXEL_BAD_ARGUMENT;
×
1884
    }
1885
    if (data == NULL || size == 0u) {
×
1886
        sixel_helper_set_additional_message(
×
1887
            "sixel_palette_parse_pal_jasc: empty palette.");
1888
        return SIXEL_BAD_INPUT;
×
1889
    }
1890

1891
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
1892
    if (text == NULL) {
×
1893
        sixel_helper_set_additional_message(
×
1894
            "sixel_palette_parse_pal_jasc: allocation failed.");
1895
        return SIXEL_BAD_ALLOCATION;
×
1896
    }
1897
    memcpy(text, data, size);
×
1898
    text[size] = '\0';
×
1899

1900
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1901
        offset = 3u;
×
1902
    }
1903
    cursor = text + offset;
×
1904

1905
    while (*cursor != '\0') {
×
1906
        line = cursor;
×
1907
        line_end = cursor;
1908
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1909
            ++line_end;
×
1910
        }
1911
        if (*line_end != '\0') {
×
1912
            *line_end = '\0';
×
1913
            cursor = line_end + 1;
×
1914
        } else {
1915
            cursor = line_end;
1916
        }
1917
        while (*cursor == '\n' || *cursor == '\r') {
×
1918
            ++cursor;
×
1919
        }
1920

1921
        while (*line == ' ' || *line == '\t') {
×
1922
            ++line;
×
1923
        }
1924
        index = strlen(line);
×
1925
        while (index > 0u) {
×
1926
            tail = line[index - 1];
×
1927
            if (tail != ' ' && tail != '\t') {
×
1928
                break;
1929
            }
1930
            line[index - 1] = '\0';
×
1931
            --index;
×
1932
        }
1933
        if (*line == '\0') {
×
1934
            continue;
×
1935
        }
1936
        if (*line == '#') {
×
1937
            continue;
×
1938
        }
1939

1940
        if (stage == 0) {
×
1941
            if (strcmp(line, "JASC-PAL") != 0) {
×
1942
                sixel_helper_set_additional_message(
×
1943
                    "sixel_palette_parse_pal_jasc: missing header.");
1944
                status = SIXEL_BAD_INPUT;
×
1945
                goto cleanup;
×
1946
            }
1947
            stage = 1;
×
1948
            continue;
×
1949
        }
1950
        if (stage == 1) {
×
1951
            stage = 2;
×
1952
            continue;
×
1953
        }
1954
        if (stage == 2) {
×
1955
            component = strtol(line, &parse_end, 10);
×
1956
            if (parse_end == line || component <= 0L || component > 256L) {
×
1957
                sixel_helper_set_additional_message(
×
1958
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1959
                status = SIXEL_BAD_INPUT;
×
1960
                goto cleanup;
×
1961
            }
1962
            exported_colors = (int)component;
×
1963
            if (exported_colors <= 0) {
×
1964
                sixel_helper_set_additional_message(
1965
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1966
                status = SIXEL_BAD_INPUT;
1967
                goto cleanup;
1968
            }
1969
            palette_buffer = (unsigned char *)sixel_allocator_malloc(
×
1970
                encoder->allocator,
1971
                (size_t)exported_colors * 3u);
×
1972
            if (palette_buffer == NULL) {
×
1973
                sixel_helper_set_additional_message(
×
1974
                    "sixel_palette_parse_pal_jasc: allocation failed.");
1975
                status = SIXEL_BAD_ALLOCATION;
×
1976
                goto cleanup;
×
1977
            }
1978
            status = sixel_dither_new(&local, exported_colors,
×
1979
                                      encoder->allocator);
1980
            if (SIXEL_FAILED(status)) {
×
1981
                goto cleanup;
×
1982
            }
1983
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1984
            stage = 3;
×
1985
            continue;
×
1986
        }
1987

1988
        value_index = 0;
1989
        while (value_index < 3) {
×
1990
            component = strtol(line, &parse_end, 10);
×
1991
            if (parse_end == line || component < 0L || component > 255L) {
×
1992
                sixel_helper_set_additional_message(
×
1993
                    "sixel_palette_parse_pal_jasc: invalid component.");
1994
                status = SIXEL_BAD_INPUT;
×
1995
                goto cleanup;
×
1996
            }
1997
            values[value_index] = (int)component;
×
1998
            ++value_index;
×
1999
            line = parse_end;
×
2000
            while (*line == ' ' || *line == '\t') {
×
2001
                ++line;
×
2002
            }
2003
        }
2004

2005
        if (parsed_colors >= exported_colors) {
×
2006
            sixel_helper_set_additional_message(
×
2007
                "sixel_palette_parse_pal_jasc: excess entries.");
2008
            status = SIXEL_BAD_INPUT;
×
2009
            goto cleanup;
×
2010
        }
2011

2012
        palette_buffer[parsed_colors * 3 + 0] =
×
2013
            (unsigned char)values[0];
×
2014
        palette_buffer[parsed_colors * 3 + 1] =
×
2015
            (unsigned char)values[1];
×
2016
        palette_buffer[parsed_colors * 3 + 2] =
×
2017
            (unsigned char)values[2];
×
2018
        ++parsed_colors;
×
2019
    }
2020

2021
    if (stage < 3) {
×
2022
        sixel_helper_set_additional_message(
×
2023
            "sixel_palette_parse_pal_jasc: incomplete header.");
2024
        status = SIXEL_BAD_INPUT;
×
2025
        goto cleanup;
×
2026
    }
2027
    if (parsed_colors != exported_colors) {
×
2028
        sixel_helper_set_additional_message(
×
2029
            "sixel_palette_parse_pal_jasc: color count mismatch.");
2030
        status = SIXEL_BAD_INPUT;
×
2031
        goto cleanup;
×
2032
    }
2033

2034
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2035
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2036
        goto cleanup;
×
2037
    }
2038
    status = sixel_palette_set_entries(palette_obj,
×
2039
                                       palette_buffer,
2040
                                       (unsigned int)exported_colors,
2041
                                       3,
2042
                                       encoder->allocator);
2043
    sixel_palette_unref(palette_obj);
×
2044
    palette_obj = NULL;
×
2045
    if (SIXEL_FAILED(status)) {
×
2046
        goto cleanup;
×
2047
    }
2048

2049
    *dither = local;
×
2050
    status = SIXEL_OK;
×
2051

2052
cleanup:
×
2053
    if (palette_obj != NULL) {
×
2054
        sixel_palette_unref(palette_obj);
×
2055
    }
2056
    if (SIXEL_FAILED(status) && local != NULL) {
×
2057
        sixel_dither_unref(local);
×
2058
    }
2059
    if (palette_buffer != NULL) {
×
2060
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
2061
    }
2062
    if (text != NULL) {
×
2063
        sixel_allocator_free(encoder->allocator, text);
×
2064
    }
2065
    return status;
×
2066
}
2067

2068

2069
static SIXELSTATUS
2070
sixel_palette_parse_pal_riff(unsigned char const *data,
×
2071
                             size_t size,
2072
                             sixel_encoder_t *encoder,
2073
                             sixel_dither_t **dither)
2074
{
2075
    SIXELSTATUS status;
×
2076
    size_t offset;
×
2077
    size_t chunk_size;
×
2078
    sixel_dither_t *local;
×
2079
    sixel_palette_t *palette_obj;
×
2080
    unsigned char const *chunk;
×
2081
    unsigned char *palette_buffer;
×
2082
    unsigned int entry_count;
×
2083
    unsigned int version;
×
2084
    unsigned int index;
×
2085
    size_t palette_offset;
×
2086

2087
    status = SIXEL_FALSE;
×
2088
    offset = 0u;
×
2089
    chunk_size = 0u;
×
2090
    local = NULL;
×
2091
    chunk = NULL;
×
2092
    palette_obj = NULL;
×
2093
    palette_buffer = NULL;
×
2094
    entry_count = 0u;
×
2095
    version = 0u;
×
2096
    index = 0u;
×
2097
    palette_offset = 0u;
×
2098

2099
    if (encoder == NULL || dither == NULL) {
×
2100
        sixel_helper_set_additional_message(
×
2101
            "sixel_palette_parse_pal_riff: invalid argument.");
2102
        return SIXEL_BAD_ARGUMENT;
×
2103
    }
2104
    if (data == NULL || size < 12u) {
×
2105
        sixel_helper_set_additional_message(
×
2106
            "sixel_palette_parse_pal_riff: truncated palette.");
2107
        return SIXEL_BAD_INPUT;
×
2108
    }
2109
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
×
2110
        sixel_helper_set_additional_message(
×
2111
            "sixel_palette_parse_pal_riff: missing RIFF header.");
2112
        return SIXEL_BAD_INPUT;
×
2113
    }
2114

2115
    offset = 12u;
2116
    while (offset + 8u <= size) {
×
2117
        chunk = data + offset;
×
2118
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
×
2119
        if (offset + 8u + chunk_size > size) {
×
2120
            sixel_helper_set_additional_message(
×
2121
                "sixel_palette_parse_pal_riff: chunk extends past end.");
2122
            return SIXEL_BAD_INPUT;
×
2123
        }
2124
        if (memcmp(chunk, "data", 4) == 0) {
×
2125
            break;
2126
        }
2127
        offset += 8u + ((chunk_size + 1u) & ~1u);
×
2128
    }
2129

2130
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
2131
        sixel_helper_set_additional_message(
×
2132
            "sixel_palette_parse_pal_riff: missing data chunk.");
2133
        return SIXEL_BAD_INPUT;
×
2134
    }
2135

2136
    if (chunk_size < 4u) {
×
2137
        sixel_helper_set_additional_message(
×
2138
            "sixel_palette_parse_pal_riff: data chunk too small.");
2139
        return SIXEL_BAD_INPUT;
×
2140
    }
2141
    version = sixel_palette_read_le16(chunk + 8);
×
2142
    (void)version;
×
2143
    entry_count = sixel_palette_read_le16(chunk + 10);
×
2144
    if (entry_count == 0u || entry_count > 256u) {
×
2145
        sixel_helper_set_additional_message(
×
2146
            "sixel_palette_parse_pal_riff: invalid entry count.");
2147
        return SIXEL_BAD_INPUT;
×
2148
    }
2149
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
×
2150
        sixel_helper_set_additional_message(
×
2151
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
2152
        return SIXEL_BAD_INPUT;
×
2153
    }
2154

2155
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
×
2156
    if (SIXEL_FAILED(status)) {
×
2157
        return status;
2158
    }
2159
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2160
    palette_buffer = (unsigned char *)sixel_allocator_malloc(
×
2161
        encoder->allocator,
2162
        (size_t)entry_count * 3u);
×
2163
    if (palette_buffer == NULL) {
×
2164
        sixel_helper_set_additional_message(
×
2165
            "sixel_palette_parse_pal_riff: allocation failed.");
2166
        sixel_dither_unref(local);
×
2167
        return SIXEL_BAD_ALLOCATION;
×
2168
    }
2169
    palette_offset = 12u;
×
2170
    for (index = 0u; index < entry_count; ++index) {
×
2171
        palette_buffer[index * 3u + 0u] =
×
2172
            chunk[palette_offset + index * 4u + 0u];
×
2173
        palette_buffer[index * 3u + 1u] =
×
2174
            chunk[palette_offset + index * 4u + 1u];
×
2175
        palette_buffer[index * 3u + 2u] =
×
2176
            chunk[palette_offset + index * 4u + 2u];
×
2177
    }
2178

2179
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2180
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2181
        sixel_allocator_free(encoder->allocator, palette_buffer);
×
2182
        sixel_dither_unref(local);
×
2183
        return status;
×
2184
    }
2185
    status = sixel_palette_set_entries(palette_obj,
×
2186
                                       palette_buffer,
2187
                                       (unsigned int)entry_count,
2188
                                       3,
2189
                                       encoder->allocator);
2190
    sixel_palette_unref(palette_obj);
×
2191
    palette_obj = NULL;
×
2192
    sixel_allocator_free(encoder->allocator, palette_buffer);
×
2193
    palette_buffer = NULL;
×
2194
    if (SIXEL_FAILED(status)) {
×
2195
        sixel_dither_unref(local);
×
2196
        return status;
×
2197
    }
2198

2199
    *dither = local;
×
2200
    return SIXEL_OK;
×
2201
}
2202

2203

2204
static SIXELSTATUS
2205
sixel_palette_parse_gpl(unsigned char const *data,
×
2206
                        size_t size,
2207
                        sixel_encoder_t *encoder,
2208
                        sixel_dither_t **dither)
2209
{
2210
    SIXELSTATUS status;
×
2211
    char *text;
×
2212
    size_t offset;
×
2213
    char *cursor;
×
2214
    char *line;
×
2215
    char *line_end;
×
2216
    size_t index;
×
2217
    int header_seen;
×
2218
    int parsed_colors;
×
2219
    unsigned char palette_bytes[256 * 3];
×
2220
    long component;
×
2221
    char *parse_end;
×
2222
    int value_index;
×
2223
    int values[3];
×
2224
    sixel_dither_t *local;
×
2225
    sixel_palette_t *palette_obj;
×
2226
    char tail;
×
2227

2228
    status = SIXEL_FALSE;
×
2229
    text = NULL;
×
2230
    offset = 0u;
×
2231
    cursor = NULL;
×
2232
    line = NULL;
×
2233
    line_end = NULL;
×
2234
    index = 0u;
×
2235
    header_seen = 0;
×
2236
    parsed_colors = 0;
×
2237
    component = 0;
×
2238
    parse_end = NULL;
×
2239
    value_index = 0;
×
2240
    values[0] = 0;
×
2241
    values[1] = 0;
×
2242
    values[2] = 0;
×
2243
    local = NULL;
×
2244
    palette_obj = NULL;
×
2245

2246
    if (encoder == NULL || dither == NULL) {
×
2247
        sixel_helper_set_additional_message(
×
2248
            "sixel_palette_parse_gpl: invalid argument.");
2249
        return SIXEL_BAD_ARGUMENT;
×
2250
    }
2251
    if (data == NULL || size == 0u) {
×
2252
        sixel_helper_set_additional_message(
×
2253
            "sixel_palette_parse_gpl: empty palette.");
2254
        return SIXEL_BAD_INPUT;
×
2255
    }
2256

2257
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
2258
    if (text == NULL) {
×
2259
        sixel_helper_set_additional_message(
×
2260
            "sixel_palette_parse_gpl: allocation failed.");
2261
        return SIXEL_BAD_ALLOCATION;
×
2262
    }
2263
    memcpy(text, data, size);
×
2264
    text[size] = '\0';
×
2265

2266
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2267
        offset = 3u;
×
2268
    }
2269
    cursor = text + offset;
×
2270

2271
    while (*cursor != '\0') {
×
2272
        line = cursor;
×
2273
        line_end = cursor;
2274
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2275
            ++line_end;
×
2276
        }
2277
        if (*line_end != '\0') {
×
2278
            *line_end = '\0';
×
2279
            cursor = line_end + 1;
×
2280
        } else {
2281
            cursor = line_end;
2282
        }
2283
        while (*cursor == '\n' || *cursor == '\r') {
×
2284
            ++cursor;
×
2285
        }
2286

2287
        while (*line == ' ' || *line == '\t') {
×
2288
            ++line;
×
2289
        }
2290
        index = strlen(line);
×
2291
        while (index > 0u) {
×
2292
            tail = line[index - 1];
×
2293
            if (tail != ' ' && tail != '\t') {
×
2294
                break;
2295
            }
2296
            line[index - 1] = '\0';
×
2297
            --index;
×
2298
        }
2299
        if (*line == '\0') {
×
2300
            continue;
×
2301
        }
2302
        if (*line == '#') {
×
2303
            continue;
×
2304
        }
2305
        if (strncmp(line, "Name:", 5) == 0) {
×
2306
            continue;
×
2307
        }
2308
        if (strncmp(line, "Columns:", 8) == 0) {
×
2309
            continue;
×
2310
        }
2311

2312
        if (!header_seen) {
×
2313
            if (strcmp(line, "GIMP Palette") != 0) {
×
2314
                sixel_helper_set_additional_message(
×
2315
                    "sixel_palette_parse_gpl: missing header.");
2316
                status = SIXEL_BAD_INPUT;
×
2317
                goto cleanup;
×
2318
            }
2319
            header_seen = 1;
×
2320
            continue;
×
2321
        }
2322

2323
        if (parsed_colors >= 256) {
×
2324
            sixel_helper_set_additional_message(
×
2325
                "sixel_palette_parse_gpl: too many colors.");
2326
            status = SIXEL_BAD_INPUT;
×
2327
            goto cleanup;
×
2328
        }
2329

2330
        value_index = 0;
2331
        while (value_index < 3) {
×
2332
            component = strtol(line, &parse_end, 10);
×
2333
            if (parse_end == line || component < 0L || component > 255L) {
×
2334
                sixel_helper_set_additional_message(
×
2335
                    "sixel_palette_parse_gpl: invalid component.");
2336
                status = SIXEL_BAD_INPUT;
×
2337
                goto cleanup;
×
2338
            }
2339
            values[value_index] = (int)component;
×
2340
            ++value_index;
×
2341
            line = parse_end;
×
2342
            while (*line == ' ' || *line == '\t') {
×
2343
                ++line;
×
2344
            }
2345
        }
2346

2347
        palette_bytes[parsed_colors * 3 + 0] =
×
2348
            (unsigned char)values[0];
×
2349
        palette_bytes[parsed_colors * 3 + 1] =
×
2350
            (unsigned char)values[1];
×
2351
        palette_bytes[parsed_colors * 3 + 2] =
×
2352
            (unsigned char)values[2];
×
2353
        ++parsed_colors;
×
2354
    }
2355

2356
    if (!header_seen) {
×
2357
        sixel_helper_set_additional_message(
×
2358
            "sixel_palette_parse_gpl: header missing.");
2359
        status = SIXEL_BAD_INPUT;
×
2360
        goto cleanup;
×
2361
    }
2362
    if (parsed_colors <= 0) {
×
2363
        sixel_helper_set_additional_message(
×
2364
            "sixel_palette_parse_gpl: no colors parsed.");
2365
        status = SIXEL_BAD_INPUT;
×
2366
        goto cleanup;
×
2367
    }
2368

2369
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2370
    if (SIXEL_FAILED(status)) {
×
2371
        goto cleanup;
×
2372
    }
2373
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2374
    status = sixel_dither_get_quantized_palette(local, &palette_obj);
×
2375
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2376
        goto cleanup;
×
2377
    }
2378
    status = sixel_palette_set_entries(palette_obj,
×
2379
                                       palette_bytes,
2380
                                       (unsigned int)parsed_colors,
2381
                                       3,
2382
                                       encoder->allocator);
2383
    sixel_palette_unref(palette_obj);
×
2384
    palette_obj = NULL;
×
2385
    if (SIXEL_FAILED(status)) {
×
2386
        goto cleanup;
×
2387
    }
2388

2389
    *dither = local;
×
2390
    status = SIXEL_OK;
×
2391

2392
cleanup:
×
2393
    if (palette_obj != NULL) {
×
2394
        sixel_palette_unref(palette_obj);
×
2395
    }
2396
    if (SIXEL_FAILED(status) && local != NULL) {
×
2397
        sixel_dither_unref(local);
×
2398
    }
2399
    if (text != NULL) {
×
2400
        sixel_allocator_free(encoder->allocator, text);
×
2401
    }
2402
    return status;
×
2403
}
2404

2405

2406
/*
2407
 * Palette exporters
2408
 *
2409
 *   +----------+-------------------------+
2410
 *   | format   | emission strategy       |
2411
 *   +----------+-------------------------+
2412
 *   | ACT      | fixed 256 entries + EOF |
2413
 *   | PAL JASC | textual lines           |
2414
 *   | PAL RIFF | RIFF container          |
2415
 *   | GPL      | textual lines           |
2416
 *   +----------+-------------------------+
2417
 */
2418
static SIXELSTATUS
2419
sixel_palette_write_act(FILE *stream,
×
2420
                        unsigned char const *palette,
2421
                        int exported_colors)
2422
{
2423
    SIXELSTATUS status;
×
2424
    unsigned char act_table[256 * 3];
×
2425
    unsigned char trailer[4];
×
2426
    size_t exported_bytes;
×
2427

2428
    status = SIXEL_FALSE;
×
2429
    exported_bytes = 0u;
×
2430

2431
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2432
        return SIXEL_BAD_ARGUMENT;
2433
    }
2434
    if (exported_colors > 256) {
×
2435
        exported_colors = 256;
2436
    }
2437

2438
    memset(act_table, 0, sizeof(act_table));
×
2439
    exported_bytes = (size_t)exported_colors * 3u;
×
2440
    memcpy(act_table, palette, exported_bytes);
×
2441

2442
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2443
                                 & 0xffu);
2444
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2445
    trailer[2] = 0u;
×
2446
    trailer[3] = 0u;
×
2447

2448
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2449
            != sizeof(act_table)) {
2450
        status = SIXEL_LIBC_ERROR;
×
2451
        return status;
2452
    }
2453
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2454
            != sizeof(trailer)) {
2455
        status = SIXEL_LIBC_ERROR;
×
2456
        return status;
2457
    }
2458

2459
    return SIXEL_OK;
2460
}
2461

2462

2463
static SIXELSTATUS
2464
sixel_palette_write_pal_jasc(FILE *stream,
×
2465
                             unsigned char const *palette,
2466
                             int exported_colors)
2467
{
2468
    int index;
×
2469

2470
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2471
        return SIXEL_BAD_ARGUMENT;
2472
    }
2473
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
2474
        return SIXEL_LIBC_ERROR;
2475
    }
2476
    for (index = 0; index < exported_colors; ++index) {
×
2477
        if (fprintf(stream, "%d %d %d\n",
×
2478
                    (int)palette[index * 3 + 0],
×
2479
                    (int)palette[index * 3 + 1],
×
2480
                    (int)palette[index * 3 + 2]) < 0) {
×
2481
            return SIXEL_LIBC_ERROR;
2482
        }
2483
    }
2484
    return SIXEL_OK;
2485
}
2486

2487

2488
static SIXELSTATUS
2489
sixel_palette_write_pal_riff(FILE *stream,
×
2490
                             unsigned char const *palette,
2491
                             int exported_colors)
2492
{
2493
    unsigned char header[12];
×
2494
    unsigned char chunk[8];
×
2495
    unsigned char log_palette[4 + 256 * 4];
×
2496
    unsigned int data_size;
×
2497
    unsigned int riff_size;
×
2498
    int index;
×
2499

2500
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2501
        return SIXEL_BAD_ARGUMENT;
2502
    }
2503
    if (exported_colors > 256) {
×
2504
        exported_colors = 256;
2505
    }
2506

2507
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2508
    riff_size = 4u + 8u + data_size;
×
2509

2510
    memcpy(header, "RIFF", 4);
×
2511
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2512
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2513
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2514
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2515
    memcpy(header + 8, "PAL ", 4);
×
2516

2517
    memcpy(chunk, "data", 4);
×
2518
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2519
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2520
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2521
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2522

2523
    memset(log_palette, 0, sizeof(log_palette));
×
2524
    log_palette[0] = 0x00;
×
2525
    log_palette[1] = 0x03;
×
2526
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
2527
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
2528
    for (index = 0; index < exported_colors; ++index) {
×
2529
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
2530
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
2531
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
2532
        log_palette[4 + index * 4 + 3] = 0u;
×
2533
    }
2534

2535
    if (fwrite(header, 1, sizeof(header), stream)
×
2536
            != sizeof(header)) {
2537
        return SIXEL_LIBC_ERROR;
2538
    }
2539
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2540
        return SIXEL_LIBC_ERROR;
2541
    }
2542
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2543
            != (size_t)data_size) {
2544
        return SIXEL_LIBC_ERROR;
2545
    }
2546
    return SIXEL_OK;
2547
}
2548

2549

2550
static SIXELSTATUS
2551
sixel_palette_write_gpl(FILE *stream,
×
2552
                        unsigned char const *palette,
2553
                        int exported_colors)
2554
{
2555
    int index;
×
2556

2557
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2558
        return SIXEL_BAD_ARGUMENT;
2559
    }
2560
    if (fprintf(stream, "GIMP Palette\n") < 0) {
×
2561
        return SIXEL_LIBC_ERROR;
2562
    }
2563
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
×
2564
        return SIXEL_LIBC_ERROR;
2565
    }
2566
    if (fprintf(stream, "Columns: 16\n") < 0) {
×
2567
        return SIXEL_LIBC_ERROR;
2568
    }
2569
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
×
2570
        return SIXEL_LIBC_ERROR;
2571
    }
2572
    for (index = 0; index < exported_colors; ++index) {
×
2573
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
×
2574
                    (int)palette[index * 3 + 0],
×
2575
                    (int)palette[index * 3 + 1],
×
2576
                    (int)palette[index * 3 + 2],
×
2577
                    index) < 0) {
2578
            return SIXEL_LIBC_ERROR;
2579
        }
2580
    }
2581
    return SIXEL_OK;
2582
}
2583

2584

2585
static int
2586
sixel_encoder_parse_sample_target(char const *text, size_t *value_out)
×
2587
{
2588
    char *endptr;
×
2589
    unsigned long long parsed;
×
2590

2591
    endptr = NULL;
×
2592
    parsed = 0ull;
×
2593

2594
    if (text == NULL || value_out == NULL) {
×
2595
        return 0;
2596
    }
2597

2598
    errno = 0;
×
2599
    parsed = strtoull(text, &endptr, 10);
×
2600
    if (errno == ERANGE || parsed == 0ull) {
×
2601
        return 0;
2602
    }
2603
    if (endptr == text || *endptr != '\0') {
×
2604
        return 0;
2605
    }
2606

2607
    *value_out = (size_t)parsed;
×
2608
    if ((unsigned long long)(*value_out) != parsed) {
×
2609
        return 0;
2610
    }
2611

2612
    return 1;
2613
}
2614

2615

2616
static void
2617
sixel_encoding_planner_init(sixel_encoding_planner_t *planner)
1,128✔
2618
{
2619
    if (planner == NULL) {
1,128!
2620
        return;
2621
    }
2622

2623
    planner->total_threads = 1;
1,128✔
2624
    planner->main_threads = 1;
1,128✔
2625
    planner->palette_threads = 0;
1,128✔
2626
    planner->allow_palette_async = 0;
1,128✔
2627
    planner->clip_active = 0;
1,128✔
2628
    planner->scale_active = 0;
1,128✔
2629
    planner->colorspace_active = 0;
1,128✔
2630
    planner->heavy_ops = 0;
1,128✔
2631
}
2632

2633

2634
static void
2635
sixel_encoding_planner_plan(sixel_encoding_planner_t *planner,
552✔
2636
                            sixel_encoder_t *encoder,
2637
                            sixel_frame_t *frame)
2638
{
2639
    int total;
552✔
2640
    int budget;
552✔
2641
    int source_colorspace;
552✔
2642

2643
    if (planner == NULL || encoder == NULL || frame == NULL) {
552!
2644
        return;
2645
    }
2646

2647
    /*
2648
     * Keep palette sampling from spawning extra threads when resize/clip or
2649
     * full-frame colorspace conversion already occupy the available workers.
2650
     * Heavy steps consume the budget; the palette worker runs only when at
2651
     * least one spare thread remains after accounting for them.
2652
     */
2653
    total = sixel_threads_resolve();
552✔
2654
    planner->total_threads = total;
552✔
2655
    planner->palette_threads = 0;
552✔
2656
    planner->allow_palette_async = 0;
552✔
2657
    planner->main_threads = (total > 0) ? total : 1;
552!
2658
    planner->clip_active = (encoder->clipwidth > 0 && encoder->clipheight > 0)
1,119✔
2659
        ? 1
2660
        : 0;
552!
2661
    planner->scale_active = (encoder->pixelwidth >= 0
1,104✔
2662
                            || encoder->pixelheight >= 0
492✔
2663
                            || encoder->percentwidth >= 0
456✔
2664
                            || encoder->percentheight >= 0)
450✔
2665
        ? 1
2666
        : 0;
1,002✔
2667
    source_colorspace = sixel_frame_get_colorspace(frame);
552✔
2668
    planner->colorspace_active = (encoder->working_colorspace
552✔
2669
                                  != source_colorspace)
2670
        ? 1
2671
        : 0;
552✔
2672

2673
    planner->heavy_ops = planner->clip_active
552✔
2674
        + planner->scale_active
552✔
2675
        + planner->colorspace_active;
552✔
2676

2677
    if (total <= 1) {
552✔
2678
        return;
2679
    }
2680

2681
    budget = total - planner->heavy_ops;
18✔
2682
    if (budget > 1) {
18!
2683
        planner->palette_threads = 1;
18✔
2684
        planner->allow_palette_async = 1;
18✔
2685
        planner->main_threads = total - planner->palette_threads;
18✔
2686
    } else {
2687
        planner->main_threads = total;
×
2688
    }
2689
}
1!
2690

2691

2692
static size_t
2693
sixel_encoder_select_sample_stride(sixel_encoder_t *encoder,
16✔
2694
                                   int width,
2695
                                   int height,
2696
                                   int reqcolors,
2697
                                   int quality_mode)
2698
{
2699
    size_t stride;
16✔
2700
    size_t base_target;
16✔
2701
    size_t color_budget;
16✔
2702
    size_t target;
16✔
2703
    size_t total;
16✔
2704

2705
    /*
2706
     * Scale the sampling grid to cover more pixels when a 256-color palette
2707
     * or high quality is requested. The target is:
2708
     *
2709
     *   max(4096, reqcolors * 64) adjusted by quality mode
2710
     *
2711
     * With reqcolors=256 and quality=high/full this yields ~28k-40k
2712
     * samples on large frames, which more closely matches the legacy
2713
     * heuristics.
2714
     *
2715
     * When $SIXEL_PALETTE_SAMPLE_TARGET is set, the environment-defined
2716
     * target bypasses this heuristic so callers can force a specific
2717
     * sample budget without recompiling.
2718
     */
2719

2720
    stride = 1u;
16✔
2721
    base_target = 4096u;
16✔
2722
    color_budget = 0u;
16✔
2723
    target = base_target;
16✔
2724
    total = 0u;
16✔
2725

2726
    if (width <= 0 || height <= 0) {
16!
2727
        return stride;
2728
    }
2729

2730
    if (encoder != NULL && encoder->palette_sample_override) {
16!
2731
        target = encoder->palette_sample_target;
×
2732
    } else {
2733
        if (reqcolors > 0) {
16✔
2734
            color_budget = (size_t)reqcolors * 64u;
14✔
2735
            if (color_budget / 64u != (size_t)reqcolors) {
14!
2736
                color_budget = base_target;
2737
            }
2738
            if (color_budget > target) {
14✔
2739
                target = color_budget;
16✔
2740
            }
2741
        }
2742

2743
        if (quality_mode == SIXEL_QUALITY_HIGH
16!
2744
                || quality_mode == SIXEL_QUALITY_HIGHCOLOR) {
16✔
2745
            if (target <= SIZE_MAX / 2u) {
2!
2746
                target *= 2u;
2✔
2747
            } else {
2748
                target = SIZE_MAX;
2749
            }
2750
        } else if (quality_mode == SIXEL_QUALITY_FULL) {
14✔
2751
            if (target <= SIZE_MAX / 4u) {
2!
2752
                target *= 4u;
2✔
2753
            } else {
2754
                target = SIZE_MAX;
2755
            }
2756
        }
2757
    }
2758

2759
    total = (size_t)width * (size_t)height;
16✔
2760
    while (stride < total && total / (stride * stride) > target) {
188!
2761
        ++stride;
172✔
2762
    }
2763

2764
    return stride;
2765
}
2766

2767

2768
static SIXELSTATUS
2769
sixel_encoder_copy_samples(sixel_encoder_t *encoder,
16✔
2770
                           sixel_frame_t *frame,
2771
                           sixel_allocator_t *allocator,
2772
                           sixel_frame_t **sample_out)
2773
{
2774
    SIXELSTATUS status = SIXEL_FALSE;
16✔
2775
    sixel_frame_t *sample;
16✔
2776
    unsigned char *src_pixels;
16✔
2777
    unsigned char *dst_pixels;
16✔
2778
    size_t stride;
16✔
2779
    int clip_x;
16✔
2780
    int clip_y;
16✔
2781
    int clip_w;
16✔
2782
    int clip_h;
16✔
2783
    int src_width;
16✔
2784
    int src_height;
16✔
2785
    int width = 0;
16✔
2786
    int height = 0;
16✔
2787
    int depth;
16✔
2788
    int sample_width;
16✔
2789
    int sample_height;
16✔
2790
    size_t sample_count;
16✔
2791
    size_t payload_size;
16✔
2792
    size_t dst_index;
16✔
2793
    size_t src_offset;
16✔
2794
    int x;
16✔
2795
    int y;
16✔
2796

2797
    if (encoder == NULL || frame == NULL || sample_out == NULL) {
16!
2798
        return SIXEL_BAD_ARGUMENT;
2799
    }
2800
    if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
16!
2801
        return SIXEL_FEATURE_ERROR;
2802
    }
2803

2804
    sample = NULL;
16✔
2805
    src_pixels = sixel_frame_get_pixels(frame);
16✔
2806
    stride = 1u;
16✔
2807
    src_width = sixel_frame_get_width(frame);
16✔
2808
    src_height = sixel_frame_get_height(frame);
16✔
2809
    depth = sixel_helper_compute_depth(sixel_frame_get_pixelformat(frame));
16✔
2810
    sample_width = 0;
16✔
2811
    sample_height = 0;
16✔
2812
    sample_count = 0u;
16✔
2813
    payload_size = 0u;
16✔
2814
    dst_index = 0u;
16✔
2815
    src_offset = 0u;
16✔
2816

2817
    if (depth <= 0 || src_pixels == NULL) {
16!
2818
        return SIXEL_BAD_ARGUMENT;
2819
    }
2820

2821
    clip_x = encoder->clipx;
16✔
2822
    clip_y = encoder->clipy;
16✔
2823
    clip_w = encoder->clipwidth;
16✔
2824
    clip_h = encoder->clipheight;
16✔
2825

2826
    /*
2827
     * Mirror the clipping contract without mutating the source frame. Only
2828
     * the visible region feeds the palette worker so that off-screen areas do
2829
     * not skew the histogram. When clipping is disabled, the full frame
2830
     * is sampled to keep the worker active.
2831
     */
2832

2833
    if (clip_w <= 0 || clip_h <= 0) {
16!
2834
        clip_x = 0;
2835
        clip_y = 0;
2836
        clip_w = src_width;
2837
        clip_h = src_height;
2838
    } else {
2839
        if (clip_w + clip_x > src_width) {
×
2840
            if (clip_x > src_width) {
×
2841
                clip_w = 0;
2842
            } else {
2843
                clip_w = src_width - clip_x;
×
2844
            }
2845
        }
2846

2847
        if (clip_h + clip_y > src_height) {
×
2848
            if (clip_y > src_height) {
×
2849
                clip_h = 0;
2850
            } else {
2851
                clip_h = src_height - clip_y;
×
2852
            }
2853
        }
2854

2855
        if (clip_w <= 0 || clip_h <= 0) {
×
2856
            return SIXEL_BAD_ARGUMENT;
2857
        }
2858
    }
2859

2860
    width = clip_w;
16✔
2861
    height = clip_h;
16✔
2862

2863
    stride = sixel_encoder_select_sample_stride(encoder,
16✔
2864
                                                width,
2865
                                                height,
2866
                                                encoder->reqcolors,
2867
                                                encoder->quality_mode);
2868
    sample_width = (width + (int)stride - 1) / (int)stride;
16✔
2869
    sample_height = (height + (int)stride - 1) / (int)stride;
16✔
2870
    if (sample_width <= 0 || sample_height <= 0) {
16!
2871
        return SIXEL_BAD_ARGUMENT;
2872
    }
2873

2874
    sample_count = (size_t)sample_width * (size_t)sample_height;
16✔
2875
    if (sample_count != 0u && sample_count / (size_t)sample_height
16!
2876
            != (size_t)sample_width) {
2877
        return SIXEL_RUNTIME_ERROR;
2878
    }
2879

2880
    payload_size = sample_count * (size_t)depth;
16✔
2881
    if (sample_count != 0u
16!
2882
            && payload_size / sample_count != (size_t)depth) {
16!
2883
        return SIXEL_RUNTIME_ERROR;
2884
    }
2885

2886
    status = sixel_frame_new(&sample, allocator);
16✔
2887
    if (SIXEL_FAILED(status)) {
16!
2888
        return status;
2889
    }
2890
    dst_pixels = (unsigned char *)sixel_allocator_malloc(allocator,
16✔
2891
                                                         payload_size);
2892
    if (dst_pixels == NULL) {
16!
2893
        sixel_frame_unref(sample);
×
2894
        return SIXEL_BAD_ALLOCATION;
×
2895
    }
2896

2897
    sample->pixels.u8ptr = dst_pixels;
16✔
2898
    sample->width = sample_width;
16✔
2899
    sample->height = sample_height;
16✔
2900
    sample->pixelformat = sixel_frame_get_pixelformat(frame);
16✔
2901
    sample->colorspace = sixel_frame_get_colorspace(frame);
16✔
2902
    sample->ncolors = (-1);
16✔
2903

2904
    dst_index = 0u;
16✔
2905
    for (y = 0; y < height; y += (int)stride) {
1,356✔
2906
        for (x = 0; x < width; x += (int)stride) {
256,212✔
2907
            src_offset = ((size_t)(clip_y + y) * (size_t)src_width
254,872✔
2908
                       + (size_t)(clip_x + x))
254,872✔
2909
                       * (size_t)depth;
2910
            memcpy(dst_pixels + dst_index * (size_t)depth,
254,872✔
2911
                   src_pixels + src_offset,
254,872✔
2912
                   (size_t)depth);
2913
            ++dst_index;
254,872✔
2914
        }
2915
    }
2916

2917
    *sample_out = sample;
16✔
2918

2919
    return SIXEL_OK;
16✔
2920
}
2921

2922

2923
static int
2924
sixel_encoder_palette_job_thread(void *priv)
16✔
2925
{
2926
    sixel_palette_async_job_t *job;
16✔
2927
    SIXELSTATUS status;
16✔
2928
    sixel_dither_t *local;
16✔
2929

2930
    job = (sixel_palette_async_job_t *)priv;
16✔
2931
    status = SIXEL_BAD_ARGUMENT;
16✔
2932
    local = NULL;
16✔
2933

2934
    if (job != NULL && job->sample_frame != NULL) {
16!
2935
        status = sixel_frame_set_pixelformat(job->sample_frame,
16✔
2936
                                             job->target_pixelformat);
2937
        if (SIXEL_SUCCEEDED(status)) {
16!
2938
            status = sixel_dither_new(&local,
16✔
2939
                                      job->reqcolors,
2940
                                      job->allocator);
2941
        }
2942
        if (SIXEL_SUCCEEDED(status)) {
16!
2943
            sixel_dither_set_lut_policy(local, job->lut_policy);
16✔
2944
            sixel_dither_set_sixel_reversible(local,
16✔
2945
                                              job->sixel_reversible);
2946
            sixel_dither_set_final_merge(local, job->final_merge_mode);
16✔
2947
            local->quantize_model = job->quantize_model;
16✔
2948
            status = sixel_dither_initialize(
16✔
2949
                local,
2950
                sixel_frame_get_pixels(job->sample_frame),
2951
                sixel_frame_get_width(job->sample_frame),
2952
                sixel_frame_get_height(job->sample_frame),
2953
                sixel_frame_get_pixelformat(job->sample_frame),
2954
                job->method_for_largest,
2955
                job->method_for_rep,
2956
                job->quality_mode);
2957
        }
2958
        if (SIXEL_SUCCEEDED(status)) {
16!
2959
            sixel_dither_set_pixelformat(local,
16✔
2960
                                         sixel_frame_get_pixelformat(
2961
                                             job->sample_frame));
2962
            local->force_palette = job->force_palette;
16✔
2963
        }
2964
    }
2965

2966
    sixel_mutex_lock(&job->mutex);
16✔
2967
    job->status = status;
16✔
2968
    job->dither = local;
16✔
2969
    job->finished = 1;
16✔
2970
    sixel_cond_broadcast(&job->cond);
16✔
2971
    sixel_mutex_unlock(&job->mutex);
16✔
2972

2973
    if (SIXEL_FAILED(status) && local != NULL) {
16!
2974
        sixel_dither_unref(local);
×
2975
    }
2976

2977
    return 0;
16✔
2978
}
2979

2980

2981
static SIXELSTATUS
2982
sixel_encoder_palette_job_init(sixel_palette_async_job_t *job,
16✔
2983
                               sixel_allocator_t *allocator)
2984
{
2985
    SIXELSTATUS status = SIXEL_FALSE;
16✔
2986
    int result;
16✔
2987

2988
    if (job == NULL || allocator == NULL) {
16!
2989
        return SIXEL_BAD_ARGUMENT;
2990
    }
2991

2992
    job->sample_frame = NULL;
16✔
2993
    job->allocator = allocator;
16✔
2994
    job->dither = NULL;
16✔
2995
    job->status = SIXEL_OK;
16✔
2996
    job->target_pixelformat = SIXEL_PIXELFORMAT_RGB888;
16✔
2997
    job->reqcolors = 0;
16✔
2998
    job->method_for_largest = SIXEL_LARGE_AUTO;
16✔
2999
    job->method_for_rep = SIXEL_REP_AUTO;
16✔
3000
    job->quality_mode = SIXEL_QUALITY_AUTO;
16✔
3001
    job->lut_policy = SIXEL_LUT_POLICY_AUTO;
16✔
3002
    job->final_merge_mode = SIXEL_FINAL_MERGE_AUTO;
16✔
3003
    job->sixel_reversible = 0;
16✔
3004
    job->quantize_model = SIXEL_QUANTIZE_MODEL_AUTO;
16✔
3005
    job->force_palette = 0;
16✔
3006
    job->started = 0;
16✔
3007
    job->finished = 0;
16✔
3008

3009
    result = sixel_mutex_init(&job->mutex);
16✔
3010
    if (result != 0) {
16!
3011
        return SIXEL_RUNTIME_ERROR;
3012
    }
3013
    result = sixel_cond_init(&job->cond);
16✔
3014
    if (result != 0) {
16!
3015
        sixel_mutex_destroy(&job->mutex);
×
3016
        return SIXEL_RUNTIME_ERROR;
×
3017
    }
3018

3019
    status = SIXEL_OK;
16✔
3020

3021
    return status;
3022
}
3023

3024

3025
static void
3026
sixel_encoder_palette_job_dispose(sixel_palette_async_job_t *job)
16✔
3027
{
3028
    if (job == NULL) {
16!
3029
        return;
3030
    }
3031
    if (job->sample_frame != NULL) {
16!
3032
        sixel_frame_unref(job->sample_frame);
16✔
3033
        job->sample_frame = NULL;
16✔
3034
    }
3035
    if (job->dither != NULL) {
16!
3036
        sixel_dither_unref(job->dither);
×
3037
        job->dither = NULL;
×
3038
    }
3039
    sixel_cond_destroy(&job->cond);
16✔
3040
    sixel_mutex_destroy(&job->mutex);
16✔
3041
}
3042

3043

3044
static SIXELSTATUS
3045
sixel_encoder_palette_job_launch(sixel_palette_async_job_t *job,
16✔
3046
                                 sixel_frame_t *frame,
3047
                                 int target_pixelformat,
3048
                                 sixel_encoder_t *encoder)
3049
{
3050
    SIXELSTATUS status = SIXEL_FALSE;
16✔
3051
    int result;
16✔
3052

3053
    if (job == NULL || frame == NULL || encoder == NULL) {
16!
3054
        return SIXEL_BAD_ARGUMENT;
3055
    }
3056

3057
    job->target_pixelformat = target_pixelformat;
16✔
3058
    job->reqcolors = encoder->reqcolors;
16✔
3059
    job->method_for_largest = encoder->method_for_largest;
16✔
3060
    job->method_for_rep = encoder->method_for_rep;
16✔
3061
    job->quality_mode = encoder->quality_mode;
16✔
3062
    job->lut_policy = encoder->lut_policy;
16✔
3063
    job->final_merge_mode = encoder->final_merge_mode;
16✔
3064
    job->sixel_reversible = encoder->sixel_reversible;
16✔
3065
    job->quantize_model = encoder->quantize_model;
16✔
3066
    job->force_palette = encoder->force_palette;
16✔
3067

3068
    status = sixel_encoder_copy_samples(encoder,
16✔
3069
                                        frame,
3070
                                        encoder->allocator,
3071
                                        &job->sample_frame);
3072
    if (SIXEL_FAILED(status)) {
16!
3073
        return status;
3074
    }
3075

3076
    result = sixel_thread_create(&job->thread,
16✔
3077
                                 sixel_encoder_palette_job_thread,
3078
                                 job);
3079
    if (result != 0) {
16!
3080
        sixel_frame_unref(job->sample_frame);
×
3081
        job->sample_frame = NULL;
×
3082
        return SIXEL_RUNTIME_ERROR;
×
3083
    }
3084

3085
    job->started = 1;
16✔
3086

3087
    return SIXEL_OK;
16✔
3088
}
3089

3090

3091
static SIXELSTATUS
3092
sixel_encoder_palette_job_wait(sixel_palette_async_job_t *job,
16✔
3093
                               sixel_dither_t **dither_out)
3094
{
3095
    SIXELSTATUS status = SIXEL_FALSE;
16✔
3096

3097
    if (job == NULL || dither_out == NULL) {
16!
3098
        return SIXEL_BAD_ARGUMENT;
3099
    }
3100
    *dither_out = NULL;
16✔
3101

3102
    if (job->started == 0) {
16!
3103
        return SIXEL_LOGIC_ERROR;
3104
    }
3105

3106
    sixel_mutex_lock(&job->mutex);
16✔
3107
    while (!job->finished) {
26✔
3108
        sixel_cond_wait(&job->cond, &job->mutex);
10✔
3109
    }
3110
    sixel_mutex_unlock(&job->mutex);
16✔
3111

3112
    sixel_thread_join(&job->thread);
16✔
3113

3114
    status = job->status;
16✔
3115
    if (SIXEL_SUCCEEDED(status)) {
16!
3116
        *dither_out = job->dither;
16✔
3117
        job->dither = NULL;
16✔
3118
    }
3119

3120
    return status;
3121
}
3122

3123

3124
/* create palette from specified map file */
3125
static SIXELSTATUS
3126
sixel_prepare_specified_palette(
21✔
3127
    sixel_dither_t  /* out */   **dither,
3128
    sixel_encoder_t /* in */    *encoder)
3129
{
3130
    SIXELSTATUS status;
21✔
3131
    sixel_callback_context_for_mapfile_t callback_context;
21✔
3132
    sixel_loader_t *loader;
21✔
3133
    int fstatic;
21✔
3134
    int fuse_palette;
21✔
3135
    int reqcolors;
21✔
3136
    int loop_override;
21✔
3137
    char const *path;
21✔
3138
    sixel_palette_format_t format_hint;
21✔
3139
    sixel_palette_format_t format_ext;
21✔
3140
    sixel_palette_format_t format_final;
21✔
3141
    sixel_palette_format_t format_detected;
21✔
3142
    FILE *stream;
21✔
3143
    int close_stream;
21✔
3144
    unsigned char *buffer;
21✔
3145
    size_t buffer_size;
21✔
3146
    int palette_request;
21✔
3147
    int need_detection;
21✔
3148
    int treat_as_image;
21✔
3149
    int path_has_extension;
21✔
3150
    char mapfile_message[256];
21✔
3151

3152
    status = SIXEL_FALSE;
21✔
3153
    loader = NULL;
21✔
3154
    fstatic = 1;
21✔
3155
    fuse_palette = 1;
21✔
3156
    reqcolors = SIXEL_PALETTE_MAX;
21✔
3157
    loop_override = SIXEL_LOOP_DISABLE;
21✔
3158
    path = NULL;
21✔
3159
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
3160
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
21✔
3161
    format_final = SIXEL_PALETTE_FORMAT_NONE;
21✔
3162
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
21✔
3163
    stream = NULL;
21✔
3164
    close_stream = 0;
21✔
3165
    buffer = NULL;
21✔
3166
    buffer_size = 0u;
21✔
3167
    palette_request = 0;
21✔
3168
    need_detection = 0;
21✔
3169
    treat_as_image = 0;
21✔
3170
    path_has_extension = 0;
21✔
3171
    mapfile_message[0] = '\0';
21✔
3172

3173
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
21!
3174
        sixel_helper_set_additional_message(
×
3175
            "sixel_prepare_specified_palette: invalid mapfile path.");
3176
        return SIXEL_BAD_ARGUMENT;
×
3177
    }
3178

3179
    sixel_encoder_log_stage(encoder,
21✔
3180
                            NULL,
3181
                            "palette",
3182
                            "worker",
3183
                            "start",
3184
                            "mapfile=%s",
3185
                            encoder->mapfile);
3186

3187
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
21✔
3188
    if (path == NULL || *path == '\0') {
21!
3189
        sixel_helper_set_additional_message(
×
3190
            "sixel_prepare_specified_palette: empty mapfile path.");
3191
        return SIXEL_BAD_ARGUMENT;
×
3192
    }
3193

3194
    format_ext = sixel_palette_format_from_extension(path);
21✔
3195
    path_has_extension = sixel_path_has_any_extension(path);
21✔
3196

3197
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
21!
3198
        palette_request = 1;
3199
        format_final = format_hint;
3200
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
21!
3201
        palette_request = 1;
3202
        format_final = format_ext;
3203
    } else if (!path_has_extension) {
21!
3204
        palette_request = 1;
3205
        need_detection = 1;
3206
    } else {
3207
        treat_as_image = 1;
21✔
3208
    }
3209

3210
    if (palette_request) {
21!
3211
        status = sixel_palette_open_read(path, &stream, &close_stream);
×
3212
        if (SIXEL_FAILED(status)) {
×
3213
            goto palette_cleanup;
×
3214
        }
3215
        status = sixel_palette_read_stream(stream,
×
3216
                                           encoder->allocator,
3217
                                           &buffer,
3218
                                           &buffer_size);
3219
        if (close_stream) {
×
3220
            sixel_palette_close_stream(stream, close_stream);
×
3221
            stream = NULL;
×
3222
            close_stream = 0;
×
3223
        }
3224
        if (SIXEL_FAILED(status)) {
×
3225
            goto palette_cleanup;
×
3226
        }
3227
        if (buffer_size == 0u) {
×
3228
            sixel_compat_snprintf(mapfile_message,
×
3229
                                  sizeof(mapfile_message),
3230
                                  "sixel_prepare_specified_palette: "
3231
                                  "mapfile \"%s\" is empty.",
3232
                                  path != NULL ? path : "");
×
3233
            sixel_helper_set_additional_message(mapfile_message);
×
3234
            status = SIXEL_BAD_INPUT;
×
3235
            goto palette_cleanup;
×
3236
        }
3237

3238
        if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
3239
            format_detected = sixel_palette_guess_format(buffer,
×
3240
                                                         buffer_size);
3241
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
3242
                sixel_helper_set_additional_message(
×
3243
                    "sixel_prepare_specified_palette: "
3244
                    "unable to detect palette format.");
3245
                status = SIXEL_BAD_INPUT;
×
3246
                goto palette_cleanup;
×
3247
            }
3248
            format_final = format_detected;
3249
        } else if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
3250
            format_detected = sixel_palette_guess_format(buffer,
×
3251
                                                         buffer_size);
3252
            if (format_detected == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
3253
                    format_detected == SIXEL_PALETTE_FORMAT_PAL_RIFF) {
3254
                format_final = format_detected;
3255
            } else {
3256
                sixel_helper_set_additional_message(
×
3257
                    "sixel_prepare_specified_palette: "
3258
                    "ambiguous .pal content.");
3259
                status = SIXEL_BAD_INPUT;
×
3260
                goto palette_cleanup;
×
3261
            }
3262
        } else if (need_detection) {
×
3263
            format_detected = sixel_palette_guess_format(buffer,
×
3264
                                                         buffer_size);
3265
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
3266
                sixel_helper_set_additional_message(
×
3267
                    "sixel_prepare_specified_palette: "
3268
                    "unable to detect palette format.");
3269
                status = SIXEL_BAD_INPUT;
×
3270
                goto palette_cleanup;
×
3271
            }
3272
            format_final = format_detected;
3273
        }
3274

3275
        switch (format_final) {
×
3276
        case SIXEL_PALETTE_FORMAT_ACT:
×
3277
            status = sixel_palette_parse_act(buffer,
×
3278
                                             buffer_size,
3279
                                             encoder,
3280
                                             dither);
3281
            break;
×
3282
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
×
3283
            status = sixel_palette_parse_pal_jasc(buffer,
×
3284
                                                  buffer_size,
3285
                                                  encoder,
3286
                                                  dither);
3287
            break;
×
3288
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
×
3289
            status = sixel_palette_parse_pal_riff(buffer,
×
3290
                                                  buffer_size,
3291
                                                  encoder,
3292
                                                  dither);
3293
            break;
×
3294
        case SIXEL_PALETTE_FORMAT_GPL:
×
3295
            status = sixel_palette_parse_gpl(buffer,
×
3296
                                             buffer_size,
3297
                                             encoder,
3298
                                             dither);
3299
            break;
×
3300
        default:
×
3301
            sixel_helper_set_additional_message(
×
3302
                "sixel_prepare_specified_palette: "
3303
                "unsupported palette format.");
3304
            status = SIXEL_BAD_INPUT;
×
3305
            break;
×
3306
        }
3307

3308
palette_cleanup:
×
3309
        if (buffer != NULL) {
×
3310
            sixel_allocator_free(encoder->allocator, buffer);
×
3311
            buffer = NULL;
×
3312
        }
3313
        if (stream != NULL) {
×
3314
            sixel_palette_close_stream(stream, close_stream);
×
3315
            stream = NULL;
×
3316
        }
3317
        if (SIXEL_SUCCEEDED(status)) {
×
3318
            return status;
3319
        }
3320
        if (!treat_as_image) {
×
3321
            return status;
×
3322
        }
3323
    }
3324

3325
    callback_context.reqcolors = encoder->reqcolors;
21✔
3326
    callback_context.dither = NULL;
21✔
3327
    callback_context.allocator = encoder->allocator;
21✔
3328
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
3329
    callback_context.lut_policy = encoder->lut_policy;
21✔
3330
    callback_context.prefer_float32 = encoder->prefer_float32;
21✔
3331

3332
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
3333
    sixel_helper_set_thumbnail_size_hint(
21✔
3334
        sixel_encoder_thumbnail_hint(encoder));
3335
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
3336
    if (SIXEL_FAILED(status)) {
21!
3337
        goto end_loader;
×
3338
    }
3339

3340
    status = sixel_loader_setopt(loader,
21✔
3341
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
3342
                                 &fstatic);
3343
    if (SIXEL_FAILED(status)) {
21!
3344
        goto end_loader;
×
3345
    }
3346

3347
    status = sixel_loader_setopt(loader,
21✔
3348
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
3349
                                 &fuse_palette);
3350
    if (SIXEL_FAILED(status)) {
21!
3351
        goto end_loader;
×
3352
    }
3353

3354
    status = sixel_loader_setopt(loader,
21✔
3355
                                 SIXEL_LOADER_OPTION_REQCOLORS,
3356
                                 &reqcolors);
3357
    if (SIXEL_FAILED(status)) {
21!
3358
        goto end_loader;
×
3359
    }
3360

3361
    status = sixel_loader_setopt(loader,
42✔
3362
                                 SIXEL_LOADER_OPTION_BGCOLOR,
3363
                                 encoder->bgcolor);
21✔
3364
    if (SIXEL_FAILED(status)) {
21!
3365
        goto end_loader;
×
3366
    }
3367

3368
    status = sixel_loader_setopt(loader,
21✔
3369
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
3370
                                 &loop_override);
3371
    if (SIXEL_FAILED(status)) {
21!
3372
        goto end_loader;
×
3373
    }
3374

3375
    status = sixel_loader_setopt(loader,
42✔
3376
                                 SIXEL_LOADER_OPTION_INSECURE,
3377
                                 &encoder->finsecure);
21✔
3378
    if (SIXEL_FAILED(status)) {
21!
3379
        goto end_loader;
×
3380
    }
3381

3382
    status = sixel_loader_setopt(loader,
42✔
3383
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
3384
                                 encoder->cancel_flag);
21✔
3385
    if (SIXEL_FAILED(status)) {
21!
3386
        goto end_loader;
×
3387
    }
3388

3389
    status = sixel_loader_setopt(loader,
42✔
3390
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
3391
                                 encoder->loader_order);
21✔
3392
    if (SIXEL_FAILED(status)) {
21!
3393
        goto end_loader;
×
3394
    }
3395

3396
    status = sixel_loader_setopt(loader,
21✔
3397
                                 SIXEL_LOADER_OPTION_CONTEXT,
3398
                                 &callback_context);
3399
    if (SIXEL_FAILED(status)) {
21!
3400
        goto end_loader;
×
3401
    }
3402

3403
    status = sixel_loader_load_file(loader,
21✔
3404
                                    encoder->mapfile,
21✔
3405
                                    load_image_callback_for_palette);
3406
    if (status != SIXEL_OK) {
21!
3407
        goto end_loader;
3408
    }
3409

3410
end_loader:
21✔
3411
    sixel_loader_unref(loader);
21✔
3412

3413
    if (status != SIXEL_OK) {
21!
3414
        return status;
3415
    }
3416

3417
    if (! callback_context.dither) {
21!
3418
        sixel_compat_snprintf(mapfile_message,
×
3419
                              sizeof(mapfile_message),
3420
                              "sixel_prepare_specified_palette() failed.\n"
3421
                              "reason: mapfile \"%s\" is empty.",
3422
                              encoder->mapfile != NULL
×
3423
                                ? encoder->mapfile
3424
                                : "");
3425
        sixel_helper_set_additional_message(mapfile_message);
×
3426
        return SIXEL_BAD_INPUT;
×
3427
    }
3428

3429
    *dither = callback_context.dither;
21✔
3430

3431
    sixel_encoder_log_stage(encoder,
21✔
3432
                            NULL,
3433
                            "palette",
3434
                            "worker",
3435
                            "finish",
3436
                            "mapfile=%s format=%d",
3437
                            encoder->mapfile,
3438
                            format_final);
3439

3440
    return status;
21✔
3441
}
3442

3443

3444
/* create dither object from a frame */
3445
static SIXELSTATUS
3446
sixel_encoder_prepare_palette(
530✔
3447
    sixel_encoder_t *encoder,  /* encoder object */
3448
    sixel_frame_t   *frame,    /* input frame object */
3449
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
3450
{
3451
    SIXELSTATUS status = SIXEL_FALSE;
530✔
3452
    int histogram_colors;
530✔
3453
    sixel_assessment_t *assessment;
530✔
3454
    int promoted_stage;
530✔
3455

3456
    assessment = NULL;
530✔
3457
    promoted_stage = 0;
530✔
3458
    if (encoder != NULL) {
530!
3459
        assessment = encoder->assessment_observer;
530✔
3460
    }
3461

3462
    switch (encoder->color_option) {
530!
3463
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
36✔
3464
        if (encoder->dither_cache) {
36!
3465
            *dither = encoder->dither_cache;
×
3466
            status = SIXEL_OK;
×
3467
        } else {
3468
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
3469
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
3470
        }
3471
        goto end;
36✔
3472
    case SIXEL_COLOR_OPTION_MONOCHROME:
12✔
3473
        if (encoder->dither_cache) {
12!
3474
            *dither = encoder->dither_cache;
×
3475
            status = SIXEL_OK;
×
3476
        } else {
3477
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
3478
        }
3479
        goto end;
12✔
3480
    case SIXEL_COLOR_OPTION_MAPFILE:
21✔
3481
        if (encoder->dither_cache) {
21!
3482
            *dither = encoder->dither_cache;
×
3483
            status = SIXEL_OK;
×
3484
        } else {
3485
            status = sixel_prepare_specified_palette(dither, encoder);
21✔
3486
        }
3487
        goto end;
21✔
3488
    case SIXEL_COLOR_OPTION_BUILTIN:
27✔
3489
        if (encoder->dither_cache) {
27!
3490
            *dither = encoder->dither_cache;
×
3491
            status = SIXEL_OK;
×
3492
        } else {
3493
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
3494
        }
3495
        goto end;
27✔
3496
    case SIXEL_COLOR_OPTION_DEFAULT:
3497
    default:
3498
        break;
434✔
3499
    }
3500

3501
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
434✔
3502
        if (!sixel_frame_get_palette(frame)) {
231!
3503
            status = SIXEL_LOGIC_ERROR;
×
3504
            goto end;
×
3505
        }
3506
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
231✔
3507
                                  encoder->allocator);
3508
        if (SIXEL_FAILED(status)) {
231!
3509
            goto end;
×
3510
        }
3511
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
231✔
3512
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
231✔
3513
        if (sixel_frame_get_transparent(frame) != (-1)) {
231!
3514
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
3515
        }
3516
        if (*dither && encoder->dither_cache) {
231!
3517
            sixel_dither_unref(encoder->dither_cache);
×
3518
        }
3519
        goto end;
231✔
3520
    }
3521

3522
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
203!
3523
        switch (sixel_frame_get_pixelformat(frame)) {
×
3524
        case SIXEL_PIXELFORMAT_G1:
×
3525
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
3526
            break;
×
3527
        case SIXEL_PIXELFORMAT_G2:
×
3528
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
3529
            break;
×
3530
        case SIXEL_PIXELFORMAT_G4:
×
3531
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
3532
            break;
×
3533
        case SIXEL_PIXELFORMAT_G8:
×
3534
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
3535
            break;
×
3536
        default:
×
3537
            *dither = NULL;
×
3538
            status = SIXEL_LOGIC_ERROR;
×
3539
            goto end;
×
3540
        }
3541
        if (*dither && encoder->dither_cache) {
×
3542
            sixel_dither_unref(encoder->dither_cache);
×
3543
        }
3544
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
3545
        status = SIXEL_OK;
×
3546
        goto end;
×
3547
    }
3548

3549
    if (encoder->dither_cache) {
203!
3550
        sixel_dither_unref(encoder->dither_cache);
×
3551
    }
3552
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
203✔
3553
    if (SIXEL_FAILED(status)) {
203!
3554
        goto end;
×
3555
    }
3556

3557
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
203✔
3558
    sixel_dither_set_sixel_reversible(*dither,
203✔
3559
                                      encoder->sixel_reversible);
3560
    sixel_dither_set_final_merge(*dither, encoder->final_merge_mode);
203✔
3561
    (*dither)->quantize_model = encoder->quantize_model;
203✔
3562

3563
    status = sixel_dither_initialize(*dither,
203✔
3564
                                     sixel_frame_get_pixels(frame),
3565
                                     sixel_frame_get_width(frame),
3566
                                     sixel_frame_get_height(frame),
3567
                                     sixel_frame_get_pixelformat(frame),
3568
                                     encoder->method_for_largest,
3569
                                     encoder->method_for_rep,
3570
                                     encoder->quality_mode);
3571
    if (SIXEL_FAILED(status)) {
203!
3572
        sixel_dither_unref(*dither);
×
3573
        goto end;
×
3574
    }
3575

3576
    if (assessment != NULL && promoted_stage == 0) {
203!
3577
        sixel_assessment_stage_transition(
3✔
3578
            assessment,
3579
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
3580
        promoted_stage = 1;
3✔
3581
    }
3582

3583
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
203✔
3584
    if (histogram_colors <= encoder->reqcolors) {
203✔
3585
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
126✔
3586
    }
3587
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
203✔
3588

3589
    status = SIXEL_OK;
203✔
3590

3591
end:
530✔
3592
    if (assessment != NULL && promoted_stage == 0) {
530!
3593
        sixel_assessment_stage_transition(
×
3594
            assessment,
3595
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
3596
        promoted_stage = 1;
×
3597
    }
3598
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
530!
3599
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
530✔
3600
        /* pass down the user's demand for an exact palette size */
3601
        (*dither)->force_palette = encoder->force_palette;
530✔
3602
    }
3603
    return status;
530✔
3604
}
3605

3606

3607
/* resize a frame with settings of specified encoder object */
3608
static SIXELSTATUS
3609
sixel_encoder_do_resize(
552✔
3610
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3611
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3612
{
3613
    SIXELSTATUS status = SIXEL_FALSE;
552✔
3614
    int src_width;
552✔
3615
    int src_height;
552✔
3616
    int dst_width;
552✔
3617
    int dst_height;
552✔
3618
    int use_float_resize;
552✔
3619

3620
    /* get frame width and height */
3621
    src_width = sixel_frame_get_width(frame);
552✔
3622
    src_height = sixel_frame_get_height(frame);
552✔
3623

3624
    if (src_width < 1) {
552✔
3625
         sixel_helper_set_additional_message(
6✔
3626
             "sixel_encoder_do_resize: "
3627
             "detected a frame with a non-positive width.");
3628
        return SIXEL_BAD_ARGUMENT;
6✔
3629
    }
3630

3631
    if (src_height < 1) {
546!
3632
         sixel_helper_set_additional_message(
×
3633
             "sixel_encoder_do_resize: "
3634
             "detected a frame with a non-positive height.");
3635
        return SIXEL_BAD_ARGUMENT;
×
3636
    }
3637

3638
    /* settings around scaling */
3639
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
546✔
3640
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
546✔
3641

3642
    use_float_resize = 0;
546✔
3643

3644
    /* if the encoder has percentwidth or percentheight property,
3645
       convert them to pixelwidth / pixelheight */
3646
    if (encoder->percentwidth > 0) {
546✔
3647
        dst_width = src_width * encoder->percentwidth / 100;
12✔
3648
    }
3649
    if (encoder->percentheight > 0) {
546✔
3650
        dst_height = src_height * encoder->percentheight / 100;
9✔
3651
    }
3652

3653
    /* if only either width or height is set, set also the other
3654
       to retain frame aspect ratio */
3655
    if (dst_width > 0 && dst_height <= 0) {
546✔
3656
        dst_height = src_height * dst_width / src_width;
45✔
3657
    }
3658
    if (dst_height > 0 && dst_width <= 0) {
546✔
3659
        dst_width = src_width * dst_height / src_height;
33✔
3660
    }
3661

3662
    sixel_encoder_log_stage(encoder,
546✔
3663
                            frame,
3664
                            "scale",
3665
                            "worker",
3666
                            "start",
3667
                            "src=%dx%d dst=%dx%d resample=%d",
3668
                            src_width,
3669
                            src_height,
3670
                            dst_width,
3671
                            dst_height,
3672
                            encoder->method_for_resampling);
3673

3674
    /* do resize */
3675
    if (dst_width > 0 && dst_height > 0) {
546!
3676
        if (encoder->method_for_resampling != SIXEL_RES_NEAREST) {
102✔
3677
            if (SIXEL_PIXELFORMAT_IS_FLOAT32(
84!
3678
                    encoder->working_colorspace) != 0) {
3679
                use_float_resize = 1;
×
3680
            }
3681
            if (encoder->prefer_float32 != 0) {
84!
3682
                use_float_resize = 1;
3683
            }
3684
        }
3685

3686
        if (use_float_resize != 0) {
84!
3687
            status = sixel_frame_resize_float32(
×
3688
                frame,
3689
                dst_width,
3690
                dst_height,
3691
                encoder->method_for_resampling);
3692
        } else {
3693
            status = sixel_frame_resize(
102✔
3694
                frame,
3695
                dst_width,
3696
                dst_height,
3697
                encoder->method_for_resampling);
3698
        }
3699
        if (SIXEL_FAILED(status)) {
102!
3700
            goto end;
×
3701
        }
3702
    }
3703

3704
    sixel_encoder_log_stage(encoder,
546✔
3705
                            frame,
3706
                            "scale",
3707
                            "worker",
3708
                            "finish",
3709
                            "result=%dx%d",
3710
                            sixel_frame_get_width(frame),
3711
                            sixel_frame_get_height(frame));
3712

3713
    /* success */
3714
    status = SIXEL_OK;
546✔
3715

3716
end:
3717
    return status;
3718
}
3719

3720

3721
/* clip a frame with settings of specified encoder object */
3722
static SIXELSTATUS
3723
sixel_encoder_do_clip(
546✔
3724
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3725
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3726
{
3727
    SIXELSTATUS status = SIXEL_FALSE;
546✔
3728
    int src_width;
546✔
3729
    int src_height;
546✔
3730
    int clip_x;
546✔
3731
    int clip_y;
546✔
3732
    int clip_w;
546✔
3733
    int clip_h;
546✔
3734

3735
    /* get frame width and height */
3736
    src_width = sixel_frame_get_width(frame);
546✔
3737
    src_height = sixel_frame_get_height(frame);
546✔
3738

3739
    /* settings around clipping */
3740
    clip_x = encoder->clipx;
546✔
3741
    clip_y = encoder->clipy;
546✔
3742
    clip_w = encoder->clipwidth;
546✔
3743
    clip_h = encoder->clipheight;
546✔
3744

3745
    /* adjust clipping width with comparing it to frame width */
3746
    if (clip_w + clip_x > src_width) {
546✔
3747
        if (clip_x > src_width) {
6✔
3748
            clip_w = 0;
3749
        } else {
3750
            clip_w = src_width - clip_x;
3✔
3751
        }
3752
    }
3753

3754
    /* adjust clipping height with comparing it to frame height */
3755
    if (clip_h + clip_y > src_height) {
546✔
3756
        if (clip_y > src_height) {
6✔
3757
            clip_h = 0;
3758
        } else {
3759
            clip_h = src_height - clip_y;
3✔
3760
        }
3761
    }
3762

3763
    sixel_encoder_log_stage(encoder,
546✔
3764
                            frame,
3765
                            "crop",
3766
                            "worker",
3767
                            "start",
3768
                            "src=%dx%d region=%dx%d@%d,%d",
3769
                            src_width,
3770
                            src_height,
3771
                            clip_w,
3772
                            clip_h,
3773
                            clip_x,
3774
                            clip_y);
3775

3776
    /* do clipping */
3777
    if (clip_w > 0 && clip_h > 0) {
546!
3778
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
12✔
3779
        if (SIXEL_FAILED(status)) {
12!
3780
            goto end;
×
3781
        }
3782
    }
3783

3784
    sixel_encoder_log_stage(encoder,
546✔
3785
                            frame,
3786
                            "crop",
3787
                            "worker",
3788
                            "finish",
3789
                            "result=%dx%d",
3790
                            sixel_frame_get_width(frame),
3791
                            sixel_frame_get_height(frame));
3792

3793
    /* success */
3794
    status = SIXEL_OK;
546✔
3795

3796
end:
546✔
3797
    return status;
546✔
3798
}
3799

3800

3801
static void
3802
sixel_debug_print_palette(
3✔
3803
    sixel_dither_t /* in */ *dither /* dithering object */
3804
)
3805
{
3806
    sixel_palette_t *palette_obj;
3✔
3807
    unsigned char *palette_copy;
3✔
3808
    size_t palette_count;
3✔
3809
    int i;
3✔
3810

3811
    palette_obj = NULL;
3✔
3812
    palette_copy = NULL;
3✔
3813
    palette_count = 0U;
3✔
3814
    if (dither == NULL) {
3!
3815
        return;
×
3816
    }
3817

3818
    if (SIXEL_FAILED(
3!
3819
            sixel_dither_get_quantized_palette(dither, &palette_obj))
3820
            || palette_obj == NULL) {
3!
3821
        return;
3822
    }
3823
    if (SIXEL_FAILED(sixel_palette_copy_entries_8bit(
3!
3824
            palette_obj,
3825
            &palette_copy,
3826
            &palette_count,
3827
            SIXEL_PIXELFORMAT_RGB888,
3828
            dither->allocator))
3829
            || palette_copy == NULL) {
3!
3830
        sixel_palette_unref(palette_obj);
×
3831
        return;
×
3832
    }
3833
    sixel_palette_unref(palette_obj);
3✔
3834

3835
    fprintf(stderr, "palette:\n");
3✔
3836
    for (i = 0; i < (int)palette_count;
51✔
3837
            ++i) {
45✔
3838
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
45✔
3839
                palette_copy[i * 3 + 0],
45✔
3840
                palette_copy[i * 3 + 1],
45✔
3841
                palette_copy[i * 3 + 2]);
45✔
3842
    }
3843
    sixel_allocator_free(dither->allocator, palette_copy);
3✔
3844
}
1!
3845

3846

3847
static SIXELSTATUS
3848
sixel_encoder_output_without_macro(
462✔
3849
    sixel_frame_t       /* in */ *frame,
3850
    sixel_dither_t      /* in */ *dither,
3851
    sixel_output_t      /* in */ *output,
3852
    sixel_encoder_t     /* in */ *encoder)
3853
{
3854
    SIXELSTATUS status = SIXEL_OK;
462✔
3855
    static unsigned char *p;
462✔
3856
    int depth;
462✔
3857
    enum { message_buffer_size = 2048 };
462✔
3858
    char message[message_buffer_size];
462✔
3859
    int nwrite;
462✔
3860
    int dulation;
462✔
3861
    int delay;
462✔
3862
    unsigned int remaining_delay;
462✔
3863
    unsigned char *pixbuf;
462✔
3864
    int width;
462✔
3865
    int height;
462✔
3866
    int pixelformat = 0;
462✔
3867
    size_t size;
462✔
3868
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
462✔
3869
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3870
    sixel_clock_t last_clock;
462✔
3871
#endif
3872

3873
    if (encoder == NULL) {
462!
3874
        sixel_helper_set_additional_message(
×
3875
            "sixel_encoder_output_without_macro: encoder object is null.");
3876
        status = SIXEL_BAD_ARGUMENT;
×
3877
        goto end;
×
3878
    }
3879

3880
    if (encoder->assessment_observer != NULL) {
462✔
3881
        sixel_assessment_stage_transition(
3✔
3882
            encoder->assessment_observer,
3883
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3884
    }
3885

3886
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
462✔
3887
        if (encoder->force_palette) {
366!
3888
            /* keep every slot when the user forced the palette size */
3889
            sixel_dither_set_optimize_palette(dither, 0);
×
3890
        } else {
3891
            sixel_dither_set_optimize_palette(dither, 1);
366✔
3892
        }
3893
    }
3894

3895
    pixelformat = sixel_frame_get_pixelformat(frame);
462✔
3896
    frame_colorspace = sixel_frame_get_colorspace(frame);
462✔
3897
    output->pixelformat = pixelformat;
462✔
3898
    output->source_colorspace = frame_colorspace;
462✔
3899
    output->colorspace = encoder->output_colorspace;
462✔
3900
    sixel_dither_set_pixelformat(dither, pixelformat);
462✔
3901
    depth = sixel_helper_compute_depth(pixelformat);
462✔
3902
    if (depth < 0) {
462!
3903
        status = SIXEL_LOGIC_ERROR;
×
3904
        nwrite = sixel_compat_snprintf(
×
3905
            message,
3906
            sizeof(message),
3907
            "sixel_encoder_output_without_macro: "
3908
            "sixel_helper_compute_depth(%08x) failed.",
3909
            pixelformat);
3910
        if (nwrite > 0) {
×
3911
            sixel_helper_set_additional_message(message);
×
3912
        }
3913
        goto end;
×
3914
    }
3915

3916
    width = sixel_frame_get_width(frame);
462✔
3917
    height = sixel_frame_get_height(frame);
462✔
3918
    size = (size_t)(width * height * depth);
462✔
3919

3920
    sixel_encoder_log_stage(encoder,
462✔
3921
                            frame,
3922
                            "encode",
3923
                            "worker",
3924
                            "start",
3925
                            "size=%dx%d fmt=%08x dst_cs=%d",
3926
                            width,
3927
                            height,
3928
                            pixelformat,
3929
                            output->colorspace);
3930

3931
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
462✔
3932
    if (p == NULL) {
462!
3933
        sixel_helper_set_additional_message(
×
3934
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
3935
        status = SIXEL_BAD_ALLOCATION;
×
3936
        goto end;
×
3937
    }
3938
#if defined(HAVE_CLOCK)
3939
    if (output->last_clock == 0) {
462!
3940
        output->last_clock = clock();
462✔
3941
    }
3942
#elif defined(HAVE_CLOCK_WIN)
3943
    if (output->last_clock == 0) {
3944
        output->last_clock = clock_win();
3945
    }
3946
#endif
3947
    delay = sixel_frame_get_delay(frame);
462✔
3948
    if (delay > 0 && !encoder->fignore_delay) {
462✔
3949
# if defined(HAVE_CLOCK)
3950
        last_clock = clock();
3✔
3951
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3952
#  if defined(__APPLE__)
3953
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3954
                          / 100000);
3955
#  else
3956
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3✔
3957
                          / CLOCKS_PER_SEC);
3958
#  endif
3959
        output->last_clock = last_clock;
3✔
3960
# elif defined(HAVE_CLOCK_WIN)
3961
        last_clock = clock_win();
3962
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3963
                          / CLOCKS_PER_SEC);
3964
        output->last_clock = last_clock;
3965
# else
3966
        dulation = 0;
3967
# endif
3968
        if (dulation < 1000 * 10 * delay) {
3!
3969
            remaining_delay =
3✔
3970
                (unsigned int)(1000 * 10 * delay - dulation);
3✔
3971
            sixel_sleep(remaining_delay);
3✔
3972
        }
3973
    }
3974

3975
    pixbuf = sixel_frame_get_pixels(frame);
462✔
3976
    memcpy(p, pixbuf, (size_t)(width * height * depth));
462✔
3977

3978
    if (encoder->cancel_flag && *encoder->cancel_flag) {
462!
3979
        goto end;
×
3980
    }
3981

3982
    if (encoder->capture_quantized) {
462✔
3983
        status = sixel_encoder_capture_quantized(encoder,
3✔
3984
                                                 dither,
3985
                                                 p,
3986
                                                 size,
3987
                                                 width,
3988
                                                 height,
3989
                                                 pixelformat,
3990
                                                 frame_colorspace,
3991
                                                 output->colorspace);
3992
        if (SIXEL_FAILED(status)) {
3!
3993
            goto end;
×
3994
        }
3995
    }
3996

3997
    if (encoder->assessment_observer != NULL) {
462✔
3998
        sixel_assessment_stage_transition(
3✔
3999
            encoder->assessment_observer,
4000
            SIXEL_ASSESSMENT_STAGE_ENCODE);
4001
    }
4002
    status = sixel_encode(p, width, height, depth, dither, output);
462✔
4003
    if (encoder->assessment_observer != NULL) {
462✔
4004
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
4005
    }
4006
    if (status != SIXEL_OK) {
462!
4007
        goto end;
×
4008
    }
4009

4010
end:
462✔
4011
    if (SIXEL_SUCCEEDED(status)) {
×
4012
        sixel_encoder_log_stage(encoder,
462✔
4013
                                frame,
4014
                                "encode",
4015
                                "worker",
4016
                                "finish",
4017
                                "size=%dx%d",
4018
                                width,
4019
                                height);
4020
    }
4021
    output->pixelformat = pixelformat;
462✔
4022
    output->source_colorspace = frame_colorspace;
462✔
4023
    sixel_allocator_free(encoder->allocator, p);
462✔
4024

4025
    return status;
462✔
4026
}
4027

4028

4029
static SIXELSTATUS
4030
sixel_encoder_output_with_macro(
84✔
4031
    sixel_frame_t   /* in */ *frame,
4032
    sixel_dither_t  /* in */ *dither,
4033
    sixel_output_t  /* in */ *output,
4034
    sixel_encoder_t /* in */ *encoder)
4035
{
4036
    SIXELSTATUS status = SIXEL_OK;
84✔
4037
    enum { message_buffer_size = 256 };
84✔
4038
    char buffer[message_buffer_size];
84✔
4039
    int nwrite;
84✔
4040
    int dulation;
84✔
4041
    int delay;
84✔
4042
    unsigned int remaining_delay;
84✔
4043
    int width;
84✔
4044
    int height;
84✔
4045
    int pixelformat;
84✔
4046
    int depth;
84✔
4047
    size_t size = 0;
84✔
4048
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
84✔
4049
    unsigned char *converted = NULL;
84✔
4050
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
4051
    sixel_clock_t last_clock;
84✔
4052
#endif
4053
    double write_started_at;
84✔
4054
    double write_finished_at;
84✔
4055
    double write_duration;
84✔
4056

4057
    if (encoder != NULL && encoder->assessment_observer != NULL) {
84!
4058
        sixel_assessment_stage_transition(
×
4059
            encoder->assessment_observer,
4060
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
4061
    }
4062

4063
#if defined(HAVE_CLOCK)
4064
    if (output->last_clock == 0) {
84!
4065
        output->last_clock = clock();
84✔
4066
    }
4067
#elif defined(HAVE_CLOCK_WIN)
4068
    if (output->last_clock == 0) {
4069
        output->last_clock = clock_win();
4070
    }
4071
#endif
4072

4073
    width = sixel_frame_get_width(frame);
84✔
4074
    height = sixel_frame_get_height(frame);
84✔
4075
    pixelformat = sixel_frame_get_pixelformat(frame);
84✔
4076
    depth = sixel_helper_compute_depth(pixelformat);
84✔
4077
    if (depth < 0) {
84!
4078
        status = SIXEL_LOGIC_ERROR;
×
4079
        sixel_helper_set_additional_message(
×
4080
            "sixel_encoder_output_with_macro: "
4081
            "sixel_helper_compute_depth() failed.");
4082
        goto end;
×
4083
    }
4084

4085
    frame_colorspace = sixel_frame_get_colorspace(frame);
84✔
4086
    size = (size_t)width * (size_t)height * (size_t)depth;
84✔
4087
    converted = (unsigned char *)sixel_allocator_malloc(
84✔
4088
        encoder->allocator, size);
4089
    if (converted == NULL) {
84!
4090
        sixel_helper_set_additional_message(
×
4091
            "sixel_encoder_output_with_macro: "
4092
            "sixel_allocator_malloc() failed.");
4093
        status = SIXEL_BAD_ALLOCATION;
×
4094
        goto end;
×
4095
    }
4096

4097
    memcpy(converted, sixel_frame_get_pixels(frame), size);
84✔
4098
    output->pixelformat = pixelformat;
84✔
4099
    output->source_colorspace = frame_colorspace;
84✔
4100
    output->colorspace = encoder->output_colorspace;
84✔
4101

4102
    if (sixel_frame_get_loop_no(frame) == 0) {
84✔
4103
        if (encoder->macro_number >= 0) {
54!
4104
            nwrite = sixel_compat_snprintf(
×
4105
                buffer,
4106
                sizeof(buffer),
4107
                "\033P%d;0;1!z",
4108
                encoder->macro_number);
4109
        } else {
4110
            nwrite = sixel_compat_snprintf(
54✔
4111
                buffer,
4112
                sizeof(buffer),
4113
                "\033P%d;0;1!z",
4114
                sixel_frame_get_frame_no(frame));
4115
        }
4116
        if (nwrite < 0) {
54!
4117
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4118
            sixel_helper_set_additional_message(
×
4119
                "sixel_encoder_output_with_macro: command format failed.");
4120
            goto end;
×
4121
        }
4122
        write_started_at = 0.0;
54✔
4123
        write_finished_at = 0.0;
54✔
4124
        write_duration = 0.0;
54✔
4125
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
4126
            write_started_at = sixel_assessment_timer_now();
×
4127
        }
4128
        nwrite = sixel_write_callback(buffer,
108✔
4129
                                      (int)strlen(buffer),
54✔
4130
                                      &encoder->outfd);
4131
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
4132
            write_finished_at = sixel_assessment_timer_now();
×
4133
            write_duration = write_finished_at - write_started_at;
×
4134
            if (write_duration < 0.0) {
×
4135
                write_duration = 0.0;
54✔
4136
            }
4137
        }
4138
        if (nwrite < 0) {
54!
4139
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4140
            sixel_helper_set_additional_message(
×
4141
                "sixel_encoder_output_with_macro: "
4142
                "sixel_write_callback() failed.");
4143
            goto end;
×
4144
        }
4145
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
4146
            sixel_assessment_record_output_write(
×
4147
                encoder->assessment_observer,
4148
                (size_t)nwrite,
4149
                write_duration);
4150
        }
4151

4152
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
4153
            sixel_assessment_stage_transition(
×
4154
                encoder->assessment_observer,
4155
                SIXEL_ASSESSMENT_STAGE_ENCODE);
4156
        }
4157
        status = sixel_encode(converted,
54✔
4158
                              width,
4159
                              height,
4160
                              depth,
4161
                              dither,
4162
                              output);
4163
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
4164
            sixel_assessment_stage_finish(
×
4165
                encoder->assessment_observer);
4166
        }
4167
        if (SIXEL_FAILED(status)) {
54!
4168
            goto end;
×
4169
        }
4170

4171
        write_started_at = 0.0;
54✔
4172
        write_finished_at = 0.0;
54✔
4173
        write_duration = 0.0;
54✔
4174
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
4175
            write_started_at = sixel_assessment_timer_now();
×
4176
        }
4177
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
54✔
4178
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
4179
            write_finished_at = sixel_assessment_timer_now();
×
4180
            write_duration = write_finished_at - write_started_at;
×
4181
            if (write_duration < 0.0) {
×
4182
                write_duration = 0.0;
54✔
4183
            }
4184
        }
4185
        if (nwrite < 0) {
54!
4186
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4187
            sixel_helper_set_additional_message(
×
4188
                "sixel_encoder_output_with_macro: "
4189
                "sixel_write_callback() failed.");
4190
            goto end;
×
4191
        }
4192
        if (encoder != NULL && encoder->assessment_observer != NULL) {
54!
4193
            sixel_assessment_record_output_write(
×
4194
                encoder->assessment_observer,
4195
                (size_t)nwrite,
4196
                write_duration);
4197
        }
4198
    }
4199
    if (encoder->macro_number < 0) {
84✔
4200
        nwrite = sixel_compat_snprintf(
81✔
4201
            buffer,
4202
            sizeof(buffer),
4203
            "\033[%d*z",
4204
            sixel_frame_get_frame_no(frame));
4205
        if (nwrite < 0) {
81!
4206
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4207
            sixel_helper_set_additional_message(
×
4208
                "sixel_encoder_output_with_macro: command format failed.");
4209
        }
4210
        write_started_at = 0.0;
81✔
4211
        write_finished_at = 0.0;
81✔
4212
        write_duration = 0.0;
81✔
4213
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
4214
            write_started_at = sixel_assessment_timer_now();
×
4215
        }
4216
        nwrite = sixel_write_callback(buffer,
162✔
4217
                                      (int)strlen(buffer),
81✔
4218
                                      &encoder->outfd);
4219
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
4220
            write_finished_at = sixel_assessment_timer_now();
×
4221
            write_duration = write_finished_at - write_started_at;
×
4222
            if (write_duration < 0.0) {
×
4223
                write_duration = 0.0;
81✔
4224
            }
4225
        }
4226
        if (nwrite < 0) {
81!
4227
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4228
            sixel_helper_set_additional_message(
×
4229
                "sixel_encoder_output_with_macro: "
4230
                "sixel_write_callback() failed.");
4231
            goto end;
×
4232
        }
4233
        if (encoder != NULL && encoder->assessment_observer != NULL) {
81!
4234
            sixel_assessment_record_output_write(
×
4235
                encoder->assessment_observer,
4236
                (size_t)nwrite,
4237
                write_duration);
4238
        }
4239
    delay = sixel_frame_get_delay(frame);
81✔
4240
    if (delay > 0 && !encoder->fignore_delay) {
81!
4241
# if defined(HAVE_CLOCK)
4242
            last_clock = clock();
54✔
4243
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
4244
#  if defined(__APPLE__)
4245
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
4246
                             / 100000);
4247
#  else
4248
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
54✔
4249
                             / CLOCKS_PER_SEC);
4250
#  endif
4251
            output->last_clock = last_clock;
54✔
4252
# elif defined(HAVE_CLOCK_WIN)
4253
            last_clock = clock_win();
4254
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
4255
                             / CLOCKS_PER_SEC);
4256
            output->last_clock = last_clock;
4257
# else
4258
            dulation = 0;
4259
# endif
4260
            if (dulation < 1000 * 10 * delay) {
54!
4261
                remaining_delay =
54✔
4262
                    (unsigned int)(1000 * 10 * delay - dulation);
54✔
4263
                sixel_sleep(remaining_delay);
54✔
4264
            }
4265
        }
4266
    }
4267

4268
end:
30✔
4269
    output->pixelformat = pixelformat;
84✔
4270
    output->source_colorspace = frame_colorspace;
84✔
4271
    sixel_allocator_free(encoder->allocator, converted);
84✔
4272

4273
    return status;
84✔
4274
}
4275

4276

4277
static SIXELSTATUS
4278
sixel_encoder_emit_iso2022_chars(
×
4279
    sixel_encoder_t *encoder,
4280
    sixel_frame_t *frame
4281
)
4282
{
4283
    char *buf_p, *buf;
×
4284
    int col, row;
×
4285
    int charset;
×
4286
    int is_96cs;
×
4287
    unsigned int charset_no;
×
4288
    unsigned int code;
×
4289
    int num_cols, num_rows;
×
4290
    SIXELSTATUS status;
×
4291
    size_t alloc_size;
×
4292
    int nwrite;
×
4293
    int target_fd;
×
4294
    int chunk_size;
×
4295

4296
    charset_no = encoder->drcs_charset_no;
×
4297
    if (charset_no == 0u) {
×
4298
        charset_no = 1u;
4299
    }
4300
    if (encoder->drcs_mmv == 0) {
×
4301
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
4302
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
4303
    } else if (encoder->drcs_mmv == 1) {
×
4304
        is_96cs = 0;
×
4305
        charset = (int)(charset_no + 0x3fu);
×
4306
    } else {
4307
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
4308
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
4309
    }
4310
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
4311
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
4312
             / encoder->cell_width;
4313
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
4314
             / encoder->cell_height;
4315

4316
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
4317
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
4318
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
4319
    if (buf == NULL) {
×
4320
        sixel_helper_set_additional_message(
×
4321
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
4322
        status = SIXEL_BAD_ALLOCATION;
×
4323
        goto end;
×
4324
    }
4325

4326
    code = 0x20;
×
4327
    *(buf_p++) = '\016';  /* SI */
×
4328
    *(buf_p++) = '\033';
×
4329
    *(buf_p++) = ')';
×
4330
    *(buf_p++) = ' ';
×
4331
    *(buf_p++) = charset;
×
4332
    for(row = 0; row < num_rows; row++) {
×
4333
        for(col = 0; col < num_cols; col++) {
×
4334
            if ((code & 0x7f) == 0x0) {
×
4335
                if (charset == 0x7e) {
×
4336
                    is_96cs = 1 - is_96cs;
×
4337
                    charset = '0';
×
4338
                } else {
4339
                    charset++;
×
4340
                }
4341
                code = 0x20;
×
4342
                *(buf_p++) = '\033';
×
4343
                *(buf_p++) = is_96cs ? '-': ')';
×
4344
                *(buf_p++) = ' ';
×
4345
                *(buf_p++) = charset;
×
4346
            }
4347
            *(buf_p++) = code++;
×
4348
        }
4349
        *(buf_p++) = '\n';
×
4350
    }
4351
    *(buf_p++) = '\017';  /* SO */
×
4352

4353
    if (encoder->tile_outfd >= 0) {
×
4354
        target_fd = encoder->tile_outfd;
4355
    } else {
4356
        target_fd = encoder->outfd;
×
4357
    }
4358

4359
    chunk_size = (int)(buf_p - buf);
×
4360
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
4361
                                          buf,
4362
                                          chunk_size,
4363
                                          target_fd);
4364
    if (nwrite != chunk_size) {
×
4365
        sixel_helper_set_additional_message(
×
4366
            "sixel_encoder_emit_iso2022_chars: write() failed.");
4367
        status = SIXEL_RUNTIME_ERROR;
×
4368
        goto end;
×
4369
    }
4370

4371
    sixel_allocator_free(encoder->allocator, buf);
×
4372

4373
    status = SIXEL_OK;
×
4374

4375
end:
×
4376
    return status;
×
4377
}
4378

4379

4380
/*
4381
 * This routine is derived from mlterm's drcssixel.c
4382
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
4383
 * The original implementation is credited to Araki Ken.
4384
 * Adjusted here to integrate with libsixel's encoder pipeline.
4385
 */
4386
static SIXELSTATUS
4387
sixel_encoder_emit_drcsmmv2_chars(
×
4388
    sixel_encoder_t *encoder,
4389
    sixel_frame_t *frame
4390
)
4391
{
4392
    char *buf_p, *buf;
×
4393
    int col, row;
×
4394
    char ibytes[3] = { 0x20, 0x00, 0x00 };
×
4395
    int is_96cs;
×
4396
    unsigned int charset_no;
×
4397
    unsigned int code;
×
4398
    int num_cols, num_rows;
×
4399
    SIXELSTATUS status;
×
4400
    size_t alloc_size;
×
4401
    int nwrite;
×
4402
    int target_fd;
×
4403
    int chunk_size;
×
4404
    int fill;
×
4405

4406
    charset_no = encoder->drcs_charset_no;
×
4407
    if (charset_no == 0u) {
×
4408
        charset_no = 1u;
4409
    }
4410
    if (encoder->drcs_mmv == 0) {
×
4411
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
4412
        ibytes[1] = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
4413
        fill = 0;
×
4414
    } else if (encoder->drcs_mmv == 1) {
×
4415
        is_96cs = 0;
×
4416
        ibytes[1] = (int)(charset_no + 0x3fu);
×
4417
        fill = 0;
×
4418
    } else if (encoder->drcs_mmv == 2) {
×
4419
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
4420
        ibytes[1] = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
4421
        fill = 0;
×
4422
    } else {  /* v3 */
4423
        is_96cs = 0;
×
4424
        ibytes[1] = (int)(((charset_no - 1u) / 63u) + 0x20u);
×
4425
        ibytes[2] = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
4426
        fill = 1;
×
4427
    }
4428
    if (fill) {
×
4429
        code = 0x100000 + (charset_no - 1u) * 94;
×
4430
    } else {
4431
        code = 0x100020 + (is_96cs ? 0x80 : 0) + ibytes[1] * 0x100;
×
4432
    }
4433
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
4434
             / encoder->cell_width;
4435
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
4436
             / encoder->cell_height;
4437

4438
    /* cols x rows x 4(out of BMP) + rows(LFs) */
4439
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
4440
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
4441
    if (buf == NULL) {
×
4442
        sixel_helper_set_additional_message(
×
4443
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
4444
        status = SIXEL_BAD_ALLOCATION;
×
4445
        goto end;
×
4446
    }
4447

4448
    for (row = 0; row < num_rows; row++) {
×
4449
        for (col = 0; col < num_cols; col++) {
×
4450
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
4451
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
4452
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
4453
            *(buf_p++) = (code & 0x3f) | 0x80;
×
4454
            code++;
×
4455
            if (! fill) {
×
4456
                if ((code & 0x7f) == 0x0) {
×
4457
                    if (ibytes[1] == 0x7e) {
×
4458
                        is_96cs = 1 - is_96cs;
×
4459
                        ibytes[1] = '0';
×
4460
                    } else {
4461
                        ibytes[1]++;
×
4462
                    }
4463
                    code = 0x100020 + (is_96cs ? 0x80 : 0) + ibytes[1] * 0x100;
×
4464
                }
4465
            }
4466
        }
4467
        *(buf_p++) = '\n';
×
4468
    }
4469

4470
    if (encoder->tile_outfd >= 0) {
×
4471
        target_fd = encoder->tile_outfd;
4472
    } else {
4473
        target_fd = encoder->outfd;
×
4474
    }
4475

4476
    chunk_size = (int)(buf_p - buf);
×
4477
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
4478
                                          buf,
4479
                                          chunk_size,
4480
                                          target_fd);
4481
    if (nwrite != chunk_size) {
×
4482
        sixel_helper_set_additional_message(
×
4483
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
4484
        status = SIXEL_RUNTIME_ERROR;
×
4485
        goto end;
×
4486
    }
4487

4488
    sixel_allocator_free(encoder->allocator, buf);
×
4489

4490
    status = SIXEL_OK;
×
4491

4492
end:
×
4493
    return status;
×
4494
}
4495

4496
static SIXELSTATUS
4497
sixel_encoder_encode_frame(
552✔
4498
    sixel_encoder_t *encoder,
4499
    sixel_frame_t   *frame,
4500
    sixel_output_t  *output)
4501
{
4502
    SIXELSTATUS status = SIXEL_FALSE;
552✔
4503
    sixel_dither_t *dither = NULL;
552✔
4504
    int height;
552✔
4505
    int is_animation = 0;
552✔
4506
    int nwrite;
552✔
4507
    int drcs_is_96cs_param;
552✔
4508
    char drcs_designate_str[4] = { 0x20, 0x20, 0x40, 0x00 };
552✔
4509
    char buf[256];
552✔
4510
    sixel_write_function fn_write;
552✔
4511
    sixel_write_function write_callback;
552✔
4512
    sixel_write_function scroll_callback;
552✔
4513
    void *write_priv;
552✔
4514
    void *scroll_priv;
552✔
4515
    sixel_encoder_output_probe_t probe;
552✔
4516
    sixel_encoder_output_probe_t scroll_probe;
552✔
4517
    sixel_assessment_t *assessment;
552✔
4518
    int width_before;
552✔
4519
    int height_before;
552✔
4520
    int width_after;
552✔
4521
    int height_after;
552✔
4522
    int target_pixelformat;
552✔
4523
    int frame_colorspace;
552✔
4524
    sixel_palette_async_job_t palette_job;
552✔
4525
    sixel_dither_t *async_dither;
552✔
4526
    int palette_job_started;
552✔
4527
    int palette_job_initialized;
552✔
4528
    sixel_encoding_planner_t *planner;
552✔
4529

4530
    fn_write = sixel_write_callback;
552✔
4531
    write_callback = sixel_write_callback;
552✔
4532
    scroll_callback = sixel_write_callback;
552✔
4533
    write_priv = &encoder->outfd;
552✔
4534
    scroll_priv = &encoder->outfd;
552✔
4535
    probe.encoder = NULL;
552✔
4536
    probe.base_write = NULL;
552✔
4537
    probe.base_priv = NULL;
552✔
4538
    scroll_probe.encoder = NULL;
552✔
4539
    scroll_probe.base_write = NULL;
552✔
4540
    scroll_probe.base_priv = NULL;
552✔
4541
    assessment = NULL;
552✔
4542
    async_dither = NULL;
552✔
4543
    palette_job_started = 0;
552✔
4544
    palette_job_initialized = 0;
552✔
4545
    planner = NULL;
552✔
4546
    if (encoder != NULL) {
552!
4547
        assessment = encoder->assessment_observer;
552✔
4548
        planner = &encoder->planner;
552✔
4549
    }
4550
    if (planner != NULL) {
552!
4551
        sixel_encoding_planner_init(planner);
552✔
4552
    }
4553
    if (assessment != NULL) {
552✔
4554
        if (encoder->clipfirst) {
3!
4555
            sixel_assessment_stage_transition(
×
4556
                assessment,
4557
                SIXEL_ASSESSMENT_STAGE_CROP);
4558
        } else {
4559
            sixel_assessment_stage_transition(
3✔
4560
                assessment,
4561
                SIXEL_ASSESSMENT_STAGE_SCALE);
4562
        }
4563
    }
4564

4565
    frame_colorspace = sixel_frame_get_colorspace(frame);
552✔
4566
    target_pixelformat = sixel_encoder_pixelformat_for_colorspace(
552✔
4567
        encoder->working_colorspace,
4568
        encoder->prefer_float32);
4569

4570
    /*
4571
     * Build the thread allocation plan up front so palette sampling does not
4572
     * spawn extra workers when resize/clip/colorspace conversion already have
4573
     * work to do on the main path.
4574
     */
4575
    if (planner != NULL) {
552!
4576
        sixel_encoding_planner_plan(planner,
552✔
4577
                                    encoder,
4578
                                    frame);
4579
    }
4580

4581
    /*
4582
     * Launch a palette worker as soon as the frame is loaded. The worker uses
4583
     * a clipped, down-sampled copy so palette build overlaps
4584
     * resize/clip/colorspace conversion on the main thread.
4585
     */
4586
    if (encoder->palette_job_enabled != 0
552!
4587
        && planner != NULL
552!
4588
        && planner->allow_palette_async != 0
552!
4589
        && encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT
18!
4590
        && encoder->dither_cache == NULL
18!
4591
        && (sixel_frame_get_pixelformat(frame)
18!
4592
            & SIXEL_FORMATTYPE_PALETTE) == 0) {
18✔
4593
        status = sixel_encoder_palette_job_init(&palette_job,
16✔
4594
                                                encoder->allocator);
4595
        if (SIXEL_SUCCEEDED(status)) {
16!
4596
            palette_job_initialized = 1;
16✔
4597
            status = sixel_encoder_palette_job_launch(
16✔
4598
                &palette_job,
4599
                frame,
4600
                target_pixelformat,
4601
                encoder);
4602
            if (SIXEL_SUCCEEDED(status)) {
16!
4603
                palette_job_started = 1;
4604
            } else {
4605
                sixel_encoder_palette_job_dispose(&palette_job);
×
4606
                palette_job_initialized = 0;
×
4607
            }
4608
        }
4609
    }
4610

4611
    /*
4612
     *  Geometry timeline:
4613
     *
4614
     *      +-------+    +------+    +---------------+
4615
     *      | scale | -> | crop | -> | color convert |
4616
     *      +-------+    +------+    +---------------+
4617
     *
4618
     *  The order of the first two blocks mirrors `-c`, so we hop between
4619
     *  SCALE and CROP depending on `clipfirst`.
4620
     */
4621

4622
    if (encoder->clipfirst) {
552✔
4623
        width_before = sixel_frame_get_width(frame);
6✔
4624
        height_before = sixel_frame_get_height(frame);
6✔
4625
        sixel_encoder_log_stage(encoder,
6✔
4626
                                frame,
4627
                                "crop",
4628
                                "worker",
4629
                                "start",
4630
                                "size=%dx%d",
4631
                                width_before,
4632
                                height_before);
4633
        status = sixel_encoder_do_clip(encoder, frame);
6✔
4634
        if (SIXEL_FAILED(status)) {
6!
4635
            goto end;
×
4636
        }
4637
        width_after = sixel_frame_get_width(frame);
6✔
4638
        height_after = sixel_frame_get_height(frame);
6✔
4639
        sixel_encoder_log_stage(encoder,
6✔
4640
                                frame,
4641
                                "crop",
4642
                                "worker",
4643
                                "finish",
4644
                                "result=%dx%d",
4645
                                width_after,
4646
                                height_after);
4647
        if (assessment != NULL) {
6!
4648
            sixel_assessment_stage_transition(
×
4649
                assessment,
4650
                SIXEL_ASSESSMENT_STAGE_SCALE);
4651
        }
4652
        width_before = sixel_frame_get_width(frame);
6✔
4653
        height_before = sixel_frame_get_height(frame);
6✔
4654
        sixel_encoder_log_stage(encoder,
6✔
4655
                                frame,
4656
                                "scale",
4657
                                "worker",
4658
                                "start",
4659
                                "size=%dx%d",
4660
                                width_before,
4661
                                height_before);
4662
        status = sixel_encoder_do_resize(encoder, frame);
6✔
4663
        if (SIXEL_FAILED(status)) {
6!
4664
            goto end;
×
4665
        }
4666
        width_after = sixel_frame_get_width(frame);
6✔
4667
        height_after = sixel_frame_get_height(frame);
6✔
4668
        sixel_encoder_log_stage(encoder,
6✔
4669
                                frame,
4670
                                "scale",
4671
                                "worker",
4672
                                "finish",
4673
                                "result=%dx%d",
4674
                                width_after,
4675
                                height_after);
4676
    } else {
4677
        width_before = sixel_frame_get_width(frame);
546✔
4678
        height_before = sixel_frame_get_height(frame);
546✔
4679
        sixel_encoder_log_stage(encoder,
546✔
4680
                                frame,
4681
                                "scale",
4682
                                "worker",
4683
                                "start",
4684
                                "size=%dx%d",
4685
                                width_before,
4686
                                height_before);
4687
        status = sixel_encoder_do_resize(encoder, frame);
546✔
4688
        if (SIXEL_FAILED(status)) {
546✔
4689
            goto end;
6✔
4690
        }
4691
        width_after = sixel_frame_get_width(frame);
540✔
4692
        height_after = sixel_frame_get_height(frame);
540✔
4693
        sixel_encoder_log_stage(encoder,
540✔
4694
                                frame,
4695
                                "scale",
4696
                                "worker",
4697
                                "finish",
4698
                                "result=%dx%d",
4699
                                width_after,
4700
                                height_after);
4701
        if (assessment != NULL) {
540✔
4702
            sixel_assessment_stage_transition(
3✔
4703
                assessment,
4704
                SIXEL_ASSESSMENT_STAGE_CROP);
4705
        }
4706
        width_before = sixel_frame_get_width(frame);
540✔
4707
        height_before = sixel_frame_get_height(frame);
540✔
4708
        sixel_encoder_log_stage(encoder,
540✔
4709
                                frame,
4710
                                "crop",
4711
                                "worker",
4712
                                "start",
4713
                                "size=%dx%d",
4714
                                width_before,
4715
                                height_before);
4716
        status = sixel_encoder_do_clip(encoder, frame);
540✔
4717
        if (SIXEL_FAILED(status)) {
540!
4718
            goto end;
×
4719
        }
4720
        width_after = sixel_frame_get_width(frame);
540✔
4721
        height_after = sixel_frame_get_height(frame);
540✔
4722
        sixel_encoder_log_stage(encoder,
540✔
4723
                                frame,
4724
                                "crop",
4725
                                "worker",
4726
                                "finish",
4727
                                "result=%dx%d",
4728
                                width_after,
4729
                                height_after);
4730
    }
4731

4732
    if (assessment != NULL) {
546✔
4733
        sixel_assessment_stage_transition(
3✔
4734
            assessment,
4735
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
4736
    }
4737

4738
    /*
4739
     * Promote to float formats when the user opts in via the environment or
4740
     * the precision CLI flag. The selection mirrors the requested working
4741
     * colorspace to avoid losing detail before palette generation.
4742
     */
4743
    sixel_encoder_log_stage(encoder,
546✔
4744
                            frame,
4745
                            "colorspace",
4746
                            "worker",
4747
                            "start",
4748
                            "current=%d target=%d",
4749
                            frame_colorspace,
4750
                            encoder->working_colorspace);
4751

4752
    if (encoder->working_colorspace != SIXEL_COLORSPACE_GAMMA
546!
4753
        || encoder->prefer_float32 != 0) {
546!
4754
        status = sixel_frame_set_pixelformat(
×
4755
            frame,
4756
            target_pixelformat);
4757
        if (SIXEL_FAILED(status)) {
×
4758
            goto end;
×
4759
        }
4760
    }
4761

4762
    if (assessment != NULL) {
546✔
4763
        sixel_assessment_stage_transition(
3✔
4764
            assessment,
4765
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
4766
    }
4767

4768
    sixel_encoder_log_stage(encoder,
546✔
4769
                            frame,
4770
                            "colorspace",
4771
                            "worker",
4772
                            "finish",
4773
                            "result=%d",
4774
                            sixel_frame_get_colorspace(frame));
4775

4776
    if (palette_job_started != 0) {
546✔
4777
        status = sixel_encoder_palette_job_wait(&palette_job, &async_dither);
16✔
4778
        sixel_encoder_palette_job_dispose(&palette_job);
16✔
4779
        palette_job_initialized = 0;
16✔
4780
        if (SIXEL_SUCCEEDED(status) && async_dither != NULL) {
16!
4781
            dither = async_dither;
16✔
4782
        } else {
4783
            palette_job_started = 0;
×
4784
            async_dither = NULL;
×
4785
        }
4786
    }
4787

4788
    if (palette_job_started == 0) {
16✔
4789
        status = sixel_encoder_prepare_palette(encoder, frame, &dither);
530✔
4790
        if (status != SIXEL_OK) {
530!
4791
            dither = NULL;
×
4792
            goto end;
×
4793
        }
4794
        if (palette_job_initialized != 0) {
1!
4795
            sixel_encoder_palette_job_dispose(&palette_job);
4796
            palette_job_initialized = 0;
4797
        }
4798
    }
4799

4800
    if (encoder->dither_cache != NULL) {
546!
4801
        encoder->dither_cache = dither;
×
4802
        sixel_dither_ref(dither);
×
4803
    }
4804

4805
    if (encoder->fdrcs) {
546!
4806
        status = sixel_encoder_ensure_cell_size(encoder);
×
4807
        if (SIXEL_FAILED(status)) {
×
4808
            goto end;
×
4809
        }
4810
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
4811
            sixel_helper_set_additional_message(
×
4812
                "drcs option cannot be used together with macro output.");
4813
            status = SIXEL_BAD_ARGUMENT;
×
4814
            goto end;
×
4815
        }
4816
    }
4817

4818
    /* evaluate -v option: print palette */
4819
    if (encoder->verbose) {
546✔
4820
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
4821
            sixel_debug_print_palette(dither);
3✔
4822
        }
4823
    }
4824

4825
    /* evaluate -d option: set method for diffusion */
4826
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
546✔
4827
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
546✔
4828
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
546✔
4829

4830
    /* evaluate -C option: set complexion score */
4831
    if (encoder->complexion > 1) {
546✔
4832
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
4833
    }
4834

4835
    if (output) {
546!
4836
        sixel_output_ref(output);
×
4837
        if (encoder->assessment_observer != NULL) {
×
4838
            probe.encoder = encoder;
×
4839
            probe.base_write = fn_write;
×
4840
            probe.base_priv = &encoder->outfd;
×
4841
            write_callback = sixel_write_with_probe;
×
4842
            write_priv = &probe;
×
4843
        }
4844
    } else {
4845
        /* create output context */
4846
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
546✔
4847
            /* -u or -n option */
4848
            fn_write = sixel_hex_write_callback;
4849
        } else {
4850
            fn_write = sixel_write_callback;
546✔
4851
        }
4852
        write_callback = fn_write;
546✔
4853
        write_priv = &encoder->outfd;
546✔
4854
        if (encoder->assessment_observer != NULL) {
546✔
4855
            probe.encoder = encoder;
3✔
4856
            probe.base_write = fn_write;
3✔
4857
            probe.base_priv = &encoder->outfd;
3✔
4858
            write_callback = sixel_write_with_probe;
3✔
4859
            write_priv = &probe;
3✔
4860
        }
4861
        status = sixel_output_new(&output,
546✔
4862
                                  write_callback,
4863
                                  write_priv,
4864
                                  encoder->allocator);
4865
        if (SIXEL_FAILED(status)) {
546!
4866
            goto end;
×
4867
        }
4868
    }
4869

4870
    if (encoder->fdrcs) {
546!
4871
        sixel_output_set_skip_dcs_envelope(output, 1);
×
4872
        sixel_output_set_skip_header(output, 1);
×
4873
    }
4874

4875
    sixel_output_set_8bit_availability(output, encoder->f8bit);
546✔
4876
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
546✔
4877
    sixel_output_set_palette_type(output, encoder->palette_type);
546✔
4878
    sixel_output_set_penetrate_multiplexer(
546✔
4879
        output, encoder->penetrate_multiplexer);
4880
    sixel_output_set_encode_policy(output, encoder->encode_policy);
546✔
4881
    sixel_output_set_ormode(output, encoder->ormode);
546✔
4882

4883
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
546✔
4884
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
108✔
4885
            is_animation = 1;
4886
        }
4887
        height = sixel_frame_get_height(frame);
108✔
4888
        if (encoder->assessment_observer != NULL) {
108!
4889
            scroll_probe.encoder = encoder;
×
4890
            scroll_probe.base_write = sixel_write_callback;
×
4891
            scroll_probe.base_priv = &encoder->outfd;
×
4892
            scroll_callback = sixel_write_with_probe;
×
4893
            scroll_priv = &scroll_probe;
×
4894
        } else {
4895
            scroll_callback = sixel_write_callback;
4896
            scroll_priv = &encoder->outfd;
4897
        }
4898
        (void) sixel_tty_scroll(scroll_callback,
108✔
4899
                                scroll_priv,
4900
                                encoder->outfd,
4901
                                height,
4902
                                is_animation);
4903
    }
4904

4905
    if (encoder->cancel_flag && *encoder->cancel_flag) {
546!
4906
        status = SIXEL_INTERRUPTED;
×
4907
        goto end;
×
4908
    }
4909

4910
    if (encoder->fdrcs) {  /* -@ option */
546!
4911
        if (encoder->drcs_mmv == 0) {
×
4912
            drcs_is_96cs_param =
×
4913
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
4914
            drcs_designate_str[1] =
×
4915
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
4916
            drcs_designate_str[2] = 0x00;
×
4917
        } else if (encoder->drcs_mmv == 1) {
×
4918
            drcs_is_96cs_param = 0;
×
4919
            drcs_designate_str[1] =
×
4920
                (int)(encoder->drcs_charset_no + 0x3fu);
×
4921
            drcs_designate_str[2] = 0x00;
×
4922
        } else if (encoder->drcs_mmv == 2) {
×
4923
            drcs_is_96cs_param =
×
4924
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
4925
            drcs_designate_str[1] =
×
4926
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
4927
            drcs_designate_str[2] = 0x00;
×
4928
        } else {
4929
            drcs_is_96cs_param = 0;
×
4930
            drcs_designate_str[1] =
×
4931
                (int)(((encoder->drcs_charset_no - 1u) / 63u) + 0x20u);
×
4932
            drcs_designate_str[2] =
×
4933
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
4934
            drcs_designate_str[3] = 0x00;
×
4935
        }
4936
        nwrite = sixel_compat_snprintf(
×
4937
            buf,
4938
            sizeof(buf),
4939
            "%s%sh%s1;0;0;%d;1;3;%d;%d{%s",
4940
            (encoder->drcs_mmv > 0) ? (
×
4941
                encoder->f8bit ? "\233?8800": "\033[?8800"
×
4942
            ): "",
4943
            (encoder->drcs_mmv >= 3) ? (
×
4944
                encoder->f8bit ? ";8801": ";8801"
4945
            ): "",
4946
            encoder->f8bit ? "\220": "\033P",
×
4947
            encoder->cell_width,
4948
            encoder->cell_height,
4949
            drcs_is_96cs_param,
4950
            drcs_designate_str);
4951
        if (nwrite < 0) {
×
4952
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4953
            sixel_helper_set_additional_message(
×
4954
                "sixel_encoder_encode_frame: command format failed.");
4955
            goto end;
×
4956
        }
4957
        nwrite = write_callback(buf, nwrite, write_priv);
×
4958
        if (nwrite < 0) {
×
4959
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4960
            sixel_helper_set_additional_message(
×
4961
                "sixel_encoder_encode_frame: write() failed.");
4962
            goto end;
×
4963
        }
4964
    }
4965

4966
    /* output sixel: junction of multi-frame processing strategy */
4967
    if (encoder->fuse_macro) {  /* -u option */
546✔
4968
        /* use macro */
4969
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
81✔
4970
    } else if (encoder->macro_number >= 0) { /* -n option */
465✔
4971
        /* use macro */
4972
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
4973
    } else {
4974
        /* do not use macro */
4975
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
462✔
4976
    }
4977
    if (SIXEL_FAILED(status)) {
546!
4978
        goto end;
×
4979
    }
4980

4981
    if (encoder->cancel_flag && *encoder->cancel_flag) {
546!
4982
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
4983
        if (nwrite < 0) {
×
4984
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4985
            sixel_helper_set_additional_message(
×
4986
                "sixel_encoder_encode_frame: write_callback() failed.");
4987
            goto end;
×
4988
        }
4989
        status = SIXEL_INTERRUPTED;
4990
    }
4991
    if (SIXEL_FAILED(status)) {
1!
4992
        goto end;
4993
    }
4994

4995
    if (encoder->fdrcs) {  /* -@ option */
546!
4996
        if (encoder->f8bit) {
×
4997
            nwrite = write_callback("\234", 1, write_priv);
×
4998
        } else {
4999
            nwrite = write_callback("\033\\", 2, write_priv);
×
5000
        }
5001
        if (nwrite < 0) {
×
5002
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
5003
            sixel_helper_set_additional_message(
×
5004
                "sixel_encoder_encode_frame: write_callback() failed.");
5005
            goto end;
×
5006
        }
5007

5008
        if (encoder->tile_outfd >= 0) {
×
5009
            if (encoder->drcs_mmv == 0) {
×
5010
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
5011
                if (SIXEL_FAILED(status)) {
×
5012
                    goto end;
5013
                }
5014
            } else {
5015
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
5016
                if (SIXEL_FAILED(status)) {
×
5017
                    goto end;
5018
                }
5019
            }
5020
        }
5021
    }
5022

5023

5024
end:
546✔
5025
    if (palette_job_initialized != 0) {
552!
5026
        if (palette_job_started != 0 && async_dither == NULL) {
×
5027
            (void)sixel_encoder_palette_job_wait(&palette_job,
×
5028
                                                 &async_dither);
5029
        }
5030
        if (async_dither != NULL && dither == NULL) {
×
5031
            sixel_dither_unref(async_dither);
×
5032
            async_dither = NULL;
×
5033
        }
5034
        sixel_encoder_palette_job_dispose(&palette_job);
×
5035
    }
5036
    if (output) {
552✔
5037
        sixel_output_unref(output);
546✔
5038
    }
5039
    if (dither) {
552✔
5040
        sixel_dither_unref(dither);
546✔
5041
    }
5042

5043
    return status;
552✔
5044
}
5045

5046

5047
/* create encoder object */
5048
SIXELAPI SIXELSTATUS
5049
sixel_encoder_new(
576✔
5050
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
5051
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
5052
                                                  default allocator */
5053
{
5054
    SIXELSTATUS status = SIXEL_FALSE;
576✔
5055
    char const *env_default_bgcolor = NULL;
576✔
5056
    char const *env_default_ncolors = NULL;
576✔
5057
    char const *env_prefer_float32 = NULL;
576✔
5058
    char const *env_lookup_policy = NULL;
576✔
5059
    char const *env_sample_target = NULL;
576✔
5060
    int ncolors;
576✔
5061
    int prefer_float32;
576✔
5062
    int env_match_value;
576✔
5063
    size_t parsed_sample_target;
576✔
5064
    int has_sample_target;
576✔
5065
    sixel_option_choice_result_t match_result;
576✔
5066
    char match_detail[128];
576✔
5067

5068
    parsed_sample_target = 0u;
576✔
5069
    has_sample_target = 0;
576✔
5070

5071
    if (allocator == NULL) {
576!
5072
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
576✔
5073
        if (SIXEL_FAILED(status)) {
576!
5074
            goto end;
×
5075
        }
5076
    } else {
5077
        sixel_allocator_ref(allocator);
×
5078
    }
5079

5080
    *ppencoder
576✔
5081
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
576✔
5082
                                                    sizeof(sixel_encoder_t));
5083
    if (*ppencoder == NULL) {
576!
5084
        sixel_helper_set_additional_message(
×
5085
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
5086
        status = SIXEL_BAD_ALLOCATION;
×
5087
        sixel_allocator_unref(allocator);
×
5088
        goto end;
×
5089
    }
5090

5091
    (*ppencoder)->ref                   = 1;
576✔
5092
    (*ppencoder)->reqcolors             = (-1);
576✔
5093
    (*ppencoder)->palette_sample_target = 0u;
576✔
5094
    (*ppencoder)->palette_sample_override = 0;
576✔
5095
    (*ppencoder)->force_palette         = 0;
576✔
5096
    (*ppencoder)->mapfile               = NULL;
576✔
5097
    (*ppencoder)->palette_output        = NULL;
576✔
5098
    (*ppencoder)->loader_order          = NULL;
576✔
5099
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
576✔
5100
    (*ppencoder)->builtin_palette       = 0;
576✔
5101
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
576✔
5102
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
576✔
5103
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
576✔
5104
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
576✔
5105
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
576✔
5106
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
576✔
5107
    (*ppencoder)->quantize_model        = SIXEL_QUANTIZE_MODEL_AUTO;
576✔
5108
    (*ppencoder)->final_merge_mode      = SIXEL_FINAL_MERGE_AUTO;
576✔
5109
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
576✔
5110
    (*ppencoder)->sixel_reversible      = 0;
576✔
5111
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
576✔
5112
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
576✔
5113
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
576✔
5114
    (*ppencoder)->f8bit                 = 0;
576✔
5115
    (*ppencoder)->has_gri_arg_limit     = 0;
576✔
5116
    (*ppencoder)->finvert               = 0;
576✔
5117
    (*ppencoder)->fuse_macro            = 0;
576✔
5118
    (*ppencoder)->fdrcs                 = 0;
576✔
5119
    (*ppencoder)->fignore_delay         = 0;
576✔
5120
    (*ppencoder)->complexion            = 1;
576✔
5121
    (*ppencoder)->fstatic               = 0;
576✔
5122
    (*ppencoder)->cell_width            = 0;
576✔
5123
    (*ppencoder)->cell_height           = 0;
576✔
5124
    (*ppencoder)->pixelwidth            = (-1);
576✔
5125
    (*ppencoder)->pixelheight           = (-1);
576✔
5126
    (*ppencoder)->percentwidth          = (-1);
576✔
5127
    (*ppencoder)->percentheight         = (-1);
576✔
5128
    (*ppencoder)->clipx                 = 0;
576✔
5129
    (*ppencoder)->clipy                 = 0;
576✔
5130
    (*ppencoder)->clipwidth             = 0;
576✔
5131
    (*ppencoder)->clipheight            = 0;
576✔
5132
    (*ppencoder)->clipfirst             = 0;
576✔
5133
    (*ppencoder)->macro_number          = (-1);
576✔
5134
    (*ppencoder)->verbose               = 0;
576✔
5135
    (*ppencoder)->penetrate_multiplexer = 0;
576✔
5136
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
576✔
5137
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
576✔
5138
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
576✔
5139
    (*ppencoder)->prefer_float32        = 0;
576✔
5140
    (*ppencoder)->ormode                = 0;
576✔
5141
    (*ppencoder)->pipe_mode             = 0;
576✔
5142
    (*ppencoder)->bgcolor               = NULL;
576✔
5143
    (*ppencoder)->outfd                 = STDOUT_FILENO;
576✔
5144
    (*ppencoder)->tile_outfd            = (-1);
576✔
5145
    (*ppencoder)->finsecure             = 0;
576✔
5146
    (*ppencoder)->cancel_flag           = NULL;
576✔
5147
    (*ppencoder)->dither_cache          = NULL;
576✔
5148
    (*ppencoder)->drcs_charset_no       = 1u;
576✔
5149
    (*ppencoder)->drcs_mmv              = 2;
576✔
5150
    (*ppencoder)->capture_quantized     = 0;
576✔
5151
    (*ppencoder)->capture_source        = 0;
576✔
5152
    (*ppencoder)->capture_pixels        = NULL;
576✔
5153
    (*ppencoder)->capture_pixels_size   = 0;
576✔
5154
    (*ppencoder)->capture_palette       = NULL;
576✔
5155
    (*ppencoder)->capture_palette_size  = 0;
576✔
5156
    (*ppencoder)->capture_pixel_bytes   = 0;
576✔
5157
    (*ppencoder)->capture_width         = 0;
576✔
5158
    (*ppencoder)->capture_height        = 0;
576✔
5159
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
576✔
5160
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
576✔
5161
    (*ppencoder)->capture_ncolors       = 0;
576✔
5162
    (*ppencoder)->capture_valid         = 0;
576✔
5163
    (*ppencoder)->capture_source_frame  = NULL;
576✔
5164
    (*ppencoder)->assessment_observer   = NULL;
576✔
5165
    (*ppencoder)->assessment_json_path  = NULL;
576✔
5166
    (*ppencoder)->assessment_sections   = SIXEL_ASSESSMENT_SECTION_NONE;
576✔
5167
    (*ppencoder)->last_loader_name[0]   = '\0';
576✔
5168
    (*ppencoder)->last_source_path[0]   = '\0';
576✔
5169
    (*ppencoder)->last_input_bytes      = 0u;
576✔
5170
    (*ppencoder)->output_is_png         = 0;
576✔
5171
    (*ppencoder)->output_png_to_stdout  = 0;
576✔
5172
    (*ppencoder)->png_output_path       = NULL;
576✔
5173
    (*ppencoder)->sixel_output_path     = NULL;
576✔
5174
    (*ppencoder)->clipboard_output_active = 0;
576✔
5175
    (*ppencoder)->clipboard_output_format[0] = '\0';
576✔
5176
    (*ppencoder)->clipboard_output_path = NULL;
576✔
5177
    (*ppencoder)->logger                = NULL;
576✔
5178
    (*ppencoder)->parallel_job_id       = -1;
576✔
5179
    (*ppencoder)->palette_job_enabled   = 1;
576✔
5180
    sixel_encoding_planner_init(&(*ppencoder)->planner);
576✔
5181
    (*ppencoder)->allocator             = allocator;
576✔
5182

5183
    prefer_float32 = 0;
576✔
5184
    env_prefer_float32 = sixel_compat_getenv(
576✔
5185
        SIXEL_ENCODER_PRECISION_ENVVAR);
5186
    /*
5187
     * $SIXEL_FLOAT32_DITHER seeds the precision preference and is later
5188
     * overridden by the precision CLI flag when provided.
5189
     */
5190
    prefer_float32 = sixel_encoder_env_prefers_float32(env_prefer_float32);
576✔
5191
    (*ppencoder)->prefer_float32 = prefer_float32;
576✔
5192

5193
    /*
5194
     * $SIXEL_DITHER_LOOKUP_POLICY mirrors the -~ flag so automated wrappers
5195
     * can seed the LUT backend before CLI overrides run.  Invalid prefixes are
5196
     * ignored to avoid hard failures when the environment is user-provided.
5197
     */
5198
    match_detail[0] = '\0';
576✔
5199
    env_lookup_policy = sixel_compat_getenv(
576✔
5200
        SIXEL_ENCODER_LUT_POLICY_ENVVAR);
5201
    if (env_lookup_policy != NULL) {
576!
5202
        match_result = sixel_option_match_choice(
×
5203
            env_lookup_policy,
5204
            g_option_choices_lut_policy,
5205
            sizeof(g_option_choices_lut_policy)
5206
            / sizeof(g_option_choices_lut_policy[0]),
5207
            &env_match_value,
5208
            match_detail,
5209
            sizeof(match_detail));
5210
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5211
            (*ppencoder)->lut_policy = env_match_value;
×
5212
        }
5213
    }
5214

5215
    env_sample_target = sixel_compat_getenv(
576✔
5216
        SIXEL_ENCODER_SAMPLE_TARGET_ENVVAR);
5217
    if (env_sample_target != NULL) {
576!
5218
        has_sample_target = sixel_encoder_parse_sample_target(
×
5219
            env_sample_target,
5220
            &parsed_sample_target);
5221
        if (has_sample_target) {
×
5222
            (*ppencoder)->palette_sample_target = parsed_sample_target;
×
5223
            (*ppencoder)->palette_sample_override = 1;
×
5224
        }
5225
    }
5226

5227
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
5228
    env_default_bgcolor = sixel_compat_getenv("SIXEL_BGCOLOR");
576✔
5229
    if (env_default_bgcolor != NULL) {
576!
5230
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
5231
                                         env_default_bgcolor,
5232
                                         allocator);
5233
        if (SIXEL_FAILED(status)) {
×
5234
            goto error;
×
5235
        }
5236
    }
5237

5238
    /* evaluate environment variable ${SIXEL_COLORS} */
5239
    env_default_ncolors = sixel_compat_getenv("SIXEL_COLORS");
576✔
5240
    if (env_default_ncolors) {
576!
5241
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
5242
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
5243
            (*ppencoder)->reqcolors = ncolors;
×
5244
        }
5245
    }
5246

5247
    /* success */
5248
    status = SIXEL_OK;
576✔
5249

5250
    goto end;
576✔
5251

5252
error:
×
5253
    sixel_allocator_free(allocator, *ppencoder);
×
5254
    sixel_allocator_unref(allocator);
×
5255
    *ppencoder = NULL;
×
5256

5257
end:
576✔
5258
    return status;
576✔
5259
}
5260

5261

5262
/* create encoder object (deprecated version) */
5263
SIXELAPI /* deprecated */ sixel_encoder_t *
5264
sixel_encoder_create(void)
×
5265
{
5266
    SIXELSTATUS status = SIXEL_FALSE;
×
5267
    sixel_encoder_t *encoder = NULL;
×
5268

5269
    status = sixel_encoder_new(&encoder, NULL);
×
5270
    if (SIXEL_FAILED(status)) {
×
5271
        return NULL;
5272
    }
5273

5274
    return encoder;
×
5275
}
5276

5277

5278
/* destroy encoder object */
5279
static void
5280
sixel_encoder_destroy(sixel_encoder_t *encoder)
573✔
5281
{
5282
    sixel_allocator_t *allocator;
573✔
5283

5284
    if (encoder) {
573!
5285
        allocator = encoder->allocator;
573✔
5286
        sixel_allocator_free(allocator, encoder->mapfile);
573✔
5287
        sixel_allocator_free(allocator, encoder->palette_output);
573✔
5288
        sixel_allocator_free(allocator, encoder->loader_order);
573✔
5289
        sixel_allocator_free(allocator, encoder->bgcolor);
573✔
5290
        sixel_dither_unref(encoder->dither_cache);
573✔
5291
        if (encoder->outfd
573!
5292
            && encoder->outfd != STDOUT_FILENO
1!
5293
            && encoder->outfd != STDERR_FILENO) {
573✔
5294
            (void)sixel_compat_close(encoder->outfd);
63✔
5295
        }
5296
        if (encoder->tile_outfd >= 0
573!
5297
            && encoder->tile_outfd != encoder->outfd
×
5298
            && encoder->tile_outfd != STDOUT_FILENO
×
5299
            && encoder->tile_outfd != STDERR_FILENO) {
×
5300
            (void)sixel_compat_close(encoder->tile_outfd);
×
5301
        }
5302
        if (encoder->capture_source_frame != NULL) {
573✔
5303
            sixel_frame_unref(encoder->capture_source_frame);
3✔
5304
        }
5305
        if (encoder->clipboard_output_path != NULL) {
573!
5306
            (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
5307
            encoder->clipboard_output_path = NULL;
×
5308
        }
5309
        encoder->clipboard_output_active = 0;
573✔
5310
        encoder->clipboard_output_format[0] = '\0';
573✔
5311
        sixel_allocator_free(allocator, encoder->capture_pixels);
573✔
5312
        sixel_allocator_free(allocator, encoder->capture_palette);
573✔
5313
        sixel_allocator_free(allocator, encoder->png_output_path);
573✔
5314
        sixel_allocator_free(allocator, encoder->sixel_output_path);
573✔
5315
        sixel_allocator_free(allocator, encoder);
573✔
5316
        sixel_allocator_unref(allocator);
573✔
5317
    }
5318
}
573✔
5319

5320

5321
/* increase reference count of encoder object (thread-unsafe) */
5322
SIXELAPI void
5323
sixel_encoder_ref(sixel_encoder_t *encoder)
1,290✔
5324
{
5325
    /* TODO: be thread safe */
5326
    ++encoder->ref;
1,290✔
5327
}
908✔
5328

5329

5330
/* decrease reference count of encoder object (thread-unsafe) */
5331
SIXELAPI void
5332
sixel_encoder_unref(sixel_encoder_t *encoder)
1,863✔
5333
{
5334
    /* TODO: be thread safe */
5335
    if (encoder != NULL && --encoder->ref == 0) {
1,863!
5336
        sixel_encoder_destroy(encoder);
573✔
5337
    }
5338
}
1,863✔
5339

5340

5341
/* set cancel state flag to encoder object */
5342
SIXELAPI SIXELSTATUS
5343
sixel_encoder_set_cancel_flag(
432✔
5344
    sixel_encoder_t /* in */ *encoder,
5345
    int             /* in */ *cancel_flag
5346
)
5347
{
5348
    SIXELSTATUS status = SIXEL_OK;
432✔
5349

5350
    encoder->cancel_flag = cancel_flag;
432✔
5351

5352
    return status;
432✔
5353
}
5354

5355

5356
/*
5357
 * parse_assessment_sections() translates a comma-separated section list into
5358
 * the bitmask understood by the assessment back-end.  The accepted grammar is
5359
 * intentionally small so that the CLI contract stays predictable:
5360
 *
5361
 *     list := section {"," section}
5362
 *     section := name | name "@" view
5363
 *
5364
 * Each name maps to a bit flag while the optional view toggles the encoded vs
5365
 * quantized quality comparison.  The helper folds case, trims ASCII
5366
 * whitespace, and rejects mixed encoded/quantized requests so that the caller
5367
 * can rely on a single coherent capture strategy.
5368
 */
5369
static int
5370
parse_assessment_sections(char const *spec,
6✔
5371
                          unsigned int *out_sections)
5372
{
5373
    char *copy;
6✔
5374
    char *cursor;
6✔
5375
    char *token;
6✔
5376
    char *context;
6✔
5377
    unsigned int sections;
6✔
5378
    unsigned int view;
6✔
5379
    int result;
6✔
5380
    size_t length;
6✔
5381
    size_t spec_len;
6✔
5382
    char *at;
6✔
5383
    char *base;
6✔
5384
    char *variant;
6✔
5385
    char *p;
6✔
5386

5387
    if (spec == NULL || out_sections == NULL) {
6!
5388
        return -1;
5389
    }
5390
    spec_len = strlen(spec);
6✔
5391
    copy = (char *)malloc(spec_len + 1u);
6✔
5392
    if (copy == NULL) {
6!
5393
        return -1;
5394
    }
5395
    memcpy(copy, spec, spec_len + 1u);
6✔
5396
    cursor = copy;
6✔
5397
    sections = 0u;
6✔
5398
    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
6✔
5399
    result = 0;
6✔
5400
    context = NULL;
6✔
5401
    while (result == 0) {
6!
5402
        token = sixel_compat_strtok(cursor, ",", &context);
12✔
5403
        if (token == NULL) {
12✔
5404
            break;
5405
        }
5406
        cursor = NULL;
6✔
5407
        while (*token == ' ' || *token == '\t') {
6!
5408
            token += 1;
×
5409
        }
5410
        length = strlen(token);
6✔
5411
        while (length > 0u &&
6!
5412
               (token[length - 1u] == ' ' || token[length - 1u] == '\t')) {
6!
5413
            token[length - 1u] = '\0';
×
5414
            length -= 1u;
×
5415
        }
5416
        if (*token == '\0') {
6!
5417
            result = -1;
5418
            break;
5419
        }
5420
        for (p = token; *p != '\0'; ++p) {
42✔
5421
            *p = (char)tolower((unsigned char)*p);
36✔
5422
        }
5423
        at = strchr(token, '@');
6✔
5424
        if (at != NULL) {
6!
5425
            *at = '\0';
×
5426
            variant = at + 1;
×
5427
            if (*variant == '\0') {
×
5428
                result = -1;
5429
                break;
5430
            }
5431
        } else {
5432
            variant = NULL;
5433
        }
5434
        base = token;
6✔
5435
        if (strcmp(base, "all") == 0) {
6!
5436
            sections |= SIXEL_ASSESSMENT_SECTION_ALL;
×
5437
            if (variant != NULL && variant[0] != '\0') {
×
5438
                if (strcmp(variant, "quantized") == 0) {
×
5439
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
5440
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
5441
                        result = -1;
5442
                    }
5443
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
5444
                } else if (strcmp(variant, "encoded") == 0) {
×
5445
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
5446
                        result = -1;
5447
                    }
5448
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
5449
                } else {
5450
                    result = -1;
5451
                }
5452
            }
5453
        } else if (strcmp(base, "basic") == 0) {
6✔
5454
            sections |= SIXEL_ASSESSMENT_SECTION_BASIC;
3✔
5455
            if (variant != NULL) {
3!
5456
                result = -1;
5457
            }
5458
        } else if (strcmp(base, "performance") == 0) {
3!
5459
            sections |= SIXEL_ASSESSMENT_SECTION_PERFORMANCE;
×
5460
            if (variant != NULL) {
×
5461
                result = -1;
5462
            }
5463
        } else if (strcmp(base, "size") == 0) {
3!
5464
            sections |= SIXEL_ASSESSMENT_SECTION_SIZE;
×
5465
            if (variant != NULL) {
×
5466
                result = -1;
5467
            }
5468
        } else if (strcmp(base, "quality") == 0) {
3!
5469
            sections |= SIXEL_ASSESSMENT_SECTION_QUALITY;
3✔
5470
            if (variant != NULL && variant[0] != '\0') {
3!
5471
                if (strcmp(variant, "quantized") == 0) {
×
5472
                    if (view != SIXEL_ASSESSMENT_VIEW_ENCODED &&
×
5473
                            view != SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
5474
                        result = -1;
5475
                    }
5476
                    view = SIXEL_ASSESSMENT_VIEW_QUANTIZED;
5477
                } else if (strcmp(variant, "encoded") == 0) {
×
5478
                    if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
5479
                        result = -1;
5480
                    }
5481
                    view = SIXEL_ASSESSMENT_VIEW_ENCODED;
5482
                } else {
5483
                    result = -1;
5484
                }
5485
            } else if (variant != NULL) {
1!
5486
                if (view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
×
5487
                    result = -1;
5488
                }
5489
                view = SIXEL_ASSESSMENT_VIEW_ENCODED;
5490
            }
5491
        } else {
5492
            result = -1;
5493
        }
5494
    }
5495
    if (result == 0) {
6!
5496
        if (sections == 0u) {
6!
5497
            result = -1;
5498
        } else {
5499
            if ((sections & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u &&
6!
5500
                    view == SIXEL_ASSESSMENT_VIEW_QUANTIZED) {
5501
                sections |= SIXEL_ASSESSMENT_VIEW_QUANTIZED;
×
5502
            }
5503
            *out_sections = sections;
6✔
5504
        }
5505
    }
5506
    free(copy);
6✔
5507
    return result;
6✔
5508
}
5509

5510

5511
static int
5512
is_png_target(char const *path)
66✔
5513
{
5514
    size_t len;
66✔
5515
    int matched;
66✔
5516

5517
    /*
5518
     * Detect PNG requests from explicit prefixes or a ".png" suffix:
5519
     *
5520
     *   argument
5521
     *   |
5522
     *   v
5523
     *   .............. . p n g
5524
     *   ^             ^^^^^^^^^
5525
     *   |             +-- case-insensitive suffix comparison
5526
     *   +-- accepts the "png:" inline prefix used for stdout capture
5527
     */
5528

5529
    len = 0;
66✔
5530
    matched = 0;
66✔
5531

5532
    if (path == NULL) {
66!
5533
        return 0;
5534
    }
5535

5536
    if (strncmp(path, "png:", 4) == 0) {
66✔
5537
        return path[4] != '\0';
6✔
5538
    }
5539

5540
    len = strlen(path);
60✔
5541
    if (len >= 4) {
60✔
5542
        matched = (tolower((unsigned char)path[len - 4]) == '.')
57✔
5543
            && (tolower((unsigned char)path[len - 3]) == 'p')
39✔
5544
            && (tolower((unsigned char)path[len - 2]) == 'n')
3!
5545
            && (tolower((unsigned char)path[len - 1]) == 'g');
60!
5546
    }
5547

5548
    return matched;
5549
}
5550

5551

5552
static char const *
5553
png_target_payload_view(char const *argument)
9✔
5554
{
5555
    /*
5556
     * Inline PNG targets split into either a prefix/payload pair or rely on
5557
     * a simple file-name suffix:
5558
     *
5559
     *   +--------------+------------+-------------+
5560
     *   | form         | payload    | destination |
5561
     *   +--------------+------------+-------------+
5562
     *   | png:         | -          | stdout      |
5563
     *   | png:         | filename   | filesystem  |
5564
     *   | *.png        | filename   | filesystem  |
5565
     *   +--------------+------------+-------------+
5566
     *
5567
     * The caller only needs the payload column, so we expose it here.  When
5568
     * the user omits the prefix we simply echo the original pointer so the
5569
     * caller can copy the value verbatim.
5570
     */
5571
    if (argument == NULL) {
9!
5572
        return NULL;
5573
    }
5574
    if (strncmp(argument, "png:", 4) == 0) {
9✔
5575
        return argument + 4;
6✔
5576
    }
5577

5578
    return argument;
5579
}
5580

5581
static void
5582
encoder_normalise_windows_drive_path(char *path)
9✔
5583
{
5584
#if defined(_WIN32)
5585
    size_t length;
5586

5587
    /*
5588
     * MSYS-like environments forward POSIX-looking absolute paths to native
5589
     * binaries.  When a user writes "/d/..." MSYS converts the command line to
5590
     * UTF-16 and preserves the literal bytes.  The Windows CRT, however,
5591
     * expects "d:/..." or "d:\...".  The tiny state machine below rewrites the
5592
     * leading token so the runtime resolves the drive correctly:
5593
     *
5594
     *   input     normalised
5595
     *   |         |
5596
     *   v         v
5597
     *   / d / ... d : / ...
5598
     *
5599
     * The body keeps the rest of the string intact so UNC paths ("//server")
5600
     * and relative references pass through untouched.
5601
     */
5602

5603
    length = 0u;
5604

5605
    if (path == NULL) {
5606
        return;
5607
    }
5608

5609
    length = strlen(path);
5610
    if (length >= 3u
5611
            && path[0] == '/'
5612
            && ((path[1] >= 'A' && path[1] <= 'Z')
5613
                || (path[1] >= 'a' && path[1] <= 'z'))
5614
            && path[2] == '/') {
5615
        path[0] = path[1];
5616
        path[1] = ':';
5617
    }
5618
#else
5619
    (void)path;
9✔
5620
#endif
5621
}
9✔
5622

5623

5624
static int
5625
is_dev_null_path(char const *path)
×
5626
{
5627
    if (path == NULL || path[0] == '\0') {
×
5628
        return 0;
5629
    }
5630
#if defined(_WIN32)
5631
    if (_stricmp(path, "nul") == 0) {
5632
        return 1;
5633
    }
5634
#endif
5635
    return strcmp(path, "/dev/null") == 0;
×
5636
}
5637

5638

5639
static int
5640
sixel_encoder_threads_token_is_auto(char const *text)
×
5641
{
5642
    if (text == NULL) {
×
5643
        return 0;
5644
    }
5645

5646
    if ((text[0] == 'a' || text[0] == 'A') &&
×
5647
        (text[1] == 'u' || text[1] == 'U') &&
×
5648
        (text[2] == 't' || text[2] == 'T') &&
×
5649
        (text[3] == 'o' || text[3] == 'O') &&
×
5650
        text[4] == '\0') {
×
5651
        return 1;
×
5652
    }
5653

5654
    return 0;
5655
}
5656

5657
static int
5658
sixel_encoder_parse_threads_argument(char const *text, int *value)
×
5659
{
5660
    long parsed;
×
5661
    char *endptr;
×
5662

5663
    parsed = 0L;
×
5664
    endptr = NULL;
×
5665

5666
    if (text == NULL || value == NULL) {
×
5667
        return 0;
5668
    }
5669

5670
    if (sixel_encoder_threads_token_is_auto(text) != 0) {
×
5671
        *value = 0;
×
5672
        return 1;
×
5673
    }
5674

5675
    errno = 0;
×
5676
    parsed = strtol(text, &endptr, 10);
×
5677
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
×
5678
        return 0;
5679
    }
5680

5681
    if (parsed < 1L || parsed > (long)INT_MAX) {
×
5682
        return 0;
5683
    }
5684

5685
    *value = (int)parsed;
×
5686
    return 1;
×
5687
}
5688

5689
/* set an option flag to encoder object */
5690
SIXELAPI SIXELSTATUS
5691
sixel_encoder_setopt(
831✔
5692
    sixel_encoder_t /* in */ *encoder,
5693
    int             /* in */ arg,
5694
    char const      /* in */ *value)
5695
{
5696
    SIXELSTATUS status = SIXEL_FALSE;
831✔
5697
    int number;
831✔
5698
    int parsed;
831✔
5699
    char unit[32];
831✔
5700
    char lowered[16];
831✔
5701
    size_t len;
831✔
5702
    size_t i;
831✔
5703
    long parsed_reqcolors;
831✔
5704
    char *endptr;
831✔
5705
    int forced_palette;
831✔
5706
    char *opt_copy;
831✔
5707
    char const *drcs_arg_delim;
831✔
5708
    char const *drcs_arg_charset;
831✔
5709
    char const *drcs_arg_second_delim;
831✔
5710
    char const *drcs_arg_path;
831✔
5711
    size_t drcs_arg_path_length;
831✔
5712
    size_t drcs_segment_length;
831✔
5713
    char drcs_segment[32];
831✔
5714
    int drcs_mmv_value;
831✔
5715
    long drcs_charset_value;
831✔
5716
    unsigned int drcs_charset_limit;
831✔
5717
    sixel_option_choice_result_t match_result;
831✔
5718
    int match_value;
831✔
5719
    char match_detail[128];
831✔
5720
    char match_message[256];
831✔
5721
    int png_argument_has_prefix = 0;
831✔
5722
    char const *png_path_view = NULL;
831✔
5723
    size_t png_path_length;
831✔
5724
    char cell_message[256];
831✔
5725
    char const *cell_detail;
831✔
5726
    unsigned int path_flags;
831✔
5727
    char const *mapfile_view;
831✔
5728
    int path_check;
831✔
5729

5730
    sixel_encoder_ref(encoder);
831✔
5731
    opt_copy = NULL;
831✔
5732
    path_flags = 0u;
831✔
5733
    mapfile_view = NULL;
831✔
5734
    path_check = 0;
831✔
5735

5736
    switch(arg) {
831!
5737
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
66✔
5738
        if (*value == '\0') {
66!
5739
            sixel_helper_set_additional_message(
×
5740
                "no file name specified.");
5741
            status = SIXEL_BAD_ARGUMENT;
×
5742
            goto end;
×
5743
        }
5744
        if (is_png_target(value)) {
66✔
5745
            encoder->output_is_png = 1;
9✔
5746
            png_argument_has_prefix =
18✔
5747
                (value != NULL)
5748
                && (strncmp(value, "png:", 4) == 0);
9!
5749
            png_path_view = png_target_payload_view(value);
9✔
5750
            if (png_argument_has_prefix
9!
5751
                    && (png_path_view == NULL
6!
5752
                        || png_path_view[0] == '\0')) {
6!
5753
                sixel_helper_set_additional_message(
×
5754
                    "sixel_encoder_setopt: missing target after the \"png:\" "
5755
                    "prefix. use png:- or png:<path> with a non-empty payload."
5756
                );
5757
                status = SIXEL_BAD_ARGUMENT;
×
5758
                goto end;
×
5759
            }
5760
            encoder->output_png_to_stdout =
12✔
5761
                (png_path_view != NULL)
5762
                && (strcmp(png_path_view, "-") == 0);
9!
5763
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
9✔
5764
            encoder->png_output_path = NULL;
9✔
5765
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
9✔
5766
            encoder->sixel_output_path = NULL;
9✔
5767
            if (! encoder->output_png_to_stdout) {
9!
5768
                /*
5769
                 * +-----------------------------------------+
5770
                 * |  PNG target normalization               |
5771
                 * +-----------------------------------------+
5772
                 * |  Raw input  |  Stored file path         |
5773
                 * |-------------+---------------------------|
5774
                 * |  png:-      |  "-" (stdout sentinel)    |
5775
                 * |  png:/foo   |  "/foo"                   |
5776
                 * +-----------------------------------------+
5777
                 * Strip the "png:" prefix so the decoder can
5778
                 * pass the true filesystem path to libpng
5779
                 * while the CLI retains its shorthand.
5780
                 */
5781
                png_path_view = value;
9✔
5782
                if (strncmp(value, "png:", 4) == 0) {
9✔
5783
                    png_path_view = value + 4;
6✔
5784
                }
5785
                if (png_path_view[0] == '\0') {
9!
5786
                    sixel_helper_set_additional_message(
×
5787
                        "sixel_encoder_setopt: PNG output path is empty.");
5788
                    status = SIXEL_BAD_ARGUMENT;
×
5789
                    goto end;
×
5790
                }
5791
                png_path_length = strlen(png_path_view);
9✔
5792
                encoder->png_output_path =
18✔
5793
                    (char *)sixel_allocator_malloc(
9✔
5794
                        encoder->allocator, png_path_length + 1u);
5795
                if (encoder->png_output_path == NULL) {
9!
5796
                    sixel_helper_set_additional_message(
×
5797
                        "sixel_encoder_setopt: sixel_allocator_malloc() "
5798
                        "failed for PNG output path.");
5799
                    status = SIXEL_BAD_ALLOCATION;
×
5800
                    goto end;
×
5801
                }
5802
                if (png_path_view != NULL) {
9!
5803
                    (void)sixel_compat_strcpy(encoder->png_output_path,
9✔
5804
                                              png_path_length + 1u,
5805
                                              png_path_view);
5806
                } else {
5807
                    encoder->png_output_path[0] = '\0';
5808
                }
5809
                encoder_normalise_windows_drive_path(encoder->png_output_path);
9✔
5810
            }
5811
        } else {
5812
            encoder->output_is_png = 0;
57✔
5813
            encoder->output_png_to_stdout = 0;
57✔
5814
            png_argument_has_prefix = 0;
57✔
5815
            png_path_view = NULL;
57✔
5816
            sixel_allocator_free(encoder->allocator, encoder->png_output_path);
57✔
5817
            encoder->png_output_path = NULL;
57✔
5818
            sixel_allocator_free(encoder->allocator, encoder->sixel_output_path);
57✔
5819
            encoder->sixel_output_path = NULL;
57✔
5820
            if (encoder->clipboard_output_path != NULL) {
57!
5821
                (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
5822
                sixel_allocator_free(encoder->allocator,
×
5823
                                     encoder->clipboard_output_path);
×
5824
                encoder->clipboard_output_path = NULL;
×
5825
            }
5826
            encoder->clipboard_output_active = 0;
57✔
5827
            encoder->clipboard_output_format[0] = '\0';
57✔
5828
            {
5829
                sixel_clipboard_spec_t clipboard_spec;
57✔
5830
                SIXELSTATUS clip_status;
57✔
5831
                char *spool_path;
57✔
5832
                int spool_fd;
57✔
5833

5834
                clipboard_spec.is_clipboard = 0;
57✔
5835
                clipboard_spec.format[0] = '\0';
57✔
5836
                clip_status = SIXEL_OK;
57✔
5837
                spool_path = NULL;
57✔
5838
                spool_fd = (-1);
57✔
5839

5840
                if (sixel_clipboard_parse_spec(value, &clipboard_spec)
57!
5841
                        && clipboard_spec.is_clipboard) {
×
5842
                    clip_status = clipboard_create_spool(
×
5843
                        encoder->allocator,
5844
                        "clipboard-out",
5845
                        &spool_path,
5846
                        &spool_fd);
5847
                    if (SIXEL_FAILED(clip_status)) {
×
5848
                        status = clip_status;
×
5849
                        goto end;
×
5850
                    }
5851
                    clipboard_select_format(
×
5852
                        encoder->clipboard_output_format,
×
5853
                        sizeof(encoder->clipboard_output_format),
5854
                        clipboard_spec.format,
5855
                        "sixel");
5856
                    if (encoder->outfd
×
5857
                            && encoder->outfd != STDOUT_FILENO
1!
5858
                            && encoder->outfd != STDERR_FILENO) {
×
5859
                        (void)sixel_compat_close(encoder->outfd);
×
5860
                    }
5861
                    encoder->outfd = spool_fd;
×
5862
                    spool_fd = (-1);
×
5863
                    encoder->sixel_output_path = spool_path;
×
5864
                    encoder->clipboard_output_path = spool_path;
×
5865
                    spool_path = NULL;
×
5866
                    encoder->clipboard_output_active = 1;
×
5867
                    break;
×
5868
                }
5869

5870
                if (spool_fd >= 0) {
57!
5871
                    (void)sixel_compat_close(spool_fd);
5872
                }
5873
                if (spool_path != NULL) {
57!
5874
                    sixel_allocator_free(encoder->allocator, spool_path);
5875
                }
5876
            }
2!
5877
            if (strcmp(value, "-") != 0) {
57!
5878
                encoder->sixel_output_path = (char *)sixel_allocator_malloc(
114✔
5879
                    encoder->allocator, strlen(value) + 1);
57✔
5880
                if (encoder->sixel_output_path == NULL) {
57!
5881
                    sixel_helper_set_additional_message(
×
5882
                        "sixel_encoder_setopt: malloc() failed for output path.");
5883
                    status = SIXEL_BAD_ALLOCATION;
×
5884
                    goto end;
×
5885
                }
5886
                (void)sixel_compat_strcpy(encoder->sixel_output_path,
57✔
5887
                                          strlen(value) + 1,
57✔
5888
                                          value);
5889
            }
5890
        }
5891

5892
        if (!encoder->clipboard_output_active && strcmp(value, "-") != 0) {
66!
5893
            if (encoder->outfd && encoder->outfd != STDOUT_FILENO) {
66!
5894
                (void)sixel_compat_close(encoder->outfd);
×
5895
            }
5896
            encoder->outfd = sixel_compat_open(value,
66✔
5897
                                               O_RDWR | O_CREAT | O_TRUNC,
5898
                                               S_IRUSR | S_IWUSR);
5899
        }
5900
        break;
5901
    case SIXEL_OPTFLAG_ASSESSMENT:  /* a */
6✔
5902
        if (parse_assessment_sections(value,
6!
5903
                                      &encoder->assessment_sections) != 0) {
5904
            sixel_helper_set_additional_message(
×
5905
                "sixel_encoder_setopt: cannot parse assessment section list");
5906
            status = SIXEL_BAD_ARGUMENT;
×
5907
            goto end;
×
5908
        }
5909
        break;
5910
    case SIXEL_OPTFLAG_ASSESSMENT_FILE:  /* J */
3✔
5911
        encoder->assessment_json_path = value;
3✔
5912
        break;
3✔
5913
    case SIXEL_OPTFLAG_7BIT_MODE:  /* 7 */
15✔
5914
        encoder->f8bit = 0;
15✔
5915
        break;
15✔
5916
    case SIXEL_OPTFLAG_8BIT_MODE:  /* 8 */
18✔
5917
        encoder->f8bit = 1;
18✔
5918
        break;
18✔
5919
    case SIXEL_OPTFLAG_6REVERSIBLE:  /* 6 */
×
5920
        encoder->sixel_reversible = 1;
×
5921
        break;
×
5922
    case SIXEL_OPTFLAG_HAS_GRI_ARG_LIMIT:  /* R */
×
5923
        encoder->has_gri_arg_limit = 1;
×
5924
        break;
×
5925
    case SIXEL_OPTFLAG_PRECISION:  /* . */
×
5926
        match_result = sixel_option_match_choice(
×
5927
            value,
5928
            g_option_choices_precision,
5929
            sizeof(g_option_choices_precision) /
5930
                sizeof(g_option_choices_precision[0]),
5931
            &match_value,
5932
            match_detail,
5933
            sizeof(match_detail));
5934
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5935
            status = sixel_encoder_apply_precision_override(
×
5936
                encoder,
5937
                (sixel_encoder_precision_mode_t)match_value);
5938
            if (SIXEL_FAILED(status)) {
×
5939
                goto end;
×
5940
            }
5941
        } else {
5942
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5943
                sixel_option_report_ambiguous_prefix(
×
5944
                    value,
5945
                    match_detail,
5946
                    match_message,
5947
                    sizeof(match_message));
5948
            } else {
5949
                sixel_option_report_invalid_choice(
×
5950
                    "precision accepts auto, 8bit, or float32.",
5951
                    match_detail,
5952
                    match_message,
5953
                    sizeof(match_message));
5954
            }
5955
            status = SIXEL_BAD_ARGUMENT;
×
5956
            goto end;
×
5957
        }
5958
        break;
5959
    case SIXEL_OPTFLAG_THREADS:  /* = */
×
5960
        if (sixel_encoder_parse_threads_argument(value, &number) == 0) {
×
5961
            sixel_helper_set_additional_message(
×
5962
                "threads accepts positive integers or 'auto'.");
5963
            status = SIXEL_BAD_ARGUMENT;
×
5964
            goto end;
×
5965
        }
5966
        sixel_set_threads(number);
×
5967
        break;
×
5968
    case SIXEL_OPTFLAG_COLORS:  /* p */
33✔
5969
        forced_palette = 0;
33✔
5970
        errno = 0;
33✔
5971
        endptr = NULL;
33✔
5972
        if (*value == '!' && value[1] == '\0') {
33!
5973
            /*
5974
             * Force the default palette size even when the median cut
5975
             * finished early.
5976
             *
5977
             *   requested colors
5978
             *          |
5979
             *          v
5980
             *        [ 256 ]  <--- "-p!" triggers this shortcut
5981
             */
5982
            parsed_reqcolors = SIXEL_PALETTE_MAX;
5983
            forced_palette = 1;
5984
        } else {
5985
            if (value[0] == '-') {
33!
5986
                /*
5987
                 * Negative palette sizes are rejected explicitly here rather
5988
                 * than depending on strtol() overflow behavior. MSVCRT's
5989
                 * strtol() can accept "-1" and return a valid number, so we
5990
                 * enforce the positive range before attempting to parse.
5991
                 */
5992
                sixel_helper_set_additional_message(
3✔
5993
                    "-p/--colors parameter must be 1 or more.");
5994
                status = SIXEL_BAD_ARGUMENT;
3✔
5995
                goto end;
3✔
5996
            }
5997
            parsed_reqcolors = strtol(value, &endptr, 10);
30✔
5998
            if (endptr != NULL && *endptr == '!') {
30!
5999
                forced_palette = 1;
×
6000
                ++endptr;
×
6001
            }
6002
            if (errno == ERANGE || endptr == value) {
30!
6003
                sixel_helper_set_additional_message(
×
6004
                    "cannot parse -p/--colors option.");
6005
                status = SIXEL_BAD_ARGUMENT;
×
6006
                goto end;
×
6007
            }
6008
            if (endptr != NULL && *endptr != '\0') {
30!
6009
                sixel_helper_set_additional_message(
×
6010
                    "cannot parse -p/--colors option.");
6011
                status = SIXEL_BAD_ARGUMENT;
×
6012
                goto end;
×
6013
            }
6014
        }
6015
        if (parsed_reqcolors < 1) {
30!
6016
            sixel_helper_set_additional_message(
×
6017
                "-p/--colors parameter must be 1 or more.");
6018
            status = SIXEL_BAD_ARGUMENT;
×
6019
            goto end;
×
6020
        }
6021
        if (parsed_reqcolors > SIXEL_PALETTE_MAX) {
30!
6022
            sixel_helper_set_additional_message(
×
6023
                "-p/--colors parameter must be less then or equal to 256.");
6024
            status = SIXEL_BAD_ARGUMENT;
×
6025
            goto end;
×
6026
        }
6027
        encoder->reqcolors = (int)parsed_reqcolors;
30✔
6028
        encoder->force_palette = forced_palette;
30✔
6029
        break;
30✔
6030
    case SIXEL_OPTFLAG_MAPFILE:  /* m */
27✔
6031
        mapfile_view = sixel_palette_strip_prefix(value, NULL);
27✔
6032
        if (mapfile_view == NULL) {
27!
6033
            mapfile_view = value;
×
6034
        }
6035
        path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
27✔
6036
            SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
6037
            SIXEL_OPTION_PATH_ALLOW_REMOTE |
6038
            SIXEL_OPTION_PATH_ALLOW_EMPTY;
6039
        path_check = sixel_option_validate_filesystem_path(
27✔
6040
            value,
6041
            mapfile_view,
6042
            path_flags);
6043
        if (path_check != 0) {
27✔
6044
            status = SIXEL_BAD_ARGUMENT;
3✔
6045
            goto end;
3✔
6046
        }
6047
        if (encoder->mapfile) {
24✔
6048
            sixel_allocator_free(encoder->allocator, encoder->mapfile);
3✔
6049
        }
6050
        encoder->mapfile = arg_strdup(value, encoder->allocator);
24✔
6051
        if (encoder->mapfile == NULL) {
24!
6052
            sixel_helper_set_additional_message(
×
6053
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
6054
            status = SIXEL_BAD_ALLOCATION;
×
6055
            goto end;
×
6056
        }
6057
        encoder->color_option = SIXEL_COLOR_OPTION_MAPFILE;
24✔
6058
        break;
24✔
6059
    case SIXEL_OPTFLAG_MAPFILE_OUTPUT:  /* M */
×
6060
        if (value == NULL || *value == '\0') {
×
6061
            sixel_helper_set_additional_message(
×
6062
                "sixel_encoder_setopt: mapfile-output path is empty.");
6063
            status = SIXEL_BAD_ARGUMENT;
×
6064
            goto end;
×
6065
        }
6066
        opt_copy = arg_strdup(value, encoder->allocator);
×
6067
        if (opt_copy == NULL) {
×
6068
            sixel_helper_set_additional_message(
×
6069
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
6070
            status = SIXEL_BAD_ALLOCATION;
×
6071
            goto end;
×
6072
        }
6073
        status = sixel_encoder_enable_quantized_capture(encoder, 1);
×
6074
        if (SIXEL_FAILED(status)) {
×
6075
            sixel_allocator_free(encoder->allocator, opt_copy);
6076
            goto end;
6077
        }
6078
        sixel_allocator_free(encoder->allocator, encoder->palette_output);
×
6079
        encoder->palette_output = opt_copy;
×
6080
        opt_copy = NULL;
×
6081
        break;
×
6082
    case SIXEL_OPTFLAG_MONOCHROME:  /* e */
15✔
6083
        encoder->color_option = SIXEL_COLOR_OPTION_MONOCHROME;
15✔
6084
        break;
15✔
6085
    case SIXEL_OPTFLAG_HIGH_COLOR:  /* I */
42✔
6086
        encoder->color_option = SIXEL_COLOR_OPTION_HIGHCOLOR;
42✔
6087
        break;
42✔
6088
    case SIXEL_OPTFLAG_BUILTIN_PALETTE:  /* b */
33✔
6089
        match_result = sixel_option_match_choice(
33✔
6090
            value,
6091
            g_option_choices_builtin_palette,
6092
            sizeof(g_option_choices_builtin_palette) /
6093
            sizeof(g_option_choices_builtin_palette[0]),
6094
            &match_value,
6095
            match_detail,
6096
            sizeof(match_detail));
6097
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
33✔
6098
            encoder->builtin_palette = match_value;
30✔
6099
        } else {
6100
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
6101
                sixel_option_report_ambiguous_prefix(value,
×
6102
                                              match_detail,
6103
                                              match_message,
6104
                                              sizeof(match_message));
6105
            } else {
6106
                sixel_option_report_invalid_choice(
3✔
6107
                    "cannot parse builtin palette option.",
6108
                    match_detail,
6109
                    match_message,
6110
                    sizeof(match_message));
6111
            }
6112
            status = SIXEL_BAD_ARGUMENT;
3✔
6113
            goto end;
3✔
6114
        }
6115
        encoder->color_option = SIXEL_COLOR_OPTION_BUILTIN;
30✔
6116
        break;
30✔
6117
    case SIXEL_OPTFLAG_DIFFUSION:  /* d */
78✔
6118
        match_result = sixel_option_match_choice(
78✔
6119
            value,
6120
            g_option_choices_diffusion,
6121
            sizeof(g_option_choices_diffusion) /
6122
            sizeof(g_option_choices_diffusion[0]),
6123
            &match_value,
6124
            match_detail,
6125
            sizeof(match_detail));
6126
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
78✔
6127
            encoder->method_for_diffuse = match_value;
69✔
6128
        } else {
6129
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
9✔
6130
                sixel_option_report_ambiguous_prefix(value,
3✔
6131
                                              match_detail,
6132
                                              match_message,
6133
                                              sizeof(match_message));
6134
            } else {
6135
                sixel_option_report_invalid_choice(
6✔
6136
                    "specified diffusion method is not supported.",
6137
                    match_detail,
6138
                    match_message,
6139
                    sizeof(match_message));
6140
            }
6141
            status = SIXEL_BAD_ARGUMENT;
9✔
6142
            goto end;
9✔
6143
        }
6144
        break;
69✔
6145
    case SIXEL_OPTFLAG_DIFFUSION_SCAN:  /* y */
3✔
6146
        match_result = sixel_option_match_choice(
3✔
6147
            value,
6148
            g_option_choices_diffusion_scan,
6149
            sizeof(g_option_choices_diffusion_scan) /
6150
            sizeof(g_option_choices_diffusion_scan[0]),
6151
            &match_value,
6152
            match_detail,
6153
            sizeof(match_detail));
6154
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
3!
6155
            encoder->method_for_scan = match_value;
3✔
6156
        } else {
6157
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6158
                sixel_option_report_ambiguous_prefix(value,
×
6159
                                              match_detail,
6160
                                              match_message,
6161
                                              sizeof(match_message));
6162
            } else {
6163
                sixel_option_report_invalid_choice(
×
6164
                    "specified diffusion scan is not supported.",
6165
                    match_detail,
6166
                    match_message,
6167
                    sizeof(match_message));
6168
            }
6169
            status = SIXEL_BAD_ARGUMENT;
×
6170
            goto end;
×
6171
        }
6172
        break;
3✔
6173
    case SIXEL_OPTFLAG_DIFFUSION_CARRY:  /* Y */
×
6174
        match_result = sixel_option_match_choice(
×
6175
            value,
6176
            g_option_choices_diffusion_carry,
6177
            sizeof(g_option_choices_diffusion_carry) /
6178
            sizeof(g_option_choices_diffusion_carry[0]),
6179
            &match_value,
6180
            match_detail,
6181
            sizeof(match_detail));
6182
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6183
            encoder->method_for_carry = match_value;
×
6184
        } else {
6185
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6186
                sixel_option_report_ambiguous_prefix(value,
×
6187
                                              match_detail,
6188
                                              match_message,
6189
                                              sizeof(match_message));
6190
            } else {
6191
                sixel_option_report_invalid_choice(
×
6192
                    "specified diffusion carry mode is not supported.",
6193
                    match_detail,
6194
                    match_message,
6195
                    sizeof(match_message));
6196
            }
6197
            status = SIXEL_BAD_ARGUMENT;
×
6198
            goto end;
×
6199
        }
6200
        break;
×
6201
    case SIXEL_OPTFLAG_FIND_LARGEST:  /* f */
15✔
6202
        if (value != NULL) {
15!
6203
            match_result = sixel_option_match_choice(
15✔
6204
                value,
6205
                g_option_choices_find_largest,
6206
                sizeof(g_option_choices_find_largest) /
6207
                sizeof(g_option_choices_find_largest[0]),
6208
                &match_value,
6209
                match_detail,
6210
                sizeof(match_detail));
6211
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
6212
                encoder->method_for_largest = match_value;
12✔
6213
            } else {
6214
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
6215
                    sixel_option_report_ambiguous_prefix(value,
×
6216
                                                  match_detail,
6217
                                                  match_message,
6218
                                                  sizeof(match_message));
6219
                } else {
6220
                    sixel_option_report_invalid_choice(
3✔
6221
                        "specified finding method is not supported.",
6222
                        match_detail,
6223
                        match_message,
6224
                        sizeof(match_message));
6225
                }
6226
                status = SIXEL_BAD_ARGUMENT;
3✔
6227
                goto end;
3✔
6228
            }
6229
        }
6230
        break;
6231
    case SIXEL_OPTFLAG_SELECT_COLOR:  /* s */
21✔
6232
        match_result = sixel_option_match_choice(
21✔
6233
            value,
6234
            g_option_choices_select_color,
6235
            sizeof(g_option_choices_select_color) /
6236
            sizeof(g_option_choices_select_color[0]),
6237
            &match_value,
6238
            match_detail,
6239
            sizeof(match_detail));
6240
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
21✔
6241
            encoder->method_for_rep = match_value;
15✔
6242
        } else {
6243
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
6✔
6244
                sixel_option_report_ambiguous_prefix(value,
3✔
6245
                                              match_detail,
6246
                                              match_message,
6247
                                              sizeof(match_message));
6248
            } else {
6249
                sixel_option_report_invalid_choice(
3✔
6250
                    "specified finding method is not supported.",
6251
                    match_detail,
6252
                    match_message,
6253
                    sizeof(match_message));
6254
            }
6255
            status = SIXEL_BAD_ARGUMENT;
6✔
6256
            goto end;
6✔
6257
        }
6258
        break;
15✔
6259
    case SIXEL_OPTFLAG_QUANTIZE_MODEL:  /* Q */
×
6260
        match_result = sixel_option_match_choice(
×
6261
            value,
6262
            g_option_choices_quantize_model,
6263
            sizeof(g_option_choices_quantize_model) /
6264
            sizeof(g_option_choices_quantize_model[0]),
6265
            &match_value,
6266
            match_detail,
6267
            sizeof(match_detail));
6268
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6269
            encoder->quantize_model = match_value;
×
6270
        } else {
6271
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6272
                sixel_option_report_ambiguous_prefix(value,
×
6273
                                              match_detail,
6274
                                              match_message,
6275
                                              sizeof(match_message));
6276
            } else {
6277
                sixel_option_report_invalid_choice(
×
6278
                    "sixel_encoder_setopt: unsupported quantize model.",
6279
                    match_detail,
6280
                    match_message,
6281
                    sizeof(match_message));
6282
            }
6283
            status = SIXEL_BAD_ARGUMENT;
×
6284
            goto end;
×
6285
        }
6286
        break;
×
6287
    case SIXEL_OPTFLAG_FINAL_MERGE:  /* F */
×
6288
        match_result = sixel_option_match_choice(
×
6289
            value,
6290
            g_option_choices_final_merge,
6291
            sizeof(g_option_choices_final_merge) /
6292
            sizeof(g_option_choices_final_merge[0]),
6293
            &match_value,
6294
            match_detail,
6295
            sizeof(match_detail));
6296
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6297
            encoder->final_merge_mode = match_value;
×
6298
        } else {
6299
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6300
                sixel_option_report_ambiguous_prefix(value,
×
6301
                                              match_detail,
6302
                                              match_message,
6303
                                              sizeof(match_message));
6304
            } else {
6305
                sixel_option_report_invalid_choice(
×
6306
                    "specified final merge policy is not supported.",
6307
                    match_detail,
6308
                    match_message,
6309
                    sizeof(match_message));
6310
            }
6311
            status = SIXEL_BAD_ARGUMENT;
×
6312
            goto end;
×
6313
        }
6314
        break;
×
6315
    case SIXEL_OPTFLAG_CROP:  /* c */
15✔
6316
        number = sixel_compat_sscanf(value,
15✔
6317
                                     "%dx%d+%d+%d",
6318
                                     &encoder->clipwidth,
6319
                                     &encoder->clipheight,
6320
                                     &encoder->clipx,
6321
                                     &encoder->clipy);
6322
        if (number != 4) {
15!
6323
            status = SIXEL_BAD_ARGUMENT;
×
6324
            goto end;
×
6325
        }
6326
        if (encoder->clipwidth <= 0 || encoder->clipheight <= 0) {
15!
6327
            status = SIXEL_BAD_ARGUMENT;
×
6328
            goto end;
×
6329
        }
6330
        if (encoder->clipx < 0 || encoder->clipy < 0) {
15!
6331
            status = SIXEL_BAD_ARGUMENT;
×
6332
            goto end;
×
6333
        }
6334
        encoder->clipfirst = 0;
15✔
6335
        break;
15✔
6336
    case SIXEL_OPTFLAG_WIDTH:  /* w */
84✔
6337
        parsed = sixel_compat_sscanf(value,
84✔
6338
                                     "%d%2s",
6339
                                     &number,
6340
                                     unit);
6341
        if (parsed == 2 && strcmp(unit, "%") == 0) {
84!
6342
            if (number <= 0) {
12!
6343
                sixel_helper_set_additional_message(
×
6344
                    "-w/--width percent must be 1 or more.");
6345
                status = SIXEL_BAD_ARGUMENT;
×
6346
                goto end;
×
6347
            }
6348
            encoder->pixelwidth = (-1);
12✔
6349
            encoder->percentwidth = number;
12✔
6350
        } else if (parsed == 2 && strcmp(unit, "c") == 0) {
×
6351
            status = sixel_encoder_ensure_cell_size(encoder);
×
6352
            if (SIXEL_FAILED(status)) {
×
6353
                cell_detail = sixel_helper_get_additional_message();
×
6354
                if (cell_detail != NULL && cell_detail[0] != '\0') {
×
6355
                    (void) snprintf(cell_message,
×
6356
                                    sizeof(cell_message),
6357
                                    "cannot determine terminal cell size for "
6358
                                    "-w/--width option: %s",
6359
                                    cell_detail);
6360
                    sixel_helper_set_additional_message(cell_message);
×
6361
                } else {
6362
                    sixel_helper_set_additional_message(
×
6363
                        "cannot determine terminal cell size for "
6364
                        "-w/--width option.");
6365
                }
6366
                goto end;
×
6367
            }
6368
            /*
6369
             * Terminal cell units map the requested column count to pixels.
6370
             * The cell size probe caches the tty geometry so repeated calls
6371
             * reuse the same measurement.
6372
             */
6373
            if (number <= 0) {
×
6374
                sixel_helper_set_additional_message(
×
6375
                    "-w/--width cells must be 1 or more.");
6376
                status = SIXEL_BAD_ARGUMENT;
×
6377
                goto end;
×
6378
            }
6379
            encoder->pixelwidth = number * encoder->cell_width;
×
6380
            encoder->percentwidth = (-1);
×
6381
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
72!
6382
            if (number <= 0) {
60!
6383
                sixel_helper_set_additional_message(
×
6384
                    "-w/--width must be 1 or more.");
6385
                status = SIXEL_BAD_ARGUMENT;
×
6386
                goto end;
×
6387
            }
6388
            encoder->pixelwidth = number;
60✔
6389
            encoder->percentwidth = (-1);
60✔
6390
        } else if (strcmp(value, "auto") == 0) {
12✔
6391
            encoder->pixelwidth = (-1);
9✔
6392
            encoder->percentwidth = (-1);
9✔
6393
        } else {
6394
            sixel_helper_set_additional_message(
3✔
6395
                "cannot parse -w/--width option.");
6396
            status = SIXEL_BAD_ARGUMENT;
3✔
6397
            goto end;
3✔
6398
        }
6399
        if (encoder->clipwidth) {
81✔
6400
            encoder->clipfirst = 1;
6✔
6401
        }
6402
        break;
6403
    case SIXEL_OPTFLAG_HEIGHT:  /* h */
72✔
6404
#if HAVE_SSCANF_S
6405
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
6406
#else
6407
        parsed = sixel_compat_sscanf(value,
72✔
6408
                                     "%d%2s",
6409
                                     &number,
6410
                                     unit);
6411
#endif  /* HAVE_SSCANF_S */
6412
        if (parsed == 2 && strcmp(unit, "%") == 0) {
72!
6413
            if (number <= 0) {
9!
6414
                sixel_helper_set_additional_message(
×
6415
                    "-h/--height percent must be 1 or more.");
6416
                status = SIXEL_BAD_ARGUMENT;
×
6417
                goto end;
×
6418
            }
6419
            encoder->pixelheight = (-1);
9✔
6420
            encoder->percentheight = number;
9✔
6421
        } else if (parsed == 2 && strcmp(unit, "c") == 0) {
×
6422
            status = sixel_encoder_ensure_cell_size(encoder);
×
6423
            if (SIXEL_FAILED(status)) {
×
6424
                cell_detail = sixel_helper_get_additional_message();
×
6425
                if (cell_detail != NULL && cell_detail[0] != '\0') {
×
6426
                    (void) snprintf(cell_message,
×
6427
                                    sizeof(cell_message),
6428
                                    "cannot determine terminal cell size for "
6429
                                    "-h/--height option: %s",
6430
                                    cell_detail);
6431
                    sixel_helper_set_additional_message(cell_message);
×
6432
                } else {
6433
                    sixel_helper_set_additional_message(
×
6434
                        "cannot determine terminal cell size for "
6435
                        "-h/--height option.");
6436
                }
6437
                goto end;
×
6438
            }
6439
            /*
6440
             * Rows specified in terminal cells use the current tty metrics to
6441
             * translate into pixel counts before scaling.
6442
             */
6443
            if (number <= 0) {
×
6444
                sixel_helper_set_additional_message(
×
6445
                    "-h/--height cells must be 1 or more.");
6446
                status = SIXEL_BAD_ARGUMENT;
×
6447
                goto end;
×
6448
            }
6449
            encoder->pixelheight = number * encoder->cell_height;
×
6450
            encoder->percentheight = (-1);
×
6451
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
63!
6452
            if (number <= 0) {
51!
6453
                sixel_helper_set_additional_message(
×
6454
                    "-h/--height must be 1 or more.");
6455
                status = SIXEL_BAD_ARGUMENT;
×
6456
                goto end;
×
6457
            }
6458
            encoder->pixelheight = number;
51✔
6459
            encoder->percentheight = (-1);
51✔
6460
        } else if (strcmp(value, "auto") == 0) {
12✔
6461
            encoder->pixelheight = (-1);
9✔
6462
            encoder->percentheight = (-1);
9✔
6463
        } else {
6464
            sixel_helper_set_additional_message(
3✔
6465
                "cannot parse -h/--height option.");
6466
            status = SIXEL_BAD_ARGUMENT;
3✔
6467
            goto end;
3✔
6468
        }
6469
        if (encoder->clipheight) {
69✔
6470
            encoder->clipfirst = 1;
3✔
6471
        }
6472
        break;
6473
    case SIXEL_OPTFLAG_RESAMPLING:  /* r */
66✔
6474
        match_result = sixel_option_match_choice(
66✔
6475
            value,
6476
            g_option_choices_resampling,
6477
            sizeof(g_option_choices_resampling) /
6478
            sizeof(g_option_choices_resampling[0]),
6479
            &match_value,
6480
            match_detail,
6481
            sizeof(match_detail));
6482
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
66✔
6483
            encoder->method_for_resampling = match_value;
54✔
6484
        } else {
6485
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
12!
6486
                sixel_option_report_ambiguous_prefix(value,
×
6487
                                              match_detail,
6488
                                              match_message,
6489
                                              sizeof(match_message));
6490
            } else {
6491
                sixel_option_report_invalid_choice(
12✔
6492
                    "specified desampling method is not supported.",
6493
                    match_detail,
6494
                    match_message,
6495
                    sizeof(match_message));
6496
            }
6497
            status = SIXEL_BAD_ARGUMENT;
12✔
6498
            goto end;
12✔
6499
        }
6500
        break;
54✔
6501
    case SIXEL_OPTFLAG_QUALITY:  /* q */
27✔
6502
        match_result = sixel_option_match_choice(
27✔
6503
            value,
6504
            g_option_choices_quality,
6505
            sizeof(g_option_choices_quality) /
6506
            sizeof(g_option_choices_quality[0]),
6507
            &match_value,
6508
            match_detail,
6509
            sizeof(match_detail));
6510
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
27✔
6511
            encoder->quality_mode = match_value;
24✔
6512
        } else {
6513
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
6514
                sixel_option_report_ambiguous_prefix(value,
×
6515
                                              match_detail,
6516
                                              match_message,
6517
                                              sizeof(match_message));
6518
            } else {
6519
                sixel_option_report_invalid_choice(
3✔
6520
                    "cannot parse quality option.",
6521
                    match_detail,
6522
                    match_message,
6523
                    sizeof(match_message));
6524
            }
6525
            status = SIXEL_BAD_ARGUMENT;
3✔
6526
            goto end;
3✔
6527
        }
6528
        break;
24✔
6529
    case SIXEL_OPTFLAG_LOOPMODE:  /* l */
15✔
6530
        match_result = sixel_option_match_choice(
15✔
6531
            value,
6532
            g_option_choices_loopmode,
6533
            sizeof(g_option_choices_loopmode) /
6534
            sizeof(g_option_choices_loopmode[0]),
6535
            &match_value,
6536
            match_detail,
6537
            sizeof(match_detail));
6538
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
6539
            encoder->loop_mode = match_value;
12✔
6540
        } else {
6541
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
6542
                sixel_option_report_ambiguous_prefix(value,
×
6543
                                              match_detail,
6544
                                              match_message,
6545
                                              sizeof(match_message));
6546
            } else {
6547
                sixel_option_report_invalid_choice(
3✔
6548
                    "cannot parse loop-control option.",
6549
                    match_detail,
6550
                    match_message,
6551
                    sizeof(match_message));
6552
            }
6553
            status = SIXEL_BAD_ARGUMENT;
3✔
6554
            goto end;
3✔
6555
        }
6556
        break;
12✔
6557
    case SIXEL_OPTFLAG_PALETTE_TYPE:  /* t */
30✔
6558
        match_result = sixel_option_match_choice(
30✔
6559
            value,
6560
            g_option_choices_palette_type,
6561
            sizeof(g_option_choices_palette_type) /
6562
            sizeof(g_option_choices_palette_type[0]),
6563
            &match_value,
6564
            match_detail,
6565
            sizeof(match_detail));
6566
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
30✔
6567
            encoder->palette_type = match_value;
27✔
6568
        } else {
6569
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
6570
                sixel_option_report_ambiguous_prefix(value,
×
6571
                                              match_detail,
6572
                                              match_message,
6573
                                              sizeof(match_message));
6574
            } else {
6575
                sixel_option_report_invalid_choice(
3✔
6576
                    "cannot parse palette type option.",
6577
                    match_detail,
6578
                    match_message,
6579
                    sizeof(match_message));
6580
            }
6581
            status = SIXEL_BAD_ARGUMENT;
3✔
6582
            goto end;
3✔
6583
        }
6584
        break;
27✔
6585
    case SIXEL_OPTFLAG_BGCOLOR:  /* B */
48✔
6586
        /* parse --bgcolor option */
6587
        if (encoder->bgcolor) {
48✔
6588
            sixel_allocator_free(encoder->allocator, encoder->bgcolor);
6✔
6589
            encoder->bgcolor = NULL;
6✔
6590
        }
6591
        status = sixel_parse_x_colorspec(&encoder->bgcolor,
48✔
6592
                                         value,
6593
                                         encoder->allocator);
6594
        if (SIXEL_FAILED(status)) {
48✔
6595
            sixel_helper_set_additional_message(
21✔
6596
                "cannot parse bgcolor option.");
6597
            status = SIXEL_BAD_ARGUMENT;
21✔
6598
            goto end;
21✔
6599
        }
6600
        break;
6601
    case SIXEL_OPTFLAG_INSECURE:  /* k */
×
6602
        encoder->finsecure = 1;
×
6603
        break;
×
6604
    case SIXEL_OPTFLAG_INVERT:  /* i */
39✔
6605
        encoder->finvert = 1;
39✔
6606
        break;
39✔
6607
    case SIXEL_OPTFLAG_USE_MACRO:  /* u */
6✔
6608
        encoder->fuse_macro = 1;
6✔
6609
        break;
6✔
6610
    case SIXEL_OPTFLAG_MACRO_NUMBER:  /* n */
6611
        encoder->macro_number = atoi(value);
3✔
6612
        if (encoder->macro_number < 0) {
3!
6613
            status = SIXEL_BAD_ARGUMENT;
×
6614
            goto end;
×
6615
        }
6616
        break;
6617
    case SIXEL_OPTFLAG_IGNORE_DELAY:  /* g */
6✔
6618
        encoder->fignore_delay = 1;
6✔
6619
        break;
6✔
6620
    case SIXEL_OPTFLAG_VERBOSE:  /* v */
9✔
6621
        encoder->verbose = 1;
9✔
6622
        sixel_helper_set_loader_trace(1);
9✔
6623
        break;
9✔
6624
    case SIXEL_OPTFLAG_LOADERS:  /* L */
×
6625
        if (encoder->loader_order != NULL) {
×
6626
            sixel_allocator_free(encoder->allocator,
×
6627
                                 encoder->loader_order);
6628
            encoder->loader_order = NULL;
×
6629
        }
6630
        if (value != NULL && *value != '\0') {
×
6631
            encoder->loader_order = arg_strdup(value,
×
6632
                                               encoder->allocator);
6633
            if (encoder->loader_order == NULL) {
×
6634
                sixel_helper_set_additional_message(
×
6635
                    "sixel_encoder_setopt: "
6636
                    "sixel_allocator_malloc() failed.");
6637
                status = SIXEL_BAD_ALLOCATION;
×
6638
                goto end;
×
6639
            }
6640
        }
6641
        break;
6642
    case SIXEL_OPTFLAG_STATIC:  /* S */
6✔
6643
        encoder->fstatic = 1;
6✔
6644
        break;
6✔
6645
    case SIXEL_OPTFLAG_DRCS:  /* @ */
×
6646
        encoder->fdrcs = 1;
×
6647
        drcs_arg_delim = NULL;
×
6648
        drcs_arg_charset = NULL;
×
6649
        drcs_arg_second_delim = NULL;
×
6650
        drcs_arg_path = NULL;
×
6651
        drcs_arg_path_length = 0u;
×
6652
        drcs_segment_length = 0u;
×
6653
        drcs_mmv_value = 2;
×
6654
        drcs_charset_value = 1L;
×
6655
        drcs_charset_limit = 0u;
×
6656
        if (value != NULL && *value != '\0') {
×
6657
            drcs_arg_delim = strchr(value, ':');
×
6658
            if (drcs_arg_delim == NULL) {
×
6659
                drcs_segment_length = strlen(value);
×
6660
                if (drcs_segment_length >= sizeof(drcs_segment)) {
×
6661
                    sixel_helper_set_additional_message(
×
6662
                        "DRCS mapping revision is too long.");
6663
                    status = SIXEL_BAD_ARGUMENT;
×
6664
                    goto end;
×
6665
                }
6666
                memcpy(drcs_segment, value, drcs_segment_length);
×
6667
                drcs_segment[drcs_segment_length] = '\0';
×
6668
                errno = 0;
×
6669
                endptr = NULL;
×
6670
                drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
6671
                if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
6672
                    sixel_helper_set_additional_message(
×
6673
                        "cannot parse DRCS option.");
6674
                    status = SIXEL_BAD_ARGUMENT;
×
6675
                    goto end;
×
6676
                }
6677
            } else {
6678
                if (drcs_arg_delim != value) {
×
6679
                    drcs_segment_length =
×
6680
                        (size_t)(drcs_arg_delim - value);
×
6681
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
6682
                        sixel_helper_set_additional_message(
×
6683
                            "DRCS mapping revision is too long.");
6684
                        status = SIXEL_BAD_ARGUMENT;
×
6685
                        goto end;
×
6686
                    }
6687
                    memcpy(drcs_segment, value, drcs_segment_length);
×
6688
                    drcs_segment[drcs_segment_length] = '\0';
×
6689
                    errno = 0;
×
6690
                    endptr = NULL;
×
6691
                    drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
6692
                    if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
6693
                        sixel_helper_set_additional_message(
×
6694
                            "cannot parse DRCS option.");
6695
                        status = SIXEL_BAD_ARGUMENT;
×
6696
                        goto end;
×
6697
                    }
6698
                }
6699
                drcs_arg_charset = drcs_arg_delim + 1;
×
6700
                drcs_arg_second_delim = strchr(drcs_arg_charset, ':');
×
6701
                if (drcs_arg_second_delim != NULL) {
×
6702
                    if (drcs_arg_second_delim != drcs_arg_charset) {
×
6703
                        drcs_segment_length =
×
6704
                            (size_t)(drcs_arg_second_delim - drcs_arg_charset);
×
6705
                        if (drcs_segment_length >= sizeof(drcs_segment)) {
×
6706
                            sixel_helper_set_additional_message(
×
6707
                                "DRCS charset number is too long.");
6708
                            status = SIXEL_BAD_ARGUMENT;
×
6709
                            goto end;
×
6710
                        }
6711
                        memcpy(drcs_segment,
×
6712
                               drcs_arg_charset,
6713
                               drcs_segment_length);
6714
                        drcs_segment[drcs_segment_length] = '\0';
×
6715
                        errno = 0;
×
6716
                        endptr = NULL;
×
6717
                        drcs_charset_value = strtol(drcs_segment,
×
6718
                                                    &endptr,
6719
                                                    10);
6720
                        if (errno != 0 || endptr == drcs_segment ||
×
6721
                                *endptr != '\0') {
×
6722
                            sixel_helper_set_additional_message(
×
6723
                                "cannot parse DRCS charset number.");
6724
                            status = SIXEL_BAD_ARGUMENT;
×
6725
                            goto end;
×
6726
                        }
6727
                    }
6728
                    drcs_arg_path = drcs_arg_second_delim + 1;
×
6729
                    drcs_arg_path_length = strlen(drcs_arg_path);
×
6730
                    if (drcs_arg_path_length == 0u) {
×
6731
                        drcs_arg_path = NULL;
×
6732
                    }
6733
                } else if (*drcs_arg_charset != '\0') {
×
6734
                    drcs_segment_length = strlen(drcs_arg_charset);
×
6735
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
6736
                        sixel_helper_set_additional_message(
×
6737
                            "DRCS charset number is too long.");
6738
                        status = SIXEL_BAD_ARGUMENT;
×
6739
                        goto end;
×
6740
                    }
6741
                    memcpy(drcs_segment,
×
6742
                           drcs_arg_charset,
6743
                           drcs_segment_length);
6744
                    drcs_segment[drcs_segment_length] = '\0';
×
6745
                    errno = 0;
×
6746
                    endptr = NULL;
×
6747
                    drcs_charset_value = strtol(drcs_segment,
×
6748
                                                &endptr,
6749
                                                10);
6750
                    if (errno != 0 || endptr == drcs_segment ||
×
6751
                            *endptr != '\0') {
×
6752
                        sixel_helper_set_additional_message(
×
6753
                            "cannot parse DRCS charset number.");
6754
                        status = SIXEL_BAD_ARGUMENT;
×
6755
                        goto end;
×
6756
                    }
6757
                }
6758
            }
6759
        }
6760
        /*
6761
         * Layout of the DRCS option value:
6762
         *
6763
         *    value = <mmv>:<charset_no>:<path>
6764
         *          ^        ^                ^
6765
         *          |        |                |
6766
         *          |        |                +-- optional path that may reuse
6767
         *          |        |                    STDOUT when set to "-" or drop
6768
         *          |        |                    tiles when left blank
6769
         *          |        +-- charset number (defaults to 1 when omitted)
6770
         *          +-- mapping revision (defaults to 2 when omitted)
6771
         */
6772
        if (drcs_mmv_value < 0 || drcs_mmv_value > 3) {
×
6773
            sixel_helper_set_additional_message(
×
6774
                "unknown DRCS unicode mapping version.");
6775
            status = SIXEL_BAD_ARGUMENT;
×
6776
            goto end;
×
6777
        }
6778
        if (drcs_mmv_value == 0) {
×
6779
            drcs_charset_limit = 126u;
6780
        } else if (drcs_mmv_value == 1) {
×
6781
            drcs_charset_limit = 63u;
6782
        } else if (drcs_mmv_value == 2) {
×
6783
            drcs_charset_limit = 158u;
6784
        } else {
6785
            drcs_charset_limit = 697u;
×
6786
        }
6787
        if (drcs_charset_value < 1 ||
×
6788
            (unsigned long)drcs_charset_value > drcs_charset_limit) {
×
6789
            sixel_helper_set_additional_message(
×
6790
                "DRCS charset number is out of range.");
6791
            status = SIXEL_BAD_ARGUMENT;
×
6792
            goto end;
×
6793
        }
6794
        encoder->drcs_mmv = drcs_mmv_value;
×
6795
        encoder->drcs_charset_no = (unsigned short)drcs_charset_value;
×
6796
        if (encoder->tile_outfd >= 0
×
6797
            && encoder->tile_outfd != encoder->outfd
×
6798
            && encoder->tile_outfd != STDOUT_FILENO
×
6799
            && encoder->tile_outfd != STDERR_FILENO) {
×
6800
            /*
6801
             * Drop any previously opened tile stream through the
6802
             * compatibility layer so that platform-specific details stay
6803
             * centralized in src/compat_stub.c.
6804
             */
6805
            (void)sixel_compat_close(encoder->tile_outfd);
×
6806
        }
6807
        encoder->tile_outfd = (-1);
×
6808
        if (drcs_arg_path != NULL) {
×
6809
            if (strcmp(drcs_arg_path, "-") == 0) {
×
6810
                encoder->tile_outfd = STDOUT_FILENO;
×
6811
            } else {
6812
                encoder->tile_outfd = sixel_compat_open(drcs_arg_path,
×
6813
                                                       O_RDWR | O_CREAT |
6814
                                                       O_TRUNC,
6815
                                                       S_IRUSR | S_IWUSR);
6816
                if (encoder->tile_outfd < 0) {
×
6817
                    sixel_helper_set_additional_message(
×
6818
                        "sixel_encoder_setopt: failed to open tile"
6819
                        " output path.");
6820
                    status = SIXEL_RUNTIME_ERROR;
×
6821
                    goto end;
×
6822
                }
6823
            }
6824
        }
6825
        break;
6826
    case SIXEL_OPTFLAG_PENETRATE:  /* P */
9✔
6827
        encoder->penetrate_multiplexer = 1;
9✔
6828
        break;
9✔
6829
    case SIXEL_OPTFLAG_ENCODE_POLICY:  /* E */
12✔
6830
        match_result = sixel_option_match_choice(
12✔
6831
            value,
6832
            g_option_choices_encode_policy,
6833
            sizeof(g_option_choices_encode_policy) /
6834
            sizeof(g_option_choices_encode_policy[0]),
6835
            &match_value,
6836
            match_detail,
6837
            sizeof(match_detail));
6838
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
12✔
6839
            encoder->encode_policy = match_value;
9✔
6840
        } else {
6841
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
6842
                sixel_option_report_ambiguous_prefix(value,
×
6843
                                              match_detail,
6844
                                              match_message,
6845
                                              sizeof(match_message));
6846
            } else {
6847
                sixel_option_report_invalid_choice(
3✔
6848
                    "cannot parse encode policy option.",
6849
                    match_detail,
6850
                    match_message,
6851
                    sizeof(match_message));
6852
            }
6853
            status = SIXEL_BAD_ARGUMENT;
3✔
6854
            goto end;
3✔
6855
        }
6856
        break;
9✔
6857
    case SIXEL_OPTFLAG_LUT_POLICY:  /* ~ */
×
6858
        match_result = sixel_option_match_choice(
×
6859
            value,
6860
            g_option_choices_lut_policy,
6861
            sizeof(g_option_choices_lut_policy) /
6862
            sizeof(g_option_choices_lut_policy[0]),
6863
            &match_value,
6864
            match_detail,
6865
            sizeof(match_detail));
6866
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6867
            encoder->lut_policy = match_value;
×
6868
        } else {
6869
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6870
                sixel_option_report_ambiguous_prefix(value,
×
6871
                                              match_detail,
6872
                                              match_message,
6873
                                              sizeof(match_message));
6874
            } else {
6875
                sixel_option_report_invalid_choice(
×
6876
                    "cannot parse lut policy option.",
6877
                    match_detail,
6878
                    match_message,
6879
                    sizeof(match_message));
6880
            }
6881
            status = SIXEL_BAD_ARGUMENT;
×
6882
            goto end;
×
6883
        }
6884
        if (encoder->dither_cache != NULL) {
×
6885
            sixel_dither_set_lut_policy(encoder->dither_cache,
×
6886
                                        encoder->lut_policy);
6887
        }
6888
        break;
6889
    case SIXEL_OPTFLAG_WORKING_COLORSPACE:  /* W */
×
6890
        if (value == NULL) {
×
6891
            sixel_helper_set_additional_message(
×
6892
                "working-colorspace requires an argument.");
6893
            status = SIXEL_BAD_ARGUMENT;
×
6894
            goto end;
×
6895
        } else {
6896
            len = strlen(value);
×
6897

6898
            if (len >= sizeof(lowered)) {
×
6899
                sixel_helper_set_additional_message(
×
6900
                    "specified working colorspace name is too long.");
6901
                status = SIXEL_BAD_ARGUMENT;
×
6902
                goto end;
×
6903
            }
6904
            for (i = 0; i < len; ++i) {
×
6905
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6906
            }
6907
            lowered[len] = '\0';
×
6908

6909
            match_result = sixel_option_match_choice(
×
6910
                lowered,
6911
                g_option_choices_working_colorspace,
6912
                sizeof(g_option_choices_working_colorspace) /
6913
                sizeof(g_option_choices_working_colorspace[0]),
6914
                &match_value,
6915
                match_detail,
6916
                sizeof(match_detail));
6917
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6918
                encoder->working_colorspace = match_value;
×
6919
            } else {
6920
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6921
                    sixel_option_report_ambiguous_prefix(value,
×
6922
                        match_detail,
6923
                        match_message,
6924
                        sizeof(match_message));
6925
                } else {
6926
                    sixel_option_report_invalid_choice(
×
6927
                        "unsupported working colorspace specified.",
6928
                        match_detail,
6929
                        match_message,
6930
                        sizeof(match_message));
6931
                }
6932
                status = SIXEL_BAD_ARGUMENT;
×
6933
                goto end;
×
6934
            }
6935
        }
6936
        break;
×
6937
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
×
6938
        if (value == NULL) {
×
6939
            sixel_helper_set_additional_message(
×
6940
                "output-colorspace requires an argument.");
6941
            status = SIXEL_BAD_ARGUMENT;
×
6942
            goto end;
×
6943
        } else {
6944
            len = strlen(value);
×
6945

6946
            if (len >= sizeof(lowered)) {
×
6947
                sixel_helper_set_additional_message(
×
6948
                    "specified output colorspace name is too long.");
6949
                status = SIXEL_BAD_ARGUMENT;
×
6950
                goto end;
×
6951
            }
6952
            for (i = 0; i < len; ++i) {
×
6953
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
6954
            }
6955
            lowered[len] = '\0';
×
6956

6957
            match_result = sixel_option_match_choice(
×
6958
                lowered,
6959
                g_option_choices_output_colorspace,
6960
                sizeof(g_option_choices_output_colorspace) /
6961
                sizeof(g_option_choices_output_colorspace[0]),
6962
                &match_value,
6963
                match_detail,
6964
                sizeof(match_detail));
6965
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
6966
                encoder->output_colorspace = match_value;
×
6967
            } else {
6968
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
6969
                    sixel_option_report_ambiguous_prefix(value,
×
6970
                        match_detail,
6971
                        match_message,
6972
                        sizeof(match_message));
6973
                } else {
6974
                    sixel_option_report_invalid_choice(
×
6975
                        "unsupported output colorspace specified.",
6976
                        match_detail,
6977
                        match_message,
6978
                        sizeof(match_message));
6979
                }
6980
                status = SIXEL_BAD_ARGUMENT;
×
6981
                goto end;
×
6982
            }
6983
        }
6984
        break;
×
6985
    case SIXEL_OPTFLAG_ORMODE:  /* O */
×
6986
        encoder->ormode = 1;
×
6987
        break;
×
6988
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6989
        encoder->complexion = atoi(value);
9✔
6990
        if (encoder->complexion < 1) {
9✔
6991
            sixel_helper_set_additional_message(
3✔
6992
                "complexion parameter must be 1 or more.");
6993
            status = SIXEL_BAD_ARGUMENT;
3✔
6994
            goto end;
3✔
6995
        }
6996
        break;
6997
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
×
6998
        encoder->pipe_mode = 1;
×
6999
        break;
×
7000
    case '?':  /* unknown option */
×
7001
    default:
7002
        /* exit if unknown options are specified */
7003
        sixel_helper_set_additional_message(
×
7004
            "unknown option is specified.");
7005
        status = SIXEL_BAD_ARGUMENT;
×
7006
        goto end;
×
7007
    }
7008

7009
    /* detects arguments conflictions */
7010
    if (encoder->reqcolors != (-1)) {
750✔
7011
        switch (encoder->color_option) {
114!
7012
        case SIXEL_COLOR_OPTION_MAPFILE:
×
7013
            sixel_helper_set_additional_message(
×
7014
                "option -p, --colors conflicts with -m, --mapfile.");
7015
            status = SIXEL_BAD_ARGUMENT;
×
7016
            goto end;
×
7017
        case SIXEL_COLOR_OPTION_MONOCHROME:
3✔
7018
            sixel_helper_set_additional_message(
3✔
7019
                "option -e, --monochrome conflicts with -p, --colors.");
7020
            status = SIXEL_BAD_ARGUMENT;
3✔
7021
            goto end;
3✔
7022
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
3✔
7023
            sixel_helper_set_additional_message(
3✔
7024
                "option -p, --colors conflicts with -I, --high-color.");
7025
            status = SIXEL_BAD_ARGUMENT;
3✔
7026
            goto end;
3✔
7027
        case SIXEL_COLOR_OPTION_BUILTIN:
3✔
7028
            sixel_helper_set_additional_message(
3✔
7029
                "option -p, --colors conflicts with -b, --builtin-palette.");
7030
            status = SIXEL_BAD_ARGUMENT;
3✔
7031
            goto end;
3✔
7032
        default:
7033
            break;
7034
        }
7035
    }
7036

7037
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
7038
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
741✔
7039
        sixel_helper_set_additional_message(
3✔
7040
            "option -8 --8bit-mode conflicts"
7041
            " with -P, --penetrate.");
7042
        status = SIXEL_BAD_ARGUMENT;
3✔
7043
        goto end;
3✔
7044
    }
7045

7046
    status = SIXEL_OK;
7047

7048
end:
831✔
7049
    if (opt_copy != NULL) {
831!
7050
        sixel_allocator_free(encoder->allocator, opt_copy);
7051
    }
7052
    sixel_encoder_unref(encoder);
831✔
7053

7054
    return status;
831✔
7055
}
7056

7057

7058
/* called when image loader component load a image frame */
7059
static SIXELSTATUS
7060
load_image_callback(sixel_frame_t *frame, void *data)
549✔
7061
{
7062
    sixel_encoder_t *encoder;
549✔
7063

7064
    encoder = (sixel_encoder_t *)data;
549✔
7065
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
549!
7066
        sixel_frame_ref(frame);
3✔
7067
        encoder->capture_source_frame = frame;
3✔
7068
    }
7069

7070
    return sixel_encoder_encode_frame(encoder, frame, NULL);
549✔
7071
}
7072

7073
/*
7074
 * Build a tee for encoded-assessment output:
7075
 *
7076
 *     +-------------+     +-------------------+     +------------+
7077
 *     | encoder FD  | --> | temporary SIXEL   | --> | tee sink   |
7078
 *     +-------------+     +-------------------+     +------------+
7079
 *
7080
 * The tee sink can be stdout or a user-provided file such as /dev/null.
7081
 */
7082
static SIXELSTATUS
7083
copy_file_to_stream(char const *path,
×
7084
                    FILE *stream,
7085
                    sixel_assessment_t *assessment)
7086
{
7087
    FILE *source;
×
7088
    unsigned char buffer[4096];
×
7089
    size_t nread;
×
7090
    size_t nwritten;
×
7091
    double started_at;
×
7092
    double finished_at;
×
7093
    double duration;
×
7094

7095
    source = NULL;
×
7096
    nread = 0;
×
7097
    nwritten = 0;
×
7098
    started_at = 0.0;
×
7099
    finished_at = 0.0;
×
7100
    duration = 0.0;
×
7101

7102
    source = sixel_compat_fopen(path, "rb");
×
7103
    if (source == NULL) {
×
7104
        sixel_helper_set_additional_message(
×
7105
            "copy_file_to_stream: failed to open assessment staging file.");
7106
        return SIXEL_LIBC_ERROR;
×
7107
    }
7108

7109
    for (;;) {
×
7110
        nread = fread(buffer, 1, sizeof(buffer), source);
×
7111
        if (nread == 0) {
×
7112
            if (ferror(source)) {
×
7113
                sixel_helper_set_additional_message(
×
7114
                    "copy_file_to_stream: failed while reading assessment staging file.");
7115
                (void) fclose(source);
×
7116
                return SIXEL_LIBC_ERROR;
×
7117
            }
7118
            break;
×
7119
        }
7120
        if (assessment != NULL) {
×
7121
            started_at = sixel_assessment_timer_now();
×
7122
        }
7123
        nwritten = fwrite(buffer, 1, nread, stream);
×
7124
        if (nwritten != nread) {
×
7125
            sixel_helper_set_additional_message(
×
7126
                "img2sixel: failed while copying assessment staging file.");
7127
            (void) fclose(source);
×
7128
            return SIXEL_LIBC_ERROR;
×
7129
        }
7130
        if (assessment != NULL) {
×
7131
            finished_at = sixel_assessment_timer_now();
×
7132
            duration = finished_at - started_at;
×
7133
            if (duration < 0.0) {
×
7134
                duration = 0.0;
7135
            }
7136
            sixel_assessment_record_output_write(assessment,
×
7137
                                                 nwritten,
7138
                                                 duration);
7139
        }
7140
    }
7141

7142
    if (fclose(source) != 0) {
×
7143
        sixel_helper_set_additional_message(
×
7144
            "img2sixel: failed to close assessment staging file.");
7145
        return SIXEL_LIBC_ERROR;
×
7146
    }
7147

7148
    return SIXEL_OK;
7149
}
7150

7151
typedef struct assessment_json_sink {
7152
    FILE *stream;
7153
    int failed;
7154
} assessment_json_sink_t;
7155

7156
static void
7157
assessment_json_callback(char const *chunk,
42✔
7158
                         size_t length,
7159
                         void *user_data)
7160
{
7161
    assessment_json_sink_t *sink;
42✔
7162

7163
    sink = (assessment_json_sink_t *)user_data;
42✔
7164
    if (sink == NULL || sink->stream == NULL) {
42!
7165
        return;
7166
    }
7167
    if (sink->failed) {
42!
7168
        return;
7169
    }
7170
    if (fwrite(chunk, 1, length, sink->stream) != length) {
42!
7171
        sink->failed = 1;
×
7172
    }
7173
}
1!
7174

7175
static char *
7176
create_temp_template_with_prefix(sixel_allocator_t *allocator,
9✔
7177
                                 char const *prefix,
7178
                                 size_t *capacity_out)
7179
{
7180
    char const *tmpdir;
9✔
7181
    size_t tmpdir_len;
9✔
7182
    size_t prefix_len;
9✔
7183
    size_t suffix_len;
9✔
7184
    size_t template_len;
9✔
7185
    char *template_path;
9✔
7186
    int needs_separator;
9✔
7187
    size_t maximum_tmpdir_len;
9✔
7188

7189
    tmpdir = sixel_compat_getenv("TMPDIR");
9✔
7190
#if defined(_WIN32)
7191
    if (tmpdir == NULL || tmpdir[0] == '\0') {
7192
        tmpdir = sixel_compat_getenv("TEMP");
7193
    }
7194
    if (tmpdir == NULL || tmpdir[0] == '\0') {
7195
        tmpdir = sixel_compat_getenv("TMP");
7196
    }
7197
#endif
7198
    if (tmpdir == NULL || tmpdir[0] == '\0') {
9!
7199
#if defined(_WIN32)
7200
        tmpdir = ".";
7201
#else
7202
        tmpdir = "/tmp";
9✔
7203
#endif
7204
    }
7205

7206
    tmpdir_len = strlen(tmpdir);
9✔
7207
    prefix_len = 0u;
9✔
7208
    suffix_len = 0u;
9✔
7209
    if (prefix == NULL) {
9!
7210
        return NULL;
7211
    }
7212

7213
    prefix_len = strlen(prefix);
9✔
7214
    suffix_len = prefix_len + strlen("-XXXXXX");
9✔
7215
    maximum_tmpdir_len = (size_t)INT_MAX;
9✔
7216

7217
    if (maximum_tmpdir_len <= suffix_len + 2) {
9!
7218
        return NULL;
7219
    }
7220
    if (tmpdir_len > maximum_tmpdir_len - (suffix_len + 2)) {
9!
7221
        return NULL;
7222
    }
7223
    needs_separator = 1;
9✔
7224
    if (tmpdir_len > 0) {
9!
7225
        if (tmpdir[tmpdir_len - 1] == '/' || tmpdir[tmpdir_len - 1] == '\\') {
9!
7226
            needs_separator = 0;
9✔
7227
        }
7228
    }
7229

7230
    template_len = tmpdir_len + suffix_len + 2;
9✔
7231
    template_path = (char *)sixel_allocator_malloc(allocator, template_len);
9✔
7232
    if (template_path == NULL) {
9!
7233
        return NULL;
7234
    }
7235

7236
    if (needs_separator) {
9!
7237
#if defined(_WIN32)
7238
        (void) snprintf(template_path, template_len,
7239
                        "%s\\%s-XXXXXX", tmpdir, prefix);
7240
#else
7241
        (void) snprintf(template_path, template_len,
9✔
7242
                        "%s/%s-XXXXXX", tmpdir, prefix);
7243
#endif
7244
    } else {
7245
        (void) snprintf(template_path, template_len,
×
7246
                        "%s%s-XXXXXX", tmpdir, prefix);
7247
    }
7248

7249
    if (capacity_out != NULL) {
9!
7250
        *capacity_out = template_len;
9✔
7251
    }
7252

7253
    return template_path;
7254
}
7255

7256

7257
static char *
7258
create_temp_template(sixel_allocator_t *allocator,
9✔
7259
                     size_t *capacity_out)
7260
{
7261
    return create_temp_template_with_prefix(allocator,
9✔
7262
                                            "img2sixel",
7263
                                            capacity_out);
7264
}
7265

7266

7267
static void
7268
clipboard_select_format(char *dest,
×
7269
                        size_t dest_size,
7270
                        char const *format,
7271
                        char const *fallback)
7272
{
7273
    char const *source;
×
7274
    size_t limit;
×
7275

7276
    if (dest == NULL || dest_size == 0u) {
×
7277
        return;
7278
    }
7279

7280
    source = fallback;
×
7281
    if (format != NULL && format[0] != '\0') {
×
7282
        source = format;
×
7283
    }
7284

7285
    limit = dest_size - 1u;
×
7286
    if (limit == 0u) {
×
7287
        dest[0] = '\0';
×
7288
        return;
×
7289
    }
7290

7291
    (void)snprintf(dest, dest_size, "%.*s", (int)limit, source);
×
7292
}
1!
7293

7294

7295
static SIXELSTATUS
7296
clipboard_create_spool(sixel_allocator_t *allocator,
×
7297
                       char const *prefix,
7298
                       char **path_out,
7299
                       int *fd_out)
7300
{
7301
    SIXELSTATUS status;
×
7302
    char *template_path;
×
7303
    size_t template_capacity;
×
7304
    int open_flags;
×
7305
    int fd;
×
7306
    char *tmpname_result;
×
7307

7308
    status = SIXEL_FALSE;
×
7309
    template_path = NULL;
×
7310
    template_capacity = 0u;
×
7311
    open_flags = 0;
×
7312
    fd = (-1);
×
7313
    tmpname_result = NULL;
×
7314

7315
    template_path = create_temp_template_with_prefix(allocator,
×
7316
                                                     prefix,
7317
                                                     &template_capacity);
7318
    if (template_path == NULL) {
×
7319
        sixel_helper_set_additional_message(
×
7320
            "clipboard: failed to allocate spool template.");
7321
        status = SIXEL_BAD_ALLOCATION;
×
7322
        goto end;
×
7323
    }
7324

7325
    if (sixel_compat_mktemp(template_path, template_capacity) != 0) {
×
7326
        tmpname_result = sixel_compat_tmpnam(template_path,
×
7327
                                             template_capacity);
7328
        if (tmpname_result == NULL) {
×
7329
            sixel_helper_set_additional_message(
×
7330
                "clipboard: failed to reserve spool template.");
7331
            status = SIXEL_LIBC_ERROR;
×
7332
            goto end;
×
7333
        }
7334
        template_capacity = strlen(template_path) + 1u;
×
7335
    }
7336

7337
    open_flags = O_RDWR | O_CREAT | O_TRUNC;
×
7338
#if defined(O_EXCL)
7339
    open_flags |= O_EXCL;
×
7340
#endif
7341
    fd = sixel_compat_open(template_path, open_flags, S_IRUSR | S_IWUSR);
×
7342
    if (fd < 0) {
×
7343
        sixel_helper_set_additional_message(
×
7344
            "clipboard: failed to open spool file.");
7345
        status = SIXEL_LIBC_ERROR;
×
7346
        goto end;
×
7347
    }
7348

7349
    *path_out = template_path;
×
7350
    if (fd_out != NULL) {
×
7351
        *fd_out = fd;
×
7352
        fd = (-1);
×
7353
    }
7354

7355
    template_path = NULL;
×
7356
    status = SIXEL_OK;
×
7357

7358
end:
×
7359
    if (fd >= 0) {
×
7360
        (void)sixel_compat_close(fd);
×
7361
    }
7362
    if (template_path != NULL) {
×
7363
        sixel_allocator_free(allocator, template_path);
×
7364
    }
7365

7366
    return status;
×
7367
}
7368

7369

7370
static SIXELSTATUS
7371
clipboard_write_file(char const *path,
×
7372
                     unsigned char const *data,
7373
                     size_t size)
7374
{
7375
    FILE *stream;
×
7376
    size_t written;
×
7377

7378
    if (path == NULL) {
×
7379
        sixel_helper_set_additional_message(
×
7380
            "clipboard: spool path is null.");
7381
        return SIXEL_BAD_ARGUMENT;
×
7382
    }
7383

7384
    stream = sixel_compat_fopen(path, "wb");
×
7385
    if (stream == NULL) {
×
7386
        sixel_helper_set_additional_message(
×
7387
            "clipboard: failed to open spool file for write.");
7388
        return SIXEL_LIBC_ERROR;
×
7389
    }
7390

7391
    written = 0u;
×
7392
    if (size > 0u && data != NULL) {
×
7393
        written = fwrite(data, 1u, size, stream);
×
7394
        if (written != size) {
×
7395
            (void)fclose(stream);
×
7396
            sixel_helper_set_additional_message(
×
7397
                "clipboard: failed to write spool payload.");
7398
            return SIXEL_LIBC_ERROR;
×
7399
        }
7400
    }
7401

7402
    if (fclose(stream) != 0) {
×
7403
        sixel_helper_set_additional_message(
×
7404
            "clipboard: failed to close spool file after write.");
7405
        return SIXEL_LIBC_ERROR;
×
7406
    }
7407

7408
    return SIXEL_OK;
7409
}
7410

7411

7412
static SIXELSTATUS
7413
clipboard_read_file(char const *path,
×
7414
                    unsigned char **data,
7415
                    size_t *size)
7416
{
7417
    FILE *stream;
×
7418
    long seek_result;
×
7419
    long file_size;
×
7420
    unsigned char *buffer;
×
7421
    size_t read_size;
×
7422

7423
    if (data == NULL || size == NULL) {
×
7424
        sixel_helper_set_additional_message(
×
7425
            "clipboard: read buffer pointers are null.");
7426
        return SIXEL_BAD_ARGUMENT;
×
7427
    }
7428

7429
    *data = NULL;
×
7430
    *size = 0u;
×
7431

7432
    if (path == NULL) {
×
7433
        sixel_helper_set_additional_message(
×
7434
            "clipboard: spool path is null.");
7435
        return SIXEL_BAD_ARGUMENT;
×
7436
    }
7437

7438
    stream = sixel_compat_fopen(path, "rb");
×
7439
    if (stream == NULL) {
×
7440
        sixel_helper_set_additional_message(
×
7441
            "clipboard: failed to open spool file for read.");
7442
        return SIXEL_LIBC_ERROR;
×
7443
    }
7444

7445
    seek_result = fseek(stream, 0L, SEEK_END);
×
7446
    if (seek_result != 0) {
×
7447
        (void)fclose(stream);
×
7448
        sixel_helper_set_additional_message(
×
7449
            "clipboard: failed to seek spool file.");
7450
        return SIXEL_LIBC_ERROR;
×
7451
    }
7452

7453
    file_size = ftell(stream);
×
7454
    if (file_size < 0) {
×
7455
        (void)fclose(stream);
×
7456
        sixel_helper_set_additional_message(
×
7457
            "clipboard: failed to determine spool size.");
7458
        return SIXEL_LIBC_ERROR;
×
7459
    }
7460

7461
    seek_result = fseek(stream, 0L, SEEK_SET);
×
7462
    if (seek_result != 0) {
×
7463
        (void)fclose(stream);
×
7464
        sixel_helper_set_additional_message(
×
7465
            "clipboard: failed to rewind spool file.");
7466
        return SIXEL_LIBC_ERROR;
×
7467
    }
7468

7469
    if (file_size == 0) {
×
7470
        buffer = NULL;
7471
        read_size = 0u;
7472
    } else {
7473
        buffer = (unsigned char *)malloc((size_t)file_size);
×
7474
        if (buffer == NULL) {
×
7475
            (void)fclose(stream);
×
7476
            sixel_helper_set_additional_message(
×
7477
                "clipboard: malloc() failed for spool payload.");
7478
            return SIXEL_BAD_ALLOCATION;
×
7479
        }
7480
        read_size = fread(buffer, 1u, (size_t)file_size, stream);
×
7481
        if (read_size != (size_t)file_size) {
×
7482
            free(buffer);
×
7483
            (void)fclose(stream);
×
7484
            sixel_helper_set_additional_message(
×
7485
                "clipboard: failed to read spool payload.");
7486
            return SIXEL_LIBC_ERROR;
×
7487
        }
7488
    }
7489

7490
    if (fclose(stream) != 0) {
×
7491
        if (buffer != NULL) {
×
7492
            free(buffer);
×
7493
        }
7494
        sixel_helper_set_additional_message(
×
7495
            "clipboard: failed to close spool file after read.");
7496
        return SIXEL_LIBC_ERROR;
×
7497
    }
7498

7499
    *data = buffer;
×
7500
    *size = read_size;
×
7501

7502
    return SIXEL_OK;
×
7503
}
7504

7505

7506
static SIXELSTATUS
7507
write_png_from_sixel(char const *sixel_path, char const *output_path)
9✔
7508
{
7509
    SIXELSTATUS status;
9✔
7510
    sixel_decoder_t *decoder;
9✔
7511

7512
    status = SIXEL_FALSE;
9✔
7513
    decoder = NULL;
9✔
7514

7515
    status = sixel_decoder_new(&decoder, NULL);
9✔
7516
    if (SIXEL_FAILED(status)) {
9!
7517
        goto end;
×
7518
    }
7519

7520
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, sixel_path);
9✔
7521
    if (SIXEL_FAILED(status)) {
9!
7522
        goto end;
×
7523
    }
7524

7525
    status = sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_OUTPUT, output_path);
9✔
7526
    if (SIXEL_FAILED(status)) {
9!
7527
        goto end;
×
7528
    }
7529

7530
    status = sixel_decoder_decode(decoder);
9✔
7531

7532
end:
9✔
7533
    sixel_decoder_unref(decoder);
9✔
7534

7535
    return status;
9✔
7536
}
7537

7538

7539
/* load source data from specified file and encode it to SIXEL format
7540
 * output to encoder->outfd */
7541
SIXELAPI SIXELSTATUS
7542
sixel_encoder_encode(
459✔
7543
    sixel_encoder_t *encoder,   /* encoder object */
7544
    char const      *filename)  /* input filename */
7545
{
7546
    SIXELSTATUS status = SIXEL_FALSE;
459✔
7547
    SIXELSTATUS palette_status = SIXEL_OK;
459✔
7548
    int fuse_palette = 1;
459✔
7549
    sixel_loader_t *loader = NULL;
459✔
7550
    sixel_allocator_t *assessment_allocator = NULL;
459✔
7551
    sixel_allocator_t *encode_allocator = NULL;
459✔
7552
    sixel_frame_t *assessment_source_frame = NULL;
459✔
7553
    sixel_frame_t *assessment_target_frame = NULL;
459✔
7554
    sixel_frame_t *assessment_expanded_frame = NULL;
459✔
7555
    unsigned int assessment_section_mask =
459✔
7556
        encoder->assessment_sections & SIXEL_ASSESSMENT_SECTION_MASK;
459✔
7557
    int assessment_need_source_capture = 0;
459✔
7558
    int assessment_need_quantized_capture = 0;
459✔
7559
    int assessment_need_quality = 0;
459✔
7560
    int assessment_quality_quantized = 0;
459✔
7561
    assessment_json_sink_t assessment_sink;
459✔
7562
    FILE *assessment_json_file = NULL;
459✔
7563
    FILE *assessment_forward_stream = NULL;
459✔
7564
    int assessment_json_owned = 0;
459✔
7565
    char *assessment_temp_path = NULL;
459✔
7566
    size_t assessment_temp_capacity = 0u;
459✔
7567
    char *assessment_tmpnam_result = NULL;
459✔
7568
    sixel_assessment_spool_mode_t assessment_spool_mode
459✔
7569
        = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
7570
    char *assessment_forward_path = NULL;
459✔
7571
    size_t assessment_output_bytes;
459✔
7572
#if HAVE_SYS_STAT_H
7573
    struct stat assessment_stat;
459✔
7574
    int assessment_stat_result;
459✔
7575
    char const *assessment_size_path = NULL;
459✔
7576
#endif
7577
    char const *png_final_path = NULL;
459✔
7578
    char *png_temp_path = NULL;
459✔
7579
    size_t png_temp_capacity = 0u;
459✔
7580
    char *png_tmpnam_result = NULL;
459✔
7581
    int png_open_flags = 0;
459✔
7582
    int spool_required;
459✔
7583
    sixel_clipboard_spec_t clipboard_spec;
459✔
7584
    char clipboard_input_format[32];
459✔
7585
    char *clipboard_input_path;
459✔
7586
    unsigned char *clipboard_blob;
459✔
7587
    size_t clipboard_blob_size;
459✔
7588
    SIXELSTATUS clipboard_status;
459✔
7589
    char const *effective_filename;
459✔
7590
    unsigned int path_flags;
459✔
7591
    int path_check;
459✔
7592
    sixel_logger_t logger;
459✔
7593
    int logger_prepared;
459✔
7594

7595
    clipboard_input_format[0] = '\0';
459✔
7596
    clipboard_input_path = NULL;
459✔
7597
    clipboard_blob = NULL;
459✔
7598
    clipboard_blob_size = 0u;
459✔
7599
    clipboard_status = SIXEL_OK;
459✔
7600
    effective_filename = filename;
459✔
7601
    path_flags = SIXEL_OPTION_PATH_ALLOW_STDIN |
459✔
7602
        SIXEL_OPTION_PATH_ALLOW_CLIPBOARD |
7603
        SIXEL_OPTION_PATH_ALLOW_REMOTE;
7604
    path_check = 0;
459✔
7605
    logger_prepared = 0;
459✔
7606
    sixel_logger_init(&logger);
459✔
7607
    sixel_logger_prepare_env(&logger);
459✔
7608
    logger_prepared = logger.active;
459✔
7609
    if (encoder != NULL) {
459!
7610
        encoder->logger = &logger;
459✔
7611
        encoder->parallel_job_id = -1;
459✔
7612
    }
7613

7614
    if (filename != NULL) {
459✔
7615
        path_check = sixel_option_validate_filesystem_path(
318✔
7616
            filename,
7617
            filename,
7618
            path_flags);
7619
        if (path_check != 0) {
318!
7620
            status = SIXEL_BAD_ARGUMENT;
×
7621
            goto end;
×
7622
        }
7623
    }
7624

7625
    if (encoder != NULL) {
459!
7626
        encode_allocator = encoder->allocator;
459✔
7627
        if (encode_allocator != NULL) {
459!
7628
            /*
7629
             * Hold a reference until cleanup so worker side-effects or loader
7630
             * destruction cannot release the allocator before sequential
7631
             * teardown finishes using it.
7632
             */
7633
            sixel_allocator_ref(encode_allocator);
459✔
7634
        }
7635
    }
7636

7637
    clipboard_spec.is_clipboard = 0;
459✔
7638
    clipboard_spec.format[0] = '\0';
459✔
7639
    if (effective_filename != NULL
459!
7640
            && sixel_clipboard_parse_spec(effective_filename,
318!
7641
                                          &clipboard_spec)
7642
            && clipboard_spec.is_clipboard) {
×
7643
        clipboard_select_format(clipboard_input_format,
×
7644
                                sizeof(clipboard_input_format),
7645
                                clipboard_spec.format,
7646
                                "sixel");
7647
        clipboard_status = sixel_clipboard_read(
×
7648
            clipboard_input_format,
7649
            &clipboard_blob,
7650
            &clipboard_blob_size,
7651
            encoder->allocator);
7652
        if (SIXEL_FAILED(clipboard_status)) {
×
7653
            status = clipboard_status;
×
7654
            goto end;
×
7655
        }
7656
        clipboard_status = clipboard_create_spool(
×
7657
            encoder->allocator,
7658
            "clipboard-in",
7659
            &clipboard_input_path,
7660
            NULL);
7661
        if (SIXEL_FAILED(clipboard_status)) {
×
7662
            status = clipboard_status;
×
7663
            goto end;
×
7664
        }
7665
        clipboard_status = clipboard_write_file(
×
7666
            clipboard_input_path,
7667
            clipboard_blob,
7668
            clipboard_blob_size);
7669
        if (SIXEL_FAILED(clipboard_status)) {
×
7670
            status = clipboard_status;
×
7671
            goto end;
×
7672
        }
7673
        if (clipboard_blob != NULL) {
×
7674
            free(clipboard_blob);
×
7675
            clipboard_blob = NULL;
×
7676
        }
7677
        effective_filename = clipboard_input_path;
7678
    }
7679

7680
    if (assessment_section_mask != SIXEL_ASSESSMENT_SECTION_NONE) {
459✔
7681
        status = sixel_allocator_new(&assessment_allocator,
3✔
7682
                                     malloc,
7683
                                     calloc,
7684
                                     realloc,
7685
                                     free);
7686
        if (SIXEL_FAILED(status) || assessment_allocator == NULL) {
3!
7687
            goto end;
×
7688
        }
7689
        status = sixel_assessment_new(&encoder->assessment_observer,
3✔
7690
                                       assessment_allocator);
7691
        if (SIXEL_FAILED(status) || encoder->assessment_observer == NULL) {
3!
7692
            goto end;
×
7693
        }
7694
        sixel_assessment_select_sections(encoder->assessment_observer,
3✔
7695
                                         encoder->assessment_sections);
7696
        sixel_assessment_attach_encoder(encoder->assessment_observer,
3✔
7697
                                        encoder);
7698
        assessment_need_quality =
3✔
7699
            (assessment_section_mask & SIXEL_ASSESSMENT_SECTION_QUALITY) != 0u;
3✔
7700
        assessment_quality_quantized =
3✔
7701
            (encoder->assessment_sections & SIXEL_ASSESSMENT_VIEW_QUANTIZED) != 0u;
3✔
7702
        assessment_need_quantized_capture =
6✔
7703
            ((assessment_section_mask &
3✔
7704
              (SIXEL_ASSESSMENT_SECTION_BASIC |
7705
               SIXEL_ASSESSMENT_SECTION_SIZE)) != 0u) ||
3!
7706
            assessment_quality_quantized;
7707
        assessment_need_source_capture =
3✔
7708
            (assessment_section_mask &
7709
             (SIXEL_ASSESSMENT_SECTION_BASIC |
7710
              SIXEL_ASSESSMENT_SECTION_PERFORMANCE |
7711
              SIXEL_ASSESSMENT_SECTION_SIZE |
7712
              SIXEL_ASSESSMENT_SECTION_QUALITY)) != 0u;
7713
        if (assessment_need_quality && !assessment_quality_quantized &&
3!
7714
                encoder->output_is_png) {
×
7715
            sixel_helper_set_additional_message(
×
7716
                "sixel_encoder_setopt: encoded quality assessment requires SIXEL output.");
7717
            status = SIXEL_BAD_ARGUMENT;
×
7718
            goto end;
×
7719
        }
7720
        status = sixel_encoder_enable_source_capture(encoder,
3✔
7721
                                                     assessment_need_source_capture);
7722
        if (SIXEL_FAILED(status)) {
3!
7723
            goto end;
×
7724
        }
7725
        status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_STATIC, NULL);
3✔
7726
        if (SIXEL_FAILED(status)) {
3!
7727
            goto end;
×
7728
        }
7729
        if (assessment_need_quantized_capture) {
3!
7730
            status = sixel_encoder_enable_quantized_capture(encoder, 1);
3✔
7731
            if (SIXEL_FAILED(status)) {
3!
7732
                goto end;
7733
            }
7734
        }
7735
        assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_NONE;
3✔
7736
        spool_required = 0;
3✔
7737
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
7738
            if (encoder->sixel_output_path == NULL) {
×
7739
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
7740
                spool_required = 1;
7741
            } else if (strcmp(encoder->sixel_output_path, "-") == 0) {
×
7742
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT;
×
7743
                spool_required = 1;
×
7744
                free(encoder->sixel_output_path);
×
7745
                encoder->sixel_output_path = NULL;
×
7746
            } else if (is_dev_null_path(encoder->sixel_output_path)) {
×
7747
                assessment_spool_mode = SIXEL_ASSESSMENT_SPOOL_MODE_PATH;
×
7748
                spool_required = 1;
×
7749
                assessment_forward_path = encoder->sixel_output_path;
×
7750
                encoder->sixel_output_path = NULL;
×
7751
            }
7752
        }
7753
        if (spool_required) {
×
7754
            assessment_temp_capacity = 0u;
×
7755
            assessment_tmpnam_result = NULL;
×
7756
            assessment_temp_path = create_temp_template(encoder->allocator,
×
7757
                                                        &assessment_temp_capacity);
7758
            if (assessment_temp_path == NULL) {
×
7759
                sixel_helper_set_additional_message(
×
7760
                    "sixel_encoder_encode: sixel_allocator_malloc() "
7761
                    "failed for assessment staging path.");
7762
                status = SIXEL_BAD_ALLOCATION;
×
7763
                goto end;
×
7764
            }
7765
            if (sixel_compat_mktemp(assessment_temp_path,
×
7766
                                    assessment_temp_capacity) != 0) {
7767
                assessment_tmpnam_result = sixel_compat_tmpnam(
×
7768
                    assessment_temp_path,
7769
                    assessment_temp_capacity);
7770
                if (assessment_tmpnam_result == NULL) {
×
7771
                    sixel_helper_set_additional_message(
×
7772
                        "sixel_encoder_encode: mktemp() failed for assessment staging file.");
7773
                    status = SIXEL_RUNTIME_ERROR;
×
7774
                    goto end;
×
7775
                }
7776
                assessment_temp_capacity = strlen(assessment_temp_path) + 1u;
×
7777
            }
7778
            status = sixel_encoder_setopt(encoder, SIXEL_OPTFLAG_OUTFILE,
×
7779
                                          assessment_temp_path);
7780
            if (SIXEL_FAILED(status)) {
×
7781
                goto end;
×
7782
            }
7783
            encoder->sixel_output_path = (char *)sixel_allocator_malloc(
×
7784
                encoder->allocator, strlen(assessment_temp_path) + 1);
×
7785
            if (encoder->sixel_output_path == NULL) {
×
7786
                sixel_helper_set_additional_message(
×
7787
                    "sixel_encoder_encode: malloc() failed for assessment staging name.");
7788
                status = SIXEL_BAD_ALLOCATION;
×
7789
                goto end;
×
7790
            }
7791
            (void)sixel_compat_strcpy(encoder->sixel_output_path,
×
7792
                                      strlen(assessment_temp_path) + 1,
×
7793
                                      assessment_temp_path);
7794
        }
7795

7796
    }
7797

7798
    if (encoder->output_is_png) {
459✔
7799
        png_temp_capacity = 0u;
9✔
7800
        png_tmpnam_result = NULL;
9✔
7801
        png_temp_path = create_temp_template(encoder->allocator,
9✔
7802
                                             &png_temp_capacity);
7803
        if (png_temp_path == NULL) {
9!
7804
            sixel_helper_set_additional_message(
×
7805
                "sixel_encoder_encode: malloc() failed for PNG staging path.");
7806
            status = SIXEL_BAD_ALLOCATION;
×
7807
            goto end;
×
7808
        }
7809
        if (sixel_compat_mktemp(png_temp_path, png_temp_capacity) != 0) {
9!
7810
            png_tmpnam_result = sixel_compat_tmpnam(png_temp_path,
×
7811
                                                   png_temp_capacity);
7812
            if (png_tmpnam_result == NULL) {
×
7813
                sixel_helper_set_additional_message(
×
7814
                    "sixel_encoder_encode: mktemp() failed for PNG staging file.");
7815
                status = SIXEL_RUNTIME_ERROR;
×
7816
                goto end;
×
7817
            }
7818
            png_temp_capacity = strlen(png_temp_path) + 1u;
×
7819
        }
7820
        if (encoder->outfd >= 0 && encoder->outfd != STDOUT_FILENO) {
9!
7821
            (void)sixel_compat_close(encoder->outfd);
3✔
7822
        }
7823
        png_open_flags = O_RDWR | O_CREAT | O_TRUNC;
9✔
7824
#if defined(O_EXCL)
7825
        png_open_flags |= O_EXCL;
9✔
7826
#endif
7827
        encoder->outfd = sixel_compat_open(png_temp_path,
9✔
7828
                                           png_open_flags,
7829
                                           S_IRUSR | S_IWUSR);
7830
        if (encoder->outfd < 0) {
9!
7831
            sixel_helper_set_additional_message(
×
7832
                "sixel_encoder_encode: failed to create the PNG target file.");
7833
            status = SIXEL_LIBC_ERROR;
×
7834
            goto end;
×
7835
        }
7836
    }
7837

7838
    if (encoder == NULL) {
1!
7839
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7840
#  pragma GCC diagnostic push
7841
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
7842
#endif
7843
        encoder = sixel_encoder_create();
7844
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
7845
#  pragma GCC diagnostic pop
7846
#endif
7847
        if (encoder == NULL) {
×
7848
            sixel_helper_set_additional_message(
7849
                "sixel_encoder_encode: sixel_encoder_create() failed.");
7850
            status = SIXEL_BAD_ALLOCATION;
7851
            goto end;
7852
        }
7853
    } else {
7854
        sixel_encoder_ref(encoder);
459✔
7855
    }
7856

7857
    if (encode_allocator == NULL && encoder != NULL) {
459!
7858
        encode_allocator = encoder->allocator;
×
7859
        if (encode_allocator != NULL) {
×
7860
            /* Ensure the allocator stays valid after lazy encoder creation. */
7861
            sixel_allocator_ref(encode_allocator);
×
7862
        }
7863
    }
7864

7865
    if (encoder->assessment_observer != NULL) {
459✔
7866
        sixel_assessment_stage_transition(
3✔
7867
            encoder->assessment_observer,
7868
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
7869
    }
7870
    encoder->last_loader_name[0] = '\0';
459✔
7871
    encoder->last_source_path[0] = '\0';
459✔
7872
    encoder->last_input_bytes = 0u;
459✔
7873

7874
    /* if required color is not set, set the max value */
7875
    if (encoder->reqcolors == (-1)) {
459✔
7876
        encoder->reqcolors = SIXEL_PALETTE_MAX;
438✔
7877
    }
7878

7879
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
459!
7880
        sixel_frame_unref(encoder->capture_source_frame);
×
7881
        encoder->capture_source_frame = NULL;
×
7882
    }
7883

7884
    /* if required color is less then 2, set the min value */
7885
    if (encoder->reqcolors < 2) {
459✔
7886
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
7887
    }
7888

7889
    /* if color space option is not set, choose RGB color space */
7890
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
459✔
7891
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
438✔
7892
    }
7893

7894
    /* if color option is not default value, prohibit to read
7895
       the file as a paletted image */
7896
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
459✔
7897
        fuse_palette = 0;
96✔
7898
    }
7899

7900
    /* if scaling options are set, prohibit to read the file as
7901
       a paletted image */
7902
    if (encoder->percentwidth > 0 ||
459✔
7903
        encoder->percentheight > 0 ||
447✔
7904
        encoder->pixelwidth > 0 ||
441✔
7905
        encoder->pixelheight > 0) {
381✔
7906
        fuse_palette = 0;
108✔
7907
    }
7908

7909
reload:
351✔
7910

7911
    sixel_helper_set_loader_trace(encoder->verbose);
459✔
7912
    sixel_helper_set_thumbnail_size_hint(
459✔
7913
        sixel_encoder_thumbnail_hint(encoder));
7914

7915
    status = sixel_loader_new(&loader, encoder->allocator);
459✔
7916
    if (SIXEL_FAILED(status)) {
459!
7917
        goto load_end;
×
7918
    }
7919

7920
    status = sixel_loader_setopt(loader,
918✔
7921
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
7922
                                 &encoder->fstatic);
459✔
7923
    if (SIXEL_FAILED(status)) {
459!
7924
        goto load_end;
×
7925
    }
7926

7927
    status = sixel_loader_setopt(loader,
459✔
7928
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
7929
                                 &fuse_palette);
7930
    if (SIXEL_FAILED(status)) {
459!
7931
        goto load_end;
×
7932
    }
7933

7934
    status = sixel_loader_setopt(loader,
918✔
7935
                                 SIXEL_LOADER_OPTION_REQCOLORS,
7936
                                 &encoder->reqcolors);
459✔
7937
    if (SIXEL_FAILED(status)) {
459!
7938
        goto load_end;
×
7939
    }
7940

7941
    status = sixel_loader_setopt(loader,
918✔
7942
                                 SIXEL_LOADER_OPTION_BGCOLOR,
7943
                                 encoder->bgcolor);
459✔
7944
    if (SIXEL_FAILED(status)) {
459!
7945
        goto load_end;
×
7946
    }
7947

7948
    status = sixel_loader_setopt(loader,
918✔
7949
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
7950
                                 &encoder->loop_mode);
459✔
7951
    if (SIXEL_FAILED(status)) {
459!
7952
        goto load_end;
×
7953
    }
7954

7955
    status = sixel_loader_setopt(loader,
918✔
7956
                                 SIXEL_LOADER_OPTION_INSECURE,
7957
                                 &encoder->finsecure);
459✔
7958
    if (SIXEL_FAILED(status)) {
459!
7959
        goto load_end;
×
7960
    }
7961

7962
    status = sixel_loader_setopt(loader,
918✔
7963
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
7964
                                 encoder->cancel_flag);
459✔
7965
    if (SIXEL_FAILED(status)) {
459!
7966
        goto load_end;
×
7967
    }
7968

7969
    status = sixel_loader_setopt(loader,
918✔
7970
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
7971
                                 encoder->loader_order);
459✔
7972
    if (SIXEL_FAILED(status)) {
459!
7973
        goto load_end;
×
7974
    }
7975

7976
    status = sixel_loader_setopt(loader,
459✔
7977
                                 SIXEL_LOADER_OPTION_CONTEXT,
7978
                                 encoder);
7979
    if (SIXEL_FAILED(status)) {
459!
7980
        goto load_end;
×
7981
    }
7982

7983
    /*
7984
     * Wire the optional assessment observer into the loader.
7985
     *
7986
     * The observer travels separately from the callback context so mapfile
7987
     * palette probes and other callbacks can keep using arbitrary structs.
7988
     */
7989
    status = sixel_loader_setopt(loader,
918✔
7990
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
7991
                                 encoder->assessment_observer);
459✔
7992
    if (SIXEL_FAILED(status)) {
459!
7993
        goto load_end;
×
7994
    }
7995

7996
    status = sixel_loader_load_file(loader,
459✔
7997
                                    effective_filename,
7998
                                    load_image_callback);
7999
    if (status != SIXEL_OK) {
459✔
8000
        goto load_end;
15✔
8001
    }
8002
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
444✔
8003
    if (sixel_loader_get_last_success_name(loader) != NULL) {
444!
8004
        (void)snprintf(encoder->last_loader_name,
444✔
8005
                       sizeof(encoder->last_loader_name),
8006
                       "%s",
8007
                       sixel_loader_get_last_success_name(loader));
8008
    } else {
8009
        encoder->last_loader_name[0] = '\0';
×
8010
    }
8011
    if (sixel_loader_get_last_source_path(loader) != NULL) {
444✔
8012
        (void)snprintf(encoder->last_source_path,
306✔
8013
                       sizeof(encoder->last_source_path),
8014
                       "%s",
8015
                       sixel_loader_get_last_source_path(loader));
8016
    } else {
8017
        encoder->last_source_path[0] = '\0';
138✔
8018
    }
8019
    if (encoder->assessment_observer != NULL) {
444✔
8020
        sixel_assessment_record_loader(encoder->assessment_observer,
3✔
8021
                                       encoder->last_source_path,
3✔
8022
                                       encoder->last_loader_name,
3✔
8023
                                       encoder->last_input_bytes);
8024
    }
8025

8026
load_end:
441✔
8027
    sixel_loader_unref(loader);
459✔
8028
    loader = NULL;
459✔
8029

8030
    if (status != SIXEL_OK) {
459✔
8031
        goto end;
15✔
8032
    }
8033

8034
    palette_status = sixel_encoder_emit_palette_output(encoder);
444✔
8035
    if (SIXEL_FAILED(palette_status)) {
444!
8036
        status = palette_status;
×
8037
        goto end;
×
8038
    }
8039

8040
    if (encoder->pipe_mode) {
444!
8041
#if HAVE_CLEARERR
8042
        clearerr(stdin);
×
8043
#endif  /* HAVE_FSEEK */
8044
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
8045
            status = sixel_tty_wait_stdin(1000000);
×
8046
            if (SIXEL_FAILED(status)) {
×
8047
                goto end;
×
8048
            }
8049
            if (status != SIXEL_OK) {
×
8050
                break;
8051
            }
8052
        }
8053
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
8054
            goto reload;
×
8055
        }
8056
    }
8057

8058
    if (encoder->assessment_observer) {
444✔
8059
        if (assessment_allocator == NULL || encoder->assessment_observer == NULL) {
3!
8060
            status = SIXEL_RUNTIME_ERROR;
×
8061
            goto end;
×
8062
        }
8063
        if (assessment_need_source_capture) {
3!
8064
            status = sixel_encoder_copy_source_frame(encoder,
3✔
8065
                                                     &assessment_source_frame);
8066
            if (SIXEL_FAILED(status)) {
3!
8067
                goto end;
×
8068
            }
8069
            sixel_assessment_record_source_frame(encoder->assessment_observer,
3✔
8070
                                                 assessment_source_frame);
8071
        }
8072
        if (assessment_need_quality) {
3!
8073
            if (assessment_quality_quantized) {
×
8074
                status = sixel_encoder_copy_quantized_frame(
×
8075
                    encoder, assessment_allocator, &assessment_target_frame);
8076
                if (SIXEL_FAILED(status)) {
×
8077
                    goto end;
×
8078
                }
8079
                status = sixel_assessment_expand_quantized_frame(
×
8080
                    assessment_target_frame,
8081
                    assessment_allocator,
8082
                    &assessment_expanded_frame);
8083
                if (SIXEL_FAILED(status)) {
×
8084
                    goto end;
×
8085
                }
8086
                sixel_frame_unref(assessment_target_frame);
×
8087
                assessment_target_frame = assessment_expanded_frame;
×
8088
                assessment_expanded_frame = NULL;
×
8089
                sixel_assessment_record_quantized_capture(
×
8090
                    encoder->assessment_observer, encoder);
×
8091
            } else {
8092
                status = sixel_assessment_load_single_frame(
×
8093
                    encoder->sixel_output_path,
×
8094
                    assessment_allocator,
8095
                    &assessment_target_frame);
8096
                if (SIXEL_FAILED(status)) {
×
8097
                    goto end;
×
8098
                }
8099
            }
8100
            if (!assessment_quality_quantized &&
×
8101
                    assessment_need_quantized_capture) {
8102
                sixel_assessment_record_quantized_capture(
×
8103
                    encoder->assessment_observer, encoder);
×
8104
            }
8105
        } else if (assessment_need_quantized_capture) {
3!
8106
            sixel_assessment_record_quantized_capture(
3✔
8107
                encoder->assessment_observer, encoder);
3✔
8108
        }
8109
        if (encoder->assessment_observer != NULL &&
3!
8110
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
8111
            sixel_assessment_stage_transition(
×
8112
                encoder->assessment_observer,
8113
                SIXEL_ASSESSMENT_STAGE_OUTPUT);
8114
        }
8115
        if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
×
8116
            status = copy_file_to_stream(assessment_temp_path,
×
8117
                                         stdout,
8118
                                         encoder->assessment_observer);
×
8119
            if (SIXEL_FAILED(status)) {
×
8120
                goto end;
×
8121
            }
8122
        } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
3!
8123
            if (assessment_forward_path == NULL) {
×
8124
                sixel_helper_set_additional_message(
×
8125
                    "sixel_encoder_encode: missing assessment spool target.");
8126
                status = SIXEL_RUNTIME_ERROR;
×
8127
                goto end;
×
8128
            }
8129
            assessment_forward_stream = sixel_compat_fopen(
×
8130
                assessment_forward_path,
8131
                "wb");
8132
            if (assessment_forward_stream == NULL) {
×
8133
                sixel_helper_set_additional_message(
×
8134
                    "sixel_encoder_encode: failed to open assessment spool sink.");
8135
                status = SIXEL_LIBC_ERROR;
×
8136
                goto end;
×
8137
            }
8138
            status = copy_file_to_stream(assessment_temp_path,
×
8139
                                         assessment_forward_stream,
8140
                                         encoder->assessment_observer);
×
8141
            if (fclose(assessment_forward_stream) != 0) {
×
8142
                if (SIXEL_SUCCEEDED(status)) {
×
8143
                    sixel_helper_set_additional_message(
×
8144
                        "img2sixel: failed to close assessment spool sink.");
8145
                    status = SIXEL_LIBC_ERROR;
×
8146
                }
8147
            }
8148
            assessment_forward_stream = NULL;
×
8149
            if (SIXEL_FAILED(status)) {
×
8150
                goto end;
×
8151
            }
8152
        }
8153
        if (encoder->assessment_observer != NULL &&
3!
8154
                assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
8155
            sixel_assessment_stage_finish(encoder->assessment_observer);
×
8156
        }
8157
#if HAVE_SYS_STAT_H
8158
        assessment_output_bytes = 0u;
3✔
8159
        assessment_size_path = NULL;
3✔
8160
        if (assessment_need_quality && !assessment_quality_quantized) {
3!
8161
            if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
×
8162
                    assessment_spool_mode ==
8163
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
8164
                assessment_size_path = assessment_temp_path;
8165
            } else if (encoder->sixel_output_path != NULL &&
×
8166
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
8167
                assessment_size_path = encoder->sixel_output_path;
8168
            }
8169
        } else {
8170
            if (encoder->sixel_output_path != NULL &&
3!
8171
                    strcmp(encoder->sixel_output_path, "-") != 0) {
×
8172
                assessment_size_path = encoder->sixel_output_path;
8173
            } else if (assessment_spool_mode ==
3!
8174
                    SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT ||
3!
8175
                    assessment_spool_mode ==
8176
                        SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
8177
                assessment_size_path = assessment_temp_path;
8178
            }
8179
        }
8180
        if (assessment_size_path != NULL) {
×
8181
            assessment_stat_result = stat(assessment_size_path,
×
8182
                                          &assessment_stat);
8183
            if (assessment_stat_result == 0 &&
×
8184
                    assessment_stat.st_size >= 0) {
×
8185
                assessment_output_bytes =
×
8186
                    (size_t)assessment_stat.st_size;
8187
            }
8188
        }
8189
#else
8190
        assessment_output_bytes = 0u;
8191
#endif
8192
        sixel_assessment_record_output_size(encoder->assessment_observer,
3✔
8193
                                            assessment_output_bytes);
8194
        if (assessment_need_quality) {
3!
8195
            status = sixel_assessment_analyze(encoder->assessment_observer,
×
8196
                                              assessment_source_frame,
8197
                                              assessment_target_frame);
8198
            if (SIXEL_FAILED(status)) {
×
8199
                goto end;
×
8200
            }
8201
        }
8202
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
8203
        if (encoder->assessment_json_path != NULL &&
3!
8204
                strcmp(encoder->assessment_json_path, "-") != 0) {
3!
8205
            assessment_json_file = sixel_compat_fopen(
3✔
8206
                encoder->assessment_json_path,
8207
                "wb");
8208
            if (assessment_json_file == NULL) {
3!
8209
                sixel_helper_set_additional_message(
×
8210
                    "sixel_encoder_encode: failed to open assessment JSON file.");
8211
                status = SIXEL_LIBC_ERROR;
×
8212
                goto end;
×
8213
            }
8214
            assessment_json_owned = 1;
3✔
8215
            assessment_sink.stream = assessment_json_file;
3✔
8216
        } else {
8217
            assessment_sink.stream = stdout;
×
8218
        }
8219
        assessment_sink.failed = 0;
3✔
8220
        status = sixel_assessment_get_json(encoder->assessment_observer,
3✔
8221
                                           encoder->assessment_sections,
8222
                                           assessment_json_callback,
8223
                                           &assessment_sink);
8224
        if (SIXEL_FAILED(status) || assessment_sink.failed) {
3!
8225
            sixel_helper_set_additional_message(
×
8226
                "sixel_encoder_encode: failed to emit assessment JSON.");
8227
            goto end;
×
8228
        }
8229
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_STDOUT) {
441!
8230
        status = copy_file_to_stream(assessment_temp_path,
×
8231
                                     stdout,
8232
                                     encoder->assessment_observer);
8233
        if (SIXEL_FAILED(status)) {
×
8234
            goto end;
×
8235
        }
8236
    } else if (assessment_spool_mode == SIXEL_ASSESSMENT_SPOOL_MODE_PATH) {
441!
8237
        if (assessment_forward_path == NULL) {
×
8238
            sixel_helper_set_additional_message(
×
8239
                "img2sixel: missing assessment spool target.");
8240
            status = SIXEL_RUNTIME_ERROR;
×
8241
            goto end;
×
8242
        }
8243
        assessment_forward_stream = sixel_compat_fopen(
×
8244
            assessment_forward_path,
8245
            "wb");
8246
        if (assessment_forward_stream == NULL) {
×
8247
            sixel_helper_set_additional_message(
×
8248
                "img2sixel: failed to open assessment spool sink.");
8249
            status = SIXEL_LIBC_ERROR;
×
8250
            goto end;
×
8251
        }
8252
        status = copy_file_to_stream(assessment_temp_path,
×
8253
                                     assessment_forward_stream,
8254
                                     encoder->assessment_observer);
×
8255
        if (fclose(assessment_forward_stream) != 0) {
×
8256
            if (SIXEL_SUCCEEDED(status)) {
×
8257
                sixel_helper_set_additional_message(
×
8258
                    "img2sixel: failed to close assessment spool sink.");
8259
                status = SIXEL_LIBC_ERROR;
×
8260
            }
8261
        }
8262
        assessment_forward_stream = NULL;
×
8263
        if (SIXEL_FAILED(status)) {
×
8264
            goto end;
×
8265
        }
8266
    }
8267

8268
    if (encoder->output_is_png) {
444✔
8269
        png_final_path = encoder->output_png_to_stdout ? "-" : encoder->png_output_path;
9!
8270
        if (! encoder->output_png_to_stdout && png_final_path == NULL) {
9!
8271
            sixel_helper_set_additional_message(
×
8272
                "sixel_encoder_encode: missing PNG output path.");
8273
            status = SIXEL_RUNTIME_ERROR;
×
8274
            goto end;
×
8275
        }
8276
        status = write_png_from_sixel(png_temp_path, png_final_path);
9✔
8277
        if (SIXEL_FAILED(status)) {
9!
8278
            goto end;
×
8279
        }
8280
    }
8281

8282
    if (encoder->clipboard_output_active
444!
8283
            && encoder->clipboard_output_path != NULL) {
×
8284
        unsigned char *clipboard_output_data;
×
8285
        size_t clipboard_output_size;
×
8286

8287
        clipboard_output_data = NULL;
×
8288
        clipboard_output_size = 0u;
×
8289

8290
        if (encoder->outfd
×
8291
                && encoder->outfd != STDOUT_FILENO
1!
8292
                && encoder->outfd != STDERR_FILENO) {
×
8293
            (void)sixel_compat_close(encoder->outfd);
×
8294
            encoder->outfd = STDOUT_FILENO;
×
8295
        }
8296

8297
        clipboard_status = clipboard_read_file(
×
8298
            encoder->clipboard_output_path,
×
8299
            &clipboard_output_data,
8300
            &clipboard_output_size);
8301
        if (SIXEL_SUCCEEDED(clipboard_status)) {
×
8302
            clipboard_status = sixel_clipboard_write(
×
8303
                encoder->clipboard_output_format,
×
8304
                clipboard_output_data,
8305
                clipboard_output_size);
8306
        }
8307
        if (clipboard_output_data != NULL) {
×
8308
            free(clipboard_output_data);
×
8309
        }
8310
        if (SIXEL_FAILED(clipboard_status)) {
×
8311
            status = clipboard_status;
×
8312
            goto end;
×
8313
        }
8314
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
8315
        sixel_allocator_free(encoder->allocator,
×
8316
                             encoder->clipboard_output_path);
×
8317
        encoder->clipboard_output_path = NULL;
×
8318
        encoder->sixel_output_path = NULL;
×
8319
        encoder->clipboard_output_active = 0;
×
8320
        encoder->clipboard_output_format[0] = '\0';
×
8321
    }
1!
8322

8323
    /* the status may not be SIXEL_OK */
8324

8325
end:
444✔
8326
    if (png_temp_path != NULL) {
459✔
8327
        (void)sixel_compat_unlink(png_temp_path);
9✔
8328
    }
8329
    sixel_allocator_free(encoder->allocator, png_temp_path);
459✔
8330
    if (clipboard_input_path != NULL) {
459!
8331
        (void)sixel_compat_unlink(clipboard_input_path);
×
8332
        sixel_allocator_free(encoder->allocator, clipboard_input_path);
×
8333
    }
8334
    if (clipboard_blob != NULL) {
459!
8335
        free(clipboard_blob);
×
8336
    }
8337
    if (encoder->clipboard_output_path != NULL) {
459!
8338
        (void)sixel_compat_unlink(encoder->clipboard_output_path);
×
8339
        sixel_allocator_free(encoder->allocator,
×
8340
                             encoder->clipboard_output_path);
×
8341
        encoder->clipboard_output_path = NULL;
×
8342
        encoder->sixel_output_path = NULL;
×
8343
        encoder->clipboard_output_active = 0;
×
8344
        encoder->clipboard_output_format[0] = '\0';
×
8345
    }
8346
    sixel_allocator_free(encoder->allocator, encoder->png_output_path);
459✔
8347
    encoder->png_output_path = NULL;
459✔
8348
    if (assessment_forward_stream != NULL) {
459!
8349
        (void) fclose(assessment_forward_stream);
8350
    }
8351
    if (assessment_temp_path != NULL &&
459!
8352
            assessment_spool_mode != SIXEL_ASSESSMENT_SPOOL_MODE_NONE) {
459!
8353
        (void)sixel_compat_unlink(assessment_temp_path);
×
8354
    }
8355
    sixel_allocator_free(encoder->allocator, assessment_temp_path);
459✔
8356
    sixel_allocator_free(encoder->allocator, assessment_forward_path);
459✔
8357
    if (assessment_json_owned && assessment_json_file != NULL) {
459!
8358
        (void) fclose(assessment_json_file);
3✔
8359
    }
8360
    if (assessment_target_frame != NULL) {
459!
8361
        sixel_frame_unref(assessment_target_frame);
×
8362
    }
8363
    if (assessment_expanded_frame != NULL) {
459!
8364
        sixel_frame_unref(assessment_expanded_frame);
×
8365
    }
8366
    if (assessment_source_frame != NULL) {
459✔
8367
        sixel_frame_unref(assessment_source_frame);
3✔
8368
    }
8369
    if (encoder->assessment_observer != NULL) {
459✔
8370
        sixel_assessment_unref(encoder->assessment_observer);
3✔
8371
        encoder->assessment_observer = NULL;
3✔
8372
    }
8373
    if (assessment_allocator != NULL) {
459✔
8374
        sixel_allocator_unref(assessment_allocator);
3✔
8375
    }
8376

8377
    if (encoder != NULL) {
459!
8378
        encoder->logger = NULL;
459✔
8379
        encoder->parallel_job_id = -1;
459✔
8380
    }
8381
    if (logger_prepared) {
459!
8382
        sixel_logger_close(&logger);
×
8383
    }
8384

8385
    sixel_encoder_unref(encoder);
459✔
8386

8387
    if (encode_allocator != NULL) {
459!
8388
        /*
8389
         * Release the retained allocator reference *after* dropping the
8390
         * encoder reference so that a lazily created encoder can run its
8391
         * destructor while the allocator is still alive.  This ensures that
8392
         * cleanup routines never dereference a freed allocator instance.
8393
         */
8394
        sixel_allocator_unref(encode_allocator);
459✔
8395
        encode_allocator = NULL;
459✔
8396
    }
8397

8398
    return status;
459✔
8399
}
8400

8401

8402
/* encode specified pixel data to SIXEL format
8403
 * output to encoder->outfd */
8404
SIXELAPI SIXELSTATUS
8405
sixel_encoder_encode_bytes(
3✔
8406
    sixel_encoder_t     /* in */    *encoder,
8407
    unsigned char       /* in */    *bytes,
8408
    int                 /* in */    width,
8409
    int                 /* in */    height,
8410
    int                 /* in */    pixelformat,
8411
    unsigned char       /* in */    *palette,
8412
    int                 /* in */    ncolors)
8413
{
8414
    SIXELSTATUS status = SIXEL_FALSE;
3✔
8415
    sixel_frame_t *frame = NULL;
3✔
8416

8417
    if (encoder == NULL || bytes == NULL) {
3!
8418
        status = SIXEL_BAD_ARGUMENT;
×
8419
        goto end;
×
8420
    }
8421

8422
    status = sixel_frame_new(&frame, encoder->allocator);
3✔
8423
    if (SIXEL_FAILED(status)) {
3!
8424
        goto end;
×
8425
    }
8426

8427
    status = sixel_frame_init(frame, bytes, width, height,
3✔
8428
                              pixelformat, palette, ncolors);
8429
    if (SIXEL_FAILED(status)) {
3!
8430
        goto end;
×
8431
    }
8432

8433
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
3✔
8434
    if (SIXEL_FAILED(status)) {
3!
8435
        goto end;
×
8436
    }
8437

8438
    status = SIXEL_OK;
8439

8440
end:
3✔
8441
    /* we need to free the frame before exiting, but we can't use the
8442
       sixel_frame_destroy function, because that will also attempt to
8443
       free the pixels and palette, which we don't own */
8444
    if (frame != NULL && encoder->allocator != NULL) {
3!
8445
        sixel_allocator_free(encoder->allocator, frame);
3✔
8446
        sixel_allocator_unref(encoder->allocator);
3✔
8447
    }
8448
    return status;
3✔
8449
}
8450

8451

8452
/*
8453
 * Toggle source-frame capture for assessment consumers.
8454
 */
8455
SIXELAPI SIXELSTATUS
8456
sixel_encoder_enable_source_capture(
3✔
8457
    sixel_encoder_t *encoder,
8458
    int enable)
8459
{
8460
    if (encoder == NULL) {
3!
8461
        sixel_helper_set_additional_message(
×
8462
            "sixel_encoder_enable_source_capture: encoder is null.");
8463
        return SIXEL_BAD_ARGUMENT;
×
8464
    }
8465

8466
    encoder->capture_source = enable ? 1 : 0;
3✔
8467
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
3!
8468
        sixel_frame_unref(encoder->capture_source_frame);
×
8469
        encoder->capture_source_frame = NULL;
×
8470
    }
8471

8472
    return SIXEL_OK;
8473
}
8474

8475

8476
/*
8477
 * Enable or disable the quantized-frame capture facility.
8478
 *
8479
 *     capture on --> encoder keeps the latest palette-quantized frame.
8480
 *     capture off --> encoder forgets previously stored frames.
8481
 */
8482
SIXELAPI SIXELSTATUS
8483
sixel_encoder_enable_quantized_capture(
3✔
8484
    sixel_encoder_t *encoder,
8485
    int enable)
8486
{
8487
    if (encoder == NULL) {
2!
8488
        sixel_helper_set_additional_message(
×
8489
            "sixel_encoder_enable_quantized_capture: encoder is null.");
8490
        return SIXEL_BAD_ARGUMENT;
×
8491
    }
8492

8493
    encoder->capture_quantized = enable ? 1 : 0;
3✔
8494
    if (!encoder->capture_quantized) {
3!
8495
        encoder->capture_valid = 0;
×
8496
    }
8497

8498
    return SIXEL_OK;
8499
}
8500

8501

8502
/*
8503
 * Materialize the captured quantized frame as a heap-allocated
8504
 * sixel_frame_t instance for assessment consumers.
8505
 */
8506
SIXELAPI SIXELSTATUS
8507
sixel_encoder_copy_quantized_frame(
×
8508
    sixel_encoder_t   *encoder,
8509
    sixel_allocator_t *allocator,
8510
    sixel_frame_t     **ppframe)
8511
{
8512
    SIXELSTATUS status = SIXEL_FALSE;
×
8513
    sixel_frame_t *frame;
×
8514
    unsigned char *pixels;
×
8515
    unsigned char *palette;
×
8516
    size_t palette_bytes;
×
8517

8518
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
8519
        sixel_helper_set_additional_message(
×
8520
            "sixel_encoder_copy_quantized_frame: invalid argument.");
8521
        return SIXEL_BAD_ARGUMENT;
×
8522
    }
8523

8524
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
8525
        sixel_helper_set_additional_message(
×
8526
            "sixel_encoder_copy_quantized_frame: no frame captured.");
8527
        return SIXEL_RUNTIME_ERROR;
×
8528
    }
8529

8530
    *ppframe = NULL;
×
8531
    frame = NULL;
×
8532
    pixels = NULL;
×
8533
    palette = NULL;
×
8534

8535
    status = sixel_frame_new(&frame, allocator);
×
8536
    if (SIXEL_FAILED(status)) {
×
8537
        return status;
8538
    }
8539

8540
    if (encoder->capture_pixel_bytes > 0) {
×
8541
        pixels = (unsigned char *)sixel_allocator_malloc(
×
8542
            allocator, encoder->capture_pixel_bytes);
8543
        if (pixels == NULL) {
×
8544
            sixel_helper_set_additional_message(
×
8545
                "sixel_encoder_copy_quantized_frame: "
8546
                "sixel_allocator_malloc() failed.");
8547
            status = SIXEL_BAD_ALLOCATION;
×
8548
            goto cleanup;
×
8549
        }
8550
        memcpy(pixels,
×
8551
               encoder->capture_pixels,
×
8552
               encoder->capture_pixel_bytes);
8553
    }
8554

8555
    palette_bytes = encoder->capture_palette_size;
×
8556
    if (palette_bytes > 0) {
×
8557
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
8558
                                                          palette_bytes);
8559
        if (palette == NULL) {
×
8560
            sixel_helper_set_additional_message(
×
8561
                "sixel_encoder_copy_quantized_frame: "
8562
                "sixel_allocator_malloc() failed.");
8563
            status = SIXEL_BAD_ALLOCATION;
×
8564
            goto cleanup;
×
8565
        }
8566
        memcpy(palette,
×
8567
               encoder->capture_palette,
×
8568
               palette_bytes);
8569
    }
8570

8571
    status = sixel_frame_init(frame,
×
8572
                              pixels,
8573
                              encoder->capture_width,
8574
                              encoder->capture_height,
8575
                              encoder->capture_pixelformat,
8576
                              palette,
8577
                              encoder->capture_ncolors);
8578
    if (SIXEL_FAILED(status)) {
×
8579
        goto cleanup;
×
8580
    }
8581

8582
    pixels = NULL;
×
8583
    palette = NULL;
×
8584
    /*
8585
     * Capture colorspace must be preserved for assessment consumers.
8586
     * Keep access encapsulated via the public setter to avoid
8587
     * depending on frame internals.
8588
     */
8589
    sixel_frame_set_colorspace(frame, encoder->capture_colorspace);
×
8590
    *ppframe = frame;
×
8591
    return SIXEL_OK;
×
8592

8593
cleanup:
×
8594
    if (palette != NULL) {
×
8595
        sixel_allocator_free(allocator, palette);
×
8596
    }
8597
    if (pixels != NULL) {
×
8598
        sixel_allocator_free(allocator, pixels);
×
8599
    }
8600
    if (frame != NULL) {
×
8601
        sixel_frame_unref(frame);
×
8602
    }
8603
    return status;
8604
}
8605

8606

8607
/*
8608
 * Emit the captured palette in the requested format.
8609
 *
8610
 *   palette_output == NULL  -> skip
8611
 *   palette_output != NULL  -> materialize captured palette
8612
 */
8613
static SIXELSTATUS
8614
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
444✔
8615
{
8616
    SIXELSTATUS status;
444✔
8617
    sixel_frame_t *frame;
444✔
8618
    unsigned char const *palette;
444✔
8619
    int exported_colors;
444✔
8620
    FILE *stream;
444✔
8621
    int close_stream;
444✔
8622
    char const *path;
444✔
8623
    sixel_palette_format_t format_hint;
444✔
8624
    sixel_palette_format_t format_ext;
444✔
8625
    sixel_palette_format_t format_final;
444✔
8626
    char const *mode;
444✔
8627

8628
    status = SIXEL_OK;
444✔
8629
    frame = NULL;
444✔
8630
    palette = NULL;
444✔
8631
    exported_colors = 0;
444✔
8632
    stream = NULL;
444✔
8633
    close_stream = 0;
444✔
8634
    path = NULL;
444✔
8635
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
444✔
8636
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
444✔
8637
    format_final = SIXEL_PALETTE_FORMAT_NONE;
444✔
8638
    mode = "wb";
444✔
8639

8640
    if (encoder == NULL || encoder->palette_output == NULL) {
444!
8641
        return SIXEL_OK;
8642
    }
8643

8644
    status = sixel_encoder_copy_quantized_frame(encoder,
×
8645
                                                encoder->allocator,
8646
                                                &frame);
8647
    if (SIXEL_FAILED(status)) {
×
8648
        return status;
8649
    }
8650

8651
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
8652
    exported_colors = sixel_frame_get_ncolors(frame);
×
8653
    if (palette == NULL || exported_colors <= 0) {
×
8654
        sixel_helper_set_additional_message(
×
8655
            "sixel_encoder_emit_palette_output: palette unavailable.");
8656
        status = SIXEL_BAD_INPUT;
×
8657
        goto cleanup;
×
8658
    }
8659
    if (exported_colors > 256) {
×
8660
        exported_colors = 256;
8661
    }
8662

8663
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
8664
    if (path == NULL || *path == '\0') {
×
8665
        sixel_helper_set_additional_message(
×
8666
            "sixel_encoder_emit_palette_output: invalid path.");
8667
        status = SIXEL_BAD_ARGUMENT;
×
8668
        goto cleanup;
×
8669
    }
8670

8671
    format_ext = sixel_palette_format_from_extension(path);
×
8672
    format_final = format_hint;
×
8673
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
8674
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
8675
            if (strcmp(path, "-") == 0) {
×
8676
                sixel_helper_set_additional_message(
×
8677
                    "sixel_encoder_emit_palette_output: "
8678
                    "format required for '-'.");
8679
                status = SIXEL_BAD_ARGUMENT;
×
8680
                goto cleanup;
×
8681
            }
8682
            sixel_helper_set_additional_message(
×
8683
                "sixel_encoder_emit_palette_output: "
8684
                "unknown palette file extension.");
8685
            status = SIXEL_BAD_ARGUMENT;
×
8686
            goto cleanup;
×
8687
        }
8688
        format_final = format_ext;
8689
    }
8690
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
8691
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
8692
    }
8693

8694
    if (strcmp(path, "-") == 0) {
×
8695
        stream = stdout;
×
8696
    } else {
8697
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
8698
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
×
8699
            mode = "w";
8700
        } else {
8701
            mode = "wb";
×
8702
        }
8703
        stream = sixel_compat_fopen(path, mode);
×
8704
        if (stream == NULL) {
×
8705
            sixel_helper_set_additional_message(
×
8706
                "sixel_encoder_emit_palette_output: failed to open file.");
8707
            status = SIXEL_LIBC_ERROR;
×
8708
            goto cleanup;
×
8709
        }
8710
        close_stream = 1;
8711
    }
8712

8713
    switch (format_final) {
×
8714
    case SIXEL_PALETTE_FORMAT_ACT:
×
8715
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
8716
        if (SIXEL_FAILED(status)) {
×
8717
            sixel_helper_set_additional_message(
×
8718
                "sixel_encoder_emit_palette_output: failed to write ACT.");
8719
        }
8720
        break;
8721
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
×
8722
        status = sixel_palette_write_pal_jasc(stream,
×
8723
                                              palette,
8724
                                              exported_colors);
8725
        if (SIXEL_FAILED(status)) {
×
8726
            sixel_helper_set_additional_message(
×
8727
                "sixel_encoder_emit_palette_output: failed to write JASC.");
8728
        }
8729
        break;
8730
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
×
8731
        status = sixel_palette_write_pal_riff(stream,
×
8732
                                              palette,
8733
                                              exported_colors);
8734
        if (SIXEL_FAILED(status)) {
×
8735
            sixel_helper_set_additional_message(
×
8736
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
8737
        }
8738
        break;
8739
    case SIXEL_PALETTE_FORMAT_GPL:
×
8740
        status = sixel_palette_write_gpl(stream,
×
8741
                                         palette,
8742
                                         exported_colors);
8743
        if (SIXEL_FAILED(status)) {
×
8744
            sixel_helper_set_additional_message(
×
8745
                "sixel_encoder_emit_palette_output: failed to write GPL.");
8746
        }
8747
        break;
8748
    default:
×
8749
        sixel_helper_set_additional_message(
×
8750
            "sixel_encoder_emit_palette_output: unsupported format.");
8751
        status = SIXEL_BAD_ARGUMENT;
×
8752
        break;
×
8753
    }
8754
    if (SIXEL_FAILED(status)) {
×
8755
        goto cleanup;
×
8756
    }
8757

8758
    if (close_stream) {
×
8759
        if (fclose(stream) != 0) {
×
8760
            sixel_helper_set_additional_message(
×
8761
                "sixel_encoder_emit_palette_output: fclose() failed.");
8762
            status = SIXEL_LIBC_ERROR;
×
8763
            stream = NULL;
×
8764
            goto cleanup;
×
8765
        }
8766
        stream = NULL;
8767
    } else {
8768
        if (fflush(stream) != 0) {
×
8769
            sixel_helper_set_additional_message(
×
8770
                "sixel_encoder_emit_palette_output: fflush() failed.");
8771
            status = SIXEL_LIBC_ERROR;
×
8772
            goto cleanup;
×
8773
        }
8774
    }
8775

8776
cleanup:
×
8777
    if (close_stream && stream != NULL) {
×
8778
        (void) fclose(stream);
×
8779
    }
8780
    if (frame != NULL) {
×
8781
        sixel_frame_unref(frame);
×
8782
    }
8783

8784
    return status;
8785
}
8786

8787

8788
/*
8789
 * Share the captured source frame with assessment consumers.
8790
 */
8791
SIXELAPI SIXELSTATUS
8792
sixel_encoder_copy_source_frame(
3✔
8793
    sixel_encoder_t *encoder,
8794
    sixel_frame_t  **ppframe)
8795
{
8796
    if (encoder == NULL || ppframe == NULL) {
3!
8797
        sixel_helper_set_additional_message(
×
8798
            "sixel_encoder_copy_source_frame: invalid argument.");
8799
        return SIXEL_BAD_ARGUMENT;
×
8800
    }
8801

8802
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
3!
8803
        sixel_helper_set_additional_message(
×
8804
            "sixel_encoder_copy_source_frame: no frame captured.");
8805
        return SIXEL_RUNTIME_ERROR;
×
8806
    }
8807

8808
    sixel_frame_ref(encoder->capture_source_frame);
3✔
8809
    *ppframe = encoder->capture_source_frame;
3✔
8810

8811
    return SIXEL_OK;
3✔
8812
}
8813

8814

8815
#if HAVE_TESTS
8816
/* Validate legacy constructor and refcounting paths for the encoder. */
8817
static int
8818
encoder_test_create_with_legacy_api(void)
×
8819
{
8820
    int nret = EXIT_FAILURE;
×
8821
    sixel_encoder_t *encoder = NULL;
×
8822

8823
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8824
#  pragma GCC diagnostic push
8825
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8826
#endif
8827
    encoder = sixel_encoder_create();
×
8828
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8829
#  pragma GCC diagnostic pop
8830
#endif
8831
    if (encoder == NULL) {
×
8832
        goto error;
×
8833
    }
8834
    sixel_encoder_ref(encoder);
×
8835
    sixel_encoder_unref(encoder);
×
8836
    nret = EXIT_SUCCESS;
×
8837

8838
error:
×
8839
    sixel_encoder_unref(encoder);
×
8840
    return nret;
×
8841
}
8842

8843

8844
/*
8845
 * Exercise frame initialization and terminal scroll helper with a tiny
8846
 * frame.
8847
 */
8848
static int
8849
encoder_test_scroll_with_frame(void)
×
8850
{
8851
    int nret = EXIT_FAILURE;
×
8852
    SIXELSTATUS status;
×
8853
    sixel_encoder_t *encoder = NULL;
×
8854
    sixel_frame_t *frame = NULL;
×
8855
    unsigned char *buffer;
×
8856
    int height = 0;
×
8857
    int is_animation = 0;
×
8858

8859
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8860
#  pragma GCC diagnostic push
8861
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8862
#endif
8863
    encoder = sixel_encoder_create();
×
8864
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8865
#  pragma GCC diagnostic pop
8866
#endif
8867
    if (encoder == NULL) {
×
8868
        goto error;
×
8869
    }
8870

8871
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8872
#  pragma GCC diagnostic push
8873
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8874
#endif
8875
    frame = sixel_frame_create();
×
8876
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8877
#  pragma GCC diagnostic pop
8878
#endif
8879
    if (encoder == NULL) {
×
8880
        goto error;
8881
    }
8882

8883
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
8884
    if (buffer == NULL) {
×
8885
        goto error;
×
8886
    }
8887
    status = sixel_frame_init(frame, buffer, 1, 1,
×
8888
                              SIXEL_PIXELFORMAT_RGB888,
8889
                              NULL, 0);
8890
    if (SIXEL_FAILED(status)) {
×
8891
        goto error;
×
8892
    }
8893

8894
    if (sixel_frame_get_loop_no(frame) != 0 ||
×
8895
            sixel_frame_get_frame_no(frame) != 0) {
×
8896
        is_animation = 1;
8897
    }
8898

8899
    height = sixel_frame_get_height(frame);
×
8900

8901
    status = sixel_tty_scroll(sixel_write_callback,
×
8902
                              &encoder->outfd,
×
8903
                              encoder->outfd,
8904
                              height,
8905
                              is_animation);
8906
    if (SIXEL_FAILED(status)) {
×
8907
        goto error;
×
8908
    }
8909

8910
    nret = EXIT_SUCCESS;
8911

8912
error:
×
8913
    sixel_encoder_unref(encoder);
×
8914
    sixel_frame_unref(frame);
×
8915
    return nret;
×
8916
}
8917

8918

8919
/* Confirm stdin wait helper handles timeout path without errors. */
8920
static int
8921
encoder_test_wait_stdin_timeout(void)
×
8922
{
8923
    int nret = EXIT_FAILURE;
×
8924
    int result;
×
8925

8926
    result = sixel_tty_wait_stdin(1000);
×
8927
    if (result != 0) {
×
8928
        goto error;
×
8929
    }
8930

8931
    nret = EXIT_SUCCESS;
8932

8933
error:
×
8934
    return nret;
×
8935
}
8936

8937

8938
/* Ensure option setter accepts loop and pipe forcing values. */
8939
static int
8940
encoder_test_force_options(void)
×
8941
{
8942
    int nret = EXIT_FAILURE;
×
8943
    sixel_encoder_t *encoder = NULL;
×
8944
    SIXELSTATUS status;
×
8945

8946
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8947
# pragma GCC diagnostic push
8948
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
8949
#endif
8950
    encoder = sixel_encoder_create();
×
8951
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
8952
# pragma GCC diagnostic pop
8953
#endif
8954
    if (encoder == NULL) {
×
8955
        goto error;
×
8956
    }
8957

8958
    status = sixel_encoder_setopt(encoder,
×
8959
                                  SIXEL_OPTFLAG_LOOPMODE,
8960
                                  "force");
8961
    if (SIXEL_FAILED(status)) {
×
8962
        goto error;
×
8963
    }
8964

8965
    status = sixel_encoder_setopt(encoder,
×
8966
                                  SIXEL_OPTFLAG_PIPE_MODE,
8967
                                  "force");
8968
    if (SIXEL_FAILED(status)) {
×
8969
        goto error;
×
8970
    }
8971

8972
    nret = EXIT_SUCCESS;
8973

8974
error:
×
8975
    sixel_encoder_unref(encoder);
×
8976
    return nret;
×
8977
}
8978

8979

8980
/* Validate encoder construction with explicit allocator wiring. */
8981
static int
8982
encoder_test_new_with_allocator(void)
×
8983
{
8984
    int nret = EXIT_FAILURE;
×
8985
    sixel_encoder_t *encoder = NULL;
×
8986
    sixel_allocator_t *allocator = NULL;
×
8987
    SIXELSTATUS status;
×
8988

8989
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
8990
    if (SIXEL_FAILED(status)) {
×
8991
        goto error;
×
8992
    }
8993

8994
    status = sixel_encoder_new(&encoder, allocator);
×
8995
    if (SIXEL_FAILED(status)) {
×
8996
        goto error;
×
8997
    }
8998

8999
    sixel_encoder_ref(encoder);
×
9000
    sixel_encoder_unref(encoder);
×
9001
    nret = EXIT_SUCCESS;
×
9002

9003
error:
×
9004
    sixel_encoder_unref(encoder);
×
9005
    return nret;
×
9006
}
9007

9008

9009
SIXELAPI int
9010
sixel_encoder_tests_main(void)
×
9011
{
9012
    int nret = EXIT_FAILURE;
×
9013
    size_t i;
×
9014
    typedef int (* testcase)(void);
×
9015

9016
    static testcase const testcases[] = {
×
9017
        encoder_test_create_with_legacy_api,
9018
        encoder_test_scroll_with_frame,
9019
        encoder_test_wait_stdin_timeout,
9020
        encoder_test_force_options,
9021
        encoder_test_new_with_allocator
9022
    };
9023

9024
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
9025
        nret = testcases[i]();
×
9026
        if (nret != EXIT_SUCCESS) {
×
9027
            goto error;
×
9028
        }
9029
    }
9030

9031
    nret = EXIT_SUCCESS;
9032

9033
error:
×
9034
    return nret;
×
9035
}
9036
#endif  /* HAVE_TESTS */
9037

9038

9039
/* emacs Local Variables:      */
9040
/* emacs mode: c               */
9041
/* emacs tab-width: 4          */
9042
/* emacs indent-tabs-mode: nil */
9043
/* emacs c-basic-offset: 4     */
9044
/* emacs End:                  */
9045
/* vim: set expandtab ts=4 : */
9046
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc