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

saitoha / libsixel / 24196493809

09 Apr 2026 02:34PM UTC coverage: 85.375% (+0.8%) from 84.596%
24196493809

push

github

saitoha
tests: stabilize temporal animated-diff check with libwebp

88056 of 184023 branches covered (47.85%)

107446 of 125852 relevant lines covered (85.37%)

5535973.37 hits per line

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

91.57
/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
#        +-------------------------------+
89
SIXEL_LOADER_OPTION_REQUIRE_STATIC = 1
4✔
90
SIXEL_LOADER_OPTION_USE_PALETTE = 2
4✔
91
SIXEL_LOADER_OPTION_REQCOLORS = 3
4✔
92
SIXEL_LOADER_OPTION_BGCOLOR = 4
4✔
93
SIXEL_LOADER_OPTION_LOOP_CONTROL = 5
4✔
94
SIXEL_LOADER_OPTION_INSECURE = 6
4✔
95
SIXEL_LOADER_OPTION_CANCEL_FLAG = 7
4✔
96
SIXEL_LOADER_OPTION_LOADER_ORDER = 8
4✔
97
SIXEL_LOADER_OPTION_CONTEXT = 9
4✔
98
SIXEL_LOADER_OPTION_WIC_ICO_MINSIZE = 10
4✔
99
SIXEL_LOADER_OPTION_START_FRAME_NO = 11
4✔
100

101
# return value
102
SIXEL_OK              = 0x0000
4✔
103
SIXEL_FALSE           = 0x1000
4✔
104

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

120
SIXEL_INTERRUPTED          = (SIXEL_OK            | 0x0001)  # interrupted by a signal
4✔
121

122
SIXEL_BAD_ALLOCATION       = (SIXEL_RUNTIME_ERROR | 0x0001)  # malloc() failed
4✔
123
SIXEL_BAD_ARGUMENT         = (SIXEL_RUNTIME_ERROR | 0x0002)  # bad argument detected
4✔
124
SIXEL_BAD_INPUT            = (SIXEL_RUNTIME_ERROR | 0x0003)  # bad input detected
4✔
125
SIXEL_BAD_INTEGER_OVERFLOW = (SIXEL_RUNTIME_ERROR | 0x0004)  # integer overflow
4✔
126

127
SIXEL_NOT_IMPLEMENTED      = (SIXEL_FEATURE_ERROR | 0x0001)  # feature not implemented
4✔
128

129
def SIXEL_SUCCEEDED(status):
4✔
130
    return (((status) & 0x1000) == 0)
4✔
131

132
def SIXEL_FAILED(status):
4✔
133
    return (((status) & 0x1000) != 0)
4✔
134

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

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

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

166

167
_PYTHON_BITS = struct.calcsize("P") * 8
4✔
168

169

170
def _detect_elf_class(data: bytes) -> str:
4✔
171
    """Return the ELF class width as a string."""
172

173
    if not data.startswith(b"\x7fELF"):
4✔
174
        return ""
2✔
175

176
    elf_class = data[4]
2✔
177
    if elf_class == 1:
2✔
178
        return "32"
×
179
    if elf_class == 2:
2✔
180
        return "64"
2✔
181
    return ""
×
182

183

184
def _detect_macho_class(data: bytes) -> str:
4✔
185
    """Return the Mach-O class width as a string."""
186

187
    magic = data[:4]
2✔
188
    macho_32 = (0xfeedface, 0xcefaedfe)
2✔
189
    macho_64 = (0xfeedfacf, 0xcffaedfe)
2✔
190

191
    value = int.from_bytes(magic, byteorder="big", signed=False)
2✔
192
    if value in macho_32:
2✔
193
        return "32"
×
194
    if value in macho_64:
2✔
195
        return "64"
2✔
196
    return ""
×
197

198

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

202
    if not data.startswith(b"MZ") or len(data) < 0x40:
×
203
        return ""
×
204

205
    pe_offset = int.from_bytes(data[0x3C:0x40], byteorder="little")
×
206
    try:
×
207
        with bin_path.open("rb") as handle:
×
208
            handle.seek(pe_offset)
×
209
            signature = handle.read(6)
×
210
    except OSError:
×
211
        return ""
×
212

213
    if not signature.startswith(b"PE\0\0") or len(signature) < 6:
×
214
        return ""
×
215

216
    machine = struct.unpack("<H", signature[4:6])[0]
×
217
    if machine in (0x014C,):
×
218
        return "32"
×
219
    if machine in (0x8664,):
×
220
        return "64"
×
221
    return ""
×
222

223

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

227
    try:
4✔
228
        with bin_path.open("rb") as handle:
4✔
229
            head = handle.read(1024)
4✔
230
    except OSError:
×
231
        return ""
×
232

233
    for detector in (_detect_elf_class, _detect_macho_class):
4✔
234
        width = detector(head)
4✔
235
        if width:
4✔
236
            return width
4✔
237

238
    return _detect_pe_class(bin_path, head)
×
239
# scan order for diffusing
240
SIXEL_SCAN_AUTO       = 0x0  # choose scan order automatically
4✔
241
SIXEL_SCAN_RASTER     = 0x1  # scan from left to right on each line
4✔
242
SIXEL_SCAN_SERPENTINE = 0x2  # alternate scan direction per line
4✔
243
SIXEL_CARRY_AUTO      = 0x0  # choose carry mode automatically
4✔
244
SIXEL_CARRY_DISABLE   = 0x1  # apply errors directly to pixels
4✔
245
SIXEL_CARRY_ENABLE    = 0x2  # accumulate errors in carry lines
4✔
246

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

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

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

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

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

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

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

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

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

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

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

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

459
SIXEL_OPTFLAG_DIFFUSION_CARRY  = 'Y'  # -Y CARRYTYPE, --diffusion-carry=CARRYTYPE:
4✔
460
                                      #        control diffusion carry buffers.
461
                                      #          auto   -> choose automatically
462
                                      #          direct -> write error back immediately
463
                                      #          carry  -> accumulate in workspace lines
464

465
SIXEL_OPTFLAG_FIND_LARGEST     = 'f'  # -f FINDTYPE, --find-largest=FINDTYPE:
4✔
466
                                      #         choose method for finding the largest
467
                                      #         dimension of median cut boxes for
468
                                      #         splitting, make sense only when -p
469
                                      #         option (color reduction) is
470
                                      #         specified
471
                                      #         FINDTYPE is one of them:
472
                                      #           auto -> choose finding method
473
                                      #                   automatically (default)
474
                                      #           norm -> simply comparing the
475
                                      #                   range in RGB space
476
                                      #           lum  -> transforming into
477
                                      #                   luminosities before the
478
                                      #                   comparison
479
                                      #           pca  -> split along the first
480
                                      #                   principal component and
481
                                      #                   cut at weighted median
482

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

533
SIXEL_OPTFLAG_WIDTH            = 'w'  # -w WIDTH, --width=WIDTH:
4✔
534
                                      #        resize image to specified width
535
                                      #        WIDTH is represented by the
536
                                      #        following syntax
537
                                      #          auto       -> preserving aspect
538
                                      #                        ratio (default)
539
                                      #          <number>%  -> scale width with
540
                                      #                        given percentage
541
                                      #          <number>   -> scale width with
542
                                      #                        pixel counts
543
                                      #          <number>c  -> scale width with
544
                                      #                        terminal cell count
545
                                      #          <number>px -> scale width with
546
                                      #                        pixel counts
547

548
SIXEL_OPTFLAG_HEIGHT           = 'h'  # -h HEIGHT, --height=HEIGHT:
4✔
549
                                      #         resize image to specified height
550
                                      #         HEIGHT is represented by the
551
                                      #         following syntax
552
                                      #           auto       -> preserving aspect
553
                                      #                         ratio (default)
554
                                      #           <number>%  -> scale height with
555
                                      #                         given percentage
556
                                      #           <number>   -> scale height with
557
                                      #                         pixel counts
558
                                      #           <number>c  -> scale height with
559
                                      #                         terminal cell count
560
                                      #           <number>px -> scale height with
561
                                      #                         pixel counts
562

563
SIXEL_OPTFLAG_RESAMPLING       = 'r'  # -r RESAMPLINGTYPE, --resampling=RESAMPLINGTYPE:
4✔
564
                                      #        choose resampling filter used
565
                                      #        with -w or -h option (scaling)
566
                                      #        RESAMPLINGTYPE is one of them:
567
                                      #          nearest  -> Nearest-Neighbor
568
                                      #                      method
569
                                      #          gaussian -> Gaussian filter
570
                                      #          hanning  -> Hanning filter
571
                                      #          hamming  -> Hamming filter
572
                                      #          bilinear -> Bilinear filter
573
                                      #                      (default)
574
                                      #          welsh    -> Welsh filter
575
                                      #          bicubic  -> Bicubic filter
576
                                      #          lanczos2 -> Lanczos-2 filter
577
                                      #          lanczos3 -> Lanczos-3 filter
578
                                      #          lanczos4 -> Lanczos-4 filter
579

580
SIXEL_OPTFLAG_QUALITY          = 'q'  # -q QUALITYMODE, --quality=QUALITYMODE:
4✔
581
                                      #        select quality of color
582
                                      #        quanlization.
583
                                      #          auto -> decide quality mode
584
                                      #                  automatically (default)
585
                                      #          low  -> low quality and high
586
                                      #                  speed mode
587
                                      #          high -> high quality and low
588
                                      #                  speed mode
589
                                      #          full -> full quality and careful
590
                                      #                  speed mode
591

592
SIXEL_OPTFLAG_LOOPMODE         = 'l'  # -l LOOPMODE, --loop-control=LOOPMODE:
4✔
593
                                      #        select loop control mode for GIF
594
                                      #        animation.
595
                                      #          auto    -> honor the setting of
596
                                      #                     GIF header (default)
597
                                      #          force   -> always enable loop
598
                                      #          disable -> always disable loop
599

600
SIXEL_OPTFLAG_START_FRAME      = 'T'  # -T FRAME_NO, --start-frame=FRAME_NO:
4✔
601
                                      #        set the first animation frame index
602
                                      #          non-negative -> absolute index
603
                                      #          negative     -> offset from end
604

605
SIXEL_OPTFLAG_PALETTE_TYPE     = 't'  # -t PALETTETYPE, --palette-type=PALETTETYPE:
4✔
606
                                      #        select palette color space type
607
                                      #          auto -> choose palette type
608
                                      #                  automatically (default)
609
                                      #          hls  -> use HLS color space
610
                                      #          rgb  -> use RGB color space
611

612
SIXEL_OPTFLAG_BUILTIN_PALETTE  = 'b'  # -b BUILTINPALETTE, --builtin-palette=BUILTINPALETTE:
4✔
613
                                      #        select built-in palette type
614
                                      #          xterm16    -> X default 16 color map
615
                                      #          xterm256   -> X default 256 color map
616
                                      #          vt340mono  -> VT340 monochrome map
617
                                      #          vt340color -> VT340 color map
618
                                      #          gray1      -> 1bit grayscale map
619
                                      #          gray2      -> 2bit grayscale map
620
                                      #          gray4      -> 4bit grayscale map
621
                                      #          gray8      -> 8bit grayscale map
622

623
SIXEL_OPTFLAG_ENCODE_POLICY    = 'E'  # -E ENCODEPOLICY, --encode-policy=ENCODEPOLICY:
4✔
624
                                      #        select encoding policy
625
                                      #          auto -> choose encoding policy
626
                                      #                  automatically (default)
627
                                      #          fast -> encode as fast as possible
628
                                      #          size -> encode to as small sixel
629
                                      #                  sequence as possible
630
SIXEL_OPTFLAG_LUT_POLICY        = '~'  # -~ LOOKUPPOLICY,
4✔
631
                                      #   --lookup-policy=LOOKUPPOLICY:
632
                                      #        choose histogram lookup width.
633
                                      #          auto    -> follow pixel depth
634
                                      #          5bit    -> force 5-bit buckets
635
                                      #          6bit    -> force 6-bit buckets
636
                                      #                     (RGB inputs)
637
                                      #          none    -> disable LUT caching
638
                                      #                     and scan directly
639
                                      #          certlut -> certified
640
                                      #                     hierarchical LUT
641
                                      #                     with zero error
642
                                      #          fhedt    -> Voronoi grid built
643
                                      #                     via 3D EDT with
644
                                      #                     optional
645
                                      #                     refinement
646
SIXEL_OPTFLAG_CLUSTERING_COLORSPACE = 'X'  # -X COLORSPACE, --clustering-colorspace=COLORSPACE:
4✔
647
                                          #        select palette clustering space.
648
                                          #          gamma  -> keep gamma encoded pixels
649
                                          #          linear -> convert to linear RGB
650
                                          #          oklab  -> operate in OKLab
651
                                          #          cielab -> operate in CIELAB
652
                                          #          din99d -> operate in DIN99d
653
SIXEL_OPTFLAG_WORKING_COLORSPACE = 'W'  # -W WORKING_COLORSPACE, --working-colorspace=COLORSPACE:
4✔
654
                                      #        select internal working space.
655
                                      #          gamma  -> keep gamma encoded pixels
656
                                      #          linear -> convert to linear RGB
657
                                      #          oklab  -> operate in OKLab
658
                                      #          cielab -> operate in CIELAB
659
                                      #          din99d -> operate in DIN99d
660
SIXEL_OPTFLAG_OUTPUT_COLORSPACE = 'U'  # -U OUTPUT_COLORSPACE, --output-colorspace=COLORSPACE:
4✔
661
                                      #        select output buffer color space.
662
                                      #          gamma   -> sRGB gamma encoded output
663
                                      #          linear  -> linear RGB output
664
                                      #          smpte-c -> SMPTE-C gamma encoded output
665
SIXEL_OPTFLAG_ORMODE           = 'O'  # -O, --ormode: output ormode sixel image
4✔
666

667
SIXEL_OPTFLAG_BGCOLOR          = 'B'  # -B BGCOLOR, --bgcolor=BGCOLOR:
4✔
668
                                      #        specify background color
669
                                      #        BGCOLOR is represented by the
670
                                      #        following syntax
671
                                      #          #rgb
672
                                      #          #rrggbb
673
                                      #          #rrrgggbbb
674
                                      #          #rrrrggggbbbb
675
                                      #          rgb:r/g/b
676
                                      #          rgb:rr/gg/bb
677
                                      #          rgb:rrr/ggg/bbb
678
                                      #          rgb:rrrr/gggg/bbbb
679

680
SIXEL_OPTFLAG_PENETRATE        = 'P'  # -P, --penetrate: (deprecated)
4✔
681
                                      #        penetrate GNU Screen using DCS
682
                                      #        pass-through sequence
683
SIXEL_OPTFLAG_DRCS             = '@'  # -@ MMV:CHARSET:PATH, --drcs=MMV:CHARSET:PATH:
4✔
684
                                      #        emit extended DRCS tiles, optionally
685
                                      #        overriding mapping revision, charset,
686
                                      #        and tile sink (defaults to 2:1:;
687
                                      #        experimental)
688
SIXEL_OPTFLAG_PIPE_MODE        = 'D'  # -D, --pipe-mode: (deprecated)
4✔
689
                                      #         read source images from stdin continuously
690
SIXEL_OPTFLAG_VERBOSE          = 'v'  # -v, --verbose: show debugging info
4✔
691
SIXEL_OPTFLAG_VERSION          = 'V'  # -V, --version: show version and license info
4✔
692
SIXEL_OPTFLAG_HELP             = 'H'  # -H, --help: show this help
4✔
693

694
_sixel_names = [
4✔
695
    "sixel",
696
    "libsixel",
697
    "sixel-1",
698
    "libsixel-1",
699
    "msys-sixel",
700
    "cygsixel",
701
]
702

703
def _match_library_in_dir(libdir, lib_names):
4✔
704
    """Return the first matching shared library in the given directory.
705

706
    The selection logic is intentionally aligned with the build helper:
707

708
    - Accept both "lib" prefixed and prefixless names.
709
    - Accept .so, .dylib, or .dll (including versioned .so.* files).
710
    - Skip import archives like *.dll.a or *.dll.def.
711
    """
712

713
    prefixes = ["lib", ""]
4✔
714
    suffixes = [".so", ".dylib", ".dll"]
4✔
715

716
    for name in lib_names:
4✔
717
        for prefix in prefixes:
4✔
718
            for suffix in suffixes:
4✔
719
                patterns: list[str]
720
                if suffix == ".so":
4✔
721
                    patterns = [
4✔
722
                        os.path.join(libdir, f"{prefix}{name}*{suffix}"),
723
                        os.path.join(libdir, f"{prefix}{name}*{suffix}.*"),
724
                    ]
725
                else:
726
                    patterns = [
2✔
727
                        os.path.join(libdir, f"{prefix}{name}*{suffix}"),
728
                    ]
729

730
                matches: list[str] = []
4✔
731
                for pattern in patterns:
4✔
732
                    matches.extend(glob.glob(pattern))
4✔
733

734
                filtered: list[str] = []
4✔
735
                for candidate in sorted(set(matches)):
4✔
736
                    if candidate.endswith((".dll.a", ".dll.def")):
4✔
737
                        continue
×
738

739
                    bits = _detect_library_bits(pathlib.Path(candidate))
4✔
740
                    if bits and bits != str(_PYTHON_BITS):
4✔
741
                        continue
×
742

743
                    filtered.append(candidate)
4✔
744

745
                if filtered:
4✔
746
                    return filtered[0]
4✔
747

748
    return None
×
749

750

751
def _prefer_bundled_library(lib_names):
4✔
752
    """Locate a bundled shared library shipped inside the wheel package.
753

754
    Wheel builds copy the shared library into libsixel/_libs so that imports
755
    succeed even when libsixel is not installed system-wide.
756
    """
757

758
    bundle_dir = pathlib.Path(__file__).resolve().parent / "_libs"
4✔
759
    if not bundle_dir.is_dir():
4✔
760
        return None
×
761
    return _match_library_in_dir(str(bundle_dir), lib_names)
4✔
762

763

764
def _prefer_env_library(lib_names):
4✔
765
    """Locate libsixel under LIBSIXEL_LIBDIR when running from a build tree."""
766

767
    libdir = os.environ.get("LIBSIXEL_LIBDIR")
×
768
    if libdir is None:
×
769
        return None
×
770
    return _match_library_in_dir(libdir, lib_names)
×
771

772

773
_lib_path = _prefer_bundled_library(_sixel_names)
4✔
774

775
if _lib_path is None:
4✔
776
    _lib_path = _prefer_env_library(_sixel_names)
×
777

778
if _lib_path is None:
4✔
779
    _lib_path = next(
×
780
        (path for path in (find_library(name) for name in _sixel_names)
781
         if path is not None),
782
        None,
783
    )
784

785
if _lib_path is None:
4✔
786
    raise ImportError(
×
787
        "libsixel not found. Set LIBSIXEL_LIBDIR to the built shared library."
788
    )
789

790
_lib_bits = _detect_library_bits(pathlib.Path(_lib_path))
4✔
791
if _lib_bits and _lib_bits != str(_PYTHON_BITS):
4✔
792
    raise ImportError(
×
793
        f"libsixel {_lib_bits}-bit library is incompatible with "
794
        f"python {_PYTHON_BITS}-bit"
795
    )
796

797
# load shared library
798
_sixel = cdll.LoadLibrary(_lib_path)
4✔
799

800
# convert error status code int formatted string
801
def sixel_helper_format_error(status):
4✔
802
    _sixel.sixel_helper_format_error.restype = c_char_p;
4✔
803
    _sixel.sixel_helper_format_error.argtypes = [c_int];
4✔
804
    return _sixel.sixel_helper_format_error(status)
4✔
805

806

807
# compute pixel depth from pixelformat
808
def sixel_helper_compute_depth(pixelformat):
4✔
809
    _sixel.sixel_helper_compute_depth.restype = c_int
4✔
810
    _sixel.sixel_helper_compute_depth.argtypes = [c_int]
4✔
811
    return _sixel.sixel_helper_compute_depth(pixelformat)
4✔
812

813

814
# generic loader -----------------------------------------------------------
815

816
_sixel_loader_callback_type = CFUNCTYPE(c_int, c_void_p, c_void_p)
4✔
817

818

819
def sixel_loader_new(allocator=c_void_p(None)):
4✔
820
    """Create a loader object that mirrors sixel_loader_new()."""
821

822
    _sixel.sixel_loader_new.restype = c_int
4✔
823
    _sixel.sixel_loader_new.argtypes = [POINTER(c_void_p), c_void_p]
4✔
824

825
    loader = c_void_p(None)
4✔
826
    status = _sixel.sixel_loader_new(byref(loader), allocator)
4✔
827
    if SIXEL_FAILED(status):
4✔
828
        message = sixel_helper_format_error(status)
×
829
        raise RuntimeError(message)
×
830
    return loader
4✔
831

832

833
def sixel_loader_ref(loader):
4✔
834
    """Increase the reference count of a loader object."""
835

836
    _sixel.sixel_loader_ref.restype = None
4✔
837
    _sixel.sixel_loader_ref.argtypes = [c_void_p]
4✔
838
    _sixel.sixel_loader_ref(loader)
4✔
839

840

841
def sixel_loader_unref(loader):
4✔
842
    """Decrease the reference count of a loader object."""
843

844
    _sixel.sixel_loader_unref.restype = None
4✔
845
    _sixel.sixel_loader_unref.argtypes = [c_void_p]
4✔
846
    _sixel.sixel_loader_unref(loader)
4✔
847

848

849
def sixel_loader_setopt(loader, option, value=None):
4✔
850
    """Configure loader behavior via sixel_loader_setopt().
851

852
    The helper routes Python values into the pointer-based C API while keeping
853
    the conversion rules in plain sight:
854

855
        +-----------+---------------------------+---------------------+
856
        | Option    | Expected Python value     | Example             |
857
        +-----------+---------------------------+---------------------+
858
        | STATIC    | bool/int or None          | True                |
859
        | PALETTE   | bool/int or None          | 0                   |
860
        | REQCOLORS | int or None               | 256                 |
861
        | BGCOLOR   | iterable[3] or None       | (0, 0, 0)           |
862
        | LOOP      | int or None               | SIXEL_LOOP_FORCE    |
863
        | INSECURE  | bool/int or None          | False               |
864
        | CANCEL    | ctypes pointer / address  | byref(c_int(0))     |
865
        | ORDER     | str/bytes/bytearray or None | "builtin"         |
866
        | CONTEXT   | ctypes pointer / address  | c_void_p(id(obj))   |
867
        | WIC SIZE  | int or None               | 64                  |
868
        +-----------+---------------------------+---------------------+
869

870
    Values left as ``None`` map to NULL so that the C side may install its
871
    default behavior.
872
    """
873

874
    _sixel.sixel_loader_setopt.restype = c_int
4✔
875
    _sixel.sixel_loader_setopt.argtypes = [c_void_p, c_int, c_void_p]
4✔
876

877
    option = int(option)
4✔
878
    pointer_value = c_void_p(None)
4✔
879
    keepalive = None
4✔
880

881
    int_options = {
4✔
882
        SIXEL_LOADER_OPTION_REQUIRE_STATIC,
883
        SIXEL_LOADER_OPTION_USE_PALETTE,
884
        SIXEL_LOADER_OPTION_REQCOLORS,
885
        SIXEL_LOADER_OPTION_LOOP_CONTROL,
886
        SIXEL_LOADER_OPTION_INSECURE,
887
        SIXEL_LOADER_OPTION_WIC_ICO_MINSIZE,
888
        SIXEL_LOADER_OPTION_START_FRAME_NO,
889
    }
890

891
    if option in int_options:
4✔
892
        if value is not None:
4✔
893
            keepalive = c_int(int(value))
4✔
894
            pointer_value = cast(byref(keepalive), c_void_p)
4✔
895
    elif option == SIXEL_LOADER_OPTION_BGCOLOR:
4✔
896
        if value is not None:
4✔
897
            if len(value) != 3:
4✔
898
                raise ValueError("bgcolor expects three components")
4✔
899
            keepalive = (c_byte * 3)(value[0], value[1], value[2])
4✔
900
            pointer_value = cast(keepalive, c_void_p)
4✔
901
    elif option == SIXEL_LOADER_OPTION_LOADER_ORDER:
4✔
902
        if value is not None:
4✔
903
            if isinstance(value, bytes):
4✔
904
                encoded = value
4✔
905
            elif isinstance(value, bytearray):
4✔
906
                encoded = bytes(value)
4✔
907
            elif isinstance(value, str):
4✔
908
                encoded = value.encode('utf-8')
4✔
909
            else:
910
                raise TypeError(
4✔
911
                    "loader_order expects str, bytes, bytearray, or None"
912
                )
913
            keepalive = c_char_p(encoded)
4✔
914
            pointer_value = cast(keepalive, c_void_p)
4✔
915
    elif option in (
4✔
916
        SIXEL_LOADER_OPTION_CANCEL_FLAG,
917
        SIXEL_LOADER_OPTION_CONTEXT,
918
    ):
919
        if value is None:
4✔
920
            pointer_value = c_void_p(None)
×
921
        elif isinstance(value, c_void_p):
4✔
922
            pointer_value = value
4✔
923
        elif isinstance(value, int):
4✔
924
            pointer_value = c_void_p(value)
4✔
925
        else:
926
            pointer_value = cast(value, c_void_p)
4✔
927
    else:
928
        raise ValueError("unknown loader option: %r" % option)
4✔
929

930
    status = _sixel.sixel_loader_setopt(loader, option, pointer_value)
4✔
931
    if SIXEL_FAILED(status):
4✔
932
        message = sixel_helper_format_error(status)
×
933
        raise RuntimeError(message)
×
934

935

936
def sixel_loader_load_file(loader, filename, fn_load):
4✔
937
    """Load ``filename`` and feed each frame to ``fn_load``.
938

939
    ``fn_load`` receives ``(frame_ptr, context_ptr)`` mirroring the C
940
    signature.  The loader's context pointer may be set via
941
    ``sixel_loader_setopt``.
942
    """
943

944
    if fn_load is None:
4✔
945
        raise ValueError("fn_load callback is required")
4✔
946
    if not callable(fn_load):
4✔
947
        raise TypeError("fn_load callback must be callable")
4✔
948

949
    _sixel.sixel_loader_load_file.restype = c_int
4✔
950
    _sixel.sixel_loader_load_file.argtypes = [
4✔
951
        c_void_p,
952
        c_char_p,
953
        _sixel_loader_callback_type,
954
    ]
955

956
    encoding = _resolve_locale_encoding(default="utf-8")
4✔
957

958
    # The C API expects a non-NULL filename pointer.
959
    # Reject None in the Python wrapper to avoid passing NULL and
960
    # crashing inside the native loader implementation.
961
    if filename is None:
4✔
962
        raise TypeError("filename must be str or bytes, not None")
4✔
963
    elif isinstance(filename, bytes):
4✔
964
        c_filename = filename
4✔
965
    else:
966
        c_filename = filename.encode(encoding)
4✔
967

968
    def _fn_load_local(frame, context):
4✔
969
        return fn_load(frame, context)
4✔
970

971
    callback = _sixel_loader_callback_type(_fn_load_local)
4✔
972
    status = _sixel.sixel_loader_load_file(loader, c_filename, callback)
4✔
973
    if SIXEL_FAILED(status):
4✔
974
        message = sixel_helper_format_error(status)
4✔
975
        raise RuntimeError(message)
4✔
976

977

978
# create new output context object
979
def sixel_output_new(fn_write, priv=None, allocator=c_void_p(None)):
4✔
980
    output = c_void_p(None)
4✔
981

982
    # ctypes callback exceptions do not propagate to the original Python
983
    # caller. Keep the original exception object on the output handle so
984
    # sixel_encode() can re-raise it in the caller context.
985
    output.__callback_exception = None
4✔
986

987
    def _fn_write_local(data, size, priv_from_c):
4✔
988
        try:
4✔
989
            fn_write(string_at(data, size), priv)
4✔
990
        except Exception as exc:
4✔
991
            output.__callback_exception = exc
4✔
992
            return -1
4✔
993
        return size
4✔
994

995
    sixel_write_function = CFUNCTYPE(c_int, c_char_p, c_int, c_void_p)
4✔
996
    _sixel.sixel_output_new.restype = c_int
4✔
997
    _sixel.sixel_output_new.argtypes = [POINTER(c_void_p), sixel_write_function, c_void_p, c_void_p]
4✔
998
    _fn_write = sixel_write_function(_fn_write_local)
4✔
999
    _fn_write.restype = c_int
4✔
1000
    _fn_write.argtypes = [sixel_write_function, c_void_p, c_void_p]
4✔
1001
    status = _sixel.sixel_output_new(byref(output), _fn_write, c_void_p(None), allocator)
4✔
1002
    if SIXEL_FAILED(status):
4✔
1003
        message = sixel_helper_format_error(status)
×
1004
        raise RuntimeError(message)
×
1005
    output.__fn_write = _fn_write
4✔
1006
    return output
4✔
1007

1008

1009
# increase reference count of output object (thread-unsafe)
1010
def sixel_output_ref(output):
4✔
1011
    _sixel.sixel_output_ref.restype = None
4✔
1012
    _sixel.sixel_output_ref.argtypes = [c_void_p]
4✔
1013
    _sixel.sixel_output_ref(output)
4✔
1014

1015

1016
# decrease reference count of output object (thread-unsafe)
1017
def sixel_output_unref(output):
4✔
1018
    _sixel.sixel_output_unref.restype = None
4✔
1019
    _sixel.sixel_output_unref.argtypes = [c_void_p]
4✔
1020
    _sixel.sixel_output_unref(output)
4✔
1021
    output.__fn_write = None
4✔
1022
    output.__callback_exception = None
4✔
1023

1024

1025
# get 8bit output mode which indicates whether it uses C1 control characters
1026
def sixel_output_get_8bit_availability(output):
4✔
1027
    _sixel.sixel_output_get_8bit_availability.restype = c_int
4✔
1028
    _sixel.sixel_output_get_8bit_availability.argtypes = [c_void_p]
4✔
1029
    return _sixel.sixel_output_get_8bit_availability(output)
4✔
1030

1031

1032
# set 8bit output mode state
1033
def sixel_output_set_8bit_availability(output, availability):
4✔
1034
    _sixel.sixel_output_set_8bit_availability.restype = None
4✔
1035
    _sixel.sixel_output_set_8bit_availability.argtypes = [c_void_p, c_int]
4✔
1036
    _sixel.sixel_output_set_8bit_availability(output, availability)
4✔
1037

1038

1039
# set whether limit arguments of DECGRI('!') to 255
1040
def sixel_output_set_gri_arg_limit(output, value):
4✔
1041
    _sixel.sixel_output_set_gri_arg_limit.restype = None
4✔
1042
    _sixel.sixel_output_set_gri_arg_limit.argtypes = [c_void_p, c_int]
4✔
1043
    _sixel.sixel_output_set_gri_arg_limit(output, value)
4✔
1044

1045

1046
# set GNU Screen penetration feature enable or disable
1047
def sixel_output_set_penetrate_multiplexer(output, penetrate):
4✔
1048
    _sixel.sixel_output_set_penetrate_multiplexer.restype = None
4✔
1049
    _sixel.sixel_output_set_penetrate_multiplexer.argtypes = [c_void_p, c_int]
4✔
1050
    _sixel.sixel_output_set_penetrate_multiplexer(output, penetrate)
4✔
1051

1052

1053
# set whether we skip DCS envelope
1054
def sixel_output_set_skip_dcs_envelope(output, skip):
4✔
1055
    _sixel.sixel_output_set_skip_dcs_envelope.restype = None
4✔
1056
    _sixel.sixel_output_set_skip_dcs_envelope.argtypes = [c_void_p, c_int]
4✔
1057
    _sixel.sixel_output_set_skip_dcs_envelope(output, skip)
4✔
1058

1059

1060
# set whether we skip SIXEL header
1061
def sixel_output_set_skip_header(output, skip):
4✔
1062
    _sixel.sixel_output_set_skip_header.restype = None
4✔
1063
    _sixel.sixel_output_set_skip_header.argtypes = [c_void_p, c_int]
4✔
1064
    _sixel.sixel_output_set_skip_header(output, skip)
4✔
1065

1066

1067
# set palette type: RGB or HLS
1068
def sixel_output_set_palette_type(output, palettetype):
4✔
1069
    _sixel.sixel_output_set_palette_type.restype = None
4✔
1070
    _sixel.sixel_output_set_palette_type.argtypes = [c_void_p, c_int]
4✔
1071
    _sixel.sixel_output_set_palette_type(output, palettetype)
4✔
1072

1073

1074
# enable or disable ormode output
1075
def sixel_output_set_ormode(output, ormode):
4✔
1076
    _sixel.sixel_output_set_ormode.restype = None
4✔
1077
    _sixel.sixel_output_set_ormode.argtypes = [c_void_p, c_int]
4✔
1078
    _sixel.sixel_output_set_ormode(output, ormode)
4✔
1079

1080

1081
# set encodeing policy: auto, fast or size
1082
def sixel_output_set_encode_policy(output, encode_policy):
4✔
1083
    _sixel.sixel_output_set_encode_policy.restype = None
4✔
1084
    _sixel.sixel_output_set_encode_policy.argtypes = [c_void_p, c_int]
4✔
1085
    _sixel.sixel_output_set_encode_policy(output, encode_policy)
4✔
1086

1087

1088
# create dither context object
1089
def sixel_dither_new(ncolors, allocator=None):
4✔
1090
    _sixel.sixel_dither_new.restype = c_int
4✔
1091
    _sixel.sixel_dither_new.argtypes = [POINTER(c_void_p), c_int, c_void_p]
4✔
1092
    dither = c_void_p(None)
4✔
1093
    status = _sixel.sixel_dither_new(byref(dither), ncolors, allocator)
4✔
1094
    if SIXEL_FAILED(status):
4✔
1095
        message = sixel_helper_format_error(status)
×
1096
        raise RuntimeError(message)
×
1097
    return dither
4✔
1098

1099

1100
# get built-in dither context object
1101
def sixel_dither_get(builtin_dither):
4✔
1102
    _sixel.sixel_dither_get.restype = c_void_p
4✔
1103
    _sixel.sixel_dither_get.argtypes = [c_int]
4✔
1104
    return _sixel.sixel_dither_get(builtin_dither)
4✔
1105

1106

1107
# destroy dither context object
1108
def sixel_dither_destroy(dither):
4✔
1109
    _sixel.sixel_dither_destroy.restype = None
4✔
1110
    _sixel.sixel_dither_destroy.argtypes = [c_void_p]
4✔
1111
    return _sixel.sixel_dither_destroy(dither)
4✔
1112

1113

1114
# increase reference count of dither context object (thread-unsafe)
1115
def sixel_dither_ref(dither):
4✔
1116
    _sixel.sixel_dither_ref.restype = None
4✔
1117
    _sixel.sixel_dither_ref.argtypes = [c_void_p]
4✔
1118
    return _sixel.sixel_dither_ref(dither)
4✔
1119

1120

1121
# decrease reference count of dither context object (thread-unsafe)
1122
def sixel_dither_unref(dither):
4✔
1123
    _sixel.sixel_dither_unref.restype = None
4✔
1124
    _sixel.sixel_dither_unref.argtypes = [c_void_p]
4✔
1125
    return _sixel.sixel_dither_unref(dither)
4✔
1126

1127

1128
# initialize internal palette from specified pixel buffer
1129
def sixel_dither_initialize(dither, data, width, height, pixelformat,
4✔
1130
                            method_for_largest=SIXEL_LARGE_AUTO,
1131
                            method_for_rep=SIXEL_REP_AUTO,
1132
                            quality_mode=SIXEL_QUALITY_AUTO):
1133
    _sixel.sixel_dither_initialize.restype = c_int
4✔
1134
    _sixel.sixel_dither_initialize.argtypes = [c_void_p, c_char_p, c_int, c_int, c_int,
4✔
1135
                                              c_int, c_int, c_int]
1136
    status = _sixel.sixel_dither_initialize(dither, data, width, height, pixelformat,
4✔
1137
                                            method_for_largest,
1138
                                            method_for_rep,
1139
                                            quality_mode)
1140
    if SIXEL_FAILED(status):
4✔
1141
        message = sixel_helper_format_error(status)
×
1142
        raise RuntimeError(message)
×
1143

1144

1145
# set diffusion type, choose from enum methodForDiffuse
1146
def sixel_dither_set_diffusion_type(dither, method_for_diffuse):
4✔
1147
    _sixel.sixel_dither_set_diffusion_type.restype = None
4✔
1148
    _sixel.sixel_dither_set_diffusion_type.argtypes = [c_void_p, c_int]
4✔
1149
    _sixel.sixel_dither_set_diffusion_type(dither, method_for_diffuse)
4✔
1150

1151

1152
def sixel_dither_set_diffusion_scan(dither, method_for_scan):
4✔
1153
    _sixel.sixel_dither_set_diffusion_scan.restype = None
4✔
1154
    _sixel.sixel_dither_set_diffusion_scan.argtypes = [c_void_p, c_int]
4✔
1155
    _sixel.sixel_dither_set_diffusion_scan(dither, method_for_scan)
4✔
1156

1157

1158
def sixel_dither_set_diffusion_carry(dither, method_for_carry):
4✔
1159
    _sixel.sixel_dither_set_diffusion_carry.restype = None
4✔
1160
    _sixel.sixel_dither_set_diffusion_carry.argtypes = [c_void_p, c_int]
4✔
1161
    _sixel.sixel_dither_set_diffusion_carry(dither, method_for_carry)
4✔
1162

1163

1164
# get number of palette colors
1165
def sixel_dither_get_num_of_palette_colors(dither):
4✔
1166
    _sixel.sixel_dither_get_num_of_palette_colors.restype = c_int
4✔
1167
    _sixel.sixel_dither_get_num_of_palette_colors.argtypes = [c_void_p]
4✔
1168
    return _sixel.sixel_dither_get_num_of_palette_colors(dither)
4✔
1169

1170

1171
# get number of histogram colors */
1172
def sixel_dither_get_num_of_histogram_colors(dither):
4✔
1173
    _sixel.sixel_dither_get_num_of_histogram_colors.restype = c_int
4✔
1174
    _sixel.sixel_dither_get_num_of_histogram_colors.argtypes = [c_void_p]
4✔
1175
    return _sixel.sixel_dither_get_num_of_histogram_colors(dither)
4✔
1176

1177

1178
def sixel_dither_get_palette(dither):
4✔
1179
    _sixel.sixel_dither_get_palette.restype = c_char_p
4✔
1180
    _sixel.sixel_dither_get_palette.argtypes = [c_void_p]
4✔
1181
    cpalette = _sixel.sixel_dither_get_palette(dither)
4✔
1182
    return list(cpalette)
4✔
1183

1184

1185
def sixel_dither_set_palette(dither, palette):
4✔
1186
    _sixel.sixel_dither_set_palette.restype = None
4✔
1187
    _sixel.sixel_dither_set_palette.argtypes = [c_void_p, c_char_p]
4✔
1188
    cpalette = bytes(palette)
4✔
1189
    _sixel.sixel_dither_set_palette(dither, cpalette)
4✔
1190

1191

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

1197

1198
def sixel_dither_set_body_only(dither, bodyonly):
4✔
1199
    _sixel.sixel_dither_set_body_only.restype = None
4✔
1200
    _sixel.sixel_dither_set_body_only.argtypes = [c_void_p, c_int]
4✔
1201
    _sixel.sixel_dither_set_body_only(dither, bodyonly)
4✔
1202

1203

1204
def sixel_dither_set_optimize_palette(dither, do_opt):
4✔
1205
    _sixel.sixel_dither_set_optimize_palette.restype = None
4✔
1206
    _sixel.sixel_dither_set_optimize_palette.argtypes = [c_void_p, c_int]
4✔
1207
    _sixel.sixel_dither_set_optimize_palette(dither, do_opt)
4✔
1208

1209

1210
def sixel_dither_set_pixelformat(dither, pixelformat):
4✔
1211
    _sixel.sixel_dither_set_pixelformat.restype = None
4✔
1212
    _sixel.sixel_dither_set_pixelformat.argtypes = [c_void_p, c_int]
4✔
1213
    _sixel.sixel_dither_set_pixelformat(dither, pixelformat)
4✔
1214

1215

1216
def sixel_dither_set_transparent(dither, transparent):
4✔
1217
    _sixel.sixel_dither_set_transparent.restype = None
4✔
1218
    _sixel.sixel_dither_set_transparent.argtypes = [c_void_p, c_int]
4✔
1219
    _sixel.sixel_dither_set_transparent(dither, transparent)
4✔
1220

1221

1222
# configure the encoder thread count for band parallelism
1223
def sixel_set_threads(threads):
4✔
1224
    auto_requested = False
4✔
1225
    value = 0
4✔
1226
    text = None
4✔
1227

1228
    if isinstance(threads, bytes):
4✔
1229
        try:
4✔
1230
            text = threads.decode('utf-8').strip()
4✔
1231
        except UnicodeDecodeError as exc:
4✔
1232
            raise ValueError(
4✔
1233
                "threads must be a positive integer or 'auto'"
1234
            ) from exc
1235
    elif isinstance(threads, str):
4✔
1236
        text = threads.strip()
4✔
1237
    else:
1238
        text = None
4✔
1239

1240
    if text is not None:
4✔
1241
        if text.lower() == 'auto':
4✔
1242
            auto_requested = True
4✔
1243
            value = 0
4✔
1244
        else:
1245
            try:
4✔
1246
                value = int(text, 10)
4✔
1247
            except ValueError as exc:
4✔
1248
                raise ValueError(
4✔
1249
                    "threads must be a positive integer or 'auto'"
1250
                ) from exc
1251
    else:
1252
        try:
4✔
1253
            value = int(threads)
4✔
1254
        except (TypeError, ValueError) as exc:
4✔
1255
            raise ValueError(
4✔
1256
                "threads must be a positive integer or 'auto'"
1257
            ) from exc
1258

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

1262
    _sixel.sixel_set_threads.restype = None
4✔
1263
    _sixel.sixel_set_threads.argtypes = [c_int]
4✔
1264
    _sixel.sixel_set_threads(value)
4✔
1265

1266

1267
# convert pixels into sixel format and write it to output context
1268
def sixel_encode(pixels, width, height, depth, dither, output):
4✔
1269
    _sixel.sixel_encode.restype = c_int
4✔
1270
    _sixel.sixel_encode.argtypes = [c_char_p, c_int, c_int, c_int, c_void_p, c_void_p]
4✔
1271
    status = _sixel.sixel_encode(pixels, width, height, depth, dither, output)
4✔
1272

1273
    callback_exception = getattr(output, '__callback_exception', None)
4✔
1274
    if callback_exception is not None:
4✔
1275
        output.__callback_exception = None
4✔
1276
        raise callback_exception
4✔
1277

1278
    return status
4✔
1279

1280

1281
# create encoder object
1282
def sixel_encoder_new(allocator=c_void_p(None)):
4✔
1283
    _sixel.sixel_encoder_new.restype = c_int
4✔
1284
    _sixel.sixel_encoder_new.argtypes = [POINTER(c_void_p), c_void_p]
4✔
1285
    encoder = c_void_p(None)
4✔
1286
    status = _sixel.sixel_encoder_new(byref(encoder), allocator)
4✔
1287
    if SIXEL_FAILED(status):
4✔
1288
        message = sixel_helper_format_error(status)
×
1289
        raise RuntimeError(message)
×
1290
    return encoder
4✔
1291

1292

1293
# increase reference count of encoder object (thread-unsafe)
1294
def sixel_encoder_ref(encoder):
4✔
1295
    _sixel.sixel_encoder_ref.restype = None
4✔
1296
    _sixel.sixel_encoder_ref.argtypes = [c_void_p]
4✔
1297
    _sixel.sixel_encoder_ref(encoder)
4✔
1298

1299

1300
# decrease reference count of encoder object (thread-unsafe)
1301
def sixel_encoder_unref(encoder):
4✔
1302
    _sixel.sixel_encoder_unref.restype = None
4✔
1303
    _sixel.sixel_encoder_unref.argtypes = [c_void_p]
4✔
1304
    _sixel.sixel_encoder_unref(encoder)
4✔
1305

1306

1307
# set an option flag to encoder object
1308
def sixel_encoder_setopt(encoder, flag, arg=None):
4✔
1309
    _sixel.sixel_encoder_setopt.restype = c_int
4✔
1310
    _sixel.sixel_encoder_setopt.argtypes = [c_void_p, c_int, c_char_p]
4✔
1311
    # Normalize flag for validation while keeping the numeric code used by the
1312
    # C API. Python callers may pass either the character constant ("p") or an
1313
    # integer value. We want to keep the original character for comparison so
1314
    # option-specific validation continues to work even after converting to the
1315
    # numeric code for ctypes.
1316
    if isinstance(flag, int):
4✔
1317
        flag_code = flag
4✔
1318
        flag_char = chr(flag)
4✔
1319
    else:
1320
        flag_char = str(flag)
4✔
1321
        if len(flag_char) != 1:
4✔
1322
            raise RuntimeError(
4✔
1323
                "invalid option flag: expected a single-character flag"
1324
            )
1325
        flag_code = ord(flag_char)
4✔
1326

1327
    if arg:
4✔
1328
        arg = str(arg).encode('utf-8')
4✔
1329
    status = _sixel.sixel_encoder_setopt(encoder, flag_code, arg)
4✔
1330
    if SIXEL_FAILED(status):
4✔
1331
        message = sixel_helper_format_error(status)
4✔
1332
        raise RuntimeError(message)
4✔
1333

1334

1335
# load source data from specified file and encode it to SIXEL format
1336
def sixel_encoder_encode(encoder, filename):
4✔
1337
    import os
4✔
1338
    encoding = _resolve_locale_encoding(default="ascii")
4✔
1339

1340
    # Reject None before touching the codec path because the C API expects a
1341
    # real string pointer. This keeps the exception class deterministic and
1342
    # mirrors the explicit None guard used by sixel_loader_load_file().
1343
    if filename is None:
4✔
1344
        raise TypeError("filename must be str or bytes, not None")
4✔
1345
    if isinstance(filename, memoryview):
4✔
1346
        raise TypeError("filename must be str or bytes, not memoryview")
4✔
1347

1348
    # Proactively validate the input path on the Python side so callers get a
1349
    # deterministic exception even if a platform-specific libc or loader fails
1350
    # to surface a failure.  This mirrors the C-side validation while keeping
1351
    # the behaviour consistent across wheel and in-tree builds.
1352
    if isinstance(filename, bytes):
4✔
1353
        encoded_filename = filename
4✔
1354
        stdin_token = b"-"
4✔
1355
    else:
1356
        encoded_filename = str(filename).encode(encoding)
4✔
1357
        stdin_token = b"-"
4✔
1358

1359
    if encoded_filename != stdin_token:
4✔
1360
        if not os.path.exists(filename):
4✔
1361
            raise RuntimeError(f"input path does not exist: {filename}")
4✔
1362
        if os.path.isdir(filename):
4✔
1363
            raise RuntimeError(f"input path is a directory: {filename}")
4✔
1364

1365
    _sixel.sixel_encoder_encode.restype = c_int
4✔
1366
    _sixel.sixel_encoder_encode.argtypes = [c_void_p, c_char_p]
4✔
1367
    status = _sixel.sixel_encoder_encode(encoder, encoded_filename)
4✔
1368
    if SIXEL_FAILED(status):
4✔
1369
        message = sixel_helper_format_error(status)
4✔
1370
        raise RuntimeError(message)
4✔
1371

1372

1373
# encode specified pixel data to SIXEL format
1374
def sixel_encoder_encode_bytes(encoder, buf, width, height, pixelformat, palette):
4✔
1375

1376
    depth = sixel_helper_compute_depth(pixelformat)
4✔
1377

1378
    if depth <= 0:
4✔
1379
        raise ValueError("invalid pixelformat value : %d" % pixelformat)
4✔
1380

1381
    if len(buf) < width * height * depth:
4✔
1382
        raise ValueError("buf.len is too short : %d < %d * %d * %d" % (buf.len, width, height, depth))
4✔
1383

1384
    if not hasattr(buf, "readonly") or buf.readonly:
4✔
1385
        cbuf = c_void_p.from_buffer_copy(buf)
4✔
1386
    else:
1387
        cbuf = c_void_p.from_buffer(buf)
×
1388

1389
    if palette:
4✔
1390
        cpalettelen = len(palette)
4✔
1391
        cpalette = (c_byte * cpalettelen)(*palette)
4✔
1392
    else:
1393
        cpalettelen = None
4✔
1394
        cpalette = None
4✔
1395

1396
    _sixel.sixel_encoder_encode_bytes.restype = c_int
4✔
1397
    _sixel.sixel_encoder_encode.argtypes = [c_void_p, c_void_p, c_int, c_int, c_int, c_void_p, c_int]
4✔
1398

1399
    status = _sixel.sixel_encoder_encode_bytes(encoder, buf, width, height, pixelformat, cpalette, cpalettelen)
4✔
1400
    if SIXEL_FAILED(status):
4✔
1401
        message = sixel_helper_format_error(status)
×
1402
        raise RuntimeError(message)
×
1403

1404

1405
# create decoder object
1406
def sixel_decoder_new(allocator=c_void_p(None)):
4✔
1407
    _sixel.sixel_decoder_new.restype = c_int
4✔
1408
    _sixel.sixel_decoder_new.argtypes = [POINTER(c_void_p), c_void_p]
4✔
1409
    decoder = c_void_p(None)
4✔
1410
    status = _sixel.sixel_decoder_new(byref(decoder), c_void_p(None))
4✔
1411
    if SIXEL_FAILED(status):
4✔
1412
        message = sixel_helper_format_error(status)
×
1413
        raise RuntimeError(message)
×
1414
    return decoder
4✔
1415

1416

1417
# increase reference count of decoder object (thread-unsafe)
1418
def sixel_decoder_ref(decoder):
4✔
1419
    _sixel.sixel_decoder_ref.restype = None
4✔
1420
    _sixel.sixel_decoder_ref.argtypes = [c_void_p]
4✔
1421
    _sixel.sixel_decoder_ref(decoder)
4✔
1422

1423

1424
# decrease reference count of decoder object (thread-unsafe)
1425
def sixel_decoder_unref(decoder):
4✔
1426
    _sixel.sixel_decoder_unref.restype = None
4✔
1427
    _sixel.sixel_decoder_unref.argtypes = [c_void_p]
4✔
1428
    _sixel.sixel_decoder_unref(decoder)
4✔
1429

1430

1431
# set an option flag to decoder object
1432
def sixel_decoder_setopt(decoder, flag, arg=None):
4✔
1433
    _sixel.sixel_decoder_setopt.restype = c_int
4✔
1434
    _sixel.sixel_decoder_setopt.argtypes = [c_void_p, c_int, c_char_p]
4✔
1435
    flag = ord(flag)
4✔
1436
    if arg:
4✔
1437
        arg = str(arg).encode('utf-8')
4✔
1438
    status = _sixel.sixel_decoder_setopt(decoder, flag, arg)
4✔
1439
    if SIXEL_FAILED(status):
4✔
1440
        message = sixel_helper_format_error(status)
4✔
1441
        raise RuntimeError(message)
4✔
1442

1443

1444
# load source data from stdin or the file
1445
def sixel_decoder_decode(decoder, infile=None):
4✔
1446
    _sixel.sixel_decoder_decode.restype = c_int
4✔
1447
    _sixel.sixel_decoder_decode.argtypes = [c_void_p]
4✔
1448
    if infile:
4✔
1449
        sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, infile)
4✔
1450
    status = _sixel.sixel_decoder_decode(decoder)
4✔
1451
    if SIXEL_FAILED(status):
4✔
1452
        message = sixel_helper_format_error(status)
×
1453
        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