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

saitoha / libsixel / 19023942966

03 Nov 2025 04:28AM UTC coverage: 44.603% (-0.05%) from 44.655%
19023942966

push

github

saitoha
feat: add -6,--6reversible option for palette generation

6969 of 23422 branches covered (29.75%)

30 of 84 new or added lines in 3 files covered. (35.71%)

2 existing lines in 1 file now uncovered.

10005 of 22431 relevant lines covered (44.6%)

1092199.81 hits per line

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

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

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

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

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

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

117
#if defined(_WIN32)
118

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

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

144
# if defined(CLOCKS_PER_SEC)
145
#  undef CLOCKS_PER_SEC
146
# endif
147
# define CLOCKS_PER_SEC 1000
148

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

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

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

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

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

182
    (void) WaitForSingleObject(timer, INFINITE);
183
    (void) CloseHandle(timer);
184

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

191
    return (0);
192
}
193
# endif  /* HAVE_NANOSLEEP */
194

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

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

213
#endif /* _WIN32 */
214

215

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

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

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

260
    if (diagnostic != NULL && diagnostic_size > 0u) {
279!
261
        diagnostic[0] = '\0';
279✔
262
    }
93✔
263
    if (value == NULL) {
279!
264
        return SIXEL_OPTION_CHOICE_NONE;
×
265
    }
266

267
    value_length = strlen(value);
279✔
268
    if (value_length == 0u) {
279!
269
        return SIXEL_OPTION_CHOICE_NONE;
×
270
    }
271

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

280
    while (index < choice_count) {
1,533✔
281
        if (strncmp(choices[index].name, value, value_length) == 0) {
1,449✔
282
            if (choices[index].name[value_length] == '\0') {
255✔
283
                *matched_value = choices[index].value;
195✔
284
                return SIXEL_OPTION_CHOICE_MATCH;
195✔
285
            }
286
            if (!base_value_set) {
60✔
287
                base_value = choices[index].value;
45✔
288
                base_value_set = 1;
45✔
289
            } else if (choices[index].value != base_value) {
30✔
290
                ambiguous_values = 1;
9✔
291
            }
3✔
292
            if (candidate_index == (-1)) {
60✔
293
                candidate_index = (int)index;
45✔
294
            }
15✔
295
            ++match_count;
60✔
296
            if (diagnostic != NULL && diagnostic_size > 0u) {
60!
297
                if (diag_length > 0u && diag_length + 2u < diagnostic_size) {
60!
298
                    diagnostic[diag_length] = ',';
15✔
299
                    diagnostic[diag_length + 1u] = ' ';
15✔
300
                    diag_length += 2u;
15✔
301
                    diagnostic[diag_length] = '\0';
15✔
302
                }
5✔
303
                copy_length = strlen(choices[index].name);
60✔
304
                if (copy_length > diagnostic_size - diag_length - 1u) {
60!
305
                    copy_length = diagnostic_size - diag_length - 1u;
×
306
                }
307
                memcpy(diagnostic + diag_length,
60✔
308
                       choices[index].name,
40✔
309
                       copy_length);
310
                diag_length += copy_length;
60✔
311
                diagnostic[diag_length] = '\0';
60✔
312
            }
20✔
313
        }
20✔
314
        ++index;
1,254✔
315
    }
316

317
    if (match_count == 0u) {
84✔
318
        return SIXEL_OPTION_CHOICE_NONE;
39✔
319
    }
320
    if (!ambiguous_values) {
45✔
321
        *matched_value = choices[candidate_index].value;
39✔
322
        return SIXEL_OPTION_CHOICE_MATCH;
39✔
323
    }
324

325
    return SIXEL_OPTION_CHOICE_AMBIGUOUS;
6✔
326
}
93✔
327

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

473

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

483
    len = strlen(s);
57✔
484

485
    p = (char *)sixel_allocator_malloc(allocator, len + 1);
57✔
486
    if (p) {
57!
487
        (void)sixel_compat_strcpy(p, len + 1, s);
57✔
488
    }
19✔
489
    return p;
57✔
490
}
491

492

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

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

641
    status = SIXEL_OK;
24✔
642

643
end:
30✔
644
    sixel_allocator_free(allocator, buf);
45✔
645

646
    return status;
45✔
647
}
648

649

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

656
    result = (int)sixel_compat_write(*(int *)priv,
6,090✔
657
                                     data,
1,527✔
658
                                     (size_t)size);
1,527✔
659

660
    return result;
4,563✔
661
}
662

663

664
/* the writer function with hex-encoding for passing to sixel_output_new() */
665
static int
666
sixel_hex_write_callback(
73✔
667
    char    /* in */ *data,
668
    int     /* in */ size,
669
    void    /* in */ *priv)
670
{
671
    char hex[SIXEL_OUTPUT_PACKET_SIZE * 2];
672
    int i;
673
    int j;
674
    int result;
675

676
    for (i = j = 0; i < size; ++i, ++j) {
635,609✔
677
        hex[j] = (data[i] >> 4) & 0xf;
635,536✔
678
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
635,536!
679
        hex[++j] = data[i] & 0xf;
635,536✔
680
        hex[j] += (hex[j] < 10 ? '0': ('a' - 10));
635,536✔
681
    }
168,020✔
682

683
    result = (int)sixel_compat_write(*(int *)priv,
146✔
684
                                     hex,
25✔
685
                                     (size_t)(size * 2));
73✔
686

687
    return result;
73✔
688
}
689

690
typedef struct sixel_encoder_output_probe {
691
    sixel_encoder_t *encoder;
692
    sixel_write_function base_write;
693
    void *base_priv;
694
} sixel_encoder_output_probe_t;
695

696
static int
697
sixel_write_with_probe(char *data, int size, void *priv)
70✔
698
{
699
    sixel_encoder_output_probe_t *probe;
700
    int written;
701
    double started_at;
702
    double finished_at;
703
    double duration;
704

705
    probe = (sixel_encoder_output_probe_t *)priv;
70✔
706
    if (probe == NULL || probe->base_write == NULL) {
70!
707
        return 0;
×
708
    }
709
    started_at = 0.0;
70✔
710
    finished_at = 0.0;
70✔
711
    duration = 0.0;
70✔
712
    if (probe->encoder != NULL &&
70!
713
            probe->encoder->assessment_observer != NULL) {
70!
714
        started_at = sixel_assessment_timer_now();
70✔
715
    }
22✔
716
    written = probe->base_write(data, size, probe->base_priv);
70✔
717
    if (probe->encoder != NULL &&
70!
718
            probe->encoder->assessment_observer != NULL) {
70!
719
        finished_at = sixel_assessment_timer_now();
70✔
720
        duration = finished_at - started_at;
70✔
721
        if (duration < 0.0) {
70!
722
            duration = 0.0;
×
723
        }
724
    }
22✔
725
    if (written > 0 && probe->encoder != NULL &&
70!
726
            probe->encoder->assessment_observer != NULL) {
70!
727
        sixel_assessment_record_output_write(
70✔
728
            probe->encoder->assessment_observer,
70✔
729
            (size_t)written,
22✔
730
            duration);
22✔
731
    }
22✔
732
    return written;
70✔
733
}
22✔
734

735
/*
736
 * Reuse the fn_write probe for raw escape writes so that every
737
 * assessment bucket receives the same accounting.
738
 *
739
 *     encoder        probe wrapper       write(2)
740
 *     +------+    +----------------+    +---------+
741
 *     | data | -> | sixel_write_*  | -> | target  |
742
 *     +------+    +----------------+    +---------+
743
 */
744
static int
745
sixel_encoder_probe_fd_write(sixel_encoder_t *encoder,
×
746
                             char *data,
747
                             int size,
748
                             int fd)
749
{
750
    sixel_encoder_output_probe_t probe;
751
    int written;
752

753
    probe.encoder = encoder;
×
754
    probe.base_write = sixel_write_callback;
×
755
    probe.base_priv = &fd;
×
756
    written = sixel_write_with_probe(data, size, &probe);
×
757

758
    return written;
×
759
}
760

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

769
    if (encoder->cell_width > 0 && encoder->cell_height > 0) {
×
770
        return SIXEL_OK;
×
771
    }
772

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

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

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

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

812

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

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

833
    status = SIXEL_OK;
12✔
834

835
end:
8✔
836
    return status;
12✔
837
}
838

839

840
/* returns dithering context object with specified builtin palette */
841
typedef struct palette_conversion {
842
    unsigned char *original;
843
    unsigned char *copy;
844
    size_t size;
845
    int convert_inplace;
846
    int converted;
847
    int frame_colorspace;
848
} palette_conversion_t;
849

850
static SIXELSTATUS
851
sixel_encoder_convert_palette(sixel_encoder_t *encoder,
525✔
852
                              sixel_output_t *output,
853
                              sixel_dither_t *dither,
854
                              int frame_colorspace,
855
                              int pixelformat,
856
                              palette_conversion_t *ctx)
857
{
858
    SIXELSTATUS status = SIXEL_OK;
525✔
859
    unsigned char *palette;
860
    int palette_colors;
861

862
    ctx->original = NULL;
525✔
863
    ctx->copy = NULL;
525✔
864
    ctx->size = 0;
525✔
865
    ctx->convert_inplace = 0;
525✔
866
    ctx->converted = 0;
525✔
867
    ctx->frame_colorspace = frame_colorspace;
525✔
868

869
    palette = sixel_dither_get_palette(dither);
525✔
870
    palette_colors = sixel_dither_get_num_of_palette_colors(dither);
525✔
871
    ctx->original = palette;
525✔
872

873
    if (palette == NULL || palette_colors <= 0 ||
525!
874
            frame_colorspace == output->colorspace) {
525!
875
        return SIXEL_OK;
525✔
876
    }
877

878
    ctx->size = (size_t)palette_colors * 3;
×
879

880
    output->pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
881
    output->source_colorspace = frame_colorspace;
×
882

883
    if (palette != (unsigned char *)(dither + 1)) {
×
884
        ctx->copy = (unsigned char *)sixel_allocator_malloc(
×
885
            encoder->allocator, ctx->size);
886
        if (ctx->copy == NULL) {
×
887
            sixel_helper_set_additional_message(
×
888
                "sixel_encoder_convert_palette: "
889
                "sixel_allocator_malloc() failed.");
890
            status = SIXEL_BAD_ALLOCATION;
×
891
            goto end;
×
892
        }
893
        memcpy(ctx->copy, palette, ctx->size);
×
894
        dither->palette = ctx->copy;
×
895
    } else {
896
        ctx->convert_inplace = 1;
×
897
    }
898

899
    status = sixel_output_convert_colorspace(output,
×
900
                                             dither->palette,
901
                                             ctx->size);
902
    if (SIXEL_FAILED(status)) {
×
903
        goto end;
×
904
    }
905
    ctx->converted = 1;
×
906

907
end:
908
    output->pixelformat = pixelformat;
×
909
    output->source_colorspace = frame_colorspace;
×
910

911
    return status;
×
912
}
181✔
913

914
static void
915
sixel_encoder_restore_palette(sixel_encoder_t *encoder,
525✔
916
                              sixel_dither_t *dither,
917
                              palette_conversion_t *ctx)
918
{
919
    if (ctx->copy) {
525!
920
        dither->palette = ctx->original;
×
921
        sixel_allocator_free(encoder->allocator, ctx->copy);
×
922
        ctx->copy = NULL;
×
923
    } else if (ctx->convert_inplace && ctx->converted &&
525!
924
               ctx->original && ctx->size > 0) {
×
925
        (void)sixel_helper_convert_colorspace(ctx->original,
×
926
                                              ctx->size,
927
                                              SIXEL_PIXELFORMAT_RGB888,
928
                                              SIXEL_COLORSPACE_GAMMA,
929
                                              ctx->frame_colorspace);
930
    }
931
}
525✔
932

933
static SIXELSTATUS
934
sixel_encoder_capture_quantized(sixel_encoder_t *encoder,
3✔
935
                                sixel_dither_t *dither,
936
                                unsigned char const *pixels,
937
                                size_t size,
938
                                int width,
939
                                int height,
940
                                int pixelformat,
941
                                int colorspace)
942
{
943
    SIXELSTATUS status;
944
    unsigned char *palette;
945
    int ncolors;
946
    size_t palette_bytes;
947
    unsigned char *new_pixels;
948
    unsigned char *new_palette;
949
    size_t capture_bytes;
950
    unsigned char const *capture_source;
951
    sixel_index_t *paletted_pixels;
952
    size_t quantized_pixels;
953
    sixel_allocator_t *dither_allocator;
954
    int saved_pixelformat;
955
    int restore_pixelformat;
956

957
    /*
958
     * Preserve the quantized frame for assessment observers.
959
     *
960
     *     +-----------------+     +---------------------+
961
     *     | quantized bytes | --> | encoder->capture_*  |
962
     *     +-----------------+     +---------------------+
963
     */
964

965
    status = SIXEL_OK;
3✔
966
    palette = NULL;
3✔
967
    ncolors = 0;
3✔
968
    palette_bytes = 0;
3✔
969
    new_pixels = NULL;
3✔
970
    new_palette = NULL;
3✔
971
    capture_bytes = size;
3✔
972
    capture_source = pixels;
3✔
973
    paletted_pixels = NULL;
3✔
974
    quantized_pixels = 0;
3✔
975
    dither_allocator = NULL;
3✔
976

977
    if (encoder == NULL || pixels == NULL ||
3!
978
            (dither == NULL && size == 0)) {
1!
979
        sixel_helper_set_additional_message(
×
980
            "sixel_encoder_capture_quantized: invalid capture request.");
981
        return SIXEL_BAD_ARGUMENT;
×
982
    }
983

984
    if (!encoder->capture_quantized) {
3!
985
        return SIXEL_OK;
×
986
    }
987

988
    saved_pixelformat = SIXEL_PIXELFORMAT_RGB888;
3✔
989
    restore_pixelformat = 0;
3✔
990
    if (dither != NULL) {
3!
991
        dither_allocator = dither->allocator;
3✔
992
        saved_pixelformat = dither->pixelformat;
3✔
993
        restore_pixelformat = 1;
3✔
994
        if (width <= 0 || height <= 0) {
3!
995
            sixel_helper_set_additional_message(
×
996
                "sixel_encoder_capture_quantized: invalid dimensions.");
997
            status = SIXEL_BAD_ARGUMENT;
×
998
            goto cleanup;
×
999
        }
1000
        quantized_pixels = (size_t)width * (size_t)height;
3✔
1001
        if (height != 0 &&
3!
1002
                quantized_pixels / (size_t)height != (size_t)width) {
3!
1003
            sixel_helper_set_additional_message(
×
1004
                "sixel_encoder_capture_quantized: image too large.");
1005
            status = SIXEL_RUNTIME_ERROR;
×
1006
            goto cleanup;
×
1007
        }
1008
        paletted_pixels = sixel_dither_apply_palette(
3✔
1009
            dither, (unsigned char *)pixels, width, height);
1✔
1010
        if (paletted_pixels == NULL) {
3!
1011
            sixel_helper_set_additional_message(
×
1012
                "sixel_encoder_capture_quantized: palette conversion failed.");
1013
            status = SIXEL_RUNTIME_ERROR;
×
1014
            goto cleanup;
×
1015
        }
1016
        capture_source = (unsigned char const *)paletted_pixels;
3✔
1017
        capture_bytes = quantized_pixels;
3✔
1018
    }
1✔
1019

1020
    if (capture_bytes > 0) {
3!
1021
        if (encoder->capture_pixels == NULL ||
3!
1022
                encoder->capture_pixels_size < capture_bytes) {
×
1023
            new_pixels = (unsigned char *)sixel_allocator_malloc(
3✔
1024
                encoder->allocator, capture_bytes);
1✔
1025
            if (new_pixels == NULL) {
3!
1026
                sixel_helper_set_additional_message(
×
1027
                    "sixel_encoder_capture_quantized: "
1028
                    "sixel_allocator_malloc() failed.");
1029
                status = SIXEL_BAD_ALLOCATION;
×
1030
                goto cleanup;
×
1031
            }
1032
            sixel_allocator_free(encoder->allocator, encoder->capture_pixels);
3✔
1033
            encoder->capture_pixels = new_pixels;
3✔
1034
            encoder->capture_pixels_size = capture_bytes;
3✔
1035
        }
1✔
1036
        memcpy(encoder->capture_pixels, capture_source, capture_bytes);
3✔
1037
    }
1✔
1038
    encoder->capture_pixel_bytes = capture_bytes;
3✔
1039

1040
    palette = NULL;
3✔
1041
    ncolors = 0;
3✔
1042
    palette_bytes = 0;
3✔
1043
    if (dither != NULL) {
3!
1044
        palette = sixel_dither_get_palette(dither);
3✔
1045
        ncolors = sixel_dither_get_num_of_palette_colors(dither);
3✔
1046
    }
1✔
1047
    if (palette != NULL && ncolors > 0) {
3!
1048
        palette_bytes = (size_t)ncolors * 3;
3✔
1049
        if (encoder->capture_palette == NULL ||
3!
1050
                encoder->capture_palette_size < palette_bytes) {
×
1051
            new_palette = (unsigned char *)sixel_allocator_malloc(
3✔
1052
                encoder->allocator, palette_bytes);
1✔
1053
            if (new_palette == NULL) {
3!
1054
                sixel_helper_set_additional_message(
×
1055
                    "sixel_encoder_capture_quantized: "
1056
                    "sixel_allocator_malloc() failed.");
1057
                status = SIXEL_BAD_ALLOCATION;
×
1058
                goto cleanup;
×
1059
            }
1060
            sixel_allocator_free(encoder->allocator,
4✔
1061
                                 encoder->capture_palette);
3✔
1062
            encoder->capture_palette = new_palette;
3✔
1063
            encoder->capture_palette_size = palette_bytes;
3✔
1064
        }
1✔
1065
        memcpy(encoder->capture_palette, palette, palette_bytes);
3✔
1066
    }
1✔
1067

1068
    encoder->capture_width = width;
3✔
1069
    encoder->capture_height = height;
3✔
1070
    if (dither != NULL) {
3!
1071
        encoder->capture_pixelformat = SIXEL_PIXELFORMAT_PAL8;
3✔
1072
    } else {
1✔
1073
        encoder->capture_pixelformat = pixelformat;
×
1074
    }
1075
    encoder->capture_colorspace = colorspace;
3✔
1076
    encoder->capture_palette_size = palette_bytes;
3✔
1077
    encoder->capture_ncolors = ncolors;
3✔
1078
    encoder->capture_valid = 1;
3✔
1079

1080
cleanup:
2✔
1081
    if (restore_pixelformat && dither != NULL) {
3!
1082
        /*
1083
         * Undo the normalization performed by sixel_dither_apply_palette().
1084
         *
1085
         *     RGBA8888 --capture--> RGB888 (temporary)
1086
         *          \______________________________/
1087
         *                          |
1088
         *                 restore original state for
1089
         *                 the real encoder execution.
1090
         */
1091
        sixel_dither_set_pixelformat(dither, saved_pixelformat);
3✔
1092
    }
1✔
1093
    if (paletted_pixels != NULL && dither_allocator != NULL) {
3!
1094
        sixel_allocator_free(dither_allocator, paletted_pixels);
3✔
1095
    }
1✔
1096

1097
    return status;
3✔
1098
}
1✔
1099

