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

saitoha / libsixel / 24423996296

14 Apr 2026 09:29PM UTC coverage: 85.588% (-0.03%) from 85.614%
24423996296

push

github

saitoha
python: reject invalid buffer types in encode_bytes

102457 of 214394 branches covered (47.79%)

7 of 8 new or added lines in 1 file covered. (87.5%)

226 existing lines in 7 files now uncovered.

124795 of 145809 relevant lines covered (85.59%)

17542093.85 hits per line

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

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

23
import glob
4✔
24
import locale
4✔
25
import os
4✔
26
import pathlib
4✔
27
import struct
4✔
28
from ctypes import (
4✔
29
    cdll,
30
    c_void_p,
31
    c_int,
32
    c_byte,
33
    c_char_p,
34
    POINTER,
35
    byref,
36
    CFUNCTYPE,
37
    string_at,
38
    cast,
39
)
40
from ctypes.util import find_library
4✔
41

42
# locale.getencoding() is available on newer Python versions. Fall back to
43
# getpreferredencoding() for Python 3.9/3.10 test environments.
44
def _resolve_locale_encoding(default="utf-8"):
4✔
45
    getencoding = getattr(locale, "getencoding", None)
4✔
46
    if callable(getencoding):
4✔
47
        encoding = getencoding()
4✔
48
    else:
49
        try:
×
50
            encoding = locale.getpreferredencoding(False)
×
51
        except TypeError:
×
52
            encoding = locale.getpreferredencoding()
×
53
    if not encoding:
4✔
54
        encoding = default
×
55
    return encoding
4✔
56

57
# limitations
58
SIXEL_OUTPUT_PACKET_SIZE     = 16384
4✔
59
SIXEL_PALETTE_MIN            = 2
4✔
60
SIXEL_PALETTE_MAX            = 256
4✔
61
SIXEL_USE_DEPRECATED_SYMBOLS = 1
4✔
62
SIXEL_ALLOCATE_BYTES_MAX     = 1024 * 1024 * 128  # up to 128M
4✔
63
SIXEL_WIDTH_LIMIT            = 1000000
4✔
64
SIXEL_HEIGHT_LIMIT           = 1000000
4✔
65

66
# loader settings
67
SIXEL_DEFALUT_GIF_DELAY      = 1
4✔
68

69
# loader option identifiers for sixel_loader_setopt().  The numeric values need
70
# to stay in sync with include/sixel.h.in so that Python callers can configure
71
# the loader object precisely.  Keeping the mapping here prevents mysterious
72
# breakage when new options are introduced in the C API.
73
#
74
#        +-------------------------------+
75
#        |  Python option -> C option    |
76
#        +-------------------------------+
77
#        | REQUIRE_STATIC  -> (1)        |
78
#        | USE_PALETTE     -> (2)        |
79
#        | REQCOLORS       -> (3)        |
80
#        | BGCOLOR         -> (4)        |
81
#        | LOOP_CONTROL    -> (5)        |
82
#        | INSECURE        -> (6)        |
83
#        | CANCEL_FLAG     -> (7)        |
84
#        | LOADER_ORDER    -> (8)        |
85
#        | CONTEXT         -> (9)        |
86
#        | WIC_ICO_MINSIZE -> (10)       |
87
#        | START_FRAME_NO  -> (11)       |
88
#        | BGCOLOR_SOURCE  -> (12)       |
89
#        +-------------------------------+
90
SIXEL_LOADER_OPTION_REQUIRE_STATIC = 1
4✔
91
SIXEL_LOADER_OPTION_USE_PALETTE = 2
4✔
92
SIXEL_LOADER_OPTION_REQCOLORS = 3
4✔
93
SIXEL_LOADER_OPTION_BGCOLOR = 4
4✔
94
SIXEL_LOADER_OPTION_LOOP_CONTROL = 5
4✔
95
SIXEL_LOADER_OPTION_INSECURE = 6
4✔
96
SIXEL_LOADER_OPTION_CANCEL_FLAG = 7
4✔
97
SIXEL_LOADER_OPTION_LOADER_ORDER = 8
4✔
98
SIXEL_LOADER_OPTION_CONTEXT = 9
4✔
99
SIXEL_LOADER_OPTION_WIC_ICO_MINSIZE = 10
4✔
100
SIXEL_LOADER_OPTION_START_FRAME_NO = 11
4✔
101
SIXEL_LOADER_OPTION_BGCOLOR_SOURCE = 12
4✔
102

103
SIXEL_LOADER_BGCOLOR_SOURCE_EXPLICIT = 0
4✔
104
SIXEL_LOADER_BGCOLOR_SOURCE_ENV = 1
4✔
105

106
# return value
107
SIXEL_OK              = 0x0000
4✔
108
SIXEL_FALSE           = 0x1000
4✔
109

110
# error codes
111
SIXEL_RUNTIME_ERROR        = (SIXEL_FALSE         | 0x0100)  # runtime error
4✔
112
SIXEL_LOGIC_ERROR          = (SIXEL_FALSE         | 0x0200)  # logic error
4✔
113
SIXEL_FEATURE_ERROR        = (SIXEL_FALSE         | 0x0300)  # feature not enabled
4✔
114
SIXEL_LIBC_ERROR           = (SIXEL_FALSE         | 0x0400)  # errors caused by curl
4✔
115
SIXEL_CURL_ERROR           = (SIXEL_FALSE         | 0x0500)  # errors occures in libc functions
4✔
116
SIXEL_JPEG_ERROR           = (SIXEL_FALSE         | 0x0600)  # errors occures in libjpeg functions
4✔
117
SIXEL_PNG_ERROR            = (SIXEL_FALSE         | 0x0700)  # errors occures in libpng functions
4✔
118
SIXEL_GDK_ERROR            = (SIXEL_FALSE         | 0x0800)  # errors occures in gdk functions
4✔
119
SIXEL_GD_ERROR             = (SIXEL_FALSE         | 0x0900)  # errors occures in gd functions
4✔
120
SIXEL_STBI_ERROR           = (SIXEL_FALSE         | 0x0a00)  # errors occures in stb_image functions
4✔
121
SIXEL_STBIW_ERROR          = (SIXEL_FALSE         | 0x0b00)  # errors occures in stb_image_write functions
4✔
122
SIXEL_COM_ERROR            = (SIXEL_FALSE         | 0x0c00)  # errors occures in COM functions
4✔
123
SIXEL_WIC_ERROR            = (SIXEL_FALSE         | 0x0d00)  # errors occures in WIC functions
4✔
124

125
SIXEL_INTERRUPTED          = (SIXEL_OK            | 0x0001)  # interrupted by a signal
4✔
126

127
SIXEL_BAD_ALLOCATION       = (SIXEL_RUNTIME_ERROR | 0x0001)  # malloc() failed
4✔
128
SIXEL_BAD_ARGUMENT         = (SIXEL_RUNTIME_ERROR | 0x0002)  # bad argument detected
4✔
129
SIXEL_BAD_INPUT            = (SIXEL_RUNTIME_ERROR | 0x0003)  # bad input detected
4✔
130
SIXEL_BAD_INTEGER_OVERFLOW = (SIXEL_RUNTIME_ERROR | 0x0004)  # integer overflow
4✔
131

132
SIXEL_NOT_IMPLEMENTED      = (SIXEL_FEATURE_ERROR | 0x0001)  # feature not implemented
4✔
133

134
def SIXEL_SUCCEEDED(status):
4✔
135
    return (((status) & 0x1000) == 0)
4✔
136

137
def SIXEL_FAILED(status):
4✔
138
    return (((status) & 0x1000) != 0)
4✔
139

140
# method for finding the largest dimension for splitting,
141
# and sorting by that component
142
SIXEL_LARGE_AUTO = 0x0   # choose automatically the method for finding the largest dimension
4✔
143
SIXEL_LARGE_NORM = 0x1   # simply comparing the range in RGB space
4✔
144
SIXEL_LARGE_LUM  = 0x2   # transforming into luminosities before the comparison
4✔
145
SIXEL_LARGE_PCA  = 0x3   # cut along the first principal component
4✔
146

147
# method for choosing a color from the box
148
SIXEL_REP_AUTO           = 0x0  # choose automatically the method for selecting representative color from each box
4✔
149
SIXEL_REP_CENTER_BOX     = 0x1  # choose the center of the box
4✔
150
SIXEL_REP_AVERAGE_COLORS = 0x2  # choose the average all the color in the box (specified in Heckbert's paper)
4✔
151
SIXEL_REP_AVERAGE_PIXELS = 0x3  # choose the average all the pixels in the box
4✔
152

153
# method for diffusing
154
SIXEL_DIFFUSE_AUTO         = 0x0  # choose diffusion type automatically
4✔
155
SIXEL_DIFFUSE_NONE         = 0x1  # don't diffuse
4✔
156
SIXEL_DIFFUSE_ATKINSON     = 0x2  # diffuse with Bill Atkinson's method
4✔
157
SIXEL_DIFFUSE_FS           = 0x3  # diffuse with Floyd-Steinberg method
4✔
158
SIXEL_DIFFUSE_JAJUNI       = 0x4  # diffuse with Jarvis, Judice & Ninke method
4✔
159
SIXEL_DIFFUSE_STUCKI       = 0x5  # diffuse with Stucki's method
4✔
160
SIXEL_DIFFUSE_BURKES       = 0x6  # diffuse with Burkes' method
4✔
161
SIXEL_DIFFUSE_A_DITHER     = 0x7  # positionally stable arithmetic dither
4✔
162
SIXEL_DIFFUSE_X_DITHER     = 0x8  # positionally stable arithmetic xor based dither
4✔
163
SIXEL_DIFFUSE_BLUENOISE_DITHER = 0x9  # positionally stable bluenoise dither
4✔
164
SIXEL_DIFFUSE_LSO2         = 0xa  # libsixel method based on variable error
4✔
165
                                  # diffusion
166
SIXEL_DIFFUSE_INTERFRAME   = 0xb  # interframe error diffusion
4✔
167
SIXEL_DIFFUSE_SIERRA1      = 0xc  # diffuse with Sierra Lite method
4✔
168
SIXEL_DIFFUSE_SIERRA2      = 0xd  # diffuse with Sierra Two-row method
4✔
169
SIXEL_DIFFUSE_SIERRA3      = 0xe  # diffuse with Sierra-3 method
4✔
170

171

172
_PYTHON_BITS = struct.calcsize("P") * 8
4✔
173

174

175
def _detect_elf_class(data: bytes) -> str:
4✔
176
    """Return the ELF class width as a string."""
177

178
    if not data.startswith(b"\x7fELF"):
4✔
179
        return ""
2✔
180

181
    elf_class = data[4]
2✔
182
    if elf_class == 1:
2✔
183
        return "32"
×
184
    if elf_class == 2:
2✔
185
        return "64"
2✔
186
    return ""
×
187

188

189
def _detect_macho_class(data: bytes) -> str:
4✔
190
    """Return the Mach-O class width as a string."""
191

192
    magic = data[:4]
2✔
193
    macho_32 = (0xfeedface, 0xcefaedfe)
2✔
194
    macho_64 = (0xfeedfacf, 0xcffaedfe)
2✔
195

196
    value = int.from_bytes(magic, byteorder="big", signed=False)
2✔
197
    if value in macho_32:
2✔
198
        return "32"
×
199
    if value in macho_64:
2✔
200
        return "64"
2✔
201
    return ""
×
202

203

204
def _detect_pe_class(bin_path: pathlib.Path, data: bytes) -> str:
4✔
205
    """Return the PE/COFF class width as a string."""
206

207
    if not data.startswith(b"MZ") or len(data) < 0x40:
×
208
        return ""
×
209

210
    pe_offset = int.from_bytes(data[0x3C:0x40], byteorder="little")
×
211
    try:
×
212
        with bin_path.open("rb") as handle:
×
213
            handle.seek(pe_offset)
×
214
            signature = handle.read(6)
×
215
    except OSError:
×
216
        return ""
×
217

218
    if not signature.startswith(b"PE\0\0") or len(signature) < 6:
×
219
        return ""
×
220

221
    machine = struct.unpack("<H", signature[4:6])[0]
×
222
    if machine in (0x014C,):
×
223
        return "32"
×
224
    if machine in (0x8664,):
×
225
        return "64"
×
226
    return ""
×
227

228

229
def _detect_library_bits(bin_path: pathlib.Path) -> str:
4✔
230
    """Inspect the binary header to determine its architecture width."""
231

232
    try:
4✔
233
        with bin_path.open("rb") as handle:
4✔
234
            head = handle.read(1024)
4✔
235
    except OSError:
×
236
        return ""
×
237

238
    for detector in (_detect_elf_class, _detect_macho_class):
4✔
239
        width = detector(head)
4✔
240
        if width:
4✔
241
            return width
4✔
242

243
    return _detect_pe_class(bin_path, head)
×
244
# scan order for diffusing
245
SIXEL_SCAN_AUTO       = 0x0  # choose scan order automatically
4✔
246
SIXEL_SCAN_RASTER     = 0x1  # scan from left to right on each line
4✔
247
SIXEL_SCAN_SERPENTINE = 0x2  # alternate scan direction per line
4✔
248

249
# quality modes
250
SIXEL_QUALITY_AUTO      = 0x0  # choose quality mode automatically
4✔
251
SIXEL_QUALITY_HIGH      = 0x1  # high quality palette construction
4✔
252
SIXEL_QUALITY_LOW       = 0x2  # low quality palette construction
4✔
253
SIXEL_QUALITY_FULL      = 0x3  # full quality palette construction
4✔
254
SIXEL_QUALITY_HIGHCOLOR = 0x4  # high color
4✔
255
SIXEL_QUANTIZE_MODEL_AUTO      = 0x0  # choose palette solver automatically
4✔
256
SIXEL_QUANTIZE_MODEL_MEDIANCUT = 0x1  # Heckbert median-cut solver
4✔
257
SIXEL_QUANTIZE_MODEL_KMEANS    = 0x2  # k-means palette solver
4✔
258
SIXEL_QUANTIZE_MODEL_KMEDOIDS  = 0x3  # k-medoids palette solver
4✔
259
SIXEL_FINAL_MERGE_AUTO         = 0x0  # select final merge automatically
4✔
260
                                      # (defaults to none)
261
SIXEL_FINAL_MERGE_NONE         = 0x1  # disable final merge stage
4✔
262
SIXEL_FINAL_MERGE_WARD         = 0x2  # Ward hierarchical clustering merge
4✔
263

264
# built-in dither
265
SIXEL_BUILTIN_MONO_DARK   = 0x0  # monochrome terminal with dark background
4✔
266
SIXEL_BUILTIN_MONO_LIGHT  = 0x1  # monochrome terminal with light background
4✔
267
SIXEL_BUILTIN_XTERM16     = 0x2  # xterm 16color
4✔
268
SIXEL_BUILTIN_XTERM256    = 0x3  # xterm 256color
4✔
269
SIXEL_BUILTIN_VT340_MONO  = 0x4  # vt340 monochrome
4✔
270
SIXEL_BUILTIN_VT340_COLOR = 0x5  # vt340 color
4✔
271
SIXEL_BUILTIN_G1          = 0x6  # 1bit grayscale
4✔
272
SIXEL_BUILTIN_G2          = 0x7  # 2bit grayscale
4✔
273
SIXEL_BUILTIN_G4          = 0x8  # 4bit grayscale
4✔
274
SIXEL_BUILTIN_G8          = 0x9  # 8bit grayscale
4✔
275

276
# offset value of pixelFormat
277
SIXEL_FORMATTYPE_COLOR     = (0)
4✔
278
SIXEL_FORMATTYPE_GRAYSCALE = (1 << 6)
4✔
279
SIXEL_FORMATTYPE_PALETTE   = (1 << 7)
4✔
280

281
# pixelformat type of input image
282
#   NOTE: for compatibility, the value of PIXELFORAMT_COLOR_RGB888 must be 3
283
SIXEL_PIXELFORMAT_RGB555   = (SIXEL_FORMATTYPE_COLOR     | 0x01) # 15bpp
4✔
284
SIXEL_PIXELFORMAT_RGB565   = (SIXEL_FORMATTYPE_COLOR     | 0x02) # 16bpp
4✔
285
SIXEL_PIXELFORMAT_RGB888   = (SIXEL_FORMATTYPE_COLOR     | 0x03) # 24bpp
4✔
286
SIXEL_PIXELFORMAT_BGR555   = (SIXEL_FORMATTYPE_COLOR     | 0x04) # 15bpp
4✔
287
SIXEL_PIXELFORMAT_BGR565   = (SIXEL_FORMATTYPE_COLOR     | 0x05) # 16bpp
4✔
288
SIXEL_PIXELFORMAT_BGR888   = (SIXEL_FORMATTYPE_COLOR     | 0x06) # 24bpp
4✔
289
SIXEL_PIXELFORMAT_ARGB8888 = (SIXEL_FORMATTYPE_COLOR     | 0x10) # 32bpp
4✔
290
SIXEL_PIXELFORMAT_RGBA8888 = (SIXEL_FORMATTYPE_COLOR     | 0x11) # 32bpp
4✔
291
SIXEL_PIXELFORMAT_ABGR8888 = (SIXEL_FORMATTYPE_COLOR     | 0x12) # 32bpp
4✔
292
SIXEL_PIXELFORMAT_BGRA8888 = (SIXEL_FORMATTYPE_COLOR     | 0x13) # 32bpp
4✔
293
SIXEL_PIXELFORMAT_RGBFLOAT32 = (SIXEL_FORMATTYPE_COLOR   | 0x20) # 96bpp float
4✔
294
SIXEL_PIXELFORMAT_LINEARRGBFLOAT32 = (SIXEL_FORMATTYPE_COLOR | 0x21)
4✔
295
SIXEL_PIXELFORMAT_OKLABFLOAT32 = (SIXEL_FORMATTYPE_COLOR | 0x22)
4✔
296
SIXEL_PIXELFORMAT_CIELABFLOAT32 = (SIXEL_FORMATTYPE_COLOR | 0x23)
4✔
297
SIXEL_PIXELFORMAT_DIN99DFLOAT32 = (SIXEL_FORMATTYPE_COLOR | 0x24)
4✔
298
SIXEL_PIXELFORMAT_G1       = (SIXEL_FORMATTYPE_GRAYSCALE | 0x00) # 1bpp grayscale
4✔
299
SIXEL_PIXELFORMAT_G2       = (SIXEL_FORMATTYPE_GRAYSCALE | 0x01) # 2bpp grayscale
4✔
300
SIXEL_PIXELFORMAT_G4       = (SIXEL_FORMATTYPE_GRAYSCALE | 0x02) # 4bpp grayscale
4✔
301
SIXEL_PIXELFORMAT_G8       = (SIXEL_FORMATTYPE_GRAYSCALE | 0x03) # 8bpp grayscale
4✔
302
SIXEL_PIXELFORMAT_AG88     = (SIXEL_FORMATTYPE_GRAYSCALE | 0x13) # 16bpp gray+alpha
4✔
303
SIXEL_PIXELFORMAT_GA88     = (SIXEL_FORMATTYPE_GRAYSCALE | 0x23) # 16bpp gray+alpha
4✔
304
SIXEL_PIXELFORMAT_PAL1     = (SIXEL_FORMATTYPE_PALETTE   | 0x00) # 1bpp palette
4✔
305
SIXEL_PIXELFORMAT_PAL2     = (SIXEL_FORMATTYPE_PALETTE   | 0x01) # 2bpp palette
4✔
306
SIXEL_PIXELFORMAT_PAL4     = (SIXEL_FORMATTYPE_PALETTE   | 0x02) # 4bpp palette
4✔
307
SIXEL_PIXELFORMAT_PAL8     = (SIXEL_FORMATTYPE_PALETTE   | 0x03) # 8bpp palette
4✔
308

309
# colorspace modes for clustering/working/output options
310
SIXEL_COLORSPACE_GAMMA  = 0x0  # gamma-encoded RGB
4✔
311
SIXEL_COLORSPACE_LINEAR = 0x1  # linear RGB
4✔
312
SIXEL_COLORSPACE_OKLAB  = 0x2  # OKLab
4✔
313
SIXEL_COLORSPACE_SMPTEC = 0x3  # SMPTE-C gamma
4✔
314
SIXEL_COLORSPACE_CIELAB = 0x4  # CIELAB
4✔
315
SIXEL_COLORSPACE_DIN99D = 0x5  # DIN99d
4✔
316

317
# palette type
318
SIXEL_PALETTETYPE_AUTO     = 0   # choose palette type automatically
4✔
319
SIXEL_PALETTETYPE_HLS      = 1   # HLS colorspace
4✔
320
SIXEL_PALETTETYPE_RGB      = 2   # RGB colorspace
4✔
321

322
# policies of SIXEL encoding
323
SIXEL_ENCODEPOLICY_AUTO    = 0   # choose encoding policy automatically
4✔
324
SIXEL_ENCODEPOLICY_FAST    = 1   # encode as fast as possible
4✔
325
SIXEL_ENCODEPOLICY_SIZE    = 2   # encode to as small sixel sequence as possible
4✔
326

327
# LUT policy constants mirror the C header so that Python callers can request
328
# the exact histogram backend they need.  Keeping the numeric values in sync is
329
# critical because the encoder forwards them directly to libsixel.
330
#
331
#   auto ----> channel depth based decision
332
#                |
333
#         +------+------+---------+
334
#         |      |       |
335
#      classic  none  certified
336
#      (5/6bit)        (certlut)
337
#
338
SIXEL_LUT_POLICY_AUTO      = 0x0  # choose LUT width automatically
4✔
339
SIXEL_LUT_POLICY_5BIT      = 0x1  # use legacy 5-bit buckets
4✔
340
SIXEL_LUT_POLICY_6BIT      = 0x2  # use 6-bit RGB buckets
4✔
341
SIXEL_LUT_POLICY_NONE      = 0x4  # disable LUT acceleration
4✔
342
SIXEL_LUT_POLICY_CERTLUT   = 0x5  # certified hierarchical LUT
4✔
343
SIXEL_LUT_POLICY_FHEDT      = 0x6  # Voronoi LUT with 3D EDT refinement
4✔
344
SIXEL_LUT_POLICY_EYTZINGER = 0x7  # Eytzinger implicit binary tree LUT
4✔
345
SIXEL_LUT_POLICY_VPTREE    = 0x8  # VP-tree palette lookup
4✔
346
SIXEL_LUT_POLICY_RBC       = 0x9  # randomized ball cover lookup
4✔
347
SIXEL_LUT_POLICY_MAHALANOBIS = 0xa  # Mahalanobis-aware lookup
4✔
348

349
# method for re-sampling
350
SIXEL_RES_NEAREST          = 0   # Use nearest neighbor method
4✔
351
SIXEL_RES_GAUSSIAN         = 1   # Use guaussian filter
4✔
352
SIXEL_RES_HANNING          = 2   # Use hanning filter
4✔
353
SIXEL_RES_HAMMING          = 3   # Use hamming filter
4✔
354
SIXEL_RES_BILINEAR         = 4   # Use bilinear filter
4✔
355
SIXEL_RES_WELSH            = 5   # Use welsh filter
4✔
356
SIXEL_RES_BICUBIC          = 6   # Use bicubic filter
4✔
357
SIXEL_RES_LANCZOS2         = 7   # Use lanczos-2 filter
4✔
358
SIXEL_RES_LANCZOS3         = 8   # Use lanczos-3 filter
4✔
359
SIXEL_RES_LANCZOS4         = 9   # Use lanczos-4 filter
4✔
360

361
# image format
362
SIXEL_FORMAT_GIF           = 0x0 # read only
4✔
363
SIXEL_FORMAT_PNG           = 0x1 # read/write
4✔
364
SIXEL_FORMAT_BMP           = 0x2 # read only
4✔
365
SIXEL_FORMAT_JPG           = 0x3 # read only
4✔
366
SIXEL_FORMAT_TGA           = 0x4 # read only
4✔
367
SIXEL_FORMAT_WBMP          = 0x5 # read only with --with-gd configure option
4✔
368
SIXEL_FORMAT_TIFF          = 0x6 # read only
4✔
369
SIXEL_FORMAT_SIXEL         = 0x7 # read only
4✔
370
SIXEL_FORMAT_PNM           = 0x8 # read only
4✔
371
SIXEL_FORMAT_GD2           = 0x9 # read only with --with-gd configure option
4✔
372
SIXEL_FORMAT_PSD           = 0xa # read only
4✔
373
SIXEL_FORMAT_HDR           = 0xb # read only
4✔
374

375
# loop mode
376
SIXEL_LOOP_AUTO            = 0   # honer the setting of GIF header
4✔
377
SIXEL_LOOP_FORCE           = 1   # always enable loop
4✔
378
SIXEL_LOOP_DISABLE         = 2   # always disable loop
4✔
379

380
# setopt flags
381
SIXEL_OPTFLAG_INPUT            = 'i'  # -i, --input: specify input file name.
4✔
382
SIXEL_OPTFLAG_OUTPUT           = 'o'  # -o, --output: specify output file name.
4✔
383
SIXEL_OPTFLAG_OUTFILE          = 'o'  # -o, --outfile: specify output file name.
4✔
384
SIXEL_OPTFLAG_HAS_GRI_ARG_LIMIT = 'R'  # -R, --gri-limit: clamp DECGRI arguments to 255.
4✔
385
SIXEL_OPTFLAG_PRECISION        = '.'  # -., --precision: control quantization precision.
4✔
386
SIXEL_OPTFLAG_THREADS          = '='  # -=, --threads: override encoder/decoder thread count.
4✔
387
SIXEL_OPTFLAG_LOADERS          = 'L'  # -L LIST, --loaders=LIST: override loader order (WIC: :ico_minsize=SIZE).
4✔
388
SIXEL_OPTFLAG_7BIT_MODE        = '7'  # -7, --7bit-mode: for 7bit terminals or printers (default)
4✔
389
SIXEL_OPTFLAG_8BIT_MODE        = '8'  # -8, --8bit-mode: for 8bit terminals or printers
4✔
390
SIXEL_OPTFLAG_6REVERSIBLE      = '6'  # -6, --6reversible: snap palette to reversible tones
4✔
391
SIXEL_OPTFLAG_COLORS           = 'p'  # -p COLORS, --colors=COLORS: specify number of colors
4✔
392
SIXEL_OPTFLAG_MAPFILE          = 'm'  # -m FILE, --mapfile=FILE: specify set of colors
4✔
393
SIXEL_OPTFLAG_MAPFILE_OUTPUT   = 'M'  # -M FILE, --mapfile-output=FILE: export palette file
4✔
394
SIXEL_OPTFLAG_MONOCHROME       = 'e'  # -e, --monochrome: output monochrome sixel image
4✔
395
SIXEL_OPTFLAG_INSECURE         = 'k'  # -k, --insecure: allow to connect to SSL sites without certs
4✔
396
SIXEL_OPTFLAG_INVERT           = 'i'  # -i, --invert: assume the terminal background color
4✔
397
SIXEL_OPTFLAG_HIGH_COLOR       = 'I'  # -I, --high-color: output 15bpp sixel image
4✔
398
SIXEL_OPTFLAG_USE_MACRO        = 'u'  # -u, --use-macro: use DECDMAC and DECINVM sequences
4✔
399
SIXEL_OPTFLAG_MACRO_NUMBER     = 'n'  # -n MACRONO, --macro-number=MACRONO:
4✔
400
                                      #        specify macro register number
401
SIXEL_OPTFLAG_COMPLEXION_SCORE = 'C'  # -C COMPLEXIONSCORE, --complexion-score=COMPLEXIONSCORE:
4✔
402
                                      #        (deprecated) specify an number argument for the
403
                                      #        score of complexion correction.
404
SIXEL_OPTFLAG_IGNORE_DELAY     = 'g'  # -g, --ignore-delay: render GIF animation without delay
4✔
405
SIXEL_OPTFLAG_STATIC           = 'S'  # -S, --static: render animated GIF as a static image
4✔
406
#
407
#   +------------+-------------------------------+
408
#   | short opt  | semantic scope                |
409
#   +------------+-------------------------------+
410
#   | -d         | decoder: dequantize palette   |
411
#   |            | encoder: diffusion selector   |
412
#   | -D         | decoder: emit RGBA (direct)   |
413
#   |            | encoder: legacy pipe-mode     |
414
#   +------------+-------------------------------+
415
#
416
# Python callers use these constants with ``Decoder.setopt``.  The table
417
# keeps the intent obvious when the same letter spans historic features.
418
SIXEL_OPTFLAG_DEQUANTIZE       = 'd'  # -d, --dequantize: repair palette.
4✔
419
SIXEL_OPTFLAG_DIRECT           = 'D'  # -D, --direct: decode to RGBA pixels.
4✔
420
SIXEL_OPTFLAG_SIMILARITY       = 'S'  # -S SCORE, --similarity-score=SCORE:
4✔
421
                                      #        set contour detector similarity
422
SIXEL_OPTFLAG_SIZE             = 's'  # -s SIZE, --segment-size=SIZE:
4✔
423
                                      #        set contour detector segment size
424
SIXEL_OPTFLAG_EDGE             = 'e'  # -e MODE, --detect-edge=MODE:
4✔
425
                                      #        set contour edge detector mode
426
SIXEL_OPTFLAG_DIFFUSION        = 'd'  # -d DIFFUSIONTYPE, --diffusion=DIFFUSIONTYPE:
4✔
427
                                      #          choose diffusion method which used with -p option.
428
                                      #          DIFFUSIONTYPE is one of them:
429
                                      #            auto     -> choose diffusion type
430
                                      #                        automatically (default)
431
                                      #            none     -> do not diffuse
432
                                      #            fs       -> Floyd-Steinberg method
433
                                      #            atkinson -> Bill Atkinson's method
434
                                      #            jajuni   -> Jarvis, Judice & Ninke
435
                                      #            stucki   -> Stucki's method
436
                                      #            burkes   -> Burkes' method
437
                                      #            sierra1  -> Sierra Lite method
438
                                      #            sierra2  -> Sierra Two-row method
439
                                      #            sierra3  -> Sierra-3 method
440
                                      #            a_dither -> positionally stable
441
                                      #                        arithmetic dither
442
                                      #            x_dither -> positionally stable
443
                                      #                        arithmetic xor based dither
444
                                      #            lso2     -> libsixel method based on
445
                                      #                        variable error diffusion
446
                                      #                        + jitter
447
SIXEL_OPTFLAG_FIND_LARGEST     = 'f'  # -f FINDTYPE, --find-largest=FINDTYPE:
4✔
448
                                      #         choose method for finding the largest
449
                                      #         dimension of median cut boxes for
450
                                      #         splitting, make sense only when -p
451
                                      #         option (color reduction) is
452
                                      #         specified
453
                                      #         FINDTYPE is one of them:
454
                                      #           auto -> choose finding method
455
                                      #                   automatically (default)
456
                                      #           norm -> simply comparing the
457
                                      #                   range in RGB space
458
                                      #           lum  -> transforming into
459
                                      #                   luminosities before the
460
                                      #                   comparison
461
                                      #           pca  -> split along the first
462
                                      #                   principal component and
463
                                      #                   cut at weighted median
464

465
SIXEL_OPTFLAG_SELECT_COLOR     = 's'  # -s SELECTTYPE, --select-color=SELECTTYPE
4✔
466
                                      #        choose the method for selecting
467
                                      #        representative color from each
468
                                      #        median-cut box, make sense only
469
                                      #        when -p option (color reduction) is
470
                                      #        specified
471
                                      #        SELECTTYPE is one of them:
472
                                      #          auto      -> choose selecting
473
                                      #                       method automatically
474
                                      #                       (default)
475
                                      #          center    -> choose the center of
476
                                      #                       the box
477
                                      #          average    -> calculate the color
478
                                      #                       average into the box
479
                                      #          histogram -> similar with average
480
                                      #                       but considers color
481
                                      #                       histogram
482
SIXEL_OPTFLAG_QUANTIZE_MODEL   = 'Q'  # -Q MODEL, --quantize-model=MODEL:
4✔
483
                                      #        choose the palette solver.
484
                                      #        MODEL is one of them:
485
                                      #          auto     -> select solver
486
                                      #                      automatically
487
                                      #                      (Heckbert)
488
                                      #          heckbert -> Heckbert median-cut
489
                                      #          kmeans   -> k-means palette
490
                                      #                      clustering
491
                                      #        kmeans accepts suboptions in:
492
                                      #          kmeans:key=value[:key=value...]
493
                                      #        supported suboptions:
494
                                      #          inittype (or i):
495
                                      #            auto, none, pca
496
                                      #          threshold (or t):
497
                                      #            float in 0.0-0.5
498
                                      #          binning (or b):
499
                                      #            auto, none, hard, soft
500
                                      #          binbits (or n):
501
                                      #            integer in 4-8
502
                                      #          mapping (or m):
503
                                      #            uniform, srgb
504
                                      #          softdist (or d):
505
                                      #            trilinear
506
                                      #          autoratio (or r):
507
                                      #            integer in 1-1048576
508
                                      #          feedback (or f):
509
                                      #            off, on
510
SIXEL_OPTFLAG_CROP             = 'c'  # -c REGION, --crop=REGION:
4✔
511
                                      #        crop source image to fit the
512
                                      #        specified geometry. REGION should
513
                                      #        be formatted as '%dx%d+%d+%d'
514

515
SIXEL_OPTFLAG_WIDTH            = 'w'  # -w WIDTH, --width=WIDTH:
4✔
516
                                      #        resize image to specified width
517
                                      #        WIDTH is represented by the
518
                                      #        following syntax
519
                                      #          auto       -> preserving aspect
520
                                      #                        ratio (default)
521
                                      #          <number>%  -> scale width with
522
                                      #                        given percentage
523
                                      #          <number>   -> scale width with
524
                                      #                        pixel counts
525
                                      #          <number>c  -> scale width with
526
                                      #                        terminal cell count
527
                                      #          <number>px -> scale width with
528
                                      #                        pixel counts
529

530
SIXEL_OPTFLAG_HEIGHT           = 'h'  # -h HEIGHT, --height=HEIGHT:
4✔
531
                                      #         resize image to specified height
532
                                      #         HEIGHT is represented by the
533
                                      #         following syntax
534
                                      #           auto       -> preserving aspect
535
                                      #                         ratio (default)
536
                                      #           <number>%  -> scale height with
537
                                      #                         given percentage
538
                                      #           <number>   -> scale height with
539
                                      #                         pixel counts
540
                                      #           <number>c  -> scale height with
541
                                      #                         terminal cell count
542
                                      #           <number>px -> scale height with
543
                                      #                         pixel counts
544

545
SIXEL_OPTFLAG_RESAMPLING       = 'r'  # -r RESAMPLINGTYPE, --resampling=RESAMPLINGTYPE:
4✔
546
                                      #        choose resampling filter used
547
                                      #        with -w or -h option (scaling)
548
                                      #        RESAMPLINGTYPE is one of them:
549
                                      #          nearest  -> Nearest-Neighbor
550
                                      #                      method
551
                                      #          gaussian -> Gaussian filter
552
                                      #          hanning  -> Hanning filter
553
                                      #          hamming  -> Hamming filter
554
                                      #          bilinear -> Bilinear filter
555
                                      #                      (default)
556
                                      #          welsh    -> Welsh filter
557
                                      #          bicubic  -> Bicubic filter
558
                                      #          lanczos2 -> Lanczos-2 filter
559
                                      #          lanczos3 -> Lanczos-3 filter
560
                                      #          lanczos4 -> Lanczos-4 filter
561

562
SIXEL_OPTFLAG_QUALITY          = 'q'  # -q QUALITYMODE, --quality=QUALITYMODE:
4✔
563
                                      #        select quality of color
564
                                      #        quanlization.
565
                                      #          auto -> decide quality mode
566
                                      #                  automatically (default)
567
                                      #          low  -> low quality and high
568
                                      #                  speed mode
569
                                      #          high -> high quality and low
570
                                      #                  speed mode
571
                                      #          full -> full quality and careful
572
                                      #                  speed mode
573

574
SIXEL_OPTFLAG_LOOPMODE         = 'l'  # -l LOOPMODE, --loop-control=LOOPMODE:
4✔
575
                                      #        select loop control mode for GIF
576
                                      #        animation.
577
                                      #          auto    -> honor the setting of
578
                                      #                     GIF header (default)
579
                                      #          force   -> always enable loop
580
                                      #          disable -> always disable loop
581

582
SIXEL_OPTFLAG_START_FRAME      = 'T'  # -T FRAME_NO, --start-frame=FRAME_NO:
4✔
583
                                      #        set the first animation frame index
584
                                      #          non-negative -> absolute index
585
                                      #          negative     -> offset from end
586

587
SIXEL_OPTFLAG_PALETTE_TYPE     = 't'  # -t PALETTETYPE, --palette-type=PALETTETYPE:
4✔
588
                                      #        select palette color space type
589
                                      #          auto -> choose palette type
590
                                      #                  automatically (default)
591
                                      #          hls  -> use HLS color space
592
                                      #          rgb  -> use RGB color space
593

594
SIXEL_OPTFLAG_BUILTIN_PALETTE  = 'b'  # -b BUILTINPALETTE, --builtin-palette=BUILTINPALETTE:
4✔
595
                                      #        select built-in palette type
596
                                      #          xterm16    -> X default 16 color map
597
                                      #          xterm256   -> X default 256 color map
598
                                      #          vt340mono  -> VT340 monochrome map
599
                                      #          vt340color -> VT340 color map
600
                                      #          gray1      -> 1bit grayscale map
601
                                      #          gray2      -> 2bit grayscale map
602
                                      #          gray4      -> 4bit grayscale map
603
                                      #          gray8      -> 8bit grayscale map
604

605
SIXEL_OPTFLAG_ENCODE_POLICY    = 'E'  # -E ENCODEPOLICY, --encode-policy=ENCODEPOLICY:
4✔
606
                                      #        select encoding policy
607
                                      #          auto -> choose encoding policy
608
                                      #                  automatically (default)
609
                                      #          fast -> encode as fast as possible
610
                                      #          size -> encode to as small sixel
611
                                      #                  sequence as possible
612
SIXEL_OPTFLAG_LUT_POLICY        = '~'  # -~ LOOKUPPOLICY,
4✔
613
                                      #   --lookup-policy=LOOKUPPOLICY:
614
                                      #        choose histogram lookup width.
615
                                      #          auto    -> follow pixel depth
616
                                      #          5bit    -> force 5-bit buckets
617
                                      #          6bit    -> force 6-bit buckets
618
                                      #                     (RGB inputs)
619
                                      #          none    -> disable LUT caching
620
                                      #                     and scan directly
621
                                      #          certlut -> certified
622
                                      #                     hierarchical LUT
623
                                      #                     with zero error
624
                                      #          fhedt    -> Voronoi grid built
625
                                      #                     via 3D EDT with
626
                                      #                     optional
627
                                      #                     refinement
628
SIXEL_OPTFLAG_CLUSTERING_COLORSPACE = 'X'  # -X COLORSPACE, --clustering-colorspace=COLORSPACE:
4✔
629
                                          #        select palette clustering space.
630
                                          #          gamma  -> keep gamma encoded pixels
631
                                          #          linear -> convert to linear RGB
632
                                          #          oklab  -> operate in OKLab
633
                                          #          cielab -> operate in CIELAB
634
                                          #          din99d -> operate in DIN99d
635
SIXEL_OPTFLAG_WORKING_COLORSPACE = 'W'  # -W WORKING_COLORSPACE, --working-colorspace=COLORSPACE:
4✔
636
                                      #        select internal working space.
637
                                      #          gamma  -> keep gamma encoded pixels
638
                                      #          linear -> convert to linear RGB
639
                                      #          oklab  -> operate in OKLab
640
                                      #          cielab -> operate in CIELAB
641
                                      #          din99d -> operate in DIN99d
642
SIXEL_OPTFLAG_OUTPUT_COLORSPACE = 'U'  # -U OUTPUT_COLORSPACE, --output-colorspace=COLORSPACE:
4✔
643
                                      #        select output buffer color space.
644
                                      #          gamma   -> sRGB gamma encoded output
645
                                      #          linear  -> linear RGB output
646
                                      #          smpte-c -> SMPTE-C gamma encoded output
647
SIXEL_OPTFLAG_ORMODE           = 'O'  # -O, --ormode: output ormode sixel image
4✔
648

649
SIXEL_OPTFLAG_BGCOLOR          = 'B'  # -B BGCOLOR, --bgcolor=BGCOLOR:
4✔
650
                                      #        specify background color
651
                                      #        BGCOLOR is represented by the
652
                                      #        following syntax
653
                                      #          #rgb
654
                                      #          #rrggbb
655
                                      #          #rrrgggbbb
656
                                      #          #rrrrggggbbbb
657
                                      #          rgb:r/g/b
658
                                      #          rgb:rr/gg/bb
659
                                      #          rgb:rrr/ggg/bbb
660
                                      #          rgb:rrrr/gggg/bbbb
661

662
SIXEL_OPTFLAG_PENETRATE        = 'P'  # -P, --penetrate: (deprecated)
4✔
663
                                      #        penetrate GNU Screen using DCS
664
                                      #        pass-through sequence
665
SIXEL_OPTFLAG_DRCS             = '@'  # -@ MMV:CHARSET:PATH, --drcs=MMV:CHARSET:PATH:
4✔
666
                                      #        emit extended DRCS tiles, optionally
667
                                      #        overriding mapping revision, charset,
668
                                      #        and tile sink (defaults to 2:1:;
669
                                      #        experimental)
670
SIXEL_OPTFLAG_PIPE_MODE        = 'D'  # -D, --pipe-mode: (deprecated)
4✔
671
                                      #         read source images from stdin continuously
672
SIXEL_OPTFLAG_VERBOSE          = 'v'  # -v, --verbose: show debugging info
4✔
673
SIXEL_OPTFLAG_VERSION          = 'V'  # -V, --version: show version and license info
4✔
674
SIXEL_OPTFLAG_HELP             = 'H'  # -H, --help: show this help
4✔
675

676
_sixel_names = [
4✔
677
    "sixel",
678
    "libsixel",
679
    "sixel-1",
680
    "libsixel-1",
681
    "msys-sixel",
682
    "cygsixel",
683
]
684

685
def _match_library_in_dir(libdir, lib_names):
4✔
686
    """Return the first matching shared library in the given directory.
687

688
    The selection logic is intentionally aligned with the build helper:
689

690
    - Accept both "lib" prefixed and prefixless names.
691
    - Accept .so, .dylib, or .dll (including versioned .so.* files).
692
    - Skip import archives like *.dll.a or *.dll.def.
693
    """
694

695
    prefixes = ["lib", ""]
4✔
696
    suffixes = [".so", ".dylib", ".dll"]
4✔
697

698
    for name in lib_names:
4✔
699
        for prefix in prefixes:
4✔
700
            for suffix in suffixes:
4✔
701
                patterns: list[str]
702
                if suffix == ".so":
4✔
703
                    patterns = [
4✔
704
                        os.path.join(libdir, f"{prefix}{name}*{suffix}"),
705
                        os.path.join(libdir, f"{prefix}{name}*{suffix}.*"),
706
                    ]
707
                else:
708
                    patterns = [
2✔
709
                        os.path.join(libdir, f"{prefix}{name}*{suffix}"),
710
                    ]
711

712
                matches: list[str] = []
4✔
713
                for pattern in patterns:
4✔
714
                    matches.extend(glob.glob(pattern))
4✔
715

716
                filtered: list[str] = []
4✔
717
                for candidate in sorted(set(matches)):
4✔
718
                    if candidate.endswith((".dll.a", ".dll.def")):
4✔
719
                        continue
×
720

721
                    bits = _detect_library_bits(pathlib.Path(candidate))
4✔
722
                    if bits and bits != str(_PYTHON_BITS):
4✔
723
                        continue
×
724

725
                    filtered.append(candidate)
4✔
726

727
                if filtered:
4✔
728
                    return filtered[0]
4✔
729

730
    return None
×
731

732

733
def _prefer_bundled_library(lib_names):
4✔
734
    """Locate a bundled shared library shipped inside the wheel package.
735

736
    Wheel builds copy the shared library into libsixel/_libs so that imports
737
    succeed even when libsixel is not installed system-wide.
738
    """
739

740
    bundle_dir = pathlib.Path(__file__).resolve().parent / "_libs"
4✔
741
    if not bundle_dir.is_dir():
4✔
742
        return None
×
743
    return _match_library_in_dir(str(bundle_dir), lib_names)
4✔
744

745

746
def _prefer_env_library(lib_names):
4✔
747
    """Locate libsixel under LIBSIXEL_LIBDIR when running from a build tree."""
748

749
    libdir = os.environ.get("LIBSIXEL_LIBDIR")
×
750
    if libdir is None:
×
751
        return None
×
752
    return _match_library_in_dir(libdir, lib_names)
×
753

754

755
_lib_path = _prefer_bundled_library(_sixel_names)
4✔
756

757
if _lib_path is None:
4✔
758
    _lib_path = _prefer_env_library(_sixel_names)
×
759

760
if _lib_path is None:
4✔
761
    _lib_path = next(
×
762
        (path for path in (find_library(name) for name in _sixel_names)
763
         if path is not None),
764
        None,
765
    )
766

767
if _lib_path is None:
4✔
768
    raise ImportError(
×
769
        "libsixel not found. Set LIBSIXEL_LIBDIR to the built shared library."
770
    )
771

772
_lib_bits = _detect_library_bits(pathlib.Path(_lib_path))
4✔
773
if _lib_bits and _lib_bits != str(_PYTHON_BITS):
4✔
774
    raise ImportError(
×
775
        f"libsixel {_lib_bits}-bit library is incompatible with "
776
        f"python {_PYTHON_BITS}-bit"
777
    )
778

779
# load shared library
780
_sixel = cdll.LoadLibrary(_lib_path)
4✔
781

782
# convert error status code int formatted string
783
def sixel_helper_format_error(status):
4✔
784
    _sixel.sixel_helper_format_error.restype = c_char_p;
4✔
785
    _sixel.sixel_helper_format_error.argtypes = [c_int];
4✔
786
    return _sixel.sixel_helper_format_error(status)
4✔
787

788

789
# compute pixel depth from pixelformat
790
def sixel_helper_compute_depth(pixelformat):
4✔
791
    _sixel.sixel_helper_compute_depth.restype = c_int
4✔
792
    _sixel.sixel_helper_compute_depth.argtypes = [c_int]
4✔
793
    return _sixel.sixel_helper_compute_depth(pixelformat)
4✔
794

795

796
# generic loader -----------------------------------------------------------
797

798
_sixel_loader_callback_type = CFUNCTYPE(c_int, c_void_p, c_void_p)
4✔
799

800

801
def sixel_loader_new(allocator=c_void_p(None)):
4✔
802
    """Create a loader object that mirrors sixel_loader_new()."""
803

804
    _sixel.sixel_loader_new.restype = c_int
4✔
805
    _sixel.sixel_loader_new.argtypes = [POINTER(c_void_p), c_void_p]
4✔
806

807
    loader = c_void_p(None)
4✔
808
    status = _sixel.sixel_loader_new(byref(loader), allocator)
4✔
809
    if SIXEL_FAILED(status):
4✔
810
        message = sixel_helper_format_error(status)
×
811
        raise RuntimeError(message)
×
812
    return loader
4✔
813

814

815
def sixel_loader_ref(loader):
4✔
816
    """Increase the reference count of a loader object."""
817

818
    _sixel.sixel_loader_ref.restype = None
4✔
819
    _sixel.sixel_loader_ref.argtypes = [c_void_p]
4✔
820
    _sixel.sixel_loader_ref(loader)
4✔
821

822

823
def sixel_loader_unref(loader):
4✔
824
    """Decrease the reference count of a loader object."""
825

826
    _sixel.sixel_loader_unref.restype = None
4✔
827
    _sixel.sixel_loader_unref.argtypes = [c_void_p]
4✔
828
    _sixel.sixel_loader_unref(loader)
4✔
829

830

831
def sixel_loader_setopt(loader, option, value=None):
4✔
832
    """Configure loader behavior via sixel_loader_setopt().
833

834
    The helper routes Python values into the pointer-based C API while keeping
835
    the conversion rules in plain sight:
836

837
        +-----------+---------------------------+---------------------+
838
        | Option    | Expected Python value     | Example             |
839
        +-----------+---------------------------+---------------------+
840
        | STATIC    | bool/int or None          | True                |
841
        | PALETTE   | bool/int or None          | 0                   |
842
        | REQCOLORS | int or None               | 256                 |
843
        | BGCOLOR   | iterable[3] or None       | (0, 0, 0)           |
844
        | LOOP      | int or None               | SIXEL_LOOP_FORCE    |
845
        | INSECURE  | bool/int or None          | False               |
846
        | CANCEL    | ctypes pointer / address  | byref(c_int(0))     |
847
        | ORDER     | str/bytes/bytearray or None | "builtin"         |
848
        | CONTEXT   | ctypes pointer / address  | c_void_p(id(obj))   |
849
        | WIC SIZE  | int or None               | 64                  |
850
        +-----------+---------------------------+---------------------+
851

852
    Values left as ``None`` map to NULL so that the C side may install its
853
    default behavior.
854
    """
855

856
    _sixel.sixel_loader_setopt.restype = c_int
4✔
857
    _sixel.sixel_loader_setopt.argtypes = [c_void_p, c_int, c_void_p]
4✔
858

859
    option = int(option)
4✔
860
    pointer_value = c_void_p(None)
4✔
861
    keepalive = None
4✔
862

863
    int_options = {
4✔
864
        SIXEL_LOADER_OPTION_REQUIRE_STATIC,
865
        SIXEL_LOADER_OPTION_USE_PALETTE,
866
        SIXEL_LOADER_OPTION_REQCOLORS,
867
        SIXEL_LOADER_OPTION_LOOP_CONTROL,
868
        SIXEL_LOADER_OPTION_INSECURE,
869
        SIXEL_LOADER_OPTION_WIC_ICO_MINSIZE,
870
        SIXEL_LOADER_OPTION_START_FRAME_NO,
871
    }
872

873
    if option in int_options:
4✔
874
        if value is not None:
4✔
875
            keepalive = c_int(int(value))
4✔
876
            pointer_value = cast(byref(keepalive), c_void_p)
4✔
877
    elif option == SIXEL_LOADER_OPTION_BGCOLOR:
4✔
878
        if value is not None:
4✔
879
            if len(value) != 3:
4✔
880
                raise ValueError("bgcolor expects three components")
4✔
881
            keepalive = (c_byte * 3)(value[0], value[1], value[2])
4✔
882
            pointer_value = cast(keepalive, c_void_p)
4✔
883
    elif option == SIXEL_LOADER_OPTION_LOADER_ORDER:
4✔
884
        if value is not None:
4✔
885
            if isinstance(value, bytes):
4✔
886
                encoded = value
4✔
887
            elif isinstance(value, bytearray):
4✔
888
                encoded = bytes(value)
4✔
889
            elif isinstance(value, str):
4✔
890
                encoded = value.encode('utf-8')
4✔
891
            else:
892
                raise TypeError(
4✔
893
                    "loader_order expects str, bytes, bytearray, or None"
894
                )
895
            keepalive = c_char_p(encoded)
4✔
896
            pointer_value = cast(keepalive, c_void_p)
4✔
897
    elif option in (
4✔
898
        SIXEL_LOADER_OPTION_CANCEL_FLAG,
899
        SIXEL_LOADER_OPTION_CONTEXT,
900
    ):
901
        if value is None:
4✔
902
            pointer_value = c_void_p(None)
×
903
        elif isinstance(value, c_void_p):
4✔
904
            pointer_value = value
4✔
905
        elif isinstance(value, int):
4✔
906
            pointer_value = c_void_p(value)
4✔
907
        else:
908
            pointer_value = cast(value, c_void_p)
4✔
909
    else:
910
        raise ValueError("unknown loader option: %r" % option)
4✔
911

912
    status = _sixel.sixel_loader_setopt(loader, option, pointer_value)
4✔
913
    if SIXEL_FAILED(status):
4✔
914
        message = sixel_helper_format_error(status)
×
915
        raise RuntimeError(message)
×
916

917

918
def sixel_loader_load_file(loader, filename, fn_load):
4✔
919
    """Load ``filename`` and feed each frame to ``fn_load``.
920

921
    ``fn_load`` receives ``(frame_ptr, context_ptr)`` mirroring the C
922
    signature.  The loader's context pointer may be set via
923
    ``sixel_loader_setopt``.
924
    """
925

926
    if fn_load is None:
4✔
927
        raise ValueError("fn_load callback is required")
4✔
928
    if not callable(fn_load):
4✔
929
        raise TypeError("fn_load callback must be callable")
4✔
930

931
    _sixel.sixel_loader_load_file.restype = c_int
4✔
932
    _sixel.sixel_loader_load_file.argtypes = [
4✔
933
        c_void_p,
934
        c_char_p,
935
        _sixel_loader_callback_type,
936
    ]
937

938
    encoding = _resolve_locale_encoding(default="utf-8")
4✔
939

940
    # The C API expects a non-NULL filename pointer.
941
    # Reject None in the Python wrapper to avoid passing NULL and
942
    # crashing inside the native loader implementation.
943
    if filename is None:
4✔
944
        raise TypeError("filename must be str or bytes, not None")
4✔
945
    elif isinstance(filename, bytes):
4✔
946
        c_filename = filename
4✔
947
    else:
948
        c_filename = filename.encode(encoding)
4✔
949

950
    def _fn_load_local(frame, context):
4✔
951
        return fn_load(frame, context)
4✔
952

953
    callback = _sixel_loader_callback_type(_fn_load_local)
4✔
954
    status = _sixel.sixel_loader_load_file(loader, c_filename, callback)
4✔
955
    if SIXEL_FAILED(status):
4✔
956
        message = sixel_helper_format_error(status)
4✔
957
        raise RuntimeError(message)
4✔
958

959

960
# create new output context object
961
def sixel_output_new(fn_write, priv=None, allocator=c_void_p(None)):
4✔
962
    output = c_void_p(None)
4✔
963

964
    # ctypes callback exceptions do not propagate to the original Python
965
    # caller. Keep the original exception object on the output handle so
966
    # sixel_encode() can re-raise it in the caller context.
967
    output.__callback_exception = None
4✔
968

969
    def _fn_write_local(data, size, priv_from_c):
4✔
970
        try:
4✔
971
            fn_write(string_at(data, size), priv)
4✔
972
        except Exception as exc:
4✔
973
            output.__callback_exception = exc
4✔
974
            return -1
4✔
975
        return size
4✔
976

977
    sixel_write_function = CFUNCTYPE(c_int, c_char_p, c_int, c_void_p)
4✔
978
    _sixel.sixel_output_new.restype = c_int
4✔
979
    _sixel.sixel_output_new.argtypes = [POINTER(c_void_p), sixel_write_function, c_void_p, c_void_p]
4✔
980
    _fn_write = sixel_write_function(_fn_write_local)
4✔
981
    _fn_write.restype = c_int
4✔
982
    _fn_write.argtypes = [sixel_write_function, c_void_p, c_void_p]
4✔
983
    status = _sixel.sixel_output_new(byref(output), _fn_write, c_void_p(None), allocator)
4✔
984
    if SIXEL_FAILED(status):
4✔
985
        message = sixel_helper_format_error(status)
×
986
        raise RuntimeError(message)
×
987
    output.__fn_write = _fn_write
4✔
988
    return output
4✔
989

990

991
# increase reference count of output object (thread-unsafe)
992
def sixel_output_ref(output):
4✔
993
    _sixel.sixel_output_ref.restype = None
4✔
994
    _sixel.sixel_output_ref.argtypes = [c_void_p]
4✔
995
    _sixel.sixel_output_ref(output)
4✔
996

997

998
# decrease reference count of output object (thread-unsafe)
999
def sixel_output_unref(output):
4✔
1000
    _sixel.sixel_output_unref.restype = None
4✔
1001
    _sixel.sixel_output_unref.argtypes = [c_void_p]
4✔
1002
    _sixel.sixel_output_unref(output)
4✔
1003
    output.__fn_write = None
4✔
1004
    output.__callback_exception = None
4✔
1005

1006

1007
# get 8bit output mode which indicates whether it uses C1 control characters
1008
def sixel_output_get_8bit_availability(output):
4✔
1009
    _sixel.sixel_output_get_8bit_availability.restype = c_int
4✔
1010
    _sixel.sixel_output_get_8bit_availability.argtypes = [c_void_p]
4✔
1011
    return _sixel.sixel_output_get_8bit_availability(output)
4✔
1012

1013

1014
# set 8bit output mode state
1015
def sixel_output_set_8bit_availability(output, availability):
4✔
1016
    _sixel.sixel_output_set_8bit_availability.restype = None
4✔
1017
    _sixel.sixel_output_set_8bit_availability.argtypes = [c_void_p, c_int]
4✔
1018
    _sixel.sixel_output_set_8bit_availability(output, availability)
4✔
1019

1020

1021
# set whether limit arguments of DECGRI('!') to 255
1022
def sixel_output_set_gri_arg_limit(output, value):
4✔
1023
    _sixel.sixel_output_set_gri_arg_limit.restype = None
4✔
1024
    _sixel.sixel_output_set_gri_arg_limit.argtypes = [c_void_p, c_int]
4✔
1025
    _sixel.sixel_output_set_gri_arg_limit(output, value)
4✔
1026

1027

1028
# set GNU Screen penetration feature enable or disable
1029
def sixel_output_set_penetrate_multiplexer(output, penetrate):
4✔
1030
    _sixel.sixel_output_set_penetrate_multiplexer.restype = None
4✔
1031
    _sixel.sixel_output_set_penetrate_multiplexer.argtypes = [c_void_p, c_int]
4✔
1032
    _sixel.sixel_output_set_penetrate_multiplexer(output, penetrate)
4✔
1033

1034

1035
# set whether we skip DCS envelope
1036
def sixel_output_set_skip_dcs_envelope(output, skip):
4✔
1037
    _sixel.sixel_output_set_skip_dcs_envelope.restype = None
4✔
1038
    _sixel.sixel_output_set_skip_dcs_envelope.argtypes = [c_void_p, c_int]
4✔
1039
    _sixel.sixel_output_set_skip_dcs_envelope(output, skip)
4✔
1040

1041

1042
# set whether we skip SIXEL header
1043
def sixel_output_set_skip_header(output, skip):
4✔
1044
    _sixel.sixel_output_set_skip_header.restype = None
4✔
1045
    _sixel.sixel_output_set_skip_header.argtypes = [c_void_p, c_int]
4✔
1046
    _sixel.sixel_output_set_skip_header(output, skip)
4✔
1047

1048

1049
# set palette type: RGB or HLS
1050
def sixel_output_set_palette_type(output, palettetype):
4✔
1051
    _sixel.sixel_output_set_palette_type.restype = None
4✔
1052
    _sixel.sixel_output_set_palette_type.argtypes = [c_void_p, c_int]
4✔
1053
    _sixel.sixel_output_set_palette_type(output, palettetype)
4✔
1054

1055

1056
# enable or disable ormode output
1057
def sixel_output_set_ormode(output, ormode):
4✔
1058
    _sixel.sixel_output_set_ormode.restype = None
4✔
1059
    _sixel.sixel_output_set_ormode.argtypes = [c_void_p, c_int]
4✔
1060
    _sixel.sixel_output_set_ormode(output, ormode)
4✔
1061

1062

1063
# set encodeing policy: auto, fast or size
1064
def sixel_output_set_encode_policy(output, encode_policy):
4✔
1065
    _sixel.sixel_output_set_encode_policy.restype = None
4✔
1066
    _sixel.sixel_output_set_encode_policy.argtypes = [c_void_p, c_int]
4✔
1067
    _sixel.sixel_output_set_encode_policy(output, encode_policy)
4✔
1068

1069

1070
# create dither context object
1071
def sixel_dither_new(ncolors, allocator=None):
4✔
1072
    _sixel.sixel_dither_new.restype = c_int
4✔
1073
    _sixel.sixel_dither_new.argtypes = [POINTER(c_void_p), c_int, c_void_p]
4✔
1074
    dither = c_void_p(None)
4✔
1075
    status = _sixel.sixel_dither_new(byref(dither), ncolors, allocator)
4✔
1076
    if SIXEL_FAILED(status):
4✔
1077
        message = sixel_helper_format_error(status)
×
1078
        raise RuntimeError(message)
×
1079
    return dither
4✔
1080

1081

1082
# get built-in dither context object
1083
def sixel_dither_get(builtin_dither):
4✔
1084
    _sixel.sixel_dither_get.restype = c_void_p
4✔
1085
    _sixel.sixel_dither_get.argtypes = [c_int]
4✔
1086
    return _sixel.sixel_dither_get(builtin_dither)
4✔
1087

1088

1089
# destroy dither context object
1090
def sixel_dither_destroy(dither):
4✔
1091
    _sixel.sixel_dither_destroy.restype = None
4✔
1092
    _sixel.sixel_dither_destroy.argtypes = [c_void_p]
4✔
1093
    return _sixel.sixel_dither_destroy(dither)
4✔
1094

1095

1096
# increase reference count of dither context object (thread-unsafe)
1097
def sixel_dither_ref(dither):
4✔
1098
    _sixel.sixel_dither_ref.restype = None
4✔
1099
    _sixel.sixel_dither_ref.argtypes = [c_void_p]
4✔
1100
    return _sixel.sixel_dither_ref(dither)
4✔
1101

1102

1103
# decrease reference count of dither context object (thread-unsafe)
1104
def sixel_dither_unref(dither):
4✔
1105
    _sixel.sixel_dither_unref.restype = None
4✔
1106
    _sixel.sixel_dither_unref.argtypes = [c_void_p]
4✔
1107
    return _sixel.sixel_dither_unref(dither)
4✔
1108

1109

1110
# initialize internal palette from specified pixel buffer
1111
def sixel_dither_initialize(dither, data, width, height, pixelformat,
4✔
1112
                            method_for_largest=SIXEL_LARGE_AUTO,
1113
                            method_for_rep=SIXEL_REP_AUTO,
1114
                            quality_mode=SIXEL_QUALITY_AUTO):
1115
    _sixel.sixel_dither_initialize.restype = c_int
4✔
1116
    _sixel.sixel_dither_initialize.argtypes = [c_void_p, c_char_p, c_int, c_int, c_int,
4✔
1117
                                              c_int, c_int, c_int]
1118
    status = _sixel.sixel_dither_initialize(dither, data, width, height, pixelformat,
4✔
1119
                                            method_for_largest,
1120
                                            method_for_rep,
1121
                                            quality_mode)
1122
    if SIXEL_FAILED(status):
4✔
1123
        message = sixel_helper_format_error(status)
×
1124
        raise RuntimeError(message)
×
1125

1126

1127
# set diffusion type, choose from enum methodForDiffuse
1128
def sixel_dither_set_diffusion_type(dither, method_for_diffuse):
4✔
1129
    _sixel.sixel_dither_set_diffusion_type.restype = None
4✔
1130
    _sixel.sixel_dither_set_diffusion_type.argtypes = [c_void_p, c_int]
4✔
1131
    _sixel.sixel_dither_set_diffusion_type(dither, method_for_diffuse)
4✔
1132

1133

1134
def sixel_dither_set_diffusion_scan(dither, method_for_scan):
4✔
1135
    _sixel.sixel_dither_set_diffusion_scan.restype = None
4✔
1136
    _sixel.sixel_dither_set_diffusion_scan.argtypes = [c_void_p, c_int]
4✔
1137
    _sixel.sixel_dither_set_diffusion_scan(dither, method_for_scan)
4✔
1138

1139

1140
# get number of palette colors
1141
def sixel_dither_get_num_of_palette_colors(dither):
4✔
1142
    _sixel.sixel_dither_get_num_of_palette_colors.restype = c_int
4✔
1143
    _sixel.sixel_dither_get_num_of_palette_colors.argtypes = [c_void_p]
4✔
1144
    return _sixel.sixel_dither_get_num_of_palette_colors(dither)
4✔
1145

1146

1147
# get number of histogram colors */
1148
def sixel_dither_get_num_of_histogram_colors(dither):
4✔
1149
    _sixel.sixel_dither_get_num_of_histogram_colors.restype = c_int
4✔
1150
    _sixel.sixel_dither_get_num_of_histogram_colors.argtypes = [c_void_p]
4✔
1151
    return _sixel.sixel_dither_get_num_of_histogram_colors(dither)
4✔
1152

1153

1154
def sixel_dither_get_palette(dither):
4✔
1155
    _sixel.sixel_dither_get_palette.restype = c_char_p
4✔
1156
    _sixel.sixel_dither_get_palette.argtypes = [c_void_p]
4✔
1157
    cpalette = _sixel.sixel_dither_get_palette(dither)
4✔
1158
    return list(cpalette)
4✔
1159

1160

1161
def sixel_dither_set_palette(dither, palette):
4✔
1162
    _sixel.sixel_dither_set_palette.restype = None
4✔
1163
    _sixel.sixel_dither_set_palette.argtypes = [c_void_p, c_char_p]
4✔
1164
    cpalette = bytes(palette)
4✔
1165
    _sixel.sixel_dither_set_palette(dither, cpalette)
4✔
1166

1167

1168
def sixel_dither_set_complexion_score(dither, score):
4✔
1169
    _sixel.sixel_dither_set_complexion_score.restype = None
4✔
1170
    _sixel.sixel_dither_set_complexion_score.argtypes = [c_void_p, c_int]
4✔
1171
    _sixel.sixel_dither_set_complexion_score(dither, score)
4✔
1172

1173

1174
def sixel_dither_set_body_only(dither, bodyonly):
4✔
1175
    _sixel.sixel_dither_set_body_only.restype = None
4✔
1176
    _sixel.sixel_dither_set_body_only.argtypes = [c_void_p, c_int]
4✔
1177
    _sixel.sixel_dither_set_body_only(dither, bodyonly)
4✔
1178

1179

1180
def sixel_dither_set_optimize_palette(dither, do_opt):
4✔
1181
    _sixel.sixel_dither_set_optimize_palette.restype = None
4✔
1182
    _sixel.sixel_dither_set_optimize_palette.argtypes = [c_void_p, c_int]
4✔
1183
    _sixel.sixel_dither_set_optimize_palette(dither, do_opt)
4✔
1184

1185

1186
def sixel_dither_set_pixelformat(dither, pixelformat):
4✔
1187
    _sixel.sixel_dither_set_pixelformat.restype = None
4✔
1188
    _sixel.sixel_dither_set_pixelformat.argtypes = [c_void_p, c_int]
4✔
1189
    _sixel.sixel_dither_set_pixelformat(dither, pixelformat)
4✔
1190

1191

1192
def sixel_dither_set_transparent(dither, transparent):
4✔
1193
    _sixel.sixel_dither_set_transparent.restype = None
4✔
1194
    _sixel.sixel_dither_set_transparent.argtypes = [c_void_p, c_int]
4✔
1195
    _sixel.sixel_dither_set_transparent(dither, transparent)
4✔
1196

1197

1198
# configure the encoder thread count for band parallelism
1199
def sixel_set_threads(threads):
4✔
1200
    auto_requested = False
4✔
1201
    value = 0
4✔
1202
    text = None
4✔
1203

1204
    if isinstance(threads, bytes):
4✔
1205
        try:
4✔
1206
            text = threads.decode('utf-8').strip()
4✔
1207
        except UnicodeDecodeError as exc:
4✔
1208
            raise ValueError(
4✔
1209
                "threads must be a positive integer or 'auto'"
1210
            ) from exc
1211
    elif isinstance(threads, str):
4✔
1212
        text = threads.strip()
4✔
1213
    else:
1214
        text = None
4✔
1215

1216
    if text is not None:
4✔
1217
        if text.lower() == 'auto':
4✔
1218
            auto_requested = True
4✔
1219
            value = 0
4✔
1220
        else:
1221
            try:
4✔
1222
                value = int(text, 10)
4✔
1223
            except ValueError as exc:
4✔
1224
                raise ValueError(
4✔
1225
                    "threads must be a positive integer or 'auto'"
1226
                ) from exc
1227
    else:
1228
        try:
4✔
1229
            value = int(threads)
4✔
1230
        except (TypeError, ValueError) as exc:
4✔
1231
            raise ValueError(
4✔
1232
                "threads must be a positive integer or 'auto'"
1233
            ) from exc
1234

1235
    if auto_requested is False and value < 1:
4✔
1236
        raise ValueError("threads must be a positive integer or 'auto'")
4✔
1237

1238
    _sixel.sixel_set_threads.restype = None
4✔
1239
    _sixel.sixel_set_threads.argtypes = [c_int]
4✔
1240
    _sixel.sixel_set_threads(value)
4✔
1241

1242

1243
# convert pixels into sixel format and write it to output context
1244
def sixel_encode(pixels, width, height, depth, dither, output):
4✔
1245
    _sixel.sixel_encode.restype = c_int
4✔
1246
    _sixel.sixel_encode.argtypes = [c_char_p, c_int, c_int, c_int, c_void_p, c_void_p]
4✔
1247
    status = _sixel.sixel_encode(pixels, width, height, depth, dither, output)
4✔
1248

1249
    callback_exception = getattr(output, '__callback_exception', None)
4✔
1250
    if callback_exception is not None:
4✔
1251
        output.__callback_exception = None
4✔
1252
        raise callback_exception
4✔
1253

1254
    return status
4✔
1255

1256

1257
# create encoder object
1258
def sixel_encoder_new(allocator=c_void_p(None)):
4✔
1259
    _sixel.sixel_encoder_new.restype = c_int
4✔
1260
    _sixel.sixel_encoder_new.argtypes = [POINTER(c_void_p), c_void_p]
4✔
1261
    encoder = c_void_p(None)
4✔
1262
    status = _sixel.sixel_encoder_new(byref(encoder), allocator)
4✔
1263
    if SIXEL_FAILED(status):
4✔
1264
        message = sixel_helper_format_error(status)
×
1265
        raise RuntimeError(message)
×
1266
    return encoder
4✔
1267

1268

1269
# increase reference count of encoder object (thread-unsafe)
1270
def sixel_encoder_ref(encoder):
4✔
1271
    _sixel.sixel_encoder_ref.restype = None
4✔
1272
    _sixel.sixel_encoder_ref.argtypes = [c_void_p]
4✔
1273
    _sixel.sixel_encoder_ref(encoder)
4✔
1274

1275

1276
# decrease reference count of encoder object (thread-unsafe)
1277
def sixel_encoder_unref(encoder):
4✔
1278
    _sixel.sixel_encoder_unref.restype = None
4✔
1279
    _sixel.sixel_encoder_unref.argtypes = [c_void_p]
4✔
1280
    _sixel.sixel_encoder_unref(encoder)
4✔
1281

1282

1283
# set an option flag to encoder object
1284
def sixel_encoder_setopt(encoder, flag, arg=None):
4✔
1285
    _sixel.sixel_encoder_setopt.restype = c_int
4✔
1286
    _sixel.sixel_encoder_setopt.argtypes = [c_void_p, c_int, c_char_p]
4✔
1287
    # Normalize flag for validation while keeping the numeric code used by the
1288
    # C API. Python callers may pass either the character constant ("p") or an
1289
    # integer value. We want to keep the original character for comparison so
1290
    # option-specific validation continues to work even after converting to the
1291
    # numeric code for ctypes.
1292
    if isinstance(flag, int):
4✔
1293
        flag_code = flag
4✔
1294
        flag_char = chr(flag)
4✔
1295
    else:
1296
        flag_char = str(flag)
4✔
1297
        if len(flag_char) != 1:
4✔
1298
            raise RuntimeError(
4✔
1299
                "invalid option flag: expected a single-character flag"
1300
            )
1301
        flag_code = ord(flag_char)
4✔
1302

1303
    if arg:
4✔
1304
        arg = str(arg).encode('utf-8')
4✔
1305
    status = _sixel.sixel_encoder_setopt(encoder, flag_code, arg)
4✔
1306
    if SIXEL_FAILED(status):
4✔
1307
        message = sixel_helper_format_error(status)
4✔
1308
        raise RuntimeError(message)
4✔
1309

1310

1311
# load source data from specified file and encode it to SIXEL format
1312
def sixel_encoder_encode(encoder, filename):
4✔
1313
    import os
4✔
1314
    encoding = _resolve_locale_encoding(default="ascii")
4✔
1315

1316
    # Reject None before touching the codec path because the C API expects a
1317
    # real string pointer. This keeps the exception class deterministic and
1318
    # mirrors the explicit None guard used by sixel_loader_load_file().
1319
    if filename is None:
4✔
1320
        raise TypeError("filename must be str or bytes, not None")
4✔
1321
    if isinstance(filename, memoryview):
4✔
1322
        raise TypeError("filename must be str or bytes, not memoryview")
4✔
1323

1324
    # Proactively validate the input path on the Python side so callers get a
1325
    # deterministic exception even if a platform-specific libc or loader fails
1326
    # to surface a failure.  This mirrors the C-side validation while keeping
1327
    # the behaviour consistent across wheel and in-tree builds.
1328
    if isinstance(filename, bytes):
4✔
1329
        encoded_filename = filename
4✔
1330
        stdin_token = b"-"
4✔
1331
    else:
1332
        encoded_filename = str(filename).encode(encoding)
4✔
1333
        stdin_token = b"-"
4✔
1334

1335
    if encoded_filename != stdin_token:
4✔
1336
        if not os.path.exists(filename):
4✔
1337
            raise RuntimeError(f"input path does not exist: {filename}")
4✔
1338
        if os.path.isdir(filename):
4✔
1339
            raise RuntimeError(f"input path is a directory: {filename}")
4✔
1340

1341
    _sixel.sixel_encoder_encode.restype = c_int
4✔
1342
    _sixel.sixel_encoder_encode.argtypes = [c_void_p, c_char_p]
4✔
1343
    status = _sixel.sixel_encoder_encode(encoder, encoded_filename)
4✔
1344
    if SIXEL_FAILED(status):
4✔
1345
        message = sixel_helper_format_error(status)
4✔
1346
        raise RuntimeError(message)
4✔
1347

1348

1349
# encode specified pixel data to SIXEL format
1350
def sixel_encoder_encode_bytes(encoder, buf, width, height, pixelformat, palette):
4✔
1351

1352
    # Keep buffer acceptance strict and deterministic.  Relying on ctypes
1353
    # coercion can silently accept unsupported objects (for example str) and
1354
    # pass unrelated memory to the C API.
1355
    if buf is None:
4✔
1356
        raise TypeError("buf must be bytes or bytearray, not None")
4✔
1357
    if isinstance(buf, str):
4✔
1358
        raise TypeError("buf must be bytes or bytearray, not str")
4✔
1359
    if isinstance(buf, memoryview):
4✔
1360
        raise TypeError("buf must be bytes or bytearray, not memoryview")
4✔
1361
    if not isinstance(buf, (bytes, bytearray)):
4✔
NEW
1362
        raise TypeError(
×
1363
            "buf must be bytes or bytearray, not "
1364
            + type(buf).__name__
1365
        )
1366

1367
    depth = sixel_helper_compute_depth(pixelformat)
4✔
1368

1369
    if depth <= 0:
4✔
1370
        raise ValueError("invalid pixelformat value : %d" % pixelformat)
4✔
1371

1372
    if len(buf) < width * height * depth:
4✔
1373
        raise ValueError("buf.len is too short : %d < %d * %d * %d" % (len(buf), width, height, depth))
4✔
1374

1375
    if palette:
4✔
1376
        cpalettelen = len(palette)
4✔
1377
        cpalette = (c_byte * cpalettelen)(*palette)
4✔
1378
    else:
1379
        cpalettelen = 0
4✔
1380
        cpalette = None
4✔
1381

1382
    _sixel.sixel_encoder_encode_bytes.restype = c_int
4✔
1383
    _sixel.sixel_encoder_encode_bytes.argtypes = [c_void_p, c_void_p, c_int, c_int, c_int, c_void_p, c_int]
4✔
1384

1385
    status = _sixel.sixel_encoder_encode_bytes(encoder, buf, width, height, pixelformat, cpalette, cpalettelen)
4✔
1386
    if SIXEL_FAILED(status):
4✔
UNCOV
1387
        message = sixel_helper_format_error(status)
×
UNCOV
1388
        raise RuntimeError(message)
×
1389

1390

1391
# create decoder object
1392
def sixel_decoder_new(allocator=c_void_p(None)):
4✔
1393
    _sixel.sixel_decoder_new.restype = c_int
4✔
1394
    _sixel.sixel_decoder_new.argtypes = [POINTER(c_void_p), c_void_p]
4✔
1395
    decoder = c_void_p(None)
4✔
1396
    status = _sixel.sixel_decoder_new(byref(decoder), c_void_p(None))
4✔
1397
    if SIXEL_FAILED(status):
4✔
UNCOV
1398
        message = sixel_helper_format_error(status)
×
UNCOV
1399
        raise RuntimeError(message)
×
1400
    return decoder
4✔
1401

1402

1403
# increase reference count of decoder object (thread-unsafe)
1404
def sixel_decoder_ref(decoder):
4✔
1405
    _sixel.sixel_decoder_ref.restype = None
4✔
1406
    _sixel.sixel_decoder_ref.argtypes = [c_void_p]
4✔
1407
    _sixel.sixel_decoder_ref(decoder)
4✔
1408

1409

1410
# decrease reference count of decoder object (thread-unsafe)
1411
def sixel_decoder_unref(decoder):
4✔
1412
    _sixel.sixel_decoder_unref.restype = None
4✔
1413
    _sixel.sixel_decoder_unref.argtypes = [c_void_p]
4✔
1414
    _sixel.sixel_decoder_unref(decoder)
4✔
1415

1416

1417
# set an option flag to decoder object
1418
def sixel_decoder_setopt(decoder, flag, arg=None):
4✔
1419
    _sixel.sixel_decoder_setopt.restype = c_int
4✔
1420
    _sixel.sixel_decoder_setopt.argtypes = [c_void_p, c_int, c_char_p]
4✔
1421
    flag = ord(flag)
4✔
1422
    if arg:
4✔
1423
        arg = str(arg).encode('utf-8')
4✔
1424
    status = _sixel.sixel_decoder_setopt(decoder, flag, arg)
4✔
1425
    if SIXEL_FAILED(status):
4✔
1426
        message = sixel_helper_format_error(status)
4✔
1427
        raise RuntimeError(message)
4✔
1428

1429

1430
# load source data from stdin or the file
1431
def sixel_decoder_decode(decoder, infile=None):
4✔
1432
    _sixel.sixel_decoder_decode.restype = c_int
4✔
1433
    _sixel.sixel_decoder_decode.argtypes = [c_void_p]
4✔
1434
    if infile:
4✔
1435
        sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, infile)
4✔
1436
    status = _sixel.sixel_decoder_decode(decoder)
4✔
1437
    if SIXEL_FAILED(status):
4✔
UNCOV
1438
        message = sixel_helper_format_error(status)
×
UNCOV
1439
        raise RuntimeError(message)
×
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