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

saitoha / libsixel / 21616267374

03 Feb 2026 03:53AM UTC coverage: 77.478% (-0.02%) from 77.499%
21616267374

push

github

saitoha
build: fix for mingw64-make

23678 of 50520 branches covered (46.87%)

37669 of 48619 relevant lines covered (77.48%)

40017555.73 hits per line

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

85.81
/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
#if defined(HAVE_CONFIG_H)
26
#include "config.h"
27
#endif
28

29
#include <stdlib.h>
30
#include <stdio.h>
31

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

48
#include "dither.h"
49
#include "palette.h"
50
#include "compat_stub.h"
51
#include "lookup-common.h"
52
#include "timer.h"
53
#include "dither-common-pipeline.h"
54
#include "dither-positional-8bit.h"
55
#include "dither-positional-float32.h"
56
#include "dither-fixed-8bit.h"
57
#include "dither-fixed-float32.h"
58
#include "dither-varcoeff-8bit.h"
59
#include "dither-varcoeff-float32.h"
60
#include "dither-internal.h"
61
#include "filter-lookup.h"
62
#include "logger.h"
63
#include "pixelformat.h"
64
#include "sixel_atomic.h"
65
#if SIXEL_ENABLE_THREADS
66
# include "threadpool.h"
67
#endif
68
#include <sixel.h>
69

70

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

88
    status = SIXEL_BAD_ARGUMENT;
×
89
    buffer = NULL;
×
90
    bytes = 0U;
×
91
    index = 0U;
×
92
    base = 0U;
×
93

94
    if (out_pixels == NULL || rgb888 == NULL || allocator == NULL) {
×
95
        return status;
96
    }
97

98
    *out_pixels = NULL;
×
99
    status = SIXEL_OK;
×
100
    if (pixel_total == 0U) {
×
101
        return status;
102
    }
103

104
    if (pixel_total > SIZE_MAX / (3U * sizeof(float))) {
×
105
        return SIXEL_BAD_INPUT;
106
    }
107
    bytes = pixel_total * 3U * sizeof(float);
×
108

109
    buffer = (float *)sixel_allocator_malloc(allocator, bytes);
×
110
    if (buffer == NULL) {
×
111
        return SIXEL_BAD_ALLOCATION;
112
    }
113

114
    for (index = 0U; index < pixel_total; ++index) {
×
115
        base = index * 3U;
×
116
        buffer[base + 0U] = (float)rgb888[base + 0U] / 255.0f;
×
117
        buffer[base + 1U] = (float)rgb888[base + 1U] / 255.0f;
×
118
        buffer[base + 2U] = (float)rgb888[base + 2U] / 255.0f;
×
119
    }
120

121
    *out_pixels = buffer;
×
122
    return status;
×
123
}
124

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

136
    if (dither == NULL) {
3,367!
137
        return 0;
138
    }
139
    if (dither->prefer_float32 == 0) {
3,367✔
140
        return 0;
1,111✔
141
    }
142
    if (dither->method_for_carry == SIXEL_CARRY_ENABLE) {
1,105✔
143
        return 0;
12✔
144
    }
145

146
    method = dither->method_for_diffuse;
1,079✔
147
    switch (method) {
1,079!
148
    case SIXEL_DIFFUSE_NONE:
149
    case SIXEL_DIFFUSE_ATKINSON:
150
    case SIXEL_DIFFUSE_FS:
151
    case SIXEL_DIFFUSE_JAJUNI:
152
    case SIXEL_DIFFUSE_STUCKI:
153
    case SIXEL_DIFFUSE_BURKES:
154
    case SIXEL_DIFFUSE_SIERRA1:
155
    case SIXEL_DIFFUSE_SIERRA2:
156
    case SIXEL_DIFFUSE_SIERRA3:
157
        return 1;
366✔
158
    case SIXEL_DIFFUSE_A_DITHER:
159
    case SIXEL_DIFFUSE_X_DITHER:
160
    case SIXEL_DIFFUSE_BLUENOISE_DITHER:
161
    case SIXEL_DIFFUSE_LSO2:
162
        return 1;
132✔
163
    default:
164
        return 0;
165
    }
166
}
1,618✔
167

168

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

173

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

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

182

183
static const unsigned char pal_gray_2bit[] = {
184
    0x00, 0x00, 0x00, 0x55, 0x55, 0x55, 0xaa, 0xaa, 0xaa, 0xff, 0xff, 0xff
185
};
186

187

188
static const unsigned char pal_gray_4bit[] = {
189
    0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33,
190
    0x44, 0x44, 0x44, 0x55, 0x55, 0x55, 0x66, 0x66, 0x66, 0x77, 0x77, 0x77,
191
    0x88, 0x88, 0x88, 0x99, 0x99, 0x99, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb,
192
    0xcc, 0xcc, 0xcc, 0xdd, 0xdd, 0xdd, 0xee, 0xee, 0xee, 0xff, 0xff, 0xff
193
};
194

195

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

263

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

331

332
#if defined(_MSC_VER)
333
# define SIXEL_TLS __declspec(thread)
334
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L \
335
    && !defined(__STDC_NO_THREADS__)
336
# define SIXEL_TLS _Thread_local
337
#elif defined(__GNUC__)
338
# define SIXEL_TLS __thread
339
#else
340
# define SIXEL_TLS
341
#endif
342

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

350
#undef SIXEL_TLS
351

352
/* lookup closest color from palette with "normal" strategy */
353
static int
354
lookup_normal(unsigned char const * const pixel,
14,146,564✔
355
              int const depth,
356
              unsigned char const * const palette,
357
              int const reqcolor,
358
              unsigned short * const cachetable,
359
              int const complexion)
360
{
361
    int result;
10,881,960✔
362
    int diff;
10,881,960✔
363
    int r;
10,881,960✔
364
    int i;
10,881,960✔
365
    int n;
10,881,960✔
366
    int distant;
10,881,960✔
367

368
    result = (-1);
14,146,564✔
369
    diff = INT_MAX;
14,146,564✔
370

371
    /* don't use cachetable in 'normal' strategy */
372
    (void) cachetable;
14,146,552✔
373

374
    for (i = 0; i < reqcolor; i++) {
3,635,650,084✔
375
        distant = 0;
3,621,503,520✔
376
        r = pixel[0] - palette[i * depth + 0];
3,621,503,520✔
377
        distant += r * r * complexion;
3,621,503,520✔
378
        for (n = 1; n < depth; ++n) {
10,864,510,560✔
379
            r = pixel[n] - palette[i * depth + n];
7,243,007,040✔
380
            distant += r * r;
7,243,007,040✔
381
        }
3,342,926,272✔
382
        if (distant < diff) {
3,621,503,520✔
383
            diff = distant;
210,079,159✔
384
            result = i;
210,079,159✔
385
        }
91,482,208✔
386
    }
1,671,463,136✔
387

388
    return result;
17,411,152✔
389
}
3,264,588✔
390

391

392
/*
393
 * Shared fast lookup flow handled by the lut module.  The palette lookup now
394
 * delegates to sixel_lut_map_pixel() so policy-specific caches and the
395
 * certification tree stay encapsulated inside src/lookup-common.c.
396
 */
397

398
static int
399
lookup_fast_lut(unsigned char const * const pixel,
×
400
                int const depth,
401
                unsigned char const * const palette,
402
                int const reqcolor,
403
                unsigned short * const cachetable,
404
                int const complexion)
405
{
406
    (void)depth;
407
    (void)palette;
408
    (void)reqcolor;
409
    (void)cachetable;
410
    (void)complexion;
411

412
    if (dither_lut_context == NULL) {
×
413
        return 0;
414
    }
415

416
    return sixel_lut_map_pixel(dither_lut_context, pixel);
×
417
}
418

419

420
static int
421
lookup_mono_darkbg(unsigned char const * const pixel,
7,500,740✔
422
                   int const depth,
423
                   unsigned char const * const palette,
424
                   int const reqcolor,
425
                   unsigned short * const cachetable,
426
                   int const complexion)
427
{
428
    int n;
5,769,800✔
429
    int distant;
5,769,800✔
430

431
    /* unused */ (void) palette;
7,500,740✔
432
    /* unused */ (void) cachetable;
7,500,740✔
433
    /* unused */ (void) complexion;
7,500,740✔
434

435
    distant = 0;
7,500,740✔
436
    for (n = 0; n < depth; ++n) {
30,002,960✔
437
        distant += pixel[n];
22,502,220✔
438
    }
10,385,640✔
439
    return distant >= 128 * reqcolor ? 1: 0;
9,231,680✔
440
}
1,730,940✔
441

442

443
static int
444
lookup_mono_lightbg(unsigned char const * const pixel,
3,510,000✔
445
                    int const depth,
446
                    unsigned char const * const palette,
447
                    int const reqcolor,
448
                    unsigned short * const cachetable,
449
                    int const complexion)
450
{
451
    int n;
2,700,000✔
452
    int distant;
2,700,000✔
453

454
    /* unused */ (void) palette;
3,510,000✔
455
    /* unused */ (void) cachetable;
3,510,000✔
456
    /* unused */ (void) complexion;
3,510,000✔
457

458
    distant = 0;
3,510,000✔
459
    for (n = 0; n < depth; ++n) {
14,040,000✔
460
        distant += pixel[n];
10,530,000✔
461
    }
4,860,000✔
462
    return distant < 128 * reqcolor ? 1: 0;
4,320,000✔
463
}
810,000✔
464

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

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

571
    if (dither != NULL && dither->palette != NULL) {
3,421!
572
        sixel_palette_t *palette_object;
2,613✔
573
        int float_components;
2,613✔
574

575
        palette_object = dither->palette;
3,421✔
576
        if (palette_object->entries_float32 != NULL
3,421!
577
                && palette_object->float_depth > 0) {
1,636!
578
            float_components = palette_object->float_depth
×
579
                / (int)sizeof(float);
580
            if (float_components > 0
×
581
                    && (size_t)float_components <= SIXEL_MAX_CHANNELS) {
×
582
                context.palette_float = palette_object->entries_float32;
×
583
                context.float_depth = float_components;
×
584
                context.new_palette_float = new_palette_float;
×
585
                palette_float = palette_object->entries_float32;
×
586
                palette_float_depth = palette_object->float_depth;
×
587
            }
588
        }
589
    }
1,636✔
590

591
    if (reqcolor < 1) {
3,421!
592
        status = SIXEL_BAD_ARGUMENT;
×
593
        sixel_helper_set_additional_message(
×
594
            "sixel_dither_map_pixels: "
595
            "a bad argument is detected, reqcolor < 0.");
596
        goto end;
×
597
    }
598

599
    use_varerr = (depth == 3
5,057✔
600
                  && methodForDiffuse == SIXEL_DIFFUSE_LSO2);
3,421!
601
    use_positional = (methodForDiffuse == SIXEL_DIFFUSE_A_DITHER
5,009✔
602
                      || methodForDiffuse == SIXEL_DIFFUSE_X_DITHER
1,639✔
603
                      || methodForDiffuse == SIXEL_DIFFUSE_BLUENOISE_DITHER);
3,424✔
604
    carry_mode = (methodForCarry == SIXEL_CARRY_ENABLE)
4,129✔
605
               ? SIXEL_CARRY_ENABLE
606
               : SIXEL_CARRY_DISABLE;
1,785✔
607
    context.method_for_diffuse = methodForDiffuse;
3,421✔
608
    context.method_for_scan = methodForScan;
3,421✔
609
    context.method_for_carry = carry_mode;
3,421✔
610

611
    if (reqcolor == 2) {
3,421✔
612
        sum1 = 0;
76✔
613
        sum2 = 0;
384✔
614
        for (n = 0; n < depth; ++n) {
612✔
615
            sum1 += palette[n];
459✔
616
        }
228✔
617
        for (n = depth; n < depth + depth; ++n) {
612✔
618
            sum2 += palette[n];
459✔
619
        }
228✔
620
        if (sum1 == 0 && sum2 == 255 * 3) {
153!
621
            f_lookup = lookup_mono_darkbg;
24✔
622
        } else if (sum1 == 255 * 3 && sum2 == 0) {
125✔
623
            f_lookup = lookup_mono_lightbg;
1,788✔
624
        }
6✔
625
    }
76✔
626
    if (foptimize && depth == 3 && f_lookup == lookup_normal) {
3,421!
627
        f_lookup = lookup_fast_lut;
3,345✔
628
    }
1,563✔
629
    if (lut_policy == SIXEL_LUT_POLICY_NONE) {
3,421✔
630
        f_lookup = lookup_normal;
26✔
631
    }
12✔
632

633
    if (f_lookup == lookup_fast_lut) {
3,421✔
634
        if (depth != 3) {
3,261!
635
            status = SIXEL_BAD_ARGUMENT;
×
636
            sixel_helper_set_additional_message(
×
637
                "sixel_dither_map_pixels: fast lookup requires RGB pixels.");
638
            goto end;
×
639
        }
640
        policy = lut_policy;
3,261✔
641
        if (policy != SIXEL_LUT_POLICY_CERTLUT
3,303!
642
            && policy != SIXEL_LUT_POLICY_5BIT
3,261!
643
            && policy != SIXEL_LUT_POLICY_6BIT
3,261!
644
            && policy != SIXEL_LUT_POLICY_EYTZINGER
3,261!
645
            && policy != SIXEL_LUT_POLICY_VPTE) {
1,612!
646
            policy = SIXEL_LUT_POLICY_6BIT;
647
        }
648
        shared_lut = 1;
3,310✔
649
        if (policy == SIXEL_LUT_POLICY_CERTLUT) {
1,612!
650
            shared_lut = sixel_lookup_env_shared_certlut();
×
651
        } else if (policy == SIXEL_LUT_POLICY_5BIT) {
3,261!
652
            shared_lut = sixel_lookup_env_shared_5bit();
×
653
        } else if (policy == SIXEL_LUT_POLICY_6BIT) {
3,261!
654
            shared_lut = sixel_lookup_env_shared_6bit();
×
655
        }
656
        if (lut != NULL && sixel_lookup_parallel_dither_active() != 0
3,261!
657
                && shared_lut == 0) {
1,650!
658
            /*
659
             * Caller requested thread-local CERTLUT/DENCELUT caches.
660
             * Drop the shared handle so each worker builds an isolated
661
             * instance instead of serializing on a mutex.
662
             */
663
            lut = NULL;
664
        }
665
        if (lut != NULL && sixel_lookup_parallel_dither_active() != 0
3,261!
666
                && shared_lut != 0) {
1,650!
667
            /*
668
             * Parallel palette application reuses the preconfigured LUT to
669
             * avoid rebuilding VPTE inside each worker.  The shared LUT is
670
             * immutable after setup, so workers only need a read-only handle
671
             * here.
672
             */
673
            active_lut = lut;
75✔
674
            manage_lut = 0;
75✔
675
        } else {
75✔
676
            lookup_config.palette = palette;
3,099✔
677
            lookup_config.palette_float = palette_float;
3,099✔
678
            lookup_config.depth = depth;
3,099✔
679
            lookup_config.float_depth = palette_float_depth;
3,099✔
680
            lookup_config.ncolors = reqcolor;
3,099✔
681
            lookup_config.complexion = complexion;
3,099✔
682
            lookup_config.method_for_largest = method_for_largest;
3,099✔
683
            lookup_config.lut_policy = policy;
3,099✔
684
            lookup_config.pixelformat = pixelformat;
3,099✔
685
            lookup_config.reuse_lut = lut;
3,099✔
686

687
            status = sixel_filter_lookup_build(&lookup_config,
3,099✔
688
                                               allocator,
1,488✔
689
                                               NULL,
690
                                               &lookup_result);
691
            if (SIXEL_FAILED(status)) {
3,099!
692
                goto end;
×
693
            }
694

695
            active_lut = lookup_result.lut;
3,099✔
696
            manage_lut = lookup_result.owned;
3,099✔
697
        }
698
        context.lut = active_lut;
3,261✔
699
        dither_lut_context = active_lut;
3,261✔
700
    }
1,563✔
701

702
    context.lookup = f_lookup;
3,421✔
703
    if (f_lookup == lookup_fast_lut
4,552✔
704
        && SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
3,334✔
705
        context.lookup_source_is_float = 1;
1,066✔
706
    }
492✔
707
    if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)
2,297!
708
        && context.palette_float != NULL
2,217!
709
        && context.float_depth >= context.depth
1,636!
710
        && f_lookup == lookup_normal) {
×
711
        context.prefer_palette_float_lookup = 1;
×
712
    }
713
    context.optimize_palette = foptimize_palette;
3,421✔
714
    context.complexion = complexion;
3,421✔
715

716
    if (use_positional) {
3,421✔
717
        if (context.pixels_float != NULL
406!
718
            && dither != NULL
272!
719
            && dither->prefer_float32 != 0) {
260!
720
            status = sixel_dither_apply_positional_float32(dither, &context);
260✔
721
            if (status == SIXEL_BAD_ARGUMENT) {
260!
722
                status = sixel_dither_apply_positional_8bit(dither, &context);
×
723
            }
724
        } else {
120✔
725
            status = sixel_dither_apply_positional_8bit(dither, &context);
26✔
726
        }
727
    } else if (use_varerr) {
3,267✔
728
        if (context.pixels_float != NULL
116!
729
            && dither != NULL
62!
730
            && dither->prefer_float32 != 0) {
26!
731
            status = sixel_dither_apply_varcoeff_float32(dither, &context);
26✔
732
            if (status == SIXEL_BAD_ARGUMENT) {
26!
733
                status = sixel_dither_apply_varcoeff_8bit(dither, &context);
×
734
            }
735
        } else {
12✔
736
            status = sixel_dither_apply_varcoeff_8bit(dither, &context);
78✔
737
        }
738
    } else {
48✔
739
        if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)
3,397!
740
            && context.pixels_float != NULL
1,883!
741
            && methodForCarry != SIXEL_CARRY_ENABLE
1,883✔
742
            && depth == 3
793!
743
            && dither != NULL
793!
744
            && dither->prefer_float32 != 0) {
793!
745
            /*
746
             * Float inputs can reuse the float32 renderer for every
747
             * fixed-weight kernel (FS, Sierra, Stucki, etc.) as long as
748
             * carry buffers are disabled.  The legacy 8bit path remains as
749
             * a fallback for unsupported argument combinations.
750
             */
751
            status = sixel_dither_apply_fixed_float32(dither, &context);
793✔
752
            if (status == SIXEL_BAD_ARGUMENT) {
793!
753
                status = sixel_dither_apply_fixed_8bit(dither, &context);
×
754
            }
755
        } else {
366✔
756
            status = sixel_dither_apply_fixed_8bit(dither, &context);
2,238✔
757
        }
758
    }
759
    if (SIXEL_FAILED(status)) {
3,421!
760
        goto end;
×
761
    }
762

763
    status = SIXEL_OK;
1,639✔
764

765
end:
1,785✔
766
    if (dither_lut_context != NULL && f_lookup == lookup_fast_lut) {
3,421!
767
        dither_lut_context = NULL;
3,261✔
768
    }
1,563✔
769
    if (manage_lut && active_lut != NULL) {
3,421!
770
        sixel_lut_unref(active_lut);
×
771
    }
772
    return status;
4,252✔
773
}
831✔
774

775
#if SIXEL_ENABLE_THREADS
776
typedef struct sixel_parallel_dither_plan {
777
    sixel_index_t *dest;
778
    unsigned char *pixels;
779
    sixel_palette_t *palette;
780
    sixel_lut_t *lut;
781
    sixel_allocator_t *allocator;
782
    sixel_dither_t *dither;
783
    size_t row_bytes;
784
    int width;
785
    int height;
786
    int band_height;
787
    int overlap;
788
    int method_for_diffuse;
789
    int method_for_scan;
790
    int method_for_carry;
791
    int optimize_palette;
792
    int optimize_palette_entries;
793
    int complexion;
794
    int lut_policy;
795
    int method_for_largest;
796
    int reqcolor;
797
    int pixelformat;
798
    int pin_threads;
799
    sixel_logger_t *logger;
800
} sixel_parallel_dither_plan_t;
801

802
/*
803
 * Dedicated worker state for dithering threads so it does not collide with
804
 * encoder worker bookkeeping when compiled as a single translation unit.
805
 */
806
typedef struct sixel_parallel_dither_state {
807
    sixel_lut_t *lut;
808
    int lut_initialized;
809
} sixel_parallel_dither_state_t;
810

811
static void
812
sixel_parallel_dither_cleanup(void *workspace)
813
{
814
    sixel_parallel_dither_state_t *state;
815

816
    state = (sixel_parallel_dither_state_t *)workspace;
817
    if (state == NULL) {
×
818
        return;
819
    }
820
    if (state->lut_initialized != 0 && state->lut != NULL) {
×
821
        /*
822
         * Each worker owns its private LUT instance when shared caches are
823
         * disabled.  Release it here so threadpool teardown can free the
824
         * workspace without leaking per-thread caches.  The mutex inside the
825
         * LUT was already removed during configuration for the private mode,
826
         * so this final drop is the only step needed to return allocator
827
         * ownership.
828
         */
829
        sixel_lut_unref(state->lut);
830
    }
831
}
×
832

833
static int
834
sixel_dither_parallel_worker(tp_job_t job,
108✔
835
                             void *userdata,
836
                             void *workspace)
837
{
838
    sixel_parallel_dither_plan_t *plan;
90✔
839
    unsigned char const *source;
90✔
840
    unsigned char *copy;
90✔
841
    size_t required;
90✔
842
    size_t offset;
90✔
843
    int band_index;
90✔
844
    int y0;
90✔
845
    int y1;
90✔
846
    int in0;
90✔
847
    int in1;
90✔
848
    int rows;
90✔
849
    int local_ncolors;
90✔
850
    int wcomp1;
90✔
851
    int wcomp2;
90✔
852
    int wcomp3;
90✔
853
    SIXELSTATUS status;
90✔
854
    sixel_parallel_dither_state_t *state;
90✔
855
    sixel_lut_t *local_lut;
90✔
856

857
    plan = (sixel_parallel_dither_plan_t *)userdata;
108✔
858
    if (plan == NULL) {
108!
859
        return SIXEL_BAD_ARGUMENT;
860
    }
861

862
    band_index = job.band_index;
108✔
863
    if (band_index < 0) {
108!
864
        return SIXEL_BAD_ARGUMENT;
865
    }
866

867
    y0 = band_index * plan->band_height;
108✔
868
    if (y0 >= plan->height) {
108!
869
        return SIXEL_OK;
870
    }
871

872
    y1 = y0 + plan->band_height;
108✔
873
    if (y1 > plan->height) {
108!
874
        y1 = plan->height;
875
    }
876

877
    in0 = y0 - plan->overlap;
108✔
878
    if (in0 < 0) {
108✔
879
        in0 = 0;
18✔
880
    }
18✔
881
    in1 = y1;
108✔
882
    rows = in1 - in0;
108✔
883
    if (rows <= 0) {
108!
884
        return SIXEL_OK;
885
    }
886

887
    required = (size_t)rows * plan->row_bytes;
108✔
888
    offset = (size_t)in0 * plan->row_bytes;
108✔
889
    copy = NULL;
108✔
890
    source = plan->pixels + offset;
108✔
891
    if (plan->overlap > 0) {
108!
892
        copy = (unsigned char *)malloc(required);
72✔
893
        if (copy == NULL) {
72!
894
            return SIXEL_BAD_ALLOCATION;
895
        }
896
        memcpy(copy, source, required);
72✔
897
        source = copy;
72✔
898
    }
36✔
899

900
    if (plan->logger != NULL) {
108!
901
        sixel_logger_logf(plan->logger,
902
                          "worker",
903
                          "dither",
904
                          "start",
905
                          band_index,
906
                          in0,
907
                          y0,
908
                          y1,
909
                          in0,
910
                          in1,
911
                          "prepare rows=%d",
912
                          rows);
913
    }
914

915
    local_ncolors = plan->reqcolor;
108✔
916
    state = (sixel_parallel_dither_state_t *)workspace;
108✔
917
    local_lut = plan->lut;
108✔
918
    if (local_lut == NULL && state != NULL) {
108!
919
        if (state->lut_initialized == 0) {
×
920
            status = sixel_lut_new(&state->lut,
921
                                   plan->lut_policy,
922
                                   plan->allocator);
923
            if (SIXEL_FAILED(status)) {
×
924
                if (copy != NULL) {
×
925
                    free(copy);
926
                }
927
                return status;
928
            }
929
            if (plan->lut_policy == SIXEL_LUT_POLICY_CERTLUT) {
×
930
                if (plan->method_for_largest == SIXEL_LARGE_LUM) {
×
931
                    wcomp1 = plan->complexion * 299;
932
                    wcomp2 = 587;
933
                    wcomp3 = 114;
934
                } else {
935
                    wcomp1 = plan->complexion;
936
                    wcomp2 = 1;
937
                    wcomp3 = 1;
938
                }
939
            } else {
940
                wcomp1 = plan->complexion;
941
                wcomp2 = 1;
942
                wcomp3 = 1;
943
            }
944
            status = sixel_lut_configure(state->lut,
945
                                         plan->palette->entries,
946
                                         plan->palette->entries_float32,
947
                                         plan->palette->depth,
948
                                         plan->palette->float_depth,
949
                                         (int)plan->palette->entry_count,
950
                                         plan->complexion,
951
                                         wcomp1,
952
                                         wcomp2,
953
                                         wcomp3,
954
                                         plan->lut_policy,
955
                                         plan->pixelformat);
956
            if (SIXEL_FAILED(status)) {
×
957
                sixel_lut_unref(state->lut);
958
                state->lut = NULL;
959
                if (copy != NULL) {
×
960
                    free(copy);
961
                }
962
                return status;
963
            }
964
            state->lut_initialized = 1;
965
        }
966
        local_lut = state->lut;
967
    }
968
    /*
969
     * Map directly into the shared destination but suppress writes
970
     * before output_start.  The overlap rows are computed only to warm
971
     * up the error diffusion and are discarded by the output_start
972
     * check in the renderer, so neighboring bands never clobber each
973
     * other's body.
974
     */
975
    status = sixel_dither_map_pixels(plan->dest + (size_t)in0 * plan->width,
216✔
976
                                     (unsigned char *)source,
36✔
977
                                     plan->width,
36✔
978
                                     rows,
36✔
979
                                     in0,
36✔
980
                                     y0,
36✔
981
                                     3,
982
                                     plan->palette->entries,
108✔
983
                                     plan->reqcolor,
36✔
984
                                     plan->method_for_diffuse,
36✔
985
                                     plan->method_for_scan,
36✔
986
                                     plan->method_for_carry,
36✔
987
                                     plan->optimize_palette,
36✔
988
                                     plan->optimize_palette_entries,
36✔
989
                                     plan->complexion,
36✔
990
                                     plan->lut_policy,
36✔
991
                                     plan->method_for_largest,
36✔
992
                                     local_lut,
36✔
993
                                     &local_ncolors,
994
                                     plan->allocator,
36✔
995
                                     plan->dither,
36✔
996
                                     plan->pixelformat);
36✔
997
    if (plan->logger != NULL) {
108!
998
        sixel_logger_logf(plan->logger,
999
                          "worker",
1000
                          "dither",
1001
                          "finish",
1002
                          band_index,
1003
                          in1 - 1,
1004
                          y0,
1005
                          y1,
1006
                          in0,
1007
                          in1,
1008
                          "status=%d rows=%d",
1009
                          status,
1010
                          rows);
1011
    }
1012
    if (copy != NULL) {
108!
1013
        free(copy);
72✔
1014
    }
36✔
1015
    return status;
36✔
1016
}
36✔
1017

1018
static SIXELSTATUS
1019
sixel_dither_apply_palette_parallel(sixel_parallel_dither_plan_t *plan,
54✔
1020
                                    int threads)
1021
{
1022
    SIXELSTATUS status;
45✔
1023
    threadpool_t *pool;
45✔
1024
    size_t depth_bytes;
45✔
1025
    size_t workspace_size;
45✔
1026
    int nbands;
45✔
1027
    int queue_depth;
45✔
1028
    int band_index;
45✔
1029
    int stride;
45✔
1030
    int offset;
45✔
1031
    tp_workspace_cleanup_fn cleanup;
45✔
1032

1033
    if (plan == NULL || plan->palette == NULL) {
54!
1034
        return SIXEL_BAD_ARGUMENT;
1035
    }
1036

1037
    depth_bytes = (size_t)sixel_helper_compute_depth(plan->pixelformat);
54✔
1038
    if (depth_bytes == 0U) {
54!
1039
        return SIXEL_BAD_ARGUMENT;
1040
    }
1041
    plan->row_bytes = (size_t)plan->width * depth_bytes;
54✔
1042

1043
    nbands = (plan->height + plan->band_height - 1) / plan->band_height;
54✔
1044
    if (nbands < 1) {
54!
1045
        return SIXEL_OK;
1046
    }
1047

1048
    if (threads > nbands) {
54!
1049
        threads = nbands;
1050
    }
1051
    if (threads < 1) {
54✔
1052
        threads = 1;
1053
    }
1054

1055
    queue_depth = threads * 3;
54✔
1056
    if (queue_depth > nbands) {
54!
1057
        queue_depth = nbands;
18✔
1058
    }
18✔
1059
    if (queue_depth < 1) {
54✔
1060
        queue_depth = 1;
1061
    }
1062

1063
    workspace_size = 0U;
54✔
1064
    cleanup = NULL;
54✔
1065
    if (plan->lut == NULL && plan->lut_policy != SIXEL_LUT_POLICY_NONE) {
54!
1066
        workspace_size = sizeof(sixel_parallel_dither_state_t);
36✔
1067
        cleanup = sixel_parallel_dither_cleanup;
36✔
1068
        /*
1069
         * Worker-local caches are constructed only when the shared LUT is
1070
         * disabled.  Allocating the workspace up front lets each thread keep
1071
         * the configured LUT for all assigned bands instead of rebuilding it
1072
         * every time the worker callback is invoked.
1073
         */
1074
    }
1075
    pool = threadpool_create(threads,
72✔
1076
                             queue_depth,
18✔
1077
                             workspace_size,
18✔
1078
                             sixel_dither_parallel_worker,
1079
                             plan,
18✔
1080
                             cleanup);
18✔
1081
    if (pool == NULL) {
54!
1082
        return SIXEL_BAD_ALLOCATION;
1083
    }
1084

1085
    threadpool_set_affinity(pool, plan->pin_threads);
54✔
1086

1087
    /*
1088
     * Distribute the initial jobs so each worker starts far apart, then feed
1089
     * follow-up work that walks downward from those seeds.  This staggered
1090
     * order reduces contention around the top of the image and keeps later
1091
     * assignments close to the last band a worker processed, improving cache
1092
     * locality.
1093
     */
1094
    stride = (nbands + threads - 1) / threads;
54✔
1095
    for (offset = 0; offset < stride; ++offset) {
108✔
1096
        for (band_index = 0; band_index < threads; ++band_index) {
162✔
1097
            tp_job_t job;
90✔
1098
            int seeded;
90✔
1099

1100
            seeded = band_index * stride + offset;
108✔
1101
            if (seeded >= nbands) {
108!
1102
                continue;
1103
            }
1104
            job.band_index = seeded;
108✔
1105
            threadpool_push(pool, job);
108✔
1106
        }
36!
1107
    }
18✔
1108

1109
    threadpool_finish(pool);
54✔
1110
    status = threadpool_get_error(pool);
54✔
1111
    threadpool_destroy(pool);
54✔
1112

1113
    return status;
54✔
1114
}
18✔
1115
#endif
1116

1117

1118
/*
1119
 * Helper that detects whether the palette currently matches either of the
1120
 * builtin monochrome definitions.  These tables skip cache initialization
1121
 * during fast-path dithering because they already match the terminal
1122
 * defaults.
1123
 */
1124
static int
1125
sixel_palette_is_builtin_mono(sixel_palette_t const *palette)
3,272✔
1126
{
1127
    if (palette == NULL) {
3,272!
1128
        return 0;
1129
    }
1130
    if (palette->entries == NULL) {
3,272!
1131
        return 0;
1132
    }
1133
    if (palette->entry_count < 2U) {
3,272✔
1134
        return 0;
48✔
1135
    }
1136
    if (palette->depth != 3) {
3,168!
1137
        return 0;
1138
    }
1139
    if (memcmp(palette->entries, pal_mono_dark,
4,695✔
1140
               sizeof(pal_mono_dark)) == 0) {
1,527✔
1141
        return 1;
24✔
1142
    }
1143
    if (memcmp(palette->entries, pal_mono_light,
4,619✔
1144
               sizeof(pal_mono_light)) == 0) {
1,503✔
1145
        return 1;
13✔
1146
    }
1147
    return 0;
1,497✔
1148
}
1,575✔
1149

1150
/*
1151
 * Route palette application through the local dithering helper.  The
1152
 * function keeps all state in the palette object so we can share cache
1153
 * buffers between invocations and later stages.  The flow is:
1154
 *   1. Synchronize the quantizer configuration with the dither object so the
1155
 *      LUT builder honors the requested policy.
1156
 *   2. Invoke sixel_dither_map_pixels() to populate the index buffer and
1157
 *      record the resulting palette size.
1158
 *   3. Return the status to the caller so palette application errors can be
1159
 *      reported at a single site.
1160
 */
1161
static SIXELSTATUS
1162
sixel_dither_resolve_indexes(sixel_index_t *result,
3,313✔
1163
                             unsigned char *data,
1164
                             int width,
1165
                             int height,
1166
                             int depth,
1167
                             sixel_palette_t *palette,
1168
                             int reqcolor,
1169
                             int method_for_diffuse,
1170
                             int method_for_scan,
1171
                             int method_for_carry,
1172
                             int foptimize,
1173
                             int foptimize_palette,
1174
                             int complexion,
1175
                              int lut_policy,
1176
                              int method_for_largest,
1177
                              int *ncolors,
1178
                              sixel_allocator_t *allocator,
1179
                              sixel_dither_t *dither,
1180
                              int pixelformat)
1181
{
1182
    SIXELSTATUS status = SIXEL_FALSE;
3,313✔
1183

1184
    if (palette == NULL || palette->entries == NULL) {
3,313!
1185
        return SIXEL_BAD_ARGUMENT;
1186
    }
1187

1188
    sixel_palette_set_lut_policy(lut_policy);
3,313✔
1189
    sixel_palette_set_method_for_largest(method_for_largest);
3,313✔
1190

1191
    status = sixel_dither_map_pixels(result,
4,913✔
1192
                                     data,
1,600✔
1193
                                     width,
1,600✔
1194
                                     height,
1,600✔
1195
                                     0,
1196
                                     0,
1197
                                     depth,
1,600✔
1198
                                     palette->entries,
1,600✔
1199
                                     reqcolor,
1,600✔
1200
                                     method_for_diffuse,
1,600✔
1201
                                     method_for_scan,
1,600✔
1202
                                     method_for_carry,
1,600✔
1203
                                     foptimize,
1,600✔
1204
                                     foptimize_palette,
1,600✔
1205
                                     complexion,
1,600✔
1206
                                     lut_policy,
1,600✔
1207
                                     method_for_largest,
1,600✔
1208
                                     palette->lut,
3,313✔
1209
                                     ncolors,
1,600✔
1210
                                     allocator,
1,600✔
1211
                                     dither,
1,600✔
1212
                                     pixelformat);
1,600✔
1213

1214
    return status;
3,313✔
1215
}
1,600✔
1216

1217

1218
/*
1219
 * VT340 undocumented behavior regarding the color palette reported
1220
 * by Vertis Sidus(@vrtsds):
1221
 *     it loads the first fifteen colors as 1 through 15, and loads the
1222
 *     sixteenth color as 0.
1223
 */
1224
static const unsigned char pal_vt340_mono[] = {
1225
    /* 1   Gray-2   */  13 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
1226
    /* 2   Gray-4   */  26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1227
    /* 3   Gray-6   */  40 * 255 / 100, 40 * 255 / 100, 40 * 255 / 100,
1228
    /* 4   Gray-1   */   6 * 255 / 100,  6 * 255 / 100,  6 * 255 / 100,
1229
    /* 5   Gray-3   */  20 * 255 / 100, 20 * 255 / 100, 20 * 255 / 100,
1230
    /* 6   Gray-5   */  33 * 255 / 100, 33 * 255 / 100, 33 * 255 / 100,
1231
    /* 7   White 7  */  46 * 255 / 100, 46 * 255 / 100, 46 * 255 / 100,
1232
    /* 8   Black 0  */   0 * 255 / 100,  0 * 255 / 100,  0 * 255 / 100,
1233
    /* 9   Gray-2   */  13 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
1234
    /* 10  Gray-4   */  26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1235
    /* 11  Gray-6   */  40 * 255 / 100, 40 * 255 / 100, 40 * 255 / 100,
1236
    /* 12  Gray-1   */   6 * 255 / 100,  6 * 255 / 100,  6 * 255 / 100,
1237
    /* 13  Gray-3   */  20 * 255 / 100, 20 * 255 / 100, 20 * 255 / 100,
1238
    /* 14  Gray-5   */  33 * 255 / 100, 33 * 255 / 100, 33 * 255 / 100,
1239
    /* 15  White 7  */  46 * 255 / 100, 46 * 255 / 100, 46 * 255 / 100,
1240
    /* 0   Black    */   0 * 255 / 100,  0 * 255 / 100,  0 * 255 / 100,
1241
};
1242

1243

1244
static const unsigned char pal_vt340_color[] = {
1245
    /* 1   Blue     */  20 * 255 / 100, 20 * 255 / 100, 80 * 255 / 100,
1246
    /* 2   Red      */  80 * 255 / 100, 13 * 255 / 100, 13 * 255 / 100,
1247
    /* 3   Green    */  20 * 255 / 100, 80 * 255 / 100, 20 * 255 / 100,
1248
    /* 4   Magenta  */  80 * 255 / 100, 20 * 255 / 100, 80 * 255 / 100,
1249
    /* 5   Cyan     */  20 * 255 / 100, 80 * 255 / 100, 80 * 255 / 100,
1250
    /* 6   Yellow   */  80 * 255 / 100, 80 * 255 / 100, 20 * 255 / 100,
1251
    /* 7   Gray 50% */  53 * 255 / 100, 53 * 255 / 100, 53 * 255 / 100,
1252
    /* 8   Gray 25% */  26 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1253
    /* 9   Blue*    */  33 * 255 / 100, 33 * 255 / 100, 60 * 255 / 100,
1254
    /* 10  Red*     */  60 * 255 / 100, 26 * 255 / 100, 26 * 255 / 100,
1255
    /* 11  Green*   */  33 * 255 / 100, 60 * 255 / 100, 33 * 255 / 100,
1256
    /* 12  Magenta* */  60 * 255 / 100, 33 * 255 / 100, 60 * 255 / 100,
1257
    /* 13  Cyan*    */  33 * 255 / 100, 60 * 255 / 100, 60 * 255 / 100,
1258
    /* 14  Yellow*  */  60 * 255 / 100, 60 * 255 / 100, 33 * 255 / 100,
1259
    /* 15  Gray 75% */  80 * 255 / 100, 80 * 255 / 100, 80 * 255 / 100,
1260
    /* 0   Black    */   0 * 255 / 100,  0 * 255 / 100,  0 * 255 / 100,
1261
};
1262

1263

1264
/* create dither context object */
1265
SIXELAPI SIXELSTATUS
1266
sixel_dither_new(
4,136✔
1267
    sixel_dither_t    /* out */ **ppdither, /* dither object to be created */
1268
    int               /* in */  ncolors,    /* required colors */
1269
    sixel_allocator_t /* in */  *allocator) /* allocator, null if you use
1270
                                               default allocator */
1271
{
1272
    SIXELSTATUS status = SIXEL_FALSE;
4,136✔
1273
    size_t headsize;
3,147✔
1274
    size_t palette_bytes;
3,147✔
1275
    int quality_mode;
3,147✔
1276
    sixel_palette_t *palette;
3,147✔
1277

1278
    /* ensure given pointer is not null */
1279
    if (ppdither == NULL) {
4,136!
1280
        sixel_helper_set_additional_message(
×
1281
            "sixel_dither_new: ppdither is null.");
1282
        status = SIXEL_BAD_ARGUMENT;
×
1283
        goto end;
×
1284
    }
1285
    *ppdither = NULL;
4,136✔
1286

1287
    if (allocator == NULL) {
4,136✔
1288
        status = sixel_allocator_new(&allocator, NULL, NULL, NULL, NULL);
169✔
1289
        if (SIXEL_FAILED(status)) {
169!
1290
            *ppdither = NULL;
×
1291
            goto end;
×
1292
        }
1293
    } else {
78✔
1294
        sixel_allocator_ref(allocator);
3,967✔
1295
    }
1296

1297
    if (ncolors < 0) {
4,136✔
1298
        ncolors = SIXEL_PALETTE_MAX;
78✔
1299
        quality_mode = SIXEL_QUALITY_HIGHCOLOR;
78✔
1300
    } else {
78✔
1301
        if (ncolors > SIXEL_PALETTE_MAX) {
3,964!
1302
            status = SIXEL_BAD_INPUT;
×
1303
            goto end;
×
1304
        } else if (ncolors < 1) {
3,964!
1305
            status = SIXEL_BAD_INPUT;
×
1306
            sixel_helper_set_additional_message(
×
1307
                "sixel_dither_new: palette colors must be more than 0");
1308
            goto end;
×
1309
        }
1310
        quality_mode = SIXEL_QUALITY_LOW;
1,914✔
1311
    }
1312
    headsize = sizeof(sixel_dither_t);
4,136✔
1313
    palette_bytes = 0U;
4,136✔
1314

1315
    *ppdither = (sixel_dither_t *)sixel_allocator_malloc(allocator, headsize);
4,136✔
1316
    if (*ppdither == NULL) {
4,136!
1317
        sixel_allocator_unref(allocator);
×
1318
        sixel_helper_set_additional_message(
×
1319
            "sixel_dither_new: sixel_allocator_malloc() failed.");
1320
        status = SIXEL_BAD_ALLOCATION;
×
1321
        goto end;
×
1322
    }
1323

1324
    (*ppdither)->ref = 1U;
4,136✔
1325
    (*ppdither)->palette = NULL;
4,136✔
1326
    (*ppdither)->reqcolors = ncolors;
4,136✔
1327
    (*ppdither)->force_palette = 0;
4,136✔
1328
    (*ppdither)->ncolors = ncolors;
4,136✔
1329
    (*ppdither)->origcolors = (-1);
4,136✔
1330
    (*ppdither)->keycolor = (-1);
4,136✔
1331
    (*ppdither)->optimized = 0;
4,136✔
1332
    (*ppdither)->optimize_palette = 0;
4,136✔
1333
    (*ppdither)->complexion = 1;
4,136✔
1334
    (*ppdither)->bodyonly = 0;
4,136✔
1335
    (*ppdither)->method_for_largest = SIXEL_LARGE_NORM;
4,136✔
1336
    (*ppdither)->method_for_rep = SIXEL_REP_CENTER_BOX;
4,136✔
1337
    (*ppdither)->method_for_diffuse = SIXEL_DIFFUSE_FS;
4,136✔
1338
    (*ppdither)->method_for_scan = SIXEL_SCAN_AUTO;
4,136✔
1339
    (*ppdither)->method_for_carry = SIXEL_CARRY_AUTO;
4,136✔
1340
    (*ppdither)->quality_mode = quality_mode;
4,136✔
1341
    (*ppdither)->requested_quality_mode = quality_mode;
4,136✔
1342
    (*ppdither)->pixelformat = SIXEL_PIXELFORMAT_RGB888;
4,136✔
1343
    (*ppdither)->prefer_float32 = 0;
4,136✔
1344
    (*ppdither)->allocator = allocator;
4,136✔
1345
    (*ppdither)->lut_policy = SIXEL_LUT_POLICY_AUTO;
4,136✔
1346
    (*ppdither)->sixel_reversible = 0;
4,136✔
1347
    (*ppdither)->quantize_model = SIXEL_QUANTIZE_MODEL_AUTO;
4,136✔
1348
    (*ppdither)->final_merge_mode = SIXEL_FINAL_MERGE_AUTO;
4,136✔
1349
    (*ppdither)->pipeline_row_callback = NULL;
4,136✔
1350
    (*ppdither)->pipeline_row_priv = NULL;
4,136✔
1351
    (*ppdither)->pipeline_index_buffer = NULL;
4,136✔
1352
    (*ppdither)->pipeline_index_size = 0;
4,136✔
1353
    (*ppdither)->pipeline_index_owned = 0;
4,136✔
1354
    (*ppdither)->pipeline_parallel_active = 0;
4,136✔
1355
    (*ppdither)->pipeline_band_height = 0;
4,136✔
1356
    (*ppdither)->pipeline_band_overlap = 0;
4,136✔
1357
    (*ppdither)->pipeline_dither_threads = 0;
4,136✔
1358
    (*ppdither)->pipeline_pin_threads = 1;
4,136✔
1359
    (*ppdither)->pipeline_image_height = 0;
4,136✔
1360
    (*ppdither)->pipeline_logger = NULL;
4,136✔
1361

1362
    status = sixel_palette_new(&(*ppdither)->palette, allocator);
4,136✔
1363
    if (SIXEL_FAILED(status)) {
4,136!
1364
        sixel_allocator_free(allocator, *ppdither);
×
1365
        *ppdither = NULL;
×
1366
        goto end;
×
1367
    }
1368

1369
    palette = (*ppdither)->palette;
4,136✔
1370
    palette->requested_colors = (unsigned int)ncolors;
4,136✔
1371
    palette->quality_mode = quality_mode;
4,136✔
1372
    palette->force_palette = 0;
4,136✔
1373
    palette->lut_policy = SIXEL_LUT_POLICY_AUTO;
4,136✔
1374

1375
    status = sixel_palette_resize(palette,
6,116✔
1376
                                  (unsigned int)ncolors,
1,980✔
1377
                                  3,
1378
                                  allocator);
1,980✔
1379
    if (SIXEL_FAILED(status)) {
4,136!
1380
        sixel_palette_unref(palette);
×
1381
        (*ppdither)->palette = NULL;
×
1382
        sixel_allocator_free(allocator, *ppdither);
×
1383
        *ppdither = NULL;
×
1384
        goto end;
×
1385
    }
1386
    /*
1387
     * Ensure palette entries are fully initialized before any lookup
1388
     * path reads them under MSan instrumentation.
1389
     */
1390
    palette_bytes = palette->entries_size;
4,136✔
1391
    if (palette->entries != NULL && palette_bytes > 0U) {
4,136!
1392
        memset(palette->entries, 0, palette_bytes);
4,136✔
1393
    }
1,980✔
1394

1395
    status = SIXEL_OK;
1,992✔
1396

1397
end:
12✔
1398
    if (SIXEL_FAILED(status)) {
1,992!
1399
        sixel_allocator_unref(allocator);
×
1400
    }
1401
    return status;
5,139✔
1402
}
1,003✔
1403

1404

1405
/* create dither context object (deprecated) */
1406
SIXELAPI sixel_dither_t *
1407
sixel_dither_create(
×
1408
    int     /* in */ ncolors)
1409
{
1410
    SIXELSTATUS status = SIXEL_FALSE;
×
1411
    sixel_dither_t *dither = NULL;
×
1412

1413
    status = sixel_dither_new(&dither, ncolors, NULL);
×
1414
    if (SIXEL_FAILED(status)) {
×
1415
        goto end;
1416
    }
1417

1418
end:
1419
    return dither;
×
1420
}
1421

1422

1423
SIXELAPI void
1424
sixel_dither_destroy(
4,136✔
1425
    sixel_dither_t  /* in */ *dither)
1426
{
1427
    sixel_allocator_t *allocator;
3,147✔
1428

1429
    if (dither) {
4,136!
1430
        allocator = dither->allocator;
4,136✔
1431
        if (dither->palette != NULL) {
4,136!
1432
            sixel_palette_unref(dither->palette);
4,136✔
1433
            dither->palette = NULL;
4,136✔
1434
        }
1,980✔
1435
        sixel_allocator_free(allocator, dither);
4,136✔
1436
        sixel_allocator_unref(allocator);
4,136✔
1437
    }
1,980✔
1438
}
4,136✔
1439

1440

1441
SIXELAPI void
1442
sixel_dither_ref(
10,347✔
1443
    sixel_dither_t  /* in */ *dither)
1444
{
1445
    if (dither == NULL) {
10,347!
1446
        return;
1447
    }
1448

1449
    (void)sixel_atomic_fetch_add_u32(&dither->ref, 1U);
10,347✔
1450
}
4,978✔
1451

1452

1453
SIXELAPI void
1454
sixel_dither_unref(
19,047✔
1455
    sixel_dither_t  /* in */ *dither)
1456
{
1457
    unsigned int previous;
14,533✔
1458

1459
    if (dither == NULL) {
19,047✔
1460
        return;
2,181✔
1461
    }
1462

1463
    previous = sixel_atomic_fetch_sub_u32(&dither->ref, 1U);
14,483✔
1464
    if (previous == 1U) {
14,483✔
1465
        sixel_dither_destroy(dither);
4,136✔
1466
    }
1,980✔
1467
}
9,139!
1468

1469

1470
SIXELAPI sixel_dither_t *
1471
sixel_dither_get(
169✔
1472
    int     /* in */ builtin_dither)
1473
{
1474
    SIXELSTATUS status = SIXEL_FALSE;
169✔
1475
    unsigned char *palette;
130✔
1476
    int ncolors;
130✔
1477
    int keycolor;
130✔
1478
    sixel_dither_t *dither = NULL;
169✔
1479

1480
    switch (builtin_dither) {
169!
1481
    case SIXEL_BUILTIN_MONO_DARK:
1482
        ncolors = 2;
18✔
1483
        palette = (unsigned char *)pal_mono_dark;
18✔
1484
        keycolor = 0;
18✔
1485
        break;
18✔
1486
    case SIXEL_BUILTIN_MONO_LIGHT:
7✔
1487
        ncolors = 2;
13✔
1488
        palette = (unsigned char *)pal_mono_light;
13✔
1489
        keycolor = 0;
13✔
1490
        break;
13✔
1491
    case SIXEL_BUILTIN_XTERM16:
14✔
1492
        ncolors = 16;
26✔
1493
        palette = (unsigned char *)pal_xterm256;
26✔
1494
        keycolor = (-1);
26✔
1495
        break;
26✔
1496
    case SIXEL_BUILTIN_XTERM256:
7✔
1497
        ncolors = 256;
13✔
1498
        palette = (unsigned char *)pal_xterm256;
13✔
1499
        keycolor = (-1);
13✔
1500
        break;
13✔
1501
    case SIXEL_BUILTIN_VT340_MONO:
7✔
1502
        ncolors = 16;
13✔
1503
        palette = (unsigned char *)pal_vt340_mono;
13✔
1504
        keycolor = (-1);
13✔
1505
        break;
13✔
1506
    case SIXEL_BUILTIN_VT340_COLOR:
7✔
1507
        ncolors = 16;
13✔
1508
        palette = (unsigned char *)pal_vt340_color;
13✔
1509
        keycolor = (-1);
13✔
1510
        break;
13✔
1511
    case SIXEL_BUILTIN_G1:
7✔
1512
        ncolors = 2;
13✔
1513
        palette = (unsigned char *)pal_gray_1bit;
13✔
1514
        keycolor = (-1);
13✔
1515
        break;
13✔
1516
    case SIXEL_BUILTIN_G2:
7✔
1517
        ncolors = 4;
13✔
1518
        palette = (unsigned char *)pal_gray_2bit;
13✔
1519
        keycolor = (-1);
13✔
1520
        break;
13✔
1521
    case SIXEL_BUILTIN_G4:
7✔
1522
        ncolors = 16;
13✔
1523
        palette = (unsigned char *)pal_gray_4bit;
13✔
1524
        keycolor = (-1);
13✔
1525
        break;
13✔
1526
    case SIXEL_BUILTIN_G8:
7✔
1527
        ncolors = 256;
13✔
1528
        palette = (unsigned char *)pal_gray_8bit;
13✔
1529
        keycolor = (-1);
13✔
1530
        break;
13✔
1531
    default:
1532
        goto end;
×
1533
    }
1534

1535
    status = sixel_dither_new(&dither, ncolors, NULL);
169✔
1536
    if (SIXEL_FAILED(status)) {
169!
1537
        dither = NULL;
×
1538
        goto end;
×
1539
    }
1540

1541
    status = sixel_palette_set_entries(dither->palette,
338✔
1542
                                       palette,
78✔
1543
                                       (unsigned int)ncolors,
78✔
1544
                                       3,
1545
                                       dither->allocator);
169✔
1546
    if (SIXEL_FAILED(status)) {
169!
1547
        sixel_dither_unref(dither);
×
1548
        dither = NULL;
×
1549
        goto end;
×
1550
    }
1551
    dither->palette->requested_colors = (unsigned int)ncolors;
169✔
1552
    dither->palette->entry_count = (unsigned int)ncolors;
169✔
1553
    dither->palette->depth = 3;
169✔
1554
    dither->keycolor = keycolor;
169✔
1555
    dither->optimized = 1;
169✔
1556
    dither->optimize_palette = 0;
169✔
1557

1558
end:
91✔
1559
    return dither;
208✔
1560
}
39✔
1561

1562

1563
static void
1564
sixel_dither_set_method_for_largest(
3,023✔
1565
    sixel_dither_t  /* in */ *dither,
1566
    int             /* in */ method_for_largest)
1567
{
1568
    if (method_for_largest == SIXEL_LARGE_AUTO) {
3,023✔
1569
        method_for_largest = SIXEL_LARGE_NORM;
2,776✔
1570
    }
1,345✔
1571
    dither->method_for_largest = method_for_largest;
3,023✔
1572
}
1,459✔
1573

1574

1575
static void
1576
sixel_dither_set_method_for_rep(
3,023✔
1577
    sixel_dither_t  /* in */ *dither,
1578
    int             /* in */ method_for_rep)
1579
{
1580
    if (method_for_rep == SIXEL_REP_AUTO) {
3,023✔
1581
        method_for_rep = SIXEL_REP_CENTER_BOX;
2,763✔
1582
    }
1,339✔
1583
    dither->method_for_rep = method_for_rep;
3,023✔
1584
}
1,459✔
1585

1586

1587
static void
1588
sixel_dither_set_quality_mode(
3,023✔
1589
    sixel_dither_t  /* in */  *dither,
1590
    int             /* in */  quality_mode)
1591
{
1592
    dither->requested_quality_mode = quality_mode;
3,023✔
1593

1594
    if (quality_mode == SIXEL_QUALITY_AUTO) {
3,023✔
1595
        if (dither->ncolors <= 8) {
2,786✔
1596
            quality_mode = SIXEL_QUALITY_HIGH;
24✔
1597
        } else {
24✔
1598
            quality_mode = SIXEL_QUALITY_LOW;
2,734✔
1599
        }
1600
    }
1,351✔
1601
    dither->quality_mode = quality_mode;
3,023✔
1602
}
1,459✔
1603

1604

1605
SIXELAPI SIXELSTATUS
1606
sixel_dither_initialize(
3,023✔
1607
    sixel_dither_t  /* in */ *dither,
1608
    unsigned char   /* in */ *data,
1609
    int             /* in */ width,
1610
    int             /* in */ height,
1611
    int             /* in */ pixelformat,
1612
    int             /* in */ method_for_largest,
1613
    int             /* in */ method_for_rep,
1614
    int             /* in */ quality_mode)
1615
{
1616
    unsigned char *buf = NULL;
3,023✔
1617
    unsigned char *normalized_pixels = NULL;
3,023✔
1618
    float *float_pixels = NULL;
3,023✔
1619
    unsigned char *input_pixels;
2,307✔
1620
    SIXELSTATUS status = SIXEL_FALSE;
3,023✔
1621
    size_t total_pixels;
2,307✔
1622
    unsigned int payload_length;
2,307✔
1623
    int palette_pixelformat;
2,307✔
1624
    int prefer_float32;
2,307✔
1625

1626
    /* ensure dither object is not null */
1627
    if (dither == NULL) {
3,023!
1628
        sixel_helper_set_additional_message(
×
1629
            "sixel_dither_new: dither is null.");
1630
        status = SIXEL_BAD_ARGUMENT;
×
1631
        goto end;
×
1632
    }
1633

1634
    /* increment ref count */
1635
    sixel_dither_ref(dither);
3,023✔
1636

1637
    sixel_dither_set_pixelformat(dither, pixelformat);
3,023✔
1638

1639
    /* keep quantizer policy in sync with the dither object */
1640
    sixel_palette_set_lut_policy(dither->lut_policy);
3,023✔
1641

1642
    input_pixels = NULL;
3,023✔
1643
    total_pixels = (size_t)width * (size_t)height;
3,023✔
1644
    payload_length = 0U;
3,023✔
1645
    palette_pixelformat = SIXEL_PIXELFORMAT_RGB888;
3,023✔
1646
    prefer_float32 = dither->prefer_float32;
3,023✔
1647

1648
    /* Float32 input requires the pipeline to honour higher precision. */
1649
    if (SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat)) {
3,023!
1650
        prefer_float32 = 1;
1,105✔
1651
    }
510✔
1652

1653
    switch (pixelformat) {
3,023!
1654
    case SIXEL_PIXELFORMAT_RGB888:
1655
        input_pixels = data;
949✔
1656
        break;
949✔
1657
    case SIXEL_PIXELFORMAT_RGBFLOAT32:
595✔
1658
    case SIXEL_PIXELFORMAT_LINEARRGBFLOAT32:
1659
    case SIXEL_PIXELFORMAT_OKLABFLOAT32:
1660
    case SIXEL_PIXELFORMAT_CIELABFLOAT32:
1661
    case SIXEL_PIXELFORMAT_DIN99DFLOAT32:
1662
        if (prefer_float32) {
1,105!
1663
            input_pixels = data;
1,105✔
1664
            palette_pixelformat = pixelformat;
1,105✔
1665
            payload_length = (unsigned int)(total_pixels * 3U
1,615✔
1666
                                            * sizeof(float));
510✔
1667
            break;
1,105✔
1668
        }
1669
        /* fallthrough */
1670
    default:
1671
        /* normalize pixelformat */
1672
        normalized_pixels
1673
            = (unsigned char *)sixel_allocator_malloc(
×
1674
                dither->allocator, (size_t)(width * height * 3));
×
1675
        if (normalized_pixels == NULL) {
×
1676
            sixel_helper_set_additional_message(
×
1677
                "sixel_dither_initialize: sixel_allocator_malloc() failed.");
1678
            status = SIXEL_BAD_ALLOCATION;
×
1679
            goto end;
×
1680
        }
1681

1682
        status = sixel_helper_normalize_pixelformat(
×
1683
            normalized_pixels,
1684
            &pixelformat,
1685
            data,
1686
            pixelformat,
1687
            width,
1688
            height);
1689
        if (SIXEL_FAILED(status)) {
×
1690
            goto end;
×
1691
        }
1692
        input_pixels = normalized_pixels;
1693
        break;
1694
    }
1695

1696
    if (payload_length == 0U) {
2,054!
1697
        payload_length = (unsigned int)(total_pixels * 3U);
1,918✔
1698
    }
949✔
1699

1700
    if (prefer_float32
3,023!
1701
        && !SIXEL_PIXELFORMAT_IS_FLOAT32(palette_pixelformat)
2,054!
1702
        && total_pixels > 0U) {
66!
1703
        status = sixel_dither_promote_rgb888_to_float32(
×
1704
            &float_pixels,
1705
            input_pixels,
1706
            total_pixels,
1707
            dither->allocator);
1708
        if (SIXEL_SUCCEEDED(status) && float_pixels != NULL) {
×
1709
            payload_length
1710
                = (unsigned int)(total_pixels * 3U * sizeof(float));
×
1711
            palette_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
×
1712
            input_pixels = (unsigned char *)float_pixels;
×
1713
        } else {
1714
            prefer_float32 = 0;
1715
            status = SIXEL_OK;
1,564✔
1716
        }
1717
    }
1718

1719
    dither->prefer_float32 = prefer_float32;
3,023✔
1720

1721
    sixel_dither_set_method_for_largest(dither, method_for_largest);
3,023✔
1722
    sixel_dither_set_method_for_rep(dither, method_for_rep);
3,023✔
1723
    sixel_dither_set_quality_mode(dither, quality_mode);
3,023✔
1724

1725
    status = sixel_palette_make_palette(&buf,
4,587✔
1726
                                        input_pixels,
1,459✔
1727
                                        payload_length,
1,459✔
1728
                                        palette_pixelformat,
1,459✔
1729
                                        (unsigned int)dither->reqcolors,
3,023✔
1730
                                        (unsigned int *)&dither->ncolors,
3,023✔
1731
                                        (unsigned int *)&dither->origcolors,
3,023✔
1732
                                        dither->method_for_largest,
1,459✔
1733
                                        dither->method_for_rep,
1,459✔
1734
                                        dither->quality_mode,
1,459✔
1735
                                        dither->force_palette,
1,459✔
1736
                                        dither->sixel_reversible,
1,459✔
1737
                                        dither->quantize_model,
1,459✔
1738
                                        dither->final_merge_mode,
1,459✔
1739
                                        dither->prefer_float32,
1,459✔
1740
                                        dither->allocator);
1,459✔
1741
    if (SIXEL_FAILED(status)) {
3,023!
1742
        goto end;
×
1743
    }
1744
    status = sixel_palette_set_entries(dither->palette,
6,046✔
1745
                                       buf,
1,459✔
1746
                                       (unsigned int)dither->ncolors,
3,023✔
1747
                                       3,
1748
                                       dither->allocator);
1,459✔
1749
    if (SIXEL_FAILED(status)) {
3,023!
1750
        goto end;
×
1751
    }
1752
    dither->palette->entry_count = (unsigned int)dither->ncolors;
3,023✔
1753
    dither->palette->requested_colors = (unsigned int)dither->reqcolors;
3,023✔
1754
    dither->palette->original_colors = (unsigned int)dither->origcolors;
3,023✔
1755
    dither->palette->depth = 3;
3,023✔
1756

1757
    dither->optimized = 1;
3,023✔
1758
    if (dither->origcolors <= dither->ncolors) {
3,023✔
1759
        dither->method_for_diffuse = SIXEL_DIFFUSE_NONE;
693✔
1760
    }
368✔
1761

1762
    sixel_palette_free_palette(buf, dither->allocator);
3,023✔
1763
    status = SIXEL_OK;
3,023✔
1764

1765
end:
1,564✔
1766
    if (normalized_pixels != NULL) {
3,023!
1767
        sixel_allocator_free(dither->allocator, normalized_pixels);
×
1768
    }
1769
    if (float_pixels != NULL) {
3,023!
1770
        sixel_allocator_free(dither->allocator, float_pixels);
×
1771
    }
1772

1773
    /* decrement ref count */
1774
    sixel_dither_unref(dither);
3,023✔
1775

1776
    return status;
3,766✔
1777
}
743✔
1778

1779

1780
/* set lookup table policy */
1781
SIXELAPI void
1782
sixel_dither_set_lut_policy(
7,143✔
1783
    sixel_dither_t  /* in */ *dither,
1784
    int             /* in */ lut_policy)
1785
{
1786
    int normalized;
5,454✔
1787
    int previous_policy;
5,454✔
1788

1789
    if (dither == NULL) {
7,143!
1790
        return;
1791
    }
1792

1793
    normalized = SIXEL_LUT_POLICY_AUTO;
7,143✔
1794
    if (lut_policy == SIXEL_LUT_POLICY_5BIT
7,227!
1795
        || lut_policy == SIXEL_LUT_POLICY_6BIT
7,143!
1796
        || lut_policy == SIXEL_LUT_POLICY_CERTLUT
7,143!
1797
        || lut_policy == SIXEL_LUT_POLICY_EYTZINGER
7,143!
1798
        || lut_policy == SIXEL_LUT_POLICY_NONE
7,143✔
1799
        || lut_policy == SIXEL_LUT_POLICY_VPTE) {
206!
1800
        normalized = lut_policy;
7,143✔
1801
    }
3,435✔
1802
    previous_policy = dither->lut_policy;
7,143✔
1803
    if (previous_policy == normalized) {
7,143✔
1804
        return;
1,483✔
1805
    }
1806

1807
    /*
1808
     * Policy transitions for the shared LUT mirror the previous cache flow:
1809
     *
1810
     *   [lut] --policy change--> (drop) --rebuild--> [lut]
1811
     */
1812
    dither->lut_policy = normalized;
4,068✔
1813
    if (dither->palette != NULL) {
4,068!
1814
        dither->palette->lut_policy = normalized;
4,068✔
1815
    }
1,952✔
1816
    if (dither->palette != NULL && dither->palette->lut != NULL) {
4,068!
1817
        sixel_lut_unref(dither->palette->lut);
×
1818
        dither->palette->lut = NULL;
×
1819
    }
1820
}
3,435!
1821

1822

1823
/* get lookup table policy */
1824
SIXELAPI int
1825
sixel_dither_get_lut_policy(
×
1826
    sixel_dither_t  /* in */ *dither)
1827
{
1828
    int policy;
1829

1830
    policy = SIXEL_LUT_POLICY_AUTO;
×
1831
    if (dither != NULL) {
×
1832
        policy = dither->lut_policy;
×
1833
    }
1834

1835
    return policy;
×
1836
}
1837

1838

1839
/* set diffusion type, choose from enum methodForDiffuse */
1840
SIXELAPI void
1841
sixel_dither_set_diffusion_type(
4,068✔
1842
    sixel_dither_t  /* in */ *dither,
1843
    int             /* in */ method_for_diffuse)
1844
{
1845
    if (method_for_diffuse == SIXEL_DIFFUSE_AUTO) {
4,068✔
1846
        if (dither->ncolors > 16) {
2,213✔
1847
            method_for_diffuse = SIXEL_DIFFUSE_FS;
843✔
1848
        } else {
843✔
1849
            method_for_diffuse = SIXEL_DIFFUSE_ATKINSON;
431✔
1850
        }
1851
    }
1,047✔
1852
    dither->method_for_diffuse = method_for_diffuse;
4,068✔
1853
}
4,068✔
1854

1855

1856
/* set scan order for diffusion */
1857
SIXELAPI void
1858
sixel_dither_set_diffusion_scan(
4,068✔
1859
    sixel_dither_t  /* in */ *dither,
1860
    int             /* in */ method_for_scan)
1861
{
1862
    if (method_for_scan != SIXEL_SCAN_AUTO &&
4,116!
1863
            method_for_scan != SIXEL_SCAN_RASTER &&
2,440!
1864
            method_for_scan != SIXEL_SCAN_SERPENTINE) {
48✔
1865
        method_for_scan = SIXEL_SCAN_RASTER;
×
1866
    }
1867
    dither->method_for_scan = method_for_scan;
4,068✔
1868
}
4,068✔
1869

1870

1871
/* set carry buffer mode for diffusion */
1872
SIXELAPI void
1873
sixel_dither_set_diffusion_carry(
4,068✔
1874
    sixel_dither_t  /* in */ *dither,
1875
    int             /* in */ method_for_carry)
1876
{
1877
    if (method_for_carry != SIXEL_CARRY_AUTO &&
4,146!
1878
            method_for_carry != SIXEL_CARRY_DISABLE &&
2,194!
1879
            method_for_carry != SIXEL_CARRY_ENABLE) {
78✔
1880
        method_for_carry = SIXEL_CARRY_AUTO;
×
1881
    }
1882
    dither->method_for_carry = method_for_carry;
4,068✔
1883
}
4,068✔
1884

1885

1886
/* get number of palette colors */
1887
SIXELAPI int
1888
sixel_dither_get_num_of_palette_colors(
×
1889
    sixel_dither_t  /* in */ *dither)
1890
{
1891
    return dither->ncolors;
×
1892
}
1893

1894

1895
/* get number of histogram colors */
1896
SIXELAPI int
1897
sixel_dither_get_num_of_histogram_colors(
2,815✔
1898
    sixel_dither_t /* in */ *dither)  /* dither context object */
1899
{
1900
    return dither->origcolors;
2,815✔
1901
}
1902

1903

1904
/* typoed: remained for keeping compatibility */
1905
SIXELAPI int
1906
sixel_dither_get_num_of_histgram_colors(
×
1907
    sixel_dither_t /* in */ *dither)  /* dither context object */
1908
{
1909
    return sixel_dither_get_num_of_histogram_colors(dither);
×
1910
}
1911

1912

1913
/* get palette */
1914
SIXELAPI unsigned char *
1915
sixel_dither_get_palette(
×
1916
    sixel_dither_t /* in */ *dither)  /* dither context object */
1917
{
1918
    if (dither == NULL || dither->palette == NULL) {
×
1919
        return NULL;
1920
    }
1921

1922
    return dither->palette->entries;
×
1923
}
1924

1925
SIXELAPI SIXELSTATUS
1926
sixel_dither_get_quantized_palette(sixel_dither_t *dither,
4,152✔
1927
                                   sixel_palette_t **pppalette)
1928
{
1929
    if (pppalette == NULL) {
4,152!
1930
        return SIXEL_BAD_ARGUMENT;
1931
    }
1932
    *pppalette = NULL;
4,152✔
1933

1934
    if (dither == NULL || dither->palette == NULL) {
4,152!
1935
        return SIXEL_RUNTIME_ERROR;
1936
    }
1937

1938
    sixel_palette_ref(dither->palette);
4,152✔
1939
    *pppalette = dither->palette;
4,152✔
1940

1941
    return SIXEL_OK;
4,152✔
1942
}
1,991✔
1943

1944

1945
/* set palette */
1946
SIXELAPI void
1947
sixel_dither_set_palette(
668✔
1948
    sixel_dither_t /* in */ *dither,   /* dither context object */
1949
    unsigned char  /* in */ *palette)
1950
{
1951
    if (dither == NULL || dither->palette == NULL) {
668!
1952
        return;
1953
    }
1954

1955
    (void)sixel_palette_set_entries(dither->palette,
987✔
1956
                                    palette,
319✔
1957
                                    (unsigned int)dither->ncolors,
668✔
1958
                                    3,
1959
                                    dither->allocator);
319✔
1960
}
319✔
1961

1962

1963
/* set the factor of complexion color correcting */
1964
SIXELAPI void
1965
sixel_dither_set_complexion_score(
26✔
1966
    sixel_dither_t /* in */ *dither,  /* dither context object */
1967
    int            /* in */ score)    /* complexion score (>= 1) */
1968
{
1969
    dither->complexion = score;
26✔
1970
    if (dither->palette != NULL) {
26!
1971
        dither->palette->complexion = score;
26✔
1972
    }
12✔
1973
}
26✔
1974

1975

1976
/* set whether omitting palette difinition */
1977
SIXELAPI void
1978
sixel_dither_set_body_only(
×
1979
    sixel_dither_t /* in */ *dither,     /* dither context object */
1980
    int            /* in */ bodyonly)    /* 0: output palette section
1981
                                            1: do not output palette section  */
1982
{
1983
    dither->bodyonly = bodyonly;
×
1984
}
×
1985

1986

1987
/* set whether optimize palette size */
1988
SIXELAPI void
1989
sixel_dither_set_optimize_palette(
3,119✔
1990
    sixel_dither_t /* in */ *dither,   /* dither context object */
1991
    int            /* in */ do_opt)    /* 0: optimize palette size
1992
                                          1: don't optimize palette size */
1993
{
1994
    dither->optimize_palette = do_opt;
3,119✔
1995
}
3,119✔
1996

1997

1998
/* set pixelformat */
1999
SIXELAPI void
2000
sixel_dither_set_pixelformat(
14,585✔
2001
    sixel_dither_t /* in */ *dither,     /* dither context object */
2002
    int            /* in */ pixelformat) /* one of enum pixelFormat */
2003
{
2004
    /* Keep the float32 preference aligned with the requested pixelformat. */
2005
    dither->pixelformat = pixelformat;
14,585✔
2006
    dither->prefer_float32 =
14,585✔
2007
        SIXEL_PIXELFORMAT_IS_FLOAT32(pixelformat) ? 1 : 0;
10,311✔
2008
}
13,473✔
2009

2010

2011
/* toggle SIXEL reversible palette mode */
2012
SIXELAPI void
2013
sixel_dither_set_sixel_reversible(
2,815✔
2014
    sixel_dither_t /* in */ *dither,
2015
    int            /* in */ enable)
2016
{
2017
    /*
2018
     * The diagram below shows how the flag routes palette generation:
2019
     *
2020
     *   pixels --> [histogram]
2021
     *                  |
2022
     *                  v
2023
     *           (optional reversible snap)
2024
     *                  |
2025
     *                  v
2026
     *               palette
2027
     */
2028
    if (dither == NULL) {
2,815!
2029
        return;
2030
    }
2031
    dither->sixel_reversible = enable ? 1 : 0;
2,815✔
2032
    if (dither->palette != NULL) {
2,815!
2033
        dither->palette->sixel_reversible = dither->sixel_reversible;
2,815✔
2034
    }
1,363✔
2035
}
1,363✔
2036

2037
/* select final merge policy */
2038
SIXELAPI void
2039
sixel_dither_set_final_merge(
2,849✔
2040
    sixel_dither_t /* in */ *dither,
2041
    int            /* in */ final_merge)
2042
{
2043
    int mode;
2,167✔
2044

2045
    if (dither == NULL) {
2,849!
2046
        return;
2047
    }
2048
    mode = SIXEL_FINAL_MERGE_AUTO;
2,849✔
2049
    if (final_merge == SIXEL_FINAL_MERGE_NONE
2,849✔
2050
        || final_merge == SIXEL_FINAL_MERGE_WARD) {
2,846✔
2051
        mode = final_merge;
44✔
2052
    } else if (final_merge == SIXEL_FINAL_MERGE_AUTO) {
2,801!
2053
        mode = SIXEL_FINAL_MERGE_AUTO;
2,763✔
2054
    }
1,339✔
2055
    dither->final_merge_mode = mode;
2,849✔
2056
    if (dither->palette != NULL) {
2,849!
2057
        dither->palette->final_merge = mode;
2,849✔
2058
    }
1,377✔
2059
}
1,377!
2060

2061
/* set transparent */
2062
SIXELAPI void
2063
sixel_dither_set_transparent(
×
2064
    sixel_dither_t /* in */ *dither,      /* dither context object */
2065
    int            /* in */ transparent)  /* transparent color index */
2066
{
2067
    dither->keycolor = transparent;
×
2068
}
×
2069

2070

2071
/* set transparent */
2072
sixel_index_t *
2073
sixel_dither_apply_palette(
3,367✔
2074
    sixel_dither_t  /* in */ *dither,
2075
    unsigned char   /* in */ *pixels,
2076
    int             /* in */ width,
2077
    int             /* in */ height)
2078
{
2079
    SIXELSTATUS status = SIXEL_FALSE;
3,367✔
2080
    size_t bufsize;
2,568✔
2081
    size_t normalized_size;
2,568✔
2082
    size_t total_pixels;
2,568✔
2083
    sixel_index_t *dest = NULL;
3,367✔
2084
    int ncolors;
2,568✔
2085
    int method_for_scan;
2,568✔
2086
    int method_for_carry;
2,568✔
2087
    unsigned char *normalized_pixels = NULL;
3,367✔
2088
    unsigned char *input_pixels;
2,568✔
2089
    float *float_pipeline_pixels = NULL;
3,367✔
2090
    int owns_float_pipeline = 0;
3,367✔
2091
    int pipeline_pixelformat;
2,568✔
2092
    int prefer_float_pipeline;
2,568✔
2093
    sixel_palette_t *palette;
2,568✔
2094
    int dest_owned;
2,568✔
2095
    int parallel_active = 0;
3,367✔
2096
#if SIXEL_ENABLE_THREADS
2097
    int parallel_band_height = 0;
2,862✔
2098
    int parallel_overlap = 0;
2,862✔
2099
    int parallel_threads = 1;
2,862✔
2100
#endif  /* SIXEL_ENABLE_THREADS */
2101
    sixel_logger_t *logger = NULL;
3,367✔
2102
    int wcomp1;
2,568✔
2103
    int wcomp2;
2,568✔
2104
    int wcomp3;
2,568✔
2105
#if SIXEL_ENABLE_THREADS
2106
    int shared_lut;
2,064✔
2107
#endif  /* SIXEL_ENABLE_THREADS */
2108

2109
    /* ensure dither object is not null */
2110
    if (dither == NULL) {
3,367!
2111
        sixel_helper_set_additional_message(
×
2112
            "sixel_dither_apply_palette: dither is null.");
2113
        status = SIXEL_BAD_ARGUMENT;
×
2114
        goto end;
×
2115
    }
2116

2117
    sixel_dither_ref(dither);
3,367✔
2118

2119
    palette = dither->palette;
3,367✔
2120
    if (palette == NULL) {
3,367!
2121
        sixel_helper_set_additional_message(
×
2122
            "sixel_dither_apply_palette: palette is null.");
2123
        status = SIXEL_BAD_ARGUMENT;
×
2124
        goto end;
×
2125
    }
2126

2127
    parallel_active = dither->pipeline_parallel_active;
3,367✔
2128
#if SIXEL_ENABLE_THREADS
2129
    parallel_band_height = dither->pipeline_band_height;
2,862✔
2130
    parallel_overlap = dither->pipeline_band_overlap;
2,862✔
2131
    parallel_threads = dither->pipeline_dither_threads;
2,862✔
2132
#endif  /* SIXEL_ENABLE_THREADS */
2133
    logger = dither->pipeline_logger;
3,367✔
2134

2135
    if (!parallel_active && logger != NULL) {
3,367!
2136
        sixel_logger_logf(logger,
×
2137
                          "worker",
2138
                          "dither",
2139
                          "start",
2140
                          0,
2141
                          0,
2142
                          0,
2143
                          height,
2144
                          0,
2145
                          height,
2146
                          "serial dither begin height=%d",
2147
                          height);
2148
    }
2149

2150
    if (parallel_active && dither->optimize_palette != 0) {
3,367!
2151
        /*
2152
         * Palette minimization rewrites the palette entries in place.
2153
         * Parallel bands would race on the shared table, so fall back to
2154
         * the serial path when the feature is active.
2155
         */
2156
        parallel_active = 0;
1,695✔
2157
    }
2158

2159
    /*
2160
     * Force lookup shared flags to initialize before worker threads start.
2161
     * The env helpers use lazy initialization, so touching them here avoids
2162
     * thread sanitizer reports when parallel dither starts.
2163
     */
2164
    (void)sixel_lookup_env_shared_certlut();
3,367✔
2165
    (void)sixel_lookup_env_shared_5bit();
3,367✔
2166
    (void)sixel_lookup_env_shared_6bit();
3,367✔
2167

2168
    /*
2169
     * Inform lookup helpers whether concurrent palette application will run.
2170
     * VPTE caches rely on this hint when TLS is unavailable so they can
2171
     * disable shared caches during parallel dithering while remaining enabled
2172
     * for serial passes.
2173
     */
2174
    sixel_lookup_set_parallel_dither_active(parallel_active);
3,367✔
2175

2176
    bufsize = (size_t)(width * height) * sizeof(sixel_index_t);
3,367✔
2177
    total_pixels = (size_t)width * (size_t)height;
3,367✔
2178
    pipeline_pixelformat = dither->pixelformat;
3,367✔
2179
    /*
2180
     * Reuse the externally allocated index buffer when the pipeline has
2181
     * already provisioned storage for the producer/worker hand-off.
2182
     */
2183
    if (dither->pipeline_index_buffer != NULL &&
3,367!
2184
            dither->pipeline_index_size >= bufsize) {
108!
2185
        dest = dither->pipeline_index_buffer;
57✔
2186
        dest_owned = 0;
57✔
2187
    } else {
57✔
2188
        dest = (sixel_index_t *)sixel_allocator_malloc(dither->allocator,
4,820✔
2189
                                                       bufsize);
1,561✔
2190
        if (dest == NULL) {
3,259!
2191
            sixel_helper_set_additional_message(
×
2192
                "sixel_dither_new: sixel_allocator_malloc() failed.");
2193
            status = SIXEL_BAD_ALLOCATION;
×
2194
            goto end;
×
2195
        }
2196
        dest_owned = 1;
1,564✔
2197
    }
2198
    dither->pipeline_index_owned = dest_owned;
3,367✔
2199

2200
    /*
2201
     * Disable palette caching when the caller selected the NONE policy so
2202
     * every pixel lookup performs a direct palette scan.  Other quality
2203
     * modes continue to honor the requested LUT policy, including "full".
2204
     */
2205
    if (dither->lut_policy == SIXEL_LUT_POLICY_NONE) {
3,367✔
2206
        dither->optimized = 0;
26✔
2207
    }
12✔
2208

2209
    if (dither->optimized) {
3,367✔
2210
        if (!sixel_palette_is_builtin_mono(palette)) {
3,272✔
2211
            int policy;
2,448✔
2212
            policy = dither->lut_policy;
3,207✔
2213
            if (policy != SIXEL_LUT_POLICY_CERTLUT
3,249!
2214
                && policy != SIXEL_LUT_POLICY_5BIT
3,207!
2215
                && policy != SIXEL_LUT_POLICY_6BIT
3,207!
2216
                && policy != SIXEL_LUT_POLICY_EYTZINGER
3,207!
2217
                && policy != SIXEL_LUT_POLICY_VPTE) {
1,594!
2218
                policy = SIXEL_LUT_POLICY_6BIT;
×
2219
            }
2220
            if (palette->lut == NULL) {
3,207!
2221
                status = sixel_lut_new(&palette->lut,
×
2222
                                       policy,
2223
                                       palette->allocator);
2224
                if (SIXEL_FAILED(status)) {
×
2225
                    sixel_helper_set_additional_message(
×
2226
                        "sixel_dither_apply_palette: lut allocation failed.");
2227
                    goto end;
×
2228
                }
2229
            }
2230
            if (policy == SIXEL_LUT_POLICY_CERTLUT) {
3,207!
2231
                if (dither->method_for_largest == SIXEL_LARGE_LUM) {
×
2232
                    wcomp1 = dither->complexion * 299;
×
2233
                    wcomp2 = 587;
×
2234
                    wcomp3 = 114;
×
2235
                } else {
2236
                    wcomp1 = dither->complexion;
×
2237
                    wcomp2 = 1;
×
2238
                    wcomp3 = 1;
×
2239
                }
2240
            } else {
2241
                wcomp1 = dither->complexion;
3,207✔
2242
                wcomp2 = 1;
3,207✔
2243
                wcomp3 = 1;
3,207✔
2244
            }
2245
            status = sixel_lut_configure(palette->lut,
6,414✔
2246
                                         palette->entries,
3,207✔
2247
                                         palette->entries_float32,
3,207✔
2248
                                         palette->depth,
1,545✔
2249
                                         palette->float_depth,
1,545✔
2250
                                         (int)palette->entry_count,
3,207✔
2251
                                         dither->complexion,
1,545✔
2252
                                         wcomp1,
1,545✔
2253
                                         wcomp2,
1,545✔
2254
                                         wcomp3,
1,545✔
2255
                                         policy,
1,545✔
2256
                                         dither->pixelformat);
1,545✔
2257
            if (SIXEL_FAILED(status)) {
3,207!
2258
                sixel_helper_set_additional_message(
×
2259
                    "sixel_dither_apply_palette: lut configuration failed.");
2260
                goto end;
×
2261
            }
2262
        }
1,545!
2263
    }
1,575✔
2264

2265
    pipeline_pixelformat = dither->pixelformat;
3,367✔
2266
    prefer_float_pipeline =
3,364✔
2267
        sixel_dither_method_supports_float_pipeline(dither);
3,367✔
2268
    if (pipeline_pixelformat == SIXEL_PIXELFORMAT_RGB888) {
3,367✔
2269
        input_pixels = pixels;
1,111✔
2270
    } else if (SIXEL_PIXELFORMAT_IS_FLOAT32(pipeline_pixelformat)
2,213!
2271
               && prefer_float_pipeline) {
1,105✔
2272
        input_pixels = pixels;
498✔
2273
    } else {
498✔
2274
        normalized_size = (size_t)width * (size_t)height * 3U;
26✔
2275
        normalized_pixels
14✔
2276
            = (unsigned char *)sixel_allocator_malloc(dither->allocator,
38✔
2277
                                                      normalized_size);
12✔
2278
        if (normalized_pixels == NULL) {
26!
2279
            sixel_helper_set_additional_message(
×
2280
                "sixel_dither_new: sixel_allocator_malloc() failed.");
2281
            status = SIXEL_BAD_ALLOCATION;
×
2282
            goto end;
×
2283
        }
2284
        status = sixel_helper_normalize_pixelformat(normalized_pixels,
38✔
2285
                                                    &dither->pixelformat,
12✔
2286
                                                    pixels, dither->pixelformat,
12✔
2287
                                                    width, height);
12✔
2288
        if (SIXEL_FAILED(status)) {
26!
2289
            goto end;
×
2290
        }
2291
        input_pixels = normalized_pixels;
12✔
2292
        pipeline_pixelformat = SIXEL_PIXELFORMAT_RGB888;
12✔
2293
    }
2294
    if (prefer_float_pipeline
3,367!
2295
        && pipeline_pixelformat == SIXEL_PIXELFORMAT_RGB888
3,364!
2296
        && total_pixels > 0U) {
498!
2297
        status = sixel_dither_promote_rgb888_to_float32(
×
2298
            &float_pipeline_pixels,
2299
            input_pixels,
2300
            total_pixels,
2301
            dither->allocator);
2302
        if (SIXEL_SUCCEEDED(status) && float_pipeline_pixels != NULL) {
×
2303
            input_pixels = (unsigned char *)float_pipeline_pixels;
×
2304
            pipeline_pixelformat = SIXEL_PIXELFORMAT_RGBFLOAT32;
×
2305
            owns_float_pipeline = 1;
×
2306
        } else {
2307
            prefer_float_pipeline = 0;
1,746✔
2308
            status = SIXEL_OK;
1,746✔
2309
        }
2310
    } else if (prefer_float_pipeline
1,681!
2311
               && !SIXEL_PIXELFORMAT_IS_FLOAT32(pipeline_pixelformat)) {
1,618✔
2312
        prefer_float_pipeline = 0;
2313
    }
2314

2315
    method_for_scan = dither->method_for_scan;
3,367✔
2316
    if (method_for_scan == SIXEL_SCAN_AUTO) {
3,367✔
2317
        method_for_scan = SIXEL_SCAN_RASTER;
2,665✔
2318
    }
1,294✔
2319

2320
    method_for_carry = dither->method_for_carry;
3,367✔
2321
    if (method_for_carry == SIXEL_CARRY_AUTO) {
3,367✔
2322
        method_for_carry = SIXEL_CARRY_DISABLE;
3,198✔
2323
    }
1,540✔
2324

2325
    palette->lut_policy = dither->lut_policy;
3,367✔
2326
    palette->method_for_largest = dither->method_for_largest;
3,367✔
2327
#if SIXEL_ENABLE_THREADS
2328
    if (parallel_active && parallel_threads > 1
2,862!
2329
            && parallel_band_height > 0) {
93!
2330
        sixel_parallel_dither_plan_t plan;
45✔
2331
        int adjusted_overlap;
45✔
2332
        int adjusted_height;
45✔
2333

2334
        adjusted_overlap = parallel_overlap;
54✔
2335
        if (adjusted_overlap < 0) {
54!
2336
            adjusted_overlap = 0;
2337
        }
2338
        adjusted_height = parallel_band_height;
54✔
2339
        if (adjusted_height < 6) {
54!
2340
            adjusted_height = 6;
2341
        }
2342
        if ((adjusted_height % 6) != 0) {
54!
2343
            adjusted_height = ((adjusted_height + 5) / 6) * 6;
2344
        }
2345
        if (adjusted_overlap > adjusted_height / 2) {
54!
2346
            adjusted_overlap = adjusted_height / 2;
2347
        }
2348

2349
        memset(&plan, 0, sizeof(plan));
54!
2350
        plan.dest = dest;
54✔
2351
        plan.pixels = input_pixels;
54✔
2352
        plan.palette = palette;
54✔
2353
        plan.allocator = dither->allocator;
54✔
2354
        plan.dither = dither;
54✔
2355
        plan.width = width;
54✔
2356
        plan.height = height;
54✔
2357
        plan.band_height = adjusted_height;
54✔
2358
        plan.overlap = adjusted_overlap;
54✔
2359
        plan.method_for_diffuse = dither->method_for_diffuse;
54✔
2360
        plan.method_for_scan = method_for_scan;
54✔
2361
        plan.method_for_carry = method_for_carry;
54✔
2362
        plan.optimize_palette = dither->optimized;
54✔
2363
        plan.optimize_palette_entries = dither->optimize_palette;
54✔
2364
        plan.complexion = dither->complexion;
54✔
2365
        plan.lut_policy = dither->lut_policy;
54✔
2366
        plan.method_for_largest = dither->method_for_largest;
54✔
2367
        plan.reqcolor = dither->ncolors;
54✔
2368
        plan.pixelformat = pipeline_pixelformat;
54✔
2369
        /* Carry the pipeline pinning preference as a strict 0/1 flag. */
2370
        plan.pin_threads = dither->pipeline_pin_threads != 0 ? 1 : 0;
54✔
2371
        plan.logger = logger;
54✔
2372

2373
#if SIXEL_ENABLE_THREADS
2374
        shared_lut = 1;
54✔
2375
        if (plan.lut_policy == SIXEL_LUT_POLICY_CERTLUT) {
54!
2376
            shared_lut = sixel_lookup_env_shared_certlut();
2377
        } else if (plan.lut_policy == SIXEL_LUT_POLICY_5BIT) {
54!
2378
            shared_lut = sixel_lookup_env_shared_5bit();
2379
        } else if (plan.lut_policy == SIXEL_LUT_POLICY_6BIT) {
54!
2380
            shared_lut = sixel_lookup_env_shared_6bit();
2381
        }
2382
        if (shared_lut != 0) {
18!
2383
            plan.lut = palette->lut;
54✔
2384
        } else {
18✔
2385
            plan.lut = NULL;
2386
        }
2387
#else
2388
        plan.lut = palette->lut;
2389
#endif  /* SIXEL_ENABLE_THREADS */
2390

2391
        if (plan.lut != NULL && dither->optimized != 0
54!
2392
                && plan.lut_policy != SIXEL_LUT_POLICY_NONE) {
54!
2393
            if (plan.lut_policy == SIXEL_LUT_POLICY_CERTLUT) {
54!
2394
                if (plan.method_for_largest == SIXEL_LARGE_LUM) {
×
2395
                    wcomp1 = dither->complexion * 299;
2396
                    wcomp2 = 587;
2397
                    wcomp3 = 114;
2398
                } else {
2399
                    wcomp1 = dither->complexion;
2400
                    wcomp2 = 1;
2401
                    wcomp3 = 1;
2402
                }
2403
            } else {
2404
                wcomp1 = dither->complexion;
54✔
2405
                wcomp2 = 1;
54✔
2406
                wcomp3 = 1;
54✔
2407
            }
2408
            status = sixel_lut_configure(plan.lut,
108✔
2409
                                         plan.palette->entries,
54✔
2410
                                         plan.palette->entries_float32,
54✔
2411
                                         plan.palette->depth,
18✔
2412
                                         plan.palette->float_depth,
18✔
2413
                                         (int)plan.palette->entry_count,
54✔
2414
                                         dither->complexion,
18✔
2415
                                         wcomp1,
18✔
2416
                                         wcomp2,
18✔
2417
                                         wcomp3,
18✔
2418
                                         plan.lut_policy,
18✔
2419
                                         plan.pixelformat);
18✔
2420
            if (SIXEL_FAILED(status)) {
54!
2421
                goto end;
2422
            }
2423
        }
18✔
2424

2425
        status = sixel_dither_apply_palette_parallel(&plan,
54✔
2426
                                                     parallel_threads);
18✔
2427
        ncolors = dither->ncolors;
54✔
2428
    } else
18!
2429
#endif
2430
    {
2431
        status = sixel_dither_resolve_indexes(dest,
4,913✔
2432
                                              input_pixels,
1,600✔
2433
                                              width,
1,600✔
2434
                                              height,
1,600✔
2435
                                              3,
2436
                                              palette,
1,600✔
2437
                                              dither->ncolors,
1,600✔
2438
                                              dither->method_for_diffuse,
1,600✔
2439
                                              method_for_scan,
1,600✔
2440
                                              method_for_carry,
1,600✔
2441
                                              dither->optimized,
1,600✔
2442
                                              dither->optimize_palette,
1,600✔
2443
                                              dither->complexion,
1,600✔
2444
                                              dither->lut_policy,
1,600✔
2445
                                              dither->method_for_largest,
1,600✔
2446
                                              &ncolors,
2447
                                              dither->allocator,
1,600✔
2448
                                              dither,
1,600✔
2449
                                              pipeline_pixelformat);
1,600✔
2450
    }
2451
    if (SIXEL_FAILED(status)) {
3,367!
2452
        if (dest != NULL && dest_owned) {
×
2453
            sixel_allocator_free(dither->allocator, dest);
×
2454
        }
2455
        dest = NULL;
×
2456
        goto end;
×
2457
    }
2458

2459
    dither->ncolors = ncolors;
3,367✔
2460
    palette->entry_count = (unsigned int)ncolors;
3,367✔
2461

2462
end:
1,749✔
2463
    if (!parallel_active && logger != NULL) {
3,367!
2464
        int last_row;
2465

2466
        last_row = height > 0 ? height - 1 : 0;
×
2467
        sixel_logger_logf(logger,
×
2468
                          "worker",
2469
                          "dither",
2470
                          "finish",
2471
                          0,
2472
                          last_row,
2473
                          0,
2474
                          height,
2475
                          0,
2476
                          height,
2477
                          "serial status=%d",
2478
                          status);
2479
    }
2480
    if (normalized_pixels != NULL) {
3,367✔
2481
        sixel_allocator_free(dither->allocator, normalized_pixels);
26✔
2482
    }
12✔
2483
    if (float_pipeline_pixels != NULL && owns_float_pipeline) {
3,367!
2484
        sixel_allocator_free(dither->allocator, float_pipeline_pixels);
×
2485
    }
2486
    sixel_dither_unref(dither);
3,367✔
2487
    sixel_lookup_set_parallel_dither_active(0);
3,367✔
2488
    dither->pipeline_index_buffer = NULL;
3,367✔
2489
    dither->pipeline_index_owned = 0;
3,367✔
2490
    dither->pipeline_index_size = 0;
3,367✔
2491
    dither->pipeline_parallel_active = 0;
3,367✔
2492
    dither->pipeline_band_height = 0;
3,367✔
2493
    dither->pipeline_band_overlap = 0;
3,367✔
2494
    dither->pipeline_dither_threads = 0;
3,367✔
2495
    dither->pipeline_image_height = 0;
3,367✔
2496
    dither->pipeline_logger = NULL;
3,367✔
2497
    return dest;
3,367✔
2498
}
822✔
2499

2500

2501
/*
2502
 * Verify reference counting works when new dithers are created and unrefed
2503
 * multiple times.
2504
 */
2505

2506
/* emacs Local Variables:      */
2507
/* emacs mode: c               */
2508
/* emacs tab-width: 4          */
2509
/* emacs indent-tabs-mode: nil */
2510
/* emacs c-basic-offset: 4     */
2511
/* emacs End:                  */
2512
/* vim: set expandtab ts=4 sts=4 sw=4 : */
2513
/* 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