1100
static SIXELSTATUS
1101
sixel_prepare_builtin_palette(
27✔
1102
    sixel_dither_t /* out */ **dither,
1103
    int            /* in */  builtin_palette)
1104
{
1105
    SIXELSTATUS status = SIXEL_FALSE;
27✔
1106

1107
    *dither = sixel_dither_get(builtin_palette);
27✔
1108
    if (*dither == NULL) {
27!
1109
        sixel_helper_set_additional_message(
×
1110
            "sixel_prepare_builtin_palette: sixel_dither_get() failed.");
1111
        status = SIXEL_RUNTIME_ERROR;
×
1112
        goto end;
×
1113
    }
1114

1115
    status = SIXEL_OK;
27✔
1116

1117
end:
18✔
1118
    return status;
27✔
1119
}
1120

1121
static int
1122
sixel_encoder_thumbnail_hint(sixel_encoder_t *encoder)
453✔
1123
{
1124
    int width_hint;
1125
    int height_hint;
1126
    long base;
1127
    long size;
1128

1129
    width_hint = 0;
453✔
1130
    height_hint = 0;
453✔
1131
    base = 0;
453✔
1132
    size = 0;
453✔
1133

1134
    if (encoder == NULL) {
453!
1135
        return 0;
×
1136
    }
1137

1138
    width_hint = encoder->pixelwidth;
453✔
1139
    height_hint = encoder->pixelheight;
453✔
1140

1141
    /* Request extra resolution for downscaling to preserve detail. */
1142
    if (width_hint > 0 && height_hint > 0) {
453✔
1143
        /* Follow the CLI rule: double the larger axis before doubling
1144
         * again for the final request size. */
1145
        if (width_hint >= height_hint) {
9!
1146
            base = (long)width_hint;
9✔
1147
        } else {
3✔
1148
            base = (long)height_hint;
×
1149
        }
1150
        base *= 2L;
9✔
1151
    } else if (width_hint > 0) {
447✔
1152
        base = (long)width_hint;
48✔
1153
    } else if (height_hint > 0) {
412✔
1154
        base = (long)height_hint;
36✔
1155
    } else {
12✔
1156
        return 0;
360✔
1157
    }
1158

1159
    size = base * 2L;
93✔
1160
    if (size > (long)INT_MAX) {
93!
1161
        size = (long)INT_MAX;
×
1162
    }
1163
    if (size < 1L) {
93!
1164
        size = 1L;
×
1165
    }
1166

1167
    return (int)size;
93✔
1168
}
151✔
1169

1170

1171
typedef struct sixel_callback_context_for_mapfile {
1172
    int reqcolors;
1173
    sixel_dither_t *dither;
1174
    sixel_allocator_t *allocator;
1175
    int working_colorspace;
1176
    int lut_policy;
1177
} sixel_callback_context_for_mapfile_t;
1178

1179

1180
/* callback function for sixel_helper_load_image_file() */
1181
static SIXELSTATUS
1182
load_image_callback_for_palette(
21✔
1183
    sixel_frame_t   /* in */    *frame, /* frame object from image loader */
1184
    void            /* in */    *data)  /* private data */
1185
{
1186
    SIXELSTATUS status = SIXEL_FALSE;
21✔
1187
    sixel_callback_context_for_mapfile_t *callback_context;
1188

1189
    /* get callback context object from the private data */
1190
    callback_context = (sixel_callback_context_for_mapfile_t *)data;
21✔
1191

1192
    status = sixel_frame_ensure_colorspace(frame,
28✔
1193
                                           callback_context->working_colorspace);
7✔
1194
    if (SIXEL_FAILED(status)) {
21!
1195
        goto end;
×
1196
    }
1197

1198
    switch (sixel_frame_get_pixelformat(frame)) {
21!
1199
    case SIXEL_PIXELFORMAT_PAL1:
2✔
1200
    case SIXEL_PIXELFORMAT_PAL2:
1201
    case SIXEL_PIXELFORMAT_PAL4:
1202
    case SIXEL_PIXELFORMAT_PAL8:
1203
        if (sixel_frame_get_palette(frame) == NULL) {
3!
1204
            status = SIXEL_LOGIC_ERROR;
×
1205
            goto end;
×
1206
        }
1207
        /* create new dither object */
1208
        status = sixel_dither_new(
3✔
1209
            &callback_context->dither,
1✔
1210
            sixel_frame_get_ncolors(frame),
1✔
1211
            callback_context->allocator);
1✔
1212
        if (SIXEL_FAILED(status)) {
3!
1213
            goto end;
×
1214
        }
1215

1216
        sixel_dither_set_lut_policy(callback_context->dither,
4✔
1217
                                    callback_context->lut_policy);
1✔
1218

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

1260
        sixel_dither_set_lut_policy(callback_context->dither,
24✔
1261
                                    callback_context->lut_policy);
6✔
1262

1263
        /* create adaptive palette from given frame object */
1264
        status = sixel_dither_initialize(callback_context->dither,
24✔
1265
                                         sixel_frame_get_pixels(frame),
6✔
1266
                                         sixel_frame_get_width(frame),
6✔
1267
                                         sixel_frame_get_height(frame),
6✔
1268
                                         sixel_frame_get_pixelformat(frame),
6✔
1269
                                         SIXEL_LARGE_NORM,
1270
                                         SIXEL_REP_CENTER_BOX,
1271
                                         SIXEL_QUALITY_HIGH);
1272
        if (SIXEL_FAILED(status)) {
18!
1273
            sixel_dither_unref(callback_context->dither);
×
1274
            goto end;
×
1275
        }
1276

1277
        /* success */
1278
        status = SIXEL_OK;
18✔
1279

1280
        break;
18✔
1281
    }
7✔
1282

1283
end:
14✔
1284
    return status;
21✔
1285
}
1286

1287

1288
static SIXELSTATUS
1289
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder);
1290

1291

1292
static int
1293
sixel_path_has_extension(char const *path, char const *extension)
63✔
1294
{
1295
    size_t path_len;
1296
    size_t ext_len;
1297
    size_t index;
1298

1299
    path_len = 0u;
63✔
1300
    ext_len = 0u;
63✔
1301
    index = 0u;
63✔
1302

1303
    if (path == NULL || extension == NULL) {
63!
1304
        return 0;
×
1305
    }
1306

1307
    path_len = strlen(path);
63✔
1308
    ext_len = strlen(extension);
63✔
1309
    if (ext_len == 0u || path_len < ext_len) {
63!
1310
        return 0;
×
1311
    }
1312

1313
    for (index = 0u; index < ext_len; ++index) {
144!
1314
        unsigned char path_ch;
1315
        unsigned char ext_ch;
1316

1317
        path_ch = (unsigned char)path[path_len - ext_len + index];
144✔
1318
        ext_ch = (unsigned char)extension[index];
144✔
1319
        if (tolower(path_ch) != tolower(ext_ch)) {
144✔
1320
            return 0;
63✔
1321
        }
1322
    }
27✔
1323

1324
    return 1;
×
1325
}
21✔
1326

1327
typedef enum sixel_palette_format {
1328
    SIXEL_PALETTE_FORMAT_NONE = 0,
1329
    SIXEL_PALETTE_FORMAT_ACT,
1330
    SIXEL_PALETTE_FORMAT_PAL_JASC,
1331
    SIXEL_PALETTE_FORMAT_PAL_RIFF,
1332
    SIXEL_PALETTE_FORMAT_PAL_AUTO,
1333
    SIXEL_PALETTE_FORMAT_GPL
1334
} sixel_palette_format_t;
1335

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

1356
    colon = NULL;
21✔
1357
    type_len = 0u;
21✔
1358
    index = 0u;
21✔
1359

1360
    if (format_hint != NULL) {
21!
1361
        *format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
1362
    }
7✔
1363
    if (spec == NULL) {
21!
1364
        return NULL;
×
1365
    }
1366

1367
    colon = strchr(spec, ':');
21✔
1368
    if (colon == NULL) {
21!
1369
        return spec;
21✔
1370
    }
1371

1372
    type_len = (size_t)(colon - spec);
×
1373
    if (type_len == 0u || type_len >= sizeof(lowered)) {
×
1374
        return spec;
×
1375
    }
1376

1377
    for (index = 0u; index < type_len; ++index) {
×
1378
        lowered[index] = (char)tolower((unsigned char)spec[index]);
×
1379
    }
1380
    lowered[type_len] = '\0';
×
1381

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

1413
    return spec;
×
1414
}
7✔
1415

1416
static sixel_palette_format_t
1417
sixel_palette_format_from_extension(char const *path)
21✔
1418
{
1419
    if (path == NULL) {
21!
1420
        return SIXEL_PALETTE_FORMAT_NONE;
×
1421
    }
1422

1423
    if (sixel_path_has_extension(path, ".act")) {
21!
1424
        return SIXEL_PALETTE_FORMAT_ACT;
×
1425
    }
1426
    if (sixel_path_has_extension(path, ".pal")) {
21!
1427
        return SIXEL_PALETTE_FORMAT_PAL_AUTO;
×
1428
    }
1429
    if (sixel_path_has_extension(path, ".gpl")) {
21!
1430
        return SIXEL_PALETTE_FORMAT_GPL;
×
1431
    }
1432

1433
    return SIXEL_PALETTE_FORMAT_NONE;
21✔
1434
}
7✔
1435

1436
static int
1437
sixel_path_has_any_extension(char const *path)
21✔
1438
{
1439
    char const *slash_forward;
1440
#if defined(_WIN32)
1441
    char const *slash_backward;
1442
#endif
1443
    char const *start;
1444
    char const *dot;
1445

1446
    slash_forward = NULL;
21✔
1447
#if defined(_WIN32)
1448
    slash_backward = NULL;
1449
#endif
1450
    start = path;
21✔
1451
    dot = NULL;
21✔
1452

1453
    if (path == NULL) {
21!
1454
        return 0;
×
1455
    }
1456

1457
    slash_forward = strrchr(path, '/');
21✔
1458
#if defined(_WIN32)
1459
    slash_backward = strrchr(path, '\\');
1460
    if (slash_backward != NULL &&
1461
            (slash_forward == NULL || slash_backward > slash_forward)) {
1462
        slash_forward = slash_backward;
1463
    }
1464
#endif
1465
    if (slash_forward == NULL) {
21!
1466
        start = path;
×
1467
    } else {
1468
        start = slash_forward + 1;
21✔
1469
    }
1470

1471
    dot = strrchr(start, '.');
21✔
1472
    if (dot == NULL) {
21!
1473
        return 0;
×
1474
    }
1475

1476
    if (dot[1] == '\0') {
21!
1477
        return 0;
×
1478
    }
1479

1480
    return 1;
21✔
1481
}
7✔
1482

1483
static int
1484
sixel_palette_has_utf8_bom(unsigned char const *data, size_t size)
×
1485
{
1486
    if (data == NULL || size < 3u) {
×
1487
        return 0;
×
1488
    }
1489
    if (data[0] == 0xefu && data[1] == 0xbbu && data[2] == 0xbfu) {
×
1490
        return 1;
×
1491
    }
1492
    return 0;
×
1493
}
1494

1495

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

1520
    status = SIXEL_FALSE;
×
1521
    buffer = NULL;
×
1522
    grown = NULL;
×
1523
    capacity = 0u;
×
1524
    used = 0u;
×
1525
    read_bytes = 0u;
×
1526
    needed = 0u;
×
1527
    new_capacity = 0u;
×
1528

1529
    if (pdata == NULL || psize == NULL || stream == NULL || allocator == NULL) {
×
1530
        sixel_helper_set_additional_message(
×
1531
            "sixel_palette_read_stream: invalid argument.");
1532
        return SIXEL_BAD_ARGUMENT;
×
1533
    }
1534

1535
    *pdata = NULL;
×
1536
    *psize = 0u;
×
1537

1538
    while (1) {
1539
        read_bytes = fread(scratch, 1, sizeof(scratch), stream);
×
1540
        if (read_bytes == 0u) {
×
1541
            if (ferror(stream)) {
×
1542
                sixel_helper_set_additional_message(
×
1543
                    "sixel_palette_read_stream: fread() failed.");
1544
                status = SIXEL_LIBC_ERROR;
×
1545
                goto cleanup;
×
1546
            }
1547
            break;
×
1548
        }
1549

1550
        if (used > SIZE_MAX - read_bytes) {
×
1551
            sixel_helper_set_additional_message(
×
1552
                "sixel_palette_read_stream: size overflow.");
1553
            status = SIXEL_BAD_ALLOCATION;
×
1554
            goto cleanup;
×
1555
        }
1556
        needed = used + read_bytes;
×
1557

1558
        if (needed > capacity) {
×
1559
            new_capacity = capacity;
×
1560
            if (new_capacity == 0u) {
×
1561
                new_capacity = 4096u;
×
1562
            }
1563
            while (needed > new_capacity) {
×
1564
                if (new_capacity > SIZE_MAX / 2u) {
×
1565
                    sixel_helper_set_additional_message(
×
1566
                        "sixel_palette_read_stream: size overflow.");
1567
                    status = SIXEL_BAD_ALLOCATION;
×
1568
                    goto cleanup;
×
1569
                }
1570
                new_capacity *= 2u;
×
1571
            }
1572

1573
            grown = (unsigned char *)sixel_allocator_malloc(allocator,
×
1574
                                                             new_capacity);
1575
            if (grown == NULL) {
×
1576
                sixel_helper_set_additional_message(
×
1577
                    "sixel_palette_read_stream: allocation failed.");
1578
                status = SIXEL_BAD_ALLOCATION;
×
1579
                goto cleanup;
×
1580
            }
1581

1582
            if (buffer != NULL) {
×
1583
                memcpy(grown, buffer, used);
×
1584
                sixel_allocator_free(allocator, buffer);
×
1585
            }
1586

1587
            buffer = grown;
×
1588
            grown = NULL;
×
1589
            capacity = new_capacity;
×
1590
        }
1591

1592
        memcpy(buffer + used, scratch, read_bytes);
×
1593
        used += read_bytes;
×
1594
    }
1595

1596
    *pdata = buffer;
×
1597
    *psize = used;
×
1598
    status = SIXEL_OK;
×
1599
    return status;
×
1600

1601
cleanup:
1602
    if (grown != NULL) {
×
1603
        sixel_allocator_free(allocator, grown);
×
1604
    }
1605
    if (buffer != NULL) {
×
1606
        sixel_allocator_free(allocator, buffer);
×
1607
    }
1608
    return status;
×
1609
}
1610

1611

1612
static SIXELSTATUS
1613
sixel_palette_open_read(char const *path, FILE **pstream, int *pclose)
×
1614
{
1615
    if (pstream == NULL || pclose == NULL || path == NULL) {
×
1616
        sixel_helper_set_additional_message(
×
1617
            "sixel_palette_open_read: invalid argument.");
1618
        return SIXEL_BAD_ARGUMENT;
×
1619
    }
1620

1621
    if (strcmp(path, "-") == 0) {
×
1622
        *pstream = stdin;
×
1623
        *pclose = 0;
×
1624
        return SIXEL_OK;
×
1625
    }
1626

1627
    *pstream = fopen(path, "rb");
×
1628
    if (*pstream == NULL) {
×
1629
        sixel_helper_set_additional_message(
×
1630
            "sixel_palette_open_read: failed to open file.");
1631
        return SIXEL_LIBC_ERROR;
×
1632
    }
1633

1634
    *pclose = 1;
×
1635
    return SIXEL_OK;
×
1636
}
1637

1638

1639
static void
1640
sixel_palette_close_stream(FILE *stream, int close_stream)
×
1641
{
1642
    if (close_stream && stream != NULL) {
×
1643
        (void) fclose(stream);
×
1644
    }
1645
}
×
1646

1647

