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

saitoha / libsixel / 29651119742

18 Jul 2026 02:41PM UTC coverage: 85.03% (-0.3%) from 85.292%
29651119742

push

github

saitoha
fix: clip size fill for transparent offset bands

79318 of 144774 branches covered (54.79%)

4 of 6 new or added lines in 1 file covered. (66.67%)

3156 existing lines in 22 files now uncovered.

141353 of 166238 relevant lines covered (85.03%)

6269019.35 hits per line

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

90.49
/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
3✔
24
import locale
3✔
25
import os
3✔
26
import pathlib
3✔
27
import struct
3✔
28
from ctypes import (
3✔
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
3✔
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"):
3✔
45
    getencoding = getattr(locale, "getencoding", None)
3✔
46
    if callable(getencoding):
3✔
47
        encoding = getencoding()
3✔
48
    else:
49
        try:
×
50
            encoding = locale.getpreferredencoding(False)
×
51
        except TypeError:
×
52
            encoding = locale.getpreferredencoding()
×
53
    if not encoding:
3✔
54
        encoding = default
×
55
    return encoding
3✔
56

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

66
# loader settings
67
SIXEL_DEFALUT_GIF_DELAY      = 1
3✔
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
3✔
91
SIXEL_LOADER_OPTION_USE_PALETTE = 2
3✔
92
SIXEL_LOADER_OPTION_REQCOLORS = 3
3✔
93
SIXEL_LOADER_OPTION_BGCOLOR = 4
3✔
94
SIXEL_LOADER_OPTION_LOOP_CONTROL = 5
3✔
95
SIXEL_LOADER_OPTION_INSECURE = 6
3✔
96
SIXEL_LOADER_OPTION_CANCEL_FLAG = 7
3✔
97
SIXEL_LOADER_OPTION_LOADER_ORDER = 8
3✔
98
SIXEL_LOADER_OPTION_CONTEXT = 9
3✔
99
SIXEL_LOADER_OPTION_WIC_ICO_MINSIZE = 10
3✔
100
SIXEL_LOADER_OPTION_START_FRAME_NO = 11
3✔
101
SIXEL_LOADER_OPTION_BGCOLOR_SOURCE = 12
3✔
102

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

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

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

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

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

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

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

137
def SIXEL_FAILED(status):
3✔
138
    return (((status) & 0x1000) != 0)
3✔
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
3✔
143
SIXEL_LARGE_NORM = 0x1   # simply comparing the range in RGB space
3✔
144
SIXEL_LARGE_LUM  = 0x2   # transforming into luminosities before the comparison
3✔
145
SIXEL_LARGE_PCA  = 0x3   # cut along the first principal component
3✔
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
3✔
149
SIXEL_REP_CENTER_BOX     = 0x1  # choose the center of the box
3✔
150
SIXEL_REP_AVERAGE_COLORS = 0x2  # choose the average all the color in the box (specified in Heckbert's paper)
3✔
151
SIXEL_REP_AVERAGE_PIXELS = 0x3  # choose the average all the pixels in the box
3✔
152

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

171

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

174

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

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

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

188

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

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

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

203

204
def _detect_pe_class(bin_path: pathlib.Path, data: bytes) -> str:
3✔
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:
3✔
230
    """Inspect the binary header to determine its architecture width."""
231

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

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

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

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

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

279
# offset value of pixelFormat
280
SIXEL_FORMATTYPE_COLOR     = (0)
3✔
281
SIXEL_FORMATTYPE_GRAYSCALE = (1 << 6)
3✔
282
SIXEL_FORMATTYPE_PALETTE   = (1 << 7)
3✔
283

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

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

320
# palette type
321
SIXEL_PALETTETYPE_AUTO     = 0   # choose palette type automatically
3✔
322
SIXEL_PALETTETYPE_HLS      = 1   # HLS colorspace
3✔
323
SIXEL_PALETTETYPE_RGB      = 2   # RGB colorspace
3✔
324

325
# policies of SIXEL encoding
326
SIXEL_ENCODEPOLICY_AUTO    = 0   # choose encoding policy automatically
3✔
327
SIXEL_ENCODEPOLICY_FAST    = 1   # encode as fast as possible
3✔
328
SIXEL_ENCODEPOLICY_SIZE    = 2   # encode to as small sixel sequence as possible
3✔
329

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

352
SIXEL_GPU_POLICY_OFF       = 0x0  # keep CPU palette apply
3✔
353
SIXEL_GPU_POLICY_AUTO      = 0x1  # probe GPU above internal threshold
3✔
354
SIXEL_GPU_POLICY_FORCE     = 0x2  # require a supported GPU path
3✔
355

356
# method for re-sampling
357
SIXEL_RES_NEAREST          = 0   # Use nearest neighbor method
3✔
358
SIXEL_RES_GAUSSIAN         = 1   # Use guaussian filter
3✔
359
SIXEL_RES_HANNING          = 2   # Use hanning filter
3✔
360
SIXEL_RES_HAMMING          = 3   # Use hamming filter
3✔
361
SIXEL_RES_BILINEAR         = 4   # Use bilinear filter
3✔
362
SIXEL_RES_WELSH            = 5   # Use welsh filter
3✔
363
SIXEL_RES_BICUBIC          = 6   # Use bicubic filter
3✔
364
SIXEL_RES_LANCZOS2         = 7   # Use lanczos-2 filter
3✔
365
SIXEL_RES_LANCZOS3         = 8   # Use lanczos-3 filter
3✔
366
SIXEL_RES_LANCZOS4         = 9   # Use lanczos-4 filter
3✔
367

368
# image format
369
SIXEL_FORMAT_GIF           = 0x0 # read only
3✔
370
SIXEL_FORMAT_PNG           = 0x1 # read/write
3✔
371
SIXEL_FORMAT_BMP           = 0x2 # read only
3✔
372
SIXEL_FORMAT_JPG           = 0x3 # read only
3✔
373
SIXEL_FORMAT_TGA           = 0x4 # read only
3✔
374
SIXEL_FORMAT_WBMP          = 0x5 # read only with --with-gd configure option
3✔
375
SIXEL_FORMAT_TIFF          = 0x6 # read only
3✔
376
SIXEL_FORMAT_SIXEL         = 0x7 # read only
3✔
377
SIXEL_FORMAT_PNM           = 0x8 # read only
3✔
378
SIXEL_FORMAT_GD2           = 0x9 # read only with --with-gd configure option
3✔
379
SIXEL_FORMAT_PSD           = 0xa # read only
3✔
380
SIXEL_FORMAT_HDR           = 0xb # read only
3✔
381

382
# loop mode
383
SIXEL_LOOP_AUTO            = 0   # honer the setting of GIF header
3✔
384
SIXEL_LOOP_FORCE           = 1   # always enable loop
3✔
385
SIXEL_LOOP_DISABLE         = 2   # always disable loop
3✔
386
SIXEL_6DELTA_ERROR_DIFFUSE = 0   # diffuse error from kept RGB
3✔
387
SIXEL_6DELTA_ERROR_SKIP    = 1   # skip diffusion on kept pixels
3✔
388

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

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

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

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

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

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

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

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

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

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

623
SIXEL_OPTFLAG_ENCODE_POLICY    = 'E'  # -E ENCODEPOLICY, --encode-policy=ENCODEPOLICY:
3✔
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,
3✔
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_GPU_POLICY        = 'G'  # -G GPUPOLICY,
3✔
647
                                      #   --gpu-policy=GPUPOLICY:
648
                                      #        choose palette-apply accelerator
649
                                      #          off   -> keep CPU path
650
                                      #          auto  -> probe GPU above the
651
                                      #                   internal threshold
652
                                      #          force -> require GPU path
653
SIXEL_OPTFLAG_CLUSTERING_COLORSPACE = 'X'  # -X COLORSPACE, --clustering-colorspace=COLORSPACE:
3✔
654
                                          #        select palette clustering space.
655
                                          #        ignored with fixed palette options (-b, -m, -e).
656
                                          #          gamma  -> keep gamma encoded pixels
657
                                          #          linear -> convert to linear RGB
658
                                          #          oklab  -> operate in OKLab
659
                                          #          cielab -> operate in CIELAB
660
                                          #          din99d -> operate in DIN99d
661
SIXEL_OPTFLAG_WORKING_COLORSPACE = 'W'  # -W WORKING_COLORSPACE, --working-colorspace=COLORSPACE:
3✔
662
                                      #        select internal working space.
663
                                      #          gamma  -> keep gamma encoded pixels
664
                                      #          linear -> convert to linear RGB
665
                                      #          oklab  -> operate in OKLab
666
                                      #          cielab -> operate in CIELAB
667
                                      #          din99d -> operate in DIN99d
668
SIXEL_OPTFLAG_OUTPUT_COLORSPACE = 'U'  # -U OUTPUT_COLORSPACE, --output-colorspace=COLORSPACE:
3✔
669
                                      #        select output buffer color space.
670
                                      #          gamma   -> sRGB gamma encoded output
671
                                      #          linear  -> linear RGB output
672
                                      #          smpte-c -> SMPTE-C gamma encoded output
673
SIXEL_OPTFLAG_ORMODE           = 'O'  # -O, --ormode: output ormode sixel image
3✔
674
SIXEL_OPTFLAG_TRANSPARENT_POLICY = 'A'  # -A POLICY, --transparent-policy=POLICY:
3✔
675
                                        #        select transparent pixel output
676
                                        #        handling.
677
SIXEL_OPTFLAG_TRANSPARENT_OFFSET = '+'  # -+ ROWS, --transparent-offset=ROWS:
3✔
678
                                        #        delay transparent rows.
679
SIXEL_OPTFLAG_6DELTA_THRESHOLD = 'Z'  # -Z DELTA, --6delta-threshold=DELTA:
3✔
680
                                      #        set per-channel keep tolerance.
681
SIXEL_OPTFLAG_6DELTA_ERROR     = 'Y'  # -Y MODE, --6delta-error=MODE:
3✔
682
                                      #        select kept-pixel error handling.
683

684
SIXEL_OPTFLAG_BGCOLOR          = 'B'  # -B BGCOLOR, --bgcolor=BGCOLOR:
3✔
685
                                      #        specify background color
686
                                      #        BGCOLOR is represented by the
687
                                      #        following syntax
688
                                      #          #rgb
689
                                      #          #rrggbb
690
                                      #          #rrrgggbbb
691
                                      #          #rrrrggggbbbb
692
                                      #          rgb:r/g/b
693
                                      #          rgb:rr/gg/bb
694
                                      #          rgb:rrr/ggg/bbb
695
                                      #          rgb:rrrr/gggg/bbbb
696

697
SIXEL_OPTFLAG_PENETRATE        = 'P'  # -P, --penetrate: (deprecated)
3✔
698
                                      #        penetrate GNU Screen using DCS
699
                                      #        pass-through sequence
700
SIXEL_OPTFLAG_DRCS             = '@'  # -@ MMV:CHARSET:PATH, --drcs=MMV:CHARSET:PATH:
3✔
701
                                      #        emit extended DRCS tiles, optionally
702
                                      #        overriding mapping revision, charset,
703
                                      #        and tile sink (defaults to 2:1:;
704
                                      #        experimental)
705
SIXEL_OPTFLAG_PIPE_MODE        = 'D'  # -D, --pipe-mode: (deprecated)
3✔
706
                                      #         read source images from stdin continuously
707
SIXEL_OPTFLAG_VERBOSE          = 'v'  # -v, --verbose: show debugging info
3✔
708
SIXEL_OPTFLAG_VERSION          = 'V'  # -V, --version: show version and license info
3✔
709
SIXEL_OPTFLAG_HELP             = 'H'  # -H, --help: show this help
3✔
710

711
_sixel_names = [
3✔
712
    "sixel",
713
    "libsixel",
714
    "sixel-1",
715
    "libsixel-1",
716
    "msys-sixel",
717
    "cygsixel",
718
]
719

720
def _match_library_in_dir(libdir, lib_names):
3✔
721
    """Return the first matching shared library in the given directory.
722

723
    The selection logic is intentionally aligned with the build helper:
724

725
    - Accept both "lib" prefixed and prefixless names.
726
    - Accept .so, .dylib, or .dll (including versioned .so.* files).
727
    - Skip import archives like *.dll.a or *.dll.def.
728
    """
729

730
    prefixes = ["lib", ""]
3✔
731
    suffixes = [".so", ".dylib", ".dll"]
3✔
732

733
    for name in lib_names:
3✔
734
        for prefix in prefixes:
3✔
735
            for suffix in suffixes:
3✔
736
                patterns: list[str]
737
                if suffix == ".so":
3✔
738
                    patterns = [
3✔
739
                        os.path.join(libdir, f"{prefix}{name}*{suffix}"),
740
                        os.path.join(libdir, f"{prefix}{name}*{suffix}.*"),
741
                    ]
742
                else:
UNCOV
743
                    patterns = [
×
744
                        os.path.join(libdir, f"{prefix}{name}*{suffix}"),
745
                    ]
746

747
                matches: list[str] = []
3✔
748
                for pattern in patterns:
3✔
749
                    matches.extend(glob.glob(pattern))
3✔
750

751
                filtered: list[str] = []
3✔
752
                for candidate in sorted(set(matches)):
3✔
753
                    if candidate.endswith((".dll.a", ".dll.def")):
3✔
UNCOV
754
                        continue
×
755

756
                    bits = _detect_library_bits(pathlib.Path(candidate))
3✔
757
                    if bits and bits != str(_PYTHON_BITS):
3✔
UNCOV
758
                        continue
×
759

760
                    filtered.append(candidate)
3✔
761

762
                if filtered:
3✔
763
                    return filtered[0]
3✔
764

UNCOV
765
    return None
×
766

767

768
def _prefer_bundled_library(lib_names):
3✔
769
    """Locate a bundled shared library shipped inside the wheel package.
770

771
    Wheel builds copy the shared library into libsixel/_libs so that imports
772
    succeed even when libsixel is not installed system-wide.
773
    """
774

775
    bundle_dir = pathlib.Path(__file__).resolve().parent / "_libs"
3✔
776
    if not bundle_dir.is_dir():
3✔
UNCOV
777
        return None
×
778
    return _match_library_in_dir(str(bundle_dir), lib_names)
3✔
779

780

781
def _prefer_env_library(lib_names):
3✔
782
    """Locate libsixel under LIBSIXEL_LIBDIR when running from a build tree."""
783

UNCOV
784
    libdir = os.environ.get("LIBSIXEL_LIBDIR")
×
UNCOV
785
    if libdir is None:
×
UNCOV
786
        return None
×
UNCOV
787
    return _match_library_in_dir(libdir, lib_names)
×
788

789

790
_lib_path = _prefer_bundled_library(_sixel_names)
3✔
791

792
if _lib_path is None:
3✔
UNCOV
793
    _lib_path = _prefer_env_library(_sixel_names)
×
794

795
if _lib_path is None:
3✔
UNCOV
796
    _lib_path = next(
×
797
        (path for path in (find_library(name) for name in _sixel_names)
798
         if path is not None),
799
        None,
800
    )
801

802
if _lib_path is None:
3✔
UNCOV
803
    raise ImportError(
×
804
        "libsixel not found. Set LIBSIXEL_LIBDIR to the built shared library."
805
    )
806

807
_lib_bits = _detect_library_bits(pathlib.Path(_lib_path))
3✔
808
if _lib_bits and _lib_bits != str(_PYTHON_BITS):
3✔
UNCOV
809
    raise ImportError(
×
810
        f"libsixel {_lib_bits}-bit library is incompatible with "
811
        f"python {_PYTHON_BITS}-bit"
812
    )
813

814
# load shared library
815
_sixel = cdll.LoadLibrary(_lib_path)
3✔
816

817
# convert error status code int formatted string
818
def sixel_helper_format_error(status):
3✔
819
    _sixel.sixel_helper_format_error.restype = c_char_p;
3✔
820
    _sixel.sixel_helper_format_error.argtypes = [c_int];
3✔
821
    return _sixel.sixel_helper_format_error(status)
3✔
822

823

824
# compute pixel depth from pixelformat
825
def sixel_helper_compute_depth(pixelformat):
3✔
826
    _sixel.sixel_helper_compute_depth.restype = c_int
3✔
827
    _sixel.sixel_helper_compute_depth.argtypes = [c_int]
3✔
828
    return _sixel.sixel_helper_compute_depth(pixelformat)
3✔
829

830

831
# generic loader -----------------------------------------------------------
832

833
_sixel_loader_callback_type = CFUNCTYPE(c_int, c_void_p, c_void_p)
3✔
834

835

836
def sixel_loader_new(allocator=c_void_p(None)):
3✔
837
    """Create a loader object that mirrors sixel_loader_new()."""
838

839
    _sixel.sixel_loader_new.restype = c_int
3✔
840
    _sixel.sixel_loader_new.argtypes = [POINTER(c_void_p), c_void_p]
3✔
841

842
    loader = c_void_p(None)
3✔
843
    status = _sixel.sixel_loader_new(byref(loader), allocator)
3✔
844
    if SIXEL_FAILED(status):
3✔
UNCOV
845
        message = sixel_helper_format_error(status)
×
UNCOV
846
        raise RuntimeError(message)
×
847
    return loader
3✔
848

849

850
def sixel_loader_ref(loader):
3✔
851
    """Increase the reference count of a loader object."""
852

853
    _sixel.sixel_loader_ref.restype = None
3✔
854
    _sixel.sixel_loader_ref.argtypes = [c_void_p]
3✔
855
    _sixel.sixel_loader_ref(loader)
3✔
856

857

858
def sixel_loader_unref(loader):
3✔
859
    """Decrease the reference count of a loader object."""
860

861
    _sixel.sixel_loader_unref.restype = None
3✔
862
    _sixel.sixel_loader_unref.argtypes = [c_void_p]
3✔
863
    _sixel.sixel_loader_unref(loader)
3✔
864

865

866
def sixel_loader_setopt(loader, option, value=None):
3✔
867
    """Configure loader behavior via sixel_loader_setopt().
868

869
    The helper routes Python values into the pointer-based C API while keeping
870
    the conversion rules in plain sight:
871

872
        +-----------+---------------------------+---------------------+
873
        | Option    | Expected Python value     | Example             |
874
        +-----------+---------------------------+---------------------+
875
        | STATIC    | bool/int or None          | True                |
876
        | PALETTE   | bool/int or None          | 0                   |
877
        | REQCOLORS | int or None               | 256                 |
878
        | BGCOLOR   | iterable[3] or None       | (0, 0, 0)           |
879
        | LOOP      | int or None               | SIXEL_LOOP_FORCE    |
880
        | INSECURE  | bool/int or None          | False               |
881
        | CANCEL    | ctypes pointer / address  | byref(c_int(0))     |
882
        | ORDER     | str/bytes/bytearray or None | "builtin"         |
883
        | CONTEXT   | ctypes pointer / address  | c_void_p(id(obj))   |
884
        | WIC SIZE  | int or None               | 64                  |
885
        +-----------+---------------------------+---------------------+
886

887
    Values left as ``None`` map to NULL so that the C side may install its
888
    default behavior.
889
    """
890

891
    _sixel.sixel_loader_setopt.restype = c_int
3✔
892
    _sixel.sixel_loader_setopt.argtypes = [c_void_p, c_int, c_void_p]
3✔
893

894
    option = int(option)
3✔
895
    pointer_value = c_void_p(None)
3✔
896
    keepalive = None
3✔
897

898
    int_options = {
3✔
899
        SIXEL_LOADER_OPTION_REQUIRE_STATIC,
900
        SIXEL_LOADER_OPTION_USE_PALETTE,
901
        SIXEL_LOADER_OPTION_REQCOLORS,
902
        SIXEL_LOADER_OPTION_LOOP_CONTROL,
903
        SIXEL_LOADER_OPTION_INSECURE,
904
        SIXEL_LOADER_OPTION_WIC_ICO_MINSIZE,
905
        SIXEL_LOADER_OPTION_START_FRAME_NO,
906
    }
907

908
    if option in int_options:
3✔
909
        if value is not None:
3✔
910
            keepalive = c_int(int(value))
3✔
911
            pointer_value = cast(byref(keepalive), c_void_p)
3✔
912
    elif option == SIXEL_LOADER_OPTION_BGCOLOR:
3✔
913
        if value is not None:
3✔
914
            if len(value) != 3:
3✔
915
                raise ValueError("bgcolor expects three components")
3✔
916
            keepalive = (c_byte * 3)(value[0], value[1], value[2])
3✔
917
            pointer_value = cast(keepalive, c_void_p)
3✔
918
    elif option == SIXEL_LOADER_OPTION_LOADER_ORDER:
3✔
919
        if value is not None:
3✔
920
            if isinstance(value, bytes):
3✔
921
                encoded = value
3✔
922
            elif isinstance(value, bytearray):
3✔
923
                encoded = bytes(value)
3✔
924
            elif isinstance(value, str):
3✔
925
                encoded = value.encode('utf-8')
3✔
926
            else:
927
                raise TypeError(
3✔
928
                    "loader_order expects str, bytes, bytearray, or None"
929
                )
930
            keepalive = c_char_p(encoded)
3✔
931
            pointer_value = cast(keepalive, c_void_p)
3✔
932
    elif option in (
3✔
933
        SIXEL_LOADER_OPTION_CANCEL_FLAG,
934
        SIXEL_LOADER_OPTION_CONTEXT,
935
    ):
936
        if value is None:
3✔
UNCOV
937
            pointer_value = c_void_p(None)
×
938
        elif isinstance(value, c_void_p):
3✔
939
            pointer_value = value
3✔
940
        elif isinstance(value, int):
3✔
941
            pointer_value = c_void_p(value)
3✔
942
        else:
943
            pointer_value = cast(value, c_void_p)
3✔
944
    else:
945
        raise ValueError("unknown loader option: %r" % option)
3✔
946

947
    status = _sixel.sixel_loader_setopt(loader, option, pointer_value)
3✔
948
    if SIXEL_FAILED(status):
3✔
UNCOV
949
        message = sixel_helper_format_error(status)
×
UNCOV
950
        raise RuntimeError(message)
×
951

952

953
def sixel_loader_load_file(loader, filename, fn_load):
3✔
954
    """Load ``filename`` and feed each frame to ``fn_load``.
955

956
    ``fn_load`` receives ``(frame_ptr, context_ptr)`` mirroring the C
957
    signature.  The loader's context pointer may be set via
958
    ``sixel_loader_setopt``.
959
    """
960

961
    if fn_load is None:
3✔
962
        raise ValueError("fn_load callback is required")
3✔
963
    if not callable(fn_load):
3✔
964
        raise TypeError("fn_load callback must be callable")
3✔
965

966
    _sixel.sixel_loader_load_file.restype = c_int
3✔
967
    _sixel.sixel_loader_load_file.argtypes = [
3✔
968
        c_void_p,
969
        c_char_p,
970
        _sixel_loader_callback_type,
971
    ]
972

973
    encoding = _resolve_locale_encoding(default="utf-8")
3✔
974

975
    # The C API expects a non-NULL filename pointer.
976
    # Reject None in the Python wrapper to avoid passing NULL and
977
    # crashing inside the native loader implementation.
978
    if filename is None:
3✔
979
        raise TypeError("filename must be str or bytes, not None")
3✔
980
    elif isinstance(filename, bytes):
3✔
981
        c_filename = filename
3✔
982
    else:
983
        c_filename = filename.encode(encoding)
3✔
984

985
    def _fn_load_local(frame, context):
3✔
986
        return fn_load(frame, context)
3✔
987

988
    callback = _sixel_loader_callback_type(_fn_load_local)
3✔
989
    status = _sixel.sixel_loader_load_file(loader, c_filename, callback)
3✔
990
    if SIXEL_FAILED(status):
3✔
991
        message = sixel_helper_format_error(status)
3✔
992
        raise RuntimeError(message)
3✔
993

994

995
# create new output context object
996
def sixel_output_new(fn_write, priv=None, allocator=c_void_p(None)):
3✔
997
    output = c_void_p(None)
3✔
998

999
    # ctypes callback exceptions do not propagate to the original Python
1000
    # caller. Keep the original exception object on the output handle so
1001
    # sixel_encode() can re-raise it in the caller context.
1002
    output.__callback_exception = None
3✔
1003

1004
    def _fn_write_local(data, size, priv_from_c):
3✔
1005
        try:
3✔
1006
            fn_write(string_at(data, size), priv)
3✔
1007
        except Exception as exc:
3✔
1008
            output.__callback_exception = exc
3✔
1009
            return -1
3✔
1010
        return size
3✔
1011

1012
    sixel_write_function = CFUNCTYPE(c_int, c_char_p, c_int, c_void_p)
3✔
1013
    _sixel.sixel_output_new.restype = c_int
3✔
1014
    _sixel.sixel_output_new.argtypes = [POINTER(c_void_p), sixel_write_function, c_void_p, c_void_p]
3✔
1015
    _fn_write = sixel_write_function(_fn_write_local)
3✔
1016
    _fn_write.restype = c_int
3✔
1017
    _fn_write.argtypes = [sixel_write_function, c_void_p, c_void_p]
3✔
1018
    status = _sixel.sixel_output_new(byref(output), _fn_write, c_void_p(None), allocator)
3✔
1019
    if SIXEL_FAILED(status):
3✔
UNCOV
1020
        message = sixel_helper_format_error(status)
×
UNCOV
1021
        raise RuntimeError(message)
×
1022
    output.__fn_write = _fn_write
3✔
1023
    return output
3✔
1024

1025

1026
# increase reference count of output object (thread-unsafe)
1027
def sixel_output_ref(output):
3✔
1028
    _sixel.sixel_output_ref.restype = None
3✔
1029
    _sixel.sixel_output_ref.argtypes = [c_void_p]
3✔
1030
    _sixel.sixel_output_ref(output)
3✔
1031

1032

1033
# decrease reference count of output object (thread-unsafe)
1034
def sixel_output_unref(output):
3✔
1035
    _sixel.sixel_output_unref.restype = None
3✔
1036
    _sixel.sixel_output_unref.argtypes = [c_void_p]
3✔
1037
    _sixel.sixel_output_unref(output)
3✔
1038
    output.__fn_write = None
3✔
1039
    output.__callback_exception = None
3✔
1040

1041

1042
# get 8bit output mode which indicates whether it uses C1 control characters
1043
def sixel_output_get_8bit_availability(output):
3✔
1044
    _sixel.sixel_output_get_8bit_availability.restype = c_int
3✔
1045
    _sixel.sixel_output_get_8bit_availability.argtypes = [c_void_p]
3✔
1046
    return _sixel.sixel_output_get_8bit_availability(output)
3✔
1047

1048

1049
# set 8bit output mode state
1050
def sixel_output_set_8bit_availability(output, availability):
3✔
1051
    _sixel.sixel_output_set_8bit_availability.restype = None
3✔
1052
    _sixel.sixel_output_set_8bit_availability.argtypes = [c_void_p, c_int]
3✔
1053
    _sixel.sixel_output_set_8bit_availability(output, availability)
3✔
1054

1055

1056
# set whether limit arguments of DECGRI('!') to 255
1057
def sixel_output_set_gri_arg_limit(output, value):
3✔
1058
    _sixel.sixel_output_set_gri_arg_limit.restype = None
3✔
1059
    _sixel.sixel_output_set_gri_arg_limit.argtypes = [c_void_p, c_int]
3✔
1060
    _sixel.sixel_output_set_gri_arg_limit(output, value)
3✔
1061

1062

1063
# set GNU Screen penetration feature enable or disable
1064
def sixel_output_set_penetrate_multiplexer(output, penetrate):
3✔
1065
    _sixel.sixel_output_set_penetrate_multiplexer.restype = None
3✔
1066
    _sixel.sixel_output_set_penetrate_multiplexer.argtypes = [c_void_p, c_int]
3✔
1067
    _sixel.sixel_output_set_penetrate_multiplexer(output, penetrate)
3✔
1068

1069

1070
# set whether we skip DCS envelope
1071
def sixel_output_set_skip_dcs_envelope(output, skip):
3✔
1072
    _sixel.sixel_output_set_skip_dcs_envelope.restype = None
3✔
1073
    _sixel.sixel_output_set_skip_dcs_envelope.argtypes = [c_void_p, c_int]
3✔
1074
    _sixel.sixel_output_set_skip_dcs_envelope(output, skip)
3✔
1075

1076

1077
# set whether we skip SIXEL header
1078
def sixel_output_set_skip_header(output, skip):
3✔
1079
    _sixel.sixel_output_set_skip_header.restype = None
3✔
1080
    _sixel.sixel_output_set_skip_header.argtypes = [c_void_p, c_int]
3✔
1081
    _sixel.sixel_output_set_skip_header(output, skip)
3✔
1082

1083

1084
# set palette type: RGB or HLS
1085
def sixel_output_set_palette_type(output, palettetype):
3✔
1086
    _sixel.sixel_output_set_palette_type.restype = None
3✔
1087
    _sixel.sixel_output_set_palette_type.argtypes = [c_void_p, c_int]
3✔
1088
    _sixel.sixel_output_set_palette_type(output, palettetype)
3✔
1089

1090

1091
# enable or disable ormode output
1092
def sixel_output_set_ormode(output, ormode):
3✔
1093
    _sixel.sixel_output_set_ormode.restype = None
3✔
1094
    _sixel.sixel_output_set_ormode.argtypes = [c_void_p, c_int]
3✔
1095
    _sixel.sixel_output_set_ormode(output, ormode)
3✔
1096

1097

1098
# set encodeing policy: auto, fast or size
1099
def sixel_output_set_encode_policy(output, encode_policy):
3✔
1100
    _sixel.sixel_output_set_encode_policy.restype = None
3✔
1101
    _sixel.sixel_output_set_encode_policy.argtypes = [c_void_p, c_int]
3✔
1102
    _sixel.sixel_output_set_encode_policy(output, encode_policy)
3✔
1103

1104

1105
# create dither context object
1106
def sixel_dither_new(ncolors, allocator=None):
3✔
1107
    _sixel.sixel_dither_new.restype = c_int
3✔
1108
    _sixel.sixel_dither_new.argtypes = [POINTER(c_void_p), c_int, c_void_p]
3✔
1109
    dither = c_void_p(None)
3✔
1110
    status = _sixel.sixel_dither_new(byref(dither), ncolors, allocator)
3✔
1111
    if SIXEL_FAILED(status):
3✔
UNCOV
1112
        message = sixel_helper_format_error(status)
×
UNCOV
1113
        raise RuntimeError(message)
×
1114
    return dither
3✔
1115

1116

1117
# get built-in dither context object
1118
def sixel_dither_get(builtin_dither):
3✔
1119
    _sixel.sixel_dither_get.restype = c_void_p
3✔
1120
    _sixel.sixel_dither_get.argtypes = [c_int]
3✔
1121
    return _sixel.sixel_dither_get(builtin_dither)
3✔
1122

1123

1124
# destroy dither context object
1125
def sixel_dither_destroy(dither):
3✔
1126
    _sixel.sixel_dither_destroy.restype = None
3✔
1127
    _sixel.sixel_dither_destroy.argtypes = [c_void_p]
3✔
1128
    return _sixel.sixel_dither_destroy(dither)
3✔
1129

1130

1131
# increase reference count of dither context object (thread-unsafe)
1132
def sixel_dither_ref(dither):
3✔
1133
    _sixel.sixel_dither_ref.restype = None
3✔
1134
    _sixel.sixel_dither_ref.argtypes = [c_void_p]
3✔
1135
    return _sixel.sixel_dither_ref(dither)
3✔
1136

1137

1138
# decrease reference count of dither context object (thread-unsafe)
1139
def sixel_dither_unref(dither):
3✔
1140
    _sixel.sixel_dither_unref.restype = None
3✔
1141
    _sixel.sixel_dither_unref.argtypes = [c_void_p]
3✔
1142
    return _sixel.sixel_dither_unref(dither)
3✔
1143

1144

1145
# initialize internal palette from specified pixel buffer
1146
def sixel_dither_initialize(dither, data, width, height, pixelformat,
3✔
1147
                            method_for_largest=SIXEL_LARGE_AUTO,
1148
                            method_for_rep=SIXEL_REP_AUTO,
1149
                            quality_mode=SIXEL_QUALITY_AUTO):
1150
    _sixel.sixel_dither_initialize.restype = c_int
3✔
1151
    _sixel.sixel_dither_initialize.argtypes = [c_void_p, c_char_p, c_int, c_int, c_int,
3✔
1152
                                              c_int, c_int, c_int]
1153
    status = _sixel.sixel_dither_initialize(dither, data, width, height, pixelformat,
3✔
1154
                                            method_for_largest,
1155
                                            method_for_rep,
1156
                                            quality_mode)
1157
    if SIXEL_FAILED(status):
3✔
UNCOV
1158
        message = sixel_helper_format_error(status)
×
UNCOV
1159
        raise RuntimeError(message)
×
1160

1161

1162
# set diffusion type, choose from enum methodForDiffuse
1163
def sixel_dither_set_diffusion_type(dither, method_for_diffuse):
3✔
1164
    _sixel.sixel_dither_set_diffusion_type.restype = None
3✔
1165
    _sixel.sixel_dither_set_diffusion_type.argtypes = [c_void_p, c_int]
3✔
1166
    _sixel.sixel_dither_set_diffusion_type(dither, method_for_diffuse)
3✔
1167

1168

1169
def sixel_dither_set_diffusion_scan(dither, method_for_scan):
3✔
1170
    _sixel.sixel_dither_set_diffusion_scan.restype = None
3✔
1171
    _sixel.sixel_dither_set_diffusion_scan.argtypes = [c_void_p, c_int]
3✔
1172
    _sixel.sixel_dither_set_diffusion_scan(dither, method_for_scan)
3✔
1173

1174

1175
# get number of palette colors
1176
def sixel_dither_get_num_of_palette_colors(dither):
3✔
1177
    _sixel.sixel_dither_get_num_of_palette_colors.restype = c_int
3✔
1178
    _sixel.sixel_dither_get_num_of_palette_colors.argtypes = [c_void_p]
3✔
1179
    return _sixel.sixel_dither_get_num_of_palette_colors(dither)
3✔
1180

1181

1182
# get number of histogram colors */
1183
def sixel_dither_get_num_of_histogram_colors(dither):
3✔
1184
    _sixel.sixel_dither_get_num_of_histogram_colors.restype = c_int
3✔
1185
    _sixel.sixel_dither_get_num_of_histogram_colors.argtypes = [c_void_p]
3✔
1186
    return _sixel.sixel_dither_get_num_of_histogram_colors(dither)
3✔
1187

1188

1189
def sixel_dither_get_palette(dither):
3✔
1190
    _sixel.sixel_dither_get_palette.restype = c_char_p
3✔
1191
    _sixel.sixel_dither_get_palette.argtypes = [c_void_p]
3✔
1192
    cpalette = _sixel.sixel_dither_get_palette(dither)
3✔
1193
    return list(cpalette)
3✔
1194

1195

1196
def sixel_dither_set_palette(dither, palette):
3✔
1197
    _sixel.sixel_dither_set_palette.restype = None
3✔
1198
    _sixel.sixel_dither_set_palette.argtypes = [c_void_p, c_char_p]
3✔
1199
    cpalette = bytes(palette)
3✔
1200
    _sixel.sixel_dither_set_palette(dither, cpalette)
3✔
1201

1202

1203
def sixel_dither_set_complexion_score(dither, score):
3✔
1204
    _sixel.sixel_dither_set_complexion_score.restype = None
3✔
1205
    _sixel.sixel_dither_set_complexion_score.argtypes = [c_void_p, c_int]
3✔
1206
    _sixel.sixel_dither_set_complexion_score(dither, score)
3✔
1207

1208

1209
def sixel_dither_set_body_only(dither, bodyonly):
3✔
1210
    _sixel.sixel_dither_set_body_only.restype = None
3✔
1211
    _sixel.sixel_dither_set_body_only.argtypes = [c_void_p, c_int]
3✔
1212
    _sixel.sixel_dither_set_body_only(dither, bodyonly)
3✔
1213

1214

1215
def sixel_dither_set_optimize_palette(dither, do_opt):
3✔
1216
    _sixel.sixel_dither_set_optimize_palette.restype = None
3✔
1217
    _sixel.sixel_dither_set_optimize_palette.argtypes = [c_void_p, c_int]
3✔
1218
    _sixel.sixel_dither_set_optimize_palette(dither, do_opt)
3✔
1219

1220

1221
def sixel_dither_set_pixelformat(dither, pixelformat):
3✔
1222
    _sixel.sixel_dither_set_pixelformat.restype = None
3✔
1223
    _sixel.sixel_dither_set_pixelformat.argtypes = [c_void_p, c_int]
3✔
1224
    _sixel.sixel_dither_set_pixelformat(dither, pixelformat)
3✔
1225

1226

1227
def sixel_dither_set_transparent(dither, transparent):
3✔
1228
    _sixel.sixel_dither_set_transparent.restype = None
3✔
1229
    _sixel.sixel_dither_set_transparent.argtypes = [c_void_p, c_int]
3✔
1230
    _sixel.sixel_dither_set_transparent(dither, transparent)
3✔
1231

1232

1233
# configure the encoder thread count for band parallelism
1234
def sixel_set_threads(threads):
3✔
1235
    auto_requested = False
3✔
1236
    value = 0
3✔
1237
    text = None
3✔
1238

1239
    if isinstance(threads, bytes):
3✔
1240
        try:
3✔
1241
            text = threads.decode('utf-8').strip()
3✔
1242
        except UnicodeDecodeError as exc:
3✔
1243
            raise ValueError(
3✔
1244
                "threads must be a positive integer or 'auto'"
1245
            ) from exc
1246
    elif isinstance(threads, str):
3✔
1247
        text = threads.strip()
3✔
1248
    else:
1249
        text = None
3✔
1250

1251
    if text is not None:
3✔
1252
        if text.lower() == 'auto':
3✔
1253
            auto_requested = True
3✔
1254
            value = 0
3✔
1255
        else:
1256
            try:
3✔
1257
                value = int(text, 10)
3✔
1258
            except ValueError as exc:
3✔
1259
                raise ValueError(
3✔
1260
                    "threads must be a positive integer or 'auto'"
1261
                ) from exc
1262
    else:
1263
        try:
3✔
1264
            value = int(threads)
3✔
1265
        except (TypeError, ValueError) as exc:
3✔
1266
            raise ValueError(
3✔
1267
                "threads must be a positive integer or 'auto'"
1268
            ) from exc
1269

1270
    if auto_requested is False and value < 1:
3✔
1271
        raise ValueError("threads must be a positive integer or 'auto'")
3✔
1272

1273
    _sixel.sixel_set_threads.restype = None
3✔
1274
    _sixel.sixel_set_threads.argtypes = [c_int]
3✔
1275
    _sixel.sixel_set_threads(value)
3✔
1276

1277

1278
# convert pixels into sixel format and write it to output context
1279
def sixel_encode(pixels, width, height, depth, dither, output):
3✔
1280
    _sixel.sixel_encode.restype = c_int
3✔
1281
    _sixel.sixel_encode.argtypes = [c_char_p, c_int, c_int, c_int, c_void_p, c_void_p]
3✔
1282
    status = _sixel.sixel_encode(pixels, width, height, depth, dither, output)
3✔
1283

1284
    callback_exception = getattr(output, '__callback_exception', None)
3✔
1285
    if callback_exception is not None:
3✔
1286
        output.__callback_exception = None
3✔
1287
        raise callback_exception
3✔
1288

1289
    return status
3✔
1290

1291

1292
# create encoder object
1293
def sixel_encoder_new(allocator=c_void_p(None)):
3✔
1294
    _sixel.sixel_encoder_new.restype = c_int
3✔
1295
    _sixel.sixel_encoder_new.argtypes = [POINTER(c_void_p), c_void_p]
3✔
1296
    encoder = c_void_p(None)
3✔
1297
    status = _sixel.sixel_encoder_new(byref(encoder), allocator)
3✔
1298
    if SIXEL_FAILED(status):
3✔
UNCOV
1299
        message = sixel_helper_format_error(status)
×
UNCOV
1300
        raise RuntimeError(message)
×
1301
    return encoder
3✔
1302

1303

1304
# increase reference count of encoder object (thread-unsafe)
1305
def sixel_encoder_ref(encoder):
3✔
1306
    _sixel.sixel_encoder_ref.restype = None
3✔
1307
    _sixel.sixel_encoder_ref.argtypes = [c_void_p]
3✔
1308
    _sixel.sixel_encoder_ref(encoder)
3✔
1309

1310

1311
# decrease reference count of encoder object (thread-unsafe)
1312
def sixel_encoder_unref(encoder):
3✔
1313
    _sixel.sixel_encoder_unref.restype = None
3✔
1314
    _sixel.sixel_encoder_unref.argtypes = [c_void_p]
3✔
1315
    _sixel.sixel_encoder_unref(encoder)
3✔
1316

1317

1318
# set an option flag to encoder object
1319
def sixel_encoder_setopt(encoder, flag, arg=None):
3✔
1320
    _sixel.sixel_encoder_setopt.restype = c_int
3✔
1321
    _sixel.sixel_encoder_setopt.argtypes = [c_void_p, c_int, c_char_p]
3✔
1322
    # Normalize flag for validation while keeping the numeric code used by the
1323
    # C API. Python callers may pass either the character constant ("p") or an
1324
    # integer value. We want to keep the original character for comparison so
1325
    # option-specific validation continues to work even after converting to the
1326
    # numeric code for ctypes.
1327
    if isinstance(flag, int):
3✔
1328
        flag_code = flag
3✔
1329
        flag_char = chr(flag)
3✔
1330
    else:
1331
        flag_char = str(flag)
3✔
1332
        if len(flag_char) != 1:
3✔
1333
            raise RuntimeError(
3✔
1334
                "invalid option flag: expected a single-character flag"
1335
            )
1336
        flag_code = ord(flag_char)
3✔
1337

1338
    if arg:
3✔
1339
        arg = str(arg).encode('utf-8')
3✔
1340
    status = _sixel.sixel_encoder_setopt(encoder, flag_code, arg)
3✔
1341
    if SIXEL_FAILED(status):
3✔
1342
        message = sixel_helper_format_error(status)
3✔
1343
        raise RuntimeError(message)
3✔
1344

1345

1346
# load source data from specified file and encode it to SIXEL format
1347
def sixel_encoder_encode(encoder, filename):
3✔
1348
    import os
3✔
1349
    encoding = _resolve_locale_encoding(default="ascii")
3✔
1350

1351
    # Reject None before touching the codec path because the C API expects a
1352
    # real string pointer. This keeps the exception class deterministic and
1353
    # mirrors the explicit None guard used by sixel_loader_load_file().
1354
    if filename is None:
3✔
1355
        raise TypeError("filename must be str or bytes, not None")
3✔
1356
    if isinstance(filename, memoryview):
3✔
1357
        raise TypeError("filename must be str or bytes, not memoryview")
3✔
1358

1359
    # Proactively validate the input path on the Python side so callers get a
1360
    # deterministic exception even if a platform-specific libc or loader fails
1361
    # to surface a failure.  This mirrors the C-side validation while keeping
1362
    # the behaviour consistent across wheel and in-tree builds.
1363
    if isinstance(filename, bytes):
3✔
1364
        encoded_filename = filename
3✔
1365
        stdin_token = b"-"
3✔
1366
    else:
1367
        encoded_filename = str(filename).encode(encoding)
3✔
1368
        stdin_token = b"-"
3✔
1369

1370
    if encoded_filename != stdin_token:
3✔
1371
        if not os.path.exists(filename):
3✔
1372
            raise RuntimeError(f"input path does not exist: {filename}")
3✔
1373
        if os.path.isdir(filename):
3✔
1374
            raise RuntimeError(f"input path is a directory: {filename}")
3✔
1375

1376
    _sixel.sixel_encoder_encode.restype = c_int
3✔
1377
    _sixel.sixel_encoder_encode.argtypes = [c_void_p, c_char_p]
3✔
1378
    status = _sixel.sixel_encoder_encode(encoder, encoded_filename)
3✔
1379
    if SIXEL_FAILED(status):
3✔
1380
        message = sixel_helper_format_error(status)
3✔
1381
        raise RuntimeError(message)
3✔
1382

1383

1384
# encode specified pixel data to SIXEL format
1385
def sixel_encoder_encode_bytes(encoder, buf, width, height, pixelformat, palette):
3✔
1386

1387
    # Keep buffer acceptance strict and deterministic.  Relying on ctypes
1388
    # coercion can silently accept unsupported objects (for example str) and
1389
    # pass unrelated memory to the C API.
1390
    if buf is None:
3✔
1391
        raise TypeError("buf must be bytes or bytearray, not None")
3✔
1392
    if isinstance(buf, str):
3✔
1393
        raise TypeError("buf must be bytes or bytearray, not str")
3✔
1394
    if isinstance(buf, memoryview):
3✔
1395
        raise TypeError("buf must be bytes or bytearray, not memoryview")
3✔
1396
    if not isinstance(buf, (bytes, bytearray)):
3✔
UNCOV
1397
        raise TypeError(
×
1398
            "buf must be bytes or bytearray, not "
1399
            + type(buf).__name__
1400
        )
1401

1402
    depth = sixel_helper_compute_depth(pixelformat)
3✔
1403

1404
    if depth <= 0:
3✔
1405
        raise ValueError("invalid pixelformat value : %d" % pixelformat)
3✔
1406

1407
    if len(buf) < width * height * depth:
3✔
1408
        raise ValueError("buf.len is too short : %d < %d * %d * %d" % (len(buf), width, height, depth))
3✔
1409

1410
    if palette:
3✔
1411
        cpalettelen = len(palette)
3✔
1412
        cpalette = (c_byte * cpalettelen)(*palette)
3✔
1413
    else:
1414
        cpalettelen = 0
3✔
1415
        cpalette = None
3✔
1416

1417
    _sixel.sixel_encoder_encode_bytes.restype = c_int
3✔
1418
    _sixel.sixel_encoder_encode_bytes.argtypes = [c_void_p, c_void_p, c_int, c_int, c_int, c_void_p, c_int]
3✔
1419

1420
    status = _sixel.sixel_encoder_encode_bytes(encoder, buf, width, height, pixelformat, cpalette, cpalettelen)
3✔
1421
    if SIXEL_FAILED(status):
3✔
UNCOV
1422
        message = sixel_helper_format_error(status)
×
UNCOV
1423
        raise RuntimeError(message)
×
1424

1425

1426
# create decoder object
1427
def sixel_decoder_new(allocator=c_void_p(None)):
3✔
1428
    _sixel.sixel_decoder_new.restype = c_int
3✔
1429
    _sixel.sixel_decoder_new.argtypes = [POINTER(c_void_p), c_void_p]
3✔
1430
    decoder = c_void_p(None)
3✔
1431
    status = _sixel.sixel_decoder_new(byref(decoder), c_void_p(None))
3✔
1432
    if SIXEL_FAILED(status):
3✔
UNCOV
1433
        message = sixel_helper_format_error(status)
×
UNCOV
1434
        raise RuntimeError(message)
×
1435
    return decoder
3✔
1436

1437

1438
# increase reference count of decoder object (thread-unsafe)
1439
def sixel_decoder_ref(decoder):
3✔
1440
    _sixel.sixel_decoder_ref.restype = None
3✔
1441
    _sixel.sixel_decoder_ref.argtypes = [c_void_p]
3✔
1442
    _sixel.sixel_decoder_ref(decoder)
3✔
1443

1444

1445
# decrease reference count of decoder object (thread-unsafe)
1446
def sixel_decoder_unref(decoder):
3✔
1447
    _sixel.sixel_decoder_unref.restype = None
3✔
1448
    _sixel.sixel_decoder_unref.argtypes = [c_void_p]
3✔
1449
    _sixel.sixel_decoder_unref(decoder)
3✔
1450

1451

1452
# set an option flag to decoder object
1453
def sixel_decoder_setopt(decoder, flag, arg=None):
3✔
1454
    _sixel.sixel_decoder_setopt.restype = c_int
3✔
1455
    _sixel.sixel_decoder_setopt.argtypes = [c_void_p, c_int, c_char_p]
3✔
1456
    flag = ord(flag)
3✔
1457
    if arg:
3✔
1458
        arg = str(arg).encode('utf-8')
3✔
1459
    status = _sixel.sixel_decoder_setopt(decoder, flag, arg)
3✔
1460
    if SIXEL_FAILED(status):
3✔
1461
        message = sixel_helper_format_error(status)
3✔
1462
        raise RuntimeError(message)
3✔
1463

1464

1465
# load source data from stdin or the file
1466
def sixel_decoder_decode(decoder, infile=None):
3✔
1467
    _sixel.sixel_decoder_decode.restype = c_int
3✔
1468
    _sixel.sixel_decoder_decode.argtypes = [c_void_p]
3✔
1469
    if infile:
3✔
1470
        sixel_decoder_setopt(decoder, SIXEL_OPTFLAG_INPUT, infile)
3✔
1471
    status = _sixel.sixel_decoder_decode(decoder)
3✔
1472
    if SIXEL_FAILED(status):
3✔
UNCOV
1473
        message = sixel_helper_format_error(status)
×
UNCOV
1474
        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