mirror of
https://github.com/BinomialLLC/basis_universal.git
synced 2026-09-17 07:54:30 +00:00
adding new files
This commit is contained in:
2
python/basisu_py/MANIFEST.in
Normal file
2
python/basisu_py/MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
||||
recursive-include basisu_py *.py *.so *.wasm
|
||||
include README.md
|
||||
5
python/basisu_py/READMD.md
Normal file
5
python/basisu_py/READMD.md
Normal file
@@ -0,0 +1,5 @@
|
||||
This is the Python support directory for the Basis Universal KTX2 compressor
|
||||
and transcoder modules.
|
||||
|
||||
License: Apache 2.0
|
||||
|
||||
35
python/basisu_py/__init__.py
Normal file
35
python/basisu_py/__init__.py
Normal 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",
|
||||
]
|
||||
BIN
python/basisu_py/basisu_python.cpython-312-x86_64-linux-gnu.so
Normal file
BIN
python/basisu_py/basisu_python.cpython-312-x86_64-linux-gnu.so
Normal file
Binary file not shown.
BIN
python/basisu_py/basisu_python.pyd
Normal file
BIN
python/basisu_py/basisu_python.pyd
Normal file
Binary file not shown.
Binary file not shown.
BIN
python/basisu_py/basisu_transcoder_python.pyd
Normal file
BIN
python/basisu_py/basisu_transcoder_python.pyd
Normal file
Binary file not shown.
222
python/basisu_py/codec.py
Normal file
222
python/basisu_py/codec.py
Normal 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")
|
||||
183
python/basisu_py/constants.py
Normal file
183
python/basisu_py/constants.py
Normal 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
|
||||
735
python/basisu_py/transcoder.py
Normal file
735
python/basisu_py/transcoder.py
Normal 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
|
||||
1
python/basisu_py/wasm/__init__.py
Normal file
1
python/basisu_py/wasm/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Purposely empty
|
||||
BIN
python/basisu_py/wasm/basisu_module_mt.wasm
Normal file
BIN
python/basisu_py/wasm/basisu_module_mt.wasm
Normal file
Binary file not shown.
BIN
python/basisu_py/wasm/basisu_module_st.wasm
Normal file
BIN
python/basisu_py/wasm/basisu_module_st.wasm
Normal file
Binary file not shown.
BIN
python/basisu_py/wasm/basisu_transcoder_module_mt.wasm
Normal file
BIN
python/basisu_py/wasm/basisu_transcoder_module_mt.wasm
Normal file
Binary file not shown.
BIN
python/basisu_py/wasm/basisu_transcoder_module_st.wasm
Normal file
BIN
python/basisu_py/wasm/basisu_transcoder_module_st.wasm
Normal file
Binary file not shown.
126
python/basisu_py/wasm/wasm_encoder.py
Normal file
126
python/basisu_py/wasm/wasm_encoder.py
Normal 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)
|
||||
326
python/basisu_py/wasm/wasm_transcoder.py
Normal file
326
python/basisu_py/wasm/wasm_transcoder.py
Normal 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
|
||||
))
|
||||
Reference in New Issue
Block a user