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

saitoha / libsixel / 18990332429

31 Oct 2025 08:11PM UTC coverage: 41.742% (+0.06%) from 41.681%
18990332429

push

github

saitoha
cli: prefix matching for option arguments

6196 of 22392 branches covered (27.67%)

129 of 172 new or added lines in 1 file covered. (75.0%)

9023 of 21616 relevant lines covered (41.74%)

1004277.63 hits per line

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

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

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

59
/* STDC_HEADERS */
60
#include <stdio.h>
61
#include <stdlib.h>
62
#include <stdarg.h>
63

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

105
#include <sixel.h>
106
#include "loader.h"
107
#include "assessment.h"
108
#include "tty.h"
109
#include "encoder.h"
110
#include "output.h"
111
#include "dither.h"
112
#include "frame.h"
113
#include "rgblookup.h"
114

115
#if defined(_WIN32)
116

117
# include <windows.h>
118
# if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
119
#  include <io.h>
120
# endif
121
# if defined(_MSC_VER)
122
#   include <time.h>
123
# endif
124

125
/* for msvc */
126
# ifndef STDIN_FILENO
127
#  define STDIN_FILENO 0
128
# endif
129
# ifndef STDOUT_FILENO
130
#  define STDOUT_FILENO 1
131
# endif
132
# ifndef STDERR_FILENO
133
#  define STDERR_FILENO 2
134
# endif
135
# ifndef S_IRUSR
136
#  define S_IRUSR _S_IREAD
137
# endif
138
# ifndef S_IWUSR
139
#  define S_IWUSR _S_IWRITE
140
# endif
141

142
# if defined(CLOCKS_PER_SEC)
143
#  undef CLOCKS_PER_SEC
144
# endif
145
# define CLOCKS_PER_SEC 1000
146

147
# if !defined(HAVE_NANOSLEEP)
148
# define HAVE_NANOSLEEP_WIN 1
149
static int
150
nanosleep_win(
151
    struct timespec const *req,
152
    struct timespec *rem)
153
{
154
    LONGLONG nanoseconds;
155
    LARGE_INTEGER dueTime;
156
    HANDLE timer;
157

158
    if (req == NULL || req->tv_sec < 0 || req->tv_nsec < 0 ||
159
        req->tv_nsec >= 1000000000L) {
160
        errno = EINVAL;
161
        return (-1);
162
    }
163

164
    /* Convert to 100-nanosecond intervals (Windows FILETIME units) */
165
    nanoseconds = req->tv_sec * 1000000000LL + req->tv_nsec;
166
    dueTime.QuadPart = -(nanoseconds / 100); /* Negative for relative time */
167

168
    timer = CreateWaitableTimer(NULL, TRUE, NULL);
169
    if (timer == NULL) {
170
        errno = EFAULT;  /* Approximate error */
171
        return (-1);
172
    }
173

174
    if (! SetWaitableTimer(timer, &dueTime, 0, NULL, NULL, FALSE)) {
175
        (void) CloseHandle(timer);
176
        errno = EFAULT;
177
        return (-1);
178
    }
179

180
    (void) WaitForSingleObject(timer, INFINITE);
181
    (void) CloseHandle(timer);
182

183
    /* No interruption handling, so rem is unchanged */
184
    if (rem != NULL) {
185
        rem->tv_sec = 0;
186
        rem->tv_nsec = 0;
187
    }
188

189
    return (0);
190
}
191
# endif  /* HAVE_NANOSLEEP */
192

193
# if !defined(HAVE_CLOCK)
194
# define HAVE_CLOCK_WIN 1
195
static sixel_clock_t
196
clock_win(void)
197
{
198
    FILETIME ct, et, kt, ut;
199
    ULARGE_INTEGER u, k;
200

201
    if (! GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) {
202
        return (sixel_clock_t)(-1);
203
    }
204
    u.LowPart = ut.dwLowDateTime; u.HighPart = ut.dwHighDateTime;
205
    k.LowPart = kt.dwLowDateTime; k.HighPart = kt.dwHighDateTime;
206
    /* 100ns -> ms */
207
    return (sixel_clock_t)((u.QuadPart + k.QuadPart) / 10000ULL);
208
}
209
# endif  /* HAVE_CLOCK */
210

211
#endif /* _WIN32 */
212

213

214
/*
215
 * Prefix matcher roadmap:
216
 *
217
 *   +---------+-------------------+
218
 *   | input   | decision           |
219
 *   +---------+-------------------+
220
 *   | "ave"   | average            |
221
 *   | "a"     | ambiguous (auto?)  |
222
 *   +---------+-------------------+
223
 *
224
 * The helper walks the choice table once, collecting prefixes and
225
 * checking whether a unique destination emerges.  Ambiguous prefixes
226
 * bubble up so the caller can craft a friendly diagnostic.
227
 */
228
typedef struct sixel_option_choice {
229
    char const *name;
230
    int value;
231
} sixel_option_choice_t;
232

233
typedef enum sixel_option_choice_result {
234
    SIXEL_OPTION_CHOICE_MATCH = 0,
235
    SIXEL_OPTION_CHOICE_AMBIGUOUS = 1,
236
    SIXEL_OPTION_CHOICE_NONE = 2
237
} sixel_option_choice_result_t;
238

239
static sixel_option_choice_result_t
240
sixel_match_option_choice(
261✔
241
    char const *value,
242
    sixel_option_choice_t const *choices,
243
    size_t choice_count,
244
    int *matched_value,
245
    char *diagnostic,
246
    size_t diagnostic_size)
247
{
248
    size_t index;
249
    size_t value_length;
250
    int candidate_index;
251
    size_t match_count;
252
    int base_value;
253
    int base_value_set;
254
    int ambiguous_values;
255
    size_t diag_length;
256
    size_t copy_length;
257

258
    if (diagnostic != NULL && diagnostic_size > 0u) {
261!
259
        diagnostic[0] = '\0';
261✔
260
    }
87✔
261
    if (value == NULL) {
261!
NEW
262
        return SIXEL_OPTION_CHOICE_NONE;
×
263
    }
264

265
    value_length = strlen(value);
261✔
266
    if (value_length == 0u) {
261!
NEW
267
        return SIXEL_OPTION_CHOICE_NONE;
×
268
    }
269

270
    index = 0u;
261✔
271
    candidate_index = (-1);
261✔
272
    match_count = 0u;
261✔
273
    base_value = 0;
261✔
274
    base_value_set = 0;
261✔
275
    ambiguous_values = 0;
261✔
276
    diag_length = 0u;
261✔
277

278
    while (index < choice_count) {
1,326✔
279
        if (strncmp(choices[index].name, value, value_length) == 0) {
1,260✔
280
            if (choices[index].name[value_length] == '\0') {
243✔
281
                *matched_value = choices[index].value;
195✔
282
                return SIXEL_OPTION_CHOICE_MATCH;
195✔
283
            }
284
            if (!base_value_set) {
48✔
285
                base_value = choices[index].value;
39✔
286
                base_value_set = 1;
39✔
287
            } else if (choices[index].value != base_value) {
22✔
288
                ambiguous_values = 1;
3✔
289
            }
1✔
290
            if (candidate_index == (-1)) {
48✔
291
                candidate_index = (int)index;
39✔
292
            }
13✔
293
            ++match_count;
48✔
294
            if (diagnostic != NULL && diagnostic_size > 0u) {
48!
295
                if (diag_length > 0u && diag_length + 2u < diagnostic_size) {
48!
296
                    diagnostic[diag_length] = ',';
9✔
297
                    diagnostic[diag_length + 1u] = ' ';
9✔
298
                    diag_length += 2u;
9✔
299
                    diagnostic[diag_length] = '\0';
9✔
300
                }
3✔
301
                copy_length = strlen(choices[index].name);
48✔
302
                if (copy_length > diagnostic_size - diag_length - 1u) {
48!
NEW
303
                    copy_length = diagnostic_size - diag_length - 1u;
×
304
                }
305
                memcpy(diagnostic + diag_length,
48✔
306
                       choices[index].name,
32✔
307
                       copy_length);
308
                diag_length += copy_length;
48✔
309
                diagnostic[diag_length] = '\0';
48✔
310
            }
16✔
311
        }
16✔
312
        ++index;
1,065✔
313
    }
314

315
    if (match_count == 0u) {
66✔
316
        return SIXEL_OPTION_CHOICE_NONE;
27✔
317
    }
318
    if (!ambiguous_values) {
39✔
319
        *matched_value = choices[candidate_index].value;
36✔
320
        return SIXEL_OPTION_CHOICE_MATCH;
36✔
321
    }
322

323
    return SIXEL_OPTION_CHOICE_AMBIGUOUS;
3✔
324
}
87✔
325

326
static void
327
sixel_report_ambiguous_prefix(
3✔
328
    char const *option,
329
    char const *value,
330
    char const *candidates,
331
    char *buffer,
332
    size_t buffer_size)
333
{
334
    int written;
335

336
    if (buffer == NULL || buffer_size == 0u) {
3!
NEW
337
        return;
×
338
    }
339
    if (candidates != NULL && candidates[0] != '\0') {
3!
340
        written = snprintf(buffer,
3✔
341
                           buffer_size,
342
                           "ambiguous prefix \"%s\" for %s (matches: %s).",
343
                           value,
344
                           option,
345
                           candidates);
346
    } else {
1✔
NEW
347
        written = snprintf(buffer,
×
348
                           buffer_size,
349
                           "ambiguous prefix \"%s\" for %s.",
350
                           value,
351
                           option);
352
    }
353
    (void) written;
1✔
354
    sixel_helper_set_additional_message(buffer);
3✔
355
}
1✔
356

357
static sixel_option_choice_t const g_option_choices_builtin_palette[] = {
358
    { "xterm16", SIXEL_BUILTIN_XTERM16 },
359
    { "xterm256", SIXEL_BUILTIN_XTERM256 },
360
    { "vt340mono", SIXEL_BUILTIN_VT340_MONO },
361
    { "vt340color", SIXEL_BUILTIN_VT340_COLOR },
362
    { "gray1", SIXEL_BUILTIN_G1 },
363
    { "gray2", SIXEL_BUILTIN_G2 },
364
    { "gray4", SIXEL_BUILTIN_G4 },
365
    { "gray8", SIXEL_BUILTIN_G8 }
366
};
367

368
static sixel_option_choice_t const g_option_choices_diffusion[] = {
369
    { "auto", SIXEL_DIFFUSE_AUTO },
370
    { "none", SIXEL_DIFFUSE_NONE },
371
    { "fs", SIXEL_DIFFUSE_FS },
372
    { "atkinson", SIXEL_DIFFUSE_ATKINSON },
373
    { "jajuni", SIXEL_DIFFUSE_JAJUNI },
374
    { "stucki", SIXEL_DIFFUSE_STUCKI },
375
    { "burkes", SIXEL_DIFFUSE_BURKES },
376
    { "sierra1", SIXEL_DIFFUSE_SIERRA1 },
377
    { "sierra2", SIXEL_DIFFUSE_SIERRA2 },
378
    { "sierra3", SIXEL_DIFFUSE_SIERRA3 },
379
    { "a_dither", SIXEL_DIFFUSE_A_DITHER },
380
    { "x_dither", SIXEL_DIFFUSE_X_DITHER },
381
    { "lso1", SIXEL_DIFFUSE_LSO1 },
382
    { "lso2", SIXEL_DIFFUSE_LSO2 },
383
    { "lso3", SIXEL_DIFFUSE_LSO3 }
384
};
385

386
static sixel_option_choice_t const g_option_choices_diffusion_scan[] = {
387
    { "auto", SIXEL_SCAN_AUTO },
388
    { "serpentine", SIXEL_SCAN_SERPENTINE },
389
    { "raster", SIXEL_SCAN_RASTER }
390
};
391

392
static sixel_option_choice_t const g_option_choices_diffusion_carry[] = {
393
    { "auto", SIXEL_CARRY_AUTO },
394
    { "direct", SIXEL_CARRY_DISABLE },
395
    { "carry", SIXEL_CARRY_ENABLE }
396
};
397

398
static sixel_option_choice_t const g_option_choices_find_largest[] = {
399
    { "auto", SIXEL_LARGE_AUTO },
400
    { "norm", SIXEL_LARGE_NORM },
401
    { "lum", SIXEL_LARGE_LUM }
402
};
403

404
static sixel_option_choice_t const g_option_choices_select_color[] = {
405
    { "auto", SIXEL_REP_AUTO },
406
    { "center", SIXEL_REP_CENTER_BOX },
407
    { "average", SIXEL_REP_AVERAGE_COLORS },
408
    { "histogram", SIXEL_REP_AVERAGE_PIXELS },
409
    { "histgram", SIXEL_REP_AVERAGE_PIXELS }
410
};
411

412
static sixel_option_choice_t const g_option_choices_resampling[] = {
413
    { "nearest", SIXEL_RES_NEAREST },
414
    { "gaussian", SIXEL_RES_GAUSSIAN },
415
    { "hanning", SIXEL_RES_HANNING },
416
    { "hamming", SIXEL_RES_HAMMING },
417
    { "bilinear", SIXEL_RES_BILINEAR },
418
    { "welsh", SIXEL_RES_WELSH },
419
    { "bicubic", SIXEL_RES_BICUBIC },
420
    { "lanczos2", SIXEL_RES_LANCZOS2 },
421
    { "lanczos3", SIXEL_RES_LANCZOS3 },
422
    { "lanczos4", SIXEL_RES_LANCZOS4 }
423
};
424

425
static sixel_option_choice_t const g_option_choices_quality[] = {
426
    { "auto", SIXEL_QUALITY_AUTO },
427
    { "high", SIXEL_QUALITY_HIGH },
428
    { "low", SIXEL_QUALITY_LOW },
429
    { "full", SIXEL_QUALITY_FULL }
430
};
431

432
static sixel_option_choice_t const g_option_choices_loopmode[] = {
433
    { "auto", SIXEL_LOOP_AUTO },
434
    { "force", SIXEL_LOOP_FORCE },
435
    { "disable", SIXEL_LOOP_DISABLE }
436
};
437

438
static sixel_option_choice_t const g_option_choices_palette_type[] = {
439
    { "auto", SIXEL_PALETTETYPE_AUTO },
440
    { "hls", SIXEL_PALETTETYPE_HLS },
441
    { "rgb", SIXEL_PALETTETYPE_RGB }
442
};
443

444
static sixel_option_choice_t const g_option_choices_encode_policy[] = {
445
    { "auto", SIXEL_ENCODEPOLICY_AUTO },
446
    { "fast", SIXEL_ENCODEPOLICY_FAST },
447
    { "size", SIXEL_ENCODEPOLICY_SIZE }
448
};
449

450
static sixel_option_choice_t const g_option_choices_lut_policy[] = {
451
    { "auto", SIXEL_LUT_POLICY_AUTO },
452
    { "5bit", SIXEL_LUT_POLICY_5BIT },
453
    { "6bit", SIXEL_LUT_POLICY_6BIT },
454
    { "robinhood", SIXEL_LUT_POLICY_ROBINHOOD },
455
    { "hopscotch", SIXEL_LUT_POLICY_HOPSCOTCH }
456
};
457

458
static sixel_option_choice_t const g_option_choices_working_colorspace[] = {
459
    { "gamma", SIXEL_COLORSPACE_GAMMA },
460
    { "linear", SIXEL_COLORSPACE_LINEAR },
461
    { "oklab", SIXEL_COLORSPACE_OKLAB }
462
};
463

464
static sixel_option_choice_t const g_option_choices_output_colorspace[] = {
465
    { "gamma", SIXEL_COLORSPACE_GAMMA },
466
    { "linear", SIXEL_COLORSPACE_LINEAR },
467
    { "smpte-c", SIXEL_COLORSPACE_SMPTEC },
468
    { "smptec", SIXEL_COLORSPACE_SMPTEC }
469
};
470

471

472
static char *
473
arg_strdup(
60✔
474
    char const          /* in */ *s,          /* source buffer */
475
    sixel_allocator_t   /* in */ *allocator)  /* allocator object for
476
                                                 destination buffer */
477
{
478
    char *p;
479
    size_t len;
480

481
    len = strlen(s);
60✔
482

483
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
60✔
484
    if (p) {
60!
485
#if HAVE_STRCPY_S
486
        (void) strcpy_s(p, (rsize_t)len, s);
487
#else
488
        (void) strcpy(p, s);
60✔
489
#endif  /* HAVE_STRCPY_S */
490
    }
20✔
491
    return p;
60✔
492
}
493

494

495
/* An clone function of XColorSpec() of xlib */
496
static SIXELSTATUS
497
sixel_parse_x_colorspec(
45✔
498
    unsigned char       /* out */ **bgcolor,     /* destination buffer */
499
    char const          /* in */  *s,            /* source buffer */
500
    sixel_allocator_t   /* in */  *allocator)    /* allocator object for
501
                                                    destination buffer */
502
{
503
    SIXELSTATUS status = SIXEL_FALSE;
45✔
504
    char *p;
505
    unsigned char components[3];
506
    int component_index = 0;
45✔
507
    unsigned long v;
508
    char *endptr;
509
    char *buf = NULL;
45✔
510
    struct color const *pcolor;
511

512
    /* from rgb_lookup.h generated by gpref */
513
    pcolor = lookup_rgb(s, strlen(s));
45✔
514
    if (pcolor) {
45✔
515
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
516
        if (*bgcolor == NULL) {
3!
517
            sixel_helper_set_additional_message(
×
518
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
519
            status = SIXEL_BAD_ALLOCATION;
×
520
            goto end;
×
521
        }
522
        (*bgcolor)[0] = pcolor->r;
3✔
523
        (*bgcolor)[1] = pcolor->g;
3✔
524
        (*bgcolor)[2] = pcolor->b;
3✔
525
    } else if (s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && s[3] == ':') {
43!
526
        p = buf = arg_strdup(s + 4, allocator);
6✔
527
        if (buf == NULL) {
6!
528
            sixel_helper_set_additional_message(
×
529
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
530
            status = SIXEL_BAD_ALLOCATION;
×
531
            goto end;
×
532
        }
533
        while (*p) {
15!
534
            v = 0;
15✔
535
            for (endptr = p; endptr - p <= 12; ++endptr) {
36!
536
                if (*endptr >= '0' && *endptr <= '9') {
36✔
537
                    v = (v << 4) | (unsigned long)(*endptr - '0');
15✔
538
                } else if (*endptr >= 'a' && *endptr <= 'f') {
26!
539
                    v = (v << 4) | (unsigned long)(*endptr - 'a' + 10);
3✔
540
                } else if (*endptr >= 'A' && *endptr <= 'F') {
19!
541
                    v = (v << 4) | (unsigned long)(*endptr - 'A' + 10);
3✔
542
                } else {
1✔
543
                    break;
5✔
544
                }
545
            }
7✔
546
            if (endptr - p == 0) {
15!
547
                break;
×
548
            }
549
            if (endptr - p > 4) {
15!
550
                break;
×
551
            }
552
            v = v << ((4 - (endptr - p)) * 4) >> 8;
15✔
553
            components[component_index++] = (unsigned char)v;
15✔
554
            p = endptr;
15✔
555
            if (component_index == 3) {
15✔
556
                break;
3✔
557
            }
558
            if (*p == '\0') {
12✔
559
                break;
3✔
560
            }
561
            if (*p != '/') {
9!
562
                break;
×
563
            }
564
            ++p;
9✔
565
        }
566
        if (component_index != 3 || *p != '\0' || *p == '/') {
6!
567
            status = SIXEL_BAD_ARGUMENT;
3✔
568
            goto end;
3✔
569
        }
570
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
3✔
571
        if (*bgcolor == NULL) {
3!
572
            sixel_helper_set_additional_message(
×
573
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
574
            status = SIXEL_BAD_ALLOCATION;
×
575
            goto end;
×
576
        }
577
        (*bgcolor)[0] = components[0];
3✔
578
        (*bgcolor)[1] = components[1];
3✔
579
        (*bgcolor)[2] = components[2];
3✔
580
    } else if (*s == '#') {
37✔
581
        buf = arg_strdup(s + 1, allocator);
27✔
582
        if (buf == NULL) {
27!
583
            sixel_helper_set_additional_message(
×
584
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
585
            status = SIXEL_BAD_ALLOCATION;
×
586
            goto end;
×
587
        }
588
        for (p = endptr = buf; endptr - p <= 12; ++endptr) {
192✔
589
            if (*endptr >= '0' && *endptr <= '9') {
189✔
590
                *endptr -= '0';
99✔
591
            } else if (*endptr >= 'a' && *endptr <= 'f') {
123!
592
                *endptr -= 'a' - 10;
57✔
593
            } else if (*endptr >= 'A' && *endptr <= 'F') {
52✔
594
                *endptr -= 'A' - 10;
9✔
595
            } else if (*endptr == '\0') {
27✔
596
                break;
21✔
597
            } else {
598
                status = SIXEL_BAD_ARGUMENT;
3✔
599
                goto end;
3✔
600
            }
601
        }
55✔
602
        if (endptr - p > 12) {
24✔
603
            status = SIXEL_BAD_ARGUMENT;
3✔
604
            goto end;
3✔
605
        }
606
        *bgcolor = (unsigned char *)sixel_allocator_malloc(allocator, 3);
21✔
607
        if (*bgcolor == NULL) {
21!
608
            sixel_helper_set_additional_message(
×
609
                "sixel_parse_x_colorspec: sixel_allocator_malloc() failed.");
610
            status = SIXEL_BAD_ALLOCATION;
×
611
            goto end;
×
612
        }
613
        switch (endptr - p) {
21✔
614
        case 3:
6✔
615
            (*bgcolor)[0] = (unsigned char)(p[0] << 4);
9✔
616
            (*bgcolor)[1] = (unsigned char)(p[1] << 4);
9✔
617
            (*bgcolor)[2] = (unsigned char)(p[2] << 4);
9✔
618
            break;
9✔
619
        case 6:
2✔
620
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
621
            (*bgcolor)[1] = (unsigned char)(p[2] << 4 | p[3]);
3✔
622
            (*bgcolor)[2] = (unsigned char)(p[4] << 4 | p[4]);
3✔
623
            break;
3✔
624
        case 9:
2✔
625
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
626
            (*bgcolor)[1] = (unsigned char)(p[3] << 4 | p[4]);
3✔
627
            (*bgcolor)[2] = (unsigned char)(p[6] << 4 | p[7]);
3✔
628
            break;
3✔
629
        case 12:
2✔
630
            (*bgcolor)[0] = (unsigned char)(p[0] << 4 | p[1]);
3✔
631
            (*bgcolor)[1] = (unsigned char)(p[4] << 4 | p[5]);
3✔
632
            (*bgcolor)[2] = (unsigned char)(p[8] << 4 | p[9]);
3✔
633
            break;
3✔
634
        default:
2✔
635
            status = SIXEL_BAD_ARGUMENT;
3✔
636
            goto end;
3✔
637
        }
638
    } else {
6✔
639
        status = SIXEL_BAD_ARGUMENT;
9✔
640
        goto end;
9✔
641
    }
642

643
    status = SIXEL_OK;
24✔
644

645
end:
30✔
646
    sixel_allocator_free(allocator, buf);
45✔
647

648
    return status;
45✔
649
}
650

651

652
/* generic writer function for passing to sixel_output_new() */
653
static int
654
sixel_write_callback(char *data, int size, void *priv)
4,361✔
655
{
656
    int result;
657

658
#if HAVE__WRITE
659
    result = _write(*(int *)priv, data, (size_t)size);
660
#elif defined(__MINGW64__)
661
    result = write(*(int *)priv, data, (unsigned int)size);
662
#else
663
    result = write(*(int *)priv, data, (size_t)size);
4,361✔
664
#endif
665

666
    return result;
4,361✔
667
}
668

669

670
/* the writer function with hex-encoding for passing to sixel_output_new() */
671
static int
672
sixel_hex_write_callback(
73✔
673
    char    /* in */ *data,
674
    int     /* in */ size,
675
    void    /* in */ *priv)
676
{
677
    char hex[SIXEL_OUTPUT_PACKET_SIZE * 2];
678
    int i;
679
    int j;
680
    int result;
681

682
    for (i = j = 0; i < size; ++i, ++j) {
635,609✔
683
        hex[j] = (data[i] >> 4) & 0xf;
635,536✔
684
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
635,536!
685
        hex[++j] = data[i] & 0xf;
635,536✔
686
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
635,536✔
687
    }
168,020✔
688

689
#if HAVE__WRITE
690
    result = _write(*(int *)priv, hex, (unsigned int)(size * 2));
691
#elif defined(__MINGW64__)
692
    result = write(*(int *)priv, hex, (unsigned int)(size * 2));
693
#else
694
    result = write(*(int *)priv, hex, (size_t)(size * 2));
73✔
695
#endif
696

697
    return result;
73✔
698
}
699

700
typedef struct sixel_encoder_output_probe {
701
    sixel_encoder_t *encoder;
702
    sixel_write_function base_write;
703
    void *base_priv;
704
} sixel_encoder_output_probe_t;
705

706
static int
707
sixel_write_with_probe(char *data, int size, void *priv)
×
708
{
709
    sixel_encoder_output_probe_t *probe;
710
    int written;
711
    double started_at;
712
    double finished_at;
713
    double duration;
714

715
    probe = (sixel_encoder_output_probe_t *)priv;
×
716
    if (probe == NULL || probe->base_write == NULL) {
×
717
        return 0;
×
718
    }
719
    started_at = 0.0;
×
720
    finished_at = 0.0;
×
721
    duration = 0.0;
×
722
    if (probe->encoder != NULL &&
×
723
            probe->encoder->assessment_observer != NULL) {
×
724
        started_at = sixel_assessment_timer_now();
×
725
    }
726
    written = probe->base_write(data, size, probe->base_priv);
×
727
    if (probe->encoder != NULL &&
×
728
            probe->encoder->assessment_observer != NULL) {
×
729
        finished_at = sixel_assessment_timer_now();
×
730
        duration = finished_at - started_at;
×
731
        if (duration < 0.0) {
×
732
            duration = 0.0;
×
733
        }
734
    }
735
    if (written > 0 && probe->encoder != NULL &&
×
736
            probe->encoder->assessment_observer != NULL) {
×
737
        sixel_assessment_record_output_write(
×
738
            probe->encoder->assessment_observer,
×
739
            (size_t)written,
740
            duration);
741
    }
742
    return written;
×
743
}
744

745
/*
746
 * Reuse the fn_write probe for raw escape writes so that every
747
 * assessment bucket receives the same accounting.
748
 *
749
 *     encoder        probe wrapper       write(2)
750
 *     +------+    +----------------+    +---------+
751
 *     | data | -> | sixel_write_*  | -> | target  |
752
 *     +------+    +----------------+    +---------+
753
 */
754
static int
755
sixel_encoder_probe_fd_write(sixel_encoder_t *encoder,
×
756
                             char *data,
757
                             int size,
758
                             int fd)
759
{
760
    sixel_encoder_output_probe_t probe;
761
    int written;
762

763
    probe.encoder = encoder;
×
764
    probe.base_write = sixel_write_callback;
×
765
    probe.base_priv = &fd;
×
766
    written = sixel_write_with_probe(data, size, &probe);
×
767

768
    return written;
×
769
}
770

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

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

783
#if HAVE__OPEN
784
    fd = _open("/dev/tty", O_RDONLY);
785
#else
786
    fd = open("/dev/tty", O_RDONLY);
×
787
#endif  /* #if HAVE__OPEN */
788
    if (fd >= 0) {
×
789
        result = ioctl(fd, TIOCGWINSZ, &ws);
×
790
        close(fd);
×
791
    } else {
792
        sixel_helper_set_additional_message(
×
793
            "failed to open /dev/tty");
794
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
795
    }
796
    if (result != 0) {
×
797
        sixel_helper_set_additional_message(
×
798
            "failed to query terminal geometry with ioctl().");
799
        return (SIXEL_LIBC_ERROR | (errno & 0xff));
×
800
    }
801

802
    if (ws.ws_col <= 0 || ws.ws_row <= 0 ||
×
803
        ws.ws_xpixel <= ws.ws_col || ws.ws_ypixel <= ws.ws_row) {
×
804
        sixel_helper_set_additional_message(
×
805
            "terminal does not report pixel cell size for drcs option.");
806
        return SIXEL_BAD_ARGUMENT;
×
807
    }
808

809
    encoder->cell_width = ws.ws_xpixel / ws.ws_col;
×
810
    encoder->cell_height = ws.ws_ypixel / ws.ws_row;
×
811
    if (encoder->cell_width <= 0 || encoder->cell_height <= 0) {
×
812
        sixel_helper_set_additional_message(
×
813
            "terminal cell size reported zero via ioctl().");
814
        return SIXEL_BAD_ARGUMENT;
×
815
    }
816

817
    return SIXEL_OK;
×
818
#else
819
    (void) encoder;
820
    sixel_helper_set_additional_message(
821
        "drcs option is not supported on this platform.");
822
    return SIXEL_NOT_IMPLEMENTED;
823
#endif
824
}
825

826

827
/* returns monochrome dithering context object */
828
static SIXELSTATUS
829
sixel_prepare_monochrome_palette(
12✔
830
    sixel_dither_t  /* out */ **dither,
831
     int            /* in */  finvert)
832
{
833
    SIXELSTATUS status = SIXEL_FALSE;
12✔
834

835
    if (finvert) {
12✔
836
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_LIGHT);
3✔
837
    } else {
1✔
838
        *dither = sixel_dither_get(SIXEL_BUILTIN_MONO_DARK);
9✔
839
    }
840
    if (*dither == NULL) {
12!
841
        sixel_helper_set_additional_message(
×
842
            "sixel_prepare_monochrome_palette: sixel_dither_get() failed.");
843
        status = SIXEL_RUNTIME_ERROR;
×
844
        goto end;
×
845
    }
846

847
    status = SIXEL_OK;
12✔
848

849
end:
8✔
850
    return status;
12✔
851
}
852

853

854
/* returns dithering context object with specified builtin palette */
855
typedef struct palette_conversion {
856
    unsigned char *original;
857
    unsigned char *copy;
858
    size_t size;
859
    int convert_inplace;
860
    int converted;
861
    int frame_colorspace;
862
} palette_conversion_t;
863

864
static SIXELSTATUS
865
sixel_encoder_convert_palette(sixel_encoder_t *encoder,
516✔
866
                              sixel_output_t *output,
867
                              sixel_dither_t *dither,
868
                              int frame_colorspace,
869
                              int pixelformat,
870
                              palette_conversion_t *ctx)
871
{
872
    SIXELSTATUS status = SIXEL_OK;
516✔
873
    unsigned char *palette;
874
    int palette_colors;
875

876
    ctx->original = NULL;
516✔
877
    ctx->copy = NULL;
516✔
878
    ctx->size = 0;
516✔
879
    ctx->convert_inplace = 0;
516✔
880
    ctx->converted = 0;
516✔
881
    ctx->frame_colorspace = frame_colorspace;
516✔
882

883
    palette = sixel_dither_get_palette(dither);
516✔
884
    palette_colors = sixel_dither_get_num_of_palette_colors(dither);
516✔
885
    ctx->original = palette;
516✔
886

887
    if (palette == NULL || palette_colors <= 0 ||
516!
888
            frame_colorspace == output->colorspace) {
516!
889
        return SIXEL_OK;
516✔
890
    }
891

892
    ctx->size = (size_t)palette_colors * 3;
×
893

894
    output->pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
895
    output->source_colorspace = frame_colorspace;
×
896

897
    if (palette != (unsigned char *)(dither + 1)) {
×
898
        ctx->copy = (unsigned char *)sixel_allocator_malloc(
×
899
            encoder->allocator, ctx->size);
900
        if (ctx->copy == NULL) {
×
901
            sixel_helper_set_additional_message(
×
902
                "sixel_encoder_convert_palette: "
903
                "sixel_allocator_malloc() failed.");
904
            status = SIXEL_BAD_ALLOCATION;
×
905
            goto end;
×
906
        }
907
        memcpy(ctx->copy, palette, ctx->size);
×
908
        dither->palette = ctx->copy;
×
909
    } else {
910
        ctx->convert_inplace = 1;
×
911
    }
912

913
    status = sixel_output_convert_colorspace(output,
×
914
                                             dither->palette,
915
                                             ctx->size);
916
    if (SIXEL_FAILED(status)) {
×
917
        goto end;
×
918
    }
919
    ctx->converted = 1;
×
920

921
end:
922
    output->pixelformat = pixelformat;
×
923
    output->source_colorspace = frame_colorspace;
×
924

925
    return status;
×
926
}
178✔
927

928
static void
929
sixel_encoder_restore_palette(sixel_encoder_t *encoder,
516✔
930
                              sixel_dither_t *dither,
931
                              palette_conversion_t *ctx)
932
{
933
    if (ctx->copy) {
516!
934
        dither->palette = ctx->original;
×
935
        sixel_allocator_free(encoder->allocator, ctx->copy);
×
936
        ctx->copy = NULL;
×
937
    } else if (ctx->convert_inplace && ctx->converted &&
516!
938
               ctx->original && ctx->size > 0) {
×
939
        (void)sixel_helper_convert_colorspace(ctx->original,
×
940
                                              ctx->size,
941
                                              SIXEL_PIXELFORMAT_RGB888,
942
                                              SIXEL_COLORSPACE_GAMMA,
943
                                              ctx->frame_colorspace);
944
    }
945
}
516✔
946

947
static SIXELSTATUS
948
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
×
949
                                sixel_dither_t *dither,
950
                                unsigned char const *pixels,
951
                                size_t size,
952
                                int width,
953
                                int height,
954
                                int pixelformat,
955
                                int colorspace)
956
{
957
    SIXELSTATUS status;
958
    unsigned char *palette;
959
    int ncolors;
960
    size_t palette_bytes;
961
    unsigned char *new_pixels;
962
    unsigned char *new_palette;
963
    size_t capture_bytes;
964
    unsigned char const *capture_source;
965
    sixel_index_t *paletted_pixels;
966
    size_t quantized_pixels;
967
    sixel_allocator_t *dither_allocator;
968
    int saved_pixelformat;
969
    int restore_pixelformat;
970

971
    /*
972
     * Preserve the quantized frame for assessment observers.
973
     *
974
     *     +-----------------+     +---------------------+
975
     *     | quantized bytes | --> | encoder->capture_*  |
976
     *     +-----------------+     +---------------------+
977
     */
978

979
    status = SIXEL_OK;
×
980
    palette = NULL;
×
981
    ncolors = 0;
×
982
    palette_bytes = 0;
×
983
    new_pixels = NULL;
×
984
    new_palette = NULL;
×
985
    capture_bytes = size;
×
986
    capture_source = pixels;
×
987
    paletted_pixels = NULL;
×
988
    quantized_pixels = 0;
×
989
    dither_allocator = NULL;
×
990

991
    if (encoder == NULL || pixels == NULL ||
×
992
            (dither == NULL && size == 0)) {
×
993
        sixel_helper_set_additional_message(
×
994
            "sixel_encoder_capture_quantized: invalid capture request.");
995
        return SIXEL_BAD_ARGUMENT;
×
996
    }
997

998
    if (!encoder->capture_quantized) {
×
999
        return SIXEL_OK;
×
1000
    }
1001

1002
    saved_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
1003
    restore_pixelformat = 0;
×
1004
    if (dither != NULL) {
×
1005
        dither_allocator = dither->allocator;
×
1006
        saved_pixelformat = dither->pixelformat;
×
1007
        restore_pixelformat = 1;
×
1008
        if (width <= 0 || height <= 0) {
×
1009
            sixel_helper_set_additional_message(
×
1010
                "sixel_encoder_capture_quantized: invalid dimensions.");
1011
            status = SIXEL_BAD_ARGUMENT;
×
1012
            goto cleanup;
×
1013
        }
1014
        quantized_pixels = (size_t)width * (size_t)height;
×
1015
        if (height != 0 &&
×
1016
                quantized_pixels / (size_t)height != (size_t)width) {
×
1017
            sixel_helper_set_additional_message(
×
1018
                "sixel_encoder_capture_quantized: image too large.");
1019
            status = SIXEL_RUNTIME_ERROR;
×
1020
            goto cleanup;
×
1021
        }
1022
        paletted_pixels = sixel_dither_apply_palette(
×
1023
            dither, (unsigned char *)pixels, width, height);
1024
        if (paletted_pixels == NULL) {
×
1025
            sixel_helper_set_additional_message(
×
1026
                "sixel_encoder_capture_quantized: palette conversion failed.");
1027
            status = SIXEL_RUNTIME_ERROR;
×
1028
            goto cleanup;
×
1029
        }
1030
        capture_source = (unsigned char const *)paletted_pixels;
×
1031
        capture_bytes = quantized_pixels;
×
1032
    }
1033

1034
    if (capture_bytes > 0) {
×
1035
        if (encoder->capture_pixels == NULL ||
×
1036
                encoder->capture_pixels_size < capture_bytes) {
×
1037
            new_pixels = (unsigned char *)sixel_allocator_malloc(
×
1038
                encoder->allocator, capture_bytes);
1039
            if (new_pixels == NULL) {
×
1040
                sixel_helper_set_additional_message(
×
1041
                    "sixel_encoder_capture_quantized: "
1042
                    "sixel_allocator_malloc() failed.");
1043
                status = SIXEL_BAD_ALLOCATION;
×
1044
                goto cleanup;
×
1045
            }
1046
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
×
1047
            encoder->capture_pixels = new_pixels;
×
1048
            encoder->capture_pixels_size = capture_bytes;
×
1049
        }
1050
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
×
1051
    }
1052
    encoder->capture_pixel_bytes = capture_bytes;
×
1053

1054
    palette = NULL;
×
1055
    ncolors = 0;
×
1056
    palette_bytes = 0;
×
1057
    if (dither != NULL) {
×
1058
        palette = sixel_dither_get_palette(dither);
×
1059
        ncolors = sixel_dither_get_num_of_palette_colors(dither);
×
1060
    }
1061
    if (palette != NULL && ncolors > 0) {
×
1062
        palette_bytes = (size_t)ncolors * 3;
×
1063
        if (encoder->capture_palette == NULL ||
×
1064
                encoder->capture_palette_size < palette_bytes) {
×
1065
            new_palette = (unsigned char *)sixel_allocator_malloc(
×
1066
                encoder->allocator, palette_bytes);
1067
            if (new_palette == NULL) {
×
1068
                sixel_helper_set_additional_message(
×
1069
                    "sixel_encoder_capture_quantized: "
1070
                    "sixel_allocator_malloc() failed.");
1071
                status = SIXEL_BAD_ALLOCATION;
×
1072
                goto cleanup;
×
1073
            }
1074
            sixel_allocator_free(encoder->allocator,
×
1075
                                 encoder->capture_palette);
×
1076
            encoder->capture_palette = new_palette;
×
1077
            encoder->capture_palette_size = palette_bytes;
×
1078
        }
1079
        memcpy(encoder->capture_palette, palette, palette_bytes);
×
1080
    }
1081

1082
    encoder->capture_width = width;
×
1083
    encoder->capture_height = height;
×
1084
    if (dither != NULL) {
×
1085
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
×
1086
    } else {
1087
        encoder->capture_pixelformat = pixelformat;
×
1088
    }
1089
    encoder->capture_colorspace = colorspace;
×
1090
    encoder->capture_palette_size = palette_bytes;
×
1091
    encoder->capture_ncolors = ncolors;
×
1092
    encoder->capture_valid = 1;
×
1093

1094
cleanup:
1095
    if (restore_pixelformat && dither != NULL) {
×
1096
        /*
1097
         * Undo the normalization performed by sixel_dither_apply_palette().
1098
         *
1099
         *     RGBA8888 --capture--> RGB888 (temporary)
1100
         *          \______________________________/
1101
         *                          |
1102
         *                 restore original state for
1103
         *                 the real encoder execution.
1104
         */
1105
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
×
1106
    }
1107
    if (paletted_pixels != NULL && dither_allocator != NULL) {
×
1108
        sixel_allocator_free(dither_allocator, paletted_pixels);
×
1109
    }
1110

1111
    return status;
×
1112
}
1113

1114
static SIXELSTATUS
1115
sixel_prepare_builtin_palette(
27✔
1116
    sixel_dither_t /* out */ **dither,
1117
    int            /* in */  builtin_palette)
1118
{
1119
    SIXELSTATUS status = SIXEL_FALSE;
27✔
1120

1121
    *dither = sixel_dither_get(builtin_palette);
27✔
1122
    if (*dither == NULL) {
27!
1123
        sixel_helper_set_additional_message(
×
1124
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
1125
        status = SIXEL_RUNTIME_ERROR;
×
1126
        goto end;
×
1127
    }
1128

1129
    status = SIXEL_OK;
27✔
1130

1131
end:
18✔
1132
    return status;
27✔
1133
}
1134

1135
static int
1136
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
450✔
1137
{
1138
    int width_hint;
1139
    int height_hint;
1140
    long base;
1141
    long size;
1142

1143
    width_hint = 0;
450✔
1144
    height_hint = 0;
450✔
1145
    base = 0;
450✔
1146
    size = 0;
450✔
1147

1148
    if (encoder == NULL) {
450!
1149
        return 0;
×
1150
    }
1151

1152
    width_hint = encoder->pixelwidth;
450✔
1153
    height_hint = encoder->pixelheight;
450✔
1154

1155
    /* Request extra resolution for downscaling to preserve detail. */
1156
    if (width_hint > 0 && height_hint > 0) {
450✔
1157
        /* Follow the CLI rule: double the larger axis before doubling
1158
         * again for the final request size. */
1159
        if (width_hint >= height_hint) {
9!
1160
            base = (long)width_hint;
9✔
1161
        } else {
3✔
1162
            base = (long)height_hint;
×
1163
        }
1164
        base *= 2L;
9✔
1165
    } else if (width_hint > 0) {
444✔
1166
        base = (long)width_hint;
48✔
1167
    } else if (height_hint > 0) {
409✔
1168
        base = (long)height_hint;
36✔
1169
    } else {
12✔
1170
        return 0;
357✔
1171
    }
1172

1173
    size = base * 2L;
93✔
1174
    if (size > (long)INT_MAX) {
93!
1175
        size = (long)INT_MAX;
×
1176
    }
1177
    if (size < 1L) {
93!
1178
        size = 1L;
×
1179
    }
1180

1181
    return (int)size;
93✔
1182
}
150✔
1183

1184

1185
typedef struct sixel_callback_context_for_mapfile {
1186
    int reqcolors;
1187
    sixel_dither_t *dither;
1188
    sixel_allocator_t *allocator;
1189
    int working_colorspace;
1190
    int lut_policy;
1191
} sixel_callback_context_for_mapfile_t;
1192

1193

1194
/* callback function for sixel_helper_load_image_file() */
1195
static SIXELSTATUS
1196
load_image_callback_for_palette(
21✔
1197
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1198
    void            /* in */    *data)  /* private data */
1199
{
1200
    SIXELSTATUS status = SIXEL_FALSE;
21✔
1201
    sixel_callback_context_for_mapfile_t *callback_context;
1202

1203
    /* get callback context object from the private data */
1204
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
1205

1206
    status = sixel_frame_ensure_colorspace(frame,
28✔
1207
                                           callback_context->working_colorspace);
7✔
1208
    if (SIXEL_FAILED(status)) {
21!
1209
        goto end;
×
1210
    }
1211

1212
    switch (sixel_frame_get_pixelformat(frame)) {
21!
1213
    case SIXEL_PIXELFORMAT_PAL1:
2✔
1214
    case SIXEL_PIXELFORMAT_PAL2:
1215
    case SIXEL_PIXELFORMAT_PAL4:
1216
    case SIXEL_PIXELFORMAT_PAL8:
1217
        if (sixel_frame_get_palette(frame) == NULL) {
3!
1218
            status = SIXEL_LOGIC_ERROR;
×
1219
            goto end;
×
1220
        }
1221
        /* create new dither object */
1222
        status = sixel_dither_new(
3✔
1223
            &callback_context->dither,
1✔
1224
            sixel_frame_get_ncolors(frame),
1✔
1225
            callback_context->allocator);
1✔
1226
        if (SIXEL_FAILED(status)) {
3!
1227
            goto end;
×
1228
        }
1229

1230
        sixel_dither_set_lut_policy(callback_context->dither,
4✔
1231
                                    callback_context->lut_policy);
1✔
1232

1233
        /* use palette which is extracted from the image */
1234
        sixel_dither_set_palette(callback_context->dither,
4✔
1235
                                 sixel_frame_get_palette(frame));
1✔
1236
        /* success */
1237
        status = SIXEL_OK;
3✔
1238
        break;
3✔
1239
    case SIXEL_PIXELFORMAT_G1:
1240
        /* use 1bpp grayscale builtin palette */
1241
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1242
        /* success */
1243
        status = SIXEL_OK;
×
1244
        break;
×
1245
    case SIXEL_PIXELFORMAT_G2:
1246
        /* use 2bpp grayscale builtin palette */
1247
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
1248
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
1249
        /* success */
1250
        status = SIXEL_OK;
×
1251
        break;
×
1252
    case SIXEL_PIXELFORMAT_G4:
1253
        /* use 4bpp grayscale builtin palette */
1254
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
1255
        /* success */
1256
        status = SIXEL_OK;
×
1257
        break;
×
1258
    case SIXEL_PIXELFORMAT_G8:
1259
        /* use 8bpp grayscale builtin palette */
1260
        callback_context->dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
1261
        /* success */
1262
        status = SIXEL_OK;
×
1263
        break;
×
1264
    default:
12✔
1265
        /* create new dither object */
1266
        status = sixel_dither_new(
18✔
1267
            &callback_context->dither,
6✔
1268
            callback_context->reqcolors,
6✔
1269
            callback_context->allocator);
6✔
1270
        if (SIXEL_FAILED(status)) {
18!
1271
            goto end;
×
1272
        }
1273

1274
        sixel_dither_set_lut_policy(callback_context->dither,
24✔
1275
                                    callback_context->lut_policy);
6✔
1276

1277
        /* create adaptive palette from given frame object */
1278
        status = sixel_dither_initialize(callback_context->dither,
24✔
1279
                                         sixel_frame_get_pixels(frame),
6✔
1280
                                         sixel_frame_get_width(frame),
6✔
1281
                                         sixel_frame_get_height(frame),
6✔
1282
                                         sixel_frame_get_pixelformat(frame),
6✔
1283
                                         SIXEL_LARGE_NORM,
1284
                                         SIXEL_REP_CENTER_BOX,
1285
                                         SIXEL_QUALITY_HIGH);
1286
        if (SIXEL_FAILED(status)) {
18!
1287
            sixel_dither_unref(callback_context->dither);
×
1288
            goto end;
×
1289
        }
1290

1291
        /* success */
1292
        status = SIXEL_OK;
18✔
1293

1294
        break;
18✔
1295
    }
7✔
1296

1297
end:
14✔
1298
    return status;
21✔
1299
}
1300

1301

1302
static SIXELSTATUS
1303
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1304

1305

1306
static int
1307
sixel_path_has_extension(char const *path, char const *extension)
78✔
1308
{
1309
    size_t path_len;
1310
    size_t ext_len;
1311
    size_t index;
1312

1313
    path_len = 0u;
78✔
1314
    ext_len = 0u;
78✔
1315
    index = 0u;
78✔
1316

1317
    if (path == NULL || extension == NULL) {
78!
1318
        return 0;
×
1319
    }
1320

1321
    path_len = strlen(path);
78✔
1322
    ext_len = strlen(extension);
78✔
1323
    if (ext_len == 0u || path_len < ext_len) {
78!
1324
        return 0;
×
1325
    }
1326

1327
    for (index = 0u; index < ext_len; ++index) {
159!
1328
        unsigned char path_ch;
1329
        unsigned char ext_ch;
1330

1331
        path_ch = (unsigned char)path[path_len - ext_len + index];
159✔
1332
        ext_ch = (unsigned char)extension[index];
159✔
1333
        if (tolower(path_ch) != tolower(ext_ch)) {
159✔
1334
            return 0;
78✔
1335
        }
1336
    }
27✔
1337

1338
    return 1;
×
1339
}
30✔
1340

1341
typedef enum sixel_palette_format {
1342
    SIXEL_PALETTE_FORMAT_NONE = 0,
1343
    SIXEL_PALETTE_FORMAT_ACT,
1344
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1345
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1346
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1347
    SIXEL_PALETTE_FORMAT_GPL
1348
} sixel_palette_format_t;
1349

1350
/*
1351
 * Palette specification parser
1352
 *
1353
 *   TYPE:PATH  -> explicit format prefix
1354
 *   PATH       -> rely on extension or heuristics
1355
 *
1356
 * The ASCII diagram below shows how the prefix is peeled:
1357
 *
1358
 *   [type] : [path]
1359
 *    ^-- left part selects decoder/encoder when present.
1360
 */
1361
static char const *
1362
sixel_palette_strip_prefix(char const *spec,
26✔
1363
                           sixel_palette_format_t *format_hint)
1364
{
1365
    char const *colon;
1366
    size_t type_len;
1367
    size_t index;
1368
    char lowered[16];
1369

1370
    colon = NULL;
26✔
1371
    type_len = 0u;
26✔
1372
    index = 0u;
26✔
1373

1374
    if (format_hint != NULL) {
26!
1375
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
26✔
1376
    }
10✔
1377
    if (spec == NULL) {
26!
1378
        return NULL;
×
1379
    }
1380

1381
    colon = strchr(spec, ':');
26✔
1382
    if (colon == NULL) {
26!
1383
        return spec;
26✔
1384
    }
1385

1386
    type_len = (size_t)(colon - spec);
×
1387
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1388
        return spec;
×
1389
    }
1390

1391
    for (index = 0u; index < type_len; ++index) {
×
1392
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1393
    }
1394
    lowered[type_len] = '\0';
×
1395

1396
    if (strcmp(lowered, "act") == 0) {
×
1397
        if (format_hint != NULL) {
×
1398
            *format_hint = SIXEL_PALETTE_FORMAT_ACT;
×
1399
        }
1400
        return colon + 1;
×
1401
    }
1402
    if (strcmp(lowered, "pal") == 0) {
×
1403
        if (format_hint != NULL) {
×
1404
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1405
        }
1406
        return colon + 1;
×
1407
    }
1408
    if (strcmp(lowered, "pal-jasc") == 0) {
×
1409
        if (format_hint != NULL) {
×
1410
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1411
        }
1412
        return colon + 1;
×
1413
    }
1414
    if (strcmp(lowered, "pal-riff") == 0) {
×
1415
        if (format_hint != NULL) {
×
1416
            *format_hint = SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1417
        }
1418
        return colon + 1;
×
1419
    }
1420
    if (strcmp(lowered, "gpl") == 0) {
×
1421
        if (format_hint != NULL) {
×
1422
            *format_hint = SIXEL_PALETTE_FORMAT_GPL;
×
1423
        }
1424
        return colon + 1;
×
1425
    }
1426

1427
    return spec;
×
1428
}
10✔
1429

1430
static sixel_palette_format_t
1431
sixel_palette_format_from_extension(char const *path)
26✔
1432
{
1433
    if (path == NULL) {
26!
1434
        return SIXEL_PALETTE_FORMAT_NONE;
×
1435
    }
1436

1437
    if (sixel_path_has_extension(path, ".act")) {
26!
1438
        return SIXEL_PALETTE_FORMAT_ACT;
×
1439
    }
1440
    if (sixel_path_has_extension(path, ".pal")) {
26!
1441
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1442
    }
1443
    if (sixel_path_has_extension(path, ".gpl")) {
26!
1444
        return SIXEL_PALETTE_FORMAT_GPL;
×
1445
    }
1446

1447
    return SIXEL_PALETTE_FORMAT_NONE;
26✔
1448
}
10✔
1449

1450
static int
1451
sixel_path_has_any_extension(char const *path)
26✔
1452
{
1453
    char const *slash_forward;
1454
#if defined(_WIN32)
1455
    char const *slash_backward;
1456
#endif
1457
    char const *start;
1458
    char const *dot;
1459

1460
    slash_forward = NULL;
26✔
1461
#if defined(_WIN32)
1462
    slash_backward = NULL;
1463
#endif
1464
    start = path;
26✔
1465
    dot = NULL;
26✔
1466

1467
    if (path == NULL) {
26!
1468
        return 0;
×
1469
    }
1470

1471
    slash_forward = strrchr(path, '/');
26✔
1472
#if defined(_WIN32)
1473
    slash_backward = strrchr(path, '\\');
1474
    if (slash_backward != NULL &&
1475
            (slash_forward == NULL || slash_backward > slash_forward)) {
1476
        slash_forward = slash_backward;
1477
    }
1478
#endif
1479
    if (slash_forward == NULL) {
26!
1480
        start = path;
×
1481
    } else {
1482
        start = slash_forward + 1;
26✔
1483
    }
1484

1485
    dot = strrchr(start, '.');
26✔
1486
    if (dot == NULL) {
26✔
1487
        return 0;
5✔
1488
    }
1489

1490
    if (dot[1] == '\0') {
21!
1491
        return 0;
×
1492
    }
1493

1494
    return 1;
21✔
1495
}
10✔
1496

1497
static int
1498
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1499
{
1500
    if (data == NULL || size < 3u) {
×
1501
        return 0;
×
1502
    }
1503
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
1504
        return 1;
×
1505
    }
1506
    return 0;
×
1507
}
1508

1509

1510
/*
1511
 * Materialize palette bytes from a stream.
1512
 *
1513
 * The flow looks like:
1514
 *
1515
 *   stream --> [scratch buffer] --> [resizable heap buffer]
1516
 *                  ^ looped read        ^ returned payload
1517
 */
1518
static SIXELSTATUS
1519
sixel_palette_read_stream(FILE *stream,
×
1520
                          sixel_allocator_t *allocator,
1521
                          unsigned char **pdata,
1522
                          size_t *psize)
1523
{
1524
    SIXELSTATUS status;
1525
    unsigned char *buffer;
1526
    unsigned char *grown;
1527
    size_t capacity;
1528
    size_t used;
1529
    size_t read_bytes;
1530
    size_t needed;
1531
    size_t new_capacity;
1532
    unsigned char scratch[4096];
1533

1534
    status = SIXEL_FALSE;
×
1535
    buffer = NULL;
×
1536
    grown = NULL;
×
1537
    capacity = 0u;
×
1538
    used = 0u;
×
1539
    read_bytes = 0u;
×
1540
    needed = 0u;
×
1541
    new_capacity = 0u;
×
1542

1543
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1544
        sixel_helper_set_additional_message(
×
1545
            "sixel_palette_read_stream: invalid argument.");
1546
        return SIXEL_BAD_ARGUMENT;
×
1547
    }
1548

1549
    *pdata = NULL;
×
1550
    *psize = 0u;
×
1551

1552
    while (1) {
1553
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1554
        if (read_bytes == 0u) {
×
1555
            if (ferror(stream)) {
×
1556
                sixel_helper_set_additional_message(
×
1557
                    "sixel_palette_read_stream: fread() failed.");
1558
                status = SIXEL_LIBC_ERROR;
×
1559
                goto cleanup;
×
1560
            }
1561
            break;
×
1562
        }
1563

1564
        if (used > SIZE_MAX - read_bytes) {
×
1565
            sixel_helper_set_additional_message(
×
1566
                "sixel_palette_read_stream: size overflow.");
1567
            status = SIXEL_BAD_ALLOCATION;
×
1568
            goto cleanup;
×
1569
        }
1570
        needed = used + read_bytes;
×
1571

1572
        if (needed > capacity) {
×
1573
            new_capacity = capacity;
×
1574
            if (new_capacity == 0u) {
×
1575
                new_capacity = 4096u;
×
1576
            }
1577
            while (needed > new_capacity) {
×
1578
                if (new_capacity > SIZE_MAX / 2u) {
×
1579
                    sixel_helper_set_additional_message(
×
1580
                        "sixel_palette_read_stream: size overflow.");
1581
                    status = SIXEL_BAD_ALLOCATION;
×
1582
                    goto cleanup;
×
1583
                }
1584
                new_capacity *= 2u;
×
1585
            }
1586

1587
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1588
                                                             new_capacity);
1589
            if (grown == NULL) {
×
1590
                sixel_helper_set_additional_message(
×
1591
                    "sixel_palette_read_stream: allocation failed.");
1592
                status = SIXEL_BAD_ALLOCATION;
×
1593
                goto cleanup;
×
1594
            }
1595

1596
            if (buffer != NULL) {
×
1597
                memcpy(grown, buffer, used);
×
1598
                sixel_allocator_free(allocator, buffer);
×
1599
            }
1600

1601
            buffer = grown;
×
1602
            grown = NULL;
×
1603
            capacity = new_capacity;
×
1604
        }
1605

1606
        memcpy(buffer + used, scratch, read_bytes);
×
1607
        used += read_bytes;
×
1608
    }
1609

1610
    *pdata = buffer;
×
1611
    *psize = used;
×
1612
    status = SIXEL_OK;
×
1613
    return status;
×
1614

1615
cleanup:
1616
    if (grown != NULL) {
×
1617
        sixel_allocator_free(allocator, grown);
×
1618
    }
1619
    if (buffer != NULL) {
×
1620
        sixel_allocator_free(allocator, buffer);
×
1621
    }
1622
    return status;
×
1623
}
1624

1625

1626
static SIXELSTATUS
1627
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
5✔
1628
{
1629
    if (pstream == NULL || pclose == NULL || path == NULL) {
5!
1630
        sixel_helper_set_additional_message(
×
1631
            "sixel_palette_open_read: invalid argument.");
1632
        return SIXEL_BAD_ARGUMENT;
×
1633
    }
1634

1635
    if (strcmp(path, "-") == 0) {
5!
1636
        *pstream = stdin;
×
1637
        *pclose = 0;
×
1638
        return SIXEL_OK;
×
1639
    }
1640

1641
    *pstream = fopen(path, "rb");
5✔
1642
    if (*pstream == NULL) {
5!
1643
        sixel_helper_set_additional_message(
5✔
1644
            "sixel_palette_open_read: failed to open file.");
1645
        return SIXEL_LIBC_ERROR;
5✔
1646
    }
1647

1648
    *pclose = 1;
×
1649
    return SIXEL_OK;
×
1650
}
3✔
1651

1652

1653
static void
1654
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1655
{
1656
    if (close_stream && stream != NULL) {
×
1657
        (void) fclose(stream);
×
1658
    }
1659
}
×
1660

1661

1662
static sixel_palette_format_t
1663
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1664
{
1665
    size_t offset;
1666
    size_t data_size;
1667

1668
    offset = 0u;
×
1669
    data_size = size;
×
1670

1671
    if (data == NULL || size == 0u) {
×
1672
        return SIXEL_PALETTE_FORMAT_NONE;
×
1673
    }
1674

1675
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
1676
        return SIXEL_PALETTE_FORMAT_ACT;
×
1677
    }
1678

1679
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
1680
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
1681
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1682
    }
1683

1684
    if (sixel_palette_has_utf8_bom(data, size)) {
×
1685
        offset = 3u;
×
1686
        data_size = size - 3u;
×
1687
    }
1688

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

1696
    return SIXEL_PALETTE_FORMAT_NONE;
×
1697
}
1698

1699

1700
static unsigned int
1701
sixel_palette_read_le16(unsigned char const *ptr)
×
1702
{
1703
    if (ptr == NULL) {
×
1704
        return 0u;
×
1705
    }
1706
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1707
}
1708

1709

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

1722

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

1748
    status = SIXEL_FALSE;
×
1749
    local = NULL;
×
1750
    palette_start = data;
×
1751
    trailer = NULL;
×
1752
    target = NULL;
×
1753
    copy_bytes = 0u;
×
1754
    exported_colors = 0;
×
1755
    start_index = 0;
×
1756

1757
    if (encoder == NULL || dither == NULL) {
×
1758
        sixel_helper_set_additional_message(
×
1759
            "sixel_palette_parse_act: invalid argument.");
1760
        return SIXEL_BAD_ARGUMENT;
×
1761
    }
1762
    if (data == NULL || size < 256u * 3u) {
×
1763
        sixel_helper_set_additional_message(
×
1764
            "sixel_palette_parse_act: truncated ACT palette.");
1765
        return SIXEL_BAD_INPUT;
×
1766
    }
1767

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

1783
    if (start_index < 0 || start_index >= 256) {
×
1784
        sixel_helper_set_additional_message(
×
1785
            "sixel_palette_parse_act: ACT start index out of range.");
1786
        return SIXEL_BAD_INPUT;
×
1787
    }
1788
    if (exported_colors <= 0 || exported_colors > 256) {
×
1789
        exported_colors = 256;
×
1790
    }
1791
    if (start_index + exported_colors > 256) {
×
1792
        sixel_helper_set_additional_message(
×
1793
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
1794
        return SIXEL_BAD_INPUT;
×
1795
    }
1796

1797
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
1798
    if (SIXEL_FAILED(status)) {
×
1799
        return status;
×
1800
    }
1801

1802
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1803

1804
    target = sixel_dither_get_palette(local);
×
1805
    copy_bytes = (size_t)exported_colors * 3u;
×
1806
    memcpy(target, palette_start + (size_t)start_index * 3u, copy_bytes);
×
1807

1808
    *dither = local;
×
1809
    return SIXEL_OK;
×
1810
}
1811

1812

1813
static SIXELSTATUS
1814
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1815
                             size_t size,
1816
                             sixel_encoder_t *encoder,
1817
                             sixel_dither_t **dither)
1818
{
1819
    SIXELSTATUS status;
1820
    char *text;
1821
    size_t index;
1822
    size_t offset;
1823
    char *cursor;
1824
    char *line;
1825
    char *line_end;
1826
    int stage;
1827
    int exported_colors;
1828
    int parsed_colors;
1829
    sixel_dither_t *local;
1830
    unsigned char *target;
1831
    long component;
1832
    char *parse_end;
1833
    int value_index;
1834
    int values[3];
1835
    char tail;
1836

1837
    status = SIXEL_FALSE;
×
1838
    text = NULL;
×
1839
    index = 0u;
×
1840
    offset = 0u;
×
1841
    cursor = NULL;
×
1842
    line = NULL;
×
1843
    line_end = NULL;
×
1844
    stage = 0;
×
1845
    exported_colors = 0;
×
1846
    parsed_colors = 0;
×
1847
    local = NULL;
×
1848
    target = NULL;
×
1849
    component = 0;
×
1850
    parse_end = NULL;
×
1851
    value_index = 0;
×
1852
    values[0] = 0;
×
1853
    values[1] = 0;
×
1854
    values[2] = 0;
×
1855

1856
    if (encoder == NULL || dither == NULL) {
×
1857
        sixel_helper_set_additional_message(
×
1858
            "sixel_palette_parse_pal_jasc: invalid argument.");
1859
        return SIXEL_BAD_ARGUMENT;
×
1860
    }
1861
    if (data == NULL || size == 0u) {
×
1862
        sixel_helper_set_additional_message(
×
1863
            "sixel_palette_parse_pal_jasc: empty palette.");
1864
        return SIXEL_BAD_INPUT;
×
1865
    }
1866

1867
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
1868
    if (text == NULL) {
×
1869
        sixel_helper_set_additional_message(
×
1870
            "sixel_palette_parse_pal_jasc: allocation failed.");
1871
        return SIXEL_BAD_ALLOCATION;
×
1872
    }
1873
    memcpy(text, data, size);
×
1874
    text[size] = '\0';
×
1875

1876
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1877
        offset = 3u;
×
1878
    }
1879
    cursor = text + offset;
×
1880

1881
    while (*cursor != '\0') {
×
1882
        line = cursor;
×
1883
        line_end = cursor;
×
1884
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1885
            ++line_end;
×
1886
        }
1887
        if (*line_end != '\0') {
×
1888
            *line_end = '\0';
×
1889
            cursor = line_end + 1;
×
1890
        } else {
1891
            cursor = line_end;
×
1892
        }
1893
        while (*cursor == '\n' || *cursor == '\r') {
×
1894
            ++cursor;
×
1895
        }
1896

1897
        while (*line == ' ' || *line == '\t') {
×
1898
            ++line;
×
1899
        }
1900
        index = strlen(line);
×
1901
        while (index > 0u) {
×
1902
            tail = line[index - 1];
×
1903
            if (tail != ' ' && tail != '\t') {
×
1904
                break;
×
1905
            }
1906
            line[index - 1] = '\0';
×
1907
            --index;
×
1908
        }
1909
        if (*line == '\0') {
×
1910
            continue;
×
1911
        }
1912
        if (*line == '#') {
×
1913
            continue;
×
1914
        }
1915

1916
        if (stage == 0) {
×
1917
            if (strcmp(line, "JASC-PAL") != 0) {
×
1918
                sixel_helper_set_additional_message(
×
1919
                    "sixel_palette_parse_pal_jasc: missing header.");
1920
                status = SIXEL_BAD_INPUT;
×
1921
                goto cleanup;
×
1922
            }
1923
            stage = 1;
×
1924
            continue;
×
1925
        }
1926
        if (stage == 1) {
×
1927
            stage = 2;
×
1928
            continue;
×
1929
        }
1930
        if (stage == 2) {
×
1931
            component = strtol(line, &parse_end, 10);
×
1932
            if (parse_end == line || component <= 0L || component > 256L) {
×
1933
                sixel_helper_set_additional_message(
×
1934
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1935
                status = SIXEL_BAD_INPUT;
×
1936
                goto cleanup;
×
1937
            }
1938
            exported_colors = (int)component;
×
1939
            status = sixel_dither_new(&local, exported_colors,
×
1940
                                      encoder->allocator);
1941
            if (SIXEL_FAILED(status)) {
×
1942
                goto cleanup;
×
1943
            }
1944
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1945
            target = sixel_dither_get_palette(local);
×
1946
            stage = 3;
×
1947
            continue;
×
1948
        }
1949

1950
        value_index = 0;
×
1951
        while (value_index < 3) {
×
1952
            component = strtol(line, &parse_end, 10);
×
1953
            if (parse_end == line || component < 0L || component > 255L) {
×
1954
                sixel_helper_set_additional_message(
×
1955
                    "sixel_palette_parse_pal_jasc: invalid component.");
1956
                status = SIXEL_BAD_INPUT;
×
1957
                goto cleanup;
×
1958
            }
1959
            values[value_index] = (int)component;
×
1960
            ++value_index;
×
1961
            line = parse_end;
×
1962
            while (*line == ' ' || *line == '\t') {
×
1963
                ++line;
×
1964
            }
1965
        }
1966

1967
        if (parsed_colors >= exported_colors) {
×
1968
            sixel_helper_set_additional_message(
×
1969
                "sixel_palette_parse_pal_jasc: excess entries.");
1970
            status = SIXEL_BAD_INPUT;
×
1971
            goto cleanup;
×
1972
        }
1973

1974
        target[parsed_colors * 3 + 0] =
×
1975
            (unsigned char)values[0];
×
1976
        target[parsed_colors * 3 + 1] =
×
1977
            (unsigned char)values[1];
×
1978
        target[parsed_colors * 3 + 2] =
×
1979
            (unsigned char)values[2];
×
1980
        ++parsed_colors;
×
1981
    }
1982

1983
    if (stage < 3) {
×
1984
        sixel_helper_set_additional_message(
×
1985
            "sixel_palette_parse_pal_jasc: incomplete header.");
1986
        status = SIXEL_BAD_INPUT;
×
1987
        goto cleanup;
×
1988
    }
1989
    if (parsed_colors != exported_colors) {
×
1990
        sixel_helper_set_additional_message(
×
1991
            "sixel_palette_parse_pal_jasc: color count mismatch.");
1992
        status = SIXEL_BAD_INPUT;
×
1993
        goto cleanup;
×
1994
    }
1995

1996
    *dither = local;
×
1997
    status = SIXEL_OK;
×
1998

1999
cleanup:
2000
    if (SIXEL_FAILED(status) && local != NULL) {
×
2001
        sixel_dither_unref(local);
×
2002
    }
2003
    if (text != NULL) {
×
2004
        sixel_allocator_free(encoder->allocator, text);
×
2005
    }
2006
    return status;
×
2007
}
2008

2009

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

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

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

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

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

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

2094
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
×
2095
    if (SIXEL_FAILED(status)) {
×
2096
        return status;
×
2097
    }
2098
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2099
    target = sixel_dither_get_palette(local);
×
2100
    palette_offset = 12u;
×
2101
    for (index = 0u; index < entry_count; ++index) {
×
2102
        target[index * 3u + 0u] =
×
2103
            chunk[palette_offset + index * 4u + 0u];
×
2104
        target[index * 3u + 1u] =
×
2105
            chunk[palette_offset + index * 4u + 1u];
×
2106
        target[index * 3u + 2u] =
×
2107
            chunk[palette_offset + index * 4u + 2u];
×
2108
    }
2109

2110
    *dither = local;
×
2111
    return SIXEL_OK;
×
2112
}
2113

2114

2115
static SIXELSTATUS
2116
sixel_palette_parse_gpl(unsigned char const *data,
×
2117
                        size_t size,
2118
                        sixel_encoder_t *encoder,
2119
                        sixel_dither_t **dither)
2120
{
2121
    SIXELSTATUS status;
2122
    char *text;
2123
    size_t offset;
2124
    char *cursor;
2125
    char *line;
2126
    char *line_end;
2127
    size_t index;
2128
    int header_seen;
2129
    int parsed_colors;
2130
    unsigned char palette_bytes[256 * 3];
2131
    long component;
2132
    char *parse_end;
2133
    int value_index;
2134
    int values[3];
2135
    sixel_dither_t *local;
2136
    unsigned char *target;
2137
    char tail;
2138

2139
    status = SIXEL_FALSE;
×
2140
    text = NULL;
×
2141
    offset = 0u;
×
2142
    cursor = NULL;
×
2143
    line = NULL;
×
2144
    line_end = NULL;
×
2145
    index = 0u;
×
2146
    header_seen = 0;
×
2147
    parsed_colors = 0;
×
2148
    component = 0;
×
2149
    parse_end = NULL;
×
2150
    value_index = 0;
×
2151
    values[0] = 0;
×
2152
    values[1] = 0;
×
2153
    values[2] = 0;
×
2154
    local = NULL;
×
2155
    target = NULL;
×
2156

2157
    if (encoder == NULL || dither == NULL) {
×
2158
        sixel_helper_set_additional_message(
×
2159
            "sixel_palette_parse_gpl: invalid argument.");
2160
        return SIXEL_BAD_ARGUMENT;
×
2161
    }
2162
    if (data == NULL || size == 0u) {
×
2163
        sixel_helper_set_additional_message(
×
2164
            "sixel_palette_parse_gpl: empty palette.");
2165
        return SIXEL_BAD_INPUT;
×
2166
    }
2167

2168
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
2169
    if (text == NULL) {
×
2170
        sixel_helper_set_additional_message(
×
2171
            "sixel_palette_parse_gpl: allocation failed.");
2172
        return SIXEL_BAD_ALLOCATION;
×
2173
    }
2174
    memcpy(text, data, size);
×
2175
    text[size] = '\0';
×
2176

2177
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2178
        offset = 3u;
×
2179
    }
2180
    cursor = text + offset;
×
2181

2182
    while (*cursor != '\0') {
×
2183
        line = cursor;
×
2184
        line_end = cursor;
×
2185
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2186
            ++line_end;
×
2187
        }
2188
        if (*line_end != '\0') {
×
2189
            *line_end = '\0';
×
2190
            cursor = line_end + 1;
×
2191
        } else {
2192
            cursor = line_end;
×
2193
        }
2194
        while (*cursor == '\n' || *cursor == '\r') {
×
2195
            ++cursor;
×
2196
        }
2197

2198
        while (*line == ' ' || *line == '\t') {
×
2199
            ++line;
×
2200
        }
2201
        index = strlen(line);
×
2202
        while (index > 0u) {
×
2203
            tail = line[index - 1];
×
2204
            if (tail != ' ' && tail != '\t') {
×
2205
                break;
×
2206
            }
2207
            line[index - 1] = '\0';
×
2208
            --index;
×
2209
        }
2210
        if (*line == '\0') {
×
2211
            continue;
×
2212
        }
2213
        if (*line == '#') {
×
2214
            continue;
×
2215
        }
2216
        if (strncmp(line, "Name:", 5) == 0) {
×
2217
            continue;
×
2218
        }
2219
        if (strncmp(line, "Columns:", 8) == 0) {
×
2220
            continue;
×
2221
        }
2222

2223
        if (!header_seen) {
×
2224
            if (strcmp(line, "GIMP Palette") != 0) {
×
2225
                sixel_helper_set_additional_message(
×
2226
                    "sixel_palette_parse_gpl: missing header.");
2227
                status = SIXEL_BAD_INPUT;
×
2228
                goto cleanup;
×
2229
            }
2230
            header_seen = 1;
×
2231
            continue;
×
2232
        }
2233

2234
        if (parsed_colors >= 256) {
×
2235
            sixel_helper_set_additional_message(
×
2236
                "sixel_palette_parse_gpl: too many colors.");
2237
            status = SIXEL_BAD_INPUT;
×
2238
            goto cleanup;
×
2239
        }
2240

2241
        value_index = 0;
×
2242
        while (value_index < 3) {
×
2243
            component = strtol(line, &parse_end, 10);
×
2244
            if (parse_end == line || component < 0L || component > 255L) {
×
2245
                sixel_helper_set_additional_message(
×
2246
                    "sixel_palette_parse_gpl: invalid component.");
2247
                status = SIXEL_BAD_INPUT;
×
2248
                goto cleanup;
×
2249
            }
2250
            values[value_index] = (int)component;
×
2251
            ++value_index;
×
2252
            line = parse_end;
×
2253
            while (*line == ' ' || *line == '\t') {
×
2254
                ++line;
×
2255
            }
2256
        }
2257

2258
        palette_bytes[parsed_colors * 3 + 0] =
×
2259
            (unsigned char)values[0];
×
2260
        palette_bytes[parsed_colors * 3 + 1] =
×
2261
            (unsigned char)values[1];
×
2262
        palette_bytes[parsed_colors * 3 + 2] =
×
2263
            (unsigned char)values[2];
×
2264
        ++parsed_colors;
×
2265
    }
2266

2267
    if (!header_seen) {
×
2268
        sixel_helper_set_additional_message(
×
2269
            "sixel_palette_parse_gpl: header missing.");
2270
        status = SIXEL_BAD_INPUT;
×
2271
        goto cleanup;
×
2272
    }
2273
    if (parsed_colors <= 0) {
×
2274
        sixel_helper_set_additional_message(
×
2275
            "sixel_palette_parse_gpl: no colors parsed.");
2276
        status = SIXEL_BAD_INPUT;
×
2277
        goto cleanup;
×
2278
    }
2279

2280
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2281
    if (SIXEL_FAILED(status)) {
×
2282
        goto cleanup;
×
2283
    }
2284
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2285
    target = sixel_dither_get_palette(local);
×
2286
    memcpy(target, palette_bytes, (size_t)parsed_colors * 3u);
×
2287

2288
    *dither = local;
×
2289
    status = SIXEL_OK;
×
2290

2291
cleanup:
2292
    if (SIXEL_FAILED(status) && local != NULL) {
×
2293
        sixel_dither_unref(local);
×
2294
    }
2295
    if (text != NULL) {
×
2296
        sixel_allocator_free(encoder->allocator, text);
×
2297
    }
2298
    return status;
×
2299
}
2300

2301

2302
/*
2303
 * Palette exporters
2304
 *
2305
 *   +----------+-------------------------+
2306
 *   | format   | emission strategy       |
2307
 *   +----------+-------------------------+
2308
 *   | ACT      | fixed 256 entries + EOF |
2309
 *   | PAL JASC | textual lines           |
2310
 *   | PAL RIFF | RIFF container          |
2311
 *   | GPL      | textual lines           |
2312
 *   +----------+-------------------------+
2313
 */
2314
static SIXELSTATUS
2315
sixel_palette_write_act(FILE *stream,
×
2316
                        unsigned char const *palette,
2317
                        int exported_colors)
2318
{
2319
    SIXELSTATUS status;
2320
    unsigned char act_table[256 * 3];
2321
    unsigned char trailer[4];
2322
    size_t exported_bytes;
2323

2324
    status = SIXEL_FALSE;
×
2325
    exported_bytes = 0u;
×
2326

2327
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2328
        return SIXEL_BAD_ARGUMENT;
×
2329
    }
2330
    if (exported_colors > 256) {
×
2331
        exported_colors = 256;
×
2332
    }
2333

2334
    memset(act_table, 0, sizeof(act_table));
×
2335
    exported_bytes = (size_t)exported_colors * 3u;
×
2336
    memcpy(act_table, palette, exported_bytes);
×
2337

2338
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2339
                                 & 0xffu);
2340
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2341
    trailer[2] = 0u;
×
2342
    trailer[3] = 0u;
×
2343

2344
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2345
            != sizeof(act_table)) {
2346
        status = SIXEL_LIBC_ERROR;
×
2347
        return status;
×
2348
    }
2349
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2350
            != sizeof(trailer)) {
2351
        status = SIXEL_LIBC_ERROR;
×
2352
        return status;
×
2353
    }
2354

2355
    return SIXEL_OK;
×
2356
}
2357

2358

2359
static SIXELSTATUS
2360
sixel_palette_write_pal_jasc(FILE *stream,
×
2361
                             unsigned char const *palette,
2362
                             int exported_colors)
2363
{
2364
    int index;
2365

2366
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2367
        return SIXEL_BAD_ARGUMENT;
×
2368
    }
2369
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
2370
        return SIXEL_LIBC_ERROR;
×
2371
    }
2372
    for (index = 0; index < exported_colors; ++index) {
×
2373
        if (fprintf(stream, "%d %d %d\n",
×
2374
                    (int)palette[index * 3 + 0],
×
2375
                    (int)palette[index * 3 + 1],
×
2376
                    (int)palette[index * 3 + 2]) < 0) {
×
2377
            return SIXEL_LIBC_ERROR;
×
2378
        }
2379
    }
2380
    return SIXEL_OK;
×
2381
}
2382

2383

2384
static SIXELSTATUS
2385
sixel_palette_write_pal_riff(FILE *stream,
×
2386
                             unsigned char const *palette,
2387
                             int exported_colors)
2388
{
2389
    unsigned char header[12];
2390
    unsigned char chunk[8];
2391
    unsigned char log_palette[4 + 256 * 4];
2392
    unsigned int data_size;
2393
    unsigned int riff_size;
2394
    int index;
2395

2396
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2397
        return SIXEL_BAD_ARGUMENT;
×
2398
    }
2399
    if (exported_colors > 256) {
×
2400
        exported_colors = 256;
×
2401
    }
2402

2403
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2404
    riff_size = 4u + 8u + data_size;
×
2405

2406
    memcpy(header, "RIFF", 4);
×
2407
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2408
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2409
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2410
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2411
    memcpy(header + 8, "PAL ", 4);
×
2412

2413
    memcpy(chunk, "data", 4);
×
2414
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2415
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2416
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2417
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2418

2419
    memset(log_palette, 0, sizeof(log_palette));
×
2420
    log_palette[0] = 0x00;
×
2421
    log_palette[1] = 0x03;
×
2422
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
2423
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
2424
    for (index = 0; index < exported_colors; ++index) {
×
2425
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
2426
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
2427
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
2428
        log_palette[4 + index * 4 + 3] = 0u;
×
2429
    }
2430

2431
    if (fwrite(header, 1, sizeof(header), stream)
×
2432
            != sizeof(header)) {
2433
        return SIXEL_LIBC_ERROR;
×
2434
    }
2435
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2436
        return SIXEL_LIBC_ERROR;
×
2437
    }
2438
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2439
            != (size_t)data_size) {
×
2440
        return SIXEL_LIBC_ERROR;
×
2441
    }
2442
    return SIXEL_OK;
×
2443
}
2444

2445

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

2453
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2454
        return SIXEL_BAD_ARGUMENT;
×
2455
    }
2456
    if (fprintf(stream, "GIMP Palette\n") < 0) {
×
2457
        return SIXEL_LIBC_ERROR;
×
2458
    }
2459
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
×
2460
        return SIXEL_LIBC_ERROR;
×
2461
    }
2462
    if (fprintf(stream, "Columns: 16\n") < 0) {
×
2463
        return SIXEL_LIBC_ERROR;
×
2464
    }
2465
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
×
2466
        return SIXEL_LIBC_ERROR;
×
2467
    }
2468
    for (index = 0; index < exported_colors; ++index) {
×
2469
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
×
2470
                    (int)palette[index * 3 + 0],
×
2471
                    (int)palette[index * 3 + 1],
×
2472
                    (int)palette[index * 3 + 2],
×
2473
                    index) < 0) {
2474
            return SIXEL_LIBC_ERROR;
×
2475
        }
2476
    }
2477
    return SIXEL_OK;
×
2478
}
2479

2480

2481
/* create palette from specified map file */
2482
static SIXELSTATUS
2483
sixel_prepare_specified_palette(
26✔
2484
    sixel_dither_t  /* out */   **dither,
2485
    sixel_encoder_t /* in */    *encoder)
2486
{
2487
    SIXELSTATUS status;
2488
    sixel_callback_context_for_mapfile_t callback_context;
2489
    sixel_loader_t *loader;
2490
    int fstatic;
2491
    int fuse_palette;
2492
    int reqcolors;
2493
    int loop_override;
2494
    char const *path;
2495
    sixel_palette_format_t format_hint;
2496
    sixel_palette_format_t format_ext;
2497
    sixel_palette_format_t format_final;
2498
    sixel_palette_format_t format_detected;
2499
    FILE *stream;
2500
    int close_stream;
2501
    unsigned char *buffer;
2502
    size_t buffer_size;
2503
    int palette_request;
2504
    int need_detection;
2505
    int treat_as_image;
2506
    int path_has_extension;
2507

2508
    status = SIXEL_FALSE;
26✔
2509
    loader = NULL;
26✔
2510
    fstatic = 1;
26✔
2511
    fuse_palette = 1;
26✔
2512
    reqcolors = SIXEL_PALETTE_MAX;
26✔
2513
    loop_override = SIXEL_LOOP_DISABLE;
26✔
2514
    path = NULL;
26✔
2515
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
26✔
2516
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
26✔
2517
    format_final = SIXEL_PALETTE_FORMAT_NONE;
26✔
2518
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
26✔
2519
    stream = NULL;
26✔
2520
    close_stream = 0;
26✔
2521
    buffer = NULL;
26✔
2522
    buffer_size = 0u;
26✔
2523
    palette_request = 0;
26✔
2524
    need_detection = 0;
26✔
2525
    treat_as_image = 0;
26✔
2526
    path_has_extension = 0;
26✔
2527

2528
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
26!
2529
        sixel_helper_set_additional_message(
×
2530
            "sixel_prepare_specified_palette: invalid mapfile path.");
2531
        return SIXEL_BAD_ARGUMENT;
×
2532
    }
2533

2534
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
26✔
2535
    if (path == NULL || *path == '\0') {
26!
2536
        sixel_helper_set_additional_message(
×
2537
            "sixel_prepare_specified_palette: empty mapfile path.");
2538
        return SIXEL_BAD_ARGUMENT;
×
2539
    }
2540

2541
    format_ext = sixel_palette_format_from_extension(path);
26✔
2542
    path_has_extension = sixel_path_has_any_extension(path);
26✔
2543

2544
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
26!
2545
        palette_request = 1;
×
2546
        format_final = format_hint;
×
2547
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
26!
2548
        palette_request = 1;
×
2549
        format_final = format_ext;
×
2550
    } else if (!path_has_extension) {
26✔
2551
        palette_request = 1;
5✔
2552
        need_detection = 1;
5✔
2553
    } else {
3✔
2554
        treat_as_image = 1;
21✔
2555
    }
2556

2557
    if (palette_request) {
26✔
2558
        status = sixel_palette_open_read(path, &stream, &close_stream);
5✔
2559
        if (SIXEL_FAILED(status)) {
5!
2560
            goto palette_cleanup;
5✔
2561
        }
2562
        status = sixel_palette_read_stream(stream,
×
2563
                                           encoder->allocator,
2564
                                           &buffer,
2565
                                           &buffer_size);
2566
        if (close_stream) {
×
2567
            sixel_palette_close_stream(stream, close_stream);
×
2568
            stream = NULL;
×
2569
            close_stream = 0;
×
2570
        }
2571
        if (SIXEL_FAILED(status)) {
×
2572
            goto palette_cleanup;
×
2573
        }
2574
        if (buffer_size == 0u) {
×
2575
            sixel_helper_set_additional_message(
×
2576
                "sixel_prepare_specified_palette: mapfile is empty.");
2577
            status = SIXEL_BAD_INPUT;
×
2578
            goto palette_cleanup;
×
2579
        }
2580

2581
        if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
2582
            format_detected = sixel_palette_guess_format(buffer,
×
2583
                                                         buffer_size);
2584
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2585
                sixel_helper_set_additional_message(
×
2586
                    "sixel_prepare_specified_palette: "
2587
                    "unable to detect palette format.");
2588
                status = SIXEL_BAD_INPUT;
×
2589
                goto palette_cleanup;
×
2590
            }
2591
            format_final = format_detected;
×
2592
        } else if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
2593
            format_detected = sixel_palette_guess_format(buffer,
×
2594
                                                         buffer_size);
2595
            if (format_detected == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
2596
                    format_detected == SIXEL_PALETTE_FORMAT_PAL_RIFF) {
2597
                format_final = format_detected;
×
2598
            } else {
2599
                sixel_helper_set_additional_message(
×
2600
                    "sixel_prepare_specified_palette: "
2601
                    "ambiguous .pal content.");
2602
                status = SIXEL_BAD_INPUT;
×
2603
                goto palette_cleanup;
×
2604
            }
2605
        } else if (need_detection) {
×
2606
            format_detected = sixel_palette_guess_format(buffer,
×
2607
                                                         buffer_size);
2608
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2609
                sixel_helper_set_additional_message(
×
2610
                    "sixel_prepare_specified_palette: "
2611
                    "unable to detect palette format.");
2612
                status = SIXEL_BAD_INPUT;
×
2613
                goto palette_cleanup;
×
2614
            }
2615
            format_final = format_detected;
×
2616
        }
2617

2618
        switch (format_final) {
×
2619
        case SIXEL_PALETTE_FORMAT_ACT:
2620
            status = sixel_palette_parse_act(buffer,
×
2621
                                             buffer_size,
2622
                                             encoder,
2623
                                             dither);
2624
            break;
×
2625
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
2626
            status = sixel_palette_parse_pal_jasc(buffer,
×
2627
                                                  buffer_size,
2628
                                                  encoder,
2629
                                                  dither);
2630
            break;
×
2631
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
2632
            status = sixel_palette_parse_pal_riff(buffer,
×
2633
                                                  buffer_size,
2634
                                                  encoder,
2635
                                                  dither);
2636
            break;
×
2637
        case SIXEL_PALETTE_FORMAT_GPL:
2638
            status = sixel_palette_parse_gpl(buffer,
×
2639
                                             buffer_size,
2640
                                             encoder,
2641
                                             dither);
2642
            break;
×
2643
        default:
2644
            sixel_helper_set_additional_message(
×
2645
                "sixel_prepare_specified_palette: "
2646
                "unsupported palette format.");
2647
            status = SIXEL_BAD_INPUT;
×
2648
            break;
×
2649
        }
2650

2651
palette_cleanup:
2✔
2652
        if (buffer != NULL) {
5!
2653
            sixel_allocator_free(encoder->allocator, buffer);
×
2654
            buffer = NULL;
×
2655
        }
2656
        if (stream != NULL) {
5!
2657
            sixel_palette_close_stream(stream, close_stream);
×
2658
            stream = NULL;
×
2659
        }
2660
        if (SIXEL_SUCCEEDED(status)) {
5!
2661
            return status;
×
2662
        }
2663
        if (!treat_as_image) {
5!
2664
            return status;
5✔
2665
        }
2666
    }
2667

2668
    callback_context.reqcolors = encoder->reqcolors;
21✔
2669
    callback_context.dither = NULL;
21✔
2670
    callback_context.allocator = encoder->allocator;
21✔
2671
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
2672
    callback_context.lut_policy = encoder->lut_policy;
21✔
2673

2674
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
2675
    sixel_helper_set_thumbnail_size_hint(
21✔
2676
        sixel_encoder_thumbnail_hint(encoder));
7✔
2677
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
2678
    if (SIXEL_FAILED(status)) {
21!
2679
        goto end_loader;
×
2680
    }
2681

2682
    status = sixel_loader_setopt(loader,
21✔
2683
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
2684
                                 &fstatic);
2685
    if (SIXEL_FAILED(status)) {
21!
2686
        goto end_loader;
×
2687
    }
2688

2689
    status = sixel_loader_setopt(loader,
21✔
2690
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2691
                                 &fuse_palette);
2692
    if (SIXEL_FAILED(status)) {
21!
2693
        goto end_loader;
×
2694
    }
2695

2696
    status = sixel_loader_setopt(loader,
21✔
2697
                                 SIXEL_LOADER_OPTION_REQCOLORS,
2698
                                 &reqcolors);
2699
    if (SIXEL_FAILED(status)) {
21!
2700
        goto end_loader;
×
2701
    }
2702

2703
    status = sixel_loader_setopt(loader,
28✔
2704
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2705
                                 encoder->bgcolor);
21✔
2706
    if (SIXEL_FAILED(status)) {
21!
2707
        goto end_loader;
×
2708
    }
2709

2710
    status = sixel_loader_setopt(loader,
21✔
2711
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
2712
                                 &loop_override);
2713
    if (SIXEL_FAILED(status)) {
21!
2714
        goto end_loader;
×
2715
    }
2716

2717
    status = sixel_loader_setopt(loader,
28✔
2718
                                 SIXEL_LOADER_OPTION_INSECURE,
2719
                                 &encoder->finsecure);
21✔
2720
    if (SIXEL_FAILED(status)) {
21!
2721
        goto end_loader;
×
2722
    }
2723

2724
    status = sixel_loader_setopt(loader,
28✔
2725
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2726
                                 encoder->cancel_flag);
21✔
2727
    if (SIXEL_FAILED(status)) {
21!
2728
        goto end_loader;
×
2729
    }
2730

2731
    status = sixel_loader_setopt(loader,
28✔
2732
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
2733
                                 encoder->loader_order);
21✔
2734
    if (SIXEL_FAILED(status)) {
21!
2735
        goto end_loader;
×
2736
    }
2737

2738
    status = sixel_loader_setopt(loader,
21✔
2739
                                 SIXEL_LOADER_OPTION_CONTEXT,
2740
                                 &callback_context);
2741
    if (SIXEL_FAILED(status)) {
21!
2742
        goto end_loader;
×
2743
    }
2744

2745
    status = sixel_loader_load_file(loader,
28✔
2746
                                    encoder->mapfile,
21✔
2747
                                    load_image_callback_for_palette);
2748
    if (status != SIXEL_OK) {
21!
2749
        goto end_loader;
×
2750
    }
2751

2752
end_loader:
14✔
2753
    sixel_loader_unref(loader);
21✔
2754

2755
    if (status != SIXEL_OK) {
21!
2756
        return status;
×
2757
    }
2758

2759
    if (! callback_context.dither) {
21!
2760
        sixel_helper_set_additional_message(
×
2761
            "sixel_prepare_specified_palette() failed.\n"
2762
            "reason: mapfile is empty.");
2763
        return SIXEL_BAD_INPUT;
×
2764
    }
2765

2766
    *dither = callback_context.dither;
21✔
2767

2768
    return status;
21✔
2769
}
10✔
2770

2771

2772
/* create dither object from a frame */
2773
static SIXELSTATUS
2774
sixel_encoder_prepare_palette(
521✔
2775
    sixel_encoder_t *encoder,  /* encoder object */
2776
    sixel_frame_t   *frame,    /* input frame object */
2777
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2778
{
2779
    SIXELSTATUS status = SIXEL_FALSE;
521✔
2780
    int histogram_colors;
2781
    sixel_assessment_t *assessment;
2782
    int promoted_stage;
2783

2784
    assessment = NULL;
521✔
2785
    promoted_stage = 0;
521✔
2786
    if (encoder != NULL) {
521!
2787
        assessment = encoder->assessment_observer;
521✔
2788
    }
181✔
2789

2790
    switch (encoder->color_option) {
521!
2791
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
24✔
2792
        if (encoder->dither_cache) {
36!
2793
            *dither = encoder->dither_cache;
×
2794
            status = SIXEL_OK;
×
2795
        } else {
2796
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
2797
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
2798
        }
2799
        goto end;
36✔
2800
    case SIXEL_COLOR_OPTION_MONOCHROME:
8✔
2801
        if (encoder->dither_cache) {
12!
2802
            *dither = encoder->dither_cache;
×
2803
            status = SIXEL_OK;
×
2804
        } else {
2805
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
2806
        }
2807
        goto end;
12✔
2808
    case SIXEL_COLOR_OPTION_MAPFILE:
16✔
2809
        if (encoder->dither_cache) {
26!
2810
            *dither = encoder->dither_cache;
×
2811
            status = SIXEL_OK;
×
2812
        } else {
2813
            status = sixel_prepare_specified_palette(dither, encoder);
26✔
2814
        }
2815
        goto end;
26✔
2816
    case SIXEL_COLOR_OPTION_BUILTIN:
18✔
2817
        if (encoder->dither_cache) {
27!
2818
            *dither = encoder->dither_cache;
×
2819
            status = SIXEL_OK;
×
2820
        } else {
2821
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
2822
        }
2823
        goto end;
27✔
2824
    case SIXEL_COLOR_OPTION_DEFAULT:
420✔
2825
    default:
2826
        break;
420✔
2827
    }
2828

2829
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
420✔
2830
        if (!sixel_frame_get_palette(frame)) {
191!
2831
            status = SIXEL_LOGIC_ERROR;
×
2832
            goto end;
×
2833
        }
2834
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
230✔
2835
                                  encoder->allocator);
39✔
2836
        if (SIXEL_FAILED(status)) {
191!
2837
            goto end;
×
2838
        }
2839
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
191✔
2840
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
191✔
2841
        if (sixel_frame_get_transparent(frame) != (-1)) {
191!
2842
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2843
        }
2844
        if (*dither && encoder->dither_cache) {
191!
2845
            sixel_dither_unref(encoder->dither_cache);
×
2846
        }
2847
        goto end;
191✔
2848
    }
2849

2850
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
229!
2851
        switch (sixel_frame_get_pixelformat(frame)) {
×
2852
        case SIXEL_PIXELFORMAT_G1:
2853
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2854
            break;
×
2855
        case SIXEL_PIXELFORMAT_G2:
2856
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2857
            break;
×
2858
        case SIXEL_PIXELFORMAT_G4:
2859
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2860
            break;
×
2861
        case SIXEL_PIXELFORMAT_G8:
2862
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
2863
            break;
×
2864
        default:
2865
            *dither = NULL;
×
2866
            status = SIXEL_LOGIC_ERROR;
×
2867
            goto end;
×
2868
        }
2869
        if (*dither && encoder->dither_cache) {
×
2870
            sixel_dither_unref(encoder->dither_cache);
×
2871
        }
2872
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
2873
        status = SIXEL_OK;
×
2874
        goto end;
×
2875
    }
2876

2877
    if (encoder->dither_cache) {
229!
2878
        sixel_dither_unref(encoder->dither_cache);
×
2879
    }
2880
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
229✔
2881
    if (SIXEL_FAILED(status)) {
229!
2882
        goto end;
×
2883
    }
2884

2885
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
229✔
2886

2887
    status = sixel_dither_initialize(*dither,
336✔
2888
                                     sixel_frame_get_pixels(frame),
107✔
2889
                                     sixel_frame_get_width(frame),
107✔
2890
                                     sixel_frame_get_height(frame),
107✔
2891
                                     sixel_frame_get_pixelformat(frame),
107✔
2892
                                     encoder->method_for_largest,
107✔
2893
                                     encoder->method_for_rep,
107✔
2894
                                     encoder->quality_mode);
107✔
2895
    if (SIXEL_FAILED(status)) {
229!
2896
        sixel_dither_unref(*dither);
×
2897
        goto end;
×
2898
    }
2899

2900
    if (assessment != NULL && promoted_stage == 0) {
229!
2901
        sixel_assessment_stage_transition(
×
2902
            assessment,
2903
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2904
        promoted_stage = 1;
×
2905
    }
2906

2907
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
229✔
2908
    if (histogram_colors <= encoder->reqcolors) {
229✔
2909
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
172✔
2910
    }
88✔
2911
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
229✔
2912

2913
    status = SIXEL_OK;
229✔
2914

2915
end:
340✔
2916
    if (assessment != NULL && promoted_stage == 0) {
521!
2917
        sixel_assessment_stage_transition(
×
2918
            assessment,
2919
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2920
        promoted_stage = 1;
×
2921
    }
2922
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
521!
2923
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
516✔
2924
        /* pass down the user's demand for an exact palette size */
2925
        (*dither)->force_palette = encoder->force_palette;
516✔
2926
    }
178✔
2927
    return status;
521✔
2928
}
2929

2930

2931
/* resize a frame with settings of specified encoder object */
2932
static SIXELSTATUS
2933
sixel_encoder_do_resize(
528✔
2934
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2935
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2936
{
2937
    SIXELSTATUS status = SIXEL_FALSE;
528✔
2938
    int src_width;
2939
    int src_height;
2940
    int dst_width;
2941
    int dst_height;
2942

2943
    /* get frame width and height */
2944
    src_width = sixel_frame_get_width(frame);
528✔
2945
    src_height = sixel_frame_get_height(frame);
528✔
2946

2947
    if (src_width < 1) {
528✔
2948
         sixel_helper_set_additional_message(
6✔
2949
             "sixel_encoder_do_resize: "
2950
             "detected a frame with a non-positive width.");
2951
        return SIXEL_BAD_ARGUMENT;
6✔
2952
    }
2953

2954
    if (src_height < 1) {
522!
2955
         sixel_helper_set_additional_message(
×
2956
             "sixel_encoder_do_resize: "
2957
             "detected a frame with a non-positive height.");
2958
        return SIXEL_BAD_ARGUMENT;
×
2959
    }
2960

2961
    /* settings around scaling */
2962
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
522✔
2963
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
522✔
2964

2965
    /* if the encoder has percentwidth or percentheight property,
2966
       convert them to pixelwidth / pixelheight */
2967
    if (encoder->percentwidth > 0) {
522✔
2968
        dst_width = src_width * encoder->percentwidth / 100;
12✔
2969
    }
4✔
2970
    if (encoder->percentheight > 0) {
522✔
2971
        dst_height = src_height * encoder->percentheight / 100;
9✔
2972
    }
3✔
2973

2974
    /* if only either width or height is set, set also the other
2975
       to retain frame aspect ratio */
2976
    if (dst_width > 0 && dst_height <= 0) {
522✔
2977
        dst_height = src_height * dst_width / src_width;
42✔
2978
    }
14✔
2979
    if (dst_height > 0 && dst_width <= 0) {
522✔
2980
        dst_width = src_width * dst_height / src_height;
33✔
2981
    }
11✔
2982

2983
    /* do resize */
2984
    if (dst_width > 0 && dst_height > 0) {
522!
2985
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
2986
                                    encoder->method_for_resampling);
31✔
2987
        if (SIXEL_FAILED(status)) {
93!
2988
            goto end;
×
2989
        }
2990
    }
31✔
2991

2992
    /* success */
2993
    status = SIXEL_OK;
522✔
2994

2995
end:
340✔
2996
    return status;
522✔
2997
}
184✔
2998

2999

3000
/* clip a frame with settings of specified encoder object */
3001
static SIXELSTATUS
3002
sixel_encoder_do_clip(
524✔
3003
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
3004
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
3005
{
3006
    SIXELSTATUS status = SIXEL_FALSE;
524✔
3007
    int src_width;
3008
    int src_height;
3009
    int clip_x;
3010
    int clip_y;
3011
    int clip_w;
3012
    int clip_h;
3013

3014
    /* get frame width and height */
3015
    src_width = sixel_frame_get_width(frame);
524✔
3016
    src_height = sixel_frame_get_height(frame);
524✔
3017

3018
    /* settings around clipping */
3019
    clip_x = encoder->clipx;
524✔
3020
    clip_y = encoder->clipy;
524✔
3021
    clip_w = encoder->clipwidth;
524✔
3022
    clip_h = encoder->clipheight;
524✔
3023

3024
    /* adjust clipping width with comparing it to frame width */
3025
    if (clip_w + clip_x > src_width) {
524✔
3026
        if (clip_x > src_width) {
7✔
3027
            clip_w = 0;
3✔
3028
        } else {
1✔
3029
            clip_w = src_width - clip_x;
4✔
3030
        }
3031
    }
3✔
3032

3033
    /* adjust clipping height with comparing it to frame height */
3034
    if (clip_h + clip_y > src_height) {
524✔
3035
        if (clip_y > src_height) {
6✔
3036
            clip_h = 0;
3✔
3037
        } else {
1✔
3038
            clip_h = src_height - clip_y;
3✔
3039
        }
3040
    }
2✔
3041

3042
    /* do clipping */
3043
    if (clip_w > 0 && clip_h > 0) {
524!
3044
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
15✔
3045
        if (SIXEL_FAILED(status)) {
15!
3046
            goto end;
3✔
3047
        }
3048
    }
4✔
3049

3050
    /* success */
3051
    status = SIXEL_OK;
521✔
3052

3053
end:
340✔
3054
    return status;
524✔
3055
}
3056

3057

3058
static void
3059
sixel_debug_print_palette(
3✔
3060
    sixel_dither_t /* in */ *dither /* dithering object */
3061
)
3062
{
3063
    unsigned char *palette;
3064
    int i;
3065

3066
    palette = sixel_dither_get_palette(dither);
3✔
3067
    fprintf(stderr, "palette:\n");
3✔
3068
    for (i = 0; i < sixel_dither_get_num_of_palette_colors(dither); ++i) {
51✔
3069
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
64✔
3070
                palette[i * 3 + 0],
48✔
3071
                palette[i * 3 + 1],
48✔
3072
                palette[i * 3 + 2]);
48✔
3073
    }
16✔
3074
}
3✔
3075

3076

3077
static SIXELSTATUS
3078
sixel_encoder_output_without_macro(
423✔
3079
    sixel_frame_t       /* in */ *frame,
3080
    sixel_dither_t      /* in */ *dither,
3081
    sixel_output_t      /* in */ *output,
3082
    sixel_encoder_t     /* in */ *encoder)
3083
{
3084
    SIXELSTATUS status = SIXEL_OK;
423✔
3085
    static unsigned char *p;
3086
    int depth;
3087
    enum { message_buffer_size = 256 };
3088
    char message[message_buffer_size];
3089
    int nwrite;
3090
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3091
    int dulation;
3092
    int delay;
3093
    struct timespec tv;
3094
#endif
3095
    unsigned char *pixbuf;
3096
    int width;
3097
    int height;
3098
    int pixelformat = 0;
423✔
3099
    size_t size;
3100
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
423✔
3101
    palette_conversion_t palette_ctx;
3102

3103
    memset(&palette_ctx, 0, sizeof(palette_ctx));
423✔
3104
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3105
    sixel_clock_t last_clock;
3106
#endif
3107

3108
    if (encoder == NULL) {
423!
3109
        sixel_helper_set_additional_message(
×
3110
            "sixel_encoder_output_without_macro: encoder object is null.");
3111
        status = SIXEL_BAD_ARGUMENT;
×
3112
        goto end;
×
3113
    }
3114

3115
    if (encoder->assessment_observer != NULL) {
423!
3116
        sixel_assessment_stage_transition(
×
3117
            encoder->assessment_observer,
×
3118
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3119
    }
3120

3121
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
423✔
3122
        if (encoder->force_palette) {
327!
3123
            /* keep every slot when the user forced the palette size */
3124
            sixel_dither_set_optimize_palette(dither, 0);
×
3125
        } else {
3126
            sixel_dither_set_optimize_palette(dither, 1);
327✔
3127
        }
3128
    }
109✔
3129

3130
    pixelformat = sixel_frame_get_pixelformat(frame);
423✔
3131
    frame_colorspace = sixel_frame_get_colorspace(frame);
423✔
3132
    output->pixelformat = pixelformat;
423✔
3133
    output->source_colorspace = frame_colorspace;
423✔
3134
    output->colorspace = encoder->output_colorspace;
423✔
3135
    sixel_dither_set_pixelformat(dither, pixelformat);
423✔
3136
    depth = sixel_helper_compute_depth(pixelformat);
423✔
3137
    if (depth < 0) {
423!
3138
        status = SIXEL_LOGIC_ERROR;
×
3139
        nwrite = sprintf(message,
×
3140
                         "sixel_encoder_output_without_macro: "
3141
                         "sixel_helper_compute_depth(%08x) failed.",
3142
                         pixelformat);
3143
        if (nwrite > 0) {
×
3144
            sixel_helper_set_additional_message(message);
×
3145
        }
3146
        goto end;
×
3147
    }
3148

3149
    width = sixel_frame_get_width(frame);
423✔
3150
    height = sixel_frame_get_height(frame);
423✔
3151
    size = (size_t)(width * height * depth);
423✔
3152
    p = (unsigned char *)sixel_allocator_malloc(encoder->allocator, size);
423✔
3153
    if (p == NULL) {
423!
3154
        sixel_helper_set_additional_message(
×
3155
            "sixel_encoder_output_without_macro: sixel_allocator_malloc() failed.");
3156
        status = SIXEL_BAD_ALLOCATION;
×
3157
        goto end;
×
3158
    }
3159
#if defined(HAVE_CLOCK)
3160
    if (output->last_clock == 0) {
423!
3161
        output->last_clock = clock();
423✔
3162
    }
141✔
3163
#elif defined(HAVE_CLOCK_WIN)
3164
    if (output->last_clock == 0) {
3165
        output->last_clock = clock_win();
3166
    }
3167
#endif
3168
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3169
    delay = sixel_frame_get_delay(frame);
423✔
3170
    if (delay > 0 && !encoder->fignore_delay) {
423✔
3171
# if defined(HAVE_CLOCK)
3172
        last_clock = clock();
80✔
3173
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3174
#  if defined(__APPLE__)
3175
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
156✔
3176
                          / 100000);
78✔
3177
#  else
3178
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
2✔
3179
                          / CLOCKS_PER_SEC);
3180
#  endif
3181
        output->last_clock = last_clock;
80✔
3182
# elif defined(HAVE_CLOCK_WIN)
3183
        last_clock = clock_win();
3184
        dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3185
                          / CLOCKS_PER_SEC);
3186
        output->last_clock = last_clock;
3187
# else
3188
        dulation = 0;
3189
# endif
3190
        if (dulation < 1000 * 10 * delay) {
80!
3191
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3192
            tv.tv_sec = 0;
80✔
3193
            tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
80✔
3194
#  if defined(HAVE_NANOSLEEP)
3195
            nanosleep(&tv, NULL);
80✔
3196
#  else
3197
            nanosleep_win(&tv, NULL);
3198
#  endif
3199
# endif
3200
        }
78✔
3201
    }
78✔
3202
#endif
3203

3204
    pixbuf = sixel_frame_get_pixels(frame);
423✔
3205
    memcpy(p, pixbuf, (size_t)(width * height * depth));
423✔
3206

3207
    status = sixel_output_convert_colorspace(output, p, size);
423✔
3208
    if (SIXEL_FAILED(status)) {
423!
3209
        goto end;
×
3210
    }
3211

3212
    if (encoder->cancel_flag && *encoder->cancel_flag) {
423!
3213
        goto end;
×
3214
    }
3215

3216
    status = sixel_encoder_convert_palette(encoder,
564✔
3217
                                           output,
141✔
3218
                                           dither,
141✔
3219
                                           frame_colorspace,
141✔
3220
                                           pixelformat,
141✔
3221
                                           &palette_ctx);
3222
    if (SIXEL_FAILED(status)) {
423!
3223
        goto end;
×
3224
    }
3225

3226
    if (encoder->capture_quantized) {
423!
3227
        status = sixel_encoder_capture_quantized(encoder,
×
3228
                                                 dither,
3229
                                                 p,
3230
                                                 size,
3231
                                                 width,
3232
                                                 height,
3233
                                                 pixelformat,
3234
                                                 output->colorspace);
3235
        if (SIXEL_FAILED(status)) {
×
3236
            goto end;
×
3237
        }
3238
    }
3239

3240
    if (encoder->assessment_observer != NULL) {
423!
3241
        sixel_assessment_stage_transition(
×
3242
            encoder->assessment_observer,
×
3243
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3244
    }
3245
    status = sixel_encode(p, width, height, depth, dither, output);
423✔
3246
    if (encoder->assessment_observer != NULL) {
423!
3247
        sixel_assessment_stage_finish(encoder->assessment_observer);
×
3248
    }
3249
    if (status != SIXEL_OK) {
423!
3250
        goto end;
×
3251
    }
3252

3253
end:
282✔
3254
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
423✔
3255
    output->pixelformat = pixelformat;
423✔
3256
    output->source_colorspace = frame_colorspace;
423✔
3257
    sixel_allocator_free(encoder->allocator, p);
423✔
3258

3259
    return status;
423✔
3260
}
3261

3262

3263
static SIXELSTATUS
3264
sixel_encoder_output_with_macro(
93✔
3265
    sixel_frame_t   /* in */ *frame,
3266
    sixel_dither_t  /* in */ *dither,
3267
    sixel_output_t  /* in */ *output,
3268
    sixel_encoder_t /* in */ *encoder)
3269
{
3270
    SIXELSTATUS status = SIXEL_OK;
93✔
3271
    enum { message_buffer_size = 256 };
3272
    char buffer[message_buffer_size];
3273
    int nwrite;
3274
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3275
    int dulation;
3276
    struct timespec tv;
3277
#endif
3278
    int width;
3279
    int height;
3280
    int pixelformat;
3281
    int depth;
3282
    size_t size = 0;
93✔
3283
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
93✔
3284
    unsigned char *converted = NULL;
93✔
3285
    palette_conversion_t palette_ctx;
3286
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3287
    int delay;
3288
#endif
3289
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3290
    sixel_clock_t last_clock;
3291
#endif
3292
    double write_started_at;
3293
    double write_finished_at;
3294
    double write_duration;
3295

3296
    memset(&palette_ctx, 0, sizeof(palette_ctx));
93✔
3297

3298
    if (encoder != NULL && encoder->assessment_observer != NULL) {
93!
3299
        sixel_assessment_stage_transition(
×
3300
            encoder->assessment_observer,
×
3301
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3302
    }
3303

3304
#if defined(HAVE_CLOCK)
3305
    if (output->last_clock == 0) {
93!
3306
        output->last_clock = clock();
93✔
3307
    }
37✔
3308
#elif defined(HAVE_CLOCK_WIN)
3309
    if (output->last_clock == 0) {
3310
        output->last_clock = clock_win();
3311
    }
3312
#endif
3313

3314
    width = sixel_frame_get_width(frame);
93✔
3315
    height = sixel_frame_get_height(frame);
93✔
3316
    pixelformat = sixel_frame_get_pixelformat(frame);
93✔
3317
    depth = sixel_helper_compute_depth(pixelformat);
93✔
3318
    if (depth < 0) {
93!
3319
        status = SIXEL_LOGIC_ERROR;
×
3320
        sixel_helper_set_additional_message(
×
3321
            "sixel_encoder_output_with_macro: "
3322
            "sixel_helper_compute_depth() failed.");
3323
        goto end;
×
3324
    }
3325

3326
    frame_colorspace = sixel_frame_get_colorspace(frame);
93✔
3327
    size = (size_t)width * (size_t)height * (size_t)depth;
93✔
3328
    converted = (unsigned char *)sixel_allocator_malloc(
93✔
3329
        encoder->allocator, size);
37✔
3330
    if (converted == NULL) {
93!
3331
        sixel_helper_set_additional_message(
×
3332
            "sixel_encoder_output_with_macro: "
3333
            "sixel_allocator_malloc() failed.");
3334
        status = SIXEL_BAD_ALLOCATION;
×
3335
        goto end;
×
3336
    }
3337

3338
    memcpy(converted, sixel_frame_get_pixels(frame), size);
93✔
3339
    output->pixelformat = pixelformat;
93✔
3340
    output->source_colorspace = frame_colorspace;
93✔
3341
    output->colorspace = encoder->output_colorspace;
93✔
3342
    status = sixel_output_convert_colorspace(output, converted, size);
93✔
3343
    if (SIXEL_FAILED(status)) {
93!
3344
        goto end;
×
3345
    }
3346

3347
    status = sixel_encoder_convert_palette(encoder,
130✔
3348
                                           output,
37✔
3349
                                           dither,
37✔
3350
                                           frame_colorspace,
37✔
3351
                                           pixelformat,
37✔
3352
                                           &palette_ctx);
3353
    if (SIXEL_FAILED(status)) {
93!
3354
        goto end;
×
3355
    }
3356

3357
    if (sixel_frame_get_loop_no(frame) == 0) {
93✔
3358
        if (encoder->macro_number >= 0) {
55!
3359
            nwrite = sprintf(buffer, "\033P%d;0;1!z",
1✔
3360
                             encoder->macro_number);
3361
        } else {
1✔
3362
            nwrite = sprintf(buffer, "\033P%d;0;1!z",
54✔
3363
                             sixel_frame_get_frame_no(frame));
3364
        }
3365
        if (nwrite < 0) {
55!
3366
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3367
            sixel_helper_set_additional_message(
×
3368
                "sixel_encoder_output_with_macro: sprintf() failed.");
3369
            goto end;
×
3370
        }
3371
        write_started_at = 0.0;
55✔
3372
        write_finished_at = 0.0;
55✔
3373
        write_duration = 0.0;
55✔
3374
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3375
            write_started_at = sixel_assessment_timer_now();
×
3376
        }
3377
        nwrite = sixel_write_callback(buffer,
74✔
3378
                                      (int)strlen(buffer),
55✔
3379
                                      &encoder->outfd);
55✔
3380
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3381
            write_finished_at = sixel_assessment_timer_now();
×
3382
            write_duration = write_finished_at - write_started_at;
×
3383
            if (write_duration < 0.0) {
×
3384
                write_duration = 0.0;
×
3385
            }
3386
        }
3387
        if (nwrite < 0) {
55!
3388
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3389
            sixel_helper_set_additional_message(
×
3390
                "sixel_encoder_output_with_macro: "
3391
                "sixel_write_callback() failed.");
3392
            goto end;
×
3393
        }
3394
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3395
            sixel_assessment_record_output_write(
×
3396
                encoder->assessment_observer,
×
3397
                (size_t)nwrite,
3398
                write_duration);
3399
        }
3400

3401
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3402
            sixel_assessment_stage_transition(
×
3403
                encoder->assessment_observer,
×
3404
                SIXEL_ASSESSMENT_STAGE_ENCODE);
3405
        }
3406
        status = sixel_encode(converted,
74✔
3407
                              width,
19✔
3408
                              height,
19✔
3409
                              depth,
19✔
3410
                              dither,
19✔
3411
                              output);
19✔
3412
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3413
            sixel_assessment_stage_finish(
×
3414
                encoder->assessment_observer);
×
3415
        }
3416
        if (SIXEL_FAILED(status)) {
55!
3417
            goto end;
×
3418
        }
3419

3420
        write_started_at = 0.0;
55✔
3421
        write_finished_at = 0.0;
55✔
3422
        write_duration = 0.0;
55✔
3423
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3424
            write_started_at = sixel_assessment_timer_now();
×
3425
        }
3426
        nwrite = sixel_write_callback("\033\\", 2, &encoder->outfd);
55✔
3427
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3428
            write_finished_at = sixel_assessment_timer_now();
×
3429
            write_duration = write_finished_at - write_started_at;
×
3430
            if (write_duration < 0.0) {
×
3431
                write_duration = 0.0;
×
3432
            }
3433
        }
3434
        if (nwrite < 0) {
55!
3435
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3436
            sixel_helper_set_additional_message(
×
3437
                "sixel_encoder_output_with_macro: "
3438
                "sixel_write_callback() failed.");
3439
            goto end;
×
3440
        }
3441
        if (encoder != NULL && encoder->assessment_observer != NULL) {
55!
3442
            sixel_assessment_record_output_write(
×
3443
                encoder->assessment_observer,
×
3444
                (size_t)nwrite,
3445
                write_duration);
3446
        }
3447
    }
19✔
3448
    if (encoder->macro_number < 0) {
129✔
3449
        nwrite = sprintf(buffer, "\033[%d*z",
90✔
3450
                         sixel_frame_get_frame_no(frame));
3451
        if (nwrite < 0) {
90!
3452
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3453
            sixel_helper_set_additional_message(
×
3454
                "sixel_encoder_output_with_macro: sprintf() failed.");
3455
        }
3456
        write_started_at = 0.0;
90✔
3457
        write_finished_at = 0.0;
90✔
3458
        write_duration = 0.0;
90✔
3459
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3460
            write_started_at = sixel_assessment_timer_now();
×
3461
        }
3462
        nwrite = sixel_write_callback(buffer,
126✔
3463
                                      (int)strlen(buffer),
90✔
3464
                                      &encoder->outfd);
90✔
3465
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3466
            write_finished_at = sixel_assessment_timer_now();
×
3467
            write_duration = write_finished_at - write_started_at;
×
3468
            if (write_duration < 0.0) {
×
3469
                write_duration = 0.0;
×
3470
            }
3471
        }
3472
        if (nwrite < 0) {
90!
3473
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3474
            sixel_helper_set_additional_message(
×
3475
                "sixel_encoder_output_with_macro: "
3476
                "sixel_write_callback() failed.");
3477
            goto end;
×
3478
        }
3479
        if (encoder != NULL && encoder->assessment_observer != NULL) {
90!
3480
            sixel_assessment_record_output_write(
×
3481
                encoder->assessment_observer,
×
3482
                (size_t)nwrite,
3483
                write_duration);
3484
        }
3485
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3486
        delay = sixel_frame_get_delay(frame);
90✔
3487
        if (delay > 0 && !encoder->fignore_delay) {
90!
3488
# if defined(HAVE_CLOCK)
3489
            last_clock = clock();
63✔
3490
/* https://stackoverflow.com/questions/16697005/clock-and-clocks-per-sec-on-osx-10-7 */
3491
#  if defined(__APPLE__)
3492
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
54✔
3493
                             / 100000);
27✔
3494
#  else
3495
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
36✔
3496
                             / CLOCKS_PER_SEC);
3497
#  endif
3498
            output->last_clock = last_clock;
63✔
3499
# elif defined(HAVE_CLOCK_WIN)
3500
            last_clock = clock_win();
3501
            dulation = (int)((last_clock - output->last_clock) * 1000 * 1000
3502
                             / CLOCKS_PER_SEC);
3503
            output->last_clock = last_clock;
3504
# else
3505
            dulation = 0;
3506
# endif
3507
            if (dulation < 1000 * 10 * delay) {
63✔
3508
# if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3509
                tv.tv_sec = 0;
58✔
3510
                tv.tv_nsec = (long)((1000 * 10 * delay - dulation) * 1000);
58✔
3511
#  if defined(HAVE_NANOSLEEP)
3512
                nanosleep(&tv, NULL);
58✔
3513
#  else
3514
                nanosleep_win(&tv, NULL);
3515
#  endif
3516
# endif
3517
            }
24✔
3518
        }
27✔
3519
#endif
3520
    }
36✔
3521

3522
end:
22✔
3523
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
93✔
3524
    output->pixelformat = pixelformat;
93✔
3525
    output->source_colorspace = frame_colorspace;
93✔
3526
    sixel_allocator_free(encoder->allocator, converted);
93✔
3527

3528
    return status;
93✔
3529
}
3530

3531

3532
static SIXELSTATUS
3533
sixel_encoder_emit_iso2022_chars(
×
3534
    sixel_encoder_t *encoder,
3535
    sixel_frame_t *frame
3536
)
3537
{
3538
    char *buf_p, *buf;
3539
    int col, row;
3540
    int charset;
3541
    int is_96cs;
3542
    unsigned int charset_no;
3543
    unsigned int code;
3544
    int num_cols, num_rows;
3545
    SIXELSTATUS status;
3546
    size_t alloc_size;
3547
    int nwrite;
3548
    int target_fd;
3549
    int chunk_size;
3550

3551
    charset_no = encoder->drcs_charset_no;
×
3552
    if (charset_no == 0u) {
×
3553
        charset_no = 1u;
×
3554
    }
3555
    if (encoder->drcs_mmv == 0) {
×
3556
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3557
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3558
    } else if (encoder->drcs_mmv == 1) {
×
3559
        is_96cs = 0;
×
3560
        charset = (int)(charset_no + 0x3fu);
×
3561
    } else {
3562
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3563
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3564
    }
3565
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3566
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3567
             / encoder->cell_width;
×
3568
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3569
             / encoder->cell_height;
×
3570

3571
    /* cols x rows + designation(4 chars) + SI + SO + LFs */
3572
    alloc_size = num_cols * num_rows + (num_cols * num_rows / 96 + 1) * 4 + 2 + num_rows;
×
3573
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3574
    if (buf == NULL) {
×
3575
        sixel_helper_set_additional_message(
×
3576
            "sixel_encoder_emit_iso2022_chars: sixel_allocator_malloc() failed.");
3577
        status = SIXEL_BAD_ALLOCATION;
×
3578
        goto end;
×
3579
    }
3580

3581
    code = 0x20;
×
3582
    *(buf_p++) = '\016';  /* SI */
×
3583
    *(buf_p++) = '\033';
×
3584
    *(buf_p++) = ')';
×
3585
    *(buf_p++) = ' ';
×
3586
    *(buf_p++) = charset;
×
3587
    for(row = 0; row < num_rows; row++) {
×
3588
        for(col = 0; col < num_cols; col++) {
×
3589
            if ((code & 0x7f) == 0x0) {
×
3590
                if (charset == 0x7e) {
×
3591
                    is_96cs = 1 - is_96cs;
×
3592
                    charset = '0';
×
3593
                } else {
3594
                    charset++;
×
3595
                }
3596
                code = 0x20;
×
3597
                *(buf_p++) = '\033';
×
3598
                *(buf_p++) = is_96cs ? '-': ')';
×
3599
                *(buf_p++) = ' ';
×
3600
                *(buf_p++) = charset;
×
3601
            }
3602
            *(buf_p++) = code++;
×
3603
        }
3604
        *(buf_p++) = '\n';
×
3605
    }
3606
    *(buf_p++) = '\017';  /* SO */
×
3607

3608
    if (encoder->tile_outfd >= 0) {
×
3609
        target_fd = encoder->tile_outfd;
×
3610
    } else {
3611
        target_fd = encoder->outfd;
×
3612
    }
3613

3614
    chunk_size = (int)(buf_p - buf);
×
3615
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3616
                                          buf,
3617
                                          chunk_size,
3618
                                          target_fd);
3619
    if (nwrite != chunk_size) {
×
3620
        sixel_helper_set_additional_message(
×
3621
            "sixel_encoder_emit_iso2022_chars: write() failed.");
3622
        status = SIXEL_RUNTIME_ERROR;
×
3623
        goto end;
×
3624
    }
3625

3626
    sixel_allocator_free(encoder->allocator, buf);
×
3627

3628
    status = SIXEL_OK;
×
3629

3630
end:
3631
    return status;
×
3632
}
3633

3634

3635
/*
3636
 * This routine is derived from mlterm's drcssixel.c
3637
 * (https://raw.githubusercontent.com/arakiken/mlterm/master/drcssixel/drcssixel.c).
3638
 * The original implementation is credited to Araki Ken.
3639
 * Adjusted here to integrate with libsixel's encoder pipeline.
3640
 */
3641
static SIXELSTATUS
3642
sixel_encoder_emit_drcsmmv2_chars(
×
3643
    sixel_encoder_t *encoder,
3644
    sixel_frame_t *frame
3645
)
3646
{
3647
    char *buf_p, *buf;
3648
    int col, row;
3649
    int charset;
3650
    int is_96cs;
3651
    unsigned int charset_no;
3652
    unsigned int code;
3653
    int num_cols, num_rows;
3654
    SIXELSTATUS status;
3655
    size_t alloc_size;
3656
    int nwrite;
3657
    int target_fd;
3658
    int chunk_size;
3659

3660
    charset_no = encoder->drcs_charset_no;
×
3661
    if (charset_no == 0u) {
×
3662
        charset_no = 1u;
×
3663
    }
3664
    if (encoder->drcs_mmv == 0) {
×
3665
        is_96cs = (charset_no > 63u) ? 1 : 0;
×
3666
        charset = (int)(((charset_no - 1u) % 63u) + 0x40u);
×
3667
    } else if (encoder->drcs_mmv == 1) {
×
3668
        is_96cs = 0;
×
3669
        charset = (int)(charset_no + 0x3fu);
×
3670
    } else {
3671
        is_96cs = (charset_no > 79u) ? 1 : 0;
×
3672
        charset = (int)(((charset_no - 1u) % 79u) + 0x30u);
×
3673
    }
3674
    code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3675
    num_cols = (sixel_frame_get_width(frame) + encoder->cell_width - 1)
×
3676
             / encoder->cell_width;
×
3677
    num_rows = (sixel_frame_get_height(frame) + encoder->cell_height - 1)
×
3678
             / encoder->cell_height;
×
3679

3680
    /* cols x rows x 4(out of BMP) + rows(LFs) */
3681
    alloc_size = num_cols * num_rows * 4 + num_rows;
×
3682
    buf_p = buf = sixel_allocator_malloc(encoder->allocator, alloc_size);
×
3683
    if (buf == NULL) {
×
3684
        sixel_helper_set_additional_message(
×
3685
            "sixel_encoder_emit_drcsmmv2_chars: sixel_allocator_malloc() failed.");
3686
        status = SIXEL_BAD_ALLOCATION;
×
3687
        goto end;
×
3688
    }
3689

3690
    for(row = 0; row < num_rows; row++) {
×
3691
        for(col = 0; col < num_cols; col++) {
×
3692
            *(buf_p++) = ((code >> 18) & 0x07) | 0xf0;
×
3693
            *(buf_p++) = ((code >> 12) & 0x3f) | 0x80;
×
3694
            *(buf_p++) = ((code >> 6) & 0x3f) | 0x80;
×
3695
            *(buf_p++) = (code & 0x3f) | 0x80;
×
3696
            code++;
×
3697
            if ((code & 0x7f) == 0x0) {
×
3698
                if (charset == 0x7e) {
×
3699
                    is_96cs = 1 - is_96cs;
×
3700
                    charset = '0';
×
3701
                } else {
3702
                    charset++;
×
3703
                }
3704
                code = 0x100020 + (is_96cs ? 0x80 : 0) + charset * 0x100;
×
3705
            }
3706
        }
3707
        *(buf_p++) = '\n';
×
3708
    }
3709

3710
    if (charset == 0x7e) {
×
3711
        is_96cs = 1 - is_96cs;
×
3712
    } else {
3713
        charset = '0';
×
3714
        charset++;
×
3715
    }
3716
    if (encoder->tile_outfd >= 0) {
×
3717
        target_fd = encoder->tile_outfd;
×
3718
    } else {
3719
        target_fd = encoder->outfd;
×
3720
    }
3721

3722
    chunk_size = (int)(buf_p - buf);
×
3723
    nwrite = sixel_encoder_probe_fd_write(encoder,
×
3724
                                          buf,
3725
                                          chunk_size,
3726
                                          target_fd);
3727
    if (nwrite != chunk_size) {
×
3728
        sixel_helper_set_additional_message(
×
3729
            "sixel_encoder_emit_drcsmmv2_chars: write() failed.");
3730
        status = SIXEL_RUNTIME_ERROR;
×
3731
        goto end;
×
3732
    }
3733

3734
    sixel_allocator_free(encoder->allocator, buf);
×
3735

3736
    status = SIXEL_OK;
×
3737

3738
end:
3739
    return status;
×
3740
}
3741

3742
static SIXELSTATUS
3743
sixel_encoder_encode_frame(
530✔
3744
    sixel_encoder_t *encoder,
3745
    sixel_frame_t   *frame,
3746
    sixel_output_t  *output)
3747
{
3748
    SIXELSTATUS status = SIXEL_FALSE;
530✔
3749
    sixel_dither_t *dither = NULL;
530✔
3750
    int height;
3751
    int is_animation = 0;
530✔
3752
    int nwrite;
3753
    int drcs_is_96cs_param;
3754
    int drcs_designate_char;
3755
    char buf[256];
3756
    sixel_write_function fn_write;
3757
    sixel_write_function write_callback;
3758
    sixel_write_function scroll_callback;
3759
    void *write_priv;
3760
    void *scroll_priv;
3761
    sixel_encoder_output_probe_t probe;
3762
    sixel_encoder_output_probe_t scroll_probe;
3763
    sixel_assessment_t *assessment;
3764

3765
    fn_write = sixel_write_callback;
530✔
3766
    write_callback = sixel_write_callback;
530✔
3767
    scroll_callback = sixel_write_callback;
530✔
3768
    write_priv = &encoder->outfd;
530✔
3769
    scroll_priv = &encoder->outfd;
530✔
3770
    probe.encoder = NULL;
530✔
3771
    probe.base_write = NULL;
530✔
3772
    probe.base_priv = NULL;
530✔
3773
    scroll_probe.encoder = NULL;
530✔
3774
    scroll_probe.base_write = NULL;
530✔
3775
    scroll_probe.base_priv = NULL;
530✔
3776
    assessment = NULL;
530✔
3777
    if (encoder != NULL) {
530!
3778
        assessment = encoder->assessment_observer;
530✔
3779
    }
186✔
3780
    if (assessment != NULL) {
530!
3781
        if (encoder->clipfirst) {
×
3782
            sixel_assessment_stage_transition(
×
3783
                assessment,
3784
                SIXEL_ASSESSMENT_STAGE_CROP);
3785
        } else {
3786
            sixel_assessment_stage_transition(
×
3787
                assessment,
3788
                SIXEL_ASSESSMENT_STAGE_SCALE);
3789
        }
3790
    }
3791

3792
    /*
3793
     *  Geometry timeline:
3794
     *
3795
     *      +-------+    +------+    +---------------+
3796
     *      | scale | -> | crop | -> | color convert |
3797
     *      +-------+    +------+    +---------------+
3798
     *
3799
     *  The order of the first two blocks mirrors `-c`, so we hop between
3800
     *  SCALE and CROP depending on `clipfirst`.
3801
     */
3802

3803
    if (encoder->clipfirst) {
530✔
3804
        status = sixel_encoder_do_clip(encoder, frame);
8✔
3805
        if (SIXEL_FAILED(status)) {
8!
3806
            goto end;
2✔
3807
        }
3808
        if (assessment != NULL) {
6!
3809
            sixel_assessment_stage_transition(
×
3810
                assessment,
3811
                SIXEL_ASSESSMENT_STAGE_SCALE);
3812
        }
3813
        status = sixel_encoder_do_resize(encoder, frame);
6✔
3814
        if (SIXEL_FAILED(status)) {
6!
3815
            goto end;
×
3816
        }
3817
    } else {
2✔
3818
        status = sixel_encoder_do_resize(encoder, frame);
522✔
3819
        if (SIXEL_FAILED(status)) {
522✔
3820
            goto end;
6✔
3821
        }
3822
        if (assessment != NULL) {
516!
3823
            sixel_assessment_stage_transition(
×
3824
                assessment,
3825
                SIXEL_ASSESSMENT_STAGE_CROP);
3826
        }
3827
        status = sixel_encoder_do_clip(encoder, frame);
516✔
3828
        if (SIXEL_FAILED(status)) {
516!
3829
            goto end;
1✔
3830
        }
3831
    }
3832

3833
    if (assessment != NULL) {
521!
3834
        sixel_assessment_stage_transition(
×
3835
            assessment,
3836
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
3837
    }
3838

3839
    status = sixel_frame_ensure_colorspace(frame,
702✔
3840
                                           encoder->working_colorspace);
181✔
3841
    if (SIXEL_FAILED(status)) {
521!
3842
        goto end;
×
3843
    }
3844

3845
    if (assessment != NULL) {
521!
3846
        sixel_assessment_stage_transition(
×
3847
            assessment,
3848
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
3849
    }
3850

3851
    /* prepare dither context */
3852
    status = sixel_encoder_prepare_palette(encoder, frame, &dither);
521✔
3853
    if (status != SIXEL_OK) {
521✔
3854
        dither = NULL;
5✔
3855
        goto end;
5✔
3856
    }
3857

3858
    if (encoder->dither_cache != NULL) {
516!
3859
        encoder->dither_cache = dither;
×
3860
        sixel_dither_ref(dither);
×
3861
    }
3862

3863
    if (encoder->fdrcs) {
516!
3864
        status = sixel_encoder_ensure_cell_size(encoder);
×
3865
        if (SIXEL_FAILED(status)) {
×
3866
            goto end;
×
3867
        }
3868
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
×
3869
            sixel_helper_set_additional_message(
×
3870
                "drcs option cannot be used together with macro output.");
3871
            status = SIXEL_BAD_ARGUMENT;
×
3872
            goto end;
×
3873
        }
3874
    }
3875

3876
    /* evaluate -v option: print palette */
3877
    if (encoder->verbose) {
516✔
3878
        if ((sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE)) {
9✔
3879
            sixel_debug_print_palette(dither);
3✔
3880
        }
1✔
3881
    }
3✔
3882

3883
    /* evaluate -d option: set method for diffusion */
3884
    sixel_dither_set_diffusion_type(dither, encoder->method_for_diffuse);
516✔
3885
    sixel_dither_set_diffusion_scan(dither, encoder->method_for_scan);
516✔
3886
    sixel_dither_set_diffusion_carry(dither, encoder->method_for_carry);
516✔
3887

3888
    /* evaluate -C option: set complexion score */
3889
    if (encoder->complexion > 1) {
516✔
3890
        sixel_dither_set_complexion_score(dither, encoder->complexion);
6✔
3891
    }
2✔
3892

3893
    if (output) {
516!
3894
        sixel_output_ref(output);
×
3895
        if (encoder->assessment_observer != NULL) {
×
3896
            probe.encoder = encoder;
×
3897
            probe.base_write = fn_write;
×
3898
            probe.base_priv = &encoder->outfd;
×
3899
            write_callback = sixel_write_with_probe;
×
3900
            write_priv = &probe;
×
3901
        }
3902
    } else {
3903
        /* create output context */
3904
        if (encoder->fuse_macro || encoder->macro_number >= 0) {
516✔
3905
            /* -u or -n option */
3906
            fn_write = sixel_hex_write_callback;
93✔
3907
        } else {
37✔
3908
            fn_write = sixel_write_callback;
423✔
3909
        }
3910
        write_callback = fn_write;
516✔
3911
        write_priv = &encoder->outfd;
516✔
3912
        if (encoder->assessment_observer != NULL) {
516!
3913
            probe.encoder = encoder;
×
3914
            probe.base_write = fn_write;
×
3915
            probe.base_priv = &encoder->outfd;
×
3916
            write_callback = sixel_write_with_probe;
×
3917
            write_priv = &probe;
×
3918
        }
3919
        status = sixel_output_new(&output,
516✔
3920
                                  write_callback,
178✔
3921
                                  write_priv,
178✔
3922
                                  encoder->allocator);
178✔
3923
        if (SIXEL_FAILED(status)) {
516!
3924
            goto end;
×
3925
        }
3926
    }
3927

3928
    if (encoder->fdrcs) {
516!
3929
        sixel_output_set_skip_dcs_envelope(output, 1);
×
3930
        sixel_output_set_skip_header(output, 1);
×
3931
    }
3932

3933
    sixel_output_set_8bit_availability(output, encoder->f8bit);
516✔
3934
    sixel_output_set_gri_arg_limit(output, encoder->has_gri_arg_limit);
516✔
3935
    sixel_output_set_palette_type(output, encoder->palette_type);
516✔
3936
    sixel_output_set_penetrate_multiplexer(
516✔
3937
        output, encoder->penetrate_multiplexer);
178✔
3938
    sixel_output_set_encode_policy(output, encoder->encode_policy);
516✔
3939
    sixel_output_set_ormode(output, encoder->ormode);
516✔
3940

3941
    if (sixel_frame_get_multiframe(frame) && !encoder->fstatic) {
516!
3942
        if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
117✔
3943
            is_animation = 1;
108✔
3944
        }
42✔
3945
        height = sixel_frame_get_height(frame);
117✔
3946
        if (encoder->assessment_observer != NULL) {
117!
3947
            scroll_probe.encoder = encoder;
×
3948
            scroll_probe.base_write = sixel_write_callback;
×
3949
            scroll_probe.base_priv = &encoder->outfd;
×
3950
            scroll_callback = sixel_write_with_probe;
×
3951
            scroll_priv = &scroll_probe;
×
3952
        } else {
3953
            scroll_callback = sixel_write_callback;
117✔
3954
            scroll_priv = &encoder->outfd;
117✔
3955
        }
3956
        (void) sixel_tty_scroll(scroll_callback,
162✔
3957
                                scroll_priv,
45✔
3958
                                encoder->outfd,
45✔
3959
                                height,
45✔
3960
                                is_animation);
45✔
3961
    }
45✔
3962

3963
    if (encoder->cancel_flag && *encoder->cancel_flag) {
516!
3964
        status = SIXEL_INTERRUPTED;
×
3965
        goto end;
×
3966
    }
3967

3968
    if (encoder->fdrcs) {  /* -@ option */
516!
3969
        if (encoder->drcs_mmv == 0) {
×
3970
            drcs_is_96cs_param =
×
3971
                (encoder->drcs_charset_no > 63u) ? 1 : 0;
×
3972
            drcs_designate_char =
×
3973
                (int)(((encoder->drcs_charset_no - 1u) % 63u) + 0x40u);
×
3974
        } else if (encoder->drcs_mmv == 1) {
×
3975
            drcs_is_96cs_param = 0;
×
3976
            drcs_designate_char =
×
3977
                (int)(encoder->drcs_charset_no + 0x3fu);
×
3978
        } else {
3979
            drcs_is_96cs_param =
×
3980
                (encoder->drcs_charset_no > 79u) ? 1 : 0;
×
3981
            drcs_designate_char =
×
3982
                (int)(((encoder->drcs_charset_no - 1u) % 79u) + 0x30u);
×
3983
        }
3984
        nwrite = sprintf(buf,
×
3985
                         "%s%s1;0;0;%d;1;3;%d;%d{ %c",
3986
                         (encoder->drcs_mmv > 0) ? (
×
3987
                             encoder->f8bit ? "\233?8800h": "\033[?8800h"
×
3988
                         ): "",
3989
                         encoder->f8bit ? "\220": "\033P",
×
3990
                         encoder->cell_width,
3991
                         encoder->cell_height,
3992
                         drcs_is_96cs_param,
3993
                         drcs_designate_char);
3994
        if (nwrite < 0) {
×
3995
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
3996
            sixel_helper_set_additional_message(
×
3997
                "sixel_encoder_encode_frame: sprintf() failed.");
3998
            goto end;
×
3999
        }
4000
        nwrite = write_callback(buf, nwrite, write_priv);
×
4001
        if (nwrite < 0) {
×
4002
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4003
            sixel_helper_set_additional_message(
×
4004
                "sixel_encoder_encode_frame: write() failed.");
4005
            goto end;
×
4006
        }
4007
    }
4008

4009
    /* output sixel: junction of multi-frame processing strategy */
4010
    if (encoder->fuse_macro) {  /* -u option */
516✔
4011
        /* use macro */
4012
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
90✔
4013
    } else if (encoder->macro_number >= 0) { /* -n option */
462✔
4014
        /* use macro */
4015
        status = sixel_encoder_output_with_macro(frame, dither, output, encoder);
3✔
4016
    } else {
1✔
4017
        /* do not use macro */
4018
        status = sixel_encoder_output_without_macro(frame, dither, output, encoder);
423✔
4019
    }
4020
    if (SIXEL_FAILED(status)) {
516!
4021
        goto end;
×
4022
    }
4023

4024
    if (encoder->cancel_flag && *encoder->cancel_flag) {
516!
4025
        nwrite = write_callback("\x18\033\\", 3, write_priv);
×
4026
        if (nwrite < 0) {
×
4027
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4028
            sixel_helper_set_additional_message(
×
4029
                "sixel_encoder_encode_frame: write_callback() failed.");
4030
            goto end;
×
4031
        }
4032
        status = SIXEL_INTERRUPTED;
×
4033
    }
4034
    if (SIXEL_FAILED(status)) {
516!
4035
        goto end;
×
4036
    }
4037

4038
    if (encoder->fdrcs) {  /* -@ option */
516!
4039
        if (encoder->f8bit) {
×
4040
            nwrite = write_callback("\234", 1, write_priv);
×
4041
        } else {
4042
            nwrite = write_callback("\033\\", 2, write_priv);
×
4043
        }
4044
        if (nwrite < 0) {
×
4045
            status = (SIXEL_LIBC_ERROR | (errno & 0xff));
×
4046
            sixel_helper_set_additional_message(
×
4047
                "sixel_encoder_encode_frame: write_callback() failed.");
4048
            goto end;
×
4049
        }
4050

4051
        if (encoder->tile_outfd >= 0) {
×
4052
            if (encoder->drcs_mmv == 0) {
×
4053
                status = sixel_encoder_emit_iso2022_chars(encoder, frame);
×
4054
                if (SIXEL_FAILED(status)) {
×
4055
                    goto end;
×
4056
                }
4057
            } else {
4058
                status = sixel_encoder_emit_drcsmmv2_chars(encoder, frame);
×
4059
                if (SIXEL_FAILED(status)) {
×
4060
                    goto end;
×
4061
                }
4062
            }
4063
        }
4064
    }
4065

4066

4067
end:
338✔
4068
    if (output) {
530✔
4069
        sixel_output_unref(output);
516✔
4070
    }
178✔
4071
    if (dither) {
530✔
4072
        sixel_dither_unref(dither);
516✔
4073
    }
178✔
4074

4075
    return status;
530✔
4076
}
4077

4078

4079
/* create encoder object */
4080
SIXELAPI SIXELSTATUS
4081
sixel_encoder_new(
513✔
4082
    sixel_encoder_t     /* out */ **ppencoder, /* encoder object to be created */
4083
    sixel_allocator_t   /* in */  *allocator)  /* allocator, null if you use
4084
                                                  default allocator */
4085
{
4086
    SIXELSTATUS status = SIXEL_FALSE;
513✔
4087
    char const *env_default_bgcolor = NULL;
513✔
4088
    char const *env_default_ncolors = NULL;
513✔
4089
    int ncolors;
4090
#if HAVE__DUPENV_S
4091
    errno_t e;
4092
    size_t len;
4093
#endif  /* HAVE__DUPENV_S */
4094

4095
    if (allocator == NULL) {
513!
4096
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
513✔
4097
        if (SIXEL_FAILED(status)) {
513!
4098
            goto end;
×
4099
        }
4100
    } else {
171✔
4101
        sixel_allocator_ref(allocator);
×
4102
    }
4103

4104
    *ppencoder
171✔
4105
        = (sixel_encoder_t *)sixel_allocator_malloc(allocator,
684✔
4106
                                                    sizeof(sixel_encoder_t));
4107
    if (*ppencoder == NULL) {
513!
4108
        sixel_helper_set_additional_message(
×
4109
            "sixel_encoder_new: sixel_allocator_malloc() failed.");
4110
        status = SIXEL_BAD_ALLOCATION;
×
4111
        sixel_allocator_unref(allocator);
×
4112
        goto end;
×
4113
    }
4114

4115
    (*ppencoder)->ref                   = 1;
513✔
4116
    (*ppencoder)->reqcolors             = (-1);
513✔
4117
    (*ppencoder)->force_palette         = 0;
513✔
4118
    (*ppencoder)->mapfile               = NULL;
513✔
4119
    (*ppencoder)->palette_output        = NULL;
513✔
4120
    (*ppencoder)->loader_order          = NULL;
513✔
4121
    (*ppencoder)->color_option          = SIXEL_COLOR_OPTION_DEFAULT;
513✔
4122
    (*ppencoder)->builtin_palette       = 0;
513✔
4123
    (*ppencoder)->method_for_diffuse    = SIXEL_DIFFUSE_AUTO;
513✔
4124
    (*ppencoder)->method_for_scan       = SIXEL_SCAN_AUTO;
513✔
4125
    (*ppencoder)->method_for_carry      = SIXEL_CARRY_AUTO;
513✔
4126
    (*ppencoder)->method_for_largest    = SIXEL_LARGE_AUTO;
513✔
4127
    (*ppencoder)->method_for_rep        = SIXEL_REP_AUTO;
513✔
4128
    (*ppencoder)->quality_mode          = SIXEL_QUALITY_AUTO;
513✔
4129
    (*ppencoder)->lut_policy            = SIXEL_LUT_POLICY_AUTO;
513✔
4130
    (*ppencoder)->method_for_resampling = SIXEL_RES_BILINEAR;
513✔
4131
    (*ppencoder)->loop_mode             = SIXEL_LOOP_AUTO;
513✔
4132
    (*ppencoder)->palette_type          = SIXEL_PALETTETYPE_AUTO;
513✔
4133
    (*ppencoder)->f8bit                 = 0;
513✔
4134
    (*ppencoder)->has_gri_arg_limit     = 0;
513✔
4135
    (*ppencoder)->finvert               = 0;
513✔
4136
    (*ppencoder)->fuse_macro            = 0;
513✔
4137
    (*ppencoder)->fdrcs                 = 0;
513✔
4138
    (*ppencoder)->fignore_delay         = 0;
513✔
4139
    (*ppencoder)->complexion            = 1;
513✔
4140
    (*ppencoder)->fstatic               = 0;
513✔
4141
    (*ppencoder)->cell_width            = 0;
513✔
4142
    (*ppencoder)->cell_height           = 0;
513✔
4143
    (*ppencoder)->pixelwidth            = (-1);
513✔
4144
    (*ppencoder)->pixelheight           = (-1);
513✔
4145
    (*ppencoder)->percentwidth          = (-1);
513✔
4146
    (*ppencoder)->percentheight         = (-1);
513✔
4147
    (*ppencoder)->clipx                 = 0;
513✔
4148
    (*ppencoder)->clipy                 = 0;
513✔
4149
    (*ppencoder)->clipwidth             = 0;
513✔
4150
    (*ppencoder)->clipheight            = 0;
513✔
4151
    (*ppencoder)->clipfirst             = 0;
513✔
4152
    (*ppencoder)->macro_number          = (-1);
513✔
4153
    (*ppencoder)->verbose               = 0;
513✔
4154
    (*ppencoder)->penetrate_multiplexer = 0;
513✔
4155
    (*ppencoder)->encode_policy         = SIXEL_ENCODEPOLICY_AUTO;
513✔
4156
    (*ppencoder)->working_colorspace    = SIXEL_COLORSPACE_GAMMA;
513✔
4157
    (*ppencoder)->output_colorspace     = SIXEL_COLORSPACE_GAMMA;
513✔
4158
    (*ppencoder)->ormode                = 0;
513✔
4159
    (*ppencoder)->pipe_mode             = 0;
513✔
4160
    (*ppencoder)->bgcolor               = NULL;
513✔
4161
    (*ppencoder)->outfd                 = STDOUT_FILENO;
513✔
4162
    (*ppencoder)->tile_outfd            = (-1);
513✔
4163
    (*ppencoder)->finsecure             = 0;
513✔
4164
    (*ppencoder)->cancel_flag           = NULL;
513✔
4165
    (*ppencoder)->dither_cache          = NULL;
513✔
4166
    (*ppencoder)->drcs_charset_no       = 1u;
513✔
4167
    (*ppencoder)->drcs_mmv              = 2;
513✔
4168
    (*ppencoder)->capture_quantized     = 0;
513✔
4169
    (*ppencoder)->capture_source        = 0;
513✔
4170
    (*ppencoder)->capture_pixels        = NULL;
513✔
4171
    (*ppencoder)->capture_pixels_size   = 0;
513✔
4172
    (*ppencoder)->capture_palette       = NULL;
513✔
4173
    (*ppencoder)->capture_palette_size  = 0;
513✔
4174
    (*ppencoder)->capture_pixel_bytes   = 0;
513✔
4175
    (*ppencoder)->capture_width         = 0;
513✔
4176
    (*ppencoder)->capture_height        = 0;
513✔
4177
    (*ppencoder)->capture_pixelformat   = SIXEL_PIXELFORMAT_RGB888;
513✔
4178
    (*ppencoder)->capture_colorspace    = SIXEL_COLORSPACE_GAMMA;
513✔
4179
    (*ppencoder)->capture_ncolors       = 0;
513✔
4180
    (*ppencoder)->capture_valid         = 0;
513✔
4181
    (*ppencoder)->capture_source_frame  = NULL;
513✔
4182
    (*ppencoder)->assessment_observer   = NULL;
513✔
4183
    (*ppencoder)->last_loader_name[0]   = '\0';
513✔
4184
    (*ppencoder)->last_source_path[0]   = '\0';
513✔
4185
    (*ppencoder)->last_input_bytes      = 0u;
513✔
4186
    (*ppencoder)->allocator             = allocator;
513✔
4187

4188
    /* evaluate environment variable ${SIXEL_BGCOLOR} */
4189
#if HAVE__DUPENV_S
4190
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_BGCOLOR");
4191
    if (e != (0)) {
4192
        sixel_helper_set_additional_message(
4193
            "failed to get environment variable $SIXEL_BGCOLOR.");
4194
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4195
    }
4196
#else
4197
    env_default_bgcolor = getenv("SIXEL_BGCOLOR");
513✔
4198
#endif  /* HAVE__DUPENV_S */
4199
    if (env_default_bgcolor != NULL) {
513!
4200
        status = sixel_parse_x_colorspec(&(*ppencoder)->bgcolor,
×
4201
                                         env_default_bgcolor,
4202
                                         allocator);
4203
        if (SIXEL_FAILED(status)) {
×
4204
            goto error;
×
4205
        }
4206
    }
4207

4208
    /* evaluate environment variable ${SIXEL_COLORS} */
4209
#if HAVE__DUPENV_S
4210
    e = _dupenv_s(&env_default_bgcolor, &len, "SIXEL_COLORS");
4211
    if (e != (0)) {
4212
        sixel_helper_set_additional_message(
4213
            "failed to get environment variable $SIXEL_COLORS.");
4214
        return (SIXEL_LIBC_ERROR | (e & 0xff));
4215
    }
4216
#else
4217
    env_default_ncolors = getenv("SIXEL_COLORS");
513✔
4218
#endif  /* HAVE__DUPENV_S */
4219
    if (env_default_ncolors) {
513!
4220
        ncolors = atoi(env_default_ncolors); /* may overflow */
×
4221
        if (ncolors > 1 && ncolors <= SIXEL_PALETTE_MAX) {
×
4222
            (*ppencoder)->reqcolors = ncolors;
×
4223
        }
4224
    }
4225

4226
    /* success */
4227
    status = SIXEL_OK;
513✔
4228

4229
    goto end;
513✔
4230

4231
error:
4232
    sixel_allocator_free(allocator, *ppencoder);
×
4233
    sixel_allocator_unref(allocator);
×
4234
    *ppencoder = NULL;
×
4235

4236
end:
342✔
4237
#if HAVE__DUPENV_S
4238
    free(env_default_bgcolor);
4239
    free(env_default_ncolors);
4240
#endif  /* HAVE__DUPENV_S */
4241
    return status;
513✔
4242
}
4243

4244

4245
/* create encoder object (deprecated version) */
4246
SIXELAPI /* deprecated */ sixel_encoder_t *
4247
sixel_encoder_create(void)
×
4248
{
4249
    SIXELSTATUS status = SIXEL_FALSE;
×
4250
    sixel_encoder_t *encoder = NULL;
×
4251

4252
    status = sixel_encoder_new(&encoder, NULL);
×
4253
    if (SIXEL_FAILED(status)) {
×
4254
        return NULL;
×
4255
    }
4256

4257
    return encoder;
×
4258
}
4259

4260

4261
/* destroy encoder object */
4262
static void
4263
sixel_encoder_destroy(sixel_encoder_t *encoder)
513✔
4264
{
4265
    sixel_allocator_t *allocator;
4266

4267
    if (encoder) {
513!
4268
        allocator = encoder->allocator;
513✔
4269
        sixel_allocator_free(allocator, encoder->mapfile);
513✔
4270
        sixel_allocator_free(allocator, encoder->palette_output);
513✔
4271
        sixel_allocator_free(allocator, encoder->loader_order);
513✔
4272
        sixel_allocator_free(allocator, encoder->bgcolor);
513✔
4273
        sixel_dither_unref(encoder->dither_cache);
513✔
4274
        if (encoder->outfd
522!
4275
            && encoder->outfd != STDOUT_FILENO
513!
4276
            && encoder->outfd != STDERR_FILENO) {
189!
4277
#if HAVE__CLOSE
4278
            (void) _close(encoder->outfd);
4279
#else
4280
            (void) close(encoder->outfd);
27✔
4281
#endif  /* HAVE__CLOSE */
4282
        }
9✔
4283
        if (encoder->tile_outfd >= 0
513!
4284
            && encoder->tile_outfd != encoder->outfd
171!
4285
            && encoder->tile_outfd != STDOUT_FILENO
×
4286
            && encoder->tile_outfd != STDERR_FILENO) {
×
4287
#if HAVE__CLOSE
4288
            (void) _close(encoder->tile_outfd);
4289
#else
4290
            (void) close(encoder->tile_outfd);
×
4291
#endif  /* HAVE__CLOSE */
4292
        }
4293
        if (encoder->capture_source_frame != NULL) {
513!
4294
            sixel_frame_unref(encoder->capture_source_frame);
×
4295
        }
4296
        sixel_allocator_free(allocator, encoder->capture_pixels);
513✔
4297
        sixel_allocator_free(allocator, encoder->capture_palette);
513✔
4298
        sixel_allocator_free(allocator, encoder);
513✔
4299
        sixel_allocator_unref(allocator);
513✔
4300
    }
171✔
4301
}
513✔
4302

4303

4304
/* increase reference count of encoder object (thread-unsafe) */
4305
SIXELAPI void
4306
sixel_encoder_ref(sixel_encoder_t *encoder)
1,116✔
4307
{
4308
    /* TODO: be thread safe */
4309
    ++encoder->ref;
1,116✔
4310
}
1,116✔
4311

4312

4313
/* decrease reference count of encoder object (thread-unsafe) */
4314
SIXELAPI void
4315
sixel_encoder_unref(sixel_encoder_t *encoder)
1,629✔
4316
{
4317
    /* TODO: be thread safe */
4318
    if (encoder != NULL && --encoder->ref == 0) {
1,629!
4319
        sixel_encoder_destroy(encoder);
513✔
4320
    }
171✔
4321
}
1,629✔
4322

4323

4324
/* set cancel state flag to encoder object */
4325
SIXELAPI SIXELSTATUS
4326
sixel_encoder_set_cancel_flag(
429✔
4327
    sixel_encoder_t /* in */ *encoder,
4328
    int             /* in */ *cancel_flag
4329
)
4330
{
4331
    SIXELSTATUS status = SIXEL_OK;
429✔
4332

4333
    encoder->cancel_flag = cancel_flag;
429✔
4334

4335
    return status;
429✔
4336
}
4337

4338

4339
/* set an option flag to encoder object */
4340
SIXELAPI SIXELSTATUS
4341
sixel_encoder_setopt(
687✔
4342
    sixel_encoder_t /* in */ *encoder,
4343
    int             /* in */ arg,
4344
    char const      /* in */ *value)
4345
{
4346
    SIXELSTATUS status = SIXEL_FALSE;
687✔
4347
    int number;
4348
    int parsed;
4349
    char unit[32];
4350
    char lowered[16];
4351
    size_t len;
4352
    size_t i;
4353
    long parsed_reqcolors;
4354
    char *endptr;
4355
    int forced_palette;
4356
    char *opt_copy;
4357
    char const *drcs_arg_delim;
4358
    char const *drcs_arg_charset;
4359
    char const *drcs_arg_second_delim;
4360
    char const *drcs_arg_path;
4361
    size_t drcs_arg_path_length;
4362
    size_t drcs_segment_length;
4363
    char drcs_segment[32];
4364
    int drcs_mmv_value;
4365
    long drcs_charset_value;
4366
    unsigned int drcs_charset_limit;
4367
    sixel_option_choice_result_t match_result;
4368
    int match_value;
4369
    char match_detail[128];
4370
    char match_message[256];
4371

4372
    sixel_encoder_ref(encoder);
687✔
4373
    opt_copy = NULL;
687✔
4374

4375
    switch(arg) {
687!
4376
    case SIXEL_OPTFLAG_OUTFILE:  /* o */
18✔
4377
        if (*value == '\0') {
27!
4378
            sixel_helper_set_additional_message(
×
4379
                "no file name specified.");
4380
            status = SIXEL_BAD_ARGUMENT;
×
4381
            goto end;
×
4382
        }
4383
        if (strcmp(value, "-") != 0) {
27!
4384
            if (encoder->outfd && encoder->outfd != STDOUT_FILENO) {
27!
4385
#if HAVE__CLOSE
4386
                (void) _close(encoder->outfd);
4387
#else
4388
                (void) close(encoder->outfd);
×
4389
#endif  /* HAVE__CLOSE */
4390
            }
4391
#if HAVE__OPEN
4392
            encoder->outfd = _open(value,
4393
                                   O_RDWR|O_CREAT|O_TRUNC,
4394
                                   S_IRUSR|S_IWUSR);
4395
#else
4396
            encoder->outfd = open(value,
27✔
4397
                                  O_RDWR|O_CREAT|O_TRUNC,
4398
                                  S_IRUSR|S_IWUSR);
4399
#endif  /* HAVE__OPEN */
4400
        }
9✔
4401
        break;
27✔
4402
    case SIXEL_OPTFLAG_7BIT_MODE:  /* 7 */
10✔
4403
        encoder->f8bit = 0;
15✔
4404
        break;
15✔
4405
    case SIXEL_OPTFLAG_8BIT_MODE:  /* 8 */
12✔
4406
        encoder->f8bit = 1;
18✔
4407
        break;
18✔
4408
    case SIXEL_OPTFLAG_HAS_GRI_ARG_LIMIT:  /* R */
4409
        encoder->has_gri_arg_limit = 1;
×
4410
        break;
×
4411
    case SIXEL_OPTFLAG_COLORS:  /* p */
18✔
4412
        forced_palette = 0;
27✔
4413
        errno = 0;
27✔
4414
        endptr = NULL;
27✔
4415
        if (*value == '!' && value[1] == '\0') {
27!
4416
            /*
4417
             * Force the default palette size even when the median cut
4418
             * finished early.
4419
             *
4420
             *   requested colors
4421
             *          |
4422
             *          v
4423
             *        [ 256 ]  <--- "-p!" triggers this shortcut
4424
             */
4425
            parsed_reqcolors = SIXEL_PALETTE_MAX;
×
4426
            forced_palette = 1;
×
4427
        } else {
4428
            parsed_reqcolors = strtol(value, &endptr, 10);
27✔
4429
            if (endptr != NULL && *endptr == '!') {
27!
4430
                forced_palette = 1;
×
4431
                ++endptr;
×
4432
            }
4433
            if (errno == ERANGE || endptr == value) {
27!
4434
                sixel_helper_set_additional_message(
×
4435
                    "cannot parse -p/--colors option.");
4436
                status = SIXEL_BAD_ARGUMENT;
×
4437
                goto end;
×
4438
            }
4439
            if (endptr != NULL && *endptr != '\0') {
27!
4440
                sixel_helper_set_additional_message(
×
4441
                    "cannot parse -p/--colors option.");
4442
                status = SIXEL_BAD_ARGUMENT;
×
4443
                goto end;
×
4444
            }
4445
        }
4446
        if (parsed_reqcolors < 1) {
27!
4447
            sixel_helper_set_additional_message(
×
4448
                "-p/--colors parameter must be 1 or more.");
4449
            status = SIXEL_BAD_ARGUMENT;
×
4450
            goto end;
×
4451
        }
4452
        if (parsed_reqcolors > SIXEL_PALETTE_MAX) {
27!
4453
            sixel_helper_set_additional_message(
×
4454
                "-p/--colors parameter must be less then or equal to 256.");
4455
            status = SIXEL_BAD_ARGUMENT;
×
4456
            goto end;
×
4457
        }
4458
        encoder->reqcolors = (int)parsed_reqcolors;
27✔
4459
        encoder->force_palette = forced_palette;
27✔
4460
        break;
27✔
4461
    case SIXEL_OPTFLAG_MAPFILE:  /* m */
18✔
4462
        if (encoder->mapfile) {
27✔
4463
            sixel_allocator_free(encoder->allocator, encoder->mapfile);
3✔
4464
        }
1✔
4465
        encoder->mapfile = arg_strdup(value, encoder->allocator);
27✔
4466
        if (encoder->mapfile == NULL) {
27!
4467
            sixel_helper_set_additional_message(
×
4468
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
4469
            status = SIXEL_BAD_ALLOCATION;
×
4470
            goto end;
×
4471
        }
4472
        encoder->color_option = SIXEL_COLOR_OPTION_MAPFILE;
27✔
4473
        break;
27✔
4474
    case SIXEL_OPTFLAG_MAPFILE_OUTPUT:  /* M */
4475
        if (value == NULL || *value == '\0') {
×
4476
            sixel_helper_set_additional_message(
×
4477
                "sixel_encoder_setopt: mapfile-output path is empty.");
4478
            status = SIXEL_BAD_ARGUMENT;
×
4479
            goto end;
×
4480
        }
4481
        opt_copy = arg_strdup(value, encoder->allocator);
×
4482
        if (opt_copy == NULL) {
×
4483
            sixel_helper_set_additional_message(
×
4484
                "sixel_encoder_setopt: sixel_allocator_malloc() failed.");
4485
            status = SIXEL_BAD_ALLOCATION;
×
4486
            goto end;
×
4487
        }
4488
        status = sixel_encoder_enable_quantized_capture(encoder, 1);
×
4489
        if (SIXEL_FAILED(status)) {
×
4490
            sixel_allocator_free(encoder->allocator, opt_copy);
×
4491
            goto end;
×
4492
        }
4493
        sixel_allocator_free(encoder->allocator, encoder->palette_output);
×
4494
        encoder->palette_output = opt_copy;
×
4495
        opt_copy = NULL;
×
4496
        break;
×
4497
    case SIXEL_OPTFLAG_MONOCHROME:  /* e */
10✔
4498
        encoder->color_option = SIXEL_COLOR_OPTION_MONOCHROME;
15✔
4499
        break;
15✔
4500
    case SIXEL_OPTFLAG_HIGH_COLOR:  /* I */
28✔
4501
        encoder->color_option = SIXEL_COLOR_OPTION_HIGHCOLOR;
42✔
4502
        break;
42✔
4503
    case SIXEL_OPTFLAG_BUILTIN_PALETTE:  /* b */
22✔
4504
        match_result = sixel_match_option_choice(
33✔
4505
            value,
11✔
4506
            g_option_choices_builtin_palette,
4507
            sizeof(g_option_choices_builtin_palette) /
4508
            sizeof(g_option_choices_builtin_palette[0]),
4509
            &match_value,
4510
            match_detail,
11✔
4511
            sizeof(match_detail));
4512
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
33✔
4513
            encoder->builtin_palette = match_value;
30✔
4514
        } else {
10✔
4515
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
4516
                sixel_report_ambiguous_prefix("--builtin-palette",
×
4517
                                              value,
4518
                                              match_detail,
4519
                                              match_message,
4520
                                              sizeof(match_message));
4521
            } else {
4522
                sixel_helper_set_additional_message(
3✔
4523
                    "cannot parse builtin palette option.");
4524
            }
4525
            status = SIXEL_BAD_ARGUMENT;
3✔
4526
            goto end;
3✔
4527
        }
4528
        encoder->color_option = SIXEL_COLOR_OPTION_BUILTIN;
30✔
4529
        break;
30✔
4530
    case SIXEL_OPTFLAG_DIFFUSION:  /* d */
46✔
4531
        match_result = sixel_match_option_choice(
69✔
4532
            value,
23✔
4533
            g_option_choices_diffusion,
4534
            sizeof(g_option_choices_diffusion) /
4535
            sizeof(g_option_choices_diffusion[0]),
4536
            &match_value,
4537
            match_detail,
23✔
4538
            sizeof(match_detail));
4539
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
69✔
4540
            encoder->method_for_diffuse = match_value;
66✔
4541
        } else {
22✔
4542
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
4543
                sixel_report_ambiguous_prefix("--diffusion",
×
4544
                                              value,
4545
                                              match_detail,
4546
                                              match_message,
4547
                                              sizeof(match_message));
4548
            } else {
4549
                sixel_helper_set_additional_message(
3✔
4550
                    "specified diffusion method is not supported.");
4551
            }
4552
            status = SIXEL_BAD_ARGUMENT;
3✔
4553
            goto end;
3✔
4554
        }
4555
        break;
66✔
4556
    case SIXEL_OPTFLAG_DIFFUSION_SCAN:  /* y */
NEW
4557
        match_result = sixel_match_option_choice(
×
4558
            value,
4559
            g_option_choices_diffusion_scan,
4560
            sizeof(g_option_choices_diffusion_scan) /
4561
            sizeof(g_option_choices_diffusion_scan[0]),
4562
            &match_value,
4563
            match_detail,
4564
            sizeof(match_detail));
NEW
4565
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
NEW
4566
            encoder->method_for_scan = match_value;
×
4567
        } else {
NEW
4568
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
4569
                sixel_report_ambiguous_prefix("--diffusion-scan",
×
4570
                                              value,
4571
                                              match_detail,
4572
                                              match_message,
4573
                                              sizeof(match_message));
4574
            } else {
NEW
4575
                sixel_helper_set_additional_message(
×
4576
                    "specified diffusion scan is not supported.");
4577
            }
4578
            status = SIXEL_BAD_ARGUMENT;
×
4579
            goto end;
×
4580
        }
4581
        break;
×
4582
    case SIXEL_OPTFLAG_DIFFUSION_CARRY:  /* Y */
NEW
4583
        match_result = sixel_match_option_choice(
×
4584
            value,
4585
            g_option_choices_diffusion_carry,
4586
            sizeof(g_option_choices_diffusion_carry) /
4587
            sizeof(g_option_choices_diffusion_carry[0]),
4588
            &match_value,
4589
            match_detail,
4590
            sizeof(match_detail));
NEW
4591
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
NEW
4592
            encoder->method_for_carry = match_value;
×
4593
        } else {
NEW
4594
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
4595
                sixel_report_ambiguous_prefix("--diffusion-carry",
×
4596
                                              value,
4597
                                              match_detail,
4598
                                              match_message,
4599
                                              sizeof(match_message));
4600
            } else {
NEW
4601
                sixel_helper_set_additional_message(
×
4602
                    "specified diffusion carry mode is not supported.");
4603
            }
4604
            status = SIXEL_BAD_ARGUMENT;
×
4605
            goto end;
×
4606
        }
4607
        break;
×
4608
    case SIXEL_OPTFLAG_FIND_LARGEST:  /* f */
10✔
4609
        if (value != NULL) {
15!
4610
            match_result = sixel_match_option_choice(
15✔
4611
                value,
5✔
4612
                g_option_choices_find_largest,
4613
                sizeof(g_option_choices_find_largest) /
4614
                sizeof(g_option_choices_find_largest[0]),
4615
                &match_value,
4616
                match_detail,
5✔
4617
                sizeof(match_detail));
4618
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
4619
                encoder->method_for_largest = match_value;
12✔
4620
            } else {
4✔
4621
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
4622
                    sixel_report_ambiguous_prefix("--find-largest",
×
4623
                                                  value,
4624
                                                  match_detail,
4625
                                                  match_message,
4626
                                                  sizeof(match_message));
4627
                } else {
4628
                    sixel_helper_set_additional_message(
3✔
4629
                        "specified finding method is not supported.");
4630
                }
4631
                status = SIXEL_BAD_ARGUMENT;
3✔
4632
                goto end;
3✔
4633
            }
4634
        }
4✔
4635
        break;
12✔
4636
    case SIXEL_OPTFLAG_SELECT_COLOR:  /* s */
14✔
4637
        match_result = sixel_match_option_choice(
21✔
4638
            value,
7✔
4639
            g_option_choices_select_color,
4640
            sizeof(g_option_choices_select_color) /
4641
            sizeof(g_option_choices_select_color[0]),
4642
            &match_value,
4643
            match_detail,
7✔
4644
            sizeof(match_detail));
4645
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
21✔
4646
            encoder->method_for_rep = match_value;
15✔
4647
        } else {
5✔
4648
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
6✔
4649
                sixel_report_ambiguous_prefix("--select-color",
3✔
4650
                                              value,
1✔
4651
                                              match_detail,
1✔
4652
                                              match_message,
1✔
4653
                                              sizeof(match_message));
4654
            } else {
1✔
4655
                sixel_helper_set_additional_message(
3✔
4656
                    "specified finding method is not supported.");
4657
            }
4658
            status = SIXEL_BAD_ARGUMENT;
6✔
4659
            goto end;
6✔
4660
        }
4661
        break;
15✔
4662
    case SIXEL_OPTFLAG_CROP:  /* c */
10✔
4663
#if HAVE_SSCANF_S
4664
        number = sscanf_s(value, "%dx%d+%d+%d",
4665
                          &encoder->clipwidth, &encoder->clipheight,
4666
                          &encoder->clipx, &encoder->clipy);
4667
#else
4668
        number = sscanf(value, "%dx%d+%d+%d",
20✔
4669
                        &encoder->clipwidth, &encoder->clipheight,
5✔
4670
                        &encoder->clipx, &encoder->clipy);
5✔
4671
#endif  /* HAVE_SSCANF_S */
4672
        if (number != 4) {
15!
4673
            status = SIXEL_BAD_ARGUMENT;
×
4674
            goto end;
×
4675
        }
4676
        if (encoder->clipwidth <= 0 || encoder->clipheight <= 0) {
15!
4677
            status = SIXEL_BAD_ARGUMENT;
×
4678
            goto end;
×
4679
        }
4680
        if (encoder->clipx < 0 || encoder->clipy < 0) {
15!
4681
            status = SIXEL_BAD_ARGUMENT;
×
4682
            goto end;
×
4683
        }
4684
        encoder->clipfirst = 0;
15✔
4685
        break;
15✔
4686
    case SIXEL_OPTFLAG_WIDTH:  /* w */
50✔
4687
#if HAVE_SSCANF_S
4688
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
4689
#else
4690
        parsed = sscanf(value, "%d%2s", &number, unit);
75✔
4691
#endif  /* HAVE_SSCANF_S */
4692
        if (parsed == 2 && strcmp(unit, "%") == 0) {
75!
4693
            encoder->pixelwidth = (-1);
12✔
4694
            encoder->percentwidth = number;
12✔
4695
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
67!
4696
            encoder->pixelwidth = number;
51✔
4697
            encoder->percentwidth = (-1);
51✔
4698
        } else if (strcmp(value, "auto") == 0) {
29✔
4699
            encoder->pixelwidth = (-1);
9✔
4700
            encoder->percentwidth = (-1);
9✔
4701
        } else {
3✔
4702
            sixel_helper_set_additional_message(
3✔
4703
                "cannot parse -w/--width option.");
4704
            status = SIXEL_BAD_ARGUMENT;
3✔
4705
            goto end;
3✔
4706
        }
4707
        if (encoder->clipwidth) {
72✔
4708
            encoder->clipfirst = 1;
6✔
4709
        }
2✔
4710
        break;
72✔
4711
    case SIXEL_OPTFLAG_HEIGHT:  /* h */
44✔
4712
#if HAVE_SSCANF_S
4713
        parsed = sscanf_s(value, "%d%2s", &number, unit, sizeof(unit) - 1);
4714
#else
4715
        parsed = sscanf(value, "%d%2s", &number, unit);
66✔
4716
#endif  /* HAVE_SSCANF_S */
4717
        if (parsed == 2 && strcmp(unit, "%") == 0) {
66!
4718
            encoder->pixelheight = (-1);
9✔
4719
            encoder->percentheight = number;
9✔
4720
        } else if (parsed == 1 || (parsed == 2 && strcmp(unit, "px") == 0)) {
60!
4721
            encoder->pixelheight = number;
45✔
4722
            encoder->percentheight = (-1);
45✔
4723
        } else if (strcmp(value, "auto") == 0) {
27✔
4724
            encoder->pixelheight = (-1);
9✔
4725
            encoder->percentheight = (-1);
9✔
4726
        } else {
3✔
4727
            sixel_helper_set_additional_message(
3✔
4728
                "cannot parse -h/--height option.");
4729
            status = SIXEL_BAD_ARGUMENT;
3✔
4730
            goto end;
3✔
4731
        }
4732
        if (encoder->clipheight) {
63✔
4733
            encoder->clipfirst = 1;
3✔
4734
        }
1✔
4735
        break;
63✔
4736
    case SIXEL_OPTFLAG_RESAMPLING:  /* r */
34✔
4737
        match_result = sixel_match_option_choice(
51✔
4738
            value,
17✔
4739
            g_option_choices_resampling,
4740
            sizeof(g_option_choices_resampling) /
4741
            sizeof(g_option_choices_resampling[0]),
4742
            &match_value,
4743
            match_detail,
17✔
4744
            sizeof(match_detail));
4745
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
51✔
4746
            encoder->method_for_resampling = match_value;
48✔
4747
        } else {
16✔
4748
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
4749
                sixel_report_ambiguous_prefix("--resampling",
×
4750
                                              value,
4751
                                              match_detail,
4752
                                              match_message,
4753
                                              sizeof(match_message));
4754
            } else {
4755
                sixel_helper_set_additional_message(
3✔
4756
                    "specified desampling method is not supported.");
4757
            }
4758
            status = SIXEL_BAD_ARGUMENT;
3✔
4759
            goto end;
3✔
4760
        }
4761
        break;
48✔
4762
    case SIXEL_OPTFLAG_QUALITY:  /* q */
12✔
4763
        match_result = sixel_match_option_choice(
18✔
4764
            value,
6✔
4765
            g_option_choices_quality,
4766
            sizeof(g_option_choices_quality) /
4767
            sizeof(g_option_choices_quality[0]),
4768
            &match_value,
4769
            match_detail,
6✔
4770
            sizeof(match_detail));
4771
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
18✔
4772
            encoder->quality_mode = match_value;
15✔
4773
        } else {
5✔
4774
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
4775
                sixel_report_ambiguous_prefix("--quality",
×
4776
                                              value,
4777
                                              match_detail,
4778
                                              match_message,
4779
                                              sizeof(match_message));
4780
            } else {
4781
                sixel_helper_set_additional_message(
3✔
4782
                    "cannot parse quality option.");
4783
            }
4784
            status = SIXEL_BAD_ARGUMENT;
3✔
4785
            goto end;
3✔
4786
        }
4787
        break;
15✔
4788
    case SIXEL_OPTFLAG_LOOPMODE:  /* l */
10✔
4789
        match_result = sixel_match_option_choice(
15✔
4790
            value,
5✔
4791
            g_option_choices_loopmode,
4792
            sizeof(g_option_choices_loopmode) /
4793
            sizeof(g_option_choices_loopmode[0]),
4794
            &match_value,
4795
            match_detail,
5✔
4796
            sizeof(match_detail));
4797
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
15✔
4798
            encoder->loop_mode = match_value;
12✔
4799
        } else {
4✔
4800
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
4801
                sixel_report_ambiguous_prefix("--loop-control",
×
4802
                                              value,
4803
                                              match_detail,
4804
                                              match_message,
4805
                                              sizeof(match_message));
4806
            } else {
4807
                sixel_helper_set_additional_message(
3✔
4808
                    "cannot parse loop-control option.");
4809
            }
4810
            status = SIXEL_BAD_ARGUMENT;
3✔
4811
            goto end;
3✔
4812
        }
4813
        break;
12✔
4814
    case SIXEL_OPTFLAG_PALETTE_TYPE:  /* t */
18✔
4815
        match_result = sixel_match_option_choice(
27✔
4816
            value,
9✔
4817
            g_option_choices_palette_type,
4818
            sizeof(g_option_choices_palette_type) /
4819
            sizeof(g_option_choices_palette_type[0]),
4820
            &match_value,
4821
            match_detail,
9✔
4822
            sizeof(match_detail));
4823
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
27✔
4824
            encoder->palette_type = match_value;
24✔
4825
        } else {
8✔
4826
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
4827
                sixel_report_ambiguous_prefix("--palette-type",
×
4828
                                              value,
4829
                                              match_detail,
4830
                                              match_message,
4831
                                              sizeof(match_message));
4832
            } else {
4833
                sixel_helper_set_additional_message(
3✔
4834
                    "cannot parse palette type option.");
4835
            }
4836
            status = SIXEL_BAD_ARGUMENT;
3✔
4837
            goto end;
3✔
4838
        }
4839
        break;
24✔
4840
    case SIXEL_OPTFLAG_BGCOLOR:  /* B */
30✔
4841
        /* parse --bgcolor option */
4842
        if (encoder->bgcolor) {
45✔
4843
            sixel_allocator_free(encoder->allocator, encoder->bgcolor);
6✔
4844
            encoder->bgcolor = NULL;
6✔
4845
        }
2✔
4846
        status = sixel_parse_x_colorspec(&encoder->bgcolor,
60✔
4847
                                         value,
15✔
4848
                                         encoder->allocator);
15✔
4849
        if (SIXEL_FAILED(status)) {
45✔
4850
            sixel_helper_set_additional_message(
21✔
4851
                "cannot parse bgcolor option.");
4852
            status = SIXEL_BAD_ARGUMENT;
21✔
4853
            goto end;
21✔
4854
        }
4855
        break;
24✔
4856
    case SIXEL_OPTFLAG_INSECURE:  /* k */
4857
        encoder->finsecure = 1;
×
4858
        break;
×
4859
    case SIXEL_OPTFLAG_INVERT:  /* i */
4✔
4860
        encoder->finvert = 1;
6✔
4861
        break;
6✔
4862
    case SIXEL_OPTFLAG_USE_MACRO:  /* u */
4✔
4863
        encoder->fuse_macro = 1;
6✔
4864
        break;
6✔
4865
    case SIXEL_OPTFLAG_MACRO_NUMBER:  /* n */
2✔
4866
        encoder->macro_number = atoi(value);
3✔
4867
        if (encoder->macro_number < 0) {
3!
4868
            status = SIXEL_BAD_ARGUMENT;
×
4869
            goto end;
×
4870
        }
4871
        break;
3✔
4872
    case SIXEL_OPTFLAG_IGNORE_DELAY:  /* g */
4✔
4873
        encoder->fignore_delay = 1;
6✔
4874
        break;
6✔
4875
    case SIXEL_OPTFLAG_VERBOSE:  /* v */
6✔
4876
        encoder->verbose = 1;
9✔
4877
        sixel_helper_set_loader_trace(1);
9✔
4878
        break;
9✔
4879
    case SIXEL_OPTFLAG_LOADERS:  /* J */
4880
        if (encoder->loader_order != NULL) {
×
4881
            sixel_allocator_free(encoder->allocator,
×
4882
                                 encoder->loader_order);
×
4883
            encoder->loader_order = NULL;
×
4884
        }
4885
        if (value != NULL && *value != '\0') {
×
4886
            encoder->loader_order = arg_strdup(value,
×
4887
                                               encoder->allocator);
4888
            if (encoder->loader_order == NULL) {
×
4889
                sixel_helper_set_additional_message(
×
4890
                    "sixel_encoder_setopt: "
4891
                    "sixel_allocator_malloc() failed.");
4892
                status = SIXEL_BAD_ALLOCATION;
×
4893
                goto end;
×
4894
            }
4895
        }
4896
        break;
×
4897
    case SIXEL_OPTFLAG_STATIC:  /* S */
2✔
4898
        encoder->fstatic = 1;
3✔
4899
        break;
3✔
4900
    case SIXEL_OPTFLAG_DRCS:  /* @ */
4901
        encoder->fdrcs = 1;
×
4902
        drcs_arg_delim = NULL;
×
4903
        drcs_arg_charset = NULL;
×
4904
        drcs_arg_second_delim = NULL;
×
4905
        drcs_arg_path = NULL;
×
4906
        drcs_arg_path_length = 0u;
×
4907
        drcs_segment_length = 0u;
×
4908
        drcs_mmv_value = 2;
×
4909
        drcs_charset_value = 1L;
×
4910
        drcs_charset_limit = 0u;
×
4911
        if (value != NULL && *value != '\0') {
×
4912
            drcs_arg_delim = strchr(value, ':');
×
4913
            if (drcs_arg_delim == NULL) {
×
4914
                drcs_segment_length = strlen(value);
×
4915
                if (drcs_segment_length >= sizeof(drcs_segment)) {
×
4916
                    sixel_helper_set_additional_message(
×
4917
                        "DRCS mapping revision is too long.");
4918
                    status = SIXEL_BAD_ARGUMENT;
×
4919
                    goto end;
×
4920
                }
4921
                memcpy(drcs_segment, value, drcs_segment_length);
×
4922
                drcs_segment[drcs_segment_length] = '\0';
×
4923
                errno = 0;
×
4924
                endptr = NULL;
×
4925
                drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
4926
                if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
4927
                    sixel_helper_set_additional_message(
×
4928
                        "cannot parse DRCS option.");
4929
                    status = SIXEL_BAD_ARGUMENT;
×
4930
                    goto end;
×
4931
                }
4932
            } else {
4933
                if (drcs_arg_delim != value) {
×
4934
                    drcs_segment_length =
×
4935
                        (size_t)(drcs_arg_delim - value);
×
4936
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
4937
                        sixel_helper_set_additional_message(
×
4938
                            "DRCS mapping revision is too long.");
4939
                        status = SIXEL_BAD_ARGUMENT;
×
4940
                        goto end;
×
4941
                    }
4942
                    memcpy(drcs_segment, value, drcs_segment_length);
×
4943
                    drcs_segment[drcs_segment_length] = '\0';
×
4944
                    errno = 0;
×
4945
                    endptr = NULL;
×
4946
                    drcs_mmv_value = (int)strtol(drcs_segment, &endptr, 10);
×
4947
                    if (errno != 0 || endptr == drcs_segment || *endptr != '\0') {
×
4948
                        sixel_helper_set_additional_message(
×
4949
                            "cannot parse DRCS option.");
4950
                        status = SIXEL_BAD_ARGUMENT;
×
4951
                        goto end;
×
4952
                    }
4953
                }
4954
                drcs_arg_charset = drcs_arg_delim + 1;
×
4955
                drcs_arg_second_delim = strchr(drcs_arg_charset, ':');
×
4956
                if (drcs_arg_second_delim != NULL) {
×
4957
                    if (drcs_arg_second_delim != drcs_arg_charset) {
×
4958
                        drcs_segment_length =
×
4959
                            (size_t)(drcs_arg_second_delim - drcs_arg_charset);
×
4960
                        if (drcs_segment_length >= sizeof(drcs_segment)) {
×
4961
                            sixel_helper_set_additional_message(
×
4962
                                "DRCS charset number is too long.");
4963
                            status = SIXEL_BAD_ARGUMENT;
×
4964
                            goto end;
×
4965
                        }
4966
                        memcpy(drcs_segment,
×
4967
                               drcs_arg_charset,
4968
                               drcs_segment_length);
4969
                        drcs_segment[drcs_segment_length] = '\0';
×
4970
                        errno = 0;
×
4971
                        endptr = NULL;
×
4972
                        drcs_charset_value = strtol(drcs_segment,
×
4973
                                                    &endptr,
4974
                                                    10);
4975
                        if (errno != 0 || endptr == drcs_segment ||
×
4976
                                *endptr != '\0') {
×
4977
                            sixel_helper_set_additional_message(
×
4978
                                "cannot parse DRCS charset number.");
4979
                            status = SIXEL_BAD_ARGUMENT;
×
4980
                            goto end;
×
4981
                        }
4982
                    }
4983
                    drcs_arg_path = drcs_arg_second_delim + 1;
×
4984
                    drcs_arg_path_length = strlen(drcs_arg_path);
×
4985
                    if (drcs_arg_path_length == 0u) {
×
4986
                        drcs_arg_path = NULL;
×
4987
                    }
4988
                } else if (*drcs_arg_charset != '\0') {
×
4989
                    drcs_segment_length = strlen(drcs_arg_charset);
×
4990
                    if (drcs_segment_length >= sizeof(drcs_segment)) {
×
4991
                        sixel_helper_set_additional_message(
×
4992
                            "DRCS charset number is too long.");
4993
                        status = SIXEL_BAD_ARGUMENT;
×
4994
                        goto end;
×
4995
                    }
4996
                    memcpy(drcs_segment,
×
4997
                           drcs_arg_charset,
4998
                           drcs_segment_length);
4999
                    drcs_segment[drcs_segment_length] = '\0';
×
5000
                    errno = 0;
×
5001
                    endptr = NULL;
×
5002
                    drcs_charset_value = strtol(drcs_segment,
×
5003
                                                &endptr,
5004
                                                10);
5005
                    if (errno != 0 || endptr == drcs_segment ||
×
5006
                            *endptr != '\0') {
×
5007
                        sixel_helper_set_additional_message(
×
5008
                            "cannot parse DRCS charset number.");
5009
                        status = SIXEL_BAD_ARGUMENT;
×
5010
                        goto end;
×
5011
                    }
5012
                }
5013
            }
5014
        }
5015
        /*
5016
         * Layout of the DRCS option value:
5017
         *
5018
         *    value = <mmv>:<charset_no>:<path>
5019
         *          ^        ^                ^
5020
         *          |        |                |
5021
         *          |        |                +-- optional path that may reuse
5022
         *          |        |                    STDOUT when set to "-" or drop
5023
         *          |        |                    tiles when left blank
5024
         *          |        +-- charset number (defaults to 1 when omitted)
5025
         *          +-- mapping revision (defaults to 2 when omitted)
5026
         */
5027
        if (drcs_mmv_value < 0 || drcs_mmv_value > 2) {
×
5028
            sixel_helper_set_additional_message(
×
5029
                "unknown DRCS unicode mapping version.");
5030
            status = SIXEL_BAD_ARGUMENT;
×
5031
            goto end;
×
5032
        }
5033
        if (drcs_mmv_value == 0) {
×
5034
            drcs_charset_limit = 126u;
×
5035
        } else if (drcs_mmv_value == 1) {
×
5036
            drcs_charset_limit = 63u;
×
5037
        } else {
5038
            drcs_charset_limit = 158u;
×
5039
        }
5040
        if (drcs_charset_value < 1 ||
×
5041
            (unsigned long)drcs_charset_value > drcs_charset_limit) {
×
5042
            sixel_helper_set_additional_message(
×
5043
                "DRCS charset number is out of range.");
5044
            status = SIXEL_BAD_ARGUMENT;
×
5045
            goto end;
×
5046
        }
5047
        encoder->drcs_mmv = drcs_mmv_value;
×
5048
        encoder->drcs_charset_no = (unsigned short)drcs_charset_value;
×
5049
        if (encoder->tile_outfd >= 0
×
5050
            && encoder->tile_outfd != encoder->outfd
×
5051
            && encoder->tile_outfd != STDOUT_FILENO
×
5052
            && encoder->tile_outfd != STDERR_FILENO) {
×
5053
#if HAVE__CLOSE
5054
            (void) _close(encoder->tile_outfd);
5055
#else
5056
            (void) close(encoder->tile_outfd);
×
5057
#endif  /* HAVE__CLOSE */
5058
        }
5059
        encoder->tile_outfd = (-1);
×
5060
        if (drcs_arg_path != NULL) {
×
5061
            if (strcmp(drcs_arg_path, "-") == 0) {
×
5062
                encoder->tile_outfd = STDOUT_FILENO;
×
5063
            } else {
5064
#if HAVE__OPEN
5065
                encoder->tile_outfd = _open(drcs_arg_path,
5066
                                            O_RDWR|O_CREAT|O_TRUNC,
5067
                                            S_IRUSR|S_IWUSR);
5068
#else
5069
                encoder->tile_outfd = open(drcs_arg_path,
×
5070
                                           O_RDWR|O_CREAT|O_TRUNC,
5071
                                           S_IRUSR|S_IWUSR);
5072
#endif  /* HAVE__OPEN */
5073
                if (encoder->tile_outfd < 0) {
×
5074
                    sixel_helper_set_additional_message(
×
5075
                        "sixel_encoder_setopt: failed to open tile"
5076
                        " output path.");
5077
                    status = SIXEL_RUNTIME_ERROR;
×
5078
                    goto end;
×
5079
                }
5080
            }
5081
        }
5082
        break;
×
5083
    case SIXEL_OPTFLAG_PENETRATE:  /* P */
6✔
5084
        encoder->penetrate_multiplexer = 1;
9✔
5085
        break;
9✔
5086
    case SIXEL_OPTFLAG_ENCODE_POLICY:  /* E */
8✔
5087
        match_result = sixel_match_option_choice(
12✔
5088
            value,
4✔
5089
            g_option_choices_encode_policy,
5090
            sizeof(g_option_choices_encode_policy) /
5091
            sizeof(g_option_choices_encode_policy[0]),
5092
            &match_value,
5093
            match_detail,
4✔
5094
            sizeof(match_detail));
5095
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
12✔
5096
            encoder->encode_policy = match_value;
9✔
5097
        } else {
3✔
5098
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
3!
NEW
5099
                sixel_report_ambiguous_prefix("--encode-policy",
×
5100
                                              value,
5101
                                              match_detail,
5102
                                              match_message,
5103
                                              sizeof(match_message));
5104
            } else {
5105
                sixel_helper_set_additional_message(
3✔
5106
                    "cannot parse encode policy option.");
5107
            }
5108
            status = SIXEL_BAD_ARGUMENT;
3✔
5109
            goto end;
3✔
5110
        }
5111
        break;
9✔
5112
    case SIXEL_OPTFLAG_LUT_POLICY:  /* L */
NEW
5113
        match_result = sixel_match_option_choice(
×
5114
            value,
5115
            g_option_choices_lut_policy,
5116
            sizeof(g_option_choices_lut_policy) /
5117
            sizeof(g_option_choices_lut_policy[0]),
5118
            &match_value,
5119
            match_detail,
5120
            sizeof(match_detail));
NEW
5121
        if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
NEW
5122
            encoder->lut_policy = match_value;
×
5123
        } else {
NEW
5124
            if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
5125
                sixel_report_ambiguous_prefix("--lut-policy",
×
5126
                                              value,
5127
                                              match_detail,
5128
                                              match_message,
5129
                                              sizeof(match_message));
5130
            } else {
NEW
5131
                sixel_helper_set_additional_message(
×
5132
                    "cannot parse lut policy option.");
5133
            }
5134
            status = SIXEL_BAD_ARGUMENT;
×
5135
            goto end;
×
5136
        }
5137
        if (encoder->dither_cache != NULL) {
×
5138
            sixel_dither_set_lut_policy(encoder->dither_cache,
×
5139
                                        encoder->lut_policy);
5140
        }
5141
        break;
×
5142
    case SIXEL_OPTFLAG_WORKING_COLORSPACE:  /* W */
5143
        if (value == NULL) {
×
5144
            sixel_helper_set_additional_message(
×
5145
                "working-colorspace requires an argument.");
5146
            status = SIXEL_BAD_ARGUMENT;
×
5147
            goto end;
×
5148
        } else {
5149
            len = strlen(value);
×
5150

5151
            if (len >= sizeof(lowered)) {
×
5152
                sixel_helper_set_additional_message(
×
5153
                    "specified working colorspace name is too long.");
5154
                status = SIXEL_BAD_ARGUMENT;
×
5155
                goto end;
×
5156
            }
5157
            for (i = 0; i < len; ++i) {
×
5158
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5159
            }
5160
            lowered[len] = '\0';
×
5161

NEW
5162
            match_result = sixel_match_option_choice(
×
5163
                lowered,
5164
                g_option_choices_working_colorspace,
5165
                sizeof(g_option_choices_working_colorspace) /
5166
                sizeof(g_option_choices_working_colorspace[0]),
5167
                &match_value,
5168
                match_detail,
5169
                sizeof(match_detail));
NEW
5170
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
NEW
5171
                encoder->working_colorspace = match_value;
×
5172
            } else {
NEW
5173
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
5174
                    sixel_report_ambiguous_prefix(
×
5175
                        "--working-colorspace",
5176
                        value,
5177
                        match_detail,
5178
                        match_message,
5179
                        sizeof(match_message));
5180
                } else {
NEW
5181
                    sixel_helper_set_additional_message(
×
5182
                        "unsupported working colorspace specified.");
5183
                }
5184
                status = SIXEL_BAD_ARGUMENT;
×
5185
                goto end;
×
5186
            }
5187
        }
5188
        break;
×
5189
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
5190
        if (value == NULL) {
×
5191
            sixel_helper_set_additional_message(
×
5192
                "output-colorspace requires an argument.");
5193
            status = SIXEL_BAD_ARGUMENT;
×
5194
            goto end;
×
5195
        } else {
5196
            len = strlen(value);
×
5197

5198
            if (len >= sizeof(lowered)) {
×
5199
                sixel_helper_set_additional_message(
×
5200
                    "specified output colorspace name is too long.");
5201
                status = SIXEL_BAD_ARGUMENT;
×
5202
                goto end;
×
5203
            }
5204
            for (i = 0; i < len; ++i) {
×
5205
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5206
            }
5207
            lowered[len] = '\0';
×
5208

NEW
5209
            match_result = sixel_match_option_choice(
×
5210
                lowered,
5211
                g_option_choices_output_colorspace,
5212
                sizeof(g_option_choices_output_colorspace) /
5213
                sizeof(g_option_choices_output_colorspace[0]),
5214
                &match_value,
5215
                match_detail,
5216
                sizeof(match_detail));
NEW
5217
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
NEW
5218
                encoder->output_colorspace = match_value;
×
5219
            } else {
NEW
5220
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
NEW
5221
                    sixel_report_ambiguous_prefix(
×
5222
                        "--output-colorspace",
5223
                        value,
5224
                        match_detail,
5225
                        match_message,
5226
                        sizeof(match_message));
5227
                } else {
NEW
5228
                    sixel_helper_set_additional_message(
×
5229
                        "unsupported output colorspace specified.");
5230
                }
5231
                status = SIXEL_BAD_ARGUMENT;
×
5232
                goto end;
×
5233
            }
5234
        }
5235
        break;
×
5236
    case SIXEL_OPTFLAG_ORMODE:  /* O */
5237
        encoder->ormode = 1;
×
5238
        break;
×
5239
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
5240
        encoder->complexion = atoi(value);
9✔
5241
        if (encoder->complexion < 1) {
9✔
5242
            sixel_helper_set_additional_message(
3✔
5243
                "complexion parameter must be 1 or more.");
5244
            status = SIXEL_BAD_ARGUMENT;
3✔
5245
            goto end;
3✔
5246
        }
5247
        break;
6✔
5248
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
5249
        encoder->pipe_mode = 1;
×
5250
        break;
×
5251
    case '?':  /* unknown option */
3✔
5252
    default:
5253
        /* exit if unknown options are specified */
5254
        sixel_helper_set_additional_message(
3✔
5255
            "unknown option is specified.");
5256
        status = SIXEL_BAD_ARGUMENT;
3✔
5257
        goto end;
3✔
5258
    }
5259

5260
    /* detects arguments conflictions */
5261
    if (encoder->reqcolors != (-1)) {
624✔
5262
        switch (encoder->color_option) {
99!
5263
        case SIXEL_COLOR_OPTION_MAPFILE:
5264
            sixel_helper_set_additional_message(
×
5265
                "option -p, --colors conflicts with -m, --mapfile.");
5266
            status = SIXEL_BAD_ARGUMENT;
×
5267
            goto end;
×
5268
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
5269
            sixel_helper_set_additional_message(
3✔
5270
                "option -e, --monochrome conflicts with -p, --colors.");
5271
            status = SIXEL_BAD_ARGUMENT;
3✔
5272
            goto end;
3✔
5273
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
5274
            sixel_helper_set_additional_message(
3✔
5275
                "option -p, --colors conflicts with -I, --high-color.");
5276
            status = SIXEL_BAD_ARGUMENT;
3✔
5277
            goto end;
3✔
5278
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
5279
            sixel_helper_set_additional_message(
3✔
5280
                "option -p, --colors conflicts with -b, --builtin-palette.");
5281
            status = SIXEL_BAD_ARGUMENT;
3✔
5282
            goto end;
3✔
5283
        default:
60✔
5284
            break;
90✔
5285
        }
5286
    }
30✔
5287

5288
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
5289
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
615✔
5290
        sixel_helper_set_additional_message(
3✔
5291
            "option -8 --8bit-mode conflicts"
5292
            " with -P, --penetrate.");
5293
        status = SIXEL_BAD_ARGUMENT;
3✔
5294
        goto end;
3✔
5295
    }
5296

5297
    status = SIXEL_OK;
612✔
5298

5299
end:
458✔
5300
    if (opt_copy != NULL) {
687!
5301
        sixel_allocator_free(encoder->allocator, opt_copy);
×
5302
    }
5303
    sixel_encoder_unref(encoder);
687✔
5304

5305
    return status;
687✔
5306
}
5307

5308

5309
/* called when image loader component load a image frame */
5310
static SIXELSTATUS
5311
load_image_callback(sixel_frame_t *frame, void *data)
530✔
5312
{
5313
    sixel_encoder_t *encoder;
5314

5315
    encoder = (sixel_encoder_t *)data;
530✔
5316
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
530!
5317
        sixel_frame_ref(frame);
×
5318
        encoder->capture_source_frame = frame;
×
5319
    }
5320

5321
    return sixel_encoder_encode_frame(encoder, frame, NULL);
530✔
5322
}
5323

5324

5325
/* load source data from specified file and encode it to SIXEL format
5326
 * output to encoder->outfd */
5327
SIXELAPI SIXELSTATUS
5328
sixel_encoder_encode(
429✔
5329
    sixel_encoder_t *encoder,   /* encoder object */
5330
    char const      *filename)  /* input filename */
5331
{
5332
    SIXELSTATUS status = SIXEL_FALSE;
429✔
5333
    SIXELSTATUS palette_status = SIXEL_OK;
429✔
5334
    int fuse_palette = 1;
429✔
5335
    sixel_loader_t *loader;
5336

5337
    if (encoder == NULL) {
429!
5338
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5339
#  pragma GCC diagnostic push
5340
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
5341
#endif
5342
        encoder = sixel_encoder_create();
×
5343
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5344
#  pragma GCC diagnostic pop
5345
#endif
5346
        if (encoder == NULL) {
×
5347
            sixel_helper_set_additional_message(
×
5348
                "sixel_encoder_encode: sixel_encoder_create() failed.");
5349
            status = SIXEL_BAD_ALLOCATION;
×
5350
            goto end;
×
5351
        }
5352
    } else {
5353
        sixel_encoder_ref(encoder);
429✔
5354
    }
5355

5356
    if (encoder->assessment_observer != NULL) {
429!
5357
        sixel_assessment_stage_transition(
×
5358
            encoder->assessment_observer,
×
5359
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
5360
    }
5361
    encoder->last_loader_name[0] = '\0';
429✔
5362
    encoder->last_source_path[0] = '\0';
429✔
5363
    encoder->last_input_bytes = 0u;
429✔
5364

5365
    /* if required color is not set, set the max value */
5366
    if (encoder->reqcolors == (-1)) {
429✔
5367
        encoder->reqcolors = SIXEL_PALETTE_MAX;
411✔
5368
    }
137✔
5369

5370
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
429!
5371
        sixel_frame_unref(encoder->capture_source_frame);
×
5372
        encoder->capture_source_frame = NULL;
×
5373
    }
5374

5375
    /* if required color is less then 2, set the min value */
5376
    if (encoder->reqcolors < 2) {
429✔
5377
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
5378
    }
1✔
5379

5380
    /* if color space option is not set, choose RGB color space */
5381
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
429✔
5382
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
411✔
5383
    }
137✔
5384

5385
    /* if color option is not default value, prohibit to read
5386
       the file as a paletted image */
5387
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
429✔
5388
        fuse_palette = 0;
99✔
5389
    }
33✔
5390

5391
    /* if scaling options are set, prohibit to read the file as
5392
       a paletted image */
5393
    if (encoder->percentwidth > 0 ||
549✔
5394
        encoder->percentheight > 0 ||
417✔
5395
        encoder->pixelwidth > 0 ||
411✔
5396
        encoder->pixelheight > 0) {
393✔
5397
        fuse_palette = 0;
99✔
5398
    }
33✔
5399

5400
reload:
286✔
5401
    loader = NULL;
429✔
5402

5403
    sixel_helper_set_loader_trace(encoder->verbose);
429✔
5404
    sixel_helper_set_thumbnail_size_hint(
429✔
5405
        sixel_encoder_thumbnail_hint(encoder));
143✔
5406

5407
    status = sixel_loader_new(&loader, encoder->allocator);
429✔
5408
    if (SIXEL_FAILED(status)) {
429!
5409
        goto load_end;
×
5410
    }
5411

5412
    status = sixel_loader_setopt(loader,
572✔
5413
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
5414
                                 &encoder->fstatic);
429✔
5415
    if (SIXEL_FAILED(status)) {
429!
5416
        goto load_end;
×
5417
    }
5418

5419
    status = sixel_loader_setopt(loader,
429✔
5420
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
5421
                                 &fuse_palette);
5422
    if (SIXEL_FAILED(status)) {
429!
5423
        goto load_end;
×
5424
    }
5425

5426
    status = sixel_loader_setopt(loader,
572✔
5427
                                 SIXEL_LOADER_OPTION_REQCOLORS,
5428
                                 &encoder->reqcolors);
429✔
5429
    if (SIXEL_FAILED(status)) {
429!
5430
        goto load_end;
×
5431
    }
5432

5433
    status = sixel_loader_setopt(loader,
572✔
5434
                                 SIXEL_LOADER_OPTION_BGCOLOR,
5435
                                 encoder->bgcolor);
429✔
5436
    if (SIXEL_FAILED(status)) {
429!
5437
        goto load_end;
×
5438
    }
5439

5440
    status = sixel_loader_setopt(loader,
572✔
5441
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
5442
                                 &encoder->loop_mode);
429✔
5443
    if (SIXEL_FAILED(status)) {
429!
5444
        goto load_end;
×
5445
    }
5446

5447
    status = sixel_loader_setopt(loader,
572✔
5448
                                 SIXEL_LOADER_OPTION_INSECURE,
5449
                                 &encoder->finsecure);
429✔
5450
    if (SIXEL_FAILED(status)) {
429!
5451
        goto load_end;
×
5452
    }
5453

5454
    status = sixel_loader_setopt(loader,
572✔
5455
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
5456
                                 encoder->cancel_flag);
429✔
5457
    if (SIXEL_FAILED(status)) {
429!
5458
        goto load_end;
×
5459
    }
5460

5461
    status = sixel_loader_setopt(loader,
572✔
5462
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
5463
                                 encoder->loader_order);
429✔
5464
    if (SIXEL_FAILED(status)) {
429!
5465
        goto load_end;
×
5466
    }
5467

5468
    status = sixel_loader_setopt(loader,
572✔
5469
                                 SIXEL_LOADER_OPTION_CONTEXT,
5470
                                 encoder);
143✔
5471
    if (SIXEL_FAILED(status)) {
429!
5472
        goto load_end;
×
5473
    }
5474

5475
    /*
5476
     * Wire the optional assessment observer into the loader.
5477
     *
5478
     * The observer travels separately from the callback context so mapfile
5479
     * palette probes and other callbacks can keep using arbitrary structs.
5480
     */
5481
    status = sixel_loader_setopt(loader,
572✔
5482
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
5483
                                 encoder->assessment_observer);
429✔
5484
    if (SIXEL_FAILED(status)) {
429!
5485
        goto load_end;
×
5486
    }
5487

5488
    status = sixel_loader_load_file(loader,
572✔
5489
                                    filename,
143✔
5490
                                    load_image_callback);
5491
    if (status != SIXEL_OK) {
429✔
5492
        goto load_end;
21✔
5493
    }
5494
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
408✔
5495
    if (sixel_loader_get_last_success_name(loader) != NULL) {
408!
5496
        (void)snprintf(encoder->last_loader_name,
408✔
5497
                       sizeof(encoder->last_loader_name),
5498
                       "%s",
5499
                       sixel_loader_get_last_success_name(loader));
5500
    } else {
136✔
5501
        encoder->last_loader_name[0] = '\0';
×
5502
    }
5503
    if (sixel_loader_get_last_source_path(loader) != NULL) {
408✔
5504
        (void)snprintf(encoder->last_source_path,
270✔
5505
                       sizeof(encoder->last_source_path),
5506
                       "%s",
5507
                       sixel_loader_get_last_source_path(loader));
5508
    } else {
90✔
5509
        encoder->last_source_path[0] = '\0';
138✔
5510
    }
5511
    if (encoder->assessment_observer != NULL) {
408!
5512
        sixel_assessment_record_loader(encoder->assessment_observer,
×
5513
                                       encoder->last_source_path,
×
5514
                                       encoder->last_loader_name,
×
5515
                                       encoder->last_input_bytes);
5516
    }
5517

5518
load_end:
272✔
5519
    sixel_loader_unref(loader);
429✔
5520
    loader = NULL;
429✔
5521

5522
    if (status != SIXEL_OK) {
429✔
5523
        goto end;
21✔
5524
    }
5525

5526
    palette_status = sixel_encoder_emit_palette_output(encoder);
408✔
5527
    if (SIXEL_FAILED(palette_status)) {
408!
5528
        status = palette_status;
×
5529
        goto end;
×
5530
    }
5531

5532
    if (encoder->pipe_mode) {
408!
5533
#if HAVE_CLEARERR
5534
        clearerr(stdin);
×
5535
#endif  /* HAVE_FSEEK */
5536
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
5537
            status = sixel_tty_wait_stdin(1000000);
×
5538
            if (SIXEL_FAILED(status)) {
×
5539
                goto end;
×
5540
            }
5541
            if (status != SIXEL_OK) {
×
5542
                break;
×
5543
            }
5544
        }
5545
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
5546
            goto reload;
×
5547
        }
5548
    }
5549

5550
    /* the status may not be SIXEL_OK */
5551

5552
end:
272✔
5553
    sixel_encoder_unref(encoder);
429✔
5554

5555
    return status;
429✔
5556
}
5557

5558

5559
/* encode specified pixel data to SIXEL format
5560
 * output to encoder->outfd */
5561
SIXELAPI SIXELSTATUS
5562
sixel_encoder_encode_bytes(
×
5563
    sixel_encoder_t     /* in */    *encoder,
5564
    unsigned char       /* in */    *bytes,
5565
    int                 /* in */    width,
5566
    int                 /* in */    height,
5567
    int                 /* in */    pixelformat,
5568
    unsigned char       /* in */    *palette,
5569
    int                 /* in */    ncolors)
5570
{
5571
    SIXELSTATUS status = SIXEL_FALSE;
×
5572
    sixel_frame_t *frame = NULL;
×
5573

5574
    if (encoder == NULL || bytes == NULL) {
×
5575
        status = SIXEL_BAD_ARGUMENT;
×
5576
        goto end;
×
5577
    }
5578

5579
    status = sixel_frame_new(&frame, encoder->allocator);
×
5580
    if (SIXEL_FAILED(status)) {
×
5581
        goto end;
×
5582
    }
5583

5584
    status = sixel_frame_init(frame, bytes, width, height,
×
5585
                              pixelformat, palette, ncolors);
5586
    if (SIXEL_FAILED(status)) {
×
5587
        goto end;
×
5588
    }
5589

5590
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
5591
    if (SIXEL_FAILED(status)) {
×
5592
        goto end;
×
5593
    }
5594

5595
    status = SIXEL_OK;
×
5596

5597
end:
5598
    /* we need to free the frame before exiting, but we can't use the
5599
       sixel_frame_destroy function, because that will also attempt to
5600
       free the pixels and palette, which we don't own */
5601
    if (frame != NULL && encoder->allocator != NULL) {
×
5602
        sixel_allocator_free(encoder->allocator, frame);
×
5603
        sixel_allocator_unref(encoder->allocator);
×
5604
    }
5605
    return status;
×
5606
}
5607

5608

5609
/*
5610
 * Toggle source-frame capture for assessment consumers.
5611
 */
5612
SIXELAPI SIXELSTATUS
5613
sixel_encoder_enable_source_capture(
×
5614
    sixel_encoder_t *encoder,
5615
    int enable)
5616
{
5617
    if (encoder == NULL) {
×
5618
        sixel_helper_set_additional_message(
×
5619
            "sixel_encoder_enable_source_capture: encoder is null.");
5620
        return SIXEL_BAD_ARGUMENT;
×
5621
    }
5622

5623
    encoder->capture_source = enable ? 1 : 0;
×
5624
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
×
5625
        sixel_frame_unref(encoder->capture_source_frame);
×
5626
        encoder->capture_source_frame = NULL;
×
5627
    }
5628

5629
    return SIXEL_OK;
×
5630
}
5631

5632

5633
/*
5634
 * Enable or disable the quantized-frame capture facility.
5635
 *
5636
 *     capture on --> encoder keeps the latest palette-quantized frame.
5637
 *     capture off --> encoder forgets previously stored frames.
5638
 */
5639
SIXELAPI SIXELSTATUS
5640
sixel_encoder_enable_quantized_capture(
×
5641
    sixel_encoder_t *encoder,
5642
    int enable)
5643
{
5644
    if (encoder == NULL) {
×
5645
        sixel_helper_set_additional_message(
×
5646
            "sixel_encoder_enable_quantized_capture: encoder is null.");
5647
        return SIXEL_BAD_ARGUMENT;
×
5648
    }
5649

5650
    encoder->capture_quantized = enable ? 1 : 0;
×
5651
    if (!encoder->capture_quantized) {
×
5652
        encoder->capture_valid = 0;
×
5653
    }
5654

5655
    return SIXEL_OK;
×
5656
}
5657

5658

5659
/*
5660
 * Materialize the captured quantized frame as a heap-allocated
5661
 * sixel_frame_t instance for assessment consumers.
5662
 */
5663
SIXELAPI SIXELSTATUS
5664
sixel_encoder_copy_quantized_frame(
×
5665
    sixel_encoder_t   *encoder,
5666
    sixel_allocator_t *allocator,
5667
    sixel_frame_t     **ppframe)
5668
{
5669
    SIXELSTATUS status = SIXEL_FALSE;
×
5670
    sixel_frame_t *frame;
5671
    unsigned char *pixels;
5672
    unsigned char *palette;
5673
    size_t palette_bytes;
5674

5675
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
5676
        sixel_helper_set_additional_message(
×
5677
            "sixel_encoder_copy_quantized_frame: invalid argument.");
5678
        return SIXEL_BAD_ARGUMENT;
×
5679
    }
5680

5681
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
5682
        sixel_helper_set_additional_message(
×
5683
            "sixel_encoder_copy_quantized_frame: no frame captured.");
5684
        return SIXEL_RUNTIME_ERROR;
×
5685
    }
5686

5687
    *ppframe = NULL;
×
5688
    frame = NULL;
×
5689
    pixels = NULL;
×
5690
    palette = NULL;
×
5691

5692
    status = sixel_frame_new(&frame, allocator);
×
5693
    if (SIXEL_FAILED(status)) {
×
5694
        return status;
×
5695
    }
5696

5697
    if (encoder->capture_pixel_bytes > 0) {
×
5698
        pixels = (unsigned char *)sixel_allocator_malloc(
×
5699
            allocator, encoder->capture_pixel_bytes);
5700
        if (pixels == NULL) {
×
5701
            sixel_helper_set_additional_message(
×
5702
                "sixel_encoder_copy_quantized_frame: "
5703
                "sixel_allocator_malloc() failed.");
5704
            status = SIXEL_BAD_ALLOCATION;
×
5705
            goto cleanup;
×
5706
        }
5707
        memcpy(pixels,
×
5708
               encoder->capture_pixels,
5709
               encoder->capture_pixel_bytes);
5710
    }
5711

5712
    palette_bytes = encoder->capture_palette_size;
×
5713
    if (palette_bytes > 0) {
×
5714
        palette = (unsigned char *)sixel_allocator_malloc(allocator,
×
5715
                                                          palette_bytes);
5716
        if (palette == NULL) {
×
5717
            sixel_helper_set_additional_message(
×
5718
                "sixel_encoder_copy_quantized_frame: "
5719
                "sixel_allocator_malloc() failed.");
5720
            status = SIXEL_BAD_ALLOCATION;
×
5721
            goto cleanup;
×
5722
        }
5723
        memcpy(palette,
×
5724
               encoder->capture_palette,
5725
               palette_bytes);
5726
    }
5727

5728
    status = sixel_frame_init(frame,
×
5729
                              pixels,
5730
                              encoder->capture_width,
5731
                              encoder->capture_height,
5732
                              encoder->capture_pixelformat,
5733
                              palette,
5734
                              encoder->capture_ncolors);
5735
    if (SIXEL_FAILED(status)) {
×
5736
        goto cleanup;
×
5737
    }
5738

5739
    pixels = NULL;
×
5740
    palette = NULL;
×
5741
    frame->colorspace = encoder->capture_colorspace;
×
5742
    *ppframe = frame;
×
5743
    return SIXEL_OK;
×
5744

5745
cleanup:
5746
    if (palette != NULL) {
×
5747
        sixel_allocator_free(allocator, palette);
×
5748
    }
5749
    if (pixels != NULL) {
×
5750
        sixel_allocator_free(allocator, pixels);
×
5751
    }
5752
    if (frame != NULL) {
×
5753
        sixel_frame_unref(frame);
×
5754
    }
5755
    return status;
×
5756
}
5757

5758

5759
/*
5760
 * Emit the captured palette in the requested format.
5761
 *
5762
 *   palette_output == NULL  -> skip
5763
 *   palette_output != NULL  -> materialize captured palette
5764
 */
5765
static SIXELSTATUS
5766
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
408✔
5767
{
5768
    SIXELSTATUS status;
5769
    sixel_frame_t *frame;
5770
    unsigned char const *palette;
5771
    int exported_colors;
5772
    FILE *stream;
5773
    int close_stream;
5774
    char const *path;
5775
    sixel_palette_format_t format_hint;
5776
    sixel_palette_format_t format_ext;
5777
    sixel_palette_format_t format_final;
5778
    char const *mode;
5779

5780
    status = SIXEL_OK;
408✔
5781
    frame = NULL;
408✔
5782
    palette = NULL;
408✔
5783
    exported_colors = 0;
408✔
5784
    stream = NULL;
408✔
5785
    close_stream = 0;
408✔
5786
    path = NULL;
408✔
5787
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
408✔
5788
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
408✔
5789
    format_final = SIXEL_PALETTE_FORMAT_NONE;
408✔
5790
    mode = "wb";
408✔
5791

5792
    if (encoder == NULL || encoder->palette_output == NULL) {
408!
5793
        return SIXEL_OK;
408✔
5794
    }
5795

5796
    status = sixel_encoder_copy_quantized_frame(encoder,
×
5797
                                                encoder->allocator,
5798
                                                &frame);
5799
    if (SIXEL_FAILED(status)) {
×
5800
        return status;
×
5801
    }
5802

5803
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
5804
    exported_colors = sixel_frame_get_ncolors(frame);
×
5805
    if (palette == NULL || exported_colors <= 0) {
×
5806
        sixel_helper_set_additional_message(
×
5807
            "sixel_encoder_emit_palette_output: palette unavailable.");
5808
        status = SIXEL_BAD_INPUT;
×
5809
        goto cleanup;
×
5810
    }
5811
    if (exported_colors > 256) {
×
5812
        exported_colors = 256;
×
5813
    }
5814

5815
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
5816
    if (path == NULL || *path == '\0') {
×
5817
        sixel_helper_set_additional_message(
×
5818
            "sixel_encoder_emit_palette_output: invalid path.");
5819
        status = SIXEL_BAD_ARGUMENT;
×
5820
        goto cleanup;
×
5821
    }
5822

5823
    format_ext = sixel_palette_format_from_extension(path);
×
5824
    format_final = format_hint;
×
5825
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
5826
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
5827
            if (strcmp(path, "-") == 0) {
×
5828
                sixel_helper_set_additional_message(
×
5829
                    "sixel_encoder_emit_palette_output: "
5830
                    "format required for '-'.");
5831
                status = SIXEL_BAD_ARGUMENT;
×
5832
                goto cleanup;
×
5833
            }
5834
            sixel_helper_set_additional_message(
×
5835
                "sixel_encoder_emit_palette_output: "
5836
                "unknown palette file extension.");
5837
            status = SIXEL_BAD_ARGUMENT;
×
5838
            goto cleanup;
×
5839
        }
5840
        format_final = format_ext;
×
5841
    }
5842
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
5843
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
5844
    }
5845

5846
    if (strcmp(path, "-") == 0) {
×
5847
        stream = stdout;
×
5848
    } else {
5849
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
5850
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
5851
            mode = "w";
×
5852
        } else {
5853
            mode = "wb";
×
5854
        }
5855
        stream = fopen(path, mode);
×
5856
        if (stream == NULL) {
×
5857
            sixel_helper_set_additional_message(
×
5858
                "sixel_encoder_emit_palette_output: failed to open file.");
5859
            status = SIXEL_LIBC_ERROR;
×
5860
            goto cleanup;
×
5861
        }
5862
        close_stream = 1;
×
5863
    }
5864

5865
    switch (format_final) {
×
5866
    case SIXEL_PALETTE_FORMAT_ACT:
5867
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
5868
        if (SIXEL_FAILED(status)) {
×
5869
            sixel_helper_set_additional_message(
×
5870
                "sixel_encoder_emit_palette_output: failed to write ACT.");
5871
        }
5872
        break;
×
5873
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
5874
        status = sixel_palette_write_pal_jasc(stream,
×
5875
                                              palette,
5876
                                              exported_colors);
5877
        if (SIXEL_FAILED(status)) {
×
5878
            sixel_helper_set_additional_message(
×
5879
                "sixel_encoder_emit_palette_output: failed to write JASC.");
5880
        }
5881
        break;
×
5882
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
5883
        status = sixel_palette_write_pal_riff(stream,
×
5884
                                              palette,
5885
                                              exported_colors);
5886
        if (SIXEL_FAILED(status)) {
×
5887
            sixel_helper_set_additional_message(
×
5888
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
5889
        }
5890
        break;
×
5891
    case SIXEL_PALETTE_FORMAT_GPL:
5892
        status = sixel_palette_write_gpl(stream,
×
5893
                                         palette,
5894
                                         exported_colors);
5895
        if (SIXEL_FAILED(status)) {
×
5896
            sixel_helper_set_additional_message(
×
5897
                "sixel_encoder_emit_palette_output: failed to write GPL.");
5898
        }
5899
        break;
×
5900
    default:
5901
        sixel_helper_set_additional_message(
×
5902
            "sixel_encoder_emit_palette_output: unsupported format.");
5903
        status = SIXEL_BAD_ARGUMENT;
×
5904
        break;
×
5905
    }
5906
    if (SIXEL_FAILED(status)) {
×
5907
        goto cleanup;
×
5908
    }
5909

5910
    if (close_stream) {
×
5911
        if (fclose(stream) != 0) {
×
5912
            sixel_helper_set_additional_message(
×
5913
                "sixel_encoder_emit_palette_output: fclose() failed.");
5914
            status = SIXEL_LIBC_ERROR;
×
5915
            stream = NULL;
×
5916
            goto cleanup;
×
5917
        }
5918
        stream = NULL;
×
5919
    } else {
5920
        if (fflush(stream) != 0) {
×
5921
            sixel_helper_set_additional_message(
×
5922
                "sixel_encoder_emit_palette_output: fflush() failed.");
5923
            status = SIXEL_LIBC_ERROR;
×
5924
            goto cleanup;
×
5925
        }
5926
    }
5927

5928
cleanup:
5929
    if (close_stream && stream != NULL) {
×
5930
        (void) fclose(stream);
×
5931
    }
5932
    if (frame != NULL) {
×
5933
        sixel_frame_unref(frame);
×
5934
    }
5935

5936
    return status;
×
5937
}
136✔
5938

5939

5940
/*
5941
 * Share the captured source frame with assessment consumers.
5942
 */
5943
SIXELAPI SIXELSTATUS
5944
sixel_encoder_copy_source_frame(
×
5945
    sixel_encoder_t *encoder,
5946
    sixel_frame_t  **ppframe)
5947
{
5948
    if (encoder == NULL || ppframe == NULL) {
×
5949
        sixel_helper_set_additional_message(
×
5950
            "sixel_encoder_copy_source_frame: invalid argument.");
5951
        return SIXEL_BAD_ARGUMENT;
×
5952
    }
5953

5954
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
×
5955
        sixel_helper_set_additional_message(
×
5956
            "sixel_encoder_copy_source_frame: no frame captured.");
5957
        return SIXEL_RUNTIME_ERROR;
×
5958
    }
5959

5960
    sixel_frame_ref(encoder->capture_source_frame);
×
5961
    *ppframe = encoder->capture_source_frame;
×
5962

5963
    return SIXEL_OK;
×
5964
}
5965

5966

5967
#if HAVE_TESTS
5968
static int
5969
test1(void)
×
5970
{
5971
    int nret = EXIT_FAILURE;
×
5972
    sixel_encoder_t *encoder = NULL;
×
5973

5974
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5975
#  pragma GCC diagnostic push
5976
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
5977
#endif
5978
    encoder = sixel_encoder_create();
×
5979
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5980
#  pragma GCC diagnostic pop
5981
#endif
5982
    if (encoder == NULL) {
×
5983
        goto error;
×
5984
    }
5985
    sixel_encoder_ref(encoder);
×
5986
    sixel_encoder_unref(encoder);
×
5987
    nret = EXIT_SUCCESS;
×
5988

5989
error:
5990
    sixel_encoder_unref(encoder);
×
5991
    return nret;
×
5992
}
5993

5994

5995
static int
5996
test2(void)
×
5997
{
5998
    int nret = EXIT_FAILURE;
×
5999
    SIXELSTATUS status;
6000
    sixel_encoder_t *encoder = NULL;
×
6001
    sixel_frame_t *frame = NULL;
×
6002
    unsigned char *buffer;
6003
    int height = 0;
×
6004
    int is_animation = 0;
×
6005

6006
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6007
#  pragma GCC diagnostic push
6008
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
6009
#endif
6010
    encoder = sixel_encoder_create();
×
6011
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6012
#  pragma GCC diagnostic pop
6013
#endif
6014
    if (encoder == NULL) {
×
6015
        goto error;
×
6016
    }
6017

6018
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6019
#  pragma GCC diagnostic push
6020
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
6021
#endif
6022
    frame = sixel_frame_create();
×
6023
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6024
#  pragma GCC diagnostic pop
6025
#endif
6026
    if (encoder == NULL) {
×
6027
        goto error;
×
6028
    }
6029

6030
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
6031
    if (buffer == NULL) {
×
6032
        goto error;
×
6033
    }
6034
    status = sixel_frame_init(frame, buffer, 1, 1,
×
6035
                              SIXEL_PIXELFORMAT_RGB888,
6036
                              NULL, 0);
6037
    if (SIXEL_FAILED(status)) {
×
6038
        goto error;
×
6039
    }
6040

6041
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
6042
        is_animation = 1;
×
6043
    }
6044

6045
    height = sixel_frame_get_height(frame);
×
6046

6047
    status = sixel_tty_scroll(sixel_write_callback,
×
6048
                              &encoder->outfd,
×
6049
                              encoder->outfd,
6050
                              height,
6051
                              is_animation);
6052
    if (SIXEL_FAILED(status)) {
×
6053
        goto error;
×
6054
    }
6055

6056
    nret = EXIT_SUCCESS;
×
6057

6058
error:
6059
    sixel_encoder_unref(encoder);
×
6060
    sixel_frame_unref(frame);
×
6061
    return nret;
×
6062
}
6063

6064

6065
static int
6066
test3(void)
×
6067
{
6068
    int nret = EXIT_FAILURE;
×
6069
    int result;
6070

6071
    result = sixel_tty_wait_stdin(1000);
×
6072
    if (result != 0) {
×
6073
        goto error;
×
6074
    }
6075

6076
    nret = EXIT_SUCCESS;
×
6077

6078
error:
6079
    return nret;
×
6080
}
6081

6082

6083
static int
6084
test4(void)
×
6085
{
6086
    int nret = EXIT_FAILURE;
×
6087
    sixel_encoder_t *encoder = NULL;
×
6088
    SIXELSTATUS status;
6089

6090
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6091
# pragma GCC diagnostic push
6092
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
6093
#endif
6094
    encoder = sixel_encoder_create();
×
6095
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6096
# pragma GCC diagnostic pop
6097
#endif
6098
    if (encoder == NULL) {
×
6099
        goto error;
×
6100
    }
6101

6102
    status = sixel_encoder_setopt(encoder,
×
6103
                                  SIXEL_OPTFLAG_LOOPMODE,
6104
                                  "force");
6105
    if (SIXEL_FAILED(status)) {
×
6106
        goto error;
×
6107
    }
6108

6109
    status = sixel_encoder_setopt(encoder,
×
6110
                                  SIXEL_OPTFLAG_PIPE_MODE,
6111
                                  "force");
6112
    if (SIXEL_FAILED(status)) {
×
6113
        goto error;
×
6114
    }
6115

6116
    nret = EXIT_SUCCESS;
×
6117

6118
error:
6119
    sixel_encoder_unref(encoder);
×
6120
    return nret;
×
6121
}
6122

6123

6124
static int
6125
test5(void)
×
6126
{
6127
    int nret = EXIT_FAILURE;
×
6128
    sixel_encoder_t *encoder = NULL;
×
6129
    sixel_allocator_t *allocator = NULL;
×
6130
    SIXELSTATUS status;
6131

6132
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
6133
    if (SIXEL_FAILED(status)) {
×
6134
        goto error;
×
6135
    }
6136

6137
    status = sixel_encoder_new(&encoder, allocator);
×
6138
    if (SIXEL_FAILED(status)) {
×
6139
        goto error;
×
6140
    }
6141

6142
    sixel_encoder_ref(encoder);
×
6143
    sixel_encoder_unref(encoder);
×
6144
    nret = EXIT_SUCCESS;
×
6145

6146
error:
6147
    sixel_encoder_unref(encoder);
×
6148
    return nret;
×
6149
}
6150

6151

6152
SIXELAPI int
6153
sixel_encoder_tests_main(void)
×
6154
{
6155
    int nret = EXIT_FAILURE;
×
6156
    size_t i;
6157
    typedef int (* testcase)(void);
6158

6159
    static testcase const testcases[] = {
6160
        test1,
6161
        test2,
6162
        test3,
6163
        test4,
6164
        test5
6165
    };
6166

6167
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
6168
        nret = testcases[i]();
×
6169
        if (nret != EXIT_SUCCESS) {
×
6170
            goto error;
×
6171
        }
6172
    }
6173

6174
    nret = EXIT_SUCCESS;
×
6175

6176
error:
6177
    return nret;
×
6178
}
6179
#endif  /* HAVE_TESTS */
6180

6181

6182
/* emacs Local Variables:      */
6183
/* emacs mode: c               */
6184
/* emacs tab-width: 4          */
6185
/* emacs indent-tabs-mode: nil */
6186
/* emacs c-basic-offset: 4     */
6187
/* emacs End:                  */
6188
/* vim: set expandtab ts=4 : */
6189
/* 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