1648
static sixel_palette_format_t
1649
sixel_palette_guess_format(unsigned char const *data, size_t size)
×
1650
{
1651
    size_t offset;
1652
    size_t data_size;
1653

1654
    offset = 0u;
×
1655
    data_size = size;
×
1656

1657
    if (data == NULL || size == 0u) {
×
1658
        return SIXEL_PALETTE_FORMAT_NONE;
×
1659
    }
1660

1661
    if (size == 256u * 3u || size == 256u * 3u + 4u) {
×
1662
        return SIXEL_PALETTE_FORMAT_ACT;
×
1663
    }
1664

1665
    if (size >= 12u && memcmp(data, "RIFF", 4) == 0
×
1666
            && memcmp(data + 8, "PAL ", 4) == 0) {
×
1667
        return SIXEL_PALETTE_FORMAT_PAL_RIFF;
×
1668
    }
1669

1670
    if (sixel_palette_has_utf8_bom(data, size)) {
×
1671
        offset = 3u;
×
1672
        data_size = size - 3u;
×
1673
    }
1674

1675
    if (data_size >= 8u && memcmp(data + offset, "JASC-PAL", 8) == 0) {
×
1676
        return SIXEL_PALETTE_FORMAT_PAL_JASC;
×
1677
    }
1678
    if (data_size >= 12u && memcmp(data + offset, "GIMP Palette", 12) == 0) {
×
1679
        return SIXEL_PALETTE_FORMAT_GPL;
×
1680
    }
1681

1682
    return SIXEL_PALETTE_FORMAT_NONE;
×
1683
}
1684

1685

1686
static unsigned int
1687
sixel_palette_read_le16(unsigned char const *ptr)
×
1688
{
1689
    if (ptr == NULL) {
×
1690
        return 0u;
×
1691
    }
1692
    return (unsigned int)ptr[0] | ((unsigned int)ptr[1] << 8);
×
1693
}
1694

1695

1696
static unsigned int
1697
sixel_palette_read_le32(unsigned char const *ptr)
×
1698
{
1699
    if (ptr == NULL) {
×
1700
        return 0u;
×
1701
    }
1702
    return ((unsigned int)ptr[0])
×
1703
        | ((unsigned int)ptr[1] << 8)
×
1704
        | ((unsigned int)ptr[2] << 16)
×
1705
        | ((unsigned int)ptr[3] << 24);
×
1706
}
1707

1708

1709
/*
1710
 * Adobe Color Table (*.act) reader
1711
 *
1712
 *   +-----------+---------------------------+
1713
 *   | section   | bytes                     |
1714
 *   +-----------+---------------------------+
1715
 *   | palette   | 256 entries * 3 RGB bytes |
1716
 *   | trailer   | optional count/start pair |
1717
 *   +-----------+---------------------------+
1718
 */
1719
static SIXELSTATUS
1720
sixel_palette_parse_act(unsigned char const *data,
×
1721
                        size_t size,
1722
                        sixel_encoder_t *encoder,
1723
                        sixel_dither_t **dither)
1724
{
1725
    SIXELSTATUS status;
1726
    sixel_dither_t *local;
1727
    unsigned char const *palette_start;
1728
    unsigned char const *trailer;
1729
    unsigned char *target;
1730
    size_t copy_bytes;
1731
    int exported_colors;
1732
    int start_index;
1733

1734
    status = SIXEL_FALSE;
×
1735
    local = NULL;
×
1736
    palette_start = data;
×
1737
    trailer = NULL;
×
1738
    target = NULL;
×
1739
    copy_bytes = 0u;
×
1740
    exported_colors = 0;
×
1741
    start_index = 0;
×
1742

1743
    if (encoder == NULL || dither == NULL) {
×
1744
        sixel_helper_set_additional_message(
×
1745
            "sixel_palette_parse_act: invalid argument.");
1746
        return SIXEL_BAD_ARGUMENT;
×
1747
    }
1748
    if (data == NULL || size < 256u * 3u) {
×
1749
        sixel_helper_set_additional_message(
×
1750
            "sixel_palette_parse_act: truncated ACT palette.");
1751
        return SIXEL_BAD_INPUT;
×
1752
    }
1753

1754
    if (size == 256u * 3u) {
×
1755
        exported_colors = 256;
×
1756
        start_index = 0;
×
1757
    } else if (size == 256u * 3u + 4u) {
×
1758
        trailer = data + 256u * 3u;
×
1759
        exported_colors = (int)(((unsigned int)trailer[0] << 8)
×
1760
                                | (unsigned int)trailer[1]);
×
1761
        start_index = (int)(((unsigned int)trailer[2] << 8)
×
1762
                            | (unsigned int)trailer[3]);
×
1763
    } else {
1764
        sixel_helper_set_additional_message(
×
1765
            "sixel_palette_parse_act: invalid ACT length.");
1766
        return SIXEL_BAD_INPUT;
×
1767
    }
1768

1769
    if (start_index < 0 || start_index >= 256) {
×
1770
        sixel_helper_set_additional_message(
×
1771
            "sixel_palette_parse_act: ACT start index out of range.");
1772
        return SIXEL_BAD_INPUT;
×
1773
    }
1774
    if (exported_colors <= 0 || exported_colors > 256) {
×
1775
        exported_colors = 256;
×
1776
    }
1777
    if (start_index + exported_colors > 256) {
×
1778
        sixel_helper_set_additional_message(
×
1779
            "sixel_palette_parse_act: ACT palette exceeds 256 slots.");
1780
        return SIXEL_BAD_INPUT;
×
1781
    }
1782

1783
    status = sixel_dither_new(&local, exported_colors, encoder->allocator);
×
1784
    if (SIXEL_FAILED(status)) {
×
1785
        return status;
×
1786
    }
1787

1788
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1789

1790
    target = sixel_dither_get_palette(local);
×
1791
    copy_bytes = (size_t)exported_colors * 3u;
×
1792
    memcpy(target, palette_start + (size_t)start_index * 3u, copy_bytes);
×
1793

1794
    *dither = local;
×
1795
    return SIXEL_OK;
×
1796
}
1797

1798

1799
static SIXELSTATUS
1800
sixel_palette_parse_pal_jasc(unsigned char const *data,
×
1801
                             size_t size,
1802
                             sixel_encoder_t *encoder,
1803
                             sixel_dither_t **dither)
1804
{
1805
    SIXELSTATUS status;
1806
    char *text;
1807
    size_t index;
1808
    size_t offset;
1809
    char *cursor;
1810
    char *line;
1811
    char *line_end;
1812
    int stage;
1813
    int exported_colors;
1814
    int parsed_colors;
1815
    sixel_dither_t *local;
1816
    unsigned char *target;
1817
    long component;
1818
    char *parse_end;
1819
    int value_index;
1820
    int values[3];
1821
    char tail;
1822

1823
    status = SIXEL_FALSE;
×
1824
    text = NULL;
×
1825
    index = 0u;
×
1826
    offset = 0u;
×
1827
    cursor = NULL;
×
1828
    line = NULL;
×
1829
    line_end = NULL;
×
1830
    stage = 0;
×
1831
    exported_colors = 0;
×
1832
    parsed_colors = 0;
×
1833
    local = NULL;
×
1834
    target = NULL;
×
1835
    component = 0;
×
1836
    parse_end = NULL;
×
1837
    value_index = 0;
×
1838
    values[0] = 0;
×
1839
    values[1] = 0;
×
1840
    values[2] = 0;
×
1841

1842
    if (encoder == NULL || dither == NULL) {
×
1843
        sixel_helper_set_additional_message(
×
1844
            "sixel_palette_parse_pal_jasc: invalid argument.");
1845
        return SIXEL_BAD_ARGUMENT;
×
1846
    }
1847
    if (data == NULL || size == 0u) {
×
1848
        sixel_helper_set_additional_message(
×
1849
            "sixel_palette_parse_pal_jasc: empty palette.");
1850
        return SIXEL_BAD_INPUT;
×
1851
    }
1852

1853
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
1854
    if (text == NULL) {
×
1855
        sixel_helper_set_additional_message(
×
1856
            "sixel_palette_parse_pal_jasc: allocation failed.");
1857
        return SIXEL_BAD_ALLOCATION;
×
1858
    }
1859
    memcpy(text, data, size);
×
1860
    text[size] = '\0';
×
1861

1862
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
1863
        offset = 3u;
×
1864
    }
1865
    cursor = text + offset;
×
1866

1867
    while (*cursor != '\0') {
×
1868
        line = cursor;
×
1869
        line_end = cursor;
×
1870
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
1871
            ++line_end;
×
1872
        }
1873
        if (*line_end != '\0') {
×
1874
            *line_end = '\0';
×
1875
            cursor = line_end + 1;
×
1876
        } else {
1877
            cursor = line_end;
×
1878
        }
1879
        while (*cursor == '\n' || *cursor == '\r') {
×
1880
            ++cursor;
×
1881
        }
1882

1883
        while (*line == ' ' || *line == '\t') {
×
1884
            ++line;
×
1885
        }
1886
        index = strlen(line);
×
1887
        while (index > 0u) {
×
1888
            tail = line[index - 1];
×
1889
            if (tail != ' ' && tail != '\t') {
×
1890
                break;
×
1891
            }
1892
            line[index - 1] = '\0';
×
1893
            --index;
×
1894
        }
1895
        if (*line == '\0') {
×
1896
            continue;
×
1897
        }
1898
        if (*line == '#') {
×
1899
            continue;
×
1900
        }
1901

1902
        if (stage == 0) {
×
1903
            if (strcmp(line, "JASC-PAL") != 0) {
×
1904
                sixel_helper_set_additional_message(
×
1905
                    "sixel_palette_parse_pal_jasc: missing header.");
1906
                status = SIXEL_BAD_INPUT;
×
1907
                goto cleanup;
×
1908
            }
1909
            stage = 1;
×
1910
            continue;
×
1911
        }
1912
        if (stage == 1) {
×
1913
            stage = 2;
×
1914
            continue;
×
1915
        }
1916
        if (stage == 2) {
×
1917
            component = strtol(line, &parse_end, 10);
×
1918
            if (parse_end == line || component <= 0L || component > 256L) {
×
1919
                sixel_helper_set_additional_message(
×
1920
                    "sixel_palette_parse_pal_jasc: invalid color count.");
1921
                status = SIXEL_BAD_INPUT;
×
1922
                goto cleanup;
×
1923
            }
1924
            exported_colors = (int)component;
×
1925
            status = sixel_dither_new(&local, exported_colors,
×
1926
                                      encoder->allocator);
1927
            if (SIXEL_FAILED(status)) {
×
1928
                goto cleanup;
×
1929
            }
1930
            sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
1931
            target = sixel_dither_get_palette(local);
×
1932
            stage = 3;
×
1933
            continue;
×
1934
        }
1935

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

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

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

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

1982
    *dither = local;
×
1983
    status = SIXEL_OK;
×
1984

1985
cleanup:
1986
    if (SIXEL_FAILED(status) && local != NULL) {
×
1987
        sixel_dither_unref(local);
×
1988
    }
1989
    if (text != NULL) {
×
1990
        sixel_allocator_free(encoder->allocator, text);
×
1991
    }
1992
    return status;
×
1993
}
1994

1995

1996
static SIXELSTATUS
1997
sixel_palette_parse_pal_riff(unsigned char const *data,
×
1998
                             size_t size,
1999
                             sixel_encoder_t *encoder,
2000
                             sixel_dither_t **dither)
2001
{
2002
    SIXELSTATUS status;
2003
    size_t offset;
2004
    size_t chunk_size;
2005
    sixel_dither_t *local;
2006
    unsigned char const *chunk;
2007
    unsigned char *target;
2008
    unsigned int entry_count;
2009
    unsigned int version;
2010
    unsigned int index;
2011
    size_t palette_offset;
2012

2013
    status = SIXEL_FALSE;
×
2014
    offset = 0u;
×
2015
    chunk_size = 0u;
×
2016
    local = NULL;
×
2017
    chunk = NULL;
×
2018
    target = NULL;
×
2019
    entry_count = 0u;
×
2020
    version = 0u;
×
2021
    index = 0u;
×
2022
    palette_offset = 0u;
×
2023

2024
    if (encoder == NULL || dither == NULL) {
×
2025
        sixel_helper_set_additional_message(
×
2026
            "sixel_palette_parse_pal_riff: invalid argument.");
2027
        return SIXEL_BAD_ARGUMENT;
×
2028
    }
2029
    if (data == NULL || size < 12u) {
×
2030
        sixel_helper_set_additional_message(
×
2031
            "sixel_palette_parse_pal_riff: truncated palette.");
2032
        return SIXEL_BAD_INPUT;
×
2033
    }
2034
    if (memcmp(data, "RIFF", 4) != 0 || memcmp(data + 8, "PAL ", 4) != 0) {
×
2035
        sixel_helper_set_additional_message(
×
2036
            "sixel_palette_parse_pal_riff: missing RIFF header.");
2037
        return SIXEL_BAD_INPUT;
×
2038
    }
2039

2040
    offset = 12u;
×
2041
    while (offset + 8u <= size) {
×
2042
        chunk = data + offset;
×
2043
        chunk_size = (size_t)sixel_palette_read_le32(chunk + 4);
×
2044
        if (offset + 8u + chunk_size > size) {
×
2045
            sixel_helper_set_additional_message(
×
2046
                "sixel_palette_parse_pal_riff: chunk extends past end.");
2047
            return SIXEL_BAD_INPUT;
×
2048
        }
2049
        if (memcmp(chunk, "data", 4) == 0) {
×
2050
            break;
×
2051
        }
2052
        offset += 8u + ((chunk_size + 1u) & ~1u);
×
2053
    }
2054

2055
    if (offset + 8u > size || memcmp(chunk, "data", 4) != 0) {
×
2056
        sixel_helper_set_additional_message(
×
2057
            "sixel_palette_parse_pal_riff: missing data chunk.");
2058
        return SIXEL_BAD_INPUT;
×
2059
    }
2060

2061
    if (chunk_size < 4u) {
×
2062
        sixel_helper_set_additional_message(
×
2063
            "sixel_palette_parse_pal_riff: data chunk too small.");
2064
        return SIXEL_BAD_INPUT;
×
2065
    }
2066
    version = sixel_palette_read_le16(chunk + 8);
×
2067
    (void)version;
2068
    entry_count = sixel_palette_read_le16(chunk + 10);
×
2069
    if (entry_count == 0u || entry_count > 256u) {
×
2070
        sixel_helper_set_additional_message(
×
2071
            "sixel_palette_parse_pal_riff: invalid entry count.");
2072
        return SIXEL_BAD_INPUT;
×
2073
    }
2074
    if (chunk_size != 4u + (size_t)entry_count * 4u) {
×
2075
        sixel_helper_set_additional_message(
×
2076
            "sixel_palette_parse_pal_riff: unexpected chunk size.");
2077
        return SIXEL_BAD_INPUT;
×
2078
    }
2079

2080
    status = sixel_dither_new(&local, (int)entry_count, encoder->allocator);
×
2081
    if (SIXEL_FAILED(status)) {
×
2082
        return status;
×
2083
    }
2084
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2085
    target = sixel_dither_get_palette(local);
×
2086
    palette_offset = 12u;
×
2087
    for (index = 0u; index < entry_count; ++index) {
×
2088
        target[index * 3u + 0u] =
×
2089
            chunk[palette_offset + index * 4u + 0u];
×
2090
        target[index * 3u + 1u] =
×
2091
            chunk[palette_offset + index * 4u + 1u];
×
2092
        target[index * 3u + 2u] =
×
2093
            chunk[palette_offset + index * 4u + 2u];
×
2094
    }
2095

2096
    *dither = local;
×
2097
    return SIXEL_OK;
×
2098
}
2099

2100

2101
static SIXELSTATUS
2102
sixel_palette_parse_gpl(unsigned char const *data,
×
2103
                        size_t size,
2104
                        sixel_encoder_t *encoder,
2105
                        sixel_dither_t **dither)
