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

saitoha / libsixel / 19918707358

04 Dec 2025 05:12AM UTC coverage: 38.402% (-4.0%) from 42.395%
19918707358

push

github

saitoha
tests: fix meson msys dll lookup

9738 of 38220 branches covered (25.48%)

12841 of 33438 relevant lines covered (38.4%)

782420.02 hits per line

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

46.32
/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 "lookup-common.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 "logger.h"
60
#include "pixelformat.h"
61
#if SIXEL_ENABLE_THREADS
62
# include "threadpool.h"
63
#endif
64
#include <sixel.h>
65

66

67
/*
68
 * Promote an RGB888 buffer to RGBFLOAT32 by normalising each channel to the
69
 * 0.0-1.0 range.  The helper is used when a float32 pipeline is requested via
70
 * the pixelformat but the incoming frame still relies on 8bit pixels.
71
 */
72
static SIXELSTATUS
73
sixel_dither_promote_rgb888_to_float32(float **out_pixels,
×
74
                                       unsigned char const *rgb888,
75
                                       size_t pixel_total,
76
                                       sixel_allocator_t *allocator)
77
{
78
    SIXELSTATUS status;
×
79
    float *buffer;
×
80
    size_t bytes;
×
81
    size_t index;
×
82
    size_t base;
×
83

84
    status = SIXEL_BAD_ARGUMENT;
×
85
    buffer = NULL;
×
86
    bytes = 0U;
×
87
    index = 0U;
×
88
    base = 0U;
×
89

90
    if (out_pixels == NULL || rgb888 == NULL || allocator == NULL) {
×
91
        return status;
92
    }
93

94
    *out_pixels = NULL;
×
95
    status = SIXEL_OK;
×
96
    if (pixel_total == 0U) {
×
97
        return status;
98
    }
99

100
    if (pixel_total > SIZE_MAX / (3U * sizeof(float))) {
×
101
        return SIXEL_BAD_INPUT;
102
    }
103
    bytes = pixel_total * 3U * sizeof(float);
×
104

105
    buffer = (float *)sixel_allocator_malloc(allocator, bytes);
×
106
    if (buffer == NULL) {
×
107
        return SIXEL_BAD_ALLOCATION;
108
    }
109

110
    for (index = 0U; index < pixel_total; ++index) {
×
111
        base = index * 3U;
×
112
        buffer[base + 0U] = (float)rgb888[base + 0U] / 255.0f;
×
113
        buffer[base + 1U] = (float)rgb888[base + 1U] / 255.0f;
×
114
        buffer[base + 2U] = (float)rgb888[base + 2U] / 255.0f;
×
115
    }
116

117
    *out_pixels = buffer;
×
118
    return status;
×
119
}
120

121
/*
122
 * Determine whether the selected diffusion method can reuse the float32
123
 * pipeline.  Positional and variable-coefficient dithers have dedicated
124
 * float backends, while Floyd-Steinberg must disable the fast path when the
125
 * user explicitly enables carry buffers.
126
 */
127
static int
128
sixel_dither_method_supports_float_pipeline(sixel_dither_t const *dither)
255✔
129
{
130
    int method;
255✔
131

132
    if (dither == NULL) {
255!
133
        return 0;
134
    }
135
    if (dither->prefer_float32 == 0) {
255!
136
        return 0;
137
    }
138
    if (dither->method_for_carry == SIXEL_CARRY_ENABLE) {
×
139
        return 0;
140
    }
141

142
    method = dither->method_for_diffuse;
×
143
    switch (method) {
×
144
    case SIXEL_DIFFUSE_NONE:
145
    case SIXEL_DIFFUSE_ATKINSON:
146
    case SIXEL_DIFFUSE_FS:
147
    case SIXEL_DIFFUSE_JAJUNI:
148
    case SIXEL_DIFFUSE_STUCKI:
149
    case SIXEL_DIFFUSE_BURKES:
150
    case SIXEL_DIFFUSE_SIERRA1:
151
    case SIXEL_DIFFUSE_SIERRA2:
152
    case SIXEL_DIFFUSE_SIERRA3:
153
        return 1;
154
    case SIXEL_DIFFUSE_A_DITHER:
155
    case SIXEL_DIFFUSE_X_DITHER:
156
    case SIXEL_DIFFUSE_LSO2:
157
        return 1;
158
    default:
159
        return 0;
160
    }
161
}
162

163

164
static const unsigned char pal_mono_dark[] = {
165
    0x00, 0x00, 0x00, 0xff, 0xff, 0xff
166
};
167

168

169
static const unsigned char pal_mono_light[] = {
170
    0xff, 0xff, 0xff, 0x00, 0x00, 0x00
171
};
172

173
static const unsigned char pal_gray_1bit[] = {
174
    0x00, 0x00, 0x00, 0xff, 0xff, 0xff
175
};
176

177

178
static const unsigned char pal_gray_2bit[] = {
179
    0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff
180
};
181

182

183
static const unsigned char pal_gray_4bit[] = {
184
    0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33,
185
    0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77,
186
    0x88, 0x88, 0x88, 0x99, 0x99, 0x99, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb,
187
    0xcc, 0xcc, 0xcc, 0xdd, 0xdd, 0xdd, 0xee, 0xee, 0xee, 0xff, 0xff, 0xff
188
};
189

190

191
static const unsigned char pal_gray_8bit[] = {
192
    0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
193
    0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07,
194
    0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b,
195
    0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f,
196
    0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13,
197
    0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17,
198
    0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b,
199
    0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f,
200
    0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23,
201
    0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27,
202
    0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b,
203
    0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f,
204
    0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33,
205
    0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, 0x37, 0x37, 0x37,
206
    0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b,
207
    0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f,
208
    0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43,
209
    0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x46, 0x46, 0x46, 0x47, 0x47, 0x47,
210
    0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b,
211
    0x4c, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, 0x4f,
212
    0x50, 0x50, 0x50, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53,
213
    0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57,
214
    0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b,
215
    0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f,
216
    0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x63, 0x63, 0x63,
217
    0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67,
218
    0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b,
219
    0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f,
220
    0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x72, 0x72, 0x72, 0x73, 0x73, 0x73,
221
    0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x76, 0x76, 0x76, 0x77, 0x77, 0x77,
222
    0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b,
223
    0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f,
224
    0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x83, 0x83, 0x83,
225
    0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x86, 0x86, 0x86, 0x87, 0x87, 0x87,
226
    0x88, 0x88, 0x88, 0x89, 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8b, 0x8b, 0x8b,
227
    0x8c, 0x8c, 0x8c, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f,
228
    0x90, 0x90, 0x90, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93,
229
    0x94, 0x94, 0x94, 0x95, 0x95, 0x95, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97,
230
    0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b,
231
    0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9f, 0x9f, 0x9f,
232
    0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3,
233
    0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa6, 0xa7, 0xa7, 0xa7,
234
    0xa8, 0xa8, 0xa8, 0xa9, 0xa9, 0xa9, 0xaa, 0xaa, 0xaa, 0xab, 0xab, 0xab,
235
    0xac, 0xac, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xaf, 0xaf, 0xaf,
236
    0xb0, 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3,
237
    0xb4, 0xb4, 0xb4, 0xb5, 0xb5, 0xb5, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7,
238
    0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xbb, 0xbb, 0xbb,
239
    0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf,
240
    0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3,
241
    0xc4, 0xc4, 0xc4, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7,
242
    0xc8, 0xc8, 0xc8, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb,
243
    0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf,
244
    0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3,
245
    0xd4, 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7,
246
    0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb,
247
    0xdc, 0xdc, 0xdc, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf,
248
    0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3,
249
    0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7,
250
    0xe8, 0xe8, 0xe8, 0xe9, 0xe9, 0xe9, 0xea, 0xea, 0xea, 0xeb, 0xeb, 0xeb,
251
    0xec, 0xec, 0xec, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef,
252
    0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf2, 0xf2, 0xf2, 0xf3, 0xf3, 0xf3,
253
    0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7,
254
    0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb,
255
    0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff
256
};
257

258

259
static const unsigned char pal_xterm256[] = {
260
    0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00,
261
    0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0xc0, 0xc0, 0xc0,
262
    0x80, 0x80, 0x80, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00,
263
    0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
264
    0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x87, 0x00, 0x00, 0xaf,
265
    0x00, 0x00, 0xd7, 0x00, 0x00, 0xff, 0x00, 0x5f, 0x00, 0x00, 0x5f, 0x5f,
266
    0x00, 0x5f, 0x87, 0x00, 0x5f, 0xaf, 0x00, 0x5f, 0xd7, 0x00, 0x5f, 0xff,
267
    0x00, 0x87, 0x00, 0x00, 0x87, 0x5f, 0x00, 0x87, 0x87, 0x00, 0x87, 0xaf,
268
    0x00, 0x87, 0xd7, 0x00, 0x87, 0xff, 0x00, 0xaf, 0x00, 0x00, 0xaf, 0x5f,
269
    0x00, 0xaf, 0x87, 0x00, 0xaf, 0xaf, 0x00, 0xaf, 0xd7, 0x00, 0xaf, 0xff,
270
    0x00, 0xd7, 0x00, 0x00, 0xd7, 0x5f, 0x00, 0xd7, 0x87, 0x00, 0xd7, 0xaf,
271
    0x00, 0xd7, 0xd7, 0x00, 0xd7, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0x5f,
272
    0x00, 0xff, 0x87, 0x00, 0xff, 0xaf, 0x00, 0xff, 0xd7, 0x00, 0xff, 0xff,
273
    0x5f, 0x00, 0x00, 0x5f, 0x00, 0x5f, 0x5f, 0x00, 0x87, 0x5f, 0x00, 0xaf,
274
    0x5f, 0x00, 0xd7, 0x5f, 0x00, 0xff, 0x5f, 0x5f, 0x00, 0x5f, 0x5f, 0x5f,
275
    0x5f, 0x5f, 0x87, 0x5f, 0x5f, 0xaf, 0x5f, 0x5f, 0xd7, 0x5f, 0x5f, 0xff,
276
    0x5f, 0x87, 0x00, 0x5f, 0x87, 0x5f, 0x5f, 0x87, 0x87, 0x5f, 0x87, 0xaf,
277
    0x5f, 0x87, 0xd7, 0x5f, 0x87, 0xff, 0x5f, 0xaf, 0x00, 0x5f, 0xaf, 0x5f,
278
    0x5f, 0xaf, 0x87, 0x5f, 0xaf, 0xaf, 0x5f, 0xaf, 0xd7, 0x5f, 0xaf, 0xff,
279
    0x5f, 0xd7, 0x00, 0x5f, 0xd7, 0x5f, 0x5f, 0xd7, 0x87, 0x5f, 0xd7, 0xaf,
280
    0x5f, 0xd7, 0xd7, 0x5f, 0xd7, 0xff, 0x5f, 0xff, 0x00, 0x5f, 0xff, 0x5f,
281
    0x5f, 0xff, 0x87, 0x5f, 0xff, 0xaf, 0x5f, 0xff, 0xd7, 0x5f, 0xff, 0xff,
282
    0x87, 0x00, 0x00, 0x87, 0x00, 0x5f, 0x87, 0x00, 0x87, 0x87, 0x00, 0xaf,
283
    0x87, 0x00, 0xd7, 0x87, 0x00, 0xff, 0x87, 0x5f, 0x00, 0x87, 0x5f, 0x5f,
284
    0x87, 0x5f, 0x87, 0x87, 0x5f, 0xaf, 0x87, 0x5f, 0xd7, 0x87, 0x5f, 0xff,
285
    0x87, 0x87, 0x00, 0x87, 0x87, 0x5f, 0x87, 0x87, 0x87, 0x87, 0x87, 0xaf,
286
    0x87, 0x87, 0xd7, 0x87, 0x87, 0xff, 0x87, 0xaf, 0x00, 0x87, 0xaf, 0x5f,
287
    0x87, 0xaf, 0x87, 0x87, 0xaf, 0xaf, 0x87, 0xaf, 0xd7, 0x87, 0xaf, 0xff,
288
    0x87, 0xd7, 0x00, 0x87, 0xd7, 0x5f, 0x87, 0xd7, 0x87, 0x87, 0xd7, 0xaf,
289
    0x87, 0xd7, 0xd7, 0x87, 0xd7, 0xff, 0x87, 0xff, 0x00, 0x87, 0xff, 0x5f,
290
    0x87, 0xff, 0x87, 0x87, 0xff, 0xaf, 0x87, 0xff, 0xd7, 0x87, 0xff, 0xff,
291
    0xaf, 0x00, 0x00, 0xaf, 0x00, 0x5f, 0xaf, 0x00, 0x87, 0xaf, 0x00, 0xaf,
292
    0xaf, 0x00, 0xd7, 0xaf, 0x00, 0xff, 0xaf, 0x5f, 0x00, 0xaf, 0x5f, 0x5f,
293
    0xaf, 0x5f, 0x87, 0xaf, 0x5f, 0xaf, 0xaf, 0x5f, 0xd7, 0xaf, 0x5f, 0xff,
294
    0xaf, 0x87, 0x00, 0xaf, 0x87, 0x5f, 0xaf, 0x87, 0x87, 0xaf, 0x87, 0xaf,
295
    0xaf, 0x87, 0xd7, 0xaf, 0x87, 0xff, 0xaf, 0xaf, 0x00, 0xaf, 0xaf, 0x5f,
296
    0xaf, 0xaf, 0x87, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xd7, 0xaf, 0xaf, 0xff,
297
    0xaf, 0xd7, 0x00, 0xaf, 0xd7, 0x5f, 0xaf, 0xd7, 0x87, 0xaf, 0xd7, 0xaf,
298
    0xaf, 0xd7, 0xd7, 0xaf, 0xd7, 0xff, 0xaf, 0xff, 0x00, 0xaf, 0xff, 0x5f,
299
    0xaf, 0xff, 0x87, 0xaf, 0xff, 0xaf, 0xaf, 0xff, 0xd7, 0xaf, 0xff, 0xff,
300
    0xd7, 0x00, 0x00, 0xd7, 0x00, 0x5f, 0xd7, 0x00, 0x87, 0xd7, 0x00, 0xaf,
301
    0xd7, 0x00, 0xd7, 0xd7, 0x00, 0xff, 0xd7, 0x5f, 0x00, 0xd7, 0x5f, 0x5f,
302
    0xd7, 0x5f, 0x87, 0xd7, 0x5f, 0xaf, 0xd7, 0x5f, 0xd7, 0xd7, 0x5f, 0xff,
303
    0xd7, 0x87, 0x00, 0xd7, 0x87, 0x5f, 0xd7, 0x87, 0x87, 0xd7, 0x87, 0xaf,
304
    0xd7, 0x87, 0xd7, 0xd7, 0x87, 0xff, 0xd7, 0xaf, 0x00, 0xd7, 0xaf, 0x5f,
305
    0xd7, 0xaf, 0x87, 0xd7, 0xaf, 0xaf, 0xd7, 0xaf, 0xd7, 0xd7, 0xaf, 0xff,
306
    0xd7, 0xd7, 0x00, 0xd7, 0xd7, 0x5f, 0xd7, 0xd7, 0x87, 0xd7, 0xd7, 0xaf,
307
    0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xff, 0xd7, 0xff, 0x00, 0xd7, 0xff, 0x5f,
308
    0xd7, 0xff, 0x87, 0xd7, 0xff, 0xaf, 0xd7, 0xff, 0xd7, 0xd7, 0xff, 0xff,
309
    0xff, 0x00, 0x00, 0xff, 0x00, 0x5f, 0xff, 0x00, 0x87, 0xff, 0x00, 0xaf,
310
    0xff, 0x00, 0xd7, 0xff, 0x00, 0xff, 0xff, 0x5f, 0x00, 0xff, 0x5f, 0x5f,
311
    0xff, 0x5f, 0x87, 0xff, 0x5f, 0xaf, 0xff, 0x5f, 0xd7, 0xff, 0x5f, 0xff,
312
    0xff, 0x87, 0x00, 0xff, 0x87, 0x5f, 0xff, 0x87, 0x87, 0xff, 0x87, 0xaf,
313
    0xff, 0x87, 0xd7, 0xff, 0x87, 0xff, 0xff, 0xaf, 0x00, 0xff, 0xaf, 0x5f,
314
    0xff, 0xaf, 0x87, 0xff, 0xaf, 0xaf, 0xff, 0xaf, 0xd7, 0xff, 0xaf, 0xff,
315
    0xff, 0xd7, 0x00, 0xff, 0xd7, 0x5f, 0xff, 0xd7, 0x87, 0xff, 0xd7, 0xaf,
316
    0xff, 0xd7, 0xd7, 0xff, 0xd7, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0x5f,
317
    0xff, 0xff, 0x87, 0xff, 0xff, 0xaf, 0xff, 0xff, 0xd7, 0xff, 0xff, 0xff,
318
    0x08, 0x08, 0x08, 0x12, 0x12, 0x12, 0x1c, 0x1c, 0x1c, 0x26, 0x26, 0x26,
319
    0x30, 0x30, 0x30, 0x3a, 0x3a, 0x3a, 0x44, 0x44, 0x44, 0x4e, 0x4e, 0x4e,
320
    0x58, 0x58, 0x58, 0x62, 0x62, 0x62, 0x6c, 0x6c, 0x6c, 0x76, 0x76, 0x76,
321
    0x80, 0x80, 0x80, 0x8a, 0x8a, 0x8a, 0x94, 0x94, 0x94, 0x9e, 0x9e, 0x9e,
322
    0xa8, 0xa8, 0xa8, 0xb2, 0xb2, 0xb2, 0xbc, 0xbc, 0xbc, 0xc6, 0xc6, 0xc6,
323
    0xd0, 0xd0, 0xd0, 0xda, 0xda, 0xda, 0xe4, 0xe4, 0xe4, 0xee, 0xee, 0xee,
324
};
325

326

327
#if defined(_MSC_VER)
328
# define SIXEL_TLS __declspec(thread)
329
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L \
330
    && !defined(__STDC_NO_THREADS__)
331
# define SIXEL_TLS _Thread_local
332
#elif defined(__GNUC__)
333
# define SIXEL_TLS __thread
334
#else
335
# define SIXEL_TLS
336
#endif
337

338
/*
339
 * Fast LUT lookups rely on per-thread scratch state.  A TLS indirection keeps
340
 * parallel dithering workers from stomping on each other's lookup context when
341
 * several bands run concurrently.
342
 */
343
static SIXEL_TLS sixel_lut_t *dither_lut_context = NULL;
344

345
#undef SIXEL_TLS
346

347
/* lookup closest color from palette with "normal" strategy */
348
static int
349
lookup_normal(unsigned char const * const pixel,
×
350
              int const depth,
351
              unsigned char const * const palette,
352
              int const reqcolor,
353
              unsigned short * const cachetable,
354
              int const complexion)
355
{
356
    int result;
×
357
    int diff;
×
358
    int r;
×
359
    int i;
×
360
    int n;
×
361
    int distant;
×
362

363
    result = (-1);
×
364
    diff = INT_MAX;
×
365

366
    /* don't use cachetable in 'normal' strategy */
367
    (void) cachetable;
×
368

369
    for (i = 0; i < reqcolor; i++) {
×
370
        distant = 0;
×
371
        r = pixel[0] - palette[i * depth + 0];
×
372
        distant += r * r * complexion;
×
373
        for (n = 1; n < depth; ++n) {
×
374
            r = pixel[n] - palette[i * depth + n];
×
375
            distant += r * r;
×
376
        }
377
        if (distant < diff) {
×
378
            diff = distant;
×
379
            result = i;
×
380
        }
381
    }
382

383
    return result;
×
384
}
385

386

387
/*
388
 * Shared fast lookup flow handled by the lut module.  The palette lookup now
389
 * delegates to sixel_lut_map_pixel() so policy-specific caches and the
390
 * certification tree stay encapsulated inside src/lookup-common.c.
391
 */
392

393
static int
394
lookup_fast_lut(unsigned char const * const pixel,
×
395
                int const depth,
396
                unsigned char const * const palette,
397
                int const reqcolor,
398
                unsigned short * const cachetable,
399
                int const complexion)
400
{
401
    (void)depth;
×
402
    (void)palette;
×
403
    (void)reqcolor;
×
404
    (void)cachetable;
×
405
    (void)complexion;
×
406

407
    if (dither_lut_context == NULL) {
×
408
        return 0;
409
    }
410

411
    return sixel_lut_map_pixel(dither_lut_context, pixel);
×
412
}
413

414

415
static int
416
lookup_mono_darkbg(unsigned char const * const pixel,
1,730,520✔
417
                   int const depth,
418
                   unsigned char const * const palette,
419
                   int const reqcolor,
420
                   unsigned short * const cachetable,
421
                   int const complexion)
422
{
423
    int n;
1,730,520✔
424
    int distant;
1,730,520✔
425

426
    /* unused */ (void) palette;
1,730,520✔
427
    /* unused */ (void) cachetable;
1,730,520✔
428
    /* unused */ (void) complexion;
1,730,520✔
429

430
    distant = 0;
1,730,520✔
431
    for (n = 0; n < depth; ++n) {
6,922,080✔
432
        distant += pixel[n];
5,191,560✔
433
    }
434
    return distant >= 128 * reqcolor ? 1: 0;
1,730,520✔
435
}
436

437

438
static int
439
lookup_mono_lightbg(unsigned char const * const pixel,
810,000✔
440
                    int const depth,
441
                    unsigned char const * const palette,
442
                    int const reqcolor,
443
                    unsigned short * const cachetable,
444
                    int const complexion)
445
{
446
    int n;
810,000✔
447
    int distant;
810,000✔
448

449
    /* unused */ (void) palette;
810,000✔
450
    /* unused */ (void) cachetable;
810,000✔
451
    /* unused */ (void) complexion;
810,000✔
452

453
    distant = 0;
810,000✔
454
    for (n = 0; n < depth; ++n) {
3,240,000✔
455
        distant += pixel[n];
2,430,000✔
456
    }
457
    return distant < 128 * reqcolor ? 1: 0;
810,000✔
458
}
459

460

461

462
/*
463
 * Apply the palette into the supplied pixel buffer while coordinating the
464
 * dithering strategy.  The routine performs the following steps:
465
 *   - Select an index lookup helper, enabling the fast LUT path when the
466
 *     caller requested palette optimization and the input pixels are RGB.
467
 *   - Ensure the LUT object is prepared for the active policy, including
468
 *     weight selection for CERTLUT so complexion aware lookups remain
469
 *     accurate.
470
 *   - Dispatch to the positional, variable, or fixed error diffusion
471
 *     routines, each of which expects the LUT context to be initialized
472
 *     beforehand.
473
 *   - Release any temporary LUT references that were acquired for the fast
474
 *     lookup path.
475
 */
476
static SIXELSTATUS
477
sixel_dither_map_pixels(
255✔
478
    sixel_index_t     /* out */ *result,
479
    unsigned char     /* in */  *data,
480
    int               /* in */  width,
481
    int               /* in */  height,
482
    int               /* in */  band_origin,
483
    int               /* in */  output_start,
484
    int               /* in */  depth,
485
    unsigned char     /* in */  *palette,
486
    int               /* in */  reqcolor,
487
    int               /* in */  methodForDiffuse,
488
    int               /* in */  methodForScan,
489
    int               /* in */  methodForCarry,
490
    int               /* in */  foptimize,
491
    int               /* in */  foptimize_palette,
492
    int               /* in */  complexion,
493
    int               /* in */  lut_policy,
494
    int               /* in */  method_for_largest,
495
    sixel_lut_t       /* in */  *lut,
496
    int               /* in */  *ncolors,
497
    sixel_allocator_t /* in */  *allocator,
498
    sixel_dither_t    /* in */  *dither,
499
    int               /* in */  pixelformat)
500
{
255✔
501
#if _MSC_VER
502
    enum { max_depth = 4 };
503
#else
504
    const size_t max_depth = 4;
255✔
505
#endif
506
    unsigned char copy[max_depth];
255✔
507
    float new_palette_float[SIXEL_PALETTE_MAX * max_depth];
255✔
508
    SIXELSTATUS status = SIXEL_FALSE;
255✔
509
    int sum1;
255✔
510
    int sum2;
255✔
511
    int n;
255✔
512
    unsigned char new_palette[SIXEL_PALETTE_MAX * 4];
255✔
513
    unsigned short migration_map[SIXEL_PALETTE_MAX];
255✔
514
    sixel_dither_context_t context;
255✔
515
    int (*f_lookup)(unsigned char const * const pixel,
255✔
516
                    int const depth,
517
                    unsigned char const * const palette,
518
                    int const reqcolor,
519
                    unsigned short * const cachetable,
520
                    int const complexion) = lookup_normal;
521
    int use_varerr;
255✔
522
    int use_positional;
255✔
523
    int carry_mode;
255✔
524
    sixel_lut_t *active_lut;
255✔
525
    int manage_lut;
255✔
526
    int policy;
255✔
527
    int wcomp1;
255✔
528
    int wcomp2;
255✔
529
    int wcomp3;
255✔
530

531
    /*
532
     * Per-component weights used by the lookup backends.  These remain generic
533
     * to support RGB as well as alternate color spaces when evaluating palette
534
     * distance.
535
     */
536

537
    active_lut = NULL;
255✔
538
    manage_lut = 0;
255✔
539

540
    memset(&context, 0, sizeof(context));
255!
541
    context.result = result;
255✔
542
    context.width = width;
255✔
543
    context.height = height;
255✔
544
    context.band_origin = band_origin;
255✔
545
    context.output_start = output_start;
255✔
546
    context.depth = depth;
255✔
547
    context.palette = palette;
255✔
548
    context.reqcolor = reqcolor;
255✔
549
    context.new_palette = new_palette;
255✔
550
    context.migration_map = migration_map;
255✔
551
    context.ncolors = ncolors;
255✔
552
    context.scratch = copy;
255✔
553
    context.indextable = NULL;
255✔
554
    context.pixels = data;
255✔
555
    context.pixels_float = NULL;
255✔
556
    context.pixelformat = pixelformat;
255✔
557
    context.palette_float = NULL;
255✔
558
    context.new_palette_float = NULL;
255✔
559
    context.float_depth = 0;
255✔
560
    context.lookup_source_is_float = 0;
255✔
561
    context.prefer_palette_float_lookup = 0;
255✔
562
    context.lut = NULL;
255✔
563
    if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
255!
564
        context.pixels_float = (float *)(void *)data;
×
565
    }
566

567
    if (dither != NULL && dither->palette != NULL) {
255!
568
        sixel_palette_t *palette_object;
255✔
569
        int float_components;
255✔
570

571
        palette_object = dither->palette;
255✔
572
        if (palette_object->entries_float32 != NULL
255!
573
                && palette_object->float_depth > 0) {
×
574
            float_components = palette_object->float_depth
×
575
                / (int)sizeof(float);
576
            if (float_components > 0
×
577
                    && (size_t)float_components <= max_depth) {
×
578
                context.palette_float = palette_object->entries_float32;
×
579
                context.float_depth = float_components;
×
580
                context.new_palette_float = new_palette_float;
×
581
            }
582
        }
583
    }
584

585
    if (reqcolor < 1) {
255!
586
        status = SIXEL_BAD_ARGUMENT;
×
587
        sixel_helper_set_additional_message(
×
588
            "sixel_dither_map_pixels: "
589
            "a bad argument is detected, reqcolor < 0.");
590
        goto end;
×
591
    }
592

593
    use_varerr = (depth == 3
255✔
594
                  && methodForDiffuse == SIXEL_DIFFUSE_LSO2);
255!
595
    use_positional = (methodForDiffuse == SIXEL_DIFFUSE_A_DITHER
255✔
596
                      || methodForDiffuse == SIXEL_DIFFUSE_X_DITHER);
255!
597
    carry_mode = (methodForCarry == SIXEL_CARRY_ENABLE)
510✔
598
               ? SIXEL_CARRY_ENABLE
599
               : SIXEL_CARRY_DISABLE;
255!
600
    context.method_for_diffuse = methodForDiffuse;
255✔
601
    context.method_for_scan = methodForScan;
255✔
602
    context.method_for_carry = carry_mode;
255✔
603

604
    if (reqcolor == 2) {
255✔
605
        sum1 = 0;
606
        sum2 = 0;
156✔
607
        for (n = 0; n < depth; ++n) {
156✔
608
            sum1 += palette[n];
117✔
609
        }
610
        for (n = depth; n < depth + depth; ++n) {
156✔
611
            sum2 += palette[n];
117✔
612
        }
613
        if (sum1 == 0 && sum2 == 255 * 3) {
39!
614
            f_lookup = lookup_mono_darkbg;
615
        } else if (sum1 == 255 * 3 && sum2 == 0) {
27✔
616
            f_lookup = lookup_mono_lightbg;
255✔
617
        }
618
    }
619
    if (foptimize && depth == 3 && f_lookup == lookup_normal) {
255!
620
        f_lookup = lookup_fast_lut;
255✔
621
    }
622
    if (lut_policy == SIXEL_LUT_POLICY_NONE) {
255!
623
        f_lookup = lookup_normal;
×
624
    }
625

626
    if (f_lookup == lookup_fast_lut) {
255✔
627
        if (depth != 3) {
240!
628
            status = SIXEL_BAD_ARGUMENT;
×
629
            sixel_helper_set_additional_message(
×
630
                "sixel_dither_map_pixels: fast lookup requires RGB pixels.");
631
            goto end;
×
632
        }
633
        policy = lut_policy;
240✔
634
        if (policy != SIXEL_LUT_POLICY_CERTLUT
240!
635
            && policy != SIXEL_LUT_POLICY_5BIT
240!
636
            && policy != SIXEL_LUT_POLICY_6BIT) {
1!
637
            policy = SIXEL_LUT_POLICY_6BIT;
240✔
638
        }
639
        if (lut == NULL) {
240!
640
            status = sixel_lut_new(&active_lut, policy, allocator);
×
641
            if (SIXEL_FAILED(status)) {
×
642
                goto end;
×
643
            }
644
            manage_lut = 1;
645
        } else {
646
            active_lut = lut;
240✔
647
            manage_lut = 0;
240✔
648
        }
649
        if (policy == SIXEL_LUT_POLICY_CERTLUT) {
240!
650
            if (method_for_largest == SIXEL_LARGE_LUM) {
×
651
                wcomp1 = complexion * 299;
×
652
                wcomp2 = 587;
×
653
                wcomp3 = 114;
×
654
            } else {
655
                wcomp1 = complexion;
656
                wcomp2 = 1;
657
                wcomp3 = 1;
658
            }
659
        } else {
660
            wcomp1 = complexion;
661
            wcomp2 = 1;
662
            wcomp3 = 1;
663
        }
664
        status = sixel_lut_configure(active_lut,
240✔
665
                                     palette,
666
                                     depth,
667
                                     reqcolor,
668
                                     complexion,
669
                                     wcomp1,
670
                                     wcomp2,
671
                                     wcomp3,
672
                                     policy,
673
                                     pixelformat);
674
        if (SIXEL_FAILED(status)) {
240!
675
            goto end;
×
676
        }
677
        context.lut = active_lut;
240✔
678
        dither_lut_context = active_lut;
240✔
679
    }
680

681
    context.lookup = f_lookup;
255✔
682
    if (f_lookup == lookup_fast_lut
255!
683
        && SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
240!
684
        context.lookup_source_is_float = 1;
×
685
    }
686
    if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)
15!
687
        && context.palette_float != NULL
×
688
        && context.float_depth >= context.depth
×
689
        && f_lookup == lookup_normal) {
×
690
        context.prefer_palette_float_lookup = 1;
×
691
    }
692
    context.optimize_palette = foptimize_palette;
255✔
693
    context.complexion = complexion;
255✔
694

695
    if (use_positional) {
255!
696
        if (context.pixels_float != NULL
×
697
            && dither != NULL
×
698
            && dither->prefer_float32 != 0) {
×
699
            status = sixel_dither_apply_positional_float32(dither, &context);
×
700
            if (status == SIXEL_BAD_ARGUMENT) {
×
701
                status = sixel_dither_apply_positional_8bit(dither, &context);
×
702
            }
703
        } else {
704
            status = sixel_dither_apply_positional_8bit(dither, &context);
×
705
        }
706
    } else if (use_varerr) {
255!
707
        if (context.pixels_float != NULL
×
708
            && dither != NULL
×
709
            && dither->prefer_float32 != 0) {
×
710
            status = sixel_dither_apply_varcoeff_float32(dither, &context);
×
711
            if (status == SIXEL_BAD_ARGUMENT) {
×
712
                status = sixel_dither_apply_varcoeff_8bit(dither, &context);
×
713
            }
714
        } else {
715
            status = sixel_dither_apply_varcoeff_8bit(dither, &context);
×
716
        }
717
    } else {
718
        if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)
255!
719
            && context.pixels_float != NULL
×
720
            && methodForCarry != SIXEL_CARRY_ENABLE
×
721
            && depth == 3
×
722
            && dither != NULL
×
723
            && dither->prefer_float32 != 0) {
×
724
            /*
725
             * Float inputs can reuse the float32 renderer for every
726
             * fixed-weight kernel (FS, Sierra, Stucki, etc.) as long as
727
             * carry buffers are disabled.  The legacy 8bit path remains as
728
             * a fallback for unsupported argument combinations.
729
             */
730
            status = sixel_dither_apply_fixed_float32(dither, &context);
×
731
            if (status == SIXEL_BAD_ARGUMENT) {
×
732
                status = sixel_dither_apply_fixed_8bit(dither, &context);
×
733
            }
734
        } else {
735
            status = sixel_dither_apply_fixed_8bit(dither, &context);
255✔
736
        }
737
    }
738
    if (SIXEL_FAILED(status)) {
255!
739
        goto end;
×
740
    }
741

742
    status = SIXEL_OK;
743

744
end:
255✔
745
    if (dither_lut_context != NULL && f_lookup == lookup_fast_lut) {
255!
746
        dither_lut_context = NULL;
240✔
747
    }
748
    if (manage_lut && active_lut != NULL) {
255!
749
        sixel_lut_unref(active_lut);
×
750
    }
751
    return status;
255✔
752
}
753

754
#if SIXEL_ENABLE_THREADS
755
typedef struct sixel_parallel_dither_plan {
756
    sixel_index_t *dest;
757
    unsigned char *pixels;
758
    sixel_palette_t *palette;
759
    sixel_allocator_t *allocator;
760
    sixel_dither_t *dither;
761
    size_t row_bytes;
762
    int width;
763
    int height;
764
    int band_height;
765
    int overlap;
766
    int method_for_diffuse;
767
    int method_for_scan;
768
    int method_for_carry;
769
    int optimize_palette;
770
    int optimize_palette_entries;
771
    int complexion;
772
    int lut_policy;
773
    int method_for_largest;
774
    int reqcolor;
775
    int pixelformat;
776
    sixel_logger_t *logger;
777
} sixel_parallel_dither_plan_t;
778

779
static int
780
sixel_dither_parallel_worker(tp_job_t job,
781
                             void *userdata,
782
                             void *workspace)
783
{
784
    sixel_parallel_dither_plan_t *plan;
785
    unsigned char const *source;
786
    unsigned char *copy;
787
    size_t required;
788
    size_t offset;
789
    int band_index;
790
    int y0;
791
    int y1;
792
    int in0;
793
    int in1;
794
    int rows;
795
    int local_ncolors;
796
    SIXELSTATUS status;
797

798
    (void)workspace;
799

800
    plan = (sixel_parallel_dither_plan_t *)userdata;
801
    if (plan == NULL) {
×
802
        return SIXEL_BAD_ARGUMENT;
803
    }
804

805
    band_index = job.band_index;
806
    if (band_index < 0) {
×
807
        return SIXEL_BAD_ARGUMENT;
808
    }
809

810
    y0 = band_index * plan->band_height;
811
    if (y0 >= plan->height) {
×
812
        return SIXEL_OK;
813
    }
814

815
    y1 = y0 + plan->band_height;
816
    if (y1 > plan->height) {
×
817
        y1 = plan->height;
818
    }
819

820
    in0 = y0 - plan->overlap;
821
    if (in0 < 0) {
×
822
        in0 = 0;
823
    }
824
    in1 = y1;
825
    rows = in1 - in0;
826
    if (rows <= 0) {
×
827
        return SIXEL_OK;
828
    }
829

830
    required = (size_t)rows * plan->row_bytes;
831
    offset = (size_t)in0 * plan->row_bytes;
832
    copy = NULL;
833
    source = plan->pixels + offset;
834
    if (plan->overlap > 0) {
×
835
        copy = (unsigned char *)malloc(required);
836
        if (copy == NULL) {
×
837
            return SIXEL_BAD_ALLOCATION;
838
        }
839
        memcpy(copy, source, required);
840
        source = copy;
841
    }
842

843
    if (plan->logger != NULL) {
×
844
        sixel_logger_logf(plan->logger,
845
                          "worker",
846
                          "dither",
847
                          "start",
848
                          band_index,
849
                          in0,
850
                          y0,
851
                          y1,
852
                          in0,
853
                          in1,
854
                          "prepare rows=%d",
855
                          rows);
856
    }
857

858
    local_ncolors = plan->reqcolor;
859
    /*
860
     * Map directly into the shared destination but suppress writes
861
     * before output_start.  The overlap rows are computed only to warm
862
     * up the error diffusion and are discarded by the output_start
863
     * check in the renderer, so neighboring bands never clobber each
864
     * other's body.
865
     */
866
    status = sixel_dither_map_pixels(plan->dest + (size_t)in0 * plan->width,
867
                                     (unsigned char *)source,
868
                                     plan->width,
869
                                     rows,
870
                                     in0,
871
                                     y0,
872
                                     3,
873
                                     plan->palette->entries,
874
                                     plan->reqcolor,
875
                                     plan->method_for_diffuse,
876
                                     plan->method_for_scan,
877
                                     plan->method_for_carry,
878
                                     plan->optimize_palette,
879
                                     plan->optimize_palette_entries,
880
                                     plan->complexion,
881
                                     plan->lut_policy,
882
                                     plan->method_for_largest,
883
                                     NULL,
884
                                     &local_ncolors,
885
                                     plan->allocator,
886
                                     plan->dither,
887
                                     plan->pixelformat);
888
    if (plan->logger != NULL) {
×
889
        sixel_logger_logf(plan->logger,
890
                          "worker",
891
                          "dither",
892
                          "finish",
893
                          band_index,
894
                          in1 - 1,
895
                          y0,
896
                          y1,
897
                          in0,
898
                          in1,
899
                          "status=%d rows=%d",
900
                          status,
901
                          rows);
902
    }
903
    if (copy != NULL) {
×
904
        free(copy);
905
    }
906
    return status;
907
}
908

909
static SIXELSTATUS
910
sixel_dither_apply_palette_parallel(sixel_parallel_dither_plan_t *plan,
911
                                    int threads)
912
{
913
    SIXELSTATUS status;
914
    threadpool_t *pool;
915
    size_t depth_bytes;
916
    int nbands;
917
    int queue_depth;
918
    int band_index;
919
    int stride;
920
    int offset;
921

922
    if (plan == NULL || plan->palette == NULL) {
×
923
        return SIXEL_BAD_ARGUMENT;
924
    }
925

926
    depth_bytes = (size_t)sixel_helper_compute_depth(plan->pixelformat);
927
    if (depth_bytes == 0U) {
×
928
        return SIXEL_BAD_ARGUMENT;
929
    }
930
    plan->row_bytes = (size_t)plan->width * depth_bytes;
931

932
    nbands = (plan->height + plan->band_height - 1) / plan->band_height;
933
    if (nbands < 1) {
×
934
        return SIXEL_OK;
935
    }
936

937
    if (threads > nbands) {
×
938
        threads = nbands;
939
    }
940
    if (threads < 1) {
×
941
        threads = 1;
942
    }
943

944
    queue_depth = threads * 3;
945
    if (queue_depth > nbands) {
×
946
        queue_depth = nbands;
947
    }
948
    if (queue_depth < 1) {
×
949
        queue_depth = 1;
950
    }
951

952
    pool = threadpool_create(threads,
953
                             queue_depth,
954
                             0,
955
                             sixel_dither_parallel_worker,
956
                             plan);
957
    if (pool == NULL) {
×
958
        return SIXEL_BAD_ALLOCATION;
959
    }
960

961
    /*
962
     * Distribute the initial jobs so each worker starts far apart, then feed
963
     * follow-up work that walks downward from those seeds.  This staggered
964
     * order reduces contention around the top of the image and keeps later
965
     * assignments close to the last band a worker processed, improving cache
966
     * locality.
967
     */
968
    stride = (nbands + threads - 1) / threads;
969
    for (offset = 0; offset < stride; ++offset) {
×
970
        for (band_index = 0; band_index < threads; ++band_index) {
×
971
            tp_job_t job;
972
            int seeded;
973

974
            seeded = band_index * stride + offset;
975
            if (seeded >= nbands) {
×
976
                continue;
977
            }
978
            job.band_index = seeded;
979
            threadpool_push(pool, job);
980
        }
×
981
    }
982

983
    threadpool_finish(pool);
984
    status = threadpool_get_error(pool);
985
    threadpool_destroy(pool);
986

987
    return status;
988
}
989
#endif
990

991

992
/*
993
 * Helper that detects whether the palette currently matches either of the
994
 * builtin monochrome definitions.  These tables skip cache initialization
995
 * during fast-path dithering because they already match the terminal
996
 * defaults.
997
 */
998
static int
999
sixel_palette_is_builtin_mono(sixel_palette_t const *palette)
255✔
1000
{
1001
    if (palette == NULL) {
255!
1002
        return 0;
1003
    }
1004
    if (palette->entries == NULL) {
255!
1005
        return 0;
1006
    }
1007
    if (palette->entry_count < 2U) {
255✔
1008
        return 0;
1009
    }
1010
    if (palette->depth != 3) {
231!
1011
        return 0;
1012
    }
1013
    if (memcmp(palette->entries, pal_mono_dark,
231✔
1014
               sizeof(pal_mono_dark)) == 0) {
1015
        return 1;
1016
    }
1017
    if (memcmp(palette->entries, pal_mono_light,
219✔
1018
               sizeof(pal_mono_light)) == 0) {
1019
        return 1;
3✔
1020
    }
1021
    return 0;
1022
}
1023

1024
/*
1025
 * Route palette application through the local dithering helper.  The
1026
 * function keeps all state in the palette object so we can share cache
1027
 * buffers between invocations and later stages.  The flow is:
1028
 *   1. Synchronize the quantizer configuration with the dither object so the
1029
 *      LUT builder honors the requested policy.
1030
 *   2. Invoke sixel_dither_map_pixels() to populate the index buffer and
1031
 *      record the resulting palette size.
1032
 *   3. Return the status to the caller so palette application errors can be
1033
 *      reported at a single site.
1034
 */
1035
static SIXELSTATUS
1036
sixel_dither_resolve_indexes(sixel_index_t *result,
255✔
1037
                             unsigned char *data,
1038
                             int width,
1039
                             int height,
1040
                             int depth,
1041
                             sixel_palette_t *palette,
1042
                             int reqcolor,
1043
                             int method_for_diffuse,
1044
                             int method_for_scan,
1045
                             int method_for_carry,
1046
                             int foptimize,
1047
                             int foptimize_palette,
1048
                             int complexion,
1049
                              int lut_policy,
1050
                              int method_for_largest,
1051
                              int *ncolors,
1052
                              sixel_allocator_t *allocator,
1053
                              sixel_dither_t *dither,
1054
                              int pixelformat)
1055
{
1056
    SIXELSTATUS status = SIXEL_FALSE;
255✔
1057

1058
    if (palette == NULL || palette->entries == NULL) {
255!
1059
        return SIXEL_BAD_ARGUMENT;
1060
    }
1061

1062
    sixel_palette_set_lut_policy(lut_policy);
255✔
1063
    sixel_palette_set_method_for_largest(method_for_largest);
255✔
1064

1065
    status = sixel_dither_map_pixels(result,
255✔
1066
                                     data,
1067
                                     width,
1068
                                     height,
1069
                                     0,
1070
                                     0,
1071
                                     depth,
1072
                                     palette->entries,
1073
                                     reqcolor,
1074
                                     method_for_diffuse,
1075
                                     method_for_scan,
1076
                                     method_for_carry,
1077
                                     foptimize,
1078
                                     foptimize_palette,
1079
                                     complexion,
1080
                                     lut_policy,
1081
                                     method_for_largest,
1082
                                     palette->lut,
255✔
1083
                                     ncolors,
1084
                                     allocator,
1085
                                     dither,
1086
                                     pixelformat);
1087

1088
    return status;
255✔
1089
}
1090

1091

1092
/*
1093
 * VT340 undocumented behavior regarding the color palette reported
1094
 * by Vertis Sidus(@vrtsds):
1095
 *     it loads the first fifteen colors as 1 through 15, and loads the
1096
 *     sixteenth color as 0.
1097
 */
1098
static const unsigned char pal_vt340_mono[] = {
1099
    /* 1   Gray-2   */  13 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
1100
    /* 2   Gray-4   */  26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1101
    /* 3   Gray-6   */  40 * 255 / 100, 40 * 255 / 100, 40 * 255 / 100,
1102
    /* 4   Gray-1   */   6 * 255 / 100,  6 * 255 / 100,  6 * 255 / 100,
1103
    /* 5   Gray-3   */  20 * 255 / 100, 20 * 255 / 100, 20 * 255 / 100,
1104
    /* 6   Gray-5   */  33 * 255 / 100, 33 * 255 / 100, 33 * 255 / 100,
1105
    /* 7   White 7  */  46 * 255 / 100, 46 * 255 / 100, 46 * 255 / 100,
1106
    /* 8   Black 0  */   0 * 255 / 100,  0 * 255 / 100,  0 * 255 / 100,
1107
    /* 9   Gray-2   */  13 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
1108
    /* 10  Gray-4   */  26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1109
    /* 11  Gray-6   */  40 * 255 / 100, 40 * 255 / 100, 40 * 255 / 100,
1110
    /* 12  Gray-1   */   6 * 255 / 100,  6 * 255 / 100,  6 * 255 / 100,
1111
    /* 13  Gray-3   */  20 * 255 / 100, 20 * 255 / 100, 20 * 255 / 100,
1112
    /* 14  Gray-5   */  33 * 255 / 100, 33 * 255 / 100, 33 * 255 / 100,
1113
    /* 15  White 7  */  46 * 255 / 100, 46 * 255 / 100, 46 * 255 / 100,
1114
    /* 0   Black    */   0 * 255 / 100,  0 * 255 / 100,  0 * 255 / 100,
1115
};
1116

1117

1118
static const unsigned char pal_vt340_color[] = {
1119
    /* 1   Blue     */  20 * 255 / 100, 20 * 255 / 100, 80 * 255 / 100,
1120
    /* 2   Red      */  80 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
1121
    /* 3   Green    */  20 * 255 / 100, 80 * 255 / 100, 20 * 255 / 100,
1122
    /* 4   Magenta  */  80 * 255 / 100, 20 * 255 / 100, 80 * 255 / 100,
1123
    /* 5   Cyan     */  20 * 255 / 100, 80 * 255 / 100, 80 * 255 / 100,
1124
    /* 6   Yellow   */  80 * 255 / 100, 80 * 255 / 100, 20 * 255 / 100,
1125
    /* 7   Gray 50% */  53 * 255 / 100, 53 * 255 / 100, 53 * 255 / 100,
1126
    /* 8   Gray 25% */  26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1127
    /* 9   Blue*    */  33 * 255 / 100, 33 * 255 / 100, 60 * 255 / 100,
1128
    /* 10  Red*     */  60 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1129
    /* 11  Green*   */  33 * 255 / 100, 60 * 255 / 100, 33 * 255 / 100,
1130
    /* 12  Magenta* */  60 * 255 / 100, 33 * 255 / 100, 60 * 255 / 100,
1131
    /* 13  Cyan*    */  33 * 255 / 100, 60 * 255 / 100, 60 * 255 / 100,
1132
    /* 14  Yellow*  */  60 * 255 / 100, 60 * 255 / 100, 33 * 255 / 100,
1133
    /* 15  Gray 75% */  80 * 255 / 100, 80 * 255 / 100, 80 * 255 / 100,
1134
    /* 0   Black    */   0 * 255 / 100,  0 * 255 / 100,  0 * 255 / 100,
1135
};
1136

1137

1138
/* create dither context object */
1139
SIXELAPI SIXELSTATUS
1140
sixel_dither_new(
519✔
1141
    sixel_dither_t    /* out */ **ppdither, /* dither object to be created */
1142
    int               /* in */  ncolors,    /* required colors */
1143
    sixel_allocator_t /* in */  *allocator) /* allocator, null if you use
1144
                                               default allocator */
1145
{
1146
    SIXELSTATUS status = SIXEL_FALSE;
519✔
1147
    size_t headsize;
519✔
1148
    int quality_mode;
519✔
1149
    sixel_palette_t *palette;
519✔
1150

1151
    /* ensure given pointer is not null */
1152
    if (ppdither == NULL) {
519!
1153
        sixel_helper_set_additional_message(
×
1154
            "sixel_dither_new: ppdither is null.");
1155
        status = SIXEL_BAD_ARGUMENT;
×
1156
        goto end;
×
1157
    }
1158
    *ppdither = NULL;
519✔
1159

1160
    if (allocator == NULL) {
519✔
1161
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
39✔
1162
        if (SIXEL_FAILED(status)) {
39!
1163
            *ppdither = NULL;
×
1164
            goto end;
×
1165
        }
1166
    } else {
1167
        sixel_allocator_ref(allocator);
480✔
1168
    }
1169

1170
    if (ncolors < 0) {
519✔
1171
        ncolors = SIXEL_PALETTE_MAX;
1172
        quality_mode = SIXEL_QUALITY_HIGHCOLOR;
1173
    } else {
1174
        if (ncolors > SIXEL_PALETTE_MAX) {
483!
1175
            status = SIXEL_BAD_INPUT;
×
1176
            goto end;
×
1177
        } else if (ncolors < 1) {
483!
1178
            status = SIXEL_BAD_INPUT;
×
1179
            sixel_helper_set_additional_message(
×
1180
                "sixel_dither_new: palette colors must be more than 0");
1181
            goto end;
×
1182
        }
1183
        quality_mode = SIXEL_QUALITY_LOW;
1184
    }
1185
    headsize = sizeof(sixel_dither_t);
519✔
1186

1187
    *ppdither = (sixel_dither_t *)sixel_allocator_malloc(allocator, headsize);
519✔
1188
    if (*ppdither == NULL) {
519!
1189
        sixel_allocator_unref(allocator);
×
1190
        sixel_helper_set_additional_message(
×
1191
            "sixel_dither_new: sixel_allocator_malloc() failed.");
1192
        status = SIXEL_BAD_ALLOCATION;
×
1193
        goto end;
×
1194
    }
1195

1196
    (*ppdither)->ref = 1U;
519✔
1197
    (*ppdither)->palette = NULL;
519✔
1198
    (*ppdither)->reqcolors = ncolors;
519✔
1199
    (*ppdither)->force_palette = 0;
519✔
1200
    (*ppdither)->ncolors = ncolors;
519✔
1201
    (*ppdither)->origcolors = (-1);
519✔
1202
    (*ppdither)->keycolor = (-1);
519✔
1203
    (*ppdither)->optimized = 0;
519✔
1204
    (*ppdither)->optimize_palette = 0;
519✔
1205
    (*ppdither)->complexion = 1;
519✔
1206
    (*ppdither)->bodyonly = 0;
519✔
1207
    (*ppdither)->method_for_largest = SIXEL_LARGE_NORM;
519✔
1208
    (*ppdither)->method_for_rep = SIXEL_REP_CENTER_BOX;
519✔
1209
    (*ppdither)->method_for_diffuse = SIXEL_DIFFUSE_FS;
519✔
1210
    (*ppdither)->method_for_scan = SIXEL_SCAN_AUTO;
519✔
1211
    (*ppdither)->method_for_carry = SIXEL_CARRY_AUTO;
519✔
1212
    (*ppdither)->quality_mode = quality_mode;
519✔
1213
    (*ppdither)->requested_quality_mode = quality_mode;
519✔
1214
    (*ppdither)->pixelformat = SIXEL_PIXELFORMAT_RGB888;
519✔
1215
    (*ppdither)->prefer_float32 = 0;
519✔
1216
    (*ppdither)->allocator = allocator;
519✔
1217
    (*ppdither)->lut_policy = SIXEL_LUT_POLICY_AUTO;
519✔
1218
    (*ppdither)->sixel_reversible = 0;
519✔
1219
    (*ppdither)->quantize_model = SIXEL_QUANTIZE_MODEL_AUTO;
519✔
1220
    (*ppdither)->final_merge_mode = SIXEL_FINAL_MERGE_AUTO;
519✔
1221
    (*ppdither)->pipeline_row_callback = NULL;
519✔
1222
    (*ppdither)->pipeline_row_priv = NULL;
519✔
1223
    (*ppdither)->pipeline_index_buffer = NULL;
519✔
1224
    (*ppdither)->pipeline_index_size = 0;
519✔
1225
    (*ppdither)->pipeline_index_owned = 0;
519✔
1226
    (*ppdither)->pipeline_parallel_active = 0;
519✔
1227
    (*ppdither)->pipeline_band_height = 0;
519✔
1228
    (*ppdither)->pipeline_band_overlap = 0;
519✔
1229
    (*ppdither)->pipeline_dither_threads = 0;
519✔
1230
    (*ppdither)->pipeline_image_height = 0;
519✔
1231
    (*ppdither)->pipeline_logger = NULL;
519✔
1232

1233
    status = sixel_palette_new(&(*ppdither)->palette, allocator);
519✔
1234
    if (SIXEL_FAILED(status)) {
519!
1235
        sixel_allocator_free(allocator, *ppdither);
×
1236
        *ppdither = NULL;
×
1237
        goto end;
×
1238
    }
1239

1240
    palette = (*ppdither)->palette;
519✔
1241
    palette->requested_colors = (unsigned int)ncolors;
519✔
1242
    palette->quality_mode = quality_mode;
519✔
1243
    palette->force_palette = 0;
519✔
1244
    palette->lut_policy = SIXEL_LUT_POLICY_AUTO;
519✔
1245

1246
    status = sixel_palette_resize(palette,
519✔
1247
                                  (unsigned int)ncolors,
1248
                                  3,
1249
                                  allocator);
1250
    if (SIXEL_FAILED(status)) {
519!
1251
        sixel_palette_unref(palette);
×
1252
        (*ppdither)->palette = NULL;
×
1253
        sixel_allocator_free(allocator, *ppdither);
×
1254
        *ppdither = NULL;
×
1255
        goto end;
×
1256
    }
1257

1258
    status = SIXEL_OK;
1259

1260
end:
1261
    if (SIXEL_FAILED(status)) {
×
1262
        sixel_allocator_unref(allocator);
×
1263
    }
1264
    return status;
519✔
1265
}
1266

1267

1268
/* create dither context object (deprecated) */
1269
SIXELAPI sixel_dither_t *
1270
sixel_dither_create(
×
1271
    int     /* in */ ncolors)
1272
{
1273
    SIXELSTATUS status = SIXEL_FALSE;
×
1274
    sixel_dither_t *dither = NULL;
×
1275

1276
    status = sixel_dither_new(&dither, ncolors, NULL);
×
1277
    if (SIXEL_FAILED(status)) {
×
1278
        goto end;
1279
    }
1280

1281
end:
×
1282
    return dither;
×
1283
}
1284

1285

1286
SIXELAPI void
1287
sixel_dither_destroy(
519✔
1288
    sixel_dither_t  /* in */ *dither)
1289
{
1290
    sixel_allocator_t *allocator;
519✔
1291

1292
    if (dither) {
519!
1293
        allocator = dither->allocator;
519✔
1294
        if (dither->palette != NULL) {
519!
1295
            sixel_palette_unref(dither->palette);
519✔
1296
            dither->palette = NULL;
519✔
1297
        }
1298
        sixel_allocator_free(allocator, dither);
519✔
1299
        sixel_allocator_unref(allocator);
519✔
1300
    }
1301
}
519✔
1302

1303

1304
SIXELAPI void
1305
sixel_dither_ref(
960✔
1306
    sixel_dither_t  /* in */ *dither)
1307
{
1308
    /* TODO: be thread safe */
1309
    ++dither->ref;
960✔
1310
}
803✔
1311

1312

1313
SIXELAPI void
1314
sixel_dither_unref(
2,019✔
1315
    sixel_dither_t  /* in */ *dither)
1316
{
1317
    /* TODO: be thread safe */
1318
    if (dither != NULL && --dither->ref == 0) {
2,019✔
1319
        sixel_dither_destroy(dither);
519✔
1320
    }
1321
}
2,019✔
1322

1323

1324
SIXELAPI sixel_dither_t *
1325
sixel_dither_get(
39✔
1326
    int     /* in */ builtin_dither)
1327
{
1328
    SIXELSTATUS status = SIXEL_FALSE;
39✔
1329
    unsigned char *palette;
39✔
1330
    int ncolors;
39✔
1331
    int keycolor;
39✔
1332
    sixel_dither_t *dither = NULL;
39✔
1333

1334
    switch (builtin_dither) {
39!
1335
    case SIXEL_BUILTIN_MONO_DARK:
1336
        ncolors = 2;
1337
        palette = (unsigned char *)pal_mono_dark;
1338
        keycolor = 0;
1339
        break;
1340
    case SIXEL_BUILTIN_MONO_LIGHT:
3✔
1341
        ncolors = 2;
3✔
1342
        palette = (unsigned char *)pal_mono_light;
3✔
1343
        keycolor = 0;
3✔
1344
        break;
3✔
1345
    case SIXEL_BUILTIN_XTERM16:
6✔
1346
        ncolors = 16;
6✔
1347
        palette = (unsigned char *)pal_xterm256;
6✔
1348
        keycolor = (-1);
6✔
1349
        break;
6✔
1350
    case SIXEL_BUILTIN_XTERM256:
3✔
1351
        ncolors = 256;
3✔
1352
        palette = (unsigned char *)pal_xterm256;
3✔
1353
        keycolor = (-1);
3✔
1354
        break;
3✔
1355
    case SIXEL_BUILTIN_VT340_MONO:
3✔
1356
        ncolors = 16;
3✔
1357
        palette = (unsigned char *)pal_vt340_mono;
3✔
1358
        keycolor = (-1);
3✔
1359
        break;
3✔
1360
    case SIXEL_BUILTIN_VT340_COLOR:
3✔
1361
        ncolors = 16;
3✔
1362
        palette = (unsigned char *)pal_vt340_color;
3✔
1363
        keycolor = (-1);
3✔
1364
        break;
3✔
1365
    case SIXEL_BUILTIN_G1:
3✔
1366
        ncolors = 2;
3✔
1367
        palette = (unsigned char *)pal_gray_1bit;
3✔
1368
        keycolor = (-1);
3✔
1369
        break;
3✔
1370
    case SIXEL_BUILTIN_G2:
3✔
1371
        ncolors = 4;
3✔
1372
        palette = (unsigned char *)pal_gray_2bit;
3✔
1373
        keycolor = (-1);
3✔
1374
        break;
3✔
1375
    case SIXEL_BUILTIN_G4:
3✔
1376
        ncolors = 16;
3✔
1377
        palette = (unsigned char *)pal_gray_4bit;
3✔
1378
        keycolor = (-1);
3✔
1379
        break;
3✔
1380
    case SIXEL_BUILTIN_G8:
3✔
1381
        ncolors = 256;
3✔
1382
        palette = (unsigned char *)pal_gray_8bit;
3✔
1383
        keycolor = (-1);
3✔
1384
        break;
3✔
1385
    default:
×
1386
        goto end;
×
1387
    }
1388

1389
    status = sixel_dither_new(&dither, ncolors, NULL);
39✔
1390
    if (SIXEL_FAILED(status)) {
39!
1391
        dither = NULL;
×
1392
        goto end;
×
1393
    }
1394

1395
    status = sixel_palette_set_entries(dither->palette,
78✔
1396
                                       palette,
1397
                                       (unsigned int)ncolors,
1398
                                       3,
1399
                                       dither->allocator);
39✔
1400
    if (SIXEL_FAILED(status)) {
39!
1401
        sixel_dither_unref(dither);
×
1402
        dither = NULL;
×
1403
        goto end;
×
1404
    }
1405
    dither->palette->requested_colors = (unsigned int)ncolors;
39✔
1406
    dither->palette->entry_count = (unsigned int)ncolors;
39✔
1407
    dither->palette->depth = 3;
39✔
1408
    dither->keycolor = keycolor;
39✔
1409
    dither->optimized = 1;
39✔
1410
    dither->optimize_palette = 0;
39✔
1411

1412
end:
39✔
1413
    return dither;
39✔
1414
}
1415

1416

1417
static void
1418
sixel_dither_set_method_for_largest(
216✔
1419
    sixel_dither_t  /* in */ *dither,
1420
    int             /* in */ method_for_largest)
1421
{
1422
    if (method_for_largest == SIXEL_LARGE_AUTO) {
216✔
1423
        method_for_largest = SIXEL_LARGE_NORM;
186✔
1424
    }
1425
    dither->method_for_largest = method_for_largest;
216✔
1426
}
1427

1428

1429
static void
1430
sixel_dither_set_method_for_rep(
216✔
1431
    sixel_dither_t  /* in */ *dither,
1432
    int             /* in */ method_for_rep)
1433
{
1434
    if (method_for_rep == SIXEL_REP_AUTO) {
216✔
1435
        method_for_rep = SIXEL_REP_CENTER_BOX;
183✔
1436
    }
1437
    dither->method_for_rep = method_for_rep;
216✔
1438
}
1439

1440

1441
static void
1442
sixel_dither_set_quality_mode(
216✔
1443
    sixel_dither_t  /* in */  *dither,
1444
    int             /* in */  quality_mode)
1445
{
1446
    dither->requested_quality_mode = quality_mode;
216✔
1447

1448
    if (quality_mode == SIXEL_QUALITY_AUTO) {
216✔
1449
        if (dither->ncolors <= 8) {
192✔
1450
            quality_mode = SIXEL_QUALITY_HIGH;
1451
        } else {
1452
            quality_mode = SIXEL_QUALITY_LOW;
180✔
1453
        }
1454
    }
1455
    dither->quality_mode = quality_mode;
216✔
1456
}
1457

1458

1459
SIXELAPI SIXELSTATUS
1460
sixel_dither_initialize(
216✔
1461
    sixel_dither_t  /* in */ *dither,
1462
    unsigned char   /* in */ *data,
1463
    int             /* in */ width,
1464
    int             /* in */ height,
1465
    int             /* in */ pixelformat,
1466
    int             /* in */ method_for_largest,
1467
    int             /* in */ method_for_rep,
1468
    int             /* in */ quality_mode)
1469
{
1470
    unsigned char *buf = NULL;
216✔
1471
    unsigned char *normalized_pixels = NULL;
216✔
1472
    float *float_pixels = NULL;
216✔
1473
    unsigned char *input_pixels;
216✔
1474
    SIXELSTATUS status = SIXEL_FALSE;
216✔
1475
    size_t total_pixels;
216✔
1476
    unsigned int payload_length;
216✔
1477
    int palette_pixelformat;
216✔
1478
    int prefer_float32;
216✔
1479

1480
    /* ensure dither object is not null */
1481
    if (dither == NULL) {
216!
1482
        sixel_helper_set_additional_message(
×
1483
            "sixel_dither_new: dither is null.");
1484
        status = SIXEL_BAD_ARGUMENT;
×
1485
        goto end;
×
1486
    }
1487

1488
    /* increment ref count */
1489
    sixel_dither_ref(dither);
216✔
1490

1491
    sixel_dither_set_pixelformat(dither, pixelformat);
216✔
1492

1493
    /* keep quantizer policy in sync with the dither object */
1494
    sixel_palette_set_lut_policy(dither->lut_policy);
216✔
1495

1496
    input_pixels = NULL;
216✔
1497
    total_pixels = (size_t)width * (size_t)height;
216✔
1498
    payload_length = 0U;
216✔
1499
    palette_pixelformat = SIXEL_PIXELFORMAT_RGB888;
216✔
1500
    prefer_float32 = dither->prefer_float32;
216✔
1501

1502
    /* Float32 input requires the pipeline to honour higher precision. */
1503
    if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
216!
1504
        prefer_float32 = 1;
×
1505
    }
1506

1507
    switch (pixelformat) {
216!
1508
    case SIXEL_PIXELFORMAT_RGB888:
1509
        input_pixels = data;
1510
        break;
1511
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
×
1512
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
1513
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
1514
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
1515
        if (prefer_float32) {
×
1516
            input_pixels = data;
×
1517
            palette_pixelformat = pixelformat;
×
1518
            payload_length = (unsigned int)(total_pixels * 3U
×
1519
                                            * sizeof(float));
1520
            break;
×
1521
        }
1522
        /* fallthrough */
1523
    default:
1524
        /* normalize pixelformat */
1525
        normalized_pixels
×
1526
            = (unsigned char *)sixel_allocator_malloc(
×
1527
                dither->allocator, (size_t)(width * height * 3));
×
1528
        if (normalized_pixels == NULL) {
×
1529
            sixel_helper_set_additional_message(
×
1530
                "sixel_dither_initialize: sixel_allocator_malloc() failed.");
1531
            status = SIXEL_BAD_ALLOCATION;
×
1532
            goto end;
×
1533
        }
1534

1535
        status = sixel_helper_normalize_pixelformat(
×
1536
            normalized_pixels,
1537
            &pixelformat,
1538
            data,
1539
            pixelformat,
1540
            width,
1541
            height);
1542
        if (SIXEL_FAILED(status)) {
×
1543
            goto end;
×
1544
        }
1545
        input_pixels = normalized_pixels;
1546
        break;
1547
    }
1548

1549
    if (payload_length == 0U) {
×
1550
        payload_length = (unsigned int)(total_pixels * 3U);
216✔
1551
    }
1552

1553
    if (prefer_float32
216!
1554
        && !SIXEL_PIXELFORMAT_IS_FLOAT32(palette_pixelformat)
×
1555
        && total_pixels > 0U) {
×
1556
        status = sixel_dither_promote_rgb888_to_float32(
×
1557
            &float_pixels,
1558
            input_pixels,
1559
            total_pixels,
1560
            dither->allocator);
1561
        if (SIXEL_SUCCEEDED(status) && float_pixels != NULL) {
×
1562
            payload_length
×
1563
                = (unsigned int)(total_pixels * 3U * sizeof(float));
×
1564
            palette_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
×
1565
            input_pixels = (unsigned char *)float_pixels;
×
1566
        } else {
1567
            prefer_float32 = 0;
1568
            status = SIXEL_OK;
216✔
1569
        }
1570
    }
1571

1572
    dither->prefer_float32 = prefer_float32;
216✔
1573

1574
    sixel_dither_set_method_for_largest(dither, method_for_largest);
216✔
1575
    sixel_dither_set_method_for_rep(dither, method_for_rep);
216✔
1576
    sixel_dither_set_quality_mode(dither, quality_mode);
216✔
1577

1578
    status = sixel_palette_make_palette(&buf,
432✔
1579
                                        input_pixels,
1580
                                        payload_length,
1581
                                        palette_pixelformat,
1582
                                        (unsigned int)dither->reqcolors,
216✔
1583
                                        (unsigned int *)&dither->ncolors,
216✔
1584
                                        (unsigned int *)&dither->origcolors,
216✔
1585
                                        dither->method_for_largest,
1586
                                        dither->method_for_rep,
1587
                                        dither->quality_mode,
1588
                                        dither->force_palette,
1589
                                        dither->sixel_reversible,
1590
                                        dither->quantize_model,
1591
                                        dither->final_merge_mode,
1592
                                        dither->prefer_float32,
1593
                                        dither->allocator);
1594
    if (SIXEL_FAILED(status)) {
216!
1595
        goto end;
×
1596
    }
1597
    status = sixel_palette_set_entries(dither->palette,
432✔
1598
                                       buf,
1599
                                       (unsigned int)dither->ncolors,
216✔
1600
                                       3,
1601
                                       dither->allocator);
1602
    if (SIXEL_FAILED(status)) {
216!
1603
        goto end;
×
1604
    }
1605
    dither->palette->entry_count = (unsigned int)dither->ncolors;
216✔
1606
    dither->palette->requested_colors = (unsigned int)dither->reqcolors;
216✔
1607
    dither->palette->original_colors = (unsigned int)dither->origcolors;
216✔
1608
    dither->palette->depth = 3;
216✔
1609

1610
    dither->optimized = 1;
216✔
1611
    if (dither->origcolors <= dither->ncolors) {
216✔
1612
        dither->method_for_diffuse = SIXEL_DIFFUSE_NONE;
180✔
1613
    }
1614

1615
    sixel_palette_free_palette(buf, dither->allocator);
216✔
1616
    status = SIXEL_OK;
216✔
1617

1618
end:
216✔
1619
    if (normalized_pixels != NULL) {
216!
1620
        sixel_allocator_free(dither->allocator, normalized_pixels);
×
1621
    }
1622
    if (float_pixels != NULL) {
216!
1623
        sixel_allocator_free(dither->allocator, float_pixels);
×
1624
    }
1625

1626
    /* decrement ref count */
1627
    sixel_dither_unref(dither);
216✔
1628

1629
    return status;
216✔
1630
}
1631

1632

1633
/* set lookup table policy */
1634
SIXELAPI void
1635
sixel_dither_set_lut_policy(
735✔
1636
    sixel_dither_t  /* in */ *dither,
1637
    int             /* in */ lut_policy)
1638
{
1639
    int normalized;
735✔
1640
    int previous_policy;
735✔
1641

1642
    if (dither == NULL) {
735!
1643
        return;
1644
    }
1645

1646
    normalized = SIXEL_LUT_POLICY_AUTO;
735✔
1647
    if (lut_policy == SIXEL_LUT_POLICY_5BIT
735!
1648
        || lut_policy == SIXEL_LUT_POLICY_6BIT
735!
1649
        || lut_policy == SIXEL_LUT_POLICY_CERTLUT
735!
1650
        || lut_policy == SIXEL_LUT_POLICY_NONE) {
735!
1651
        normalized = lut_policy;
×
1652
    }
1653
    previous_policy = dither->lut_policy;
735✔
1654
    if (previous_policy == normalized) {
735!
1655
        return;
1656
    }
1657

1658
    /*
1659
     * Policy transitions for the shared LUT mirror the previous cache flow:
1660
     *
1661
     *   [lut] --policy change--> (drop) --rebuild--> [lut]
1662
     */
1663
    dither->lut_policy = normalized;
×
1664
    if (dither->palette != NULL) {
×
1665
        dither->palette->lut_policy = normalized;
×
1666
    }
1667
    if (dither->palette != NULL && dither->palette->lut != NULL) {
×
1668
        sixel_lut_unref(dither->palette->lut);
×
1669
        dither->palette->lut = NULL;
×
1670
    }
1671
}
1!
1672

1673

1674
/* get lookup table policy */
1675
SIXELAPI int
1676
sixel_dither_get_lut_policy(
×
1677
    sixel_dither_t  /* in */ *dither)
1678
{
1679
    int policy;
×
1680

1681
    policy = SIXEL_LUT_POLICY_AUTO;
×
1682
    if (dither != NULL) {
×
1683
        policy = dither->lut_policy;
×
1684
    }
1685

1686
    return policy;
×
1687
}
1688

1689

1690
/* set diffusion type, choose from enum methodForDiffuse */
1691
SIXELAPI void
1692
sixel_dither_set_diffusion_type(
519✔
1693
    sixel_dither_t  /* in */ *dither,
1694
    int             /* in */ method_for_diffuse)
1695
{
1696
    if (method_for_diffuse == SIXEL_DIFFUSE_AUTO) {
519✔
1697
        if (dither->ncolors > 16) {
219✔
1698
            method_for_diffuse = SIXEL_DIFFUSE_FS;
1699
        } else {
1700
            method_for_diffuse = SIXEL_DIFFUSE_ATKINSON;
156✔
1701
        }
1702
    }
1703
    dither->method_for_diffuse = method_for_diffuse;
519✔
1704
}
519✔
1705

1706

1707
/* set scan order for diffusion */
1708
SIXELAPI void
1709
sixel_dither_set_diffusion_scan(
519✔
1710
    sixel_dither_t  /* in */ *dither,
1711
    int             /* in */ method_for_scan)
1712
{
1713
    if (method_for_scan != SIXEL_SCAN_AUTO &&
519!
1714
            method_for_scan != SIXEL_SCAN_RASTER &&
519!
1715
            method_for_scan != SIXEL_SCAN_SERPENTINE) {
1716
        method_for_scan = SIXEL_SCAN_RASTER;
×
1717
    }
1718
    dither->method_for_scan = method_for_scan;
519✔
1719
}
519✔
1720

1721

1722
/* set carry buffer mode for diffusion */
1723
SIXELAPI void
1724
sixel_dither_set_diffusion_carry(
519✔
1725
    sixel_dither_t  /* in */ *dither,
1726
    int             /* in */ method_for_carry)
1727
{
1728
    if (method_for_carry != SIXEL_CARRY_AUTO &&
519!
1729
            method_for_carry != SIXEL_CARRY_DISABLE &&
519!
1730
            method_for_carry != SIXEL_CARRY_ENABLE) {
1731
        method_for_carry = SIXEL_CARRY_AUTO;
×
1732
    }
1733
    dither->method_for_carry = method_for_carry;
519✔
1734
}
519✔
1735

1736

1737
/* get number of palette colors */
1738
SIXELAPI int
1739
sixel_dither_get_num_of_palette_colors(
×
1740
    sixel_dither_t  /* in */ *dither)
1741
{
1742
    return dither->ncolors;
×
1743
}
1744

1745

1746
/* get number of histogram colors */
1747
SIXELAPI int
1748
sixel_dither_get_num_of_histogram_colors(
195✔
1749
    sixel_dither_t /* in */ *dither)  /* dither context object */
1750
{
1751
    return dither->origcolors;
195✔
1752
}
1753

1754

1755
/* typoed: remained for keeping compatibility */
1756
SIXELAPI int
1757
sixel_dither_get_num_of_histgram_colors(
×
1758
    sixel_dither_t /* in */ *dither)  /* dither context object */
1759
{
1760
    return sixel_dither_get_num_of_histogram_colors(dither);
×
1761
}
1762

1763

1764
/* get palette */
1765
SIXELAPI unsigned char *
1766
sixel_dither_get_palette(
×
1767
    sixel_dither_t /* in */ *dither)  /* dither context object */
1768
{
1769
    if (dither == NULL || dither->palette == NULL) {
×
1770
        return NULL;
1771
    }
1772

1773
    return dither->palette->entries;
×
1774
}
1775

1776
SIXELAPI SIXELSTATUS
1777
sixel_dither_get_quantized_palette(sixel_dither_t *dither,
495✔
1778
                                   sixel_palette_t **pppalette)
1779
{
1780
    if (pppalette == NULL) {
495!
1781
        return SIXEL_BAD_ARGUMENT;
1782
    }
1783
    *pppalette = NULL;
495✔
1784

1785
    if (dither == NULL || dither->palette == NULL) {
495!
1786
        return SIXEL_RUNTIME_ERROR;
1787
    }
1788

1789
    sixel_palette_ref(dither->palette);
495✔
1790
    *pppalette = dither->palette;
495✔
1791

1792
    return SIXEL_OK;
495✔
1793
}
1794

1795

1796
/* set palette */
1797
SIXELAPI void
1798
sixel_dither_set_palette(
228✔
1799
    sixel_dither_t /* in */ *dither,   /* dither context object */
1800
    unsigned char  /* in */ *palette)
1801
{
1802
    if (dither == NULL || dither->palette == NULL) {
228!
1803
        return;
1804
    }
1805

1806
    (void)sixel_palette_set_entries(dither->palette,
228✔
1807
                                    palette,
1808
                                    (unsigned int)dither->ncolors,
228✔
1809
                                    3,
1810
                                    dither->allocator);
1811
}
1812

1813

1814
/* set the factor of complexion color correcting */
1815
SIXELAPI void
1816
sixel_dither_set_complexion_score(
6✔
1817
    sixel_dither_t /* in */ *dither,  /* dither context object */
1818
    int            /* in */ score)    /* complexion score (>= 1) */
1819
{
1820
    dither->complexion = score;
6✔
1821
    if (dither->palette != NULL) {
6!
1822
        dither->palette->complexion = score;
6✔
1823
    }
1824
}
6✔
1825

1826

1827
/* set whether omitting palette difinition */
1828
SIXELAPI void
1829
sixel_dither_set_body_only(
×
1830
    sixel_dither_t /* in */ *dither,     /* dither context object */
1831
    int            /* in */ bodyonly)    /* 0: output palette section
1832
                                            1: do not output palette section  */
1833
{
1834
    dither->bodyonly = bodyonly;
×
1835
}
×
1836

1837

1838
/* set whether optimize palette size */
1839
SIXELAPI void
1840
sixel_dither_set_optimize_palette(
339✔
1841
    sixel_dither_t /* in */ *dither,   /* dither context object */
1842
    int            /* in */ do_opt)    /* 0: optimize palette size
1843
                                          1: don't optimize palette size */
1844
{
1845
    dither->optimize_palette = do_opt;
339✔
1846
}
339✔
1847

1848

1849
/* set pixelformat */
1850
SIXELAPI void
1851
sixel_dither_set_pixelformat(
1,113✔
1852
    sixel_dither_t /* in */ *dither,     /* dither context object */
1853
    int            /* in */ pixelformat) /* one of enum pixelFormat */
1854
{
1855
    /* Keep the float32 preference aligned with the requested pixelformat. */
1856
    dither->pixelformat = pixelformat;
1,113✔
1857
    dither->prefer_float32 =
1,113✔
1858
        SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat) ? 1 : 0;
814!
1859
}
1,041✔
1860

1861

1862
/* toggle SIXEL reversible palette mode */
1863
SIXELAPI void
1864
sixel_dither_set_sixel_reversible(
195✔
1865
    sixel_dither_t /* in */ *dither,
1866
    int            /* in */ enable)
1867
{
1868
    /*
1869
     * The diagram below shows how the flag routes palette generation:
1870
     *
1871
     *   pixels --> [histogram]
1872
     *                  |
1873
     *                  v
1874
     *           (optional reversible snap)
1875
     *                  |
1876
     *                  v
1877
     *               palette
1878
     */
1879
    if (dither == NULL) {
195!
1880
        return;
1881
    }
1882
    dither->sixel_reversible = enable ? 1 : 0;
195✔
1883
    if (dither->palette != NULL) {
195!
1884
        dither->palette->sixel_reversible = dither->sixel_reversible;
195✔
1885
    }
1886
}
1887

1888
/* select final merge policy */
1889
SIXELAPI void
1890
sixel_dither_set_final_merge(
195✔
1891
    sixel_dither_t /* in */ *dither,
1892
    int            /* in */ final_merge)
1893
{
1894
    int mode;
195✔
1895

1896
    if (dither == NULL) {
195!
1897
        return;
1898
    }
1899
    mode = SIXEL_FINAL_MERGE_AUTO;
195✔
1900
    if (final_merge == SIXEL_FINAL_MERGE_NONE
195!
1901
        || final_merge == SIXEL_FINAL_MERGE_WARD) {
195!
1902
        mode = final_merge;
1903
    } else if (final_merge == SIXEL_FINAL_MERGE_AUTO) {
195!
1904
        mode = SIXEL_FINAL_MERGE_AUTO;
195✔
1905
    }
1906
    dither->final_merge_mode = mode;
195✔
1907
    if (dither->palette != NULL) {
195!
1908
        dither->palette->final_merge = mode;
195✔
1909
    }
1910
}
1!
1911

1912
/* set transparent */
1913
SIXELAPI void
1914
sixel_dither_set_transparent(
×
1915
    sixel_dither_t /* in */ *dither,      /* dither context object */
1916
    int            /* in */ transparent)  /* transparent color index */
1917
{
1918
    dither->keycolor = transparent;
×
1919
}
×
1920

1921

1922
/* set transparent */
1923
sixel_index_t *
1924
sixel_dither_apply_palette(
255✔
1925
    sixel_dither_t  /* in */ *dither,
1926
    unsigned char   /* in */ *pixels,
1927
    int             /* in */ width,
1928
    int             /* in */ height)
1929
{
1930
    SIXELSTATUS status = SIXEL_FALSE;
255✔
1931
    size_t bufsize;
255✔
1932
    size_t normalized_size;
255✔
1933
    size_t total_pixels;
255✔
1934
    sixel_index_t *dest = NULL;
255✔
1935
    int ncolors;
255✔
1936
    int method_for_scan;
255✔
1937
    int method_for_carry;
255✔
1938
    unsigned char *normalized_pixels = NULL;
255✔
1939
    unsigned char *input_pixels;
255✔
1940
    float *float_pipeline_pixels = NULL;
255✔
1941
    int owns_float_pipeline;
255✔
1942
    int pipeline_pixelformat;
255✔
1943
    int prefer_float_pipeline;
255✔
1944
    int palette_probe_active;
255✔
1945
    double palette_started_at;
255✔
1946
    double palette_finished_at;
255✔
1947
    double palette_duration;
255✔
1948
    sixel_palette_t *palette;
255✔
1949
    int dest_owned;
255✔
1950
    int parallel_active;
255✔
1951
#if SIXEL_ENABLE_THREADS
1952
    int parallel_band_height = 0;
170✔
1953
    int parallel_overlap = 0;
170✔
1954
    int parallel_threads = 1;
170✔
1955
#endif  /* SIXEL_ENABLE_THREADS */
1956
    sixel_logger_t *logger = NULL;
255✔
1957

1958
    parallel_active = 0;
255✔
1959

1960
    /* ensure dither object is not null */
1961
    if (dither == NULL) {
255!
1962
        sixel_helper_set_additional_message(
×
1963
            "sixel_dither_apply_palette: dither is null.");
1964
        status = SIXEL_BAD_ARGUMENT;
×
1965
        goto end;
×
1966
    }
1967

1968
    sixel_dither_ref(dither);
255✔
1969

1970
    palette = dither->palette;
255✔
1971
    if (palette == NULL) {
255!
1972
        sixel_helper_set_additional_message(
×
1973
            "sixel_dither_apply_palette: palette is null.");
1974
        status = SIXEL_BAD_ARGUMENT;
×
1975
        goto end;
×
1976
    }
1977

1978
    parallel_active = dither->pipeline_parallel_active;
255✔
1979
#if SIXEL_ENABLE_THREADS
1980
    parallel_band_height = dither->pipeline_band_height;
170✔
1981
    parallel_overlap = dither->pipeline_band_overlap;
170✔
1982
    parallel_threads = dither->pipeline_dither_threads;
170✔
1983
#endif  /* SIXEL_ENABLE_THREADS */
1984
    logger = dither->pipeline_logger;
255✔
1985

1986
    if (!parallel_active && logger != NULL) {
255!
1987
        sixel_logger_logf(logger,
×
1988
                          "worker",
1989
                          "dither",
1990
                          "start",
1991
                          0,
1992
                          0,
1993
                          0,
1994
                          height,
1995
                          0,
1996
                          height,
1997
                          "serial dither begin height=%d",
1998
                          height);
1999
    }
2000

2001
    if (parallel_active && dither->optimize_palette != 0) {
255!
2002
        /*
2003
         * Palette minimization rewrites the palette entries in place.
2004
         * Parallel bands would race on the shared table, so fall back to
2005
         * the serial path when the feature is active.
2006
         */
2007
        parallel_active = 0;
255✔
2008
    }
2009

2010
    bufsize = (size_t)(width * height) * sizeof(sixel_index_t);
255✔
2011
    total_pixels = (size_t)width * (size_t)height;
255✔
2012
    owns_float_pipeline = 0;
255✔
2013
    pipeline_pixelformat = dither->pixelformat;
255✔
2014
    /*
2015
     * Reuse the externally allocated index buffer when the pipeline has
2016
     * already provisioned storage for the producer/worker hand-off.
2017
     */
2018
    if (dither->pipeline_index_buffer != NULL &&
255!
2019
            dither->pipeline_index_size >= bufsize) {
×
2020
        dest = dither->pipeline_index_buffer;
2021
        dest_owned = 0;
2022
    } else {
2023
        dest = (sixel_index_t *)sixel_allocator_malloc(dither->allocator,
255✔
2024
                                                       bufsize);
2025
        if (dest == NULL) {
255!
2026
            sixel_helper_set_additional_message(
×
2027
                "sixel_dither_new: sixel_allocator_malloc() failed.");
2028
            status = SIXEL_BAD_ALLOCATION;
×
2029
            goto end;
×
2030
        }
2031
        dest_owned = 1;
2032
    }
2033
    dither->pipeline_index_owned = dest_owned;
255✔
2034

2035
    /*
2036
     * Disable palette caching when the caller selected the NONE policy so
2037
     * every pixel lookup performs a direct palette scan.  Other quality
2038
     * modes continue to honor the requested LUT policy, including "full".
2039
     */
2040
    if (dither->lut_policy == SIXEL_LUT_POLICY_NONE) {
255!
2041
        dither->optimized = 0;
×
2042
    }
2043

2044
    if (dither->optimized) {
255!
2045
        if (!sixel_palette_is_builtin_mono(palette)) {
255✔
2046
            int policy;
240✔
2047
            int wcomp1;
240✔
2048
            int wcomp2;
240✔
2049
            int wcomp3;
240✔
2050

2051
            policy = dither->lut_policy;
240✔
2052
            if (policy != SIXEL_LUT_POLICY_CERTLUT
240!
2053
                && policy != SIXEL_LUT_POLICY_5BIT
240!
2054
                && policy != SIXEL_LUT_POLICY_6BIT) {
1!
2055
                policy = SIXEL_LUT_POLICY_6BIT;
240✔
2056
            }
2057
            if (palette->lut == NULL) {
240✔
2058
                status = sixel_lut_new(&palette->lut,
237✔
2059
                                       policy,
2060
                                       palette->allocator);
2061
                if (SIXEL_FAILED(status)) {
237!
2062
                    sixel_helper_set_additional_message(
×
2063
                        "sixel_dither_apply_palette: lut allocation failed.");
2064
                    goto end;
×
2065
                }
2066
            }
2067
            if (policy == SIXEL_LUT_POLICY_CERTLUT) {
240!
2068
                if (dither->method_for_largest == SIXEL_LARGE_LUM) {
×
2069
                    wcomp1 = dither->complexion * 299;
×
2070
                    wcomp2 = 587;
×
2071
                    wcomp3 = 114;
×
2072
                } else {
2073
                    wcomp1 = dither->complexion;
×
2074
                    wcomp2 = 1;
×
2075
                    wcomp3 = 1;
×
2076
                }
2077
            } else {
2078
                wcomp1 = dither->complexion;
240✔
2079
                wcomp2 = 1;
240✔
2080
                wcomp3 = 1;
240✔
2081
            }
2082
            status = sixel_lut_configure(palette->lut,
480✔
2083
                                         palette->entries,
240✔
2084
                                         palette->depth,
2085
                                         (int)palette->entry_count,
240✔
2086
                                         dither->complexion,
2087
                                         wcomp1,
2088
                                         wcomp2,
2089
                                         wcomp3,
2090
                                         policy,
2091
                                         dither->pixelformat);
2092
            if (SIXEL_FAILED(status)) {
240!
2093
                sixel_helper_set_additional_message(
×
2094
                    "sixel_dither_apply_palette: lut configuration failed.");
2095
                goto end;
×
2096
            }
2097
        }
1!
2098
    }
2099

2100
    owns_float_pipeline = 0;
255✔
2101
    pipeline_pixelformat = dither->pixelformat;
255✔
2102
    prefer_float_pipeline =
255✔
2103
        sixel_dither_method_supports_float_pipeline(dither);
255✔
2104
    if (pipeline_pixelformat == SIXEL_PIXELFORMAT_RGB888) {
255!
2105
        input_pixels = pixels;
2106
    } else if (SIXEL_PIXELFORMAT_IS_FLOAT32(pipeline_pixelformat)
×
2107
               && prefer_float_pipeline) {
×
2108
        input_pixels = pixels;
2109
    } else {
2110
        normalized_size = (size_t)width * (size_t)height * 3U;
×
2111
        normalized_pixels
×
2112
            = (unsigned char *)sixel_allocator_malloc(dither->allocator,
×
2113
                                                      normalized_size);
2114
        if (normalized_pixels == NULL) {
×
2115
            sixel_helper_set_additional_message(
×
2116
                "sixel_dither_new: sixel_allocator_malloc() failed.");
2117
            status = SIXEL_BAD_ALLOCATION;
×
2118
            goto end;
×
2119
        }
2120
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
×
2121
                                                    &dither->pixelformat,
2122
                                                    pixels, dither->pixelformat,
2123
                                                    width, height);
2124
        if (SIXEL_FAILED(status)) {
×
2125
            goto end;
×
2126
        }
2127
        input_pixels = normalized_pixels;
2128
        pipeline_pixelformat = SIXEL_PIXELFORMAT_RGB888;
2129
    }
2130
    if (prefer_float_pipeline
255!
2131
        && pipeline_pixelformat == SIXEL_PIXELFORMAT_RGB888
255!
2132
        && total_pixels > 0U) {
×
2133
        status = sixel_dither_promote_rgb888_to_float32(
×
2134
            &float_pipeline_pixels,
2135
            input_pixels,
2136
            total_pixels,
2137
            dither->allocator);
2138
        if (SIXEL_SUCCEEDED(status) && float_pipeline_pixels != NULL) {
×
2139
            input_pixels = (unsigned char *)float_pipeline_pixels;
×
2140
            pipeline_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
×
2141
            owns_float_pipeline = 1;
×
2142
        } else {
2143
            prefer_float_pipeline = 0;
255✔
2144
            status = SIXEL_OK;
255✔
2145
        }
2146
    } else if (prefer_float_pipeline
×
2147
               && !SIXEL_PIXELFORMAT_IS_FLOAT32(pipeline_pixelformat)) {
1!
2148
        prefer_float_pipeline = 0;
2149
    }
2150

2151
    method_for_scan = dither->method_for_scan;
255✔
2152
    if (method_for_scan == SIXEL_SCAN_AUTO) {
255✔
2153
        method_for_scan = SIXEL_SCAN_RASTER;
252✔
2154
    }
2155

2156
    method_for_carry = dither->method_for_carry;
255✔
2157
    if (method_for_carry == SIXEL_CARRY_AUTO) {
255!
2158
        method_for_carry = SIXEL_CARRY_DISABLE;
255✔
2159
    }
2160

2161
    palette_probe_active = sixel_assessment_palette_probe_enabled();
255✔
2162
    palette_started_at = 0.0;
255✔
2163
    palette_finished_at = 0.0;
255✔
2164
    palette_duration = 0.0;
255✔
2165
    if (palette_probe_active) {
255!
2166
        /*
2167
         * Palette spans execute inside the encode stage.  We sample the
2168
         * duration here so the assessment can reassign the work to the
2169
         * PaletteApply bucket.
2170
         */
2171
        palette_started_at = sixel_assessment_timer_now();
×
2172
    }
2173
    palette->lut_policy = dither->lut_policy;
255✔
2174
    palette->method_for_largest = dither->method_for_largest;
255✔
2175
#if SIXEL_ENABLE_THREADS
2176
    if (parallel_active && parallel_threads > 1
170!
2177
            && parallel_band_height > 0) {
×
2178
        sixel_parallel_dither_plan_t plan;
2179
        int adjusted_overlap;
2180
        int adjusted_height;
2181

2182
        adjusted_overlap = parallel_overlap;
2183
        if (adjusted_overlap < 0) {
×
2184
            adjusted_overlap = 0;
2185
        }
2186
        adjusted_height = parallel_band_height;
2187
        if (adjusted_height < 6) {
×
2188
            adjusted_height = 6;
2189
        }
2190
        if ((adjusted_height % 6) != 0) {
×
2191
            adjusted_height = ((adjusted_height + 5) / 6) * 6;
2192
        }
2193
        if (adjusted_overlap > adjusted_height / 2) {
×
2194
            adjusted_overlap = adjusted_height / 2;
2195
        }
2196

2197
        memset(&plan, 0, sizeof(plan));
2198
        plan.dest = dest;
2199
        plan.pixels = input_pixels;
2200
        plan.palette = palette;
2201
        plan.allocator = dither->allocator;
2202
        plan.dither = dither;
2203
        plan.width = width;
2204
        plan.height = height;
2205
        plan.band_height = adjusted_height;
2206
        plan.overlap = adjusted_overlap;
2207
        plan.method_for_diffuse = dither->method_for_diffuse;
2208
        plan.method_for_scan = method_for_scan;
2209
        plan.method_for_carry = method_for_carry;
2210
        plan.optimize_palette = dither->optimized;
2211
        plan.optimize_palette_entries = dither->optimize_palette;
2212
        plan.complexion = dither->complexion;
2213
        plan.lut_policy = dither->lut_policy;
2214
        plan.method_for_largest = dither->method_for_largest;
2215
        plan.reqcolor = dither->ncolors;
2216
        plan.pixelformat = pipeline_pixelformat;
2217
        plan.logger = logger;
2218

2219
        status = sixel_dither_apply_palette_parallel(&plan,
2220
                                                     parallel_threads);
2221
        ncolors = dither->ncolors;
2222
    } else
2223
#endif
2224
    {
2225
        status = sixel_dither_resolve_indexes(dest,
255✔
2226
                                              input_pixels,
2227
                                              width,
2228
                                              height,
2229
                                              3,
2230
                                              palette,
2231
                                              dither->ncolors,
2232
                                              dither->method_for_diffuse,
2233
                                              method_for_scan,
2234
                                              method_for_carry,
2235
                                              dither->optimized,
2236
                                              dither->optimize_palette,
2237
                                              dither->complexion,
2238
                                              dither->lut_policy,
2239
                                              dither->method_for_largest,
2240
                                              &ncolors,
2241
                                              dither->allocator,
2242
                                              dither,
2243
                                              pipeline_pixelformat);
2244
    }
2245
    if (palette_probe_active) {
255!
2246
        palette_finished_at = sixel_assessment_timer_now();
×
2247
        palette_duration = palette_finished_at - palette_started_at;
×
2248
        if (palette_duration < 0.0) {
×
2249
            palette_duration = 0.0;
2250
        }
2251
        sixel_assessment_record_palette_apply_span(palette_duration);
×
2252
    }
2253
    if (SIXEL_FAILED(status)) {
255!
2254
        if (dest != NULL && dest_owned) {
×
2255
            sixel_allocator_free(dither->allocator, dest);
×
2256
        }
2257
        dest = NULL;
×
2258
        goto end;
×
2259
    }
2260

2261
    dither->ncolors = ncolors;
255✔
2262
    palette->entry_count = (unsigned int)ncolors;
255✔
2263

2264
end:
255✔
2265
    if (!parallel_active && logger != NULL) {
255!
2266
        int last_row;
×
2267

2268
        last_row = height > 0 ? height - 1 : 0;
×
2269
        sixel_logger_logf(logger,
×
2270
                          "worker",
2271
                          "dither",
2272
                          "finish",
2273
                          0,
2274
                          last_row,
2275
                          0,
2276
                          height,
2277
                          0,
2278
                          height,
2279
                          "serial status=%d",
2280
                          status);
2281
    }
2282
    if (normalized_pixels != NULL) {
255!
2283
        sixel_allocator_free(dither->allocator, normalized_pixels);
×
2284
    }
2285
    if (float_pipeline_pixels != NULL && owns_float_pipeline) {
255!
2286
        sixel_allocator_free(dither->allocator, float_pipeline_pixels);
×
2287
    }
2288
    sixel_dither_unref(dither);
255✔
2289
    dither->pipeline_index_buffer = NULL;
255✔
2290
    dither->pipeline_index_owned = 0;
255✔
2291
    dither->pipeline_index_size = 0;
255✔
2292
    dither->pipeline_parallel_active = 0;
255✔
2293
    dither->pipeline_band_height = 0;
255✔
2294
    dither->pipeline_band_overlap = 0;
255✔
2295
    dither->pipeline_dither_threads = 0;
255✔
2296
    dither->pipeline_image_height = 0;
255✔
2297
    dither->pipeline_logger = NULL;
255✔
2298
    return dest;
255✔
2299
}
2300

2301

2302
#if HAVE_TESTS
2303
static int
2304
test1(void)
×
2305
{
2306
    sixel_dither_t *dither = NULL;
×
2307
    int nret = EXIT_FAILURE;
×
2308
    SIXELSTATUS status;
×
2309

2310
    status = sixel_dither_new(&dither, 2, NULL);
×
2311
    if (dither == NULL || status != SIXEL_OK) {
×
2312
        goto error;
×
2313
    }
2314
    sixel_dither_ref(dither);
×
2315
    sixel_dither_unref(dither);
×
2316
    sixel_dither_unref(dither);
×
2317
    nret = EXIT_SUCCESS;
×
2318

2319
error:
×
2320
    sixel_dither_unref(dither);
×
2321
    return nret;
×
2322
}
2323

2324
static int
2325
test2(void)
×
2326
{
2327
    sixel_dither_t *dither = NULL;
×
2328
    int nret = EXIT_FAILURE;
×
2329
    SIXELSTATUS status;
×
2330

2331
    status = sixel_dither_new(&dither, INT_MAX, NULL);
×
2332
    if (status != SIXEL_BAD_INPUT || dither != NULL) {
×
2333
        goto error;
×
2334
    }
2335

2336
    nret = EXIT_SUCCESS;
2337

2338
error:
×
2339
    return nret;
×
2340
}
2341

2342
/* ensure the default pixelformat keeps the legacy 8bit path */
2343
static int
2344
test_float_pixelformat_defaults_to_8bit(void)
×
2345
{
2346
    sixel_dither_t *dither = NULL;
×
2347
    int nret = EXIT_FAILURE;
×
2348
    SIXELSTATUS status;
×
2349

2350
    sixel_palette_tests_reset_last_engine();
×
2351

2352
    status = sixel_dither_new(&dither, 4, NULL);
×
2353
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2354
        goto error;
×
2355
    }
2356
    if (dither->prefer_float32 != 0) {
×
2357
        goto error;
×
2358
    }
2359

2360
    nret = EXIT_SUCCESS;
2361

2362
error:
×
2363
    sixel_dither_unref(dither);
×
2364
    sixel_palette_tests_reset_last_engine();
×
2365
    return nret;
×
2366
}
2367

2368
/* ensure pixelformat setters toggle the float32 preference */
2369
static int
2370
test_float_pixelformat_sets_flag(void)
×
2371
{
2372
    sixel_dither_t *dither;
×
2373
    SIXELSTATUS status;
×
2374
    int nret;
×
2375

2376
    dither = NULL;
×
2377
    status = SIXEL_FALSE;
×
2378
    nret = EXIT_FAILURE;
×
2379
    sixel_palette_tests_reset_last_engine();
×
2380

2381
    status = sixel_dither_new(&dither, 4, NULL);
×
2382
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2383
        goto error;
×
2384
    }
2385
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGBFLOAT32);
×
2386
    if (dither->prefer_float32 == 0) {
×
2387
        goto error;
2388
    }
2389
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGB888);
×
2390
    if (dither->prefer_float32 != 0) {
×
2391
        goto error;
2392
    }
2393

2394
    nret = EXIT_SUCCESS;
2395

2396
error:
×
2397
    sixel_dither_unref(dither);
×
2398
    sixel_palette_tests_reset_last_engine();
×
2399
    return nret;
×
2400
}
2401

2402
/* ensure the float32 route dispatches the RGBFLOAT32 quantizer */
2403
static int
2404
test_float_pixelformat_enables_quantizer(void)
×
2405
{
2406
    static unsigned char const pixels[] = {
×
2407
        0x00, 0x00, 0x00,
2408
        0xff, 0xff, 0xff,
2409
    };
2410
    sixel_dither_t *dither = NULL;
×
2411
    SIXELSTATUS status;
×
2412
    int nret;
×
2413

2414
    nret = EXIT_FAILURE;
×
2415
    sixel_palette_tests_reset_last_engine();
×
2416

2417
    status = sixel_dither_new(&dither, 2, NULL);
×
2418
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2419
        goto error;
×
2420
    }
2421
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGBFLOAT32);
×
2422
    dither->quantize_model = SIXEL_QUANTIZE_MODEL_MEDIANCUT;
×
2423

2424
    status = sixel_dither_initialize(dither,
×
2425
                                     (unsigned char *)pixels,
2426
                                     2,
2427
                                     1,
2428
                                     SIXEL_PIXELFORMAT_RGBFLOAT32,
2429
                                     SIXEL_LARGE_AUTO,
2430
                                     SIXEL_REP_AUTO,
2431
                                     SIXEL_QUALITY_AUTO);
2432
    if (SIXEL_FAILED(status)) {
×
2433
        goto error;
×
2434
    }
2435
    if (!sixel_palette_tests_last_engine_requires_float32()) {
×
2436
        goto error;
×
2437
    }
2438
    if (sixel_palette_tests_last_engine_model()
×
2439
            != SIXEL_QUANTIZE_MODEL_MEDIANCUT) {
2440
        goto error;
×
2441
    }
2442

2443
    nret = EXIT_SUCCESS;
2444

2445
error:
×
2446
    sixel_dither_unref(dither);
×
2447
    sixel_palette_tests_reset_last_engine();
×
2448
    return nret;
×
2449
}
2450

2451
/* ensure explicit Heckbert requests also take the float32 route */
2452
static int
2453
test_float_pixelformat_explicit_mediancut_dispatch(void)
×
2454
{
2455
    static unsigned char const pixels[] = {
×
2456
        0x80, 0x20, 0x20,
2457
        0x20, 0x80, 0x20,
2458
    };
2459
    sixel_dither_t *dither = NULL;
×
2460
    SIXELSTATUS status;
×
2461
    int nret;
×
2462

2463
    nret = EXIT_FAILURE;
×
2464
    sixel_palette_tests_reset_last_engine();
×
2465

2466
    status = sixel_dither_new(&dither, 2, NULL);
×
2467
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2468
        goto error;
×
2469
    }
2470
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGBFLOAT32);
×
2471
    dither->quantize_model = SIXEL_QUANTIZE_MODEL_MEDIANCUT;
×
2472

2473
    status = sixel_dither_initialize(dither,
×
2474
                                     (unsigned char *)pixels,
2475
                                     2,
2476
                                     1,
2477
                                     SIXEL_PIXELFORMAT_RGBFLOAT32,
2478
                                     SIXEL_LARGE_AUTO,
2479
                                     SIXEL_REP_AUTO,
2480
                                     SIXEL_QUALITY_AUTO);
2481
    if (SIXEL_FAILED(status)) {
×
2482
        goto error;
×
2483
    }
2484
    if (!sixel_palette_tests_last_engine_requires_float32()) {
×
2485
        goto error;
×
2486
    }
2487
    if (sixel_palette_tests_last_engine_model()
×
2488
            != SIXEL_QUANTIZE_MODEL_MEDIANCUT) {
2489
        goto error;
×
2490
    }
2491

2492
    nret = EXIT_SUCCESS;
2493

2494
error:
×
2495
    sixel_dither_unref(dither);
×
2496
    sixel_palette_tests_reset_last_engine();
×
2497
    return nret;
×
2498
}
2499

2500
/* ensure the float32 diffusion route is exercised for FS dithering */
2501
static int
2502
test_float_fs_diffusion_dispatch(void)
×
2503
{
2504
    static float pixels[] = {
×
2505
        0.10f, 0.20f, 0.30f,
2506
        0.85f, 0.60f, 0.40f,
2507
    };
2508
    sixel_dither_t *dither;
×
2509
    sixel_index_t *indexes;
×
2510
    SIXELSTATUS status;
×
2511
    int nret;
×
2512

2513
    dither = NULL;
×
2514
    indexes = NULL;
×
2515
    nret = EXIT_FAILURE;
×
2516
    sixel_palette_tests_reset_last_engine();
×
2517
    sixel_dither_diffusion_tests_reset_float32_hits();
×
2518

2519
    status = sixel_dither_new(&dither, 2, NULL);
×
2520
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2521
        goto error;
×
2522
    }
2523
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGBFLOAT32);
×
2524
    sixel_dither_set_diffusion_type(dither, SIXEL_DIFFUSE_FS);
×
2525
    sixel_dither_set_diffusion_carry(dither, SIXEL_CARRY_DISABLE);
×
2526

2527
    status = sixel_dither_initialize(dither,
×
2528
                                     (unsigned char *)pixels,
2529
                                     2,
2530
                                     1,
2531
                                     SIXEL_PIXELFORMAT_RGBFLOAT32,
2532
                                     SIXEL_LARGE_AUTO,
2533
                                     SIXEL_REP_AUTO,
2534
                                     SIXEL_QUALITY_AUTO);
2535
    if (SIXEL_FAILED(status)) {
×
2536
        goto error;
×
2537
    }
2538

2539
    indexes = sixel_dither_apply_palette(dither,
×
2540
                                         (unsigned char *)pixels,
2541
                                         2,
2542
                                         1);
2543
    if (indexes == NULL) {
×
2544
        goto error;
×
2545
    }
2546
    if (sixel_dither_diffusion_tests_float32_hits() <= 0) {
×
2547
        goto error;
×
2548
    }
2549

2550
    nret = EXIT_SUCCESS;
2551

2552
error:
×
2553
    if (indexes != NULL && dither != NULL) {
×
2554
        sixel_allocator_free(dither->allocator, indexes);
×
2555
    }
2556
    sixel_dither_unref(dither);
×
2557
    sixel_palette_tests_reset_last_engine();
×
2558
    sixel_dither_diffusion_tests_reset_float32_hits();
×
2559
    return nret;
×
2560
}
2561

2562
/* helpers shared by the float32 palette validation tests */
2563
static unsigned char
2564
test_float_component_to_u8(float value)
×
2565
{
2566
    double channel;
×
2567

2568
    channel = (double)value;
×
2569
    if (channel < 0.0) {
×
2570
        channel = 0.0;
2571
    }
2572
    if (channel > 1.0) {
×
2573
        channel = 1.0;
×
2574
    }
2575
    channel = channel * 255.0 + 0.5;
×
2576
    if (channel < 0.0) {
×
2577
        channel = 0.0;
2578
    }
2579
    if (channel > 255.0) {
×
2580
        channel = 255.0;
2581
    }
2582

2583
    return (unsigned char)channel;
×
2584
}
2585

2586
static int
2587
test_palette_float_matches(float const *float_entries,
×
2588
                           size_t entry_count,
2589
                           unsigned char const *u8_entries,
2590
                           size_t u8_count)
2591
{
2592
    size_t index;
×
2593
    size_t base;
×
2594
    unsigned char red;
×
2595
    unsigned char green;
×
2596
    unsigned char blue;
×
2597

2598
    if (float_entries == NULL || u8_entries == NULL) {
×
2599
        return 0;
2600
    }
2601
    if (entry_count != u8_count) {
×
2602
        return 0;
2603
    }
2604
    if (entry_count == 0U) {
×
2605
        return 1;
2606
    }
2607

2608
    for (index = 0U; index < entry_count; ++index) {
×
2609
        base = index * 3U;
×
2610
        red = test_float_component_to_u8(float_entries[base + 0U]);
×
2611
        green = test_float_component_to_u8(float_entries[base + 1U]);
×
2612
        blue = test_float_component_to_u8(float_entries[base + 2U]);
×
2613
        if (red != u8_entries[base + 0U]
×
2614
                || green != u8_entries[base + 1U]
×
2615
                || blue != u8_entries[base + 2U]) {
×
2616
            return 0;
2617
        }
2618
    }
2619

2620
    return 1;
2621
}
2622

2623
/* ensure palette optimization copies float buffers in lockstep */
2624
static int
2625
test_float_optimize_palette_sync(void)
×
2626
{
2627
    static float pixels[] = {
×
2628
        1.0f, 0.9f, 0.0f,
2629
        1.0f, 0.9f, 0.0f,
2630
        0.1f, 0.2f, 0.9f,
2631
        0.1f, 0.2f, 0.9f,
2632
    };
2633
    sixel_dither_t *dither;
×
2634
    sixel_palette_t *palette_obj;
×
2635
    SIXELSTATUS status;
×
2636
    sixel_index_t *indexes;
×
2637
    float *palette_float_copy;
×
2638
    unsigned char *palette_u8_copy;
×
2639
    size_t palette_float_count;
×
2640
    size_t palette_u8_count;
×
2641
    int nret;
×
2642

2643
    dither = NULL;
×
2644
    palette_obj = NULL;
×
2645
    status = SIXEL_FALSE;
×
2646
    indexes = NULL;
×
2647
    palette_float_copy = NULL;
×
2648
    palette_u8_copy = NULL;
×
2649
    palette_float_count = 0U;
×
2650
    palette_u8_count = 0U;
×
2651
    nret = EXIT_FAILURE;
×
2652

2653
    sixel_palette_tests_reset_last_engine();
×
2654

2655
    status = sixel_dither_new(&dither, 4, NULL);
×
2656
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2657
        goto error;
×
2658
    }
2659
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGBFLOAT32);
×
2660
    sixel_dither_set_diffusion_type(dither, SIXEL_DIFFUSE_FS);
×
2661
    sixel_dither_set_diffusion_carry(dither, SIXEL_CARRY_DISABLE);
×
2662
    sixel_dither_set_optimize_palette(dither, 1);
×
2663

2664
    status = sixel_dither_initialize(dither,
×
2665
                                     (unsigned char *)pixels,
2666
                                     2,
2667
                                     2,
2668
                                     SIXEL_PIXELFORMAT_RGBFLOAT32,
2669
                                     SIXEL_LARGE_AUTO,
2670
                                     SIXEL_REP_AUTO,
2671
                                     SIXEL_QUALITY_AUTO);
2672
    if (SIXEL_FAILED(status)) {
×
2673
        goto error;
×
2674
    }
2675

2676
    indexes = sixel_dither_apply_palette(dither,
×
2677
                                         (unsigned char *)pixels,
2678
                                         2,
2679
                                         2);
2680
    if (indexes == NULL) {
×
2681
        goto error;
×
2682
    }
2683

2684
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
×
2685
    if (SIXEL_FAILED(status) || palette_obj == NULL) {
×
2686
        goto error;
×
2687
    }
2688

2689
    status = sixel_palette_copy_entries_float32(
×
2690
        palette_obj,
2691
        &palette_float_copy,
2692
        &palette_float_count,
2693
        SIXEL_PIXELFORMAT_RGBFLOAT32,
2694
        dither->allocator);
2695
    if (SIXEL_FAILED(status) || palette_float_copy == NULL) {
×
2696
        goto error;
×
2697
    }
2698

2699
    status = sixel_palette_copy_entries_8bit(
×
2700
        palette_obj,
2701
        &palette_u8_copy,
2702
        &palette_u8_count,
2703
        SIXEL_PIXELFORMAT_RGB888,
2704
        dither->allocator);
2705
    if (SIXEL_FAILED(status) || palette_u8_copy == NULL) {
×
2706
        goto error;
×
2707
    }
2708

2709
    if (palette_float_count == 0U || palette_u8_count == 0U) {
×
2710
        goto error;
×
2711
    }
2712
    if (!test_palette_float_matches(palette_float_copy,
×
2713
                                    palette_float_count,
2714
                                    palette_u8_copy,
2715
                                    palette_u8_count)) {
2716
        goto error;
×
2717
    }
2718

2719
    nret = EXIT_SUCCESS;
2720

2721
error:
×
2722
    if (indexes != NULL && dither != NULL) {
×
2723
        sixel_allocator_free(dither->allocator, indexes);
×
2724
    }
2725
    if (palette_obj != NULL) {
×
2726
        sixel_palette_unref(palette_obj);
×
2727
    }
2728
    if (palette_float_copy != NULL && dither != NULL) {
×
2729
        sixel_allocator_free(dither->allocator, palette_float_copy);
×
2730
    }
2731
    if (palette_u8_copy != NULL && dither != NULL) {
×
2732
        sixel_allocator_free(dither->allocator, palette_u8_copy);
×
2733
    }
2734
    sixel_dither_unref(dither);
×
2735
    sixel_palette_tests_reset_last_engine();
×
2736
    return nret;
×
2737
}
2738

2739
/* ensure float32 palettes remain accessible alongside legacy buffers */
2740
static int
2741
test_float_palette_accessor_returns_data(void)
×
2742
{
2743
    static float pixels[] = {
×
2744
        1.0f, 0.0f, 0.0f,
2745
        0.0f, 1.0f, 0.0f,
2746
    };
2747
    sixel_dither_t *dither;
×
2748
    SIXELSTATUS status;
×
2749
    int nret;
×
2750
    int colors;
×
2751
    int red_float;
×
2752
    int green_float;
×
2753
    int red_u8;
×
2754
    int green_u8;
×
2755
    int index;
×
2756
    float *entry_float;
×
2757
    unsigned char *entry_u8;
×
2758
    sixel_palette_t *palette_obj;
×
2759
    float *palette_float_copy;
×
2760
    unsigned char *palette_u8_copy;
×
2761
    size_t palette_float_count;
×
2762
    size_t palette_u8_count;
×
2763

2764
    dither = NULL;
×
2765
    status = SIXEL_FALSE;
×
2766
    palette_float_copy = NULL;
×
2767
    palette_u8_copy = NULL;
×
2768
    palette_obj = NULL;
×
2769
    palette_float_count = 0U;
×
2770
    palette_u8_count = 0U;
×
2771
    nret = EXIT_FAILURE;
×
2772
    colors = 0;
×
2773
    red_float = 0;
×
2774
    green_float = 0;
×
2775
    red_u8 = 0;
×
2776
    green_u8 = 0;
×
2777
    index = 0;
×
2778
    entry_float = NULL;
×
2779
    entry_u8 = NULL;
×
2780

2781
    sixel_palette_tests_reset_last_engine();
×
2782

2783
    status = sixel_dither_new(&dither, 2, NULL);
×
2784
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2785
        goto error;
×
2786
    }
2787
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGBFLOAT32);
×
2788
    status = sixel_dither_initialize(dither,
×
2789
                                     (unsigned char *)pixels,
2790
                                     2,
2791
                                     1,
2792
                                     SIXEL_PIXELFORMAT_RGBFLOAT32,
2793
                                     SIXEL_LARGE_AUTO,
2794
                                     SIXEL_REP_AUTO,
2795
                                     SIXEL_QUALITY_AUTO);
2796
    if (SIXEL_FAILED(status)) {
×
2797
        goto error;
×
2798
    }
2799

2800
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
×
2801
    if (SIXEL_FAILED(status)) {
×
2802
        goto error;
×
2803
    }
2804
    status = sixel_palette_copy_entries_float32(
×
2805
        palette_obj,
2806
        &palette_float_copy,
2807
        &palette_float_count,
2808
        SIXEL_PIXELFORMAT_RGBFLOAT32,
2809
        dither->allocator);
2810
    if (SIXEL_FAILED(status) || palette_float_copy == NULL) {
×
2811
        goto error;
×
2812
    }
2813
    status = sixel_palette_copy_entries_8bit(
×
2814
        palette_obj,
2815
        &palette_u8_copy,
2816
        &palette_u8_count,
2817
        SIXEL_PIXELFORMAT_RGB888,
2818
        dither->allocator);
2819
    if (SIXEL_FAILED(status) || palette_u8_copy == NULL) {
×
2820
        goto error;
×
2821
    }
2822
    sixel_palette_unref(palette_obj);
×
2823
    palette_obj = NULL;
×
2824
    colors = sixel_dither_get_num_of_palette_colors(dither);
×
2825
    if (colors != 2) {
×
2826
        goto error;
×
2827
    }
2828

2829
    for (index = 0; index < colors; ++index) {
×
2830
        entry_float = palette_float_copy + (size_t)index * 3U;
×
2831
        if (entry_float[0] > 0.9f && entry_float[1] < 0.1f
×
2832
                && entry_float[2] < 0.1f) {
×
2833
            red_float = 1;
×
2834
        }
2835
        if (entry_float[1] > 0.9f && entry_float[0] < 0.1f
×
2836
                && entry_float[2] < 0.1f) {
×
2837
            green_float = 1;
×
2838
        }
2839

2840
        entry_u8 = palette_u8_copy + (size_t)index * 3U;
×
2841
        if (entry_u8[0] > 200U && entry_u8[1] < 30U && entry_u8[2] < 30U) {
×
2842
            red_u8 = 1;
×
2843
        }
2844
        if (entry_u8[1] > 200U && entry_u8[0] < 30U && entry_u8[2] < 30U) {
×
2845
            green_u8 = 1;
×
2846
        }
2847
    }
2848
    if (!red_float || !green_float || !red_u8 || !green_u8) {
×
2849
        goto error;
×
2850
    }
2851

2852
    nret = EXIT_SUCCESS;
2853

2854
error:
×
2855
    sixel_dither_unref(dither);
×
2856
    sixel_palette_tests_reset_last_engine();
×
2857
    return nret;
×
2858
}
2859

2860
/* ensure the float accessor reports NULL for legacy 8bit runs */
2861
static int
2862
test_float_palette_accessor_legacy_null(void)
×
2863
{
2864
    static unsigned char const pixels[] = {
×
2865
        0xff, 0x00, 0x00,
2866
        0x00, 0xff, 0x00,
2867
    };
2868
    sixel_dither_t *dither;
×
2869
    SIXELSTATUS status;
×
2870
    sixel_palette_t *palette_obj;
×
2871
    float *palette_float_copy;
×
2872
    unsigned char *palette_u8_copy;
×
2873
    size_t palette_float_count;
×
2874
    size_t palette_u8_count;
×
2875
    int nret;
×
2876

2877
    dither = NULL;
×
2878
    status = SIXEL_FALSE;
×
2879
    palette_obj = NULL;
×
2880
    palette_float_copy = NULL;
×
2881
    palette_u8_copy = NULL;
×
2882
    palette_float_count = 0U;
×
2883
    palette_u8_count = 0U;
×
2884
    nret = EXIT_FAILURE;
×
2885
    sixel_palette_tests_reset_last_engine();
×
2886

2887
    status = sixel_dither_new(&dither, 2, NULL);
×
2888
    if (SIXEL_FAILED(status) || dither == NULL) {
×
2889
        goto error;
×
2890
    }
2891
    sixel_dither_set_pixelformat(dither, SIXEL_PIXELFORMAT_RGB888);
×
2892
    status = sixel_dither_initialize(dither,
×
2893
                                     (unsigned char *)pixels,
2894
                                     2,
2895
                                     1,
2896
                                     SIXEL_PIXELFORMAT_RGB888,
2897
                                     SIXEL_LARGE_AUTO,
2898
                                     SIXEL_REP_AUTO,
2899
                                     SIXEL_QUALITY_AUTO);
2900
    if (SIXEL_FAILED(status)) {
×
2901
        goto error;
×
2902
    }
2903

2904
    status = sixel_dither_get_quantized_palette(dither, &palette_obj);
×
2905
    if (SIXEL_FAILED(status)) {
×
2906
        goto error;
×
2907
    }
2908
    status = sixel_palette_copy_entries_float32(
×
2909
        palette_obj,
2910
        &palette_float_copy,
2911
        &palette_float_count,
2912
        SIXEL_PIXELFORMAT_RGBFLOAT32,
2913
        dither->allocator);
2914
    if (SIXEL_FAILED(status)) {
×
2915
        goto error;
×
2916
    }
2917
    if (palette_float_copy != NULL) {
×
2918
        goto error;
×
2919
    }
2920
    status = sixel_palette_copy_entries_8bit(
×
2921
        palette_obj,
2922
        &palette_u8_copy,
2923
        &palette_u8_count,
2924
        SIXEL_PIXELFORMAT_RGB888,
2925
        dither->allocator);
2926
    if (SIXEL_FAILED(status) || palette_u8_copy == NULL) {
×
2927
        goto error;
×
2928
    }
2929
    sixel_palette_unref(palette_obj);
×
2930
    palette_obj = NULL;
×
2931

2932
    nret = EXIT_SUCCESS;
×
2933

2934
error:
×
2935
    if (palette_obj != NULL) {
×
2936
        sixel_palette_unref(palette_obj);
×
2937
    }
2938
    if (palette_float_copy != NULL && dither != NULL) {
×
2939
        sixel_allocator_free(dither->allocator, palette_float_copy);
×
2940
    }
2941
    if (palette_u8_copy != NULL && dither != NULL) {
×
2942
        sixel_allocator_free(dither->allocator, palette_u8_copy);
×
2943
    }
2944
    sixel_dither_unref(dither);
×
2945
    sixel_palette_tests_reset_last_engine();
×
2946
    return nret;
×
2947
}
2948

2949

2950
SIXELAPI int
2951
sixel_dither_tests_main(void)
×
2952
{
2953
    int nret = EXIT_FAILURE;
×
2954
    size_t i;
×
2955
    typedef int (* testcase)(void);
×
2956

2957
    static testcase const testcases[] = {
×
2958
        test1,
2959
        test2,
2960
        test_float_pixelformat_defaults_to_8bit,
2961
        test_float_pixelformat_sets_flag,
2962
        test_float_pixelformat_enables_quantizer,
2963
        test_float_pixelformat_explicit_mediancut_dispatch,
2964
        test_float_fs_diffusion_dispatch,
2965
        test_float_optimize_palette_sync,
2966
        test_float_palette_accessor_returns_data,
2967
        test_float_palette_accessor_legacy_null,
2968
    };
2969

2970
    for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
×
2971
        nret = testcases[i]();
×
2972
        if (nret != EXIT_SUCCESS) {
×
2973
            goto error;
×
2974
        }
2975
    }
2976

2977
    nret = EXIT_SUCCESS;
2978

2979
error:
×
2980
    return nret;
×
2981
}
2982
#endif  /* HAVE_TESTS */
2983

2984
/* emacs Local Variables:      */
2985
/* emacs mode: c               */
2986
/* emacs tab-width: 4          */
2987
/* emacs indent-tabs-mode: nil */
2988
/* emacs c-basic-offset: 4     */
2989
/* emacs End:                  */
2990
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2991
/* 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