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

saitoha / libsixel / 19541344273

20 Nov 2025 03:02PM UTC coverage: 40.773% (-0.4%) from 41.21%
19541344273

push

github

saitoha
feat: initial prototyping for parallel dithering

9711 of 33880 branches covered (28.66%)

55 of 483 new or added lines in 10 files covered. (11.39%)

12 existing lines in 4 files now uncovered.

12720 of 31197 relevant lines covered (40.77%)

656879.66 hits per line

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

47.48
/src/dither.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2021-2025 libsixel developers. See `AUTHORS`.
5
 * Copyright (c) 2014-2018 Hayaki Saito
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
8
 * this software and associated documentation files (the "Software"), to deal in
9
 * the Software without restriction, including without limitation the rights to
10
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11
 * the Software, and to permit persons to whom the Software is furnished to do so,
12
 * subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in all
15
 * copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
 */
24

25
#include "config.h"
26

27
#include <stdlib.h>
28
#include <stdio.h>
29

30
#if HAVE_MATH_H
31
# include <math.h>
32
#endif  /* HAVE_MATH_H */
33
#if HAVE_STRING_H
34
# include <string.h>
35
#endif  /* HAVE_STRING_H */
36
#if HAVE_CTYPE_H
37
# include <ctype.h>
38
#endif  /* HAVE_CTYPE_H */
39
#if HAVE_LIMITS_H
40
# include <limits.h>
41
#endif  /* HAVE_LIMITS_H */
42
#if HAVE_INTTYPES_H
43
# include <inttypes.h>
44
#endif  /* HAVE_INTTYPES_H */
45

46
#include "dither.h"
47
#include "palette.h"
48
#include "compat_stub.h"
49
#include "lut.h"
50
#include "assessment.h"
51
#include "dither-common-pipeline.h"
52
#include "dither-positional-8bit.h"
53
#include "dither-positional-float32.h"
54
#include "dither-fixed-8bit.h"
55
#include "dither-fixed-float32.h"
56
#include "dither-varcoeff-8bit.h"
57
#include "dither-varcoeff-float32.h"
58
#include "dither-internal.h"
59
#include "parallel-log.h"
60
#include "precision.h"
61
#include "pixelformat.h"
62
#if SIXEL_ENABLE_THREADS
63
# include "threadpool.h"
64
#endif
65
#include <sixel.h>
66

67
static int g_sixel_precision_float32_env_cached = (-1);
68

69
static unsigned char
70
sixel_precision_ascii_tolower(unsigned char ch)
×
71
{
72
#if HAVE_CTYPE_H
73
    return (unsigned char)tolower((int)ch);
×
74
#else
75
    if (ch >= 'A' && ch <= 'Z') {
76
        ch = (unsigned char)(ch - 'A' + 'a');
77
    }
78
    return ch;
79
#endif
80
}
81

82
static int
83
sixel_precision_is_space(unsigned char ch)
×
84
{
85
#if HAVE_CTYPE_H
86
    return isspace((int)ch);
×
87
#else
88
    switch (ch) {
89
    case ' ':  /* fallthrough */
90
    case '\t': /* fallthrough */
91
    case '\r': /* fallthrough */
92
    case '\n': /* fallthrough */
93
    case '\f':
94
        return 1;
95
    default:
96
        return 0;
97
    }
98
#endif
99
}
100

101
static int
102
sixel_precision_strcaseeq(const char *lhs, const char *rhs)
×
103
{
104
    unsigned char lc;
105
    unsigned char rc;
106

107
    if (lhs == NULL || rhs == NULL) {
×
108
        return 0;
×
109
    }
110

111
    while (*lhs != '\0' && *rhs != '\0') {
×
112
        lc = sixel_precision_ascii_tolower((unsigned char)*lhs);
×
113
        rc = sixel_precision_ascii_tolower((unsigned char)*rhs);
×
114
        if (lc != rc) {
×
115
            return 0;
×
116
        }
117
        ++lhs;
×
118
        ++rhs;
×
119
    }
120

121
    return *lhs == '\0' && *rhs == '\0';
×
122
}
123

124
static int
125
sixel_precision_parse_float32_flag(const char *value, int fallback)
×
126
{
127
    const char *cursor;
128

129
    if (value == NULL) {
×
130
        return fallback;
×
131
    }
132

133
    cursor = value;
×
134
    while (*cursor != '\0'
×
135
           && sixel_precision_is_space((unsigned char)*cursor)) {
×
136
        ++cursor;
×
137
    }
138
    if (*cursor == '\0') {
×
139
        return 0;
×
140
    }
141

142
    if (cursor[0] == '0' && cursor[1] == '\0') {
×
143
        return 0;
×
144
    }
145
    if (cursor[0] == '1' && cursor[1] == '\0') {
×
146
        return 1;
×
147
    }
148
    if (sixel_precision_strcaseeq(cursor, "false") ||
×
149
            sixel_precision_strcaseeq(cursor, "off") ||
×
150
            sixel_precision_strcaseeq(cursor, "no")) {
×
151
        return 0;
×
152
    }
153
    if (sixel_precision_strcaseeq(cursor, "true") ||
×
154
            sixel_precision_strcaseeq(cursor, "on") ||
×
155
            sixel_precision_strcaseeq(cursor, "yes") ||
×
156
            sixel_precision_strcaseeq(cursor, "auto") ||
×
157
            sixel_precision_strcaseeq(cursor, "kmeans")) {
×
158
        return 1;
×
159
    }
160

161
    return fallback;
×
162
}
163

164
int
165
sixel_precision_env_wants_float32(void)
520✔
166
{
167
    const char *value;
168

169
    if (g_sixel_precision_float32_env_cached != (-1)) {
520✔
170
        return g_sixel_precision_float32_env_cached;
99✔
171
    }
172

173
    value = sixel_compat_getenv(SIXEL_FLOAT32_ENVVAR);
421✔
174
    if (value == NULL) {
421!
175
        g_sixel_precision_float32_env_cached = 0;
421✔
176
        return g_sixel_precision_float32_env_cached;
421✔
177
    }
178

179
    g_sixel_precision_float32_env_cached =
×
180
        sixel_precision_parse_float32_flag(value, 1);
×
181
    return g_sixel_precision_float32_env_cached;
×
182
}
174✔
183

184
void
185
sixel_precision_reset_float32_cache(void)
×
186
{
187
    g_sixel_precision_float32_env_cached = (-1);
×
188
}
×
189

190
/* Cache the parsed environment decision to avoid repeated getenv() calls. */
191
static int
192
sixel_dither_env_wants_float32(void)
520✔
193
{
194
    return sixel_precision_env_wants_float32();
520✔
195
}
196

197
#if HAVE_TESTS
198
void
199
sixel_dither_tests_reset_float32_flag_cache(void)
×
200
{
201
    sixel_precision_reset_float32_cache();
×
202
}
×
203
#endif
204

205
/*
206
 * Promote an RGB888 buffer to RGBFLOAT32 by normalising each channel to the
207
 * 0.0-1.0 range.  The helper is used when the environment requests float32
208
 * precision but the incoming frame still relies on 8bit pixels.
209
 */
210
static SIXELSTATUS
211
sixel_dither_promote_rgb888_to_float32(float **out_pixels,
×
212
                                       unsigned char const *rgb888,
213
                                       size_t pixel_total,
214
                                       sixel_allocator_t *allocator)
215
{
216
    SIXELSTATUS status;
217
    float *buffer;
218
    size_t bytes;
219
    size_t index;
220
    size_t base;
221

222
    status = SIXEL_BAD_ARGUMENT;
×
223
    buffer = NULL;
×
224
    bytes = 0U;
×
225
    index = 0U;
×
226
    base = 0U;
×
227

228
    if (out_pixels == NULL || rgb888 == NULL || allocator == NULL) {
×
229
        return status;
×
230
    }
231

232
    *out_pixels = NULL;
×
233
    status = SIXEL_OK;
×
234
    if (pixel_total == 0U) {
×
235
        return status;
×
236
    }
237

238
    if (pixel_total > SIZE_MAX / (3U * sizeof(float))) {
×
239
        return SIXEL_BAD_INPUT;
×
240
    }
241
    bytes = pixel_total * 3U * sizeof(float);
×
242

243
    buffer = (float *)sixel_allocator_malloc(allocator, bytes);
×
244
    if (buffer == NULL) {
×
245
        return SIXEL_BAD_ALLOCATION;
×
246
    }
247

248
    for (index = 0U; index < pixel_total; ++index) {
×
249
        base = index * 3U;
×
250
        buffer[base + 0U] = (float)rgb888[base + 0U] / 255.0f;
×
251
        buffer[base + 1U] = (float)rgb888[base + 1U] / 255.0f;
×
252
        buffer[base + 2U] = (float)rgb888[base + 2U] / 255.0f;
×
253
    }
254

255
    *out_pixels = buffer;
×
256
    return status;
×
257
}
258

259
/*
260
 * Determine whether the selected diffusion method can reuse the float32
261
 * pipeline.  Positional and variable-coefficient dithers have dedicated
262
 * float backends, while Floyd-Steinberg must disable the fast path when the
263
 * user explicitly enables carry buffers.
264
 */
265
static int
266
sixel_dither_method_supports_float_pipeline(sixel_dither_t const *dither)
256✔
267
{
268
    int method;
269

270
    if (dither == NULL) {
256!
271
        return 0;
×
272
    }
273
    if (dither->prefer_rgbfloat32 == 0) {
256!
274
        return 0;
256✔
275
    }
276
    if (dither->method_for_carry == SIXEL_CARRY_ENABLE) {
×
277
        return 0;
×
278
    }
279

280
    method = dither->method_for_diffuse;
×
281
    switch (method) {
×
282
    case SIXEL_DIFFUSE_NONE:
283
    case SIXEL_DIFFUSE_ATKINSON:
284
    case SIXEL_DIFFUSE_FS:
285
    case SIXEL_DIFFUSE_JAJUNI:
286
    case SIXEL_DIFFUSE_STUCKI:
287
    case SIXEL_DIFFUSE_BURKES:
288
    case SIXEL_DIFFUSE_SIERRA1:
289
    case SIXEL_DIFFUSE_SIERRA2:
290
    case SIXEL_DIFFUSE_SIERRA3:
291
        return 1;
×
292
    case SIXEL_DIFFUSE_A_DITHER:
293
    case SIXEL_DIFFUSE_X_DITHER:
294
    case SIXEL_DIFFUSE_LSO2:
295
        return 1;
×
296
    default:
297
        return 0;
×
298
    }
299
}
86✔
300

301

302
static const unsigned char pal_mono_dark[] = {
303
    0x00, 0x00, 0x00, 0xff, 0xff, 0xff
304
};
305

306

307
static const unsigned char pal_mono_light[] = {
308
    0xff, 0xff, 0xff, 0x00, 0x00, 0x00
309
};
310

311
static const unsigned char pal_gray_1bit[] = {
312
    0x00, 0x00, 0x00, 0xff, 0xff, 0xff
313
};
314

315

316
static const unsigned char pal_gray_2bit[] = {
317
    0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff
318
};
319

320

321
static const unsigned char pal_gray_4bit[] = {
322
    0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33,
323
    0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77,
324
    0x88, 0x88, 0x88, 0x99, 0x99, 0x99, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb,
325
    0xcc, 0xcc, 0xcc, 0xdd, 0xdd, 0xdd, 0xee, 0xee, 0xee, 0xff, 0xff, 0xff
326
};
327

328

329
static const unsigned char pal_gray_8bit[] = {
330
    0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
331
    0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07,
332
    0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b,
333
    0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f,
334
    0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13,
335
    0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17,
336
    0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b,
337
    0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f,
338
    0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23,
339
    0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27,
340
    0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b,
341
    0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f,
342
    0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33,
343
    0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, 0x37, 0x37, 0x37,
344
    0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b,
345
    0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f,
346
    0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43,
347
    0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x46, 0x46, 0x46, 0x47, 0x47, 0x47,
348
    0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b,
349
    0x4c, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, 0x4f,
350
    0x50, 0x50, 0x50, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53,
351
    0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57,
352
    0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b,
353
    0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f,
354
    0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x63, 0x63, 0x63,
355
    0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67,
356
    0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b,
357
    0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f,
358
    0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x72, 0x72, 0x72, 0x73, 0x73, 0x73,
359
    0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x76, 0x76, 0x76, 0x77, 0x77, 0x77,
360
    0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b,
361
    0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f,
362
    0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x83, 0x83, 0x83,
363
    0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x86, 0x86, 0x86, 0x87, 0x87, 0x87,
364
    0x88, 0x88, 0x88, 0x89, 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8b, 0x8b, 0x8b,
365
    0x8c, 0x8c, 0x8c, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f,
366
    0x90, 0x90, 0x90, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93,
367
    0x94, 0x94, 0x94, 0x95, 0x95, 0x95, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97,
368
    0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b,
369
    0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9f, 0x9f, 0x9f,
370
    0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3,
371
    0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa6, 0xa7, 0xa7, 0xa7,
372
    0xa8, 0xa8, 0xa8, 0xa9, 0xa9, 0xa9, 0xaa, 0xaa, 0xaa, 0xab, 0xab, 0xab,
373
    0xac, 0xac, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xaf, 0xaf, 0xaf,
374
    0xb0, 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3,
375
    0xb4, 0xb4, 0xb4, 0xb5, 0xb5, 0xb5, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7,
376
    0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xbb, 0xbb, 0xbb,
377
    0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf,
378
    0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3,
379
    0xc4, 0xc4, 0xc4, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7,
380
    0xc8, 0xc8, 0xc8, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb,
381
    0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf,
382
    0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3,
383
    0xd4, 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7,
384
    0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb,
385
    0xdc, 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf,
386
    0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3,
387
    0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7,
388
    0xe8, 0xe8, 0xe8, 0xe9, 0xe9, 0xe9, 0xea, 0xea, 0xea, 0xeb, 0xeb, 0xeb,
389
    0xec, 0xec, 0xec, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef,
390
    0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf2, 0xf2, 0xf2, 0xf3, 0xf3, 0xf3,
391
    0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7,
392
    0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb,
393
    0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff
394
};
395

396

397
static const unsigned char pal_xterm256[] = {
398
    0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
399
    0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0,
400
    0x80, 0x80, 0x80, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00,
401
    0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
402
    0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x87, 0x00, 0x00, 0xaf,
403
    0x00, 0x00, 0xd7, 0x00, 0x00, 0xff, 0x00, 0x5f, 0x00, 0x00, 0x5f, 0x5f,
404
    0x00, 0x5f, 0x87, 0x00, 0x5f, 0xaf, 0x00, 0x5f, 0xd7, 0x00, 0x5f, 0xff,
405
    0x00, 0x87, 0x00, 0x00, 0x87, 0x5f, 0x00, 0x87, 0x87, 0x00, 0x87, 0xaf,
406
    0x00, 0x87, 0xd7, 0x00, 0x87, 0xff, 0x00, 0xaf, 0x00, 0x00, 0xaf, 0x5f,
407
    0x00, 0xaf, 0x87, 0x00, 0xaf, 0xaf, 0x00, 0xaf, 0xd7, 0x00, 0xaf, 0xff,
408
    0x00, 0xd7, 0x00, 0x00, 0xd7, 0x5f, 0x00, 0xd7, 0x87, 0x00, 0xd7, 0xaf,
409
    0x00, 0xd7, 0xd7, 0x00, 0xd7, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0x5f,
410
    0x00, 0xff, 0x87, 0x00, 0xff, 0xaf, 0x00, 0xff, 0xd7, 0x00, 0xff, 0xff,
411
    0x5f, 0x00, 0x00, 0x5f, 0x00, 0x5f, 0x5f, 0x00, 0x87, 0x5f, 0x00, 0xaf,
412
    0x5f, 0x00, 0xd7, 0x5f, 0x00, 0xff, 0x5f, 0x5f, 0x00, 0x5f, 0x5f, 0x5f,
413
    0x5f, 0x5f, 0x87, 0x5f, 0x5f, 0xaf, 0x5f, 0x5f, 0xd7, 0x5f, 0x5f, 0xff,
414
    0x5f, 0x87, 0x00, 0x5f, 0x87, 0x5f, 0x5f, 0x87, 0x87, 0x5f, 0x87, 0xaf,
415
    0x5f, 0x87, 0xd7, 0x5f, 0x87, 0xff, 0x5f, 0xaf, 0x00, 0x5f, 0xaf, 0x5f,
416
    0x5f, 0xaf, 0x87, 0x5f, 0xaf, 0xaf, 0x5f, 0xaf, 0xd7, 0x5f, 0xaf, 0xff,
417
    0x5f, 0xd7, 0x00, 0x5f, 0xd7, 0x5f, 0x5f, 0xd7, 0x87, 0x5f, 0xd7, 0xaf,
418
    0x5f, 0xd7, 0xd7, 0x5f, 0xd7, 0xff, 0x5f, 0xff, 0x00, 0x5f, 0xff, 0x5f,
419
    0x5f, 0xff, 0x87, 0x5f, 0xff, 0xaf, 0x5f, 0xff, 0xd7, 0x5f, 0xff, 0xff,
420
    0x87, 0x00, 0x00, 0x87, 0x00, 0x5f, 0x87, 0x00, 0x87, 0x87, 0x00, 0xaf,
421
    0x87, 0x00, 0xd7, 0x87, 0x00, 0xff, 0x87, 0x5f, 0x00, 0x87, 0x5f, 0x5f,
422
    0x87, 0x5f, 0x87, 0x87, 0x5f, 0xaf, 0x87, 0x5f, 0xd7, 0x87, 0x5f, 0xff,
423
    0x87, 0x87, 0x00, 0x87, 0x87, 0x5f, 0x87, 0x87, 0x87, 0x87, 0x87, 0xaf,
424
    0x87, 0x87, 0xd7, 0x87, 0x87, 0xff, 0x87, 0xaf, 0x00, 0x87, 0xaf, 0x5f,
425
    0x87, 0xaf, 0x87, 0x87, 0xaf, 0xaf, 0x87, 0xaf, 0xd7, 0x87, 0xaf, 0xff,
426
    0x87, 0xd7, 0x00, 0x87, 0xd7, 0x5f, 0x87, 0xd7, 0x87, 0x87, 0xd7, 0xaf,
427
    0x87, 0xd7, 0xd7, 0x87, 0xd7, 0xff, 0x87, 0xff, 0x00, 0x87, 0xff, 0x5f,
428
    0x87, 0xff, 0x87, 0x87, 0xff, 0xaf, 0x87, 0xff, 0xd7, 0x87, 0xff, 0xff,
429
    0xaf, 0x00, 0x00, 0xaf, 0x00, 0x5f, 0xaf, 0x00, 0x87, 0xaf, 0x00, 0xaf,
430
    0xaf, 0x00, 0xd7, 0xaf, 0x00, 0xff, 0xaf, 0x5f, 0x00, 0xaf, 0x5f, 0x5f,
431
    0xaf, 0x5f, 0x87, 0xaf, 0x5f, 0xaf, 0xaf, 0x5f, 0xd7, 0xaf, 0x5f, 0xff,
432
    0xaf, 0x87, 0x00, 0xaf, 0x87, 0x5f, 0xaf, 0x87, 0x87, 0xaf, 0x87, 0xaf,
433
    0xaf, 0x87, 0xd7, 0xaf, 0x87, 0xff, 0xaf, 0xaf, 0x00, 0xaf, 0xaf, 0x5f,
434
    0xaf, 0xaf, 0x87, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xd7, 0xaf, 0xaf, 0xff,
435
    0xaf, 0xd7, 0x00, 0xaf, 0xd7, 0x5f, 0xaf, 0xd7, 0x87, 0xaf, 0xd7, 0xaf,
436
    0xaf, 0xd7, 0xd7, 0xaf, 0xd7, 0xff, 0xaf, 0xff, 0x00, 0xaf, 0xff, 0x5f,
437
    0xaf, 0xff, 0x87, 0xaf, 0xff, 0xaf, 0xaf, 0xff, 0xd7, 0xaf, 0xff, 0xff,
438
    0xd7, 0x00, 0x00, 0xd7, 0x00, 0x5f, 0xd7, 0x00, 0x87, 0xd7, 0x00, 0xaf,
439
    0xd7, 0x00, 0xd7, 0xd7, 0x00, 0xff, 0xd7, 0x5f, 0x00, 0xd7, 0x5f, 0x5f,
440
    0xd7, 0x5f, 0x87, 0xd7, 0x5f, 0xaf, 0xd7, 0x5f, 0xd7, 0xd7, 0x5f, 0xff,
441
    0xd7, 0x87, 0x00, 0xd7, 0x87, 0x5f, 0xd7, 0x87, 0x87, 0xd7, 0x87, 0xaf,
442
    0xd7, 0x87, 0xd7, 0xd7, 0x87, 0xff, 0xd7, 0xaf, 0x00, 0xd7, 0xaf, 0x5f,
443
    0xd7, 0xaf, 0x87, 0xd7, 0xaf, 0xaf, 0xd7, 0xaf, 0xd7, 0xd7, 0xaf, 0xff,
444
    0xd7, 0xd7, 0x00, 0xd7, 0xd7, 0x5f, 0xd7, 0xd7, 0x87, 0xd7, 0xd7, 0xaf,
445
    0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xff, 0xd7, 0xff, 0x00, 0xd7, 0xff, 0x5f,
446
    0xd7, 0xff, 0x87, 0xd7, 0xff, 0xaf, 0xd7, 0xff, 0xd7, 0xd7, 0xff, 0xff,
447
    0xff, 0x00, 0x00, 0xff, 0x00, 0x5f, 0xff, 0x00, 0x87, 0xff, 0x00, 0xaf,
448
    0xff, 0x00, 0xd7, 0xff, 0x00, 0xff, 0xff, 0x5f, 0x00, 0xff, 0x5f, 0x5f,
449
    0xff, 0x5f, 0x87, 0xff, 0x5f, 0xaf, 0xff, 0x5f, 0xd7, 0xff, 0x5f, 0xff,
450
    0xff, 0x87, 0x00, 0xff, 0x87, 0x5f, 0xff, 0x87, 0x87, 0xff, 0x87, 0xaf,
451
    0xff, 0x87, 0xd7, 0xff, 0x87, 0xff, 0xff, 0xaf, 0x00, 0xff, 0xaf, 0x5f,
452
    0xff, 0xaf, 0x87, 0xff, 0xaf, 0xaf, 0xff, 0xaf, 0xd7, 0xff, 0xaf, 0xff,
453
    0xff, 0xd7, 0x00, 0xff, 0xd7, 0x5f, 0xff, 0xd7, 0x87, 0xff, 0xd7, 0xaf,
454
    0xff, 0xd7, 0xd7, 0xff, 0xd7, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x5f,
455
    0xff, 0xff, 0x87, 0xff, 0xff, 0xaf, 0xff, 0xff, 0xd7, 0xff, 0xff, 0xff,
456
    0x08, 0x08, 0x08, 0x12, 0x12, 0x12, 0x1c, 0x1c, 0x1c, 0x26, 0x26, 0x26,
457
    0x30, 0x30, 0x30, 0x3a, 0x3a, 0x3a, 0x44, 0x44, 0x44, 0x4e, 0x4e, 0x4e,
458
    0x58, 0x58, 0x58, 0x62, 0x62, 0x62, 0x6c, 0x6c, 0x6c, 0x76, 0x76, 0x76,
459
    0x80, 0x80, 0x80, 0x8a, 0x8a, 0x8a, 0x94, 0x94, 0x94, 0x9e, 0x9e, 0x9e,
460
    0xa8, 0xa8, 0xa8, 0xb2, 0xb2, 0xb2, 0xbc, 0xbc, 0xbc, 0xc6, 0xc6, 0xc6,
461
    0xd0, 0xd0, 0xd0, 0xda, 0xda, 0xda, 0xe4, 0xe4, 0xe4, 0xee, 0xee, 0xee,
462
};
463

464

465
static sixel_lut_t *dither_lut_context = NULL;
466

467
/* lookup closest color from palette with "normal" strategy */
468
static int
469
lookup_normal(unsigned char const * const pixel,
810,000✔
470
              int const depth,
471
              unsigned char const * const palette,
472
              int const reqcolor,
473
              unsigned short * const cachetable,
474
              int const complexion)
475
{
476
    int result;
477
    int diff;
478
    int r;
479
    int i;
480
    int n;
481
    int distant;
482

483
    result = (-1);
810,000✔
484
    diff = INT_MAX;
810,000✔
485

486
    /* don't use cachetable in 'normal' strategy */
487
    (void) cachetable;
270,000✔
488

489
    for (i = 0; i < reqcolor; i++) {
7,290,000✔
490
        distant = 0;
6,480,000✔
491
        r = pixel[0] - palette[i * depth + 0];
6,480,000✔
492
        distant += r * r * complexion;
6,480,000✔
493
        for (n = 1; n < depth; ++n) {
19,440,000✔
494
            r = pixel[n] - palette[i * depth + n];
12,960,000✔
495
            distant += r * r;
12,960,000✔
496
        }
4,320,000✔
497
        if (distant < diff) {
6,480,000✔
498
            diff = distant;
1,971,420✔
499
            result = i;
1,971,420✔
500
        }
657,140✔
501
    }
2,160,000✔
502

503
    return result;
810,000✔
504
}
505

506

507
/*
508
 * Shared fast lookup flow handled by the lut module.  The palette lookup now
509
 * delegates to sixel_lut_map_pixel() so policy-specific caches and the
510
 * certification tree stay encapsulated inside src/lut.c.
511
 */
512

513
static int
514
lookup_fast_lut(unsigned char const * const pixel,
35,911,278✔
515
                int const depth,
516
                unsigned char const * const palette,
517
                int const reqcolor,
518
                unsigned short * const cachetable,
519
                int const complexion)
520
{
521
    (void)depth;
14,812,538✔
522
    (void)palette;
14,812,538✔
523
    (void)reqcolor;
14,812,538✔
524
    (void)cachetable;
14,812,538✔
525
    (void)complexion;
14,812,538✔
526

527
    if (dither_lut_context == NULL) {
35,911,278!
528
        return 0;
×
529
    }
530

531
    return sixel_lut_map_pixel(dither_lut_context, pixel);
35,911,278✔
532
}
14,812,538✔
533

534

535
static int
536
lookup_mono_darkbg(unsigned char const * const pixel,
1,730,520✔
537
                   int const depth,
538
                   unsigned char const * const palette,
539
                   int const reqcolor,
540
                   unsigned short * const cachetable,
541
                   int const complexion)
542
{
543
    int n;
544
    int distant;
545

546
    /* unused */ (void) palette;
576,840✔
547
    /* unused */ (void) cachetable;
576,840✔
548
    /* unused */ (void) complexion;
576,840✔
549

550
    distant = 0;
1,730,520✔
551
    for (n = 0; n < depth; ++n) {
6,922,080✔
552
        distant += pixel[n];
5,191,560✔
553
    }
1,730,520✔
554
    return distant >= 128 * reqcolor ? 1: 0;
1,730,520✔
555
}
556

557

558
static int
559
lookup_mono_lightbg(unsigned char const * const pixel,
810,000✔
560
                    int const depth,
561
                    unsigned char const * const palette,
562
                    int const reqcolor,
563
                    unsigned short * const cachetable,
564
                    int const complexion)
565
{
566
    int n;
567
    int distant;
568

569
    /* unused */ (void) palette;
270,000✔
570
    /* unused */ (void) cachetable;
270,000✔
571
    /* unused */ (void) complexion;
270,000✔
572

573
    distant = 0;
810,000✔
574
    for (n = 0; n < depth; ++n) {
3,240,000✔
575
        distant += pixel[n];
2,430,000✔
576
    }
810,000✔
577
    return distant < 128 * reqcolor ? 1: 0;
810,000✔
578
}
579

580

581

582
/*
583
 * Apply the palette into the supplied pixel buffer while coordinating the
584
 * dithering strategy.  The routine performs the following steps:
585
 *   - Select an index lookup helper, enabling the fast LUT path when the
586
 *     caller requested palette optimization and the input pixels are RGB.
587
 *   - Ensure the LUT object is prepared for the active policy, including
588
 *     weight selection for CERTLUT so complexion aware lookups remain
589
 *     accurate.
590
 *   - Dispatch to the positional, variable, or fixed error diffusion
591
 *     routines, each of which expects the LUT context to be initialized
592
 *     beforehand.
593
 *   - Release any temporary LUT references that were acquired for the fast
594
 *     lookup path.
595
 */
596
static SIXELSTATUS
597
sixel_dither_map_pixels(
256✔
598
    sixel_index_t     /* out */ *result,
599
    unsigned char     /* in */  *data,
600
    int               /* in */  width,
601
    int               /* in */  height,
602
    int               /* in */  band_origin,
603
    int               /* in */  output_start,
604
    int               /* in */  depth,
605
    unsigned char     /* in */  *palette,
606
    int               /* in */  reqcolor,
607
    int               /* in */  methodForDiffuse,
608
    int               /* in */  methodForScan,
609
    int               /* in */  methodForCarry,
610
    int               /* in */  foptimize,
611
    int               /* in */  foptimize_palette,
612
    int               /* in */  complexion,
613
    int               /* in */  lut_policy,
614
    int               /* in */  method_for_largest,
615
    sixel_lut_t       /* in */  *lut,
616
    int               /* in */  *ncolors,
617
    sixel_allocator_t /* in */  *allocator,
618
    sixel_dither_t    /* in */  *dither,
619
    int               /* in */  pixelformat)
620
{
170✔
621
#if _MSC_VER
622
    enum { max_depth = 4 };
623
#else
624
    const size_t max_depth = 4;
256✔
625
#endif
626
    unsigned char copy[max_depth];
170✔
627
    float new_palette_float[SIXEL_PALETTE_MAX * max_depth];
170✔
628
    SIXELSTATUS status = SIXEL_FALSE;
256✔
629
    int sum1;
630
    int sum2;
631
    int n;
632
    unsigned char new_palette[SIXEL_PALETTE_MAX * 4];
633
    unsigned short migration_map[SIXEL_PALETTE_MAX];
634
    sixel_dither_context_t context;
635
    int (*f_lookup)(unsigned char const * const pixel,
256✔
636
                    int const depth,
637
                    unsigned char const * const palette,
638
                    int const reqcolor,
639
                    unsigned short * const cachetable,
640
                    int const complexion) = lookup_normal;
641
    int use_varerr;
642
    int use_positional;
643
    int carry_mode;
644
    sixel_lut_t *active_lut;
645
    int manage_lut;
646
    int policy;
647
    int wR;
648
    int wG;
649
    int wB;
650

651
    active_lut = NULL;
256✔
652
    manage_lut = 0;
256✔
653

654
    memset(&context, 0, sizeof(context));
256✔
655
    context.result = result;
256✔
656
    context.width = width;
256✔
657
    context.height = height;
256✔
658
    context.band_origin = band_origin;
256✔
659
    context.output_start = output_start;
256✔
660
    context.depth = depth;
256✔
661
    context.palette = palette;
256✔
662
    context.reqcolor = reqcolor;
256✔
663
    context.new_palette = new_palette;
256✔
664
    context.migration_map = migration_map;
256✔
665
    context.ncolors = ncolors;
256✔
666
    context.scratch = copy;
256✔
667
    context.indextable = NULL;
256✔
668
    context.pixels = data;
256✔
669
    context.pixels_float = NULL;
256✔
670
    context.pixelformat = pixelformat;
256✔
671
    context.palette_float = NULL;
256✔
672
    context.new_palette_float = NULL;
256✔
673
    context.float_depth = 0;
256✔
674
    context.lookup_source_is_float = 0;
256✔
675
    context.prefer_palette_float_lookup = 0;
256✔
676
    if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
256!
677
        context.pixels_float = (float *)(void *)data;
×
678
    }
679

680
    if (dither != NULL && dither->palette != NULL) {
256!
681
        sixel_palette_t *palette_object;
682
        int float_components;
683

684
        palette_object = dither->palette;
256✔
685
        if (palette_object->entries_float32 != NULL
256!
686
                && palette_object->float_depth > 0) {
86!
687
            float_components = palette_object->float_depth
×
688
                / (int)sizeof(float);
689
            if (float_components > 0
×
690
                    && (size_t)float_components <= max_depth) {
×
691
                context.palette_float = palette_object->entries_float32;
×
692
                context.float_depth = float_components;
×
693
                context.new_palette_float = new_palette_float;
×
694
            }
695
        }
696
    }
86✔
697

698
    if (reqcolor < 1) {
256!
699
        status = SIXEL_BAD_ARGUMENT;
×
700
        sixel_helper_set_additional_message(
×
701
            "sixel_dither_map_pixels: "
702
            "a bad argument is detected, reqcolor < 0.");
703
        goto end;
×
704
    }
705

706
    use_varerr = (depth == 3
342✔
707
                  && methodForDiffuse == SIXEL_DIFFUSE_LSO2);
256!
708
    use_positional = (methodForDiffuse == SIXEL_DIFFUSE_A_DITHER
342✔
709
                      || methodForDiffuse == SIXEL_DIFFUSE_X_DITHER);
256!
710
    carry_mode = (methodForCarry == SIXEL_CARRY_ENABLE)
256✔
711
               ? SIXEL_CARRY_ENABLE
712
               : SIXEL_CARRY_DISABLE;
170!
713
    context.method_for_diffuse = methodForDiffuse;
256✔
714
    context.method_for_scan = methodForScan;
256✔
715
    context.method_for_carry = carry_mode;
256✔
716

717
    if (reqcolor == 2) {
256✔
718
        sum1 = 0;
18✔
719
        sum2 = 0;
18✔
720
        for (n = 0; n < depth; ++n) {
72✔
721
            sum1 += palette[n];
54✔
722
        }
18✔
723
        for (n = depth; n < depth + depth; ++n) {
72✔
724
            sum2 += palette[n];
54✔
725
        }
18✔
726
        if (sum1 == 0 && sum2 == 255 * 3) {
18!
727
            f_lookup = lookup_mono_darkbg;
12✔
728
        } else if (sum1 == 255 * 3 && sum2 == 0) {
10!
729
            f_lookup = lookup_mono_lightbg;
3✔
730
        }
1✔
731
    }
6✔
732
    if (foptimize && depth == 3 && f_lookup == lookup_normal) {
256!
733
        f_lookup = lookup_fast_lut;
238✔
734
    }
80✔
735
    if (lut_policy == SIXEL_LUT_POLICY_NONE) {
256!
736
        f_lookup = lookup_normal;
×
737
    }
738

739
    if (f_lookup == lookup_fast_lut) {
256✔
740
        if (depth != 3) {
238!
741
            status = SIXEL_BAD_ARGUMENT;
×
742
            sixel_helper_set_additional_message(
×
743
                "sixel_dither_map_pixels: fast lookup requires RGB pixels.");
744
            goto end;
×
745
        }
746
        policy = lut_policy;
238✔
747
        if (policy != SIXEL_LUT_POLICY_CERTLUT
318!
748
            && policy != SIXEL_LUT_POLICY_5BIT
238!
749
            && policy != SIXEL_LUT_POLICY_6BIT) {
238!
750
            policy = SIXEL_LUT_POLICY_6BIT;
238✔
751
        }
80✔
752
        if (lut == NULL) {
238!
753
            status = sixel_lut_new(&active_lut, policy, allocator);
×
754
            if (SIXEL_FAILED(status)) {
×
755
                goto end;
×
756
            }
757
            manage_lut = 1;
×
758
        } else {
759
            active_lut = lut;
238✔
760
            manage_lut = 0;
238✔
761
        }
762
        if (policy == SIXEL_LUT_POLICY_CERTLUT) {
238!
763
            if (method_for_largest == SIXEL_LARGE_LUM) {
×
764
                wR = complexion * 299;
×
765
                wG = 587;
×
766
                wB = 114;
×
767
            } else {
768
                wR = complexion;
×
769
                wG = 1;
×
770
                wB = 1;
×
771
            }
772
        } else {
773
            wR = complexion;
238✔
774
            wG = 1;
238✔
775
            wB = 1;
238✔
776
        }
777
        status = sixel_lut_configure(active_lut,
318✔
778
                                     palette,
80✔
779
                                     depth,
80✔
780
                                     reqcolor,
80✔
781
                                     complexion,
80✔
782
                                     wR,
80✔
783
                                     wG,
80✔
784
                                     wB,
80✔
785
                                     policy,
80✔
786
                                     pixelformat);
80✔
787
        if (SIXEL_FAILED(status)) {
238!
788
            goto end;
×
789
        }
790
        dither_lut_context = active_lut;
238✔
791
    }
80✔
792

793
    context.lookup = f_lookup;
256✔
794
    if (f_lookup == lookup_fast_lut
336!
795
        && SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
244!
796
        context.lookup_source_is_float = 1;
×
797
    }
798
    if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)
256!
799
        && context.palette_float != NULL
86!
800
        && context.float_depth >= context.depth
86!
801
        && f_lookup == lookup_normal) {
×
802
        context.prefer_palette_float_lookup = 1;
×
803
    }
804
    context.optimize_palette = foptimize_palette;
256✔
805
    context.complexion = complexion;
256✔
806

807
    if (use_positional) {
256!
808
        if (context.pixels_float != NULL
×
809
            && dither != NULL
×
810
            && dither->prefer_rgbfloat32 != 0) {
×
811
            status = sixel_dither_apply_positional_float32(dither, &context);
×
812
            if (status == SIXEL_BAD_ARGUMENT) {
×
813
                status = sixel_dither_apply_positional_8bit(dither, &context);
×
814
            }
815
        } else {
816
            status = sixel_dither_apply_positional_8bit(dither, &context);
×
817
        }
818
    } else if (use_varerr) {
256!
819
        if (context.pixels_float != NULL
×
820
            && dither != NULL
×
821
            && dither->prefer_rgbfloat32 != 0) {
×
822
            status = sixel_dither_apply_varcoeff_float32(dither, &context);
×
823
            if (status == SIXEL_BAD_ARGUMENT) {
×
824
                status = sixel_dither_apply_varcoeff_8bit(dither, &context);
×
825
            }
826
        } else {
827
            status = sixel_dither_apply_varcoeff_8bit(dither, &context);
×
828
        }
829
    } else {
830
        if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)
256!
831
            && context.pixels_float != NULL
86!
832
            && methodForCarry != SIXEL_CARRY_ENABLE
86!
833
            && depth == 3
×
834
            && dither != NULL
×
835
            && dither->prefer_rgbfloat32 != 0) {
×
836
            /*
837
             * Float inputs can reuse the float32 renderer for every
838
             * fixed-weight kernel (FS, Sierra, Stucki, etc.) as long as
839
             * carry buffers are disabled.  The legacy 8bit path remains as
840
             * a fallback for unsupported argument combinations.
841
             */
842
            status = sixel_dither_apply_fixed_float32(dither, &context);
×
843
            if (status == SIXEL_BAD_ARGUMENT) {
×
844
                status = sixel_dither_apply_fixed_8bit(dither, &context);
×
845
            }
846
        } else {
847
            status = sixel_dither_apply_fixed_8bit(dither, &context);
256✔
848
        }
849
    }
850
    if (SIXEL_FAILED(status)) {
256!
851
        goto end;
×
852
    }
853

854
    status = SIXEL_OK;
256✔
855

856
end:
170✔
857
    if (dither_lut_context != NULL && f_lookup == lookup_fast_lut) {
256!
858
        dither_lut_context = NULL;
238✔
859
    }
80✔
860
    if (manage_lut && active_lut != NULL) {
256!
861
        sixel_lut_unref(active_lut);
×
862
    }
863
    return status;
256✔
864
}
865

866
#if SIXEL_ENABLE_THREADS
867
typedef struct sixel_parallel_dither_plan {
868
    sixel_index_t *dest;
869
    unsigned char *pixels;
870
    sixel_palette_t *palette;
871
    sixel_allocator_t *allocator;
872
    sixel_dither_t *dither;
873
    size_t row_bytes;
874
    int width;
875
    int height;
876
    int band_height;
877
    int overlap;
878
    int method_for_diffuse;
879
    int method_for_scan;
880
    int method_for_carry;
881
    int optimize_palette;
882
    int optimize_palette_entries;
883
    int complexion;
884
    int lut_policy;
885
    int method_for_largest;
886
    int reqcolor;
887
    int pixelformat;
888
    sixel_parallel_logger_t *logger;
889
} sixel_parallel_dither_plan_t;
890

891
static int
NEW
892
sixel_dither_parallel_worker(tp_job_t job,
×
893
                             void *userdata,
894
                             void *workspace)
895
{
896
    sixel_parallel_dither_plan_t *plan;
897
    unsigned char *copy;
898
    size_t required;
899
    size_t offset;
900
    int band_index;
901
    int y0;
902
    int y1;
903
    int in0;
904
    int in1;
905
    int rows;
906
    int local_ncolors;
907
    SIXELSTATUS status;
908

909
    (void)workspace;
910

NEW
911
    plan = (sixel_parallel_dither_plan_t *)userdata;
×
NEW
912
    if (plan == NULL) {
×
NEW
913
        return SIXEL_BAD_ARGUMENT;
×
914
    }
915

NEW
916
    band_index = job.band_index;
×
NEW
917
    if (band_index < 0) {
×
NEW
918
        return SIXEL_BAD_ARGUMENT;
×
919
    }
920

NEW
921
    y0 = band_index * plan->band_height;
×
NEW
922
    if (y0 >= plan->height) {
×
NEW
923
        return SIXEL_OK;
×
924
    }
925

NEW
926
    y1 = y0 + plan->band_height;
×
NEW
927
    if (y1 > plan->height) {
×
NEW
928
        y1 = plan->height;
×
929
    }
930

NEW
931
    in0 = y0 - plan->overlap;
×
NEW
932
    if (in0 < 0) {
×
NEW
933
        in0 = 0;
×
934
    }
NEW
935
    in1 = y1;
×
NEW
936
    rows = in1 - in0;
×
NEW
937
    if (rows <= 0) {
×
NEW
938
        return SIXEL_OK;
×
939
    }
940

NEW
941
    required = (size_t)rows * plan->row_bytes;
×
NEW
942
    copy = (unsigned char *)malloc(required);
×
NEW
943
    if (copy == NULL) {
×
NEW
944
        return SIXEL_BAD_ALLOCATION;
×
945
    }
NEW
946
    offset = (size_t)in0 * plan->row_bytes;
×
NEW
947
    memcpy(copy, plan->pixels + offset, required);
×
948

NEW
949
    if (plan->logger != NULL) {
×
NEW
950
        sixel_parallel_logger_logf(plan->logger,
×
951
                                   "dither",
952
                                   "start",
953
                                   band_index,
954
                                   in0,
955
                                   y0,
956
                                   y1,
957
                                   in0,
958
                                   in1,
959
                                   "prepare rows=%d",
960
                                   rows);
961
    }
962

NEW
963
    local_ncolors = plan->reqcolor;
×
964
    /*
965
     * Map directly into the shared destination but suppress writes
966
     * before output_start.  The overlap rows are computed only to warm
967
     * up the error diffusion and are discarded by the output_start
968
     * check in the renderer, so neighboring bands never clobber each
969
     * other's body.
970
     */
NEW
971
    status = sixel_dither_map_pixels(plan->dest + (size_t)in0 * plan->width,
×
972
                                     copy,
973
                                     plan->width,
974
                                     rows,
975
                                     in0,
976
                                     y0,
977
                                     3,
NEW
978
                                     plan->palette->entries,
×
979
                                     plan->reqcolor,
980
                                     plan->method_for_diffuse,
981
                                     plan->method_for_scan,
982
                                     plan->method_for_carry,
983
                                     plan->optimize_palette,
984
                                     plan->optimize_palette_entries,
985
                                     plan->complexion,
986
                                     plan->lut_policy,
987
                                     plan->method_for_largest,
988
                                     NULL,
989
                                     &local_ncolors,
990
                                     plan->allocator,
991
                                     plan->dither,
992
                                     plan->pixelformat);
NEW
993
    if (plan->logger != NULL) {
×
NEW
994
        sixel_parallel_logger_logf(plan->logger,
×
995
                                   "dither",
996
                                   "finish",
997
                                   band_index,
998
                                   in1 - 1,
999
                                   y0,
1000
                                   y1,
1001
                                   in0,
1002
                                   in1,
1003
                                   "status=%d rows=%d",
1004
                                   status,
1005
                                   rows);
1006
    }
NEW
1007
    free(copy);
×
NEW
1008
    return status;
×
1009
}
1010

1011
static SIXELSTATUS
NEW
1012
sixel_dither_apply_palette_parallel(sixel_parallel_dither_plan_t *plan,
×
1013
                                    int threads)
1014
{
1015
    SIXELSTATUS status;
1016
    threadpool_t *pool;
1017
    size_t depth_bytes;
1018
    int nbands;
1019
    int queue_depth;
1020
    int band_index;
1021

NEW
1022
    if (plan == NULL || plan->palette == NULL) {
×
NEW
1023
        return SIXEL_BAD_ARGUMENT;
×
1024
    }
1025

NEW
1026
    depth_bytes = (size_t)sixel_helper_compute_depth(plan->pixelformat);
×
NEW
1027
    if (depth_bytes == 0U) {
×
NEW
1028
        return SIXEL_BAD_ARGUMENT;
×
1029
    }
NEW
1030
    plan->row_bytes = (size_t)plan->width * depth_bytes;
×
1031

NEW
1032
    nbands = (plan->height + plan->band_height - 1) / plan->band_height;
×
NEW
1033
    if (nbands < 1) {
×
NEW
1034
        return SIXEL_OK;
×
1035
    }
1036

NEW
1037
    if (threads > nbands) {
×
NEW
1038
        threads = nbands;
×
1039
    }
NEW
1040
    if (threads < 1) {
×
NEW
1041
        threads = 1;
×
1042
    }
1043

NEW
1044
    queue_depth = threads * 3;
×
NEW
1045
    if (queue_depth > nbands) {
×
NEW
1046
        queue_depth = nbands;
×
1047
    }
NEW
1048
    if (queue_depth < 1) {
×
NEW
1049
        queue_depth = 1;
×
1050
    }
1051

NEW
1052
    pool = threadpool_create(threads,
×
1053
                             queue_depth,
1054
                             0,
1055
                             sixel_dither_parallel_worker,
1056
                             plan);
NEW
1057
    if (pool == NULL) {
×
NEW
1058
        return SIXEL_BAD_ALLOCATION;
×
1059
    }
1060

NEW
1061
    for (band_index = 0; band_index < nbands; ++band_index) {
×
1062
        tp_job_t job;
1063

NEW
1064
        job.band_index = band_index;
×
NEW
1065
        threadpool_push(pool, job);
×
1066
    }
1067

NEW
1068
    threadpool_finish(pool);
×
NEW
1069
    status = threadpool_get_error(pool);
×
NEW
1070
    threadpool_destroy(pool);
×
1071

NEW
1072
    return status;
×
1073
}
1074
#endif
1075

1076

1077
/*
1078
 * Helper that detects whether the palette currently matches either of the
1079
 * builtin monochrome definitions.  These tables skip cache initialization
1080
 * during fast-path dithering because they already match the terminal
1081
 * defaults.
1082
 */
1083
static int
1084
sixel_palette_is_builtin_mono(sixel_palette_t const *palette)
253✔
1085
{
1086
    if (palette == NULL) {
253!
1087
        return 0;
×
1088
    }
1089
    if (palette->entries == NULL) {
253!
1090
        return 0;
×
1091
    }
1092
    if (palette->entry_count < 2U) {
253✔
1093
        return 0;
15✔
1094
    }
1095
    if (palette->depth != 3) {
238!
1096
        return 0;
×
1097
    }
1098
    if (memcmp(palette->entries, pal_mono_dark,
318✔
1099
               sizeof(pal_mono_dark)) == 0) {
80✔
1100
        return 1;
12✔
1101
    }
1102
    if (memcmp(palette->entries, pal_mono_light,
302✔
1103
               sizeof(pal_mono_light)) == 0) {
76✔
1104
        return 1;
3✔
1105
    }
1106
    return 0;
223✔
1107
}
85✔
1108

1109
/*
1110
 * Route palette application through the local dithering helper.  The
1111
 * function keeps all state in the palette object so we can share cache
1112
 * buffers between invocations and later stages.  The flow is:
1113
 *   1. Synchronize the quantizer configuration with the dither object so the
1114
 *      LUT builder honors the requested policy.
1115
 *   2. Invoke sixel_dither_map_pixels() to populate the index buffer and
1116
 *      record the resulting palette size.
1117
 *   3. Return the status to the caller so palette application errors can be
1118
 *      reported at a single site.
1119
 */
1120
static SIXELSTATUS
1121
sixel_dither_resolve_indexes(sixel_index_t *result,
256✔
1122
                             unsigned char *data,
1123
                             int width,
1124
                             int height,
1125
                             int depth,
1126
                             sixel_palette_t *palette,
1127
                             int reqcolor,
1128
                             int method_for_diffuse,
1129
                             int method_for_scan,
1130
                             int method_for_carry,
1131
                             int foptimize,
1132
                             int foptimize_palette,
1133
                             int complexion,
1134
                              int lut_policy,
1135
                              int method_for_largest,
1136
                              int *ncolors,
1137
                              sixel_allocator_t *allocator,
1138
                              sixel_dither_t *dither,
1139
                              int pixelformat)
1140
{
1141
    SIXELSTATUS status = SIXEL_FALSE;
256✔
1142

1143
    if (palette == NULL || palette->entries == NULL) {
256!
1144
        return SIXEL_BAD_ARGUMENT;
×
1145
    }
1146

1147
    sixel_palette_set_lut_policy(lut_policy);
256✔
1148
    sixel_palette_set_method_for_largest(method_for_largest);
256✔
1149

1150
    status = sixel_dither_map_pixels(result,
342✔
1151
                                     data,
86✔
1152
                                     width,
86✔
1153
                                     height,
86✔
1154
                                     0,
1155
                                     0,
1156
                                     depth,
86✔
1157
                                     palette->entries,
86✔
1158
                                     reqcolor,
86✔
1159
                                     method_for_diffuse,
86✔
1160
                                     method_for_scan,
86✔
1161
                                     method_for_carry,
86✔
1162
                                     foptimize,
86✔
1163
                                     foptimize_palette,
86✔
1164
                                     complexion,
86✔
1165
                                     lut_policy,
86✔
1166
                                     method_for_largest,
86✔
1167
                                     palette->lut,
256✔
1168
                                     ncolors,
86✔
1169
                                     allocator,
86✔
1170
                                     dither,
86✔
1171
                                     pixelformat);
86✔
1172

1173
    return status;
256✔
1174
}
86✔
1175

1176

1177
/*
1178
 * VT340 undocumented behavior regarding the color palette reported
1179
 * by Vertis Sidus(@vrtsds):
1180
 *     it loads the first fifteen colors as 1 through 15, and loads the
1181
 *     sixteenth color as 0.
1182
 */
1183
static const unsigned char pal_vt340_mono[] = {
1184
    /* 1   Gray-2   */  13 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
1185
    /* 2   Gray-4   */  26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1186
    /* 3   Gray-6   */  40 * 255 / 100, 40 * 255 / 100, 40 * 255 / 100,
1187
    /* 4   Gray-1   */   6 * 255 / 100,  6 * 255 / 100,  6 * 255 / 100,
1188
    /* 5   Gray-3   */  20 * 255 / 100, 20 * 255 / 100, 20 * 255 / 100,
1189
    /* 6   Gray-5   */  33 * 255 / 100, 33 * 255 / 100, 33 * 255 / 100,
1190
    /* 7   White 7  */  46 * 255 / 100, 46 * 255 / 100, 46 * 255 / 100,
1191
    /* 8   Black 0  */   0 * 255 / 100,  0 * 255 / 100,  0 * 255 / 100,
1192
    /* 9   Gray-2   */  13 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
1193
    /* 10  Gray-4   */  26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1194
    /* 11  Gray-6   */  40 * 255 / 100, 40 * 255 / 100, 40 * 255 / 100,
1195
    /* 12  Gray-1   */   6 * 255 / 100,  6 * 255 / 100,  6 * 255 / 100,
1196
    /* 13  Gray-3   */  20 * 255 / 100, 20 * 255 / 100, 20 * 255 / 100,
1197
    /* 14  Gray-5   */  33 * 255 / 100, 33 * 255 / 100, 33 * 255 / 100,
1198
    /* 15  White 7  */  46 * 255 / 100, 46 * 255 / 100, 46 * 255 / 100,
1199
    /* 0   Black    */   0 * 255 / 100,  0 * 255 / 100,  0 * 255 / 100,
1200
};
1201

1202

1203
static const unsigned char pal_vt340_color[] = {
1204
    /* 1   Blue     */  20 * 255 / 100, 20 * 255 / 100, 80 * 255 / 100,
1205
    /* 2   Red      */  80 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
1206
    /* 3   Green    */  20 * 255 / 100, 80 * 255 / 100, 20 * 255 / 100,
1207
    /* 4   Magenta  */  80 * 255 / 100, 20 * 255 / 100, 80 * 255 / 100,
1208
    /* 5   Cyan     */  20 * 255 / 100, 80 * 255 / 100, 80 * 255 / 100,
1209
    /* 6   Yellow   */  80 * 255 / 100, 80 * 255 / 100, 20 * 255 / 100,
1210
    /* 7   Gray 50% */  53 * 255 / 100, 53 * 255 / 100, 53 * 255 / 100,
1211
    /* 8   Gray 25% */  26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1212
    /* 9   Blue*    */  33 * 255 / 100, 33 * 255 / 100, 60 * 255 / 100,
1213
    /* 10  Red*     */  60 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1214
    /* 11  Green*   */  33 * 255 / 100, 60 * 255 / 100, 33 * 255 / 100,
1215
    /* 12  Magenta* */  60 * 255 / 100, 33 * 255 / 100, 60 * 255 / 100,
1216
    /* 13  Cyan*    */  33 * 255 / 100, 60 * 255 / 100, 60 * 255 / 100,
1217
    /* 14  Yellow*  */  60 * 255 / 100, 60 * 255 / 100, 33 * 255 / 100,
1218
    /* 15  Gray 75% */  80 * 255 / 100, 80 * 255 / 100, 80 * 255 / 100,
1219
    /* 0   Black    */   0 * 255 / 100,  0 * 255 / 100,  0 * 255 / 100,
1220
};
1221

1222

1223
/* create dither context object */
1224
SIXELAPI SIXELSTATUS
1225
sixel_dither_new(
520✔
1226
    sixel_dither_t    /* out */ **ppdither, /* dither object to be created */
1227
    int               /* in */  ncolors,    /* required colors */
1228
    sixel_allocator_t /* in */  *allocator) /* allocator, null if you use
1229
                                               default allocator */
1230
{
1231
    SIXELSTATUS status = SIXEL_FALSE;
520✔
1232
    size_t headsize;
1233
    int quality_mode;
1234
    sixel_palette_t *palette;
1235

1236
    /* ensure given pointer is not null */
1237
    if (ppdither == NULL) {
520!
1238
        sixel_helper_set_additional_message(
×
1239
            "sixel_dither_new: ppdither is null.");
1240
        status = SIXEL_BAD_ARGUMENT;
×
1241
        goto end;
×
1242
    }
1243
    *ppdither = NULL;
520✔
1244

1245
    if (allocator == NULL) {
520✔
1246
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
39✔
1247
        if (SIXEL_FAILED(status)) {
39!
1248
            *ppdither = NULL;
×
1249
            goto end;
×
1250
        }
1251
    } else {
13✔
1252
        sixel_allocator_ref(allocator);
481✔
1253
    }
1254

1255
    if (ncolors < 0) {
520✔
1256
        ncolors = SIXEL_PALETTE_MAX;
36✔
1257
        quality_mode = SIXEL_QUALITY_HIGHCOLOR;
36✔
1258
    } else {
12✔
1259
        if (ncolors > SIXEL_PALETTE_MAX) {
484!
1260
            status = SIXEL_BAD_INPUT;
×
1261
            goto end;
×
1262
        } else if (ncolors < 1) {
484!
1263
            status = SIXEL_BAD_INPUT;
×
1264
            sixel_helper_set_additional_message(
×
1265
                "sixel_dither_new: palette colors must be more than 0");
1266
            goto end;
×
1267
        }
1268
        quality_mode = SIXEL_QUALITY_LOW;
484✔
1269
    }
1270
    headsize = sizeof(sixel_dither_t);
520✔
1271

1272
    *ppdither = (sixel_dither_t *)sixel_allocator_malloc(allocator, headsize);
520✔
1273
    if (*ppdither == NULL) {
520!
1274
        sixel_allocator_unref(allocator);
×
1275
        sixel_helper_set_additional_message(
×
1276
            "sixel_dither_new: sixel_allocator_malloc() failed.");
1277
        status = SIXEL_BAD_ALLOCATION;
×
1278
        goto end;
×
1279
    }
1280

1281
    (*ppdither)->ref = 1U;
520✔
1282
    (*ppdither)->palette = NULL;
520✔
1283
    (*ppdither)->reqcolors = ncolors;
520✔
1284
    (*ppdither)->force_palette = 0;
520✔
1285
    (*ppdither)->ncolors = ncolors;
520✔
1286
    (*ppdither)->origcolors = (-1);
520✔
1287
    (*ppdither)->keycolor = (-1);
520✔
1288
    (*ppdither)->optimized = 0;
520✔
1289
    (*ppdither)->optimize_palette = 0;
520✔
1290
    (*ppdither)->complexion = 1;
520✔
1291
    (*ppdither)->bodyonly = 0;
520✔
1292
    (*ppdither)->method_for_largest = SIXEL_LARGE_NORM;
520✔
1293
    (*ppdither)->method_for_rep = SIXEL_REP_CENTER_BOX;
520✔
1294
    (*ppdither)->method_for_diffuse = SIXEL_DIFFUSE_FS;
520✔
1295
    (*ppdither)->method_for_scan = SIXEL_SCAN_AUTO;
520✔
1296
    (*ppdither)->method_for_carry = SIXEL_CARRY_AUTO;
520✔
1297
    (*ppdither)->quality_mode = quality_mode;
520✔
1298
    (*ppdither)->requested_quality_mode = quality_mode;
520✔
1299
    (*ppdither)->pixelformat = SIXEL_PIXELFORMAT_RGB888;
520✔
1300
    (*ppdither)->prefer_rgbfloat32 = sixel_dither_env_wants_float32();
520✔
1301
    (*ppdither)->allocator = allocator;
520✔
1302
    (*ppdither)->lut_policy = SIXEL_LUT_POLICY_AUTO;
520✔
1303
    (*ppdither)->sixel_reversible = 0;
520✔
1304
    (*ppdither)->quantize_model = SIXEL_QUANTIZE_MODEL_AUTO;
520✔
1305
    (*ppdither)->final_merge_mode = SIXEL_FINAL_MERGE_AUTO;
520✔
1306
    (*ppdither)->pipeline_row_callback = NULL;
520✔
1307
    (*ppdither)->pipeline_row_priv = NULL;
520✔
1308
    (*ppdither)->pipeline_index_buffer = NULL;
520✔
1309
    (*ppdither)->pipeline_index_size = 0;
520✔
1310
    (*ppdither)->pipeline_index_owned = 0;
520✔
1311
    (*ppdither)->pipeline_parallel_active = 0;
520✔
1312
    (*ppdither)->pipeline_band_height = 0;
520✔
1313
    (*ppdither)->pipeline_band_overlap = 0;
520✔
1314
    (*ppdither)->pipeline_dither_threads = 0;
520✔
1315
    (*ppdither)->pipeline_image_height = 0;
520✔
1316
    (*ppdither)->pipeline_logger = NULL;
520✔
1317

1318
    status = sixel_palette_new(&(*ppdither)->palette, allocator);
520✔
1319
    if (SIXEL_FAILED(status)) {
520!
1320
        sixel_allocator_free(allocator, *ppdither);
×
1321
        *ppdither = NULL;
×
1322
        goto end;
×
1323
    }
1324

1325
    palette = (*ppdither)->palette;
520✔
1326
    palette->requested_colors = (unsigned int)ncolors;
520✔
1327
    palette->quality_mode = quality_mode;
520✔
1328
    palette->force_palette = 0;
520✔
1329
    palette->lut_policy = SIXEL_LUT_POLICY_AUTO;
520✔
1330

1331
    status = sixel_palette_resize(palette,
694✔
1332
                                  (unsigned int)ncolors,
174✔
1333
                                  3,
1334
                                  allocator);
174✔
1335
    if (SIXEL_FAILED(status)) {
520!
1336
        sixel_palette_unref(palette);
×
1337
        (*ppdither)->palette = NULL;
×
1338
        sixel_allocator_free(allocator, *ppdither);
×
1339
        *ppdither = NULL;
×
1340
        goto end;
×
1341
    }
1342

1343
    status = SIXEL_OK;
520✔
1344

1345
end:
346✔
1346
    if (SIXEL_FAILED(status)) {
520!
1347
        sixel_allocator_unref(allocator);
×
1348
    }
1349
    return status;
520✔
1350
}
1351

1352

1353
/* create dither context object (deprecated) */
1354
SIXELAPI sixel_dither_t *
1355
sixel_dither_create(
×
1356
    int     /* in */ ncolors)
1357
{
1358
    SIXELSTATUS status = SIXEL_FALSE;
×
1359
    sixel_dither_t *dither = NULL;
×
1360

1361
    status = sixel_dither_new(&dither, ncolors, NULL);
×
1362
    if (SIXEL_FAILED(status)) {
×
1363
        goto end;
×
1364
    }
1365

1366
end:
1367
    return dither;
×
1368
}
1369

1370

1371
SIXELAPI void
1372
sixel_dither_destroy(
520✔
1373
    sixel_dither_t  /* in */ *dither)
1374
{
1375
    sixel_allocator_t *allocator;
1376

1377
    if (dither) {
520!
1378
        allocator = dither->allocator;
520✔
1379
        if (dither->palette != NULL) {
520!
1380
            sixel_palette_unref(dither->palette);
520✔
1381
            dither->palette = NULL;
520✔
1382
        }
174✔
1383
        sixel_allocator_free(allocator, dither);
520✔
1384
        sixel_allocator_unref(allocator);
520✔
1385
    }
174✔
1386
}
520✔
1387

1388

1389
SIXELAPI void
1390
sixel_dither_ref(
960✔
1391
    sixel_dither_t  /* in */ *dither)
1392
{
1393
    /* TODO: be thread safe */
1394
    ++dither->ref;
960✔
1395
}
960✔
1396

1397

1398
SIXELAPI void
1399
sixel_dither_unref(
2,025✔
1400
    sixel_dither_t  /* in */ *dither)
1401
{
1402
    /* TODO: be thread safe */
1403
    if (dither != NULL && --dither->ref == 0) {
2,025✔
1404
        sixel_dither_destroy(dither);
520✔
1405
    }
174✔
1406
}
2,025✔
1407

1408

1409
SIXELAPI sixel_dither_t *
1410
sixel_dither_get(
39✔
1411
    int     /* in */ builtin_dither)
1412
{
1413
    SIXELSTATUS status = SIXEL_FALSE;
39✔
1414
    unsigned char *palette;
1415
    int ncolors;
1416
    int keycolor;
1417
    sixel_dither_t *dither = NULL;
39✔
1418

1419
    switch (builtin_dither) {
39!
1420
    case SIXEL_BUILTIN_MONO_DARK:
6✔
1421
        ncolors = 2;
9✔
1422
        palette = (unsigned char *)pal_mono_dark;
9✔
1423
        keycolor = 0;
9✔
1424
        break;
9✔
1425
    case SIXEL_BUILTIN_MONO_LIGHT:
2✔
1426
        ncolors = 2;
3✔
1427
        palette = (unsigned char *)pal_mono_light;
3✔
1428
        keycolor = 0;
3✔
1429
        break;
3✔
1430
    case SIXEL_BUILTIN_XTERM16:
4✔
1431
        ncolors = 16;
6✔
1432
        palette = (unsigned char *)pal_xterm256;
6✔
1433
        keycolor = (-1);
6✔
1434
        break;
6✔
1435
    case SIXEL_BUILTIN_XTERM256:
2✔
1436
        ncolors = 256;
3✔
1437
        palette = (unsigned char *)pal_xterm256;
3✔
1438
        keycolor = (-1);
3✔
1439
        break;
3✔
1440
    case SIXEL_BUILTIN_VT340_MONO:
2✔
1441
        ncolors = 16;
3✔
1442
        palette = (unsigned char *)pal_vt340_mono;
3✔
1443
        keycolor = (-1);
3✔
1444
        break;
3✔
1445
    case SIXEL_BUILTIN_VT340_COLOR:
2✔
1446
        ncolors = 16;
3✔
1447
        palette = (unsigned char *)pal_vt340_color;
3✔
1448
        keycolor = (-1);
3✔
1449
        break;
3✔
1450
    case SIXEL_BUILTIN_G1:
2✔
1451
        ncolors = 2;
3✔
1452
        palette = (unsigned char *)pal_gray_1bit;
3✔
1453
        keycolor = (-1);
3✔
1454
        break;
3✔
1455
    case SIXEL_BUILTIN_G2:
2✔
1456
        ncolors = 4;
3✔
1457
        palette = (unsigned char *)pal_gray_2bit;
3✔
1458
        keycolor = (-1);
3✔
1459
        break;
3✔
1460
    case SIXEL_BUILTIN_G4:
2✔
1461
        ncolors = 16;
3✔
1462
        palette = (unsigned char *)pal_gray_4bit;
3✔
1463
        keycolor = (-1);
3✔
1464
        break;
3✔
1465
    case SIXEL_BUILTIN_G8:
2✔
1466
        ncolors = 256;
3✔
1467
        palette = (unsigned char *)pal_gray_8bit;
3✔
1468
        keycolor = (-1);
3✔
1469
        break;
3✔
1470
    default:
1471
        goto end;
×
1472
    }
1473

1474
    status = sixel_dither_new(&dither, ncolors, NULL);
39✔
1475
    if (SIXEL_FAILED(status)) {
39!
1476
        dither = NULL;
×
1477
        goto end;
×
1478
    }
1479

1480
    status = sixel_palette_set_entries(dither->palette,
52✔
1481
                                       palette,
13✔
1482
                                       (unsigned int)ncolors,
13✔
1483
                                       3,
1484
                                       dither->allocator);
39✔
1485
    if (SIXEL_FAILED(status)) {
39!
1486
        sixel_dither_unref(dither);
×
1487
        dither = NULL;
×
1488
        goto end;
×
1489
    }
1490
    dither->palette->requested_colors = (unsigned int)ncolors;
39✔
1491
    dither->palette->entry_count = (unsigned int)ncolors;
39✔
1492
    dither->palette->depth = 3;
39✔
1493
    dither->keycolor = keycolor;
39✔
1494
    dither->optimized = 1;
39✔
1495
    dither->optimize_palette = 0;
39✔
1496

1497
end:
26✔
1498
    return dither;
39✔
1499
}
1500

1501

1502
static void
1503
sixel_dither_set_method_for_largest(
214✔
1504
    sixel_dither_t  /* in */ *dither,
1505
    int             /* in */ method_for_largest)
1506
{
1507
    if (method_for_largest == SIXEL_LARGE_AUTO) {
214✔
1508
        method_for_largest = SIXEL_LARGE_NORM;
187✔
1509
    }
63✔
1510
    dither->method_for_largest = method_for_largest;
214✔
1511
}
214✔
1512

1513

1514
static void
1515
sixel_dither_set_method_for_rep(
214✔
1516
    sixel_dither_t  /* in */ *dither,
1517
    int             /* in */ method_for_rep)
1518
{
1519
    if (method_for_rep == SIXEL_REP_AUTO) {
214✔
1520
        method_for_rep = SIXEL_REP_CENTER_BOX;
184✔
1521
    }
62✔
1522
    dither->method_for_rep = method_for_rep;
214✔
1523
}
214✔
1524

1525

1526
static void
1527
sixel_dither_set_quality_mode(
214✔
1528
    sixel_dither_t  /* in */  *dither,
1529
    int             /* in */  quality_mode)
1530
{
1531
    dither->requested_quality_mode = quality_mode;
214✔
1532

1533
    if (quality_mode == SIXEL_QUALITY_AUTO) {
214✔
1534
        if (dither->ncolors <= 8) {
193✔
1535
            quality_mode = SIXEL_QUALITY_HIGH;
12✔
1536
        } else {
4✔
1537
            quality_mode = SIXEL_QUALITY_LOW;
181✔
1538
        }
1539
    }
65✔
1540
    dither->quality_mode = quality_mode;
214✔
1541
}
214✔
1542

1543

1544
SIXELAPI SIXELSTATUS
1545
sixel_dither_initialize(
214✔
1546
    sixel_dither_t  /* in */ *dither,
1547
    unsigned char   /* in */ *data,
1548
    int             /* in */ width,
1549
    int             /* in */ height,
1550
    int             /* in */ pixelformat,
1551
    int             /* in */ method_for_largest,
1552
    int             /* in */ method_for_rep,
1553
    int             /* in */ quality_mode)
1554
{
1555
    unsigned char *buf = NULL;
214✔
1556
    unsigned char *normalized_pixels = NULL;
214✔
1557
    float *float_pixels = NULL;
214✔
1558
    unsigned char *input_pixels;
1559
    SIXELSTATUS status = SIXEL_FALSE;
214✔
1560
    size_t total_pixels;
1561
    unsigned int payload_length;
1562
    int palette_pixelformat;
1563
    int prefer_rgbfloat32;
1564

1565
    /* ensure dither object is not null */
1566
    if (dither == NULL) {
214!
1567
        sixel_helper_set_additional_message(
×
1568
            "sixel_dither_new: dither is null.");
1569
        status = SIXEL_BAD_ARGUMENT;
×
1570
        goto end;
×
1571
    }
1572

1573
    /* increment ref count */
1574
    sixel_dither_ref(dither);
214✔
1575

1576
    sixel_dither_set_pixelformat(dither, pixelformat);
214✔
1577

1578
    /* keep quantizer policy in sync with the dither object */
1579
    sixel_palette_set_lut_policy(dither->lut_policy);
214✔
1580

1581
    input_pixels = NULL;
214✔
1582
    total_pixels = (size_t)width * (size_t)height;
214✔
1583
    payload_length = 0U;
214✔
1584
    palette_pixelformat = SIXEL_PIXELFORMAT_RGB888;
214✔
1585
    prefer_rgbfloat32 = dither->prefer_rgbfloat32;
214✔
1586

1587
    switch (pixelformat) {
214!
1588
    case SIXEL_PIXELFORMAT_RGB888:
142✔
1589
        input_pixels = data;
214✔
1590
        break;
214✔
1591
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
1592
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
1593
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
1594
        if (prefer_rgbfloat32) {
×
1595
            input_pixels = data;
×
1596
            palette_pixelformat = pixelformat;
×
1597
            payload_length = (unsigned int)(total_pixels * 3U
×
1598
                                            * sizeof(float));
1599
            break;
×
1600
        }
1601
        /* fallthrough */
1602
    default:
1603
        /* normalize pixelformat */
1604
        normalized_pixels
1605
            = (unsigned char *)sixel_allocator_malloc(
×
1606
                dither->allocator, (size_t)(width * height * 3));
×
1607
        if (normalized_pixels == NULL) {
×
1608
            sixel_helper_set_additional_message(
×
1609
                "sixel_dither_initialize: sixel_allocator_malloc() failed.");
1610
            status = SIXEL_BAD_ALLOCATION;
×
1611
            goto end;
×
1612
        }
1613

1614
        status = sixel_helper_normalize_pixelformat(
×
1615
            normalized_pixels,
1616
            &pixelformat,
1617
            data,
1618
            pixelformat,
1619
            width,
1620
            height);
1621
        if (SIXEL_FAILED(status)) {
×
1622
            goto end;
×
1623
        }
1624
        input_pixels = normalized_pixels;
×
1625
        break;
×
1626
    }
1627

1628
    if (payload_length == 0U) {
214!
1629
        payload_length = (unsigned int)(total_pixels * 3U);
214✔
1630
    }
72✔
1631

1632
    if (prefer_rgbfloat32
214!
1633
        && !SIXEL_PIXELFORMAT_IS_FLOAT32(palette_pixelformat)
72!
1634
        && total_pixels > 0U) {
×
1635
        status = sixel_dither_promote_rgb888_to_float32(
×
1636
            &float_pixels,
1637
            input_pixels,
1638
            total_pixels,
1639
            dither->allocator);
1640
        if (SIXEL_SUCCEEDED(status) && float_pixels != NULL) {
×
1641
            payload_length
1642
                = (unsigned int)(total_pixels * 3U * sizeof(float));
×
1643
            palette_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
×
1644
            input_pixels = (unsigned char *)float_pixels;
×
1645
        } else {
1646
            prefer_rgbfloat32 = 0;
×
1647
            status = SIXEL_OK;
×
1648
        }
1649
    }
1650

1651
    dither->prefer_rgbfloat32 = prefer_rgbfloat32;
214✔
1652

1653
    sixel_dither_set_method_for_largest(dither, method_for_largest);
214✔
1654
    sixel_dither_set_method_for_rep(dither, method_for_rep);
214✔
1655
    sixel_dither_set_quality_mode(dither, quality_mode);
214✔
1656

1657
    status = sixel_palette_make_palette(&buf,
214✔
1658
                                        input_pixels,
72✔
1659
                                        payload_length,
72✔
1660
                                        palette_pixelformat,
72✔
1661
                                        (unsigned int)dither->reqcolors,
214✔
1662
                                        (unsigned int *)&dither->ncolors,
214✔
1663
                                        (unsigned int *)&dither->origcolors,
214✔
1664
                                        dither->method_for_largest,
72✔
1665
                                        dither->method_for_rep,
72✔
1666
                                        dither->quality_mode,
72✔
1667
                                        dither->force_palette,
72✔
1668
                                        dither->sixel_reversible,
72✔
1669
                                        dither->quantize_model,
72✔
1670
                                        dither->final_merge_mode,
72✔
1671
                                        dither->prefer_rgbfloat32,
72✔
1672
                                        dither->allocator);
72✔
1673
    if (SIXEL_FAILED(status)) {
214!
1674
        goto end;
×
1675
    }
1676
    status = sixel_palette_set_entries(dither->palette,
286✔
1677
                                       buf,
72✔
1678
                                       (unsigned int)dither->ncolors,
214✔
1679
                                       3,
1680
                                       dither->allocator);
72✔
1681
    if (SIXEL_FAILED(status)) {
214!
1682
        goto end;
×
1683
    }
1684
    dither->palette->entry_count = (unsigned int)dither->ncolors;
214✔
1685
    dither->palette->requested_colors = (unsigned int)dither->reqcolors;
214✔
1686
    dither->palette->original_colors = (unsigned int)dither->origcolors;
214✔
1687
    dither->palette->depth = 3;
214✔
1688

1689
    dither->optimized = 1;
214✔
1690
    if (dither->origcolors <= dither->ncolors) {
214✔
1691
        dither->method_for_diffuse = SIXEL_DIFFUSE_NONE;
145✔
1692
    }
49✔
1693

1694
    sixel_palette_free_palette(buf, dither->allocator);
214✔
1695
    status = SIXEL_OK;
214✔
1696

1697
end:
142✔
1698
    if (normalized_pixels != NULL) {
214!
1699
        sixel_allocator_free(dither->allocator, normalized_pixels);
×
1700
    }
1701
    if (float_pixels != NULL) {
214!
1702
        sixel_allocator_free(dither->allocator, float_pixels);
×
1703
    }
1704

1705
    /* decrement ref count */
1706
    sixel_dither_unref(dither);
214✔
1707

1708
    return status;
214✔
1709
}
1710

1711

1712
/* set lookup table policy */
1713
SIXELAPI void
1714
sixel_dither_set_lut_policy(
737✔
1715
    sixel_dither_t  /* in */ *dither,
1716
    int             /* in */ lut_policy)
1717
{
1718
    int normalized;
1719
    int previous_policy;
1720

1721
    if (dither == NULL) {
737!
1722
        return;
×
1723
    }
1724

1725
    normalized = SIXEL_LUT_POLICY_AUTO;
737✔
1726
    if (lut_policy == SIXEL_LUT_POLICY_5BIT
984!
1727
        || lut_policy == SIXEL_LUT_POLICY_6BIT
737!
1728
        || lut_policy == SIXEL_LUT_POLICY_CERTLUT
737!
1729
        || lut_policy == SIXEL_LUT_POLICY_NONE) {
737!
1730
        normalized = lut_policy;
×
1731
    }
1732
    previous_policy = dither->lut_policy;
737✔
1733
    if (previous_policy == normalized) {
737!
1734
        return;
737✔
1735
    }
1736

1737
    /*
1738
     * Policy transitions for the shared LUT mirror the previous cache flow:
1739
     *
1740
     *   [lut] --policy change--> (drop) --rebuild--> [lut]
1741
     */
1742
    dither->lut_policy = normalized;
×
1743
    if (dither->palette != NULL) {
×
1744
        dither->palette->lut_policy = normalized;
×
1745
    }
1746
    if (dither->palette != NULL && dither->palette->lut != NULL) {
×
1747
        sixel_lut_unref(dither->palette->lut);
×
1748
        dither->palette->lut = NULL;
×
1749
    }
1750
}
247✔
1751

1752

1753
/* get lookup table policy */
1754
SIXELAPI int
1755
sixel_dither_get_lut_policy(
×
1756
    sixel_dither_t  /* in */ *dither)
1757
{
1758
    int policy;
1759

1760
    policy = SIXEL_LUT_POLICY_AUTO;
×
1761
    if (dither != NULL) {
×
1762
        policy = dither->lut_policy;
×
1763
    }
1764

1765
    return policy;
×
1766
}
1767

1768

1769
/* set diffusion type, choose from enum methodForDiffuse */
1770
SIXELAPI void
1771
sixel_dither_set_diffusion_type(
520✔
1772
    sixel_dither_t  /* in */ *dither,
1773
    int             /* in */ method_for_diffuse)
1774
{
1775
    if (method_for_diffuse == SIXEL_DIFFUSE_AUTO) {
520✔
1776
        if (dither->ncolors > 16) {
237✔
1777
            method_for_diffuse = SIXEL_DIFFUSE_FS;
72✔
1778
        } else {
24✔
1779
            method_for_diffuse = SIXEL_DIFFUSE_ATKINSON;
165✔
1780
        }
1781
    }
79✔
1782
    dither->method_for_diffuse = method_for_diffuse;
520✔
1783
}
520✔
1784

1785

1786
/* set scan order for diffusion */
1787
SIXELAPI void
1788
sixel_dither_set_diffusion_scan(
520✔
1789
    sixel_dither_t  /* in */ *dither,
1790
    int             /* in */ method_for_scan)
1791
{
1792
    if (method_for_scan != SIXEL_SCAN_AUTO &&
521!
1793
            method_for_scan != SIXEL_SCAN_RASTER &&
3!
1794
            method_for_scan != SIXEL_SCAN_SERPENTINE) {
1✔
1795
        method_for_scan = SIXEL_SCAN_RASTER;
×
1796
    }
1797
    dither->method_for_scan = method_for_scan;
520✔
1798
}
520✔
1799

1800

1801
/* set carry buffer mode for diffusion */
1802
SIXELAPI void
1803
sixel_dither_set_diffusion_carry(
520✔
1804
    sixel_dither_t  /* in */ *dither,
1805
    int             /* in */ method_for_carry)
1806
{
1807
    if (method_for_carry != SIXEL_CARRY_AUTO &&
520!
1808
            method_for_carry != SIXEL_CARRY_DISABLE &&
×
1809
            method_for_carry != SIXEL_CARRY_ENABLE) {
1810
        method_for_carry = SIXEL_CARRY_AUTO;
×
1811
    }
1812
    dither->method_for_carry = method_for_carry;
520✔
1813
}
520✔
1814

1815

1816
/* get number of palette colors */
1817
SIXELAPI int
1818
sixel_dither_get_num_of_palette_colors(
×
1819
    sixel_dither_t  /* in */ *dither)
1820
{
1821
    return dither->ncolors;
×
1822
}
1823

1824

1825
/* get number of histogram colors */
1826
SIXELAPI int
1827
sixel_dither_get_num_of_histogram_colors(
196✔
1828
    sixel_dither_t /* in */ *dither)  /* dither context object */
1829
{
1830
    return dither->origcolors;
196✔
1831
}
1832

1833

1834
/* typoed: remained for keeping compatibility */
1835
SIXELAPI int
1836
sixel_dither_get_num_of_histgram_colors(
×
1837
    sixel_dither_t /* in */ *dither)  /* dither context object */
1838
{
1839
    return sixel_dither_get_num_of_histogram_colors(dither);
×
1840
}
1841

1842

1843
/* get palette */
1844
SIXELAPI unsigned char *
1845
sixel_dither_get_palette(
×
1846
    sixel_dither_t /* in */ *dither)  /* dither context object */
1847
{
1848
    if (dither == NULL || dither->palette == NULL) {
×
1849
        return NULL;
×
1850
    }
1851

1852
    return dither->palette->entries;
×
1853
}
1854

1855
SIXELAPI SIXELSTATUS
1856
sixel_dither_get_quantized_palette(sixel_dither_t *dither,
496✔
1857
                                   sixel_palette_t **pppalette)
1858
{
1859
    if (pppalette == NULL) {
496!
1860
        return SIXEL_BAD_ARGUMENT;
×
1861
    }
1862
    *pppalette = NULL;
496✔
1863

1864
    if (dither == NULL || dither->palette == NULL) {
496!
1865
        return SIXEL_RUNTIME_ERROR;
×
1866
    }
1867

1868
    sixel_palette_ref(dither->palette);
496✔
1869
    *pppalette = dither->palette;
496✔
1870

1871
    return SIXEL_OK;
496✔
1872
}
166✔
1873

1874

1875
/* set palette */
1876
SIXELAPI void
1877
sixel_dither_set_palette(
231✔
1878
    sixel_dither_t /* in */ *dither,   /* dither context object */
1879
    unsigned char  /* in */ *palette)
1880
{
1881
    if (dither == NULL || dither->palette == NULL) {
231!
1882
        return;
×
1883
    }
1884

1885
    (void)sixel_palette_set_entries(dither->palette,
308✔
1886
                                    palette,
77✔
1887
                                    (unsigned int)dither->ncolors,
231✔
1888
                                    3,
1889
                                    dither->allocator);
77✔
1890
}
77✔
1891

1892

1893
/* set the factor of complexion color correcting */
1894
SIXELAPI void
1895
sixel_dither_set_complexion_score(
6✔
1896
    sixel_dither_t /* in */ *dither,  /* dither context object */
1897
    int            /* in */ score)    /* complexion score (>= 1) */
1898
{
1899
    dither->complexion = score;
6✔
1900
    if (dither->palette != NULL) {
6!
1901
        dither->palette->complexion = score;
6✔
1902
    }
2✔
1903
}
6✔
1904

1905

1906
/* set whether omitting palette difinition */
1907
SIXELAPI void
1908
sixel_dither_set_body_only(
×
1909
    sixel_dither_t /* in */ *dither,     /* dither context object */
1910
    int            /* in */ bodyonly)    /* 0: output palette section
1911
                                            1: do not output palette section  */
1912
{
1913
    dither->bodyonly = bodyonly;
×
1914
}
×
1915

1916

1917
/* set whether optimize palette size */
1918
SIXELAPI void
1919
sixel_dither_set_optimize_palette(
340✔
1920
    sixel_dither_t /* in */ *dither,   /* dither context object */
1921
    int            /* in */ do_opt)    /* 0: optimize palette size
1922
                                          1: don't optimize palette size */
1923
{
1924
    dither->optimize_palette = do_opt;
340✔
1925
}
340✔
1926

1927

1928
/* set pixelformat */
1929
SIXELAPI void
1930
sixel_dither_set_pixelformat(
1,113✔
1931
    sixel_dither_t /* in */ *dither,     /* dither context object */
1932
    int            /* in */ pixelformat) /* one of enum pixelFormat */
1933
{
1934
    dither->pixelformat = pixelformat;
1,113✔
1935
}
1,113✔
1936

1937

1938
/* toggle SIXEL reversible palette mode */
1939
SIXELAPI void
1940
sixel_dither_set_sixel_reversible(
196✔
1941
    sixel_dither_t /* in */ *dither,
1942
    int            /* in */ enable)
1943
{
1944
    /*
1945
     * The diagram below shows how the flag routes palette generation:
1946
     *
1947
     *   pixels --> [histogram]
1948
     *                  |
1949
     *                  v
1950
     *           (optional reversible snap)
1951
     *                  |
1952
     *                  v
1953
     *               palette
1954
     */
1955
    if (dither == NULL) {
196!
1956
        return;
×
1957
    }
1958
    dither->sixel_reversible = enable ? 1 : 0;
196✔
1959
    if (dither->palette != NULL) {
196!
1960
        dither->palette->sixel_reversible = dither->sixel_reversible;
196✔
1961
    }
66✔
1962
}
66✔
1963

1964
/* select final merge policy */
1965
SIXELAPI void
1966
sixel_dither_set_final_merge(
196✔
1967
    sixel_dither_t /* in */ *dither,
1968
    int            /* in */ final_merge)
1969
{
1970
    int mode;
1971

1972
    if (dither == NULL) {
196!
1973
        return;
×
1974
    }
1975
    mode = SIXEL_FINAL_MERGE_AUTO;
196✔
1976
    if (final_merge == SIXEL_FINAL_MERGE_NONE
262!
1977
        || final_merge == SIXEL_FINAL_MERGE_WARD
196!
1978
        || final_merge == SIXEL_FINAL_MERGE_HKMEANS) {
196!
1979
        mode = final_merge;
×
1980
    } else if (final_merge == SIXEL_FINAL_MERGE_AUTO) {
196!
1981
        mode = SIXEL_FINAL_MERGE_AUTO;
196✔
1982
    }
66✔
1983
    dither->final_merge_mode = mode;
196✔
1984
    if (dither->palette != NULL) {
196!
1985
        dither->palette->final_merge = mode;
196✔
1986
    }
66✔
1987
}
66✔
1988

1989
/* set transparent */
1990
SIXELAPI void
1991
sixel_dither_set_transparent(
×
1992
    sixel_dither_t /* in */ *dither,      /* dither context object */
1993
    int            /* in */ transparent)  /* transparent color index */
1994
{
1995
    dither->keycolor = transparent;
×
1996
}
×
1997

1998

1999
/* set transparent */
2000
sixel_index_t *
2001
sixel_dither_apply_palette(
256✔
2002
    sixel_dither_t  /* in */ *dither,
2003
    unsigned char   /* in */ *pixels,
2004
    int             /* in */ width,
2005
    int             /* in */ height)
2006
{
2007
    SIXELSTATUS status = SIXEL_FALSE;
256✔
2008
    size_t bufsize;
2009
    size_t normalized_size;
2010
    size_t total_pixels;
2011
    sixel_index_t *dest = NULL;
256✔
2012
    int ncolors;
2013
    int method_for_scan;
2014
    int method_for_carry;
2015
    unsigned char *normalized_pixels = NULL;
256✔
2016
    unsigned char *input_pixels;
2017
    float *float_pipeline_pixels = NULL;
256✔
2018
    int owns_float_pipeline;
2019
    int pipeline_pixelformat;
2020
    int prefer_float_pipeline;
2021
    int palette_probe_active;
2022
    double palette_started_at;
2023
    double palette_finished_at;
2024
    double palette_duration;
2025
    sixel_palette_t *palette;
2026
    int dest_owned;
2027
    int parallel_active;
2028
    int parallel_band_height;
2029
    int parallel_overlap;
2030
    int parallel_threads;
2031
    sixel_parallel_logger_t *parallel_logger;
2032

2033
    /* ensure dither object is not null */
2034
    if (dither == NULL) {
256!
2035
        sixel_helper_set_additional_message(
×
2036
            "sixel_dither_apply_palette: dither is null.");
2037
        status = SIXEL_BAD_ARGUMENT;
×
2038
        goto end;
×
2039
    }
2040

2041
    sixel_dither_ref(dither);
256✔
2042

2043
    palette = dither->palette;
256✔
2044
    if (palette == NULL) {
256!
2045
        sixel_helper_set_additional_message(
×
2046
            "sixel_dither_apply_palette: palette is null.");
2047
        status = SIXEL_BAD_ARGUMENT;
×
2048
        goto end;
×
2049
    }
2050

2051
    parallel_active = dither->pipeline_parallel_active;
256✔
2052
    parallel_band_height = dither->pipeline_band_height;
256✔
2053
    parallel_overlap = dither->pipeline_band_overlap;
256✔
2054
    parallel_threads = dither->pipeline_dither_threads;
256✔
2055
    parallel_logger = dither->pipeline_logger;
256✔
2056

2057
    if (parallel_active && dither->optimize_palette != 0) {
256!
2058
        /*
2059
         * Palette minimization rewrites the palette entries in place.
2060
         * Parallel bands would race on the shared table, so fall back to
2061
         * the serial path when the feature is active.
2062
         */
NEW
2063
        parallel_active = 0;
×
2064
    }
2065

2066
    bufsize = (size_t)(width * height) * sizeof(sixel_index_t);
256✔
2067
    total_pixels = (size_t)width * (size_t)height;
256✔
2068
    owns_float_pipeline = 0;
256✔
2069
    pipeline_pixelformat = dither->pixelformat;
256✔
2070
    /*
2071
     * Reuse the externally allocated index buffer when the pipeline has
2072
     * already provisioned storage for the producer/worker hand-off.
2073
     */
2074
    if (dither->pipeline_index_buffer != NULL &&
256!
2075
            dither->pipeline_index_size >= bufsize) {
×
2076
        dest = dither->pipeline_index_buffer;
×
2077
        dest_owned = 0;
×
2078
    } else {
2079
        dest = (sixel_index_t *)sixel_allocator_malloc(dither->allocator,
342✔
2080
                                                       bufsize);
86✔
2081
        if (dest == NULL) {
256!
2082
            sixel_helper_set_additional_message(
×
2083
                "sixel_dither_new: sixel_allocator_malloc() failed.");
2084
            status = SIXEL_BAD_ALLOCATION;
×
2085
            goto end;
×
2086
        }
2087
        dest_owned = 1;
256✔
2088
    }
2089
    dither->pipeline_index_owned = dest_owned;
256✔
2090

2091
    /*
2092
     * Disable palette caching when the caller selected the NONE policy so
2093
     * every pixel lookup performs a direct palette scan.  Other quality
2094
     * modes continue to honor the requested LUT policy, including "full".
2095
     */
2096
    if (dither->lut_policy == SIXEL_LUT_POLICY_NONE) {
256!
2097
        dither->optimized = 0;
×
2098
    }
2099

2100
    if (dither->optimized) {
256✔
2101
        if (!sixel_palette_is_builtin_mono(palette)) {
253✔
2102
            int policy;
2103
            int wR;
2104
            int wG;
2105
            int wB;
2106

2107
            policy = dither->lut_policy;
238✔
2108
            if (policy != SIXEL_LUT_POLICY_CERTLUT
318!
2109
                && policy != SIXEL_LUT_POLICY_5BIT
238!
2110
                && policy != SIXEL_LUT_POLICY_6BIT) {
238!
2111
                policy = SIXEL_LUT_POLICY_6BIT;
238✔
2112
            }
80✔
2113
            if (palette->lut == NULL) {
238✔
2114
                status = sixel_lut_new(&palette->lut,
314✔
2115
                                       policy,
79✔
2116
                                       palette->allocator);
79✔
2117
                if (SIXEL_FAILED(status)) {
235!
2118
                    sixel_helper_set_additional_message(
×
2119
                        "sixel_dither_apply_palette: lut allocation failed.");
2120
                    goto end;
×
2121
                }
2122
            }
79✔
2123
            if (policy == SIXEL_LUT_POLICY_CERTLUT) {
238!
2124
                if (dither->method_for_largest == SIXEL_LARGE_LUM) {
×
2125
                    wR = dither->complexion * 299;
×
2126
                    wG = 587;
×
2127
                    wB = 114;
×
2128
                } else {
2129
                    wR = dither->complexion;
×
2130
                    wG = 1;
×
2131
                    wB = 1;
×
2132
                }
2133
            } else {
2134
                wR = dither->complexion;
238✔
2135
                wG = 1;
238✔
2136
                wB = 1;
238✔
2137
            }
2138
            status = sixel_lut_configure(palette->lut,
318✔
2139
                                         palette->entries,
238✔
2140
                                         palette->depth,
80✔
2141
                                         (int)palette->entry_count,
238✔
2142
                                         dither->complexion,
80✔
2143
                                         wR,
80✔
2144
                                         wG,
80✔
2145
                                         wB,
80✔
2146
                                         policy,
80✔
2147
                                         dither->pixelformat);
80✔
2148
            if (SIXEL_FAILED(status)) {
238!
2149
                sixel_helper_set_additional_message(
×
2150
                    "sixel_dither_apply_palette: lut configuration failed.");
2151
                goto end;
×
2152
            }
2153
        }
80✔
2154
    }
85✔
2155

2156
    owns_float_pipeline = 0;
256✔
2157
    pipeline_pixelformat = dither->pixelformat;
256✔
2158
    prefer_float_pipeline =
86✔
2159
        sixel_dither_method_supports_float_pipeline(dither);
256✔
2160
    if (pipeline_pixelformat == SIXEL_PIXELFORMAT_RGB888) {
256!
2161
        input_pixels = pixels;
256✔
2162
    } else if (SIXEL_PIXELFORMAT_IS_FLOAT32(pipeline_pixelformat)
86!
2163
               && prefer_float_pipeline) {
×
2164
        input_pixels = pixels;
×
2165
    } else {
2166
        normalized_size = (size_t)width * (size_t)height * 3U;
×
2167
        normalized_pixels
2168
            = (unsigned char *)sixel_allocator_malloc(dither->allocator,
×
2169
                                                      normalized_size);
2170
        if (normalized_pixels == NULL) {
×
2171
            sixel_helper_set_additional_message(
×
2172
                "sixel_dither_new: sixel_allocator_malloc() failed.");
2173
            status = SIXEL_BAD_ALLOCATION;
×
2174
            goto end;
×
2175
        }
2176
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
×
2177
                                                    &dither->pixelformat,
2178
                                                    pixels, dither->pixelformat,
2179
                                                    width, height);
2180
        if (SIXEL_FAILED(status)) {
×
2181
            goto end;
×
2182
        }
2183
        input_pixels = normalized_pixels;
×
2184
        pipeline_pixelformat = SIXEL_PIXELFORMAT_RGB888;
×
2185
    }
2186
    if (prefer_float_pipeline
256!
2187
        && pipeline_pixelformat == SIXEL_PIXELFORMAT_RGB888
86!
2188
        && total_pixels > 0U) {
×
2189
        status = sixel_dither_promote_rgb888_to_float32(
×
2190
            &float_pipeline_pixels,
2191
            input_pixels,
2192
            total_pixels,
2193
            dither->allocator);
2194
        if (SIXEL_SUCCEEDED(status) && float_pipeline_pixels != NULL) {
×
2195
            input_pixels = (unsigned char *)float_pipeline_pixels;
×
2196
            pipeline_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
×
2197
            owns_float_pipeline = 1;
×
2198
        } else {
2199
            prefer_float_pipeline = 0;
×
2200
            status = SIXEL_OK;
×
2201
        }
2202
    } else if (prefer_float_pipeline
256!
2203
               && !SIXEL_PIXELFORMAT_IS_FLOAT32(pipeline_pixelformat)) {
86!
2204
        prefer_float_pipeline = 0;
×
2205
    }
2206

2207
    method_for_scan = dither->method_for_scan;
256✔
2208
    if (method_for_scan == SIXEL_SCAN_AUTO) {
256✔
2209
        method_for_scan = SIXEL_SCAN_RASTER;
253✔
2210
    }
85✔
2211

2212
    method_for_carry = dither->method_for_carry;
256✔
2213
    if (method_for_carry == SIXEL_CARRY_AUTO) {
256!
2214
        method_for_carry = SIXEL_CARRY_DISABLE;
256✔
2215
    }
86✔
2216

2217
    palette_probe_active = sixel_assessment_palette_probe_enabled();
256✔
2218
    palette_started_at = 0.0;
256✔
2219
    palette_finished_at = 0.0;
256✔
2220
    palette_duration = 0.0;
256✔
2221
    if (palette_probe_active) {
256!
2222
        /*
2223
         * Palette spans execute inside the encode stage.  We sample the
2224
         * duration here so the assessment can reassign the work to the
2225
         * PaletteApply bucket.
2226
         */
2227
        palette_started_at = sixel_assessment_timer_now();
×
2228
    }
2229
    palette->lut_policy = dither->lut_policy;
256✔
2230
    palette->method_for_largest = dither->method_for_largest;
256✔
2231
#if SIXEL_ENABLE_THREADS
2232
    if (parallel_active && parallel_threads > 1
256!
NEW
2233
            && parallel_band_height > 0) {
×
2234
        sixel_parallel_dither_plan_t plan;
2235
        int adjusted_overlap;
2236
        int adjusted_height;
2237

NEW
2238
        adjusted_overlap = parallel_overlap;
×
NEW
2239
        if (adjusted_overlap < 0) {
×
NEW
2240
            adjusted_overlap = 0;
×
2241
        }
NEW
2242
        adjusted_height = parallel_band_height;
×
NEW
2243
        if (adjusted_height < 6) {
×
NEW
2244
            adjusted_height = 6;
×
2245
        }
NEW
2246
        if ((adjusted_height % 6) != 0) {
×
NEW
2247
            adjusted_height = ((adjusted_height + 5) / 6) * 6;
×
2248
        }
NEW
2249
        if (adjusted_overlap > adjusted_height / 2) {
×
NEW
2250
            adjusted_overlap = adjusted_height / 2;
×
2251
        }
2252

NEW
2253
        memset(&plan, 0, sizeof(plan));
×
NEW
2254
        plan.dest = dest;
×
NEW
2255
        plan.pixels = input_pixels;
×
NEW
2256
        plan.palette = palette;
×
NEW
2257
        plan.allocator = dither->allocator;
×
NEW
2258
        plan.dither = dither;
×
NEW
2259
        plan.width = width;
×
NEW
2260
        plan.height = height;
×
NEW
2261
        plan.band_height = adjusted_height;
×
NEW
2262
        plan.overlap = adjusted_overlap;
×
NEW
2263
        plan.method_for_diffuse = dither->method_for_diffuse;
×
NEW
2264
        plan.method_for_scan = method_for_scan;
×
NEW
2265
        plan.method_for_carry = method_for_carry;
×
NEW
2266
        plan.optimize_palette = dither->optimized;
×
NEW
2267
        plan.optimize_palette_entries = dither->optimize_palette;
×
NEW
2268
        plan.complexion = dither->complexion;
×
NEW
2269
        plan.lut_policy = dither->lut_policy;
×
NEW
2270
        plan.method_for_largest = dither->method_for_largest;
×
NEW
2271
        plan.reqcolor = dither->ncolors;
×
NEW
2272
        plan.pixelformat = pipeline_pixelformat;
×
NEW
2273
        plan.logger = parallel_logger;
×
2274

NEW
2275
        status = sixel_dither_apply_palette_parallel(&plan,
×
2276
                                                     parallel_threads);
NEW
2277
        ncolors = dither->ncolors;
×
2278
    } else
2279
#endif
2280
    {
2281
        status = sixel_dither_resolve_indexes(dest,
342✔
2282
                                              input_pixels,
86✔
2283
                                              width,
86✔
2284
                                              height,
86✔
2285
                                              3,
2286
                                              palette,
86✔
2287
                                              dither->ncolors,
86✔
2288
                                              dither->method_for_diffuse,
86✔
2289
                                              method_for_scan,
86✔
2290
                                              method_for_carry,
86✔
2291
                                              dither->optimized,
86✔
2292
                                              dither->optimize_palette,
86✔
2293
                                              dither->complexion,
86✔
2294
                                              dither->lut_policy,
86✔
2295
                                              dither->method_for_largest,
86✔
2296
                                              &ncolors,
2297
                                              dither->allocator,
86✔
2298
                                              dither,
86✔
2299
                                              pipeline_pixelformat);
86✔
2300
    }
2301
    if (palette_probe_active) {
256!
2302
        palette_finished_at = sixel_assessment_timer_now();
×
2303
        palette_duration = palette_finished_at - palette_started_at;
×
2304
        if (palette_duration < 0.0) {
×
2305
            palette_duration = 0.0;
×
2306
        }
2307
        sixel_assessment_record_palette_apply_span(palette_duration);
×
2308
    }
2309
    if (SIXEL_FAILED(status)) {
256!
2310
        if (dest != NULL && dest_owned) {
×
2311
            sixel_allocator_free(dither->allocator, dest);
×
2312
        }
2313
        dest = NULL;
×
2314
        goto end;
×
2315
    }
2316

2317
    dither->ncolors = ncolors;
256✔
2318
    palette->entry_count = (unsigned int)ncolors;
256✔
2319

2320
end:
170✔
2321
    if (normalized_pixels != NULL) {
256!
2322
        sixel_allocator_free(dither->allocator, normalized_pixels);
×
2323
    }
2324
    if (float_pipeline_pixels != NULL && owns_float_pipeline) {
256!
2325
        sixel_allocator_free(dither->allocator, float_pipeline_pixels);
×
2326
    }
2327
    sixel_dither_unref(dither);
256✔
2328
    dither->pipeline_index_buffer = NULL;
256✔
2329
    dither->pipeline_index_owned = 0;
256✔
2330
    dither->pipeline_index_size = 0;
256✔
2331
    dither->pipeline_parallel_active = 0;
256✔
2332
    dither->pipeline_band_height = 0;
256✔
2333
    dither->pipeline_band_overlap = 0;
256✔
2334
    dither->pipeline_dither_threads = 0;
256✔
2335
    dither->pipeline_image_height = 0;
256✔
2336
    dither->pipeline_logger = NULL;
256✔
2337
    return dest;
256✔
2338
}
2339

2340

2341
#if HAVE_TESTS
2342
static int
2343
test1(void)
×
2344
{
2345
    sixel_dither_t *dither = NULL;
×
2346
    int nret = EXIT_FAILURE;
×
2347
    SIXELSTATUS status;
2348

2349
    status = sixel_dither_new(&dither, 2, NULL);
×
2350
    if (dither == NULL || status != SIXEL_OK) {
×
2351
        goto error;
×
2352
    }
2353
    sixel_dither_ref(dither);
×
2354
    sixel_dither_unref(dither);
×
2355
    sixel_dither_unref(dither);
×
2356
    nret = EXIT_SUCCESS;
×
2357

2358
error:
2359
    sixel_dither_unref(dither);
×
2360
    return nret;
×
2361
}
2362

2363
static int
2364
test2(void)
×
2365
{
2366
    sixel_dither_t *dither = NULL;
×
2367
    int nret = EXIT_FAILURE;
×
2368
    SIXELSTATUS status;
2369

2370
    status = sixel_dither_new(&dither, INT_MAX, NULL);
×
2371
    if (status != SIXEL_BAD_INPUT || dither != NULL) {
×
2372
        goto error;
×
2373
    }
2374

2375
    nret = EXIT_SUCCESS;
×
2376

2377
error:
2378
    return nret;
×
2379
}
2380

2381
/* ensure the float32 flag honours the environment defaults */
2382
static int
2383
test_float_env_disabled(void)
×
2384
{
2385
    sixel_dither_t *dither = NULL;
×
2386
    int nret = EXIT_FAILURE;
×
2387
    SIXELSTATUS status;
2388

2389
    sixel_dither_tests_reset_float32_flag_cache();
×
2390
    sixel_palette_tests_reset_last_engine();
×
2391
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "0");
×
2392

2393
    status = sixel_dither_new(&dither, 4, NULL);
×
2394
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2395
        goto error;
×
2396
    }
2397
    if (dither->prefer_rgbfloat32 != 0) {
×
2398
        goto error;
×
2399
    }
2400

2401
    nret = EXIT_SUCCESS;
×
2402

2403
error:
2404
    sixel_dither_unref(dither);
×
2405
    return nret;
×
2406
}
2407

2408
/* ensure empty environment assignments keep the legacy pipeline */
2409
static int
2410
test_float_env_empty_string_disables(void)
×
2411
{
2412
    sixel_dither_t *dither;
2413
    SIXELSTATUS status;
2414
    int nret;
2415

2416
    dither = NULL;
×
2417
    status = SIXEL_FALSE;
×
2418
    nret = EXIT_FAILURE;
×
2419
    sixel_dither_tests_reset_float32_flag_cache();
×
2420
    sixel_palette_tests_reset_last_engine();
×
2421
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "");
×
2422

2423
    status = sixel_dither_new(&dither, 4, NULL);
×
2424
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2425
        goto error;
×
2426
    }
2427
    if (dither->prefer_rgbfloat32 != 0) {
×
2428
        goto error;
×
2429
    }
2430

2431
    nret = EXIT_SUCCESS;
×
2432

2433
error:
2434
    sixel_dither_unref(dither);
×
2435
    sixel_palette_tests_reset_last_engine();
×
2436
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "0");
×
2437
    sixel_dither_tests_reset_float32_flag_cache();
×
2438
    return nret;
×
2439
}
2440

2441
/* ensure the float32 route dispatches the RGBFLOAT32 quantizer */
2442
static int
2443
test_float_env_enables_quantizer(void)
×
2444
{
2445
    static unsigned char const pixels[] = {
2446
        0x00, 0x00, 0x00,
2447
        0xff, 0xff, 0xff,
2448
    };
2449
    sixel_dither_t *dither = NULL;
×
2450
    SIXELSTATUS status;
2451
    int nret;
2452

2453
    nret = EXIT_FAILURE;
×
2454
    sixel_dither_tests_reset_float32_flag_cache();
×
2455
    sixel_palette_tests_reset_last_engine();
×
2456
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "1");
×
2457

2458
    status = sixel_dither_new(&dither, 2, NULL);
×
2459
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2460
        goto error;
×
2461
    }
2462
    if (dither->prefer_rgbfloat32 == 0) {
×
2463
        goto error;
×
2464
    }
2465

2466
    status = sixel_dither_initialize(dither,
×
2467
                                     (unsigned char *)pixels,
2468
                                     2,
2469
                                     1,
2470
                                     SIXEL_PIXELFORMAT_RGB888,
2471
                                     SIXEL_LARGE_AUTO,
2472
                                     SIXEL_REP_AUTO,
2473
                                     SIXEL_QUALITY_AUTO);
2474
    if (SIXEL_FAILED(status)) {
×
2475
        goto error;
×
2476
    }
2477
    if (!sixel_palette_tests_last_engine_requires_float32()) {
×
2478
        goto error;
×
2479
    }
2480
    if (sixel_palette_tests_last_engine_model()
×
2481
            != SIXEL_QUANTIZE_MODEL_MEDIANCUT) {
2482
        goto error;
×
2483
    }
2484

2485
    nret = EXIT_SUCCESS;
×
2486

2487
error:
2488
    sixel_dither_unref(dither);
×
2489
    sixel_palette_tests_reset_last_engine();
×
2490
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "0");
×
2491
    sixel_dither_tests_reset_float32_flag_cache();
×
2492
    return nret;
×
2493
}
2494

2495
/* ensure explicit Heckbert requests also take the float32 route */
2496
static int
2497
test_float_env_explicit_mediancut_dispatch(void)
×
2498
{
2499
    static unsigned char const pixels[] = {
2500
        0x80, 0x20, 0x20,
2501
        0x20, 0x80, 0x20,
2502
    };
2503
    sixel_dither_t *dither = NULL;
×
2504
    SIXELSTATUS status;
2505
    int nret;
2506

2507
    nret = EXIT_FAILURE;
×
2508
    sixel_dither_tests_reset_float32_flag_cache();
×
2509
    sixel_palette_tests_reset_last_engine();
×
2510
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "1");
×
2511

2512
    status = sixel_dither_new(&dither, 2, NULL);
×
2513
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2514
        goto error;
×
2515
    }
2516
    if (dither->prefer_rgbfloat32 == 0) {
×
2517
        goto error;
×
2518
    }
2519
    dither->quantize_model = SIXEL_QUANTIZE_MODEL_MEDIANCUT;
×
2520

2521
    status = sixel_dither_initialize(dither,
×
2522
                                     (unsigned char *)pixels,
2523
                                     2,
2524
                                     1,
2525
                                     SIXEL_PIXELFORMAT_RGB888,
2526
                                     SIXEL_LARGE_AUTO,
2527
                                     SIXEL_REP_AUTO,
2528
                                     SIXEL_QUALITY_AUTO);
2529
    if (SIXEL_FAILED(status)) {
×
2530
        goto error;
×
2531
    }
2532
    if (!sixel_palette_tests_last_engine_requires_float32()) {
×
2533
        goto error;
×
2534
    }
2535
    if (sixel_palette_tests_last_engine_model()
×
2536
            != SIXEL_QUANTIZE_MODEL_MEDIANCUT) {
2537
        goto error;
×
2538
    }
2539

2540
    nret = EXIT_SUCCESS;
×
2541

2542
error:
2543
    sixel_dither_unref(dither);
×
2544
    sixel_palette_tests_reset_last_engine();
×
2545
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "0");
×
2546
    sixel_dither_tests_reset_float32_flag_cache();
×
2547
    return nret;
×
2548
}
2549

2550
/* ensure the float32 diffusion route is exercised for FS dithering */
2551
static int
2552
test_float_fs_diffusion_dispatch(void)
×
2553
{
2554
    static float pixels[] = {
2555
        0.10f, 0.20f, 0.30f,
2556
        0.85f, 0.60f, 0.40f,
2557
    };
2558
    sixel_dither_t *dither;
2559
    sixel_index_t *indexes;
2560
    SIXELSTATUS status;
2561
    int nret;
2562

2563
    dither = NULL;
×
2564
    indexes = NULL;
×
2565
    nret = EXIT_FAILURE;
×
2566
    sixel_dither_tests_reset_float32_flag_cache();
×
2567
    sixel_palette_tests_reset_last_engine();
×
2568
    sixel_dither_diffusion_tests_reset_float32_hits();
×
2569
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "1");
×
2570

2571
    status = sixel_dither_new(&dither, 2, NULL);
×
2572
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2573
        goto error;
×
2574
    }
2575
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGBFLOAT32);
×
2576
    sixel_dither_set_diffusion_type(dither, SIXEL_DIFFUSE_FS);
×
2577
    sixel_dither_set_diffusion_carry(dither, SIXEL_CARRY_DISABLE);
×
2578

2579
    status = sixel_dither_initialize(dither,
×
2580
                                     (unsigned char *)pixels,
2581
                                     2,
2582
                                     1,
2583
                                     SIXEL_PIXELFORMAT_RGBFLOAT32,
2584
                                     SIXEL_LARGE_AUTO,
2585
                                     SIXEL_REP_AUTO,
2586
                                     SIXEL_QUALITY_AUTO);
2587
    if (SIXEL_FAILED(status)) {
×
2588
        goto error;
×
2589
    }
2590

2591
    indexes = sixel_dither_apply_palette(dither,
×
2592
                                         (unsigned char *)pixels,
2593
                                         2,
2594
                                         1);
2595
    if (indexes == NULL) {
×
2596
        goto error;
×
2597
    }
2598
    if (sixel_dither_diffusion_tests_float32_hits() <= 0) {
×
2599
        goto error;
×
2600
    }
2601

2602
    nret = EXIT_SUCCESS;
×
2603

2604
error:
2605
    if (indexes != NULL && dither != NULL) {
×
2606
        sixel_allocator_free(dither->allocator, indexes);
×
2607
    }
2608
    sixel_dither_unref(dither);
×
2609
    sixel_palette_tests_reset_last_engine();
×
2610
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "0");
×
2611
    sixel_dither_tests_reset_float32_flag_cache();
×
2612
    sixel_dither_diffusion_tests_reset_float32_hits();
×
2613
    return nret;
×
2614
}
2615

2616
/* helpers shared by the float32 palette validation tests */
2617
static unsigned char
2618
test_float_component_to_u8(float value)
×
2619
{
2620
    double channel;
2621

2622
    channel = (double)value;
×
2623
    if (channel < 0.0) {
×
2624
        channel = 0.0;
×
2625
    }
2626
    if (channel > 1.0) {
×
2627
        channel = 1.0;
×
2628
    }
2629
    channel = channel * 255.0 + 0.5;
×
2630
    if (channel < 0.0) {
×
2631
        channel = 0.0;
×
2632
    }
2633
    if (channel > 255.0) {
×
2634
        channel = 255.0;
×
2635
    }
2636

2637
    return (unsigned char)channel;
×
2638
}
2639

2640
static int
2641
test_palette_float_matches(float const *float_entries,
×
2642
                           size_t entry_count,
2643
                           unsigned char const *u8_entries,
2644
                           size_t u8_count)
2645
{
2646
    size_t index;
2647
    size_t base;
2648
    unsigned char red;
2649
    unsigned char green;
2650
    unsigned char blue;
2651

2652
    if (float_entries == NULL || u8_entries == NULL) {
×
2653
        return 0;
×
2654
    }
2655
    if (entry_count != u8_count) {
×
2656
        return 0;
×
2657
    }
2658
    if (entry_count == 0U) {
×
2659
        return 1;
×
2660
    }
2661

2662
    for (index = 0U; index < entry_count; ++index) {
×
2663
        base = index * 3U;
×
2664
        red = test_float_component_to_u8(float_entries[base + 0U]);
×
2665
        green = test_float_component_to_u8(float_entries[base + 1U]);
×
2666
        blue = test_float_component_to_u8(float_entries[base + 2U]);
×
2667
        if (red != u8_entries[base + 0U]
×
2668
                || green != u8_entries[base + 1U]
×
2669
                || blue != u8_entries[base + 2U]) {
×
2670
            return 0;
×
2671
        }
2672
    }
2673

2674
    return 1;
×
2675
}
2676

2677
/* ensure palette optimization copies float buffers in lockstep */
2678
static int
2679
test_float_optimize_palette_sync(void)
×
2680
{
2681
    static float pixels[] = {
2682
        1.0f, 0.9f, 0.0f,
2683
        1.0f, 0.9f, 0.0f,
2684
        0.1f, 0.2f, 0.9f,
2685
        0.1f, 0.2f, 0.9f,
2686
    };
2687
    sixel_dither_t *dither;
2688
    sixel_palette_t *palette_obj;
2689
    SIXELSTATUS status;
2690
    sixel_index_t *indexes;
2691
    float *palette_float_copy;
2692
    unsigned char *palette_u8_copy;
2693
    size_t palette_float_count;
2694
    size_t palette_u8_count;
2695
    int nret;
2696

2697
    dither = NULL;
×
2698
    palette_obj = NULL;
×
2699
    status = SIXEL_FALSE;
×
2700
    indexes = NULL;
×
2701
    palette_float_copy = NULL;
×
2702
    palette_u8_copy = NULL;
×
2703
    palette_float_count = 0U;
×
2704
    palette_u8_count = 0U;
×
2705
    nret = EXIT_FAILURE;
×
2706

2707
    sixel_dither_tests_reset_float32_flag_cache();
×
2708
    sixel_palette_tests_reset_last_engine();
×
2709
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "1");
×
2710

2711
    status = sixel_dither_new(&dither, 4, NULL);
×
2712
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2713
        goto error;
×
2714
    }
2715
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGBFLOAT32);
×
2716
    sixel_dither_set_diffusion_type(dither, SIXEL_DIFFUSE_FS);
×
2717
    sixel_dither_set_diffusion_carry(dither, SIXEL_CARRY_DISABLE);
×
2718
    sixel_dither_set_optimize_palette(dither, 1);
×
2719

2720
    status = sixel_dither_initialize(dither,
×
2721
                                     (unsigned char *)pixels,
2722
                                     2,
2723
                                     2,
2724
                                     SIXEL_PIXELFORMAT_RGBFLOAT32,
2725
                                     SIXEL_LARGE_AUTO,
2726
                                     SIXEL_REP_AUTO,
2727
                                     SIXEL_QUALITY_AUTO);
2728
    if (SIXEL_FAILED(status)) {
×
2729
        goto error;
×
2730
    }
2731

2732
    indexes = sixel_dither_apply_palette(dither,
×
2733
                                         (unsigned char *)pixels,
2734
                                         2,
2735
                                         2);
2736
    if (indexes == NULL) {
×
2737
        goto error;
×
2738
    }
2739

2740
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
×
2741
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2742
        goto error;
×
2743
    }
2744

2745
    status = sixel_palette_copy_entries_float32(
×
2746
        palette_obj,
2747
        &palette_float_copy,
2748
        &palette_float_count,
2749
        SIXEL_PIXELFORMAT_RGBFLOAT32,
2750
        dither->allocator);
×
2751
    if (SIXEL_FAILED(status) || palette_float_copy == NULL) {
×
2752
        goto error;
×
2753
    }
2754

2755
    status = sixel_palette_copy_entries_8bit(
×
2756
        palette_obj,
2757
        &palette_u8_copy,
2758
        &palette_u8_count,
2759
        SIXEL_PIXELFORMAT_RGB888,
2760
        dither->allocator);
×
2761
    if (SIXEL_FAILED(status) || palette_u8_copy == NULL) {
×
2762
        goto error;
×
2763
    }
2764

2765
    if (palette_float_count == 0U || palette_u8_count == 0U) {
×
2766
        goto error;
×
2767
    }
2768
    if (!test_palette_float_matches(palette_float_copy,
×
2769
                                    palette_float_count,
2770
                                    palette_u8_copy,
2771
                                    palette_u8_count)) {
2772
        goto error;
×
2773
    }
2774

2775
    nret = EXIT_SUCCESS;
×
2776

2777
error:
2778
    if (indexes != NULL && dither != NULL) {
×
2779
        sixel_allocator_free(dither->allocator, indexes);
×
2780
    }
2781
    if (palette_obj != NULL) {
×
2782
        sixel_palette_unref(palette_obj);
×
2783
    }
2784
    if (palette_float_copy != NULL && dither != NULL) {
×
2785
        sixel_allocator_free(dither->allocator, palette_float_copy);
×
2786
    }
2787
    if (palette_u8_copy != NULL && dither != NULL) {
×
2788
        sixel_allocator_free(dither->allocator, palette_u8_copy);
×
2789
    }
2790
    sixel_dither_unref(dither);
×
2791
    sixel_palette_tests_reset_last_engine();
×
2792
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "0");
×
2793
    sixel_dither_tests_reset_float32_flag_cache();
×
2794
    return nret;
×
2795
}
2796

2797
/* ensure float32 palettes remain accessible alongside legacy buffers */
2798
static int
2799
test_float_palette_accessor_returns_data(void)
×
2800
{
2801
    static float pixels[] = {
2802
        1.0f, 0.0f, 0.0f,
2803
        0.0f, 1.0f, 0.0f,
2804
    };
2805
    sixel_dither_t *dither;
2806
    SIXELSTATUS status;
2807
    int nret;
2808
    int colors;
2809
    int red_float;
2810
    int green_float;
2811
    int red_u8;
2812
    int green_u8;
2813
    int index;
2814
    float *entry_float;
2815
    unsigned char *entry_u8;
2816
    sixel_palette_t *palette_obj;
2817
    float *palette_float_copy;
2818
    unsigned char *palette_u8_copy;
2819
    size_t palette_float_count;
2820
    size_t palette_u8_count;
2821

2822
    dither = NULL;
×
2823
    status = SIXEL_FALSE;
×
2824
    palette_float_copy = NULL;
×
2825
    palette_u8_copy = NULL;
×
2826
    palette_obj = NULL;
×
2827
    palette_float_count = 0U;
×
2828
    palette_u8_count = 0U;
×
2829
    nret = EXIT_FAILURE;
×
2830
    colors = 0;
×
2831
    red_float = 0;
×
2832
    green_float = 0;
×
2833
    red_u8 = 0;
×
2834
    green_u8 = 0;
×
2835
    index = 0;
×
2836
    entry_float = NULL;
×
2837
    entry_u8 = NULL;
×
2838

2839
    sixel_dither_tests_reset_float32_flag_cache();
×
2840
    sixel_palette_tests_reset_last_engine();
×
2841
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "1");
×
2842

2843
    status = sixel_dither_new(&dither, 2, NULL);
×
2844
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2845
        goto error;
×
2846
    }
2847
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGBFLOAT32);
×
2848
    status = sixel_dither_initialize(dither,
×
2849
                                     (unsigned char *)pixels,
2850
                                     2,
2851
                                     1,
2852
                                     SIXEL_PIXELFORMAT_RGBFLOAT32,
2853
                                     SIXEL_LARGE_AUTO,
2854
                                     SIXEL_REP_AUTO,
2855
                                     SIXEL_QUALITY_AUTO);
2856
    if (SIXEL_FAILED(status)) {
×
2857
        goto error;
×
2858
    }
2859

2860
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
×
2861
    if (SIXEL_FAILED(status)) {
×
2862
        goto error;
×
2863
    }
2864
    status = sixel_palette_copy_entries_float32(
×
2865
        palette_obj,
2866
        &palette_float_copy,
2867
        &palette_float_count,
2868
        SIXEL_PIXELFORMAT_RGBFLOAT32,
2869
        dither->allocator);
×
2870
    if (SIXEL_FAILED(status) || palette_float_copy == NULL) {
×
2871
        goto error;
×
2872
    }
2873
    status = sixel_palette_copy_entries_8bit(
×
2874
        palette_obj,
2875
        &palette_u8_copy,
2876
        &palette_u8_count,
2877
        SIXEL_PIXELFORMAT_RGB888,
2878
        dither->allocator);
×
2879
    if (SIXEL_FAILED(status) || palette_u8_copy == NULL) {
×
2880
        goto error;
×
2881
    }
2882
    sixel_palette_unref(palette_obj);
×
2883
    palette_obj = NULL;
×
2884
    colors = sixel_dither_get_num_of_palette_colors(dither);
×
2885
    if (colors != 2) {
×
2886
        goto error;
×
2887
    }
2888

2889
    for (index = 0; index < colors; ++index) {
×
2890
        entry_float = palette_float_copy + (size_t)index * 3U;
×
2891
        if (entry_float[0] > 0.9f && entry_float[1] < 0.1f
×
2892
                && entry_float[2] < 0.1f) {
×
2893
            red_float = 1;
×
2894
        }
2895
        if (entry_float[1] > 0.9f && entry_float[0] < 0.1f
×
2896
                && entry_float[2] < 0.1f) {
×
2897
            green_float = 1;
×
2898
        }
2899

2900
        entry_u8 = palette_u8_copy + (size_t)index * 3U;
×
2901
        if (entry_u8[0] > 200U && entry_u8[1] < 30U && entry_u8[2] < 30U) {
×
2902
            red_u8 = 1;
×
2903
        }
2904
        if (entry_u8[1] > 200U && entry_u8[0] < 30U && entry_u8[2] < 30U) {
×
2905
            green_u8 = 1;
×
2906
        }
2907
    }
2908
    if (!red_float || !green_float || !red_u8 || !green_u8) {
×
2909
        goto error;
×
2910
    }
2911

2912
    nret = EXIT_SUCCESS;
×
2913

2914
error:
2915
    sixel_dither_unref(dither);
×
2916
    sixel_palette_tests_reset_last_engine();
×
2917
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "0");
×
2918
    sixel_dither_tests_reset_float32_flag_cache();
×
2919
    return nret;
×
2920
}
2921

2922
/* ensure the float accessor reports NULL for legacy 8bit runs */
2923
static int
2924
test_float_palette_accessor_legacy_null(void)
×
2925
{
2926
    static unsigned char const pixels[] = {
2927
        0xff, 0x00, 0x00,
2928
        0x00, 0xff, 0x00,
2929
    };
2930
    sixel_dither_t *dither;
2931
    SIXELSTATUS status;
2932
    sixel_palette_t *palette_obj;
2933
    float *palette_float_copy;
2934
    unsigned char *palette_u8_copy;
2935
    size_t palette_float_count;
2936
    size_t palette_u8_count;
2937
    int nret;
2938

2939
    dither = NULL;
×
2940
    status = SIXEL_FALSE;
×
2941
    palette_obj = NULL;
×
2942
    palette_float_copy = NULL;
×
2943
    palette_u8_copy = NULL;
×
2944
    palette_float_count = 0U;
×
2945
    palette_u8_count = 0U;
×
2946
    nret = EXIT_FAILURE;
×
2947
    sixel_dither_tests_reset_float32_flag_cache();
×
2948
    sixel_palette_tests_reset_last_engine();
×
2949
    sixel_compat_setenv(SIXEL_FLOAT32_ENVVAR, "0");
×
2950

2951
    status = sixel_dither_new(&dither, 2, NULL);
×
2952
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2953
        goto error;
×
2954
    }
2955
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGB888);
×
2956
    status = sixel_dither_initialize(dither,
×
2957
                                     (unsigned char *)pixels,
2958
                                     2,
2959
                                     1,
2960
                                     SIXEL_PIXELFORMAT_RGB888,
2961
                                     SIXEL_LARGE_AUTO,
2962
                                     SIXEL_REP_AUTO,
2963
                                     SIXEL_QUALITY_AUTO);
2964
    if (SIXEL_FAILED(status)) {
×
2965
        goto error;
×
2966
    }
2967

2968
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
×
2969
    if (SIXEL_FAILED(status)) {
×
2970
        goto error;
×
2971
    }
2972
    status = sixel_palette_copy_entries_float32(
×
2973
        palette_obj,
2974
        &palette_float_copy,
2975
        &palette_float_count,
2976
        SIXEL_PIXELFORMAT_RGBFLOAT32,
2977
        dither->allocator);
×
2978
    if (SIXEL_FAILED(status)) {
×
2979
        goto error;
×
2980
    }
2981
    if (palette_float_copy != NULL) {
×
2982
        goto error;
×
2983
    }
2984
    status = sixel_palette_copy_entries_8bit(
×
2985
        palette_obj,
2986
        &palette_u8_copy,
2987
        &palette_u8_count,
2988
        SIXEL_PIXELFORMAT_RGB888,
2989
        dither->allocator);
×
2990
    if (SIXEL_FAILED(status) || palette_u8_copy == NULL) {
×
2991
        goto error;
×
2992
    }
2993
    sixel_palette_unref(palette_obj);
×
2994
    palette_obj = NULL;
×
2995

2996
    nret = EXIT_SUCCESS;
×
2997

2998
error:
2999
    if (palette_obj != NULL) {
×
3000
        sixel_palette_unref(palette_obj);
×
3001
    }
3002
    if (palette_float_copy != NULL && dither != NULL) {
×
3003
        sixel_allocator_free(dither->allocator, palette_float_copy);
×
3004
    }
3005
    if (palette_u8_copy != NULL && dither != NULL) {
×
3006
        sixel_allocator_free(dither->allocator, palette_u8_copy);
×
3007
    }
3008
    sixel_dither_unref(dither);
×
3009
    sixel_palette_tests_reset_last_engine();
×
3010
    sixel_dither_tests_reset_float32_flag_cache();
×
3011
    return nret;
×
3012
}
3013

3014

3015
SIXELAPI int
3016
sixel_dither_tests_main(void)
×
3017
{
3018
    int nret = EXIT_FAILURE;
×
3019
    size_t i;
3020
    typedef int (* testcase)(void);
3021

3022
    static testcase const testcases[] = {
3023
        test1,
3024
        test2,
3025
        test_float_env_disabled,
3026
        test_float_env_empty_string_disables,
3027
        test_float_env_enables_quantizer,
3028
        test_float_env_explicit_mediancut_dispatch,
3029
        test_float_fs_diffusion_dispatch,
3030
        test_float_optimize_palette_sync,
3031
        test_float_palette_accessor_returns_data,
3032
        test_float_palette_accessor_legacy_null,
3033
    };
3034

3035
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
3036
        nret = testcases[i]();
×
3037
        if (nret != EXIT_SUCCESS) {
×
3038
            goto error;
×
3039
        }
3040
    }
3041

3042
    nret = EXIT_SUCCESS;
×
3043

3044
error:
3045
    return nret;
×
3046
}
3047
#endif  /* HAVE_TESTS */
3048

3049
/* emacs Local Variables:      */
3050
/* emacs mode: c               */
3051
/* emacs tab-width: 4          */
3052
/* emacs indent-tabs-mode: nil */
3053
/* emacs c-basic-offset: 4     */
3054
/* emacs End:                  */
3055
/* vim: set expandtab ts=4 sts=4 sw=4 : */
3056
/* 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