2106
{
2107
    SIXELSTATUS status;
2108
    char *text;
2109
    size_t offset;
2110
    char *cursor;
2111
    char *line;
2112
    char *line_end;
2113
    size_t index;
2114
    int header_seen;
2115
    int parsed_colors;
2116
    unsigned char palette_bytes[256 * 3];
2117
    long component;
2118
    char *parse_end;
2119
    int value_index;
2120
    int values[3];
2121
    sixel_dither_t *local;
2122
    unsigned char *target;
2123
    char tail;
2124

2125
    status = SIXEL_FALSE;
×
2126
    text = NULL;
×
2127
    offset = 0u;
×
2128
    cursor = NULL;
×
2129
    line = NULL;
×
2130
    line_end = NULL;
×
2131
    index = 0u;
×
2132
    header_seen = 0;
×
2133
    parsed_colors = 0;
×
2134
    component = 0;
×
2135
    parse_end = NULL;
×
2136
    value_index = 0;
×
2137
    values[0] = 0;
×
2138
    values[1] = 0;
×
2139
    values[2] = 0;
×
2140
    local = NULL;
×
2141
    target = NULL;
×
2142

2143
    if (encoder == NULL || dither == NULL) {
×
2144
        sixel_helper_set_additional_message(
×
2145
            "sixel_palette_parse_gpl: invalid argument.");
2146
        return SIXEL_BAD_ARGUMENT;
×
2147
    }
2148
    if (data == NULL || size == 0u) {
×
2149
        sixel_helper_set_additional_message(
×
2150
            "sixel_palette_parse_gpl: empty palette.");
2151
        return SIXEL_BAD_INPUT;
×
2152
    }
2153

2154
    text = (char *)sixel_allocator_malloc(encoder->allocator, size + 1u);
×
2155
    if (text == NULL) {
×
2156
        sixel_helper_set_additional_message(
×
2157
            "sixel_palette_parse_gpl: allocation failed.");
2158
        return SIXEL_BAD_ALLOCATION;
×
2159
    }
2160
    memcpy(text, data, size);
×
2161
    text[size] = '\0';
×
2162

2163
    if (sixel_palette_has_utf8_bom((unsigned char const *)text, size)) {
×
2164
        offset = 3u;
×
2165
    }
2166
    cursor = text + offset;
×
2167

2168
    while (*cursor != '\0') {
×
2169
        line = cursor;
×
2170
        line_end = cursor;
×
2171
        while (*line_end != '\0' && *line_end != '\n' && *line_end != '\r') {
×
2172
            ++line_end;
×
2173
        }
2174
        if (*line_end != '\0') {
×
2175
            *line_end = '\0';
×
2176
            cursor = line_end + 1;
×
2177
        } else {
2178
            cursor = line_end;
×
2179
        }
2180
        while (*cursor == '\n' || *cursor == '\r') {
×
2181
            ++cursor;
×
2182
        }
2183

2184
        while (*line == ' ' || *line == '\t') {
×
2185
            ++line;
×
2186
        }
2187
        index = strlen(line);
×
2188
        while (index > 0u) {
×
2189
            tail = line[index - 1];
×
2190
            if (tail != ' ' && tail != '\t') {
×
2191
                break;
×
2192
            }
2193
            line[index - 1] = '\0';
×
2194
            --index;
×
2195
        }
2196
        if (*line == '\0') {
×
2197
            continue;
×
2198
        }
2199
        if (*line == '#') {
×
2200
            continue;
×
2201
        }
2202
        if (strncmp(line, "Name:", 5) == 0) {
×
2203
            continue;
×
2204
        }
2205
        if (strncmp(line, "Columns:", 8) == 0) {
×
2206
            continue;
×
2207
        }
2208

2209
        if (!header_seen) {
×
2210
            if (strcmp(line, "GIMP Palette") != 0) {
×
2211
                sixel_helper_set_additional_message(
×
2212
                    "sixel_palette_parse_gpl: missing header.");
2213
                status = SIXEL_BAD_INPUT;
×
2214
                goto cleanup;
×
2215
            }
2216
            header_seen = 1;
×
2217
            continue;
×
2218
        }
2219

2220
        if (parsed_colors >= 256) {
×
2221
            sixel_helper_set_additional_message(
×
2222
                "sixel_palette_parse_gpl: too many colors.");
2223
            status = SIXEL_BAD_INPUT;
×
2224
            goto cleanup;
×
2225
        }
2226

2227
        value_index = 0;
×
2228
        while (value_index < 3) {
×
2229
            component = strtol(line, &parse_end, 10);
×
2230
            if (parse_end == line || component < 0L || component > 255L) {
×
2231
                sixel_helper_set_additional_message(
×
2232
                    "sixel_palette_parse_gpl: invalid component.");
2233
                status = SIXEL_BAD_INPUT;
×
2234
                goto cleanup;
×
2235
            }
2236
            values[value_index] = (int)component;
×
2237
            ++value_index;
×
2238
            line = parse_end;
×
2239
            while (*line == ' ' || *line == '\t') {
×
2240
                ++line;
×
2241
            }
2242
        }
2243

2244
        palette_bytes[parsed_colors * 3 + 0] =
×
2245
            (unsigned char)values[0];
×
2246
        palette_bytes[parsed_colors * 3 + 1] =
×
2247
            (unsigned char)values[1];
×
2248
        palette_bytes[parsed_colors * 3 + 2] =
×
2249
            (unsigned char)values[2];
×
2250
        ++parsed_colors;
×
2251
    }
2252

2253
    if (!header_seen) {
×
2254
        sixel_helper_set_additional_message(
×
2255
            "sixel_palette_parse_gpl: header missing.");
2256
        status = SIXEL_BAD_INPUT;
×
2257
        goto cleanup;
×
2258
    }
2259
    if (parsed_colors <= 0) {
×
2260
        sixel_helper_set_additional_message(
×
2261
            "sixel_palette_parse_gpl: no colors parsed.");
2262
        status = SIXEL_BAD_INPUT;
×
2263
        goto cleanup;
×
2264
    }
2265

2266
    status = sixel_dither_new(&local, parsed_colors, encoder->allocator);
×
2267
    if (SIXEL_FAILED(status)) {
×
2268
        goto cleanup;
×
2269
    }
2270
    sixel_dither_set_lut_policy(local, encoder->lut_policy);
×
2271
    target = sixel_dither_get_palette(local);
×
2272
    memcpy(target, palette_bytes, (size_t)parsed_colors * 3u);
×
2273

2274
    *dither = local;
×
2275
    status = SIXEL_OK;
×
2276

2277
cleanup:
2278
    if (SIXEL_FAILED(status) && local != NULL) {
×
2279
        sixel_dither_unref(local);
×
2280
    }
2281
    if (text != NULL) {
×
2282
        sixel_allocator_free(encoder->allocator, text);
×
2283
    }
2284
    return status;
×
2285
}
2286

2287

2288
/*
2289
 * Palette exporters
2290
 *
2291
 *   +----------+-------------------------+
2292
 *   | format   | emission strategy       |
2293
 *   +----------+-------------------------+
2294
 *   | ACT      | fixed 256 entries + EOF |
2295
 *   | PAL JASC | textual lines           |
2296
 *   | PAL RIFF | RIFF container          |
2297
 *   | GPL      | textual lines           |
2298
 *   +----------+-------------------------+
2299
 */
2300
static SIXELSTATUS
2301
sixel_palette_write_act(FILE *stream,
×
2302
                        unsigned char const *palette,
2303
                        int exported_colors)
2304
{
2305
    SIXELSTATUS status;
2306
    unsigned char act_table[256 * 3];
2307
    unsigned char trailer[4];
2308
    size_t exported_bytes;
2309

2310
    status = SIXEL_FALSE;
×
2311
    exported_bytes = 0u;
×
2312

2313
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2314
        return SIXEL_BAD_ARGUMENT;
×
2315
    }
2316
    if (exported_colors > 256) {
×
2317
        exported_colors = 256;
×
2318
    }
2319

2320
    memset(act_table, 0, sizeof(act_table));
×
2321
    exported_bytes = (size_t)exported_colors * 3u;
×
2322
    memcpy(act_table, palette, exported_bytes);
×
2323

2324
    trailer[0] = (unsigned char)(((unsigned int)exported_colors >> 8)
×
2325
                                 & 0xffu);
2326
    trailer[1] = (unsigned char)((unsigned int)exported_colors & 0xffu);
×
2327
    trailer[2] = 0u;
×
2328
    trailer[3] = 0u;
×
2329

2330
    if (fwrite(act_table, 1, sizeof(act_table), stream)
×
2331
            != sizeof(act_table)) {
2332
        status = SIXEL_LIBC_ERROR;
×
2333
        return status;
×
2334
    }
2335
    if (fwrite(trailer, 1, sizeof(trailer), stream)
×
2336
            != sizeof(trailer)) {
2337
        status = SIXEL_LIBC_ERROR;
×
2338
        return status;
×
2339
    }
2340

2341
    return SIXEL_OK;
×
2342
}
2343

2344

2345
static SIXELSTATUS
2346
sixel_palette_write_pal_jasc(FILE *stream,
×
2347
                             unsigned char const *palette,
2348
                             int exported_colors)
2349
{
2350
    int index;
2351

2352
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2353
        return SIXEL_BAD_ARGUMENT;
×
2354
    }
2355
    if (fprintf(stream, "JASC-PAL\n0100\n%d\n", exported_colors) < 0) {
×
2356
        return SIXEL_LIBC_ERROR;
×
2357
    }
2358
    for (index = 0; index < exported_colors; ++index) {
×
2359
        if (fprintf(stream, "%d %d %d\n",
×
2360
                    (int)palette[index * 3 + 0],
×
2361
                    (int)palette[index * 3 + 1],
×
2362
                    (int)palette[index * 3 + 2]) < 0) {
×
2363
            return SIXEL_LIBC_ERROR;
×
2364
        }
2365
    }
2366
    return SIXEL_OK;
×
2367
}
2368

2369

2370
static SIXELSTATUS
2371
sixel_palette_write_pal_riff(FILE *stream,
×
2372
                             unsigned char const *palette,
2373
                             int exported_colors)
2374
{
2375
    unsigned char header[12];
2376
    unsigned char chunk[8];
2377
    unsigned char log_palette[4 + 256 * 4];
2378
    unsigned int data_size;
2379
    unsigned int riff_size;
2380
    int index;
2381

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

2389
    data_size = 4u + (unsigned int)exported_colors * 4u;
×
2390
    riff_size = 4u + 8u + data_size;
×
2391

2392
    memcpy(header, "RIFF", 4);
×
2393
    header[4] = (unsigned char)(riff_size & 0xffu);
×
2394
    header[5] = (unsigned char)((riff_size >> 8) & 0xffu);
×
2395
    header[6] = (unsigned char)((riff_size >> 16) & 0xffu);
×
2396
    header[7] = (unsigned char)((riff_size >> 24) & 0xffu);
×
2397
    memcpy(header + 8, "PAL ", 4);
×
2398

2399
    memcpy(chunk, "data", 4);
×
2400
    chunk[4] = (unsigned char)(data_size & 0xffu);
×
2401
    chunk[5] = (unsigned char)((data_size >> 8) & 0xffu);
×
2402
    chunk[6] = (unsigned char)((data_size >> 16) & 0xffu);
×
2403
    chunk[7] = (unsigned char)((data_size >> 24) & 0xffu);
×
2404

2405
    memset(log_palette, 0, sizeof(log_palette));
×
2406
    log_palette[0] = 0x00;
×
2407
    log_palette[1] = 0x03;
×
2408
    log_palette[2] = (unsigned char)(exported_colors & 0xff);
×
2409
    log_palette[3] = (unsigned char)((exported_colors >> 8) & 0xff);
×
2410
    for (index = 0; index < exported_colors; ++index) {
×
2411
        log_palette[4 + index * 4 + 0] = palette[index * 3 + 0];
×
2412
        log_palette[4 + index * 4 + 1] = palette[index * 3 + 1];
×
2413
        log_palette[4 + index * 4 + 2] = palette[index * 3 + 2];
×
2414
        log_palette[4 + index * 4 + 3] = 0u;
×
2415
    }
2416

2417
    if (fwrite(header, 1, sizeof(header), stream)
×
2418
            != sizeof(header)) {
2419
        return SIXEL_LIBC_ERROR;
×
2420
    }
2421
    if (fwrite(chunk, 1, sizeof(chunk), stream) != sizeof(chunk)) {
×
2422
        return SIXEL_LIBC_ERROR;
×
2423
    }
2424
    if (fwrite(log_palette, 1, (size_t)data_size, stream)
×
2425
            != (size_t)data_size) {
×
2426
        return SIXEL_LIBC_ERROR;
×
2427
    }
2428
    return SIXEL_OK;
×
2429
}
2430

2431

2432
static SIXELSTATUS
2433
sixel_palette_write_gpl(FILE *stream,
×
2434
                        unsigned char const *palette,
2435
                        int exported_colors)
2436
{
2437
    int index;
2438

2439
    if (stream == NULL || palette == NULL || exported_colors <= 0) {
×
2440
        return SIXEL_BAD_ARGUMENT;
×
2441
    }
2442
    if (fprintf(stream, "GIMP Palette\n") < 0) {
×
2443
        return SIXEL_LIBC_ERROR;
×
2444
    }
2445
    if (fprintf(stream, "Name: libsixel export\n") < 0) {
×
2446
        return SIXEL_LIBC_ERROR;
×
2447
    }
2448
    if (fprintf(stream, "Columns: 16\n") < 0) {
×
2449
        return SIXEL_LIBC_ERROR;
×
2450
    }
2451
    if (fprintf(stream, "# Exported by libsixel\n") < 0) {
×
2452
        return SIXEL_LIBC_ERROR;
×
2453
    }
2454
    for (index = 0; index < exported_colors; ++index) {
×
2455
        if (fprintf(stream, "%3d %3d %3d\tIndex %d\n",
×
2456
                    (int)palette[index * 3 + 0],
×
2457
                    (int)palette[index * 3 + 1],
×
2458
                    (int)palette[index * 3 + 2],
×
2459
                    index) < 0) {
2460
            return SIXEL_LIBC_ERROR;
×
2461
        }
2462
    }
2463
    return SIXEL_OK;
×
2464
}
2465

2466

2467
/* create palette from specified map file */
2468
static SIXELSTATUS
2469
sixel_prepare_specified_palette(
21✔
2470
    sixel_dither_t  /* out */   **dither,
2471
    sixel_encoder_t /* in */    *encoder)
2472
{
2473
    SIXELSTATUS status;
2474
    sixel_callback_context_for_mapfile_t callback_context;
2475
    sixel_loader_t *loader;
2476
    int fstatic;
2477
    int fuse_palette;
2478
    int reqcolors;
2479
    int loop_override;
2480
    char const *path;
2481
    sixel_palette_format_t format_hint;
2482
    sixel_palette_format_t format_ext;
2483
    sixel_palette_format_t format_final;
2484
    sixel_palette_format_t format_detected;
2485
    FILE *stream;
2486
    int close_stream;
2487
    unsigned char *buffer;
2488
    size_t buffer_size;
2489
    int palette_request;
2490
    int need_detection;
2491
    int treat_as_image;
2492
    int path_has_extension;
2493

2494
    status = SIXEL_FALSE;
21✔
2495
    loader = NULL;
21✔
2496
    fstatic = 1;
21✔
2497
    fuse_palette = 1;
21✔
2498
    reqcolors = SIXEL_PALETTE_MAX;
21✔
2499
    loop_override = SIXEL_LOOP_DISABLE;
21✔
2500
    path = NULL;
21✔
2501
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
21✔
2502
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
21✔
2503
    format_final = SIXEL_PALETTE_FORMAT_NONE;
21✔
2504
    format_detected = SIXEL_PALETTE_FORMAT_NONE;
21✔
2505
    stream = NULL;
21✔
2506
    close_stream = 0;
21✔
2507
    buffer = NULL;
21✔
2508
    buffer_size = 0u;
21✔
2509
    palette_request = 0;
21✔
2510
    need_detection = 0;
21✔
2511
    treat_as_image = 0;
21✔
2512
    path_has_extension = 0;
21✔
2513

2514
    if (dither == NULL || encoder == NULL || encoder->mapfile == NULL) {
21!
2515
        sixel_helper_set_additional_message(
×
2516
            "sixel_prepare_specified_palette: invalid mapfile path.");
2517
        return SIXEL_BAD_ARGUMENT;
×
2518
    }
2519

2520
    path = sixel_palette_strip_prefix(encoder->mapfile, &format_hint);
21✔
2521
    if (path == NULL || *path == '\0') {
21!
2522
        sixel_helper_set_additional_message(
×
2523
            "sixel_prepare_specified_palette: empty mapfile path.");
2524
        return SIXEL_BAD_ARGUMENT;
×
2525
    }
2526

2527
    format_ext = sixel_palette_format_from_extension(path);
21✔
2528
    path_has_extension = sixel_path_has_any_extension(path);
21✔
2529

2530
    if (format_hint != SIXEL_PALETTE_FORMAT_NONE) {
21!
2531
        palette_request = 1;
×
2532
        format_final = format_hint;
×
2533
    } else if (format_ext != SIXEL_PALETTE_FORMAT_NONE) {
21!
2534
        palette_request = 1;
×
2535
        format_final = format_ext;
×
2536
    } else if (!path_has_extension) {
21!
2537
        palette_request = 1;
×
2538
        need_detection = 1;
×
2539
    } else {
2540
        treat_as_image = 1;
21✔
2541
    }
2542

2543
    if (palette_request) {
21!
2544
        status = sixel_palette_open_read(path, &stream, &close_stream);
×
2545
        if (SIXEL_FAILED(status)) {
×
2546
            goto palette_cleanup;
×
2547
        }
2548
        status = sixel_palette_read_stream(stream,
×
2549
                                           encoder->allocator,
2550
                                           &buffer,
2551
                                           &buffer_size);
2552
        if (close_stream) {
×
2553
            sixel_palette_close_stream(stream, close_stream);
×
2554
            stream = NULL;
×
2555
            close_stream = 0;
×
2556
        }
2557
        if (SIXEL_FAILED(status)) {
×
2558
            goto palette_cleanup;
×
2559
        }
2560
        if (buffer_size == 0u) {
×
2561
            sixel_helper_set_additional_message(
×
2562
                "sixel_prepare_specified_palette: mapfile is empty.");
2563
            status = SIXEL_BAD_INPUT;
×
2564
            goto palette_cleanup;
×
2565
        }
2566

2567
        if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
2568
            format_detected = sixel_palette_guess_format(buffer,
×
2569
                                                         buffer_size);
2570
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2571
                sixel_helper_set_additional_message(
×
2572
                    "sixel_prepare_specified_palette: "
2573
                    "unable to detect palette format.");
2574
                status = SIXEL_BAD_INPUT;
×
2575
                goto palette_cleanup;
×
2576
            }
2577
            format_final = format_detected;
×
2578
        } else if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
2579
            format_detected = sixel_palette_guess_format(buffer,
×
2580
                                                         buffer_size);
2581
            if (format_detected == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
2582
                    format_detected == SIXEL_PALETTE_FORMAT_PAL_RIFF) {
2583
                format_final = format_detected;
×
2584
            } else {
2585
                sixel_helper_set_additional_message(
×
2586
                    "sixel_prepare_specified_palette: "
2587
                    "ambiguous .pal content.");
2588
                status = SIXEL_BAD_INPUT;
×
2589
                goto palette_cleanup;
×
2590
            }
2591
        } else if (need_detection) {
×
2592
            format_detected = sixel_palette_guess_format(buffer,
×
2593
                                                         buffer_size);
2594
            if (format_detected == SIXEL_PALETTE_FORMAT_NONE) {
×
2595
                sixel_helper_set_additional_message(
×
2596
                    "sixel_prepare_specified_palette: "
2597
                    "unable to detect palette format.");
2598
                status = SIXEL_BAD_INPUT;
×
2599
                goto palette_cleanup;
×
2600
            }
2601
            format_final = format_detected;
×
2602
        }
2603

2604
        switch (format_final) {
×
2605
        case SIXEL_PALETTE_FORMAT_ACT:
2606
            status = sixel_palette_parse_act(buffer,
×
2607
                                             buffer_size,
2608
                                             encoder,
2609
                                             dither);
2610
            break;
×
2611
        case SIXEL_PALETTE_FORMAT_PAL_JASC:
2612
            status = sixel_palette_parse_pal_jasc(buffer,
×
2613
                                                  buffer_size,
2614
                                                  encoder,
2615
                                                  dither);
2616
            break;
×
2617
        case SIXEL_PALETTE_FORMAT_PAL_RIFF:
2618
            status = sixel_palette_parse_pal_riff(buffer,
×
2619
                                                  buffer_size,
2620
                                                  encoder,
2621
                                                  dither);
2622
            break;
×
2623
        case SIXEL_PALETTE_FORMAT_GPL:
2624
            status = sixel_palette_parse_gpl(buffer,
×
2625
                                             buffer_size,
2626
                                             encoder,
2627
                                             dither);
2628
            break;
×
2629
        default:
2630
            sixel_helper_set_additional_message(
×
2631
                "sixel_prepare_specified_palette: "
2632
                "unsupported palette format.");
2633
            status = SIXEL_BAD_INPUT;
×
2634
            break;
×
2635
        }
2636

2637
palette_cleanup:
2638
        if (buffer != NULL) {
×
2639
            sixel_allocator_free(encoder->allocator, buffer);
×
2640
            buffer = NULL;
×
2641
        }
2642
        if (stream != NULL) {
×
2643
            sixel_palette_close_stream(stream, close_stream);
×
2644
            stream = NULL;
×
2645
        }
2646
        if (SIXEL_SUCCEEDED(status)) {
×
2647
            return status;
×
2648
        }
2649
        if (!treat_as_image) {
×
2650
            return status;
×
2651
        }
2652
    }
2653

2654
    callback_context.reqcolors = encoder->reqcolors;
21✔
2655
    callback_context.dither = NULL;
21✔
2656
    callback_context.allocator = encoder->allocator;
21✔
2657
    callback_context.working_colorspace = encoder->working_colorspace;
21✔
2658
    callback_context.lut_policy = encoder->lut_policy;
21✔
2659

2660
    sixel_helper_set_loader_trace(encoder->verbose);
21✔
2661
    sixel_helper_set_thumbnail_size_hint(
21✔
2662
        sixel_encoder_thumbnail_hint(encoder));
7✔
2663
    status = sixel_loader_new(&loader, encoder->allocator);
21✔
2664
    if (SIXEL_FAILED(status)) {
21!
2665
        goto end_loader;
×
2666
    }
2667

2668
    status = sixel_loader_setopt(loader,
21✔
2669
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
2670
                                 &fstatic);
2671
    if (SIXEL_FAILED(status)) {
21!
2672
        goto end_loader;
×
2673
    }
2674

2675
    status = sixel_loader_setopt(loader,
21✔
2676
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
2677
                                 &fuse_palette);
2678
    if (SIXEL_FAILED(status)) {
21!
2679
        goto end_loader;
×
2680
    }
2681

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

2689
    status = sixel_loader_setopt(loader,
28✔
2690
                                 SIXEL_LOADER_OPTION_BGCOLOR,
2691
                                 encoder->bgcolor);
21✔
2692
    if (SIXEL_FAILED(status)) {
21!
2693
        goto end_loader;
×
2694
    }
2695

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

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

2710
    status = sixel_loader_setopt(loader,
28✔
2711
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
2712
                                 encoder->cancel_flag);
21✔
2713
    if (SIXEL_FAILED(status)) {
21!
2714
        goto end_loader;
×
2715
    }
2716

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

2724
    status = sixel_loader_setopt(loader,
21✔
2725
                                 SIXEL_LOADER_OPTION_CONTEXT,
2726
                                 &callback_context);
2727
    if (SIXEL_FAILED(status)) {
21!
2728
        goto end_loader;
×
2729
    }
2730

2731
    status = sixel_loader_load_file(loader,
28✔
2732
                                    encoder->mapfile,
21✔
2733
                                    load_image_callback_for_palette);
2734
    if (status != SIXEL_OK) {
21!
2735
        goto end_loader;
×
2736
    }
2737

2738
end_loader:
14✔
2739
    sixel_loader_unref(loader);
21✔
2740

2741
    if (status != SIXEL_OK) {
21!
2742
        return status;
×
2743
    }
2744

2745
    if (! callback_context.dither) {
21!
2746
        sixel_helper_set_additional_message(
×
2747
            "sixel_prepare_specified_palette() failed.\n"
2748
            "reason: mapfile is empty.");
2749
        return SIXEL_BAD_INPUT;
×
2750
    }
2751

2752
    *dither = callback_context.dither;
21✔
2753

2754
    return status;
21✔
2755
}
7✔
2756

2757

2758
/* create dither object from a frame */
2759
static SIXELSTATUS
2760
sixel_encoder_prepare_palette(
525✔
2761
    sixel_encoder_t *encoder,  /* encoder object */
2762
    sixel_frame_t   *frame,    /* input frame object */
2763
    sixel_dither_t  **dither)  /* dither object to be created from the frame */
2764
{
2765
    SIXELSTATUS status = SIXEL_FALSE;
525✔
2766
    int histogram_colors;
2767
    sixel_assessment_t *assessment;
2768
    int promoted_stage;
2769

2770
    assessment = NULL;
525✔
2771
    promoted_stage = 0;
525✔
2772
    if (encoder != NULL) {
525!
2773
        assessment = encoder->assessment_observer;
525✔
2774
    }
181✔
2775

2776
    switch (encoder->color_option) {
525!
2777
    case SIXEL_COLOR_OPTION_HIGHCOLOR:
24✔
2778
        if (encoder->dither_cache) {
36!
2779
            *dither = encoder->dither_cache;
×
2780
            status = SIXEL_OK;
×
2781
        } else {
2782
            status = sixel_dither_new(dither, (-1), encoder->allocator);
36✔
2783
            sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
36✔
2784
        }
2785
        goto end;
36✔
2786
    case SIXEL_COLOR_OPTION_MONOCHROME:
8✔
2787
        if (encoder->dither_cache) {
12!
2788
            *dither = encoder->dither_cache;
×
2789
            status = SIXEL_OK;
×
2790
        } else {
2791
            status = sixel_prepare_monochrome_palette(dither, encoder->finvert);
12✔
2792
        }
2793
        goto end;
12✔
2794
    case SIXEL_COLOR_OPTION_MAPFILE:
14✔
2795
        if (encoder->dither_cache) {
21!
2796
            *dither = encoder->dither_cache;
×
2797
            status = SIXEL_OK;
×
2798
        } else {
2799
            status = sixel_prepare_specified_palette(dither, encoder);
21✔
2800
        }
2801
        goto end;
21✔
2802
    case SIXEL_COLOR_OPTION_BUILTIN:
18✔
2803
        if (encoder->dither_cache) {
27!
2804
            *dither = encoder->dither_cache;
×
2805
            status = SIXEL_OK;
×
2806
        } else {
2807
            status = sixel_prepare_builtin_palette(dither, encoder->builtin_palette);
27✔
2808
        }
2809
        goto end;
27✔
2810
    case SIXEL_COLOR_OPTION_DEFAULT:
429✔
2811
    default:
2812
        break;
429✔
2813
    }
2814

2815
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_PALETTE) {
429✔
2816
        if (!sixel_frame_get_palette(frame)) {
191!
2817
            status = SIXEL_LOGIC_ERROR;
×
2818
            goto end;
×
2819
        }
2820
        status = sixel_dither_new(dither, sixel_frame_get_ncolors(frame),
230✔
2821
                                  encoder->allocator);
39✔
2822
        if (SIXEL_FAILED(status)) {
191!
2823
            goto end;
×
2824
        }
2825
        sixel_dither_set_palette(*dither, sixel_frame_get_palette(frame));
191✔
2826
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
191✔
2827
        if (sixel_frame_get_transparent(frame) != (-1)) {
191!
2828
            sixel_dither_set_transparent(*dither, sixel_frame_get_transparent(frame));
×
2829
        }
2830
        if (*dither && encoder->dither_cache) {
191!
2831
            sixel_dither_unref(encoder->dither_cache);
×
2832
        }
2833
        goto end;
191✔
2834
    }
2835

2836
    if (sixel_frame_get_pixelformat(frame) & SIXEL_FORMATTYPE_GRAYSCALE) {
238!
2837
        switch (sixel_frame_get_pixelformat(frame)) {
×
2838
        case SIXEL_PIXELFORMAT_G1:
2839
            *dither = sixel_dither_get(SIXEL_BUILTIN_G1);
×
2840
            break;
×
2841
        case SIXEL_PIXELFORMAT_G2:
2842
            *dither = sixel_dither_get(SIXEL_BUILTIN_G2);
×
2843
            break;
×
2844
        case SIXEL_PIXELFORMAT_G4:
2845
            *dither = sixel_dither_get(SIXEL_BUILTIN_G4);
×
2846
            break;
×
2847
        case SIXEL_PIXELFORMAT_G8:
2848
            *dither = sixel_dither_get(SIXEL_BUILTIN_G8);
×
2849
            break;
×
2850
        default:
2851
            *dither = NULL;
×
2852
            status = SIXEL_LOGIC_ERROR;
×
2853
            goto end;
×
2854
        }
2855
        if (*dither && encoder->dither_cache) {
×
2856
            sixel_dither_unref(encoder->dither_cache);
×
2857
        }
2858
        sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
×
2859
        status = SIXEL_OK;
×
2860
        goto end;
×
2861
    }
2862

2863
    if (encoder->dither_cache) {
238!
2864
        sixel_dither_unref(encoder->dither_cache);
×
2865
    }
2866
    status = sixel_dither_new(dither, encoder->reqcolors, encoder->allocator);
238✔
2867
    if (SIXEL_FAILED(status)) {
238!
2868
        goto end;
×
2869
    }
2870

2871
    sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
238✔
2872
    sixel_dither_set_sixel_reversible(*dither,
348✔
2873
                                      encoder->sixel_reversible);
110✔
2874

2875
    status = sixel_dither_initialize(*dither,
348✔
2876
                                     sixel_frame_get_pixels(frame),
110✔
2877
                                     sixel_frame_get_width(frame),
110✔
2878
                                     sixel_frame_get_height(frame),
110✔
2879
                                     sixel_frame_get_pixelformat(frame),
110✔
2880
                                     encoder->method_for_largest,
110✔
2881
                                     encoder->method_for_rep,
110✔
2882
                                     encoder->quality_mode);
110✔
2883
    if (SIXEL_FAILED(status)) {
238!
2884
        sixel_dither_unref(*dither);
×
2885
        goto end;
×
2886
    }
2887

2888
    if (assessment != NULL && promoted_stage == 0) {
238!
2889
        sixel_assessment_stage_transition(
3✔
2890
            assessment,
1✔
2891
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2892
        promoted_stage = 1;
3✔
2893
    }
1✔
2894

2895
    histogram_colors = sixel_dither_get_num_of_histogram_colors(*dither);
238✔
2896
    if (histogram_colors <= encoder->reqcolors) {
238✔
2897
        encoder->method_for_diffuse = SIXEL_DIFFUSE_NONE;
172✔
2898
    }
88✔
2899
    sixel_dither_set_pixelformat(*dither, sixel_frame_get_pixelformat(frame));
238✔
2900

2901
    status = SIXEL_OK;
238✔
2902

2903
end:
344✔
2904
    if (assessment != NULL && promoted_stage == 0) {
525!
2905
        sixel_assessment_stage_transition(
×
2906
            assessment,
2907
            SIXEL_ASSESSMENT_STAGE_PALETTE_SOLVE);
2908
        promoted_stage = 1;
×
2909
    }
2910
    if (SIXEL_SUCCEEDED(status) && dither != NULL && *dither != NULL) {
525!
2911
        sixel_dither_set_lut_policy(*dither, encoder->lut_policy);
525✔
2912
        /* pass down the user's demand for an exact palette size */
2913
        (*dither)->force_palette = encoder->force_palette;
525✔
2914
    }
181✔
2915
    return status;
525✔
2916
}
2917

2918

2919
/* resize a frame with settings of specified encoder object */
2920
static SIXELSTATUS
2921
sixel_encoder_do_resize(
532✔
2922
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2923
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2924
{
2925
    SIXELSTATUS status = SIXEL_FALSE;
532✔
2926
    int src_width;
2927
    int src_height;
2928
    int dst_width;
2929
    int dst_height;
2930

2931
    /* get frame width and height */
2932
    src_width = sixel_frame_get_width(frame);
532✔
2933
    src_height = sixel_frame_get_height(frame);
532✔
2934

2935
    if (src_width < 1) {
532✔
2936
         sixel_helper_set_additional_message(
6✔
2937
             "sixel_encoder_do_resize: "
2938
             "detected a frame with a non-positive width.");
2939
        return SIXEL_BAD_ARGUMENT;
6✔
2940
    }
2941

2942
    if (src_height < 1) {
526!
2943
         sixel_helper_set_additional_message(
×
2944
             "sixel_encoder_do_resize: "
2945
             "detected a frame with a non-positive height.");
2946
        return SIXEL_BAD_ARGUMENT;
×
2947
    }
2948

2949
    /* settings around scaling */
2950
    dst_width = encoder->pixelwidth;    /* may be -1 (default) */
526✔
2951
    dst_height = encoder->pixelheight;  /* may be -1 (default) */
526✔
2952

2953
    /* if the encoder has percentwidth or percentheight property,
2954
       convert them to pixelwidth / pixelheight */
2955
    if (encoder->percentwidth > 0) {
526✔
2956
        dst_width = src_width * encoder->percentwidth / 100;
12✔
2957
    }
4✔
2958
    if (encoder->percentheight > 0) {
526✔
2959
        dst_height = src_height * encoder->percentheight / 100;
9✔
2960
    }
3✔
2961

2962
    /* if only either width or height is set, set also the other
2963
       to retain frame aspect ratio */
2964
    if (dst_width > 0 && dst_height <= 0) {
526✔
2965
        dst_height = src_height * dst_width / src_width;
42✔
2966
    }
14✔
2967
    if (dst_height > 0 && dst_width <= 0) {
526✔
2968
        dst_width = src_width * dst_height / src_height;
33✔
2969
    }
11✔
2970

2971
    /* do resize */
2972
    if (dst_width > 0 && dst_height > 0) {
526!
2973
        status = sixel_frame_resize(frame, dst_width, dst_height,
124✔
2974
                                    encoder->method_for_resampling);
31✔
2975
        if (SIXEL_FAILED(status)) {
93!
2976
            goto end;
×
2977
        }
2978
    }
31✔
2979

2980
    /* success */
2981
    status = SIXEL_OK;
526✔
2982

2983
end:
344✔
2984
    return status;
526✔
2985
}
184✔
2986

2987

2988
/* clip a frame with settings of specified encoder object */
2989
static SIXELSTATUS
2990
sixel_encoder_do_clip(
528✔
2991
    sixel_encoder_t /* in */    *encoder,   /* encoder object */
2992
    sixel_frame_t   /* in */    *frame)     /* frame object to be resized */
2993
{
2994
    SIXELSTATUS status = SIXEL_FALSE;
528✔
2995
    int src_width;
2996
    int src_height;
2997
    int clip_x;
2998
    int clip_y;
2999
    int clip_w;
3000
    int clip_h;
3001

3002
    /* get frame width and height */
3003
    src_width = sixel_frame_get_width(frame);
528✔
3004
    src_height = sixel_frame_get_height(frame);
528✔
3005

3006
    /* settings around clipping */
3007
    clip_x = encoder->clipx;
528✔
3008
    clip_y = encoder->clipy;
528✔
3009
    clip_w = encoder->clipwidth;
528✔
3010
    clip_h = encoder->clipheight;
528✔
3011

3012
    /* adjust clipping width with comparing it to frame width */
3013
    if (clip_w + clip_x > src_width) {
528✔
3014
        if (clip_x > src_width) {
7✔
3015
            clip_w = 0;
3✔
3016
        } else {
1✔
3017
            clip_w = src_width - clip_x;
4✔
3018
        }
3019
    }
3✔
3020

3021
    /* adjust clipping height with comparing it to frame height */
3022
    if (clip_h + clip_y > src_height) {
528✔
3023
        if (clip_y > src_height) {
6✔
3024
            clip_h = 0;
3✔
3025
        } else {
1✔
3026
            clip_h = src_height - clip_y;
3✔
3027
        }
3028
    }
2✔
3029

3030
    /* do clipping */
3031
    if (clip_w > 0 && clip_h > 0) {
528!
3032
        status = sixel_frame_clip(frame, clip_x, clip_y, clip_w, clip_h);
15✔
3033
        if (SIXEL_FAILED(status)) {
15!
3034
            goto end;
3✔
3035
        }
3036
    }
4✔
3037

3038
    /* success */
3039
    status = SIXEL_OK;
525✔
3040

3041
end:
344✔
3042
    return status;
528✔
3043
}
3044

3045

3046
static void
3047
sixel_debug_print_palette(
3✔
3048
    sixel_dither_t /* in */ *dither /* dithering object */
3049
)
3050
{
3051
    unsigned char *palette;
3052
    int i;
3053

3054
    palette = sixel_dither_get_palette(dither);
3✔
3055
    fprintf(stderr, "palette:\n");
3✔
3056
    for (i = 0; i < sixel_dither_get_num_of_palette_colors(dither); ++i) {
51✔
3057
        fprintf(stderr, "%d: #%02x%02x%02x\n", i,
64✔
3058
                palette[i * 3 + 0],
48✔
3059
                palette[i * 3 + 1],
48✔
3060
                palette[i * 3 + 2]);
48✔
3061
    }
16✔
3062
}
3✔
3063

3064

3065
static SIXELSTATUS
3066
sixel_encoder_output_without_macro(
432✔
3067
    sixel_frame_t       /* in */ *frame,
3068
    sixel_dither_t      /* in */ *dither,
3069
    sixel_output_t      /* in */ *output,
3070
    sixel_encoder_t     /* in */ *encoder)
3071
{
3072
    SIXELSTATUS status = SIXEL_OK;
432✔
3073
    static unsigned char *p;
3074
    int depth;
3075
    enum { message_buffer_size = 2048 };
3076
    char message[message_buffer_size];
3077
    int nwrite;
3078
#if defined(HAVE_NANOSLEEP) || defined(HAVE_NANOSLEEP_WIN)
3079
    int dulation;
3080
    int delay;
3081
    struct timespec tv;
3082
#endif
3083
    unsigned char *pixbuf;
3084
    int width;
3085
    int height;
3086
    int pixelformat = 0;
432✔
3087
    size_t size;
3088
    int frame_colorspace = SIXEL_COLORSPACE_GAMMA;
432✔
3089
    palette_conversion_t palette_ctx;
3090

3091
    memset(&palette_ctx, 0, sizeof(palette_ctx));
432✔
3092
#if defined(HAVE_CLOCK) || defined(HAVE_CLOCK_WIN)
3093
    sixel_clock_t last_clock;
3094
#endif
3095

3096
    if (encoder == NULL) {
432!
3097
        sixel_helper_set_additional_message(
×
3098
            "sixel_encoder_output_without_macro: encoder object is null.");
3099
        status = SIXEL_BAD_ARGUMENT;
×
3100
        goto end;
×
3101
    }
3102

3103
    if (encoder->assessment_observer != NULL) {
432✔
3104
        sixel_assessment_stage_transition(
3✔
3105
            encoder->assessment_observer,
3✔
3106
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3107
    }
1✔
3108

3109
    if (encoder->color_option == SIXEL_COLOR_OPTION_DEFAULT) {
432✔
3110
        if (encoder->force_palette) {
336!
3111
            /* keep every slot when the user forced the palette size */
3112
            sixel_dither_set_optimize_palette(dither, 0);
×
3113
        } else {
3114
            sixel_dither_set_optimize_palette(dither, 1);
336✔
3115
        }
3116
    }
112✔
3117

3118
    pixelformat = sixel_frame_get_pixelformat(frame);
432✔
3119
    frame_colorspace = sixel_frame_get_colorspace(frame);
432✔
3120
    output->pixelformat = pixelformat;
432✔
3121
    output->source_colorspace = frame_colorspace;
432✔
3122
    output->colorspace = encoder->output_colorspace;
432✔
3123
    sixel_dither_set_pixelformat(dither, pixelformat);
432✔
3124
    depth = sixel_helper_compute_depth(pixelformat);
432✔
3125
    if (depth < 0) {
432!
3126
        status = SIXEL_LOGIC_ERROR;
×
3127
        nwrite = sixel_compat_snprintf(
×
3128
            message,
3129
            sizeof(message),
3130
            "sixel_encoder_output_without_macro: "
3131
            "sixel_helper_compute_depth(%08x) failed.",
3132
            pixelformat);
3133
        if (nwrite > 0) {
×
3134
            sixel_helper_set_additional_message(message);
×
3135
        }
3136
        goto end;
×
3137
    }
3138

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

3194
    pixbuf = sixel_frame_get_pixels(frame);
432✔
3195
    memcpy(p, pixbuf, (size_t)(width * height * depth));
432✔
3196

3197
    status = sixel_output_convert_colorspace(output, p, size);
432✔
3198
    if (SIXEL_FAILED(status)) {
432!
3199
        goto end;
×
3200
    }
3201

3202
    if (encoder->cancel_flag && *encoder->cancel_flag) {
432!
3203
        goto end;
×
3204
    }
3205

3206
    status = sixel_encoder_convert_palette(encoder,
576✔
3207
                                           output,
144✔
3208
                                           dither,
144✔
3209
                                           frame_colorspace,
144✔
3210
                                           pixelformat,
144✔
3211
                                           &palette_ctx);
3212
    if (SIXEL_FAILED(status)) {
432!
3213
        goto end;
×
3214
    }
3215

3216
    if (encoder->capture_quantized) {
432✔
3217
        status = sixel_encoder_capture_quantized(encoder,
4✔
3218
                                                 dither,
1✔
3219
                                                 p,
1✔
3220
                                                 size,
1✔
3221
                                                 width,
1✔
3222
                                                 height,
1✔
3223
                                                 pixelformat,
1✔
3224
                                                 output->colorspace);
1✔
3225
        if (SIXEL_FAILED(status)) {
3!
3226
            goto end;
×
3227
        }
3228
    }
1✔
3229

3230
    if (encoder->assessment_observer != NULL) {
432✔
3231
        sixel_assessment_stage_transition(
3✔
3232
            encoder->assessment_observer,
3✔
3233
            SIXEL_ASSESSMENT_STAGE_ENCODE);
3234
    }
1✔
3235
    status = sixel_encode(p, width, height, depth, dither, output);
432✔
3236
    if (encoder->assessment_observer != NULL) {
432✔
3237
        sixel_assessment_stage_finish(encoder->assessment_observer);
3✔
3238
    }
1✔
3239
    if (status != SIXEL_OK) {
432!
3240
        goto end;
×
3241
    }
3242

3243
end:
288✔
3244
    sixel_encoder_restore_palette(encoder, dither, &palette_ctx);
432✔
3245
    output->pixelformat = pixelformat;
432✔
3246
    output->source_colorspace = frame_colorspace;
432✔
3247
    sixel_allocator_free(encoder->allocator, p);
432✔
3248

3249
    return status;
432✔
3250
}
3251

3252

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

3286
    memset(&palette_ctx, 0, sizeof(palette_ctx));
93✔
3287

3288
    if (encoder != NULL && encoder->assessment_observer != NULL) {
93!
3289
        sixel_assessment_stage_transition(
×
3290
            encoder->assessment_observer,
×
3291
            SIXEL_ASSESSMENT_STAGE_PALETTE_APPLY);
3292
    }
3293

3294
#if defined(HAVE_CLOCK)
3295
    if (output->last_clock == 0) {
93!
3296
        output->last_clock = clock();
93✔
3297
    }
37✔
3298
#elif defined(HAVE_CLOCK_WIN)
3299
    if (output->last_clock == 0) {
3300
        output->last_clock = clock_win();
3301
    }
3302
#endif
3303

3304
    width = sixel_frame_get_width(frame);
93✔
3305
    height = sixel_frame_get_height(frame);
93✔
3306
    pixelformat = sixel_frame_get_pixelformat(frame);
93✔
3307
    depth = sixel_helper_compute_depth(pixelformat);
93✔
3308
    if (depth < 0) {
93!
3309
        status = SIXEL_LOGIC_ERROR;
×
3310
        sixel_helper_set_additional_message(
×
3311
            "sixel_encoder_output_with_macro: "
3312
            "sixel_helper_compute_depth() failed.");
3313
        goto end;
×
3314
    }
3315

3316
    frame_colorspace = sixel_frame_get_colorspace(frame);
93✔
3317
    size = (size_t)width * (size_t)height * (size_t)depth;
93✔
3318
    converted = (unsigned char *)sixel_allocator_malloc(
93✔
3319
        encoder->allocator, size);
37✔
3320
    if (converted == NULL) {
93!
3321
        sixel_helper_set_additional_message(
×
3322
            "sixel_encoder_output_with_macro: "
3323
            "sixel_allocator_malloc() failed.");
3324
        status = SIXEL_BAD_ALLOCATION;
×
3325
        goto end;
×
3326
    }
3327

3328
    memcpy(converted, sixel_frame_get_pixels(frame), size);
93✔
3329
    output->pixelformat = pixelformat;
93✔
3330
    output->source_colorspace = frame_colorspace;
93✔
3331
    output->colorspace = encoder->output_colorspace;
93✔
3332
    status = sixel_output_convert_colorspace(output, converted, size);
93✔
3333
    if (SIXEL_FAILED(status)) {
93!
3334
        goto end;
×
3335
    }
3336

3337
    status = sixel_encoder_convert_palette(encoder,
130✔
3338
                                           output,
37✔
3339
                                           dither,
37✔
3340
                                           frame_colorspace,
37✔
3341
                                           pixelformat,
37✔
3342
                                           &palette_ctx);
3343
    if (SIXEL_FAILED(status)) {
93!
3344
        goto end;
×
3345
    }
3346

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

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

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

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

3527
    return status;
93✔
3528
}
3529

3530

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

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

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

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

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

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

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

3627
    status = SIXEL_OK;
×
3628

3629
end:
3630
    return status;
×
3631
}
3632

3633

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

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

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

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

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

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

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

3735
    status = SIXEL_OK;
×
3736

3737
end:
3738
    return status;
×
3739
}
3740

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

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

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

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

3832
    if (assessment != NULL) {
525✔
3833
        sixel_assessment_stage_transition(
3✔
3834
            assessment,
1✔
3835
            SIXEL_ASSESSMENT_STAGE_COLORSPACE);
3836
    }
1✔
3837

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

3844
    if (assessment != NULL) {
525✔
3845
        sixel_assessment_stage_transition(
3✔
3846
            assessment,
1✔
3847
            SIXEL_ASSESSMENT_STAGE_PALETTE_HISTOGRAM);
3848
    }
1✔
3849

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4065

4066
end:
344✔
4067
    if (output) {
534✔
4068
        sixel_output_unref(output);
525✔
4069
    }
181✔
4070
    if (dither) {
534✔
4071
        sixel_dither_unref(dither);
525✔
4072
    }
181✔
4073

4074
    return status;
534✔
4075
}
4076

4077

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

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

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

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

4229
    goto end;
540✔
4230

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

4236
end:
360✔
4237
#if HAVE__DUPENV_S
4238
    free(env_default_bgcolor);
4239
    free(env_default_ncolors);
4240
#endif  /* HAVE__DUPENV_S */
4241
    return status;
540✔
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)
540✔
4264
{
4265
    sixel_allocator_t *allocator;
4266

4267
    if (encoder) {
540!
4268
        allocator = encoder->allocator;
540✔
4269
        sixel_allocator_free(allocator, encoder->mapfile);
540✔
4270
        sixel_allocator_free(allocator, encoder->palette_output);
540✔
4271
        sixel_allocator_free(allocator, encoder->loader_order);
540✔
4272
        sixel_allocator_free(allocator, encoder->bgcolor);
540✔
4273
        sixel_dither_unref(encoder->dither_cache);
540✔
4274
        if (encoder->outfd
550!
4275
            && encoder->outfd != STDOUT_FILENO
540!
4276
            && encoder->outfd != STDERR_FILENO) {
200!
4277
            (void)sixel_compat_close(encoder->outfd);
30✔
4278
        }
10✔
4279
        if (encoder->tile_outfd >= 0
540!
4280
            && encoder->tile_outfd != encoder->outfd
180!
4281
            && encoder->tile_outfd != STDOUT_FILENO
×
4282
            && encoder->tile_outfd != STDERR_FILENO) {
×
4283
            (void)sixel_compat_close(encoder->tile_outfd);
×
4284
        }
4285
        if (encoder->capture_source_frame != NULL) {
540✔
4286
            sixel_frame_unref(encoder->capture_source_frame);
3✔
4287
        }
1✔
4288
        sixel_allocator_free(allocator, encoder->capture_pixels);
540✔
4289
        sixel_allocator_free(allocator, encoder->capture_palette);
540✔
4290
        sixel_allocator_free(allocator, encoder);
540✔
4291
        sixel_allocator_unref(allocator);
540✔
4292
    }
180✔
4293
}
540✔
4294

4295

4296
/* increase reference count of encoder object (thread-unsafe) */
4297
SIXELAPI void
4298
sixel_encoder_ref(sixel_encoder_t *encoder)
1,137✔
4299
{
4300
    /* TODO: be thread safe */
4301
    ++encoder->ref;
1,137✔
4302
}
1,137✔
4303

4304

4305
/* decrease reference count of encoder object (thread-unsafe) */
4306
SIXELAPI void
4307
sixel_encoder_unref(sixel_encoder_t *encoder)
1,677✔
4308
{
4309
    /* TODO: be thread safe */
4310
    if (encoder != NULL && --encoder->ref == 0) {
1,677!
4311
        sixel_encoder_destroy(encoder);
540✔
4312
    }
180✔
4313
}
1,677✔
4314

4315

4316
/* set cancel state flag to encoder object */
4317
SIXELAPI SIXELSTATUS
4318
sixel_encoder_set_cancel_flag(
435✔
4319
    sixel_encoder_t /* in */ *encoder,
4320
    int             /* in */ *cancel_flag
4321
)
4322
{
4323
    SIXELSTATUS status = SIXEL_OK;
435✔
4324

4325
    encoder->cancel_flag = cancel_flag;
435✔
4326

4327
    return status;
435✔
4328
}
4329

4330

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

4364
    sixel_encoder_ref(encoder);
705✔
4365
    opt_copy = NULL;
705✔
4366

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

5136
            if (len >= sizeof(lowered)) {
×
5137
                sixel_helper_set_additional_message(
×
5138
                    "specified working colorspace name is too long.");
5139
                status = SIXEL_BAD_ARGUMENT;
×
5140
                goto end;
×
5141
            }
5142
            for (i = 0; i < len; ++i) {
×
5143
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5144
            }
5145
            lowered[len] = '\0';
×
5146

5147
            match_result = sixel_match_option_choice(
×
5148
                lowered,
5149
                g_option_choices_working_colorspace,
5150
                sizeof(g_option_choices_working_colorspace) /
5151
                sizeof(g_option_choices_working_colorspace[0]),
5152
                &match_value,
5153
                match_detail,
5154
                sizeof(match_detail));
5155
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5156
                encoder->working_colorspace = match_value;
×
5157
            } else {
5158
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5159
                    sixel_report_ambiguous_prefix(
×
5160
                        "--working-colorspace",
5161
                        value,
5162
                        match_detail,
5163
                        match_message,
5164
                        sizeof(match_message));
5165
                } else {
5166
                    sixel_helper_set_additional_message(
×
5167
                        "unsupported working colorspace specified.");
5168
                }
5169
                status = SIXEL_BAD_ARGUMENT;
×
5170
                goto end;
×
5171
            }
5172
        }
5173
        break;
×
5174
    case SIXEL_OPTFLAG_OUTPUT_COLORSPACE:  /* U */
5175
        if (value == NULL) {
×
5176
            sixel_helper_set_additional_message(
×
5177
                "output-colorspace requires an argument.");
5178
            status = SIXEL_BAD_ARGUMENT;
×
5179
            goto end;
×
5180
        } else {
5181
            len = strlen(value);
×
5182

5183
            if (len >= sizeof(lowered)) {
×
5184
                sixel_helper_set_additional_message(
×
5185
                    "specified output colorspace name is too long.");
5186
                status = SIXEL_BAD_ARGUMENT;
×
5187
                goto end;
×
5188
            }
5189
            for (i = 0; i < len; ++i) {
×
5190
                lowered[i] = (char)tolower((unsigned char)value[i]);
×
5191
            }
5192
            lowered[len] = '\0';
×
5193

5194
            match_result = sixel_match_option_choice(
×
5195
                lowered,
5196
                g_option_choices_output_colorspace,
5197
                sizeof(g_option_choices_output_colorspace) /
5198
                sizeof(g_option_choices_output_colorspace[0]),
5199
                &match_value,
5200
                match_detail,
5201
                sizeof(match_detail));
5202
            if (match_result == SIXEL_OPTION_CHOICE_MATCH) {
×
5203
                encoder->output_colorspace = match_value;
×
5204
            } else {
5205
                if (match_result == SIXEL_OPTION_CHOICE_AMBIGUOUS) {
×
5206
                    sixel_report_ambiguous_prefix(
×
5207
                        "--output-colorspace",
5208
                        value,
5209
                        match_detail,
5210
                        match_message,
5211
                        sizeof(match_message));
5212
                } else {
5213
                    sixel_helper_set_additional_message(
×
5214
                        "unsupported output colorspace specified.");
5215
                }
5216
                status = SIXEL_BAD_ARGUMENT;
×
5217
                goto end;
×
5218
            }
5219
        }
5220
        break;
×
5221
    case SIXEL_OPTFLAG_ORMODE:  /* O */
5222
        encoder->ormode = 1;
×
5223
        break;
×
5224
    case SIXEL_OPTFLAG_COMPLEXION_SCORE:  /* C */
6✔
5225
        encoder->complexion = atoi(value);
9✔
5226
        if (encoder->complexion < 1) {
9✔
5227
            sixel_helper_set_additional_message(
3✔
5228
                "complexion parameter must be 1 or more.");
5229
            status = SIXEL_BAD_ARGUMENT;
3✔
5230
            goto end;
3✔
5231
        }
5232
        break;
6✔
5233
    case SIXEL_OPTFLAG_PIPE_MODE:  /* D */
5234
        encoder->pipe_mode = 1;
×
5235
        break;
×
5236
    case '?':  /* unknown option */
×
5237
    default:
5238
        /* exit if unknown options are specified */
5239
        sixel_helper_set_additional_message(
×
5240
            "unknown option is specified.");
5241
        status = SIXEL_BAD_ARGUMENT;
×
5242
        goto end;
×
5243
    }
5244

5245
    /* detects arguments conflictions */
5246
    if (encoder->reqcolors != (-1)) {
630✔
5247
        switch (encoder->color_option) {
99!
5248
        case SIXEL_COLOR_OPTION_MAPFILE:
5249
            sixel_helper_set_additional_message(
×
5250
                "option -p, --colors conflicts with -m, --mapfile.");
5251
            status = SIXEL_BAD_ARGUMENT;
×
5252
            goto end;
×
5253
        case SIXEL_COLOR_OPTION_MONOCHROME:
2✔
5254
            sixel_helper_set_additional_message(
3✔
5255
                "option -e, --monochrome conflicts with -p, --colors.");
5256
            status = SIXEL_BAD_ARGUMENT;
3✔
5257
            goto end;
3✔
5258
        case SIXEL_COLOR_OPTION_HIGHCOLOR:
2✔
5259
            sixel_helper_set_additional_message(
3✔
5260
                "option -p, --colors conflicts with -I, --high-color.");
5261
            status = SIXEL_BAD_ARGUMENT;
3✔
5262
            goto end;
3✔
5263
        case SIXEL_COLOR_OPTION_BUILTIN:
2✔
5264
            sixel_helper_set_additional_message(
3✔
5265
                "option -p, --colors conflicts with -b, --builtin-palette.");
5266
            status = SIXEL_BAD_ARGUMENT;
3✔
5267
            goto end;
3✔
5268
        default:
60✔
5269
            break;
90✔
5270
        }
5271
    }
30✔
5272

5273
    /* 8bit output option(-8) conflicts width GNU Screen integration(-P) */
5274
    if (encoder->f8bit && encoder->penetrate_multiplexer) {
621✔
5275
        sixel_helper_set_additional_message(
3✔
5276
            "option -8 --8bit-mode conflicts"
5277
            " with -P, --penetrate.");
5278
        status = SIXEL_BAD_ARGUMENT;
3✔
5279
        goto end;
3✔
5280
    }
5281

5282
    status = SIXEL_OK;
618✔
5283

5284
end:
470✔
5285
    if (opt_copy != NULL) {
705!
5286
        sixel_allocator_free(encoder->allocator, opt_copy);
×
5287
    }
5288
    sixel_encoder_unref(encoder);
705✔
5289

5290
    return status;
705✔
5291
}
5292

5293

5294
/* called when image loader component load a image frame */
5295
static SIXELSTATUS
5296
load_image_callback(sixel_frame_t *frame, void *data)
534✔
5297
{
5298
    sixel_encoder_t *encoder;
5299

5300
    encoder = (sixel_encoder_t *)data;
534✔
5301
    if (encoder->capture_source && encoder->capture_source_frame == NULL) {
534!
5302
        sixel_frame_ref(frame);
3✔
5303
        encoder->capture_source_frame = frame;
3✔
5304
    }
1✔
5305

5306
    return sixel_encoder_encode_frame(encoder, frame, NULL);
534✔
5307
}
5308

5309

5310
/* load source data from specified file and encode it to SIXEL format
5311
 * output to encoder->outfd */
5312
SIXELAPI SIXELSTATUS
5313
sixel_encoder_encode(
432✔
5314
    sixel_encoder_t *encoder,   /* encoder object */
5315
    char const      *filename)  /* input filename */
5316
{
5317
    SIXELSTATUS status = SIXEL_FALSE;
432✔
5318
    SIXELSTATUS palette_status = SIXEL_OK;
432✔
5319
    int fuse_palette = 1;
432✔
5320
    sixel_loader_t *loader;
5321

5322
    if (encoder == NULL) {
432!
5323
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5324
#  pragma GCC diagnostic push
5325
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
5326
#endif
5327
        encoder = sixel_encoder_create();
×
5328
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5329
#  pragma GCC diagnostic pop
5330
#endif
5331
        if (encoder == NULL) {
×
5332
            sixel_helper_set_additional_message(
×
5333
                "sixel_encoder_encode: sixel_encoder_create() failed.");
5334
            status = SIXEL_BAD_ALLOCATION;
×
5335
            goto end;
×
5336
        }
5337
    } else {
5338
        sixel_encoder_ref(encoder);
432✔
5339
    }
5340

5341
    if (encoder->assessment_observer != NULL) {
432✔
5342
        sixel_assessment_stage_transition(
3✔
5343
            encoder->assessment_observer,
3✔
5344
            SIXEL_ASSESSMENT_STAGE_IMAGE_CHUNK);
5345
    }
1✔
5346
    encoder->last_loader_name[0] = '\0';
432✔
5347
    encoder->last_source_path[0] = '\0';
432✔
5348
    encoder->last_input_bytes = 0u;
432✔
5349

5350
    /* if required color is not set, set the max value */
5351
    if (encoder->reqcolors == (-1)) {
432✔
5352
        encoder->reqcolors = SIXEL_PALETTE_MAX;
414✔
5353
    }
138✔
5354

5355
    if (encoder->capture_source && encoder->capture_source_frame != NULL) {
432!
5356
        sixel_frame_unref(encoder->capture_source_frame);
×
5357
        encoder->capture_source_frame = NULL;
×
5358
    }
5359

5360
    /* if required color is less then 2, set the min value */
5361
    if (encoder->reqcolors < 2) {
432✔
5362
        encoder->reqcolors = SIXEL_PALETTE_MIN;
3✔
5363
    }
1✔
5364

5365
    /* if color space option is not set, choose RGB color space */
5366
    if (encoder->palette_type == SIXEL_PALETTETYPE_AUTO) {
432✔
5367
        encoder->palette_type = SIXEL_PALETTETYPE_RGB;
414✔
5368
    }
138✔
5369

5370
    /* if color option is not default value, prohibit to read
5371
       the file as a paletted image */
5372
    if (encoder->color_option != SIXEL_COLOR_OPTION_DEFAULT) {
432✔
5373
        fuse_palette = 0;
96✔
5374
    }
32✔
5375

5376
    /* if scaling options are set, prohibit to read the file as
5377
       a paletted image */
5378
    if (encoder->percentwidth > 0 ||
553✔
5379
        encoder->percentheight > 0 ||
420✔
5380
        encoder->pixelwidth > 0 ||
414✔
5381
        encoder->pixelheight > 0) {
396✔
5382
        fuse_palette = 0;
99✔
5383
    }
33✔
5384

5385
reload:
288✔
5386
    loader = NULL;
432✔
5387

5388
    sixel_helper_set_loader_trace(encoder->verbose);
432✔
5389
    sixel_helper_set_thumbnail_size_hint(
432✔
5390
        sixel_encoder_thumbnail_hint(encoder));
144✔
5391

5392
    status = sixel_loader_new(&loader, encoder->allocator);
432✔
5393
    if (SIXEL_FAILED(status)) {
432!
5394
        goto load_end;
×
5395
    }
5396

5397
    status = sixel_loader_setopt(loader,
576✔
5398
                                 SIXEL_LOADER_OPTION_REQUIRE_STATIC,
5399
                                 &encoder->fstatic);
432✔
5400
    if (SIXEL_FAILED(status)) {
432!
5401
        goto load_end;
×
5402
    }
5403

5404
    status = sixel_loader_setopt(loader,
432✔
5405
                                 SIXEL_LOADER_OPTION_USE_PALETTE,
5406
                                 &fuse_palette);
5407
    if (SIXEL_FAILED(status)) {
432!
5408
        goto load_end;
×
5409
    }
5410

5411
    status = sixel_loader_setopt(loader,
576✔
5412
                                 SIXEL_LOADER_OPTION_REQCOLORS,
5413
                                 &encoder->reqcolors);
432✔
5414
    if (SIXEL_FAILED(status)) {
432!
5415
        goto load_end;
×
5416
    }
5417

5418
    status = sixel_loader_setopt(loader,
576✔
5419
                                 SIXEL_LOADER_OPTION_BGCOLOR,
5420
                                 encoder->bgcolor);
432✔
5421
    if (SIXEL_FAILED(status)) {
432!
5422
        goto load_end;
×
5423
    }
5424

5425
    status = sixel_loader_setopt(loader,
576✔
5426
                                 SIXEL_LOADER_OPTION_LOOP_CONTROL,
5427
                                 &encoder->loop_mode);
432✔
5428
    if (SIXEL_FAILED(status)) {
432!
5429
        goto load_end;
×
5430
    }
5431

5432
    status = sixel_loader_setopt(loader,
576✔
5433
                                 SIXEL_LOADER_OPTION_INSECURE,
5434
                                 &encoder->finsecure);
432✔
5435
    if (SIXEL_FAILED(status)) {
432!
5436
        goto load_end;
×
5437
    }
5438

5439
    status = sixel_loader_setopt(loader,
576✔
5440
                                 SIXEL_LOADER_OPTION_CANCEL_FLAG,
5441
                                 encoder->cancel_flag);
432✔
5442
    if (SIXEL_FAILED(status)) {
432!
5443
        goto load_end;
×
5444
    }
5445

5446
    status = sixel_loader_setopt(loader,
576✔
5447
                                 SIXEL_LOADER_OPTION_LOADER_ORDER,
5448
                                 encoder->loader_order);
432✔
5449
    if (SIXEL_FAILED(status)) {
432!
5450
        goto load_end;
×
5451
    }
5452

5453
    status = sixel_loader_setopt(loader,
576✔
5454
                                 SIXEL_LOADER_OPTION_CONTEXT,
5455
                                 encoder);
144✔
5456
    if (SIXEL_FAILED(status)) {
432!
5457
        goto load_end;
×
5458
    }
5459

5460
    /*
5461
     * Wire the optional assessment observer into the loader.
5462
     *
5463
     * The observer travels separately from the callback context so mapfile
5464
     * palette probes and other callbacks can keep using arbitrary structs.
5465
     */
5466
    status = sixel_loader_setopt(loader,
576✔
5467
                                 SIXEL_LOADER_OPTION_ASSESSMENT,
5468
                                 encoder->assessment_observer);
432✔
5469
    if (SIXEL_FAILED(status)) {
432!
5470
        goto load_end;
×
5471
    }
5472

5473
    status = sixel_loader_load_file(loader,
576✔
5474
                                    filename,
144✔
5475
                                    load_image_callback);
5476
    if (status != SIXEL_OK) {
432✔
5477
        goto load_end;
15✔
5478
    }
5479
    encoder->last_input_bytes = sixel_loader_get_last_input_bytes(loader);
417✔
5480
    if (sixel_loader_get_last_success_name(loader) != NULL) {
417!
5481
        (void)snprintf(encoder->last_loader_name,
417✔
5482
                       sizeof(encoder->last_loader_name),
5483
                       "%s",
5484
                       sixel_loader_get_last_success_name(loader));
5485
    } else {
139✔
5486
        encoder->last_loader_name[0] = '\0';
×
5487
    }
5488
    if (sixel_loader_get_last_source_path(loader) != NULL) {
417✔
5489
        (void)snprintf(encoder->last_source_path,
279✔
5490
                       sizeof(encoder->last_source_path),
5491
                       "%s",
5492
                       sixel_loader_get_last_source_path(loader));
5493
    } else {
93✔
5494
        encoder->last_source_path[0] = '\0';
138✔
5495
    }
5496
    if (encoder->assessment_observer != NULL) {
418✔
5497
        sixel_assessment_record_loader(encoder->assessment_observer,
4✔
5498
                                       encoder->last_source_path,
3✔
5499
                                       encoder->last_loader_name,
3✔
5500
                                       encoder->last_input_bytes);
1✔
5501
    }
1✔
5502

5503
load_end:
276✔
5504
    sixel_loader_unref(loader);
432✔
5505
    loader = NULL;
432✔
5506

5507
    if (status != SIXEL_OK) {
432✔
5508
        goto end;
15✔
5509
    }
5510

5511
    palette_status = sixel_encoder_emit_palette_output(encoder);
417✔
5512
    if (SIXEL_FAILED(palette_status)) {
417!
5513
        status = palette_status;
×
5514
        goto end;
×
5515
    }
5516

5517
    if (encoder->pipe_mode) {
417!
5518
#if HAVE_CLEARERR
5519
        clearerr(stdin);
×
5520
#endif  /* HAVE_FSEEK */
5521
        while (encoder->cancel_flag && !*encoder->cancel_flag) {
×
5522
            status = sixel_tty_wait_stdin(1000000);
×
5523
            if (SIXEL_FAILED(status)) {
×
5524
                goto end;
×
5525
            }
5526
            if (status != SIXEL_OK) {
×
5527
                break;
×
5528
            }
5529
        }
5530
        if (!encoder->cancel_flag || !*encoder->cancel_flag) {
×
5531
            goto reload;
×
5532
        }
5533
    }
5534

5535
    /* the status may not be SIXEL_OK */
5536

5537
end:
278✔
5538
    sixel_encoder_unref(encoder);
432✔
5539

5540
    return status;
432✔
5541
}
5542

5543

5544
/* encode specified pixel data to SIXEL format
5545
 * output to encoder->outfd */
5546
SIXELAPI SIXELSTATUS
5547
sixel_encoder_encode_bytes(
×
5548
    sixel_encoder_t     /* in */    *encoder,
5549
    unsigned char       /* in */    *bytes,
5550
    int                 /* in */    width,
5551
    int                 /* in */    height,
5552
    int                 /* in */    pixelformat,
5553
    unsigned char       /* in */    *palette,
5554
    int                 /* in */    ncolors)
5555
{
5556
    SIXELSTATUS status = SIXEL_FALSE;
×
5557
    sixel_frame_t *frame = NULL;
×
5558

5559
    if (encoder == NULL || bytes == NULL) {
×
5560
        status = SIXEL_BAD_ARGUMENT;
×
5561
        goto end;
×
5562
    }
5563

5564
    status = sixel_frame_new(&frame, encoder->allocator);
×
5565
    if (SIXEL_FAILED(status)) {
×
5566
        goto end;
×
5567
    }
5568

5569
    status = sixel_frame_init(frame, bytes, width, height,
×
5570
                              pixelformat, palette, ncolors);
5571
    if (SIXEL_FAILED(status)) {
×
5572
        goto end;
×
5573
    }
5574

5575
    status = sixel_encoder_encode_frame(encoder, frame, NULL);
×
5576
    if (SIXEL_FAILED(status)) {
×
5577
        goto end;
×
5578
    }
5579

5580
    status = SIXEL_OK;
×
5581

5582
end:
5583
    /* we need to free the frame before exiting, but we can't use the
5584
       sixel_frame_destroy function, because that will also attempt to
5585
       free the pixels and palette, which we don't own */
5586
    if (frame != NULL && encoder->allocator != NULL) {
×
5587
        sixel_allocator_free(encoder->allocator, frame);
×
5588
        sixel_allocator_unref(encoder->allocator);
×
5589
    }
5590
    return status;
×
5591
}
5592

5593

5594
/*
5595
 * Toggle source-frame capture for assessment consumers.
5596
 */
5597
SIXELAPI SIXELSTATUS
5598
sixel_encoder_enable_source_capture(
3✔
5599
    sixel_encoder_t *encoder,
5600
    int enable)
5601
{
5602
    if (encoder == NULL) {
3!
5603
        sixel_helper_set_additional_message(
×
5604
            "sixel_encoder_enable_source_capture: encoder is null.");
5605
        return SIXEL_BAD_ARGUMENT;
×
5606
    }
5607

5608
    encoder->capture_source = enable ? 1 : 0;
3✔
5609
    if (!encoder->capture_source && encoder->capture_source_frame != NULL) {
3!
5610
        sixel_frame_unref(encoder->capture_source_frame);
×
5611
        encoder->capture_source_frame = NULL;
×
5612
    }
5613

5614
    return SIXEL_OK;
3✔
5615
}
1✔
5616

5617

5618
/*
5619
 * Enable or disable the quantized-frame capture facility.
5620
 *
5621
 *     capture on --> encoder keeps the latest palette-quantized frame.
5622
 *     capture off --> encoder forgets previously stored frames.
5623
 */
5624
SIXELAPI SIXELSTATUS
5625
sixel_encoder_enable_quantized_capture(
3✔
5626
    sixel_encoder_t *encoder,
5627
    int enable)
5628
{
5629
    if (encoder == NULL) {
3!
5630
        sixel_helper_set_additional_message(
×
5631
            "sixel_encoder_enable_quantized_capture: encoder is null.");
5632
        return SIXEL_BAD_ARGUMENT;
×
5633
    }
5634

5635
    encoder->capture_quantized = enable ? 1 : 0;
3✔
5636
    if (!encoder->capture_quantized) {
3!
5637
        encoder->capture_valid = 0;
×
5638
    }
5639

5640
    return SIXEL_OK;
3✔
5641
}
1✔
5642

5643

5644
/*
5645
 * Materialize the captured quantized frame as a heap-allocated
5646
 * sixel_frame_t instance for assessment consumers.
5647
 */
5648
SIXELAPI SIXELSTATUS
5649
sixel_encoder_copy_quantized_frame(
×
5650
    sixel_encoder_t   *encoder,
5651
    sixel_allocator_t *allocator,
5652
    sixel_frame_t     **ppframe)
5653
{
5654
    SIXELSTATUS status = SIXEL_FALSE;
×
5655
    sixel_frame_t *frame;
5656
    unsigned char *pixels;
5657
    unsigned char *palette;
5658
    size_t palette_bytes;
5659

5660
    if (encoder == NULL || allocator == NULL || ppframe == NULL) {
×
5661
        sixel_helper_set_additional_message(
×
5662
            "sixel_encoder_copy_quantized_frame: invalid argument.");
5663
        return SIXEL_BAD_ARGUMENT;
×
5664
    }
5665

5666
    if (!encoder->capture_quantized || !encoder->capture_valid) {
×
5667
        sixel_helper_set_additional_message(
×
5668
            "sixel_encoder_copy_quantized_frame: no frame captured.");
5669
        return SIXEL_RUNTIME_ERROR;
×
5670
    }
5671

5672
    *ppframe = NULL;
×
5673
    frame = NULL;
×
5674
    pixels = NULL;
×
5675
    palette = NULL;
×
5676

5677
    status = sixel_frame_new(&frame, allocator);
×
5678
    if (SIXEL_FAILED(status)) {
×
5679
        return status;
×
5680
    }
5681

5682
    if (encoder->capture_pixel_bytes > 0) {
×
5683
        pixels = (unsigned char *)sixel_allocator_malloc(
×
5684
            allocator, encoder->capture_pixel_bytes);
5685
        if (pixels == NULL) {
×
5686
            sixel_helper_set_additional_message(
×
5687
                "sixel_encoder_copy_quantized_frame: "
5688
                "sixel_allocator_malloc() failed.");
5689
            status = SIXEL_BAD_ALLOCATION;
×
5690
            goto cleanup;
×
5691
        }
5692
        memcpy(pixels,
×
5693
               encoder->capture_pixels,
5694
               encoder->capture_pixel_bytes);
5695
    }
5696

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

5713
    status = sixel_frame_init(frame,
×
5714
                              pixels,
5715
                              encoder->capture_width,
5716
                              encoder->capture_height,
5717
                              encoder->capture_pixelformat,
5718
                              palette,
5719
                              encoder->capture_ncolors);
5720
    if (SIXEL_FAILED(status)) {
×
5721
        goto cleanup;
×
5722
    }
5723

5724
    pixels = NULL;
×
5725
    palette = NULL;
×
5726
    frame->colorspace = encoder->capture_colorspace;
×
5727
    *ppframe = frame;
×
5728
    return SIXEL_OK;
×
5729

5730
cleanup:
5731
    if (palette != NULL) {
×
5732
        sixel_allocator_free(allocator, palette);
×
5733
    }
5734
    if (pixels != NULL) {
×
5735
        sixel_allocator_free(allocator, pixels);
×
5736
    }
5737
    if (frame != NULL) {
×
5738
        sixel_frame_unref(frame);
×
5739
    }
5740
    return status;
×
5741
}
5742

5743

5744
/*
5745
 * Emit the captured palette in the requested format.
5746
 *
5747
 *   palette_output == NULL  -> skip
5748
 *   palette_output != NULL  -> materialize captured palette
5749
 */
5750
static SIXELSTATUS
5751
sixel_encoder_emit_palette_output(sixel_encoder_t *encoder)
417✔
5752
{
5753
    SIXELSTATUS status;
5754
    sixel_frame_t *frame;
5755
    unsigned char const *palette;
5756
    int exported_colors;
5757
    FILE *stream;
5758
    int close_stream;
5759
    char const *path;
5760
    sixel_palette_format_t format_hint;
5761
    sixel_palette_format_t format_ext;
5762
    sixel_palette_format_t format_final;
5763
    char const *mode;
5764

5765
    status = SIXEL_OK;
417✔
5766
    frame = NULL;
417✔
5767
    palette = NULL;
417✔
5768
    exported_colors = 0;
417✔
5769
    stream = NULL;
417✔
5770
    close_stream = 0;
417✔
5771
    path = NULL;
417✔
5772
    format_hint = SIXEL_PALETTE_FORMAT_NONE;
417✔
5773
    format_ext = SIXEL_PALETTE_FORMAT_NONE;
417✔
5774
    format_final = SIXEL_PALETTE_FORMAT_NONE;
417✔
5775
    mode = "wb";
417✔
5776

5777
    if (encoder == NULL || encoder->palette_output == NULL) {
417!
5778
        return SIXEL_OK;
417✔
5779
    }
5780

5781
    status = sixel_encoder_copy_quantized_frame(encoder,
×
5782
                                                encoder->allocator,
5783
                                                &frame);
5784
    if (SIXEL_FAILED(status)) {
×
5785
        return status;
×
5786
    }
5787

5788
    palette = (unsigned char const *)sixel_frame_get_palette(frame);
×
5789
    exported_colors = sixel_frame_get_ncolors(frame);
×
5790
    if (palette == NULL || exported_colors <= 0) {
×
5791
        sixel_helper_set_additional_message(
×
5792
            "sixel_encoder_emit_palette_output: palette unavailable.");
5793
        status = SIXEL_BAD_INPUT;
×
5794
        goto cleanup;
×
5795
    }
5796
    if (exported_colors > 256) {
×
5797
        exported_colors = 256;
×
5798
    }
5799

5800
    path = sixel_palette_strip_prefix(encoder->palette_output, &format_hint);
×
5801
    if (path == NULL || *path == '\0') {
×
5802
        sixel_helper_set_additional_message(
×
5803
            "sixel_encoder_emit_palette_output: invalid path.");
5804
        status = SIXEL_BAD_ARGUMENT;
×
5805
        goto cleanup;
×
5806
    }
5807

5808
    format_ext = sixel_palette_format_from_extension(path);
×
5809
    format_final = format_hint;
×
5810
    if (format_final == SIXEL_PALETTE_FORMAT_NONE) {
×
5811
        if (format_ext == SIXEL_PALETTE_FORMAT_NONE) {
×
5812
            if (strcmp(path, "-") == 0) {
×
5813
                sixel_helper_set_additional_message(
×
5814
                    "sixel_encoder_emit_palette_output: "
5815
                    "format required for '-'.");
5816
                status = SIXEL_BAD_ARGUMENT;
×
5817
                goto cleanup;
×
5818
            }
5819
            sixel_helper_set_additional_message(
×
5820
                "sixel_encoder_emit_palette_output: "
5821
                "unknown palette file extension.");
5822
            status = SIXEL_BAD_ARGUMENT;
×
5823
            goto cleanup;
×
5824
        }
5825
        format_final = format_ext;
×
5826
    }
5827
    if (format_final == SIXEL_PALETTE_FORMAT_PAL_AUTO) {
×
5828
        format_final = SIXEL_PALETTE_FORMAT_PAL_JASC;
×
5829
    }
5830

5831
    if (strcmp(path, "-") == 0) {
×
5832
        stream = stdout;
×
5833
    } else {
5834
        if (format_final == SIXEL_PALETTE_FORMAT_PAL_JASC ||
×
5835
                format_final == SIXEL_PALETTE_FORMAT_GPL) {
5836
            mode = "w";
×
5837
        } else {
5838
            mode = "wb";
×
5839
        }
5840
        stream = fopen(path, mode);
×
5841
        if (stream == NULL) {
×
5842
            sixel_helper_set_additional_message(
×
5843
                "sixel_encoder_emit_palette_output: failed to open file.");
5844
            status = SIXEL_LIBC_ERROR;
×
5845
            goto cleanup;
×
5846
        }
5847
        close_stream = 1;
×
5848
    }
5849

5850
    switch (format_final) {
×
5851
    case SIXEL_PALETTE_FORMAT_ACT:
5852
        status = sixel_palette_write_act(stream, palette, exported_colors);
×
5853
        if (SIXEL_FAILED(status)) {
×
5854
            sixel_helper_set_additional_message(
×
5855
                "sixel_encoder_emit_palette_output: failed to write ACT.");
5856
        }
5857
        break;
×
5858
    case SIXEL_PALETTE_FORMAT_PAL_JASC:
5859
        status = sixel_palette_write_pal_jasc(stream,
×
5860
                                              palette,
5861
                                              exported_colors);
5862
        if (SIXEL_FAILED(status)) {
×
5863
            sixel_helper_set_additional_message(
×
5864
                "sixel_encoder_emit_palette_output: failed to write JASC.");
5865
        }
5866
        break;
×
5867
    case SIXEL_PALETTE_FORMAT_PAL_RIFF:
5868
        status = sixel_palette_write_pal_riff(stream,
×
5869
                                              palette,
5870
                                              exported_colors);
5871
        if (SIXEL_FAILED(status)) {
×
5872
            sixel_helper_set_additional_message(
×
5873
                "sixel_encoder_emit_palette_output: failed to write RIFF.");
5874
        }
5875
        break;
×
5876
    case SIXEL_PALETTE_FORMAT_GPL:
5877
        status = sixel_palette_write_gpl(stream,
×
5878
                                         palette,
5879
                                         exported_colors);
5880
        if (SIXEL_FAILED(status)) {
×
5881
            sixel_helper_set_additional_message(
×
5882
                "sixel_encoder_emit_palette_output: failed to write GPL.");
5883
        }
5884
        break;
×
5885
    default:
5886
        sixel_helper_set_additional_message(
×
5887
            "sixel_encoder_emit_palette_output: unsupported format.");
5888
        status = SIXEL_BAD_ARGUMENT;
×
5889
        break;
×
5890
    }
5891
    if (SIXEL_FAILED(status)) {
×
5892
        goto cleanup;
×
5893
    }
5894

5895
    if (close_stream) {
×
5896
        if (fclose(stream) != 0) {
×
5897
            sixel_helper_set_additional_message(
×
5898
                "sixel_encoder_emit_palette_output: fclose() failed.");
5899
            status = SIXEL_LIBC_ERROR;
×
5900
            stream = NULL;
×
5901
            goto cleanup;
×
5902
        }
5903
        stream = NULL;
×
5904
    } else {
5905
        if (fflush(stream) != 0) {
×
5906
            sixel_helper_set_additional_message(
×
5907
                "sixel_encoder_emit_palette_output: fflush() failed.");
5908
            status = SIXEL_LIBC_ERROR;
×
5909
            goto cleanup;
×
5910
        }
5911
    }
5912

5913
cleanup:
5914
    if (close_stream && stream != NULL) {
×
5915
        (void) fclose(stream);
×
5916
    }
5917
    if (frame != NULL) {
×
5918
        sixel_frame_unref(frame);
×
5919
    }
5920

5921
    return status;
×
5922
}
139✔
5923

5924

5925
/*
5926
 * Share the captured source frame with assessment consumers.
5927
 */
5928
SIXELAPI SIXELSTATUS
5929
sixel_encoder_copy_source_frame(
3✔
5930
    sixel_encoder_t *encoder,
5931
    sixel_frame_t  **ppframe)
5932
{
5933
    if (encoder == NULL || ppframe == NULL) {
3!
5934
        sixel_helper_set_additional_message(
×
5935
            "sixel_encoder_copy_source_frame: invalid argument.");
5936
        return SIXEL_BAD_ARGUMENT;
×
5937
    }
5938

5939
    if (!encoder->capture_source || encoder->capture_source_frame == NULL) {
3!
5940
        sixel_helper_set_additional_message(
×
5941
            "sixel_encoder_copy_source_frame: no frame captured.");
5942
        return SIXEL_RUNTIME_ERROR;
×
5943
    }
5944

5945
    sixel_frame_ref(encoder->capture_source_frame);
3✔
5946
    *ppframe = encoder->capture_source_frame;
3✔
5947

5948
    return SIXEL_OK;
3✔
5949
}
1✔
5950

5951

5952
#if HAVE_TESTS
5953
static int
5954
test1(void)
×
5955
{
5956
    int nret = EXIT_FAILURE;
×
5957
    sixel_encoder_t *encoder = NULL;
×
5958

5959
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5960
#  pragma GCC diagnostic push
5961
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
5962
#endif
5963
    encoder = sixel_encoder_create();
×
5964
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5965
#  pragma GCC diagnostic pop
5966
#endif
5967
    if (encoder == NULL) {
×
5968
        goto error;
×
5969
    }
5970
    sixel_encoder_ref(encoder);
×
5971
    sixel_encoder_unref(encoder);
×
5972
    nret = EXIT_SUCCESS;
×
5973

5974
error:
5975
    sixel_encoder_unref(encoder);
×
5976
    return nret;
×
5977
}
5978

5979

5980
static int
5981
test2(void)
×
5982
{
5983
    int nret = EXIT_FAILURE;
×
5984
    SIXELSTATUS status;
5985
    sixel_encoder_t *encoder = NULL;
×
5986
    sixel_frame_t *frame = NULL;
×
5987
    unsigned char *buffer;
5988
    int height = 0;
×
5989
    int is_animation = 0;
×
5990

5991
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5992
#  pragma GCC diagnostic push
5993
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
5994
#endif
5995
    encoder = sixel_encoder_create();
×
5996
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
5997
#  pragma GCC diagnostic pop
5998
#endif
5999
    if (encoder == NULL) {
×
6000
        goto error;
×
6001
    }
6002

6003
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6004
#  pragma GCC diagnostic push
6005
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
6006
#endif
6007
    frame = sixel_frame_create();
×
6008
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6009
#  pragma GCC diagnostic pop
6010
#endif
6011
    if (encoder == NULL) {
×
6012
        goto error;
×
6013
    }
6014

6015
    buffer = (unsigned char *)sixel_allocator_malloc(encoder->allocator, 3);
×
6016
    if (buffer == NULL) {
×
6017
        goto error;
×
6018
    }
6019
    status = sixel_frame_init(frame, buffer, 1, 1,
×
6020
                              SIXEL_PIXELFORMAT_RGB888,
6021
                              NULL, 0);
6022
    if (SIXEL_FAILED(status)) {
×
6023
        goto error;
×
6024
    }
6025

6026
    if (sixel_frame_get_loop_no(frame) != 0 || sixel_frame_get_frame_no(frame) != 0) {
×
6027
        is_animation = 1;
×
6028
    }
6029

6030
    height = sixel_frame_get_height(frame);
×
6031

6032
    status = sixel_tty_scroll(sixel_write_callback,
×
6033
                              &encoder->outfd,
×
6034
                              encoder->outfd,
6035
                              height,
6036
                              is_animation);
6037
    if (SIXEL_FAILED(status)) {
×
6038
        goto error;
×
6039
    }
6040

6041
    nret = EXIT_SUCCESS;
×
6042

6043
error:
6044
    sixel_encoder_unref(encoder);
×
6045
    sixel_frame_unref(frame);
×
6046
    return nret;
×
6047
}
6048

6049

6050
static int
6051
test3(void)
×
6052
{
6053
    int nret = EXIT_FAILURE;
×
6054
    int result;
6055

6056
    result = sixel_tty_wait_stdin(1000);
×
6057
    if (result != 0) {
×
6058
        goto error;
×
6059
    }
6060

6061
    nret = EXIT_SUCCESS;
×
6062

6063
error:
6064
    return nret;
×
6065
}
6066

6067

6068
static int
6069
test4(void)
×
6070
{
6071
    int nret = EXIT_FAILURE;
×
6072
    sixel_encoder_t *encoder = NULL;
×
6073
    SIXELSTATUS status;
6074

6075
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6076
# pragma GCC diagnostic push
6077
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
6078
#endif
6079
    encoder = sixel_encoder_create();
×
6080
#if HAVE_DIAGNOSTIC_DEPRECATED_DECLARATIONS
6081
# pragma GCC diagnostic pop
6082
#endif
6083
    if (encoder == NULL) {
×
6084
        goto error;
×
6085
    }
6086

6087
    status = sixel_encoder_setopt(encoder,
×
6088
                                  SIXEL_OPTFLAG_LOOPMODE,
6089
                                  "force");
6090
    if (SIXEL_FAILED(status)) {
×
6091
        goto error;
×
6092
    }
6093

6094
    status = sixel_encoder_setopt(encoder,
×
6095
                                  SIXEL_OPTFLAG_PIPE_MODE,
6096
                                  "force");
6097
    if (SIXEL_FAILED(status)) {
×
6098
        goto error;
×
6099
    }
6100

6101
    nret = EXIT_SUCCESS;
×
6102

6103
error:
6104
    sixel_encoder_unref(encoder);
×
6105
    return nret;
×
6106
}
6107

6108

6109
static int
6110
test5(void)
×
6111
{
6112
    int nret = EXIT_FAILURE;
×
6113
    sixel_encoder_t *encoder = NULL;
×
6114
    sixel_allocator_t *allocator = NULL;
×
6115
    SIXELSTATUS status;
6116

6117
    status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
×
6118
    if (SIXEL_FAILED(status)) {
×
6119
        goto error;
×
6120
    }
6121

6122
    status = sixel_encoder_new(&encoder, allocator);
×
6123
    if (SIXEL_FAILED(status)) {
×
6124
        goto error;
×
6125
    }
6126

6127
    sixel_encoder_ref(encoder);
×
6128
    sixel_encoder_unref(encoder);
×
6129
    nret = EXIT_SUCCESS;
×
6130

6131
error:
6132
    sixel_encoder_unref(encoder);
×
6133
    return nret;
×
6134
}
6135

6136

6137
SIXELAPI int
6138
sixel_encoder_tests_main(void)
×
6139
{
6140
    int nret = EXIT_FAILURE;
×
6141
    size_t i;
6142
    typedef int (* testcase)(void);
6143

6144
    static testcase const testcases[] = {
6145
        test1,
6146
        test2,
6147
        test3,
6148
        test4,
6149
        test5
6150
    };
6151

6152
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
6153
        nret = testcases[i]();
×
6154
        if (nret != EXIT_SUCCESS) {
×
6155
            goto error;
×
6156
        }
6157
    }
6158

6159
    nret = EXIT_SUCCESS;
×
6160

6161
error:
6162
    return nret;
×
6163
}
6164
#endif  /* HAVE_TESTS */
6165

6166

6167
/* emacs Local Variables:      */
6168
/* emacs mode: c               */
6169
/* emacs tab-width: 4          */
6170
/* emacs indent-tabs-mode: nil */
6171
/* emacs c-basic-offset: 4     */
6172
/* emacs End:                  */
6173
/* vim: set expandtab ts=4 : */
6174
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc