adding new files

This commit is contained in:
Richard Geldreich
2026-01-19 01:59:35 -05:00
parent 9d87991078
commit ea6778b2b5
72 changed files with 31686 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
recursive-include basisu_py *.py *.so *.wasm
include README.md

View File

@@ -0,0 +1,5 @@
This is the Python support directory for the Basis Universal KTX2 compressor
and transcoder modules.
License: Apache 2.0

View File

@@ -0,0 +1,35 @@
"""
basisu_py
=========
Python bindings for the Basis Universal encoder and transcoder, with
automatic fallback between native C++ extensions and WASM modules.
Main entry points:
- Transcoder : basisu_py.transcoder.Transcoder
- Encoder : basisu_py.codec.Encoder
- constants : basisu_py.constants
"""
from .codec import Encoder
from .transcoder import Transcoder, KTX2Handle
from .constants import (
BasisTexFormat,
BasisQuality,
BasisEffort,
BasisFlags,
TranscoderTextureFormat,
TranscodeDecodeFlags,
)
# What the package publicly exposes
__all__ = [
"Encoder",
"Transcoder",
"KTX2Handle",
"BasisTexFormat",
"BasisQuality",
"BasisEffort",
"BasisFlags",
"TranscoderTextureFormat",
"TranscodeDecodeFlags",
]

Binary file not shown.

Binary file not shown.

222
python/basisu_py/codec.py Normal file
View File

@@ -0,0 +1,222 @@
# basisu_py/codec.py
import importlib
import numpy as np
from PIL import Image
import ctypes
from .constants import BasisTexFormat, BasisQuality, BasisEffort, BasisFlags
from pathlib import Path
class EncoderBackend:
NATIVE = "native"
WASM = "wasm"
AUTO = "auto"
class Encoder:
def __init__(self, backend=EncoderBackend.AUTO):
self.backend = backend
self._native = None
self._wasm = None
self.backend_name = None
# ------------------------------------------------------------------
# Try native first (AUTO or NATIVE modes)
# ------------------------------------------------------------------
if backend in (EncoderBackend.AUTO, EncoderBackend.NATIVE):
try:
import basisu_py.basisu_python as native_encoder
native_encoder.init()
self._native = native_encoder
self._wasm = None
self.backend_name = "NATIVE"
print("[Encoder] Using native backend")
return
except Exception as e:
if backend == EncoderBackend.NATIVE:
raise RuntimeError(
f"[Encoder] Native backend requested but unavailable: {e}"
)
print("[Encoder] Native unavailable; falling back to WASM:", e)
# ------------------------------------------------------------------
# Fallback to WASM (AUTO or explicitly WASM)
# ------------------------------------------------------------------
try:
from basisu_py.wasm.wasm_encoder import BasisuWasmEncoder
except Exception as e:
raise RuntimeError(
f"[Encoder] WASM backend cannot be imported: {e}\n"
"Make sure wasmtime is installed and basisu_py/wasm/*.wasm exist."
)
wasm_path = Path(__file__).parent / "wasm" / "basisu_module_st.wasm"
self._wasm = BasisuWasmEncoder(str(wasm_path))
self._wasm.load()
self._native = None
self.backend_name = "WASM"
print("[Encoder] Using WASM backend")
# ------------------------------------------------------
# Public API
# ------------------------------------------------------
def compress(self,
image,
format=-1,
quality=BasisQuality.MAX,
effort=BasisEffort.DEFAULT,
flags=BasisFlags.KTX2_OUTPUT | BasisFlags.SRGB | BasisFlags.THREADED | BasisFlags.XUASTC_LDR_FULL_ZSTD):
rgba_bytes, w, h, is_hdr = self._convert_input_to_rgba_bytes(image)
# Auto-select format if user passed -1
if format == -1:
if is_hdr:
format = BasisTexFormat.cUASTC_HDR_6x6
else:
format = BasisTexFormat.cXUASTC_LDR_6x6
if self._native:
return self._compress_native(rgba_bytes, w, h, format, quality, effort, flags, is_hdr)
else:
return self._compress_wasm(rgba_bytes, w, h, format, quality, effort, flags, is_hdr)
def compress_float32(self, arr, **kwargs):
if not isinstance(arr, np.ndarray) or arr.dtype != np.float32:
raise ValueError("compress_float32 requires float32 NumPy HxWx4 array")
return self.compress(arr, **kwargs)
# ------------------------------------------------------
# Native backend
# ------------------------------------------------------
def _compress_native(self, bytes_data, w, h, fmt, quality, effort, flags, is_hdr=False):
enc = self._native
params = enc.new_params()
try:
buf_ptr = enc.alloc(len(bytes_data))
# Write raw bytes (uint8 or float32)
ctypes.memmove(buf_ptr, bytes_data, len(bytes_data))
if is_hdr:
ok = enc.set_image_float_rgba(params, 0, buf_ptr, w, h, w * 16) # 4 floats = 16 bytes per pixel
else:
ok = enc.set_image_rgba32(params, 0, buf_ptr, w, h, w * 4)
if not ok:
raise RuntimeError("Native encoder: set_image failed (HDR or LDR)")
ok = enc.compress(params, fmt, quality, effort, flags, 0.0)
if not ok:
raise RuntimeError("Native encoder: compress() failed")
size = enc.get_comp_data_size(params)
ofs = enc.get_comp_data_ofs(params)
blob = enc.read_memory(ofs, size)
return blob
finally:
enc.delete_params(params)
if buf_ptr:
enc.free(buf_ptr)
# ------------------------------------------------------
# WASM backend
# ------------------------------------------------------
def _compress_wasm(self, bytes_data, w, h, fmt, quality, effort, flags, is_hdr=False):
enc = self._wasm
params = enc.new_params()
try:
buf_ptr = enc.alloc(len(bytes_data))
enc.write_bytes(buf_ptr, bytes_data)
if is_hdr:
ok = enc.set_image_float_rgba(params, 0, buf_ptr, w, h, w * 16)
else:
ok = enc.set_image_rgba32(params, 0, buf_ptr, w, h, w * 4)
if not ok:
raise RuntimeError("WASM encoder: set_image failed (HDR or LDR)")
ok = enc.compress(params, fmt, quality, effort, flags, 0.0)
if not ok:
raise RuntimeError("WASM encoder: compress() failed")
size = enc.get_comp_data_size(params)
ofs = enc.get_comp_data_ofs(params)
blob = enc.read_bytes(ofs, size)
return blob
finally:
enc.delete_params(params)
if buf_ptr:
enc.free(buf_ptr)
# ------------------------------------------------------
# Image conversion
# ------------------------------------------------------
def _convert_input_to_rgba_bytes(self, image):
"""
Accept:
- Pillow Image (LDR) -> returns uint8 bytes
- NumPy uint8 LDR -> returns uint8 bytes
- NumPy float32 HDR -> returns float32 bytes
Returns (bytes, width, height, is_hdr)
"""
# Pillow image -> LDR
if isinstance(image, Image.Image):
image = image.convert("RGBA")
arr = np.array(image, dtype=np.uint8)
h, w = arr.shape[:2]
return arr.tobytes(), w, h, False
# NumPy array
elif isinstance(image, np.ndarray):
# HDR float32 image
if image.dtype == np.float32:
if image.ndim != 3 or image.shape[2] not in (3,4):
raise ValueError("HDR NumPy image must be HxWx3 or HxWx4 float32")
h, w, c = image.shape
# Expand RGB -> RGBA if needed
if c == 3:
alpha = np.ones((h, w, 1), dtype=np.float32)
arr = np.concatenate([image, alpha], axis=2)
else:
arr = image
return arr.tobytes(), w, h, True
# LDR uint8 image
if image.dtype == np.uint8:
if image.ndim != 3 or image.shape[2] not in (3,4):
raise ValueError("LDR NumPy image must be HxWx3 or HxWx4 uint8")
h, w, c = image.shape
if c == 3:
alpha = np.full((h, w, 1), 255, dtype=np.uint8)
arr = np.concatenate([image, alpha], axis=2)
else:
arr = image
return arr.tobytes(), w, h, False
raise ValueError("NumPy image must be uint8 (LDR) or float32 (HDR)")
else:
raise TypeError("compress() expects Pillow Image or NumPy array")

View File

@@ -0,0 +1,183 @@
# basisu_constants.py
# ============================================================
# .KTX2/.basis file types
# basist::basis_tex_format
# ============================================================
class BasisTexFormat:
# Original LDR formats
cETC1S = 0
cUASTC_LDR_4x4 = 1
# HDR
cUASTC_HDR_4x4 = 2
cASTC_HDR_6x6 = 3
cUASTC_HDR_6x6 = 4
# XUASTC supercompressed LDR formats
cXUASTC_LDR_4x4 = 5
cXUASTC_LDR_5x4 = 6
cXUASTC_LDR_5x5 = 7
cXUASTC_LDR_6x5 = 8
cXUASTC_LDR_6x6 = 9
cXUASTC_LDR_8x5 = 10
cXUASTC_LDR_8x6 = 11
cXUASTC_LDR_10x5 = 12
cXUASTC_LDR_10x6 = 13
cXUASTC_LDR_8x8 = 14
cXUASTC_LDR_10x8 = 15
cXUASTC_LDR_10x10= 16
cXUASTC_LDR_12x10= 17
cXUASTC_LDR_12x12= 18
# Standard ASTC LDR
cASTC_LDR_4x4 = 19
cASTC_LDR_5x4 = 20
cASTC_LDR_5x5 = 21
cASTC_LDR_6x5 = 22
cASTC_LDR_6x6 = 23
cASTC_LDR_8x5 = 24
cASTC_LDR_8x6 = 25
cASTC_LDR_10x5 = 26
cASTC_LDR_10x6 = 27
cASTC_LDR_8x8 = 28
cASTC_LDR_10x8 = 29
cASTC_LDR_10x10= 30
cASTC_LDR_12x10= 31
cASTC_LDR_12x12= 32
# ============================================================
# Unified quality level: 1-100 (higher=better quality, 100 disables some codec options)
# ============================================================
class BasisQuality:
MIN = 1
MAX = 100
# ============================================================
# Unified effort level: 0-10 (0=fastest, 10=very slow, higher=slower but higher potential quality/more features utilized)
# ============================================================
class BasisEffort:
MIN = 0
MAX = 10
SUPER_FAST = 0
FAST = 2
NORMAL = 5
DEFAULT = 2
SLOW = 8
VERY_SLOW = 10
# ============================================================
# C-style API flags
# ============================================================
class BasisFlags:
NONE = 0
USE_OPENCL = 1 << 8
THREADED = 1 << 9
DEBUG_OUTPUT = 1 << 10
KTX2_OUTPUT = 1 << 11
KTX2_UASTC_ZSTD = 1 << 12
SRGB = 1 << 13
GEN_MIPS_CLAMP = 1 << 14
GEN_MIPS_WRAP = 1 << 15
Y_FLIP = 1 << 16
PRINT_STATS = 1 << 18
PRINT_STATUS = 1 << 19
DEBUG_IMAGES = 1 << 20
REC2020 = 1 << 21
VALIDATE_OUTPUT = 1 << 22
XUASTC_LDR_FULL_ARITH = 0
XUASTC_LDR_HYBRID = 1 << 23
XUASTC_LDR_FULL_ZSTD = 2 << 23
XUASTC_LDR_SYNTAX_SHIFT = 23
XUASTC_LDR_SYNTAX_MASK = 3
TEXTURE_TYPE_2D = 0 << 25
TEXTURE_TYPE_2D_ARRAY = 1 << 25
TEXTURE_TYPE_CUBEMAP_ARRAY = 2 << 25
TEXTURE_TYPE_VIDEO_FRAMES = 3 << 25
TEXTURE_TYPE_SHIFT = 25
TEXTURE_TYPE_MASK = 3
VERBOSE = PRINT_STATS | PRINT_STATUS
MIPMAP_CLAMP = GEN_MIPS_CLAMP
MIPMAP_WRAP = GEN_MIPS_WRAP
# ============================================================
# Transcoder Texture Formats (GPU block formats)
# basist::transcoder_texture_format
# ============================================================
class TranscoderTextureFormat:
TF_ETC1_RGB = 0
TF_ETC2_RGBA = 1
TF_BC1_RGB = 2
TF_BC3_RGBA = 3
TF_BC4_R = 4
TF_BC5_RG = 5
TF_BC7_RGBA = 6
TF_PVRTC1_4_RGB = 8
TF_PVRTC1_4_RGBA = 9
TF_ASTC_LDR_4X4_RGBA = 10
TF_ATC_RGB = 11
TF_ATC_RGBA = 12
# Uncompressed
TF_RGBA32 = 13
TF_RGB565 = 14
TF_BGR565 = 15
TF_RGBA4444 = 16
TF_FXT1_RGB = 17
TF_PVRTC2_4_RGB = 18
TF_PVRTC2_4_RGBA = 19
TF_ETC2_EAC_R11 = 20
TF_ETC2_EAC_RG11 = 21
TF_BC6H = 22
TF_ASTC_HDR_4X4_RGBA = 23
TF_RGB_HALF = 24
TF_RGBA_HALF = 25
TF_RGB_9E5 = 26
TF_ASTC_HDR_6X6_RGBA = 27
TF_ASTC_LDR_5X4_RGBA = 28
TF_ASTC_LDR_5X5_RGBA = 29
TF_ASTC_LDR_6X5_RGBA = 30
TF_ASTC_LDR_6X6_RGBA = 31
TF_ASTC_LDR_8X5_RGBA = 32
TF_ASTC_LDR_8X6_RGBA = 33
TF_ASTC_LDR_10X5_RGBA = 34
TF_ASTC_LDR_10X6_RGBA = 35
TF_ASTC_LDR_8X8_RGBA = 36
TF_ASTC_LDR_10X8_RGBA = 37
TF_ASTC_LDR_10X10_RGBA= 38
TF_ASTC_LDR_12X10_RGBA= 39
TF_ASTC_LDR_12X12_RGBA= 40
TOTAL = 41
# ============================================================
# Transcoder Decode Flags
# ============================================================
class TranscodeDecodeFlags:
PVRTC_DECODE_TO_NEXT_POW2 = 2
TRANSCODE_ALPHA_TO_OPAQUE = 4
BC1_FORBID_THREE_COLOR_BLOCKS = 8
OUTPUT_HAS_ALPHA_INDICES = 16
HIGH_QUALITY = 32
NO_ETC1S_CHROMA_FILTERING = 64
NO_DEBLOCK_FILTERING = 128
STRONGER_DEBLOCK_FILTERING = 256
FORCE_DEBLOCK_FILTERING = 512
XUASTC_LDR_DISABLE_FAST_BC7_TRANSCODING = 1024

View File

@@ -0,0 +1,735 @@
# basisu_py/transcoder.py
import numpy as np
from dataclasses import dataclass
from pathlib import Path
from basisu_py.constants import (
TranscoderTextureFormat,
)
import importlib
import ctypes
# ---------------------------------------------------------------------------
# Enum to select backend
# ---------------------------------------------------------------------------
class TranscoderBackend:
NATIVE = "native"
WASM = "wasm"
AUTO = "auto"
# ---------------------------------------------------------------------------
# Wrapper class storing pointer+handle
# ---------------------------------------------------------------------------
@dataclass
class KTX2Handle:
ptr: int
handle: int
# ---------------------------------------------------------------------------
# Main Transcoder class
# ---------------------------------------------------------------------------
class Transcoder:
def __init__(self, backend=TranscoderBackend.AUTO):
self._native = None
self._wasm = None
self.backend_name = None
self.backend = None
use_native = False
# ------------------------------------------------------------------
# Try native backend first if AUTO or NATIVE
# ------------------------------------------------------------------
if backend in (TranscoderBackend.AUTO, TranscoderBackend.NATIVE):
try:
native_mod = importlib.import_module("basisu_py.basisu_transcoder_python")
native_mod.init()
self._native = native_mod
self.backend = native_mod
self.backend_name = "NATIVE"
use_native = True
print("[Transcoder] Using native backend")
except Exception as e:
if backend == TranscoderBackend.NATIVE:
# Caller explicitly requested native - fail hard
raise RuntimeError(f"Native transcoder backend failed: {e}")
print("[Transcoder] Native backend unavailable, reason:", e)
self._native = None
# ------------------------------------------------------------------
# Fallback to WASM if native is not being used
# ------------------------------------------------------------------
if not use_native:
try:
from basisu_py.wasm.wasm_transcoder import BasisuWasmTranscoder
except Exception as e:
raise RuntimeError(
f"WASM backend cannot be imported: {e}\n"
"Ensure that:\n"
" - 'wasmtime' is installed\n"
" - basisu_py/wasm/*.wasm files are present in the install\n"
)
wasm_path = Path(__file__).parent / "wasm" / "basisu_transcoder_module_st.wasm"
self._wasm = BasisuWasmTranscoder(str(wasm_path))
self._wasm.load()
self.backend = self._wasm
self.backend_name = "WASM"
print("[Transcoder] Using WASM backend")
# Finally, bind the unified API to whichever backend we chose
self._bind_backend(self.backend)
# -----------------------------------------------------------------------
# Unified backend binding (native or wasm)
# -----------------------------------------------------------------------
def _bind_backend(self, b):
self.backend = b
# ------------------ memory operations ------------------
memory_mapping = [
("_alloc", "alloc"),
("_free", "free"),
("_write", "write_memory"),
("_read", "read_memory"),
]
# ------------------ KTX2 core ------------------
basis_mapping = [
# basis_tex_format helpers
("basis_tex_format_is_xuastc_ldr", "basis_tex_format_is_xuastc_ldr"),
("basis_tex_format_is_astc_ldr", "basis_tex_format_is_astc_ldr"),
("basis_tex_format_get_block_width", "basis_tex_format_get_block_width"),
("basis_tex_format_get_block_height", "basis_tex_format_get_block_height"),
("basis_tex_format_is_hdr", "basis_tex_format_is_hdr"),
("basis_tex_format_is_ldr", "basis_tex_format_is_ldr"),
# transcoder_texture_format helpers
("basis_get_bytes_per_block_or_pixel", "basis_get_bytes_per_block_or_pixel"),
("basis_transcoder_format_has_alpha", "basis_transcoder_format_has_alpha"),
("basis_transcoder_format_is_hdr", "basis_transcoder_format_is_hdr"),
("basis_transcoder_format_is_ldr", "basis_transcoder_format_is_ldr"),
("basis_transcoder_texture_format_is_astc", "basis_transcoder_texture_format_is_astc"),
("basis_transcoder_format_is_uncompressed", "basis_transcoder_format_is_uncompressed"),
("basis_get_uncompressed_bytes_per_pixel", "basis_get_uncompressed_bytes_per_pixel"),
("basis_get_block_width", "basis_get_block_width"),
("basis_get_block_height", "basis_get_block_height"),
("basis_get_transcoder_texture_format_from_basis_tex_format","basis_get_transcoder_texture_format_from_basis_tex_format"),
("basis_is_format_supported", "basis_is_format_supported"),
("basis_compute_transcoded_image_size_in_bytes","basis_compute_transcoded_image_size_in_bytes"),
]
ktx2_mapping = [
("ktx2_open", "ktx2_open"),
("ktx2_close", "ktx2_close"),
("ktx2_get_width", "ktx2_get_width"),
("ktx2_get_height", "ktx2_get_height"),
("ktx2_get_levels", "ktx2_get_levels"),
("ktx2_get_faces", "ktx2_get_faces"),
("ktx2_get_layers", "ktx2_get_layers"),
("ktx2_get_basis_tex_format", "ktx2_get_basis_tex_format"),
("ktx2_get_block_width", "ktx2_get_block_width"),
("ktx2_get_block_height", "ktx2_get_block_height"),
("ktx2_has_alpha", "ktx2_has_alpha"),
# flags
("ktx2_is_hdr", "ktx2_is_hdr"),
("ktx2_is_hdr_4x4", "ktx2_is_hdr_4x4"),
("ktx2_is_hdr_6x6", "ktx2_is_hdr_6x6"),
("ktx2_is_ldr", "ktx2_is_ldr"),
("ktx2_is_srgb", "ktx2_is_srgb"),
("ktx2_is_etc1s", "ktx2_is_etc1s"),
("ktx2_is_uastc_ldr_4x4", "ktx2_is_uastc_ldr_4x4"),
("ktx2_is_xuastc_ldr", "ktx2_is_xuastc_ldr"),
("ktx2_is_astc_ldr", "ktx2_is_astc_ldr"),
("ktx2_is_video", "ktx2_is_video"),
("ktx2_get_ldr_hdr_upconversion_nit_multiplier", "ktx2_get_ldr_hdr_upconversion_nit_multiplier"),
# DFD access
("ktx2_get_dfd_flags", "ktx2_get_dfd_flags"),
("ktx2_get_dfd_total_samples", "ktx2_get_dfd_total_samples"),
("ktx2_get_dfd_channel_id0", "ktx2_get_dfd_channel_id0"),
("ktx2_get_dfd_channel_id1", "ktx2_get_dfd_channel_id1"),
("ktx2_get_dfd_color_model", "ktx2_get_dfd_color_model"),
("ktx2_get_dfd_color_primaries", "ktx2_get_dfd_color_primaries"),
("ktx2_get_dfd_transfer_func", "ktx2_get_dfd_transfer_func"),
# per-level info
("ktx2_get_level_orig_width", "ktx2_get_level_orig_width"),
("ktx2_get_level_orig_height", "ktx2_get_level_orig_height"),
("ktx2_get_level_actual_width", "ktx2_get_level_actual_width"),
("ktx2_get_level_actual_height", "ktx2_get_level_actual_height"),
("ktx2_get_level_num_blocks_x", "ktx2_get_level_num_blocks_x"),
("ktx2_get_level_num_blocks_y", "ktx2_get_level_num_blocks_y"),
("ktx2_get_level_total_blocks", "ktx2_get_level_total_blocks"),
("ktx2_get_level_alpha_flag", "ktx2_get_level_alpha_flag"),
("ktx2_get_level_iframe_flag", "ktx2_get_level_iframe_flag"),
# transcoding
("ktx2_start_transcoding", "ktx2_start_transcoding"),
("ktx2_transcode_image_level", "ktx2_transcode_image_level"),
# version
("get_version_fn", "get_version"),
]
# Apply all mappings
for public_name, backend_name in (memory_mapping + ktx2_mapping + basis_mapping):
setattr(self, public_name, getattr(b, backend_name))
# -----------------------------------------------------------------------
# Public version query
# -----------------------------------------------------------------------
def get_version(self):
return self.get_version_fn()
# -----------------------------------------------------------------------
# Enable library debug printing to stdout (also set BASISU_FORCE_DEVEL_MESSAGES to 1 in transcoder/basisu.h)
# -----------------------------------------------------------------------
def enable_debug_printf(self, flag: bool = True):
return self.backend.enable_debug_printf(flag)
# -----------------------------------------------------------------------
# KTX2 Handle API: open/close + all queries
# -----------------------------------------------------------------------
def open(self, ktx2_bytes: bytes) -> KTX2Handle:
ptr = self._alloc(len(ktx2_bytes))
self._write(ptr, ktx2_bytes)
handle = self.ktx2_open(ptr, len(ktx2_bytes))
return KTX2Handle(ptr, handle)
def close(self, ktx2_handle: KTX2Handle):
self.ktx2_close(ktx2_handle.handle)
self._free(ktx2_handle.ptr)
# ---- Basic queries ----
def get_width(self, ktx2_handle: KTX2Handle):
return self.ktx2_get_width(ktx2_handle.handle)
def get_height(self, ktx2_handle: KTX2Handle):
return self.ktx2_get_height(ktx2_handle.handle)
def get_levels(self, ktx2_handle: KTX2Handle):
return self.ktx2_get_levels(ktx2_handle.handle)
def get_faces(self, ktx2_handle: KTX2Handle):
return self.ktx2_get_faces(ktx2_handle.handle)
def get_layers(self, ktx2_handle: KTX2Handle):
return self.ktx2_get_layers(ktx2_handle.handle)
def get_basis_tex_format(self, ktx2_handle: KTX2Handle):
return self.ktx2_get_basis_tex_format(ktx2_handle.handle)
def has_alpha(self, ktx2_handle: KTX2Handle) -> bool:
"""
Return true if the KTX2 container has alpha.
"""
return bool(self.ktx2_has_alpha(ktx2_handle.handle))
# ---- Format flags ----
def is_hdr(self, ktx2_handle): return bool(self.ktx2_is_hdr(ktx2_handle.handle))
def is_hdr_4x4(self, ktx2_handle): return bool(self.ktx2_is_hdr_4x4(ktx2_handle.handle))
def is_hdr_6x6(self, ktx2_handle): return bool(self.ktx2_is_hdr_6x6(ktx2_handle.handle))
def is_ldr(self, ktx2_handle): return bool(self.ktx2_is_ldr(ktx2_handle.handle))
def is_srgb(self, ktx2_handle): return bool(self.ktx2_is_srgb(ktx2_handle.handle))
def is_video(self, ktx2_handle): return bool(self.ktx2_is_video(ktx2_handle.handle))
def get_ldr_hdr_upconversion_nit_multiplier(self, ktx2_handle): return self.ktx2_get_ldr_hdr_upconversion_nit_multiplier(ktx2_handle.handle)
def is_etc1s(self, ktx2_handle): return bool(self.ktx2_is_etc1s(ktx2_handle.handle))
def is_uastc_ldr_4x4(self, ktx2_handle): return bool(self.ktx2_is_uastc_ldr_4x4(ktx2_handle.handle))
def is_xuastc_ldr(self, ktx2_handle): return bool(self.ktx2_is_xuastc_ldr(ktx2_handle.handle))
def is_astc_ldr(self, ktx2_handle): return bool(self.ktx2_is_astc_ldr(ktx2_handle.handle))
# ---- DFD access
def get_dfd_flags(self, ktx2_handle): return self.ktx2_get_dfd_flags(ktx2_handle.handle)
def get_dfd_total_samples(self, ktx2_handle): return self.ktx2_get_dfd_total_samples(ktx2_handle.handle)
def get_dfd_color_model(self, ktx2_handle): return self.ktx2_get_dfd_color_model(ktx2_handle.handle)
def get_dfd_color_primaries(self, ktx2_handle): return self.ktx2_get_dfd_color_primaries(ktx2_handle.handle)
def get_dfd_transfer_func(self, ktx2_handle): return self.ktx2_get_dfd_transfer_func(ktx2_handle.handle)
def get_dfd_channel_id0(self, ktx2_handle): return self.ktx2_get_dfd_channel_id0(ktx2_handle.handle)
def get_dfd_channel_id1(self, ktx2_handle): return self.ktx2_get_dfd_channel_id1(ktx2_handle.handle)
# ---- Block dimensions ----
def get_block_width(self, ktx2_handle): return self.ktx2_get_block_width(ktx2_handle.handle)
def get_block_height(self, ktx2_handle): return self.ktx2_get_block_height(ktx2_handle.handle)
# -----------------------------------------------------------------------
# Explicit: start transcoding on an already-open KTX2 file
# -----------------------------------------------------------------------
def start_transcoding(self, ktx2_handle: KTX2Handle):
"""
Must be called before per-level iframe flags become valid.
"""
ok = self.ktx2_start_transcoding(ktx2_handle.handle)
if not ok:
raise RuntimeError("start_transcoding() failed")
return True
# ---- Level info ----
def get_level_orig_width(self, ktx2_handle, level, layer=0, face=0):
return self.ktx2_get_level_orig_width(ktx2_handle.handle, level, layer, face)
def get_level_orig_height(self, ktx2_handle, level, layer=0, face=0):
return self.ktx2_get_level_orig_height(ktx2_handle.handle, level, layer, face)
def get_level_actual_width(self, ktx2_handle, level, layer=0, face=0):
return self.ktx2_get_level_actual_width(ktx2_handle.handle, level, layer, face)
def get_level_actual_height(self, ktx2_handle, level, layer=0, face=0):
return self.ktx2_get_level_actual_height(ktx2_handle.handle, level, layer, face)
def get_level_num_blocks_x(self, ktx2_handle, level, layer=0, face=0):
return self.ktx2_get_level_num_blocks_x(ktx2_handle.handle, level, layer, face)
def get_level_num_blocks_y(self, ktx2_handle, level, layer=0, face=0):
return self.ktx2_get_level_num_blocks_y(ktx2_handle.handle, level, layer, face)
def get_level_total_blocks(self, ktx2_handle, level, layer=0, face=0):
return self.ktx2_get_level_total_blocks(ktx2_handle.handle, level, layer, face)
def get_level_alpha_flag(self, ktx2_handle, level, layer=0, face=0):
return bool(self.ktx2_get_level_alpha_flag(ktx2_handle.handle, level, layer, face))
def get_level_iframe_flag(self, ktx2_handle, level, layer=0, face=0):
return bool(self.ktx2_get_level_iframe_flag(ktx2_handle.handle, level, layer, face))
# -----------------------------------------------------------------------
# Low-level: Decode RGBA8 from an already-open KTX2 handle
# -----------------------------------------------------------------------
def decode_rgba_handle(self, ktx2_handle: KTX2Handle, level=0, layer=0, face=0):
"""
Low-level fast decode. Requires an already-open KTX2Handle.
Returns HxWx4 uint8 NumPy array.
"""
w = self.ktx2_get_level_orig_width(ktx2_handle.handle, level, layer, face)
h = self.ktx2_get_level_orig_height(ktx2_handle.handle, level, layer, face)
out_size = w * h * 4
out_ptr = self._alloc(out_size)
# MUST start transcoding before ANY decode
ok = self.ktx2_start_transcoding(ktx2_handle.handle)
if not ok:
self._free(out_ptr)
raise RuntimeError("start_transcoding failed")
ok = self.ktx2_transcode_image_level(
ktx2_handle.handle,
level, layer, face,
out_ptr,
out_size,
TranscoderTextureFormat.TF_RGBA32,
0, 0, 0, -1, -1, 0
)
if not ok:
self._free(out_ptr)
raise RuntimeError("transcode_image_level failed")
raw_bytes = self._read(out_ptr, out_size)
self._free(out_ptr)
arr = np.frombuffer(raw_bytes, dtype=np.uint8)
return arr.reshape((h, w, 4))
# -----------------------------------------------------------------------
# High-level: Decode RGBA8 directly from KTX2 file data
# -----------------------------------------------------------------------
def decode_rgba(self, ktx2_bytes: bytes, level=0, layer=0, face=0):
"""
High-level convenience decode. Opens the KTX2 file bytes for you.
"""
ktx2_handle = self.open(ktx2_bytes)
try:
return self.decode_rgba_handle(ktx2_handle, level, layer, face)
finally:
self.close(ktx2_handle)
# -----------------------------------------------------------------------
# Low-level: Decode HDR (RGBA float32) from open KTX2
# -----------------------------------------------------------------------
def decode_rgba_hdr_handle(self, ktx2_handle: KTX2Handle, level=0, layer=0, face=0):
"""
Low-level HDR decode. Returns HxWx4 float32 NumPy array.
"""
w = self.ktx2_get_level_orig_width(ktx2_handle.handle, level, layer, face)
h = self.ktx2_get_level_orig_height(ktx2_handle.handle, level, layer, face)
bytes_per_pixel = 8 # 4 * half-float
out_size = w * h * bytes_per_pixel
out_ptr = self._alloc(out_size)
ok = self.ktx2_start_transcoding(ktx2_handle.handle)
if not ok:
self._free(out_ptr)
raise RuntimeError("start_transcoding failed")
ok = self.ktx2_transcode_image_level(
ktx2_handle.handle,
level, layer, face,
out_ptr,
out_size,
TranscoderTextureFormat.TF_RGBA_HALF,
0, 0, 0, -1, -1, 0
)
if not ok:
self._free(out_ptr)
raise RuntimeError("transcode_image_level failed")
raw_bytes = self._read(out_ptr, out_size)
self._free(out_ptr)
arr = np.frombuffer(raw_bytes, dtype=np.float16).astype(np.float32)
return arr.reshape((h, w, 4))
# -----------------------------------------------------------------------
# High-level: Decode HDR (RGBA float32) from KTX2 file data
# -----------------------------------------------------------------------
def decode_rgba_hdr(self, ktx2_bytes: bytes, level=0, layer=0, face=0):
"""
High-level convenience HDR decode. Opens the KTX2 file bytes for you.
"""
ktx2_handle = self.open(ktx2_bytes)
try:
return self.decode_rgba_hdr_handle(ktx2_handle, level, layer, face)
finally:
self.close(ktx2_handle)
# -----------------------------------------------------------------------
# Low-level: General-purpose transcode using a chosen TranscoderTextureFormat format
# -----------------------------------------------------------------------
def transcode_tfmt_handle(self, ktx2_handle: KTX2Handle, tfmt: int,
level=0, layer=0, face=0, decode_flags=0,
channel0=-1, channel1=-1):
"""
Low-level direct transcoding from an already-open KTX2 handle.
Parameters:
ktx2_handle: KTX2Handle -> already-open KTX2
tfmt: int -> TranscoderTextureFormat to transcode to (for ASTC: block size and LDR/HDR MUST match the KTX2 file, for HDR: must be a HDR texture format)
level/layer/face: int -> which image slice to decode
decode_flags: int -> basist::decode_flags
row_pitch, rows_in_pixels, channel0, channel1 -> advanced options
Returns: bytes (transcoded GPU texture data or uncompressed image)
"""
# Determine actual output size in bytes
ow = self.ktx2_get_level_orig_width(ktx2_handle.handle, level, layer, face)
oh = self.ktx2_get_level_orig_height(ktx2_handle.handle, level, layer, face)
out_size = self.basis_compute_transcoded_image_size_in_bytes(tfmt, ow, oh)
if out_size == 0:
raise RuntimeError("basis_compute_transcoded_image_size_in_bytes returned 0")
# print(f"*** ow={ow}, oh={oh}, out_size={out_size}")
out_ptr = self._alloc(out_size)
# Call transcoder
ok = self.ktx2_start_transcoding(ktx2_handle.handle)
if not ok:
self._free(out_ptr)
raise RuntimeError("start_transcoding failed")
ok = self.ktx2_transcode_image_level(
ktx2_handle.handle,
level, layer, face,
out_ptr,
out_size,
tfmt,
decode_flags,
0,
0,
channel0, channel1,
0 # no per-thread state object
)
if not ok:
self._free(out_ptr)
raise RuntimeError("ktx2_transcode_image_level failed")
# Extract bytes
raw_bytes = self._read(out_ptr, out_size)
self._free(out_ptr)
return raw_bytes
# -----------------------------------------------------------------------
# High-level: General-purpose transcode (opens the KTX2 for you)
# tfmt: the TranscoderTextureFormat to transcode too
# -----------------------------------------------------------------------
def transcode_tfmt(self, ktx2_bytes: bytes, tfmt: int,
level=0, layer=0, face=0, decode_flags=0,
channel0=-1, channel1=-1):
"""
High-level convenience wrapper for transcode_tfmt_handle().
Automatically opens/closes the KTX2 file.
"""
ktx2_handle = self.open(ktx2_bytes)
try:
return self.transcode_tfmt_handle(
ktx2_handle, tfmt,
level=level,
layer=layer,
face=face,
decode_flags=decode_flags,
channel0=channel0,
channel1=channel1
)
finally:
self.close(ktx2_handle)
# -----------------------------------------------------------------------
# Low-level: choose a specific transcoder_texture_format from a family string
# -----------------------------------------------------------------------
def choose_transcoder_format(self, ktx2_handle: KTX2Handle, family: str) -> int:
"""
Given an already-opened KTX2 and a desired family string, choose a concrete
TranscoderTextureFormat enum.
family: one of:
"ASTC", "BC1", "BC3", "BC4", "BC5", "BC6H", "BC7",
"PVRTC1", "PVRTC2",
"ETC1", "ETC2", "ETC2_EAC_R11", "ETC2_EAC_RG11",
"ATC", "FXT1",
"RGBA32", "RGB_HALF", "RGBA_HALF", "RGB_FLOAT", "RGBA_FLOAT",
"RGB_9E5"
Returns:
int: TranscoderTextureFormat value
"""
s = family.strip().upper().replace(" ", "")
hdr_tex = self.is_hdr(ktx2_handle)
has_alpha = self.has_alpha(ktx2_handle)
basis_fmt = self.get_basis_tex_format(ktx2_handle)
tfmt = None
# -------------------------------------------------------------------
# Uncompressed families
# -------------------------------------------------------------------
if s in ("RGBA32", "RGBA8", "UNCOMPRESSED"):
tfmt = TranscoderTextureFormat.TF_RGBA32
elif s in ("RGBHALF", "RGB16F", "RGB_FLOAT", "RGBFLOAT"):
tfmt = TranscoderTextureFormat.TF_RGB_HALF
elif s in ("RGBAHALF", "RGBA16F", "RGBA_FLOAT", "RGBAFLOAT"):
tfmt = TranscoderTextureFormat.TF_RGBA_HALF
elif s in ("RGB9E5", "RGB_9E5"):
tfmt = TranscoderTextureFormat.TF_RGB_9E5
# -------------------------------------------------------------------
# BC families
# -------------------------------------------------------------------
elif s == "BC1":
tfmt = TranscoderTextureFormat.TF_BC1_RGB
elif s == "BC3":
tfmt = TranscoderTextureFormat.TF_BC3_RGBA
elif s == "BC4":
tfmt = TranscoderTextureFormat.TF_BC4_R
elif s == "BC5":
tfmt = TranscoderTextureFormat.TF_BC5_RG
elif s == "BC6H":
tfmt = TranscoderTextureFormat.TF_BC6H
elif s == "BC7":
tfmt = TranscoderTextureFormat.TF_BC7_RGBA
# -------------------------------------------------------------------
# PVRTC families
# -------------------------------------------------------------------
elif s == "PVRTC1":
tfmt = (TranscoderTextureFormat.TF_PVRTC1_4_RGBA
if has_alpha else TranscoderTextureFormat.TF_PVRTC1_4_RGB)
elif s == "PVRTC2":
tfmt = (TranscoderTextureFormat.TF_PVRTC2_4_RGBA
if has_alpha else TranscoderTextureFormat.TF_PVRTC2_4_RGB)
# -------------------------------------------------------------------
# ETC / EAC families
# -------------------------------------------------------------------
elif s == "ETC1":
tfmt = TranscoderTextureFormat.TF_ETC1_RGB
elif s == "ETC2":
tfmt = TranscoderTextureFormat.TF_ETC2_RGBA
elif s in ("ETC2_EAC_R11", "EAC_R11"):
tfmt = TranscoderTextureFormat.TF_ETC2_EAC_R11
elif s in ("ETC2_EAC_RG11", "EAC_RG11"):
tfmt = TranscoderTextureFormat.TF_ETC2_EAC_RG11
# -------------------------------------------------------------------
# ATC / FXT
# -------------------------------------------------------------------
elif s == "ATC":
tfmt = (TranscoderTextureFormat.TF_ATC_RGBA
if has_alpha else TranscoderTextureFormat.TF_ATC_RGB)
elif s == "FXT1":
tfmt = TranscoderTextureFormat.TF_FXT1_RGB
# -------------------------------------------------------------------
# ASTC family
# -------------------------------------------------------------------
elif s == "ASTC":
# Let BasisU decide correct ASTC format (block size + LDR/HDR)
tfmt = self.basis_get_transcoder_texture_format_from_basis_tex_format(basis_fmt)
else:
# Unknown family: choose a safe uncompressed default
if hdr_tex:
tfmt = TranscoderTextureFormat.TF_RGBA_HALF
else:
tfmt = TranscoderTextureFormat.TF_RGBA32
# -------------------------------------------------------------------
# Validate HDR/LDR compatibility (optional but recommended)
# -------------------------------------------------------------------
# Use helpers to ensure we don't do HDR->LDR or LDR->HDR accidentally.
is_tfmt_hdr = self.basis_transcoder_format_is_hdr(tfmt)
if hdr_tex and not is_tfmt_hdr:
raise ValueError(f"Requested {family} (LDR transcoder format) for HDR KTX2.")
if not hdr_tex and is_tfmt_hdr:
raise ValueError(f"Requested {family} (HDR transcoder format) for LDR KTX2.")
return tfmt
# -----------------------------------------------------------------------
# Low-level: General-purpose transcode using a family string
# from an already opened ktx2 file.
# Returns:
# (data_bytes, chosen_tfmt, block_width, block_height)
# -----------------------------------------------------------------------
def transcode_handle(
self,
ktx2_handle: KTX2Handle,
family: str,
level=0,
layer=0,
face=0,
decode_flags=0,
channel0=-1,
channel1=-1
):
"""
Low-level direct transcoding from an already-open KTX2 handle,
using a high-level family string such as:
"BC7", "BC3", "BC1", "ETC1", "ETC2", "ASTC", "PVRTC1",
"RGBA32", "RGB_HALF", "RGBA_HALF", "RGB_9E5", etc.
See choose_transcoder_format().
Returns:
(data_bytes, tfmt, block_width, block_height)
"""
# Decide the exact transcoder format (BC1/BC7/etc.)
tfmt = self.choose_transcoder_format(ktx2_handle, family)
# Get original dims of the requested slice
ow = self.get_level_orig_width(ktx2_handle, level, layer, face)
oh = self.get_level_orig_height(ktx2_handle, level, layer, face)
# Compute correct output size for the chosen format
out_size = self.basis_compute_transcoded_image_size_in_bytes(tfmt, ow, oh)
if out_size == 0:
raise RuntimeError(
f"Computed output size is 0 for tfmt={tfmt}, dims={ow}x{oh}"
)
# Allocate output buffer
out_ptr = self._alloc(out_size)
# Ensure transcoding tables are ready
ok = self.ktx2_start_transcoding(ktx2_handle.handle)
if not ok:
self._free(out_ptr)
raise RuntimeError("start_transcoding failed")
# Perform the transcode
ok = self.ktx2_transcode_image_level(
ktx2_handle.handle,
level, layer, face,
out_ptr,
out_size,
tfmt,
decode_flags,
0, # row_pitch_in_blocks_or_pixels
0, # rows_in_pixels
channel0,
channel1,
0 # no thread-local state
)
if not ok:
self._free(out_ptr)
raise RuntimeError("ktx2_transcode_image_level failed")
# Extract bytes from native/WASM memory
data_bytes = self._read(out_ptr, out_size)
# Free the output buffer
self._free(out_ptr)
# Determine block dims for this texture format
if self.basis_transcoder_format_is_uncompressed(tfmt):
bw = None
bh = None
else:
bw = self.basis_get_block_width(tfmt)
bh = self.basis_get_block_height(tfmt)
return data_bytes, tfmt, bw, bh
# -----------------------------------------------------------------------
# High-level: one-shot transcode using a family string
# directly from ktx2 file data. (Slower if you're transcoding multiple
# levels/faces/layers.)
# -----------------------------------------------------------------------
def transcode(
self,
ktx2_bytes: bytes,
family: str,
level=0,
layer=0,
face=0,
decode_flags=0,
channel0=-1,
channel1=-1
):
"""
High-level version of transcode_handle().
Calls transcode_handle() internally.
Returns:
(data_bytes, tfmt, block_width, block_height)
"""
ktx2_handle = self.open(ktx2_bytes)
try:
return self.transcode_handle(
ktx2_handle,
family,
level=level,
layer=layer,
face=face,
decode_flags=decode_flags,
channel0=channel0,
channel1=channel1
)
finally:
self.close(ktx2_handle)
def tfmt_name(self, tfmt: int):
return TranscoderTextureFormat(tfmt).name

View File

@@ -0,0 +1 @@
# Purposely empty

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,126 @@
# basisu_py/wasm/wasm_encoder.py
import wasmtime
import ctypes
from ..constants import BasisTexFormat, BasisQuality, BasisEffort, BasisFlags
class BasisuWasmEncoder:
def __init__(self, wasm_path):
self.wasm_path = wasm_path
self.engine = None
self.store = None
self.memory = None
self.exports = None
# ------------------------------------------------------
# Initialize WASM + WASI
# ------------------------------------------------------
def _init_engine(self):
self.engine = wasmtime.Engine()
self.store = wasmtime.Store(self.engine)
wasi = wasmtime.WasiConfig()
wasi.argv = ["basisu-wasm"]
wasi.inherit_stdout()
wasi.inherit_stderr()
self.store.set_wasi(wasi)
def load(self):
self._init_engine()
module = wasmtime.Module.from_file(self.engine, self.wasm_path)
linker = wasmtime.Linker(self.engine)
linker.define_wasi()
instance = linker.instantiate(self.store, module)
self.exports = instance.exports(self.store)
self.memory = self.exports["memory"]
# Initialize if present
if "bu_init" in self.exports:
self.exports["bu_init"](self.store)
print("[WASM Encoder] Loaded:", self.wasm_path)
# ------------------------------------------------------
# Access raw linear memory buffer
# ------------------------------------------------------
def _buf(self):
raw_ptr = self.memory.data_ptr(self.store)
size = self.memory.data_len(self.store)
addr = ctypes.addressof(raw_ptr.contents)
return (ctypes.c_ubyte * size).from_address(addr)
# ------------------------------------------------------
# Version
# ------------------------------------------------------
def get_version(self):
return self.exports["bu_get_version"](self.store)
# ------------------------------------------------------
# Memory alloc/free
# ------------------------------------------------------
def alloc(self, size):
return self.exports["bu_alloc"](self.store, size)
def free(self, ptr):
self.exports["bu_free"](self.store, ptr)
# ------------------------------------------------------
# Params
# ------------------------------------------------------
def new_params(self):
return self.exports["bu_new_comp_params"](self.store)
def delete_params(self, params):
return self.exports["bu_delete_comp_params"](self.store, params)
# ------------------------------------------------------
# Image input
# ------------------------------------------------------
def set_image_rgba32(self, params, index, ptr, w, h, pitch):
return self.exports["bu_comp_params_set_image_rgba32"](
self.store, params, index, ptr, w, h, pitch
)
def set_image_float_rgba(self, params, index, ptr, w, h, pitch):
return self.exports["bu_comp_params_set_image_float_rgba"](
self.store, params, index, ptr, w, h, pitch
)
# ------------------------------------------------------
# Compression
# ------------------------------------------------------
def compress(self, params, fmt, quality, effort, flags, rdo):
return bool(self.exports["bu_compress_texture"](
self.store, params, fmt, quality, effort, flags, rdo
))
# ------------------------------------------------------
# Output blob
# ------------------------------------------------------
def get_comp_data_size(self, params):
return self.exports["bu_comp_params_get_comp_data_size"](self.store, params)
def get_comp_data_ofs(self, params):
return self.exports["bu_comp_params_get_comp_data_ofs"](self.store, params)
# ------------------------------------------------------
# Raw memory I/O
# ------------------------------------------------------
def write_bytes(self, ptr, data):
buf = self._buf()
buf[ptr:ptr + len(data)] = data
def read_bytes(self, ptr, size):
buf = self._buf()
return bytes(buf[ptr:ptr + size])
# NEW unified names:
def write_memory(self, ptr, data):
self.write_bytes(ptr, data)
def read_memory(self, ptr, size):
return self.read_bytes(ptr, size)

View File

@@ -0,0 +1,326 @@
# basisu_py/wasm/wasm_transcoder.py
import wasmtime
import ctypes
class BasisuWasmTranscoder:
"""
Lowest-level WASM transcoder wrapper.
Direct mapping to basisu_wasm_transcoder_api.h/.cpp
NOTE:
- This layer does NOT interpret formats or block sizes.
- It only wraps the raw C API (bt_* and basis_* exports).
- Higher-level logic (TranscoderCore, Transcoder) will build on top.
"""
def __init__(self, wasm_path: str):
self.wasm_path = wasm_path
self.engine = None
self.store = None
self.memory = None
self.exports = None
# ------------------------------------------------------
# Internal: initialize WASM + WASI
# ------------------------------------------------------
def _init_engine(self):
self.engine = wasmtime.Engine()
self.store = wasmtime.Store(self.engine)
wasi = wasmtime.WasiConfig()
wasi.argv = ["basisu-transcoder"]
wasi.inherit_stdout()
wasi.inherit_stderr()
self.store.set_wasi(wasi)
def load(self):
self._init_engine()
module = wasmtime.Module.from_file(self.engine, self.wasm_path)
linker = wasmtime.Linker(self.engine)
linker.define_wasi()
instance = linker.instantiate(self.store, module)
self.exports = instance.exports(self.store)
self.memory = self.exports["memory"]
# Mandatory transcoder init
if "bt_init" in self.exports:
self.exports["bt_init"](self.store)
print("[WASM Transcoder] Loaded:", self.wasm_path)
# ------------------------------------------------------
# Linear memory access helpers
# ------------------------------------------------------
def _buf(self):
raw_ptr = self.memory.data_ptr(self.store)
size = self.memory.data_len(self.store)
addr = ctypes.addressof(raw_ptr.contents)
return (ctypes.c_ubyte * size).from_address(addr)
def write_bytes(self, ptr: int, data: bytes):
buf = self._buf()
buf[ptr:ptr + len(data)] = data
def read_bytes(self, ptr: int, num: int) -> bytes:
buf = self._buf()
return bytes(buf[ptr:ptr + num])
# NEW unified names:
def write_memory(self, ptr, data):
self.write_bytes(ptr, data)
def read_memory(self, ptr, size):
return self.read_bytes(ptr, size)
# ------------------------------------------------------
# Memory alloc/free
# ------------------------------------------------------
def alloc(self, size: int) -> int:
return self.exports["bt_alloc"](self.store, size)
def free(self, ptr: int):
return self.exports["bt_free"](self.store, ptr)
# ------------------------------------------------------
# High-level functions: version, init, debug
# ------------------------------------------------------
def get_version(self) -> int:
return self.exports["bt_get_version"](self.store)
def enable_debug_printf(self, flag: bool = True):
return self.exports["bt_enable_debug_printf"](self.store, 1 if flag else 0)
# ------------------------------------------------------
# basis_tex_format helpers
# ------------------------------------------------------
def basis_tex_format_is_xuastc_ldr(self, basis_tex_fmt_u32: int) -> bool:
return bool(self.exports["bt_basis_tex_format_is_xuastc_ldr"](self.store, basis_tex_fmt_u32))
def basis_tex_format_is_astc_ldr(self, basis_tex_fmt_u32: int) -> bool:
return bool(self.exports["bt_basis_tex_format_is_astc_ldr"](self.store, basis_tex_fmt_u32))
def basis_tex_format_get_block_width(self, basis_tex_fmt_u32: int) -> int:
return self.exports["bt_basis_tex_format_get_block_width"](self.store, basis_tex_fmt_u32)
def basis_tex_format_get_block_height(self, basis_tex_fmt_u32: int) -> int:
return self.exports["bt_basis_tex_format_get_block_height"](self.store, basis_tex_fmt_u32)
def basis_tex_format_is_hdr(self, basis_tex_fmt_u32: int) -> bool:
return bool(self.exports["bt_basis_tex_format_is_hdr"](self.store, basis_tex_fmt_u32))
def basis_tex_format_is_ldr(self, basis_tex_fmt_u32: int) -> bool:
return bool(self.exports["bt_basis_tex_format_is_ldr"](self.store, basis_tex_fmt_u32))
# ------------------------------------------------------
# transcoder_texture_format helpers
# ------------------------------------------------------
def basis_get_bytes_per_block_or_pixel(self, tfmt_u32: int) -> int:
return self.exports["bt_basis_get_bytes_per_block_or_pixel"](self.store, tfmt_u32)
def basis_transcoder_format_has_alpha(self, tfmt_u32: int) -> bool:
return bool(self.exports["bt_basis_transcoder_format_has_alpha"](self.store, tfmt_u32))
def basis_transcoder_format_is_hdr(self, tfmt_u32: int) -> bool:
return bool(self.exports["bt_basis_transcoder_format_is_hdr"](self.store, tfmt_u32))
def basis_transcoder_format_is_ldr(self, tfmt_u32: int) -> bool:
return bool(self.exports["bt_basis_transcoder_format_is_ldr"](self.store, tfmt_u32))
def basis_transcoder_texture_format_is_astc(self, tfmt_u32: int) -> bool:
return bool(self.exports["bt_basis_transcoder_texture_format_is_astc"](self.store, tfmt_u32))
def basis_transcoder_format_is_uncompressed(self, tfmt_u32: int) -> bool:
return bool(self.exports["bt_basis_transcoder_format_is_uncompressed"](self.store, tfmt_u32))
def basis_get_uncompressed_bytes_per_pixel(self, tfmt_u32: int) -> int:
return self.exports["bt_basis_get_uncompressed_bytes_per_pixel"](self.store, tfmt_u32)
def basis_get_block_width(self, tfmt_u32: int) -> int:
return self.exports["bt_basis_get_block_width"](self.store, tfmt_u32)
def basis_get_block_height(self, tfmt_u32: int) -> int:
return self.exports["bt_basis_get_block_height"](self.store, tfmt_u32)
def basis_get_transcoder_texture_format_from_basis_tex_format(self, basis_tex_fmt_u32: int) -> int:
return self.exports["bt_basis_get_transcoder_texture_format_from_basis_tex_format"](self.store, basis_tex_fmt_u32)
def basis_is_format_supported(self, tfmt_u32: int, basis_tex_fmt_u32: int) -> bool:
return bool(self.exports["bt_basis_is_format_supported"](self.store, tfmt_u32, basis_tex_fmt_u32))
def basis_compute_transcoded_image_size_in_bytes(self, tfmt_u32: int, orig_width: int, orig_height: int) -> int:
return self.exports["bt_basis_compute_transcoded_image_size_in_bytes"](
self.store, tfmt_u32, orig_width, orig_height
)
# ------------------------------------------------------
# KTX2 handle management
# ------------------------------------------------------
def ktx2_open(self, data_ptr: int, data_len: int) -> int:
return self.exports["bt_ktx2_open"](self.store, data_ptr, data_len)
def ktx2_close(self, handle: int):
return self.exports["bt_ktx2_close"](self.store, handle)
# ------------------------------------------------------
# Basic KTX2 metadata
# ------------------------------------------------------
def ktx2_get_width(self, handle: int) -> int:
return self.exports["bt_ktx2_get_width"](self.store, handle)
def ktx2_get_height(self, handle: int) -> int:
return self.exports["bt_ktx2_get_height"](self.store, handle)
def ktx2_get_levels(self, handle: int) -> int:
return self.exports["bt_ktx2_get_levels"](self.store, handle)
def ktx2_get_faces(self, handle: int) -> int:
return self.exports["bt_ktx2_get_faces"](self.store, handle)
def ktx2_get_layers(self, handle: int) -> int:
return self.exports["bt_ktx2_get_layers"](self.store, handle)
def ktx2_get_basis_tex_format(self, handle: int) -> int:
return self.exports["bt_ktx2_get_basis_tex_format"](self.store, handle)
# KTX2 format checks
def ktx2_is_etc1s(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_etc1s"](self.store, handle))
def ktx2_is_uastc_ldr_4x4(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_uastc_ldr_4x4"](self.store, handle))
def ktx2_is_hdr(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_hdr"](self.store, handle))
def ktx2_is_hdr_4x4(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_hdr_4x4"](self.store, handle))
def ktx2_is_hdr_6x6(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_hdr_6x6"](self.store, handle))
def ktx2_is_ldr(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_ldr"](self.store, handle))
def ktx2_is_astc_ldr(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_astc_ldr"](self.store, handle))
def ktx2_is_xuastc_ldr(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_xuastc_ldr"](self.store, handle))
def ktx2_get_block_width(self, handle: int) -> int:
return self.exports["bt_ktx2_get_block_width"](self.store, handle)
def ktx2_get_block_height(self, handle: int) -> int:
return self.exports["bt_ktx2_get_block_height"](self.store, handle)
def ktx2_has_alpha(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_has_alpha"](self.store, handle))
def ktx2_get_dfd_color_model(self, handle: int) -> int:
return self.exports["bt_ktx2_get_dfd_color_model"](self.store, handle)
def ktx2_get_dfd_color_primaries(self, handle: int) -> int:
return self.exports["bt_ktx2_get_dfd_color_primaries"](self.store, handle)
def ktx2_get_dfd_transfer_func(self, handle: int) -> int:
return self.exports["bt_ktx2_get_dfd_transfer_func"](self.store, handle)
def ktx2_is_srgb(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_srgb"](self.store, handle))
def ktx2_get_dfd_flags(self, handle: int) -> int:
return self.exports["bt_ktx2_get_dfd_flags"](self.store, handle)
def ktx2_get_dfd_total_samples(self, handle: int) -> int:
return self.exports["bt_ktx2_get_dfd_total_samples"](self.store, handle)
def ktx2_get_dfd_channel_id0(self, handle: int) -> int:
return self.exports["bt_ktx2_get_dfd_channel_id0"](self.store, handle)
def ktx2_get_dfd_channel_id1(self, handle: int) -> int:
return self.exports["bt_ktx2_get_dfd_channel_id1"](self.store, handle)
def ktx2_is_video(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_is_video"](self.store, handle))
def ktx2_get_ldr_hdr_upconversion_nit_multiplier(self, handle: int) -> float:
return self.exports["bt_ktx2_get_ldr_hdr_upconversion_nit_multiplier"](self.store, handle)
# ------------------------------------------------------
# Per-level metadata
# ------------------------------------------------------
def ktx2_get_level_orig_width(self, h, lvl, layer, face) -> int:
return self.exports["bt_ktx2_get_level_orig_width"](self.store, h, lvl, layer, face)
def ktx2_get_level_orig_height(self, h, lvl, layer, face) -> int:
return self.exports["bt_ktx2_get_level_orig_height"](self.store, h, lvl, layer, face)
def ktx2_get_level_actual_width(self, h, lvl, layer, face) -> int:
return self.exports["bt_ktx2_get_level_actual_width"](self.store, h, lvl, layer, face)
def ktx2_get_level_actual_height(self, h, lvl, layer, face) -> int:
return self.exports["bt_ktx2_get_level_actual_height"](self.store, h, lvl, layer, face)
def ktx2_get_level_num_blocks_x(self, h, lvl, layer, face) -> int:
return self.exports["bt_ktx2_get_level_num_blocks_x"](self.store, h, lvl, layer, face)
def ktx2_get_level_num_blocks_y(self, h, lvl, layer, face) -> int:
return self.exports["bt_ktx2_get_level_num_blocks_y"](self.store, h, lvl, layer, face)
def ktx2_get_level_total_blocks(self, h, lvl, layer, face) -> int:
return self.exports["bt_ktx2_get_level_total_blocks"](self.store, h, lvl, layer, face)
def ktx2_get_level_alpha_flag(self, h, lvl, layer, face) -> bool:
return bool(self.exports["bt_ktx2_get_level_alpha_flag"](self.store, h, lvl, layer, face))
def ktx2_get_level_iframe_flag(self, h, lvl, layer, face) -> bool:
return bool(self.exports["bt_ktx2_get_level_iframe_flag"](self.store, h, lvl, layer, face))
# ------------------------------------------------------
# Transcoding control
# ------------------------------------------------------
def ktx2_start_transcoding(self, handle: int) -> bool:
return bool(self.exports["bt_ktx2_start_transcoding"](self.store, handle))
def ktx2_create_transcode_state(self) -> int:
return self.exports["bt_ktx2_create_transcode_state"](self.store)
def ktx2_destroy_transcode_state(self, handle: int):
return self.exports["bt_ktx2_destroy_transcode_state"](self.store, handle)
# ------------------------------------------------------
# Actual transcoding call
# ------------------------------------------------------
def ktx2_transcode_image_level(
self,
ktx2_handle: int,
level_index: int,
layer_index: int,
face_index: int,
output_block_mem_ofs: int,
output_blocks_buf_size_in_blocks_or_pixels: int,
transcoder_texture_format_u32: int,
decode_flags: int,
output_row_pitch_in_blocks_or_pixels: int,
output_rows_in_pixels: int,
channel0: int,
channel1: int,
state_handle: int,
) -> bool:
return bool(self.exports["bt_ktx2_transcode_image_level"](
self.store,
ktx2_handle,
level_index, layer_index, face_index,
output_block_mem_ofs,
output_blocks_buf_size_in_blocks_or_pixels,
transcoder_texture_format_u32,
decode_flags,
output_row_pitch_in_blocks_or_pixels,
output_rows_in_pixels,
channel0, channel1,
state_handle
))