From 7aa660981bd761920745dd69ad24624e10c9f9c1 Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Wed, 18 Mar 2026 10:29:56 +0900 Subject: [PATCH 01/13] Expose HDR features. To Do: Add tests of the new features. --- interface/python_binding/buildscript.py | 16 ++- .../pyktx/khr_df_color_model.py | 126 ++++++++++++++++++ .../python_binding/pyktx/khr_df_primaries.py | 51 +++++++ .../pyktx/khr_df_transfer_function.py | 94 +++++++++++++ .../python_binding/pyktx/ktx_basis_codec.py | 18 +++ .../python_binding/pyktx/ktx_basis_params.py | 48 ++++++- interface/python_binding/pyktx/ktx_texture2.c | 23 +++- interface/python_binding/pyktx/ktx_texture2.h | 11 +- .../python_binding/pyktx/ktx_texture2.py | 62 ++++++++- lib/include/ktx.h | 21 ++- 10 files changed, 446 insertions(+), 24 deletions(-) create mode 100644 interface/python_binding/pyktx/khr_df_color_model.py create mode 100644 interface/python_binding/pyktx/khr_df_primaries.py create mode 100644 interface/python_binding/pyktx/khr_df_transfer_function.py create mode 100644 interface/python_binding/pyktx/ktx_basis_codec.py diff --git a/interface/python_binding/buildscript.py b/interface/python_binding/buildscript.py index 2a1e04759f..1b63334b54 100644 --- a/interface/python_binding/buildscript.py +++ b/interface/python_binding/buildscript.py @@ -81,10 +81,15 @@ uint32_t faceSlice, void *src, size_t srcSize); + bool ktxTexture_IsHDR(ktxTexture *); + int ktxTexture2_DecodeAstc(void *); int ktxTexture2_TranscodeBasis(void *, int outputFormat, int transcodeFlags); int ktxTexture2_DeflateZstd(void *, uint32_t compressionLevel); - uint32_t ktxTexture2_GetOETF(void *); + uint32_t ktxTexture2_GetColorModel_e(void *); + uint32_t ktxTexture2_GetPrimaries_e(void *); + uint32_t ktxTexture2_GetTransferFunction_e(void *); bool ktxTexture2_GetPremultipliedAlpha(void *); + bool ktxTexture2_IsTranscodable(void *); bool ktxTexture2_NeedsTranscoding(void *); int ktxHashList_AddKVPair(ktxHashList *, const char *key, unsigned int valueLen, const void *value); @@ -182,7 +187,14 @@ float uastcRDOMaxSmoothBlockErrorScale, float uastcRDOMaxSmoothBlockStdDev, bool uastcRDODontFavorSimplerModes, - bool uastcRDONoMultithreading); + bool uastcRDONoMultithreading, + uint32_t uastcHDRQuality, + bool uastcHDRUberMode, + bool uastcHDRUltraQuant, + bool uastcHDRFavorAstc, + bool rec2020, + float uastcHDRLambda, + uint32_t uastcHDRLevel); uint32_t PY_ktxTexture2_get_vkFormat(void *); uint32_t PY_ktxTexture2_get_supercompressionScheme(void *); """ diff --git a/interface/python_binding/pyktx/khr_df_color_model.py b/interface/python_binding/pyktx/khr_df_color_model.py new file mode 100644 index 0000000000..501460d70c --- /dev/null +++ b/interface/python_binding/pyktx/khr_df_color_model.py @@ -0,0 +1,126 @@ +# Copyright (c) 2026, Mark Callow +# SPDX-License-Identifier: Apache-2.0 + +from enum import IntEnum + +class KhrDfColorModel(IntEnum): + """Model in which the color coordinate space is defined.""" + + KHR_DF_MODEL_UNSPECIFIED = 0 + """No interpretation of color channels defined.""" + + KHR_DF_MODEL_RGBSDA = 1 + """Color primaries (red, green, blue) + alpha, depth and stencil.""" + + KHR_DF_MODEL_YUVSDA = 2 + """Color differences (Y', Cb, Cr) + alpha, depth and stencil.""" + + KHR_DF_MODEL_YIQSDA = 3 + """Color differences (Y', I, Q) + alpha, depth and stencil.""" + + KHR_DF_MODEL_LABSDA = 4 + """Perceptual color (CIE L*a*b*) + alpha, depth and stencil.""" + + KHR_DF_MODEL_CMYKA = 5 + """Subtractive colors (cyan, magenta, yellow, black) + alpha.""" + + KHR_DF_MODEL_XYZW = 6 + """Non-color coordinate data (X, Y, Z, W).""" + + KHR_DF_MODEL_HSVA_ANG = 7 + """Hue, saturation, value, hue angle on color circle, plus alpha.""" + + KHR_DF_MODEL_HSLA_ANG = 8 + """Hue, saturation, lightness, hue angle on color circle, plus alpha.""" + + KHR_DF_MODEL_HSVA_HEX = 9 + """Hue, saturation, value, hue on color hexagon, plus alpha.""" + + KHR_DF_MODEL_HSLA_HEX = 10 + """Hue, saturation, lightness, hue on color hexagon, plus alpha.""" + + KHR_DF_MODEL_YCGCOA = 11 + """Lightweight approximate color difference (luma, orange, green).""" + + KHR_DF_MODEL_YCCBCCRC = 12 + """ITU BT.2020 constant luminance YcCbcCrc.""" + + KHR_DF_MODEL_ICTCP = 13 + """ITU BT.2100 constant intensity ICtCp.""" + + KHR_DF_MODEL_CIEXYZ = 14 + """CIE 1931 XYZ color coordinates (X, Y, Z).""" + + KHR_DF_MODEL_CIEXYY = 15 + """CIE 1931 xyY color coordinates (X, Y, Y).""" + + + KHR_DF_MODEL_DXT1A = 128 + KHR_DF_MODEL_BC1A = 128 + """Compressed formats start at 128.""" + """ + Direct3D (and S3) compressed formats. + DXT1 "channels" are RGB (0), Alpha (1). + DXT1/BC1 with one channel is opaque. + DXT1/BC1 with a cosited alpha sample is transparent. + """ + + KHR_DF_MODEL_DXT2 = 129 + KHR_DF_MODEL_DXT3 = 129 + KHR_DF_MODEL_BC2 = 129 + """DXT2/DXT3/BC2, with explicit 4-bit alpha.""" + + KHR_DF_MODEL_DXT4 = 130 + KHR_DF_MODEL_DXT5 = 130 + KHR_DF_MODEL_BC3 = 130 + """DXT4/DXT5/BC3, with interpolated alpha.""" + + KHR_DF_MODEL_ATI1N = 131 + KHR_DF_MODEL_DXT5A = 131 + KHR_DF_MODEL_BC4 = 131 + """ATI1n/DXT5A/BC4 - single channel interpolated 8-bit data. + (The UNORM/SNORM variation is recorded in the channel data).""" + + KHR_DF_MODEL_ATI2N_XY = 132 + KHR_DF_MODEL_DXN = 132 + KHR_DF_MODEL_BC5 = 132 + """ATI2n_XY/DXN/BC5 - two channel interpolated 8-bit data. + (The UNORM/SNORM variation is recorded in the channel data).""" + + KHR_DF_MODEL_BC6H = 133 + """BC6H - DX11 format for 16-bit float channels.""" + + KHR_DF_MODEL_BC7 = 134 + """BC7 - DX11 format.""" + + KHR_DF_MODEL_ETC1 = 160 + """A format of ETC1 indicates that the format shall be decodable + by an ETC1-compliant decoder and not rely on ETC2 features.""" + + KHR_DF_MODEL_ETC2 = 161 + """A format of ETC2 is permitted to use ETC2 encodings on top of + the baseline ETC1 specification. + The ETC2 format has channels "red", "green", "RGB" and "alpha", + which should be cosited samples. + Punch-through alpha can be distinguished from full alpha by + the plane size in bytes required for the texel block.""" + + KHR_DF_MODEL_ASTC = 162 + """Adaptive Scalable Texture Compression.""" + """ASTC HDR vs LDR is determined by the float flag in the channel.""" + """ASTC block size can be distinguished by texel block size.""" + + KHR_DF_MODEL_ETC1S = 163 + """ETC1S is a simplified subset of ETC1.""" + + KHR_DF_MODEL_PVRTC = 164 + KHR_DF_MODEL_PVRTC2 = 165 + """PowerVR Texture Compression.""" + + KHR_DF_MODEL_UASTC = 166 + KHR_DF_MODEL_UASTC_LDR_4x4 = 166 + KHR_DF_MODEL_UASTC_HDR_4x4 = 167 + KHR_DF_MODEL_UASTC_HDR_6x6 = 168 + """UASTC for BASIS supercompression.""" + + KHR_DF_MODEL_MAX = 0xFF diff --git a/interface/python_binding/pyktx/khr_df_primaries.py b/interface/python_binding/pyktx/khr_df_primaries.py new file mode 100644 index 0000000000..ba8f669f33 --- /dev/null +++ b/interface/python_binding/pyktx/khr_df_primaries.py @@ -0,0 +1,51 @@ +# Copyright (c) 2026, Mark Callow +# SPDX-License-Identifier: Apache-2.0 + +from enum import IntEnum + +class KhrDfPrimaries(IntEnum): + """The primary colors of an image.""" + + KHR_DF_PRIMARIES_UNSPECIFIED = 0 + """No color primaries defined""" + + KHR_DF_PRIMARIES_BT709 = 1 + """Color primaries of ITU-R BT.709 and sRGB""" + + KHR_DF_PRIMARIES_SRGB = 1 + """Synonym for KHR_DF_PRIMARIES_BT709""" + + KHR_DF_PRIMARIES_BT601_EBU = 2 + """Color primaries of ITU-R BT.601 (625-line EBU variant)""" + + KHR_DF_PRIMARIES_BT601_SMPTE = 3 + """Color primaries of ITU-R BT.601 (525-line SMPTE C variant)""" + + KHR_DF_PRIMARIES_BT2020 = 4 + """Color primaries of ITU-R BT.2020""" + + KHR_DF_PRIMARIES_BT2100 = 4 + """ITU-R BT.2100 uses the same primaries as BT.2020""" + + KHR_DF_PRIMARIES_CIEXYZ = 5 + """CIE theoretical color coordinate space""" + + KHR_DF_PRIMARIES_ACES = 6 + """Academy Color Encoding System primaries""" + + KHR_DF_PRIMARIES_ACESCC = 7 + """Color primaries of ACEScc""" + + KHR_DF_PRIMARIES_NTSC1953 = 8 + """Legacy NTSC 1953 primaries""" + + KHR_DF_PRIMARIES_PAL525 = 9 + """Legacy PAL 525-line primaries""" + + KHR_DF_PRIMARIES_DISPLAYP3 = 10 + """Color primaries of Display P3""" + + KHR_DF_PRIMARIES_ADOBERGB = 11 + """Color primaries of Adobe RGB (1998)""" + + KHR_DF_PRIMARIES_MAX = 0xFF diff --git a/interface/python_binding/pyktx/khr_df_transfer_function.py b/interface/python_binding/pyktx/khr_df_transfer_function.py new file mode 100644 index 0000000000..716b43a2b0 --- /dev/null +++ b/interface/python_binding/pyktx/khr_df_transfer_function.py @@ -0,0 +1,94 @@ +# Copyright (c) 2026, Mark Callow +# SPDX-License-Identifier: Apache-2.0 + +from enum import IntEnum + +class KhrDfTransferFunction(IntEnum): + """The transfer function of an image.""" + + TRANSFER_UNSPECIFIED = 0 + """No transfer function defined.""" + + TRANSFER_LINEAR = 1 + """Linear transfer function (value proportional to intensity.""" + + TRANSFER_SRGB = 2 + TRANSFER_SRGB_EOTF = 2 + TRANSFER_SCRGB = 2 + TRANSFER_SCRGB_EOTF = 2 + """Perceptually-linear transfer function of sRGB (~2.2); also used for scRGB.""" + + TRANSFER_ITU = 3 + TRANSFER_ITU_OETF = 3 + TRANSFER_BT601 = 3 + TRANSFER_BT601_OETF = 3 + TRANSFER_BT709 = 3 + TRANSFER_BT709_OETF = 3 + TRANSFER_BT2020 = 3 + TRANSFER_BT2020_OETF = 3 + """Perceptually-linear transfer function of ITU BT.601, BT.709 and BT.2020 (~1/.45).""" + + TRANSFER_SMTPE170M = 3 + TRANSFER_SMTPE170M_OETF = 3 + TRANSFER_SMTPE170M_EOTF = 3 + """SMTPE170M (digital NTSC) defines an alias for the ITU transfer function (~1/.45) and a linear OOTF.""" + + TRANSFER_NTSC = 4 + TRANSFER_NTSC_EOTF = 4 + """Perceptually-linear gamma function of original NTSC (simple 2.2 gamma).""" + + TRANSFER_SLOG = 5 + TRANSFER_SLOG_OETF = 5 + """Sony S-log used by Sony video cameras.""" + + TRANSFER_SLOG2 = 6 + TRANSFER_SLOG2_OETF = 6 + """Sony S-log 2 used by Sony video cameras.""" + + TRANSFER_BT1886 = 7 + TRANSFER_BT1886_EOTF = 7 + """ITU BT.1886 EOTF.""" + + TRANSFER_HLG_OETF = 8 + """ITU BT.2100 HLG OETF (typical scene-referred content), linear light normalized 0..1.""" + + TRANSFER_HLG_EOTF = 9 + """ITU BT.2100 HLG EOTF (nominal HDR display of HLG content), linear light normalized 0..1.""" + + TRANSFER_PQ_EOTF = 10 + """ITU BT.2100 PQ EOTF (typical HDR display-referred PQ content).""" + + TRANSFER_PQ_OETF = 11 + """ITU BT.2100 PQ OETF (nominal scene described by PQ HDR content).""" + + TRANSFER_DCIP3 = 12 + TRANSFER_DCIP3_EOTF = 12 + """DCI P3 transfer function.""" + + TRANSFER_PAL_OETF = 13 + """Legacy PAL OETF.""" + + TRANSFER_PAL625_EOTF = 14 + """Legacy PAL 625-line EOTF.""" + + TRANSFER_ST240 = 15 + TRANSFER_ST240_OETF = 15 + TRANSFER_ST240_EOTF = 15 + """Legacy ST240 transfer function.""" + + TRANSFER_ACESCC = 16 + TRANSFER_ACESCC_OETF = 16 + """ACEScc transfer function.""" + + TRANSFER_ACESCCT = 17 + TRANSFER_ACESCCT_OETF = 17 + """ACEScct transfer function.""" + + TRANSFER_ADOBERGB = 18 + TRANSFER_ADOBERGB_EOTF = 18 + """Adobe RGB (1998) transfer function.""" + + TRANSFER_HLG_UNNORMALIZED_OETF = 19 + """Legacy ITU BT.2100 HLG OETF (typical scene-referred content), linear light normalized 0..12.""" + + TRANSFER_MAX = 0xFF diff --git a/interface/python_binding/pyktx/ktx_basis_codec.py b/interface/python_binding/pyktx/ktx_basis_codec.py new file mode 100644 index 0000000000..3bcbc44107 --- /dev/null +++ b/interface/python_binding/pyktx/ktx_basis_codec.py @@ -0,0 +1,18 @@ +# Copyright (c) 2026, Mark Callow +# SPDX-License-Identifier: Apache-2.0 + +from enum import IntEnum + +class KtxBasisCodec(IntEnum): + """Options specifiying basis codec.""" + + NONE = 0 + """NONE.""" + ETC1S = 1 + """BasisLZ.""" + UASTC_LDR_4x4 = 2 + """UASTC.""" + UASTC_HDR_4x4 = 3 + """UASTC_HDR_4x4.""" + UASTC_HDR_6x6_INTERMEDIATE = 4 + """UASTC_HDR_6x6i.""" diff --git a/interface/python_binding/pyktx/ktx_basis_params.py b/interface/python_binding/pyktx/ktx_basis_params.py index c30fa38f60..be865b4f8e 100644 --- a/interface/python_binding/pyktx/ktx_basis_params.py +++ b/interface/python_binding/pyktx/ktx_basis_params.py @@ -2,15 +2,15 @@ # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass +from .ktx_basis_codec import KtxBasisCodec from .ktx_pack_uastc_flag_bits import KtxPackUastcFlagBits - @dataclass class KtxBasisParams: """Data for passing extended params to KtxTexture2.compressBasis().""" - uastc: bool = False - """True to use UASTC base, false to use ETC1S base.""" + codec: int = KtxBasisCodec.ETC1S + """Basis Universal codec to use. Default is ETC1S/BasisLZ.""" verbose: bool = False """If true, prints Basis Universal encoder operation details to stdout. Not recommended for GUI apps.""" @@ -170,3 +170,45 @@ class KtxBasisParams: uastc_rdo_no_multithreading: bool = False """Disable RDO multithreading (slightly higher compression, deterministic).""" + + # UASTC HDR params + + uastc_hdr_quality: int = 1 + """ + Valid range is [0,4] - higher=slower but higher quality. Default=1. + Level 0=fastest/lowest quality, 3=highest practical setting, 4=exhaustive + """ + + uastc_hdr_uber_mode: bool = False + """ + UASTC HDR 4x4: Allow the UASTC HDR 4x4 encoder to try varying the CEM 11 + selectors more for slightly higher quality (slower). This may negatively impact BC6H quality, however. + """ + + uastc_hdr_ultra_quant: bool = False + """UASTC HDR 4x4: Try to find better quantized CEM 7/11 endpoint values (slower).""" + + uastc_hdr_favor_astc: bool = False + """ + UASTC HDR 4x4: By default the UASTC HDR 4x4 encoder tries to strike a balance + or even slightly favor BC6H quality. If this option is specified, ASTC HDR 4x4 quality is favored instead. + """ + + rec_2020: bool = False + """ + UASTC HDR 6x6i specific option: The input image's gamut is Rec. 2020 vs. the + default Rec. 709 - for accurate colorspace error calculations. + """ + + uastc_hdr_lambda: float = 0. + """ + UASTC HDR 6x6i specific option: Enables rate distortion optimization (RDO). + The higher this value, the lower the quality, but the smaller the file size. + Try 100-20000, or higher values on some images. + """ + + uastc_hdr_level: int = 2 + """ + UASTC HDR 6x6i specific option: Controls the 6x6 HDR intermediate mode encoder + performance vs. max quality tradeoff. X may range from [0,12]. Default level is 2. + """ diff --git a/interface/python_binding/pyktx/ktx_texture2.c b/interface/python_binding/pyktx/ktx_texture2.c index f1f6a4ce84..eb5ccd3a25 100644 --- a/interface/python_binding/pyktx/ktx_texture2.c +++ b/interface/python_binding/pyktx/ktx_texture2.c @@ -79,7 +79,7 @@ KTX_error_code PY_ktxTexture2_CompressAstcEx(ktxTexture2 *texture, } KTX_error_code PY_ktxTexture2_CompressBasisEx(ktxTexture2 *texture, - ktx_bool_t uastc, + int codec, ktx_bool_t verbose, ktx_bool_t noSSE, ktx_uint32_t threadCount, @@ -102,11 +102,19 @@ KTX_error_code PY_ktxTexture2_CompressBasisEx(ktxTexture2 *texture, float uastcRDOMaxSmoothBlockErrorScale, float uastcRDOMaxSmoothBlockStdDev, ktx_bool_t uastcRDODontFavorSimplerModes, - ktx_bool_t uastcRDONoMultithreading) + ktx_bool_t uastcRDONoMultithreading, + ktx_uint32_t uastcHDRQuality, + ktx_bool_t uastcHDRUberMode, + ktx_bool_t uastcHDRUltraQuant, + ktx_bool_t uastcHDRFavorAstc, + ktx_bool_t rec2020, + float uastcHDRLambda, + ktx_uint32_t uastcHDRLevel + ) { ktxBasisParams params = { .structSize = sizeof(ktxBasisParams), - .codec = (uastc) ? KTX_BASIS_CODEC_UASTC_LDR_4x4 : KTX_BASIS_CODEC_ETC1S, + .codec = codec, .verbose = verbose, .noSSE = noSSE, .threadCount = threadCount, @@ -128,7 +136,14 @@ KTX_error_code PY_ktxTexture2_CompressBasisEx(ktxTexture2 *texture, .uastcRDOMaxSmoothBlockErrorScale = uastcRDOMaxSmoothBlockErrorScale, .uastcRDOMaxSmoothBlockStdDev = uastcRDOMaxSmoothBlockStdDev, .uastcRDODontFavorSimplerModes = uastcRDODontFavorSimplerModes, - .uastcRDONoMultithreading = uastcRDONoMultithreading + .uastcRDONoMultithreading = uastcRDONoMultithreading, + .uastcHDRQuality = uastcHDRQuality, + .uastcHDRUberMode = uastcHDRUberMode, + .uastcHDRUltraQuant = uastcHDRUltraQuant, + .uastcHDRFavorAstc = uastcHDRFavorAstc, + .rec2020 = rec2020, + .uastcHDRLambda = uastcHDRLambda, + .uastcHDRLevel = uastcHDRLevel }; params.inputSwizzle[0] = inputSwizzle[0]; diff --git a/interface/python_binding/pyktx/ktx_texture2.h b/interface/python_binding/pyktx/ktx_texture2.h index 27427908a7..d97e8ef991 100644 --- a/interface/python_binding/pyktx/ktx_texture2.h +++ b/interface/python_binding/pyktx/ktx_texture2.h @@ -34,7 +34,7 @@ KTX_error_code PY_ktxTexture2_CompressAstcEx(ktxTexture2 *texture, char *inputSwizzle); KTX_error_code PY_ktxTexture2_CompressBasisEx(ktxTexture2 *texture, - ktx_bool_t uastc, + int codec, ktx_bool_t verbose, ktx_bool_t noSSE, ktx_uint32_t threadCount, @@ -57,7 +57,14 @@ KTX_error_code PY_ktxTexture2_CompressBasisEx(ktxTexture2 *texture, float uastcRDOMaxSmoothBlockErrorScale, float uastcRDOMaxSmoothBlockStdDev, ktx_bool_t uastcRDODontFavorSimplerModes, - ktx_bool_t uastcRDONoMultithreading); + ktx_bool_t uastcRDONoMultithreading, + ktx_uint32_t uastcHDRQuality, + ktx_bool_t uastcHDRUberMode, + ktx_bool_t uastcHDRUltraQuant, + ktx_bool_t uastcHDRFavorAstc, + ktx_bool_t rec2020, + float uastcHDRLambda, + ktx_uint32_t uastcHDRLevel); #define KTX2_GETTER(type, prop) \ type PY_ktxTexture2_get_##prop(ktxTexture2 *texture) diff --git a/interface/python_binding/pyktx/ktx_texture2.py b/interface/python_binding/pyktx/ktx_texture2.py index 4940a33abb..1b6c6c6ce8 100644 --- a/interface/python_binding/pyktx/ktx_texture2.py +++ b/interface/python_binding/pyktx/ktx_texture2.py @@ -1,6 +1,9 @@ # Copyright (c) 2023, Shukant Pal and Contributors # SPDX-License-Identifier: Apache-2.0 +from .khr_df_color_model import KhrDfColorModel +from .khr_df_primaries import KhrDfPrimaries +from .khr_df_transfer_function import KhrDfTransferFunction from .ktx_astc_params import KtxAstcParams from .ktx_basis_params import KtxBasisParams from .ktx_error_code import KtxErrorCode, KtxError @@ -71,10 +74,22 @@ def supercompression_scheme(self) -> KtxSupercmpScheme: return KtxSupercmpScheme(lib.PY_ktxTexture2_get_supercompressionScheme(self._ptr)) @property - def oetf(self) -> int: - """The opto-electrical transfer function of the images.""" + def color_model(self) -> KhrDfColorModel: + """The color model of the images.""" - return lib.ktxTexture2_GetOETF(self._ptr) + return KhrDfColorModel(lib.ktxTexture2_GetColorModel_e(self._ptr)) + + @property + def primaries(self) -> KhrDfPrimaries: + """The color primaries of the images.""" + + return KhrDfPrimaries(lib.ktxTexture2_GetPrimaries_e(self._ptr)) + + @property + def transfer_function(self) -> KhrDfTransferFunction: + """The transfer function of the images.""" + + return KhrDfTransferFunction(lib.ktxTexture2_GetTransferFunction_e(self._ptr)) @property def premultipled_alpha(self) -> bool: @@ -82,9 +97,21 @@ def premultipled_alpha(self) -> bool: return lib.ktxTexture2_GetPremultipliedAlpha(self._ptr) + @property + def is_hdr(self) -> bool: + """Whether the images are in an HDR format.""" + + return lib.ktxTexture_IsHDR(self._ptr) + + @property + def is_transcodable(self) -> bool: + """If the images are in a format that can be transcoded.""" + + return lib.ktxTexture2_IsTranscodable(self._ptr) + @property def needs_transcoding(self) -> bool: - """If the images are in a transcodable format.""" + """If the images are in a format that must be transcoded.""" return lib.ktxTexture2_NeedsTranscoding(self._ptr) @@ -135,7 +162,7 @@ def compress_basis(self, params: Union[int, KtxBasisParams]) -> None: params.quality_level = quality error = lib.PY_ktxTexture2_CompressBasisEx(self._ptr, - params.uastc, + params.codec, params.verbose, params.no_sse, params.thread_count, @@ -158,11 +185,34 @@ def compress_basis(self, params: Union[int, KtxBasisParams]) -> None: params.uastc_rdo_max_smooth_block_error_scale, params.uastc_rdo_max_smooth_block_std_dev, params.uastc_rdo_dont_favor_simpler_modes, - params.uastc_rdo_no_multithreading) + params.uastc_rdo_no_multithreading, + params.uastc_hdr_quality, + params.uastc_hdr_uber_mode, + params.uastc_hdr_ultra_quant, + params.uastc_hdr_favor_astc, + params.rec_2020, + params.uastc_hdr_lambda, + params.uastc_hdr_level + ) if int(error) != KtxErrorCode.SUCCESS: raise KtxError('ktxTexture2_CompressBasisEx', KtxErrorCode(error)) + def decode_astc(self) -> None: + """ + Decode a ktx2 texture object, if it is ASTC encoded. + + The decompressed format is calculated from corresponding ASTC format. + There are only 3 possible options currently supported. RGBA8, SRGBA8 + and RGBA32. + + Note that 3d textures are decoded to a multi-slice 3d texture. + """ + + error = lib.ktxTexture2_DecodeAstc(self._ptr); + if int(error) != KtxErrorCode.SUCCESS: + raise KtxError('ktx2_DecodeAstc', KtxErrorCode(error)) + def deflate_zstd(self, compression_level: int) -> None: """ Deflate the data in a ktxTexture2 object using Zstandard. diff --git a/lib/include/ktx.h b/lib/include/ktx.h index 964f3d7a3f..bdf810b61b 100644 --- a/lib/include/ktx.h +++ b/lib/include/ktx.h @@ -1573,26 +1573,33 @@ typedef struct ktxBasisParams { deterministic). */ ktx_uint32_t uastcHDRQuality; - /*!< UASTC HDR 4x4: Sets the UASTC HDR 4x4 compressor's level. Valid range is [0,4] - higher=slower but higher quality. HDR default=1. - Level 0=fastest/lowest quality, 3=highest practical setting, 4=exhaustive + /*!< UASTC HDR 4x4: Sets the UASTC HDR 4x4 compressor's level. + Valid range is [0,4] - higher=slower but higher quality. Default=1. + Level 0=fastest/lowest quality, 3=highest practical setting, 4=exhaustive */ ktx_bool_t uastcHDRUberMode; - /*!< UASTC HDR 4x4: Allow the UASTC HDR 4x4 encoder to try varying the CEM 11 selectors more for slightly higher quality (slower). This may negatively impact BC6H quality, however. + /*!< UASTC HDR 4x4: Allow the UASTC HDR 4x4 encoder to try varying the CEM 11 + selectors more for slightly higher quality (slower). This may negatively impact BC6H quality, however. */ ktx_bool_t uastcHDRUltraQuant; /*!< UASTC HDR 4x4: Try to find better quantized CEM 7/11 endpoint values (slower) */ ktx_bool_t uastcHDRFavorAstc; - /*!< UASTC HDR 4x4: By default the UASTC HDR 4x4 encoder tries to strike a balance or even slightly favor BC6H quality. If this option is specified, ASTC HDR 4x4 quality is favored instead. + /*!< UASTC HDR 4x4: By default the UASTC HDR 4x4 encoder tries to strike a balance + or even slightly favor BC6H quality. If this option is specified, ASTC HDR 4x4 quality is favored instead. */ ktx_bool_t rec2020; - /*!< UASTC HDR 6x6i specific option: The input image's gamut is Rec. 2020 vs. the default Rec. 709 - for accurate colorspace error calculations. + /*!< UASTC HDR 6x6i specific option: The input image's gamut is Rec. 2020 vs. the + default Rec. 709 - for accurate colorspace error calculations. */ float uastcHDRLambda; - /*!< UASTC HDR 6x6i specific option: Enables rate distortion optimization (RDO). The higher this value, the lower the quality, but the smaller the file size. Try 100-20000, or higher values on some images. + /*!< UASTC HDR 6x6i specific option: Enables rate distortion optimization (RDO). + The higher this value, the lower the quality, but the smaller the file size. Try 100-20000, or higher values + on some images. */ ktx_uint32_t uastcHDRLevel; - /*!< UASTC HDR 6x6i specific option: Controls the 6x6 HDR intermediate mode encoder performance vs. max quality tradeoff. X may range from [0,12]. Default level is 2. + /*!< UASTC HDR 6x6i specific option: Controls the 6x6 HDR intermediate mode encoder + performance vs. max quality tradeoff. X may range from [0,12]. Default level is 2. */ } ktxBasisParams; From e2c45fc4ba45192b9600b95f87f124bae56e5ebd Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Wed, 18 Mar 2026 22:47:33 +0900 Subject: [PATCH 02/13] Expose ktxTexture[12]_IsHDR. --- lib/include/ktx.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/include/ktx.h b/lib/include/ktx.h index bdf810b61b..235e12df1d 100644 --- a/lib/include/ktx.h +++ b/lib/include/ktx.h @@ -1061,6 +1061,9 @@ ktxTexture1_CreateFromStream(ktxStream* stream, KTX_API void KTX_APIENTRY ktxTexture1_Destroy(ktxTexture1* This); +KTX_API ktx_bool_t KTX_APIENTRY +ktxTexture1_IsHDR(ktxTexture1* This); + KTX_API ktx_bool_t KTX_APIENTRY ktxTexture1_NeedsTranscoding(ktxTexture1* This); @@ -1180,6 +1183,9 @@ ktxTexture2_GetPremultipliedAlpha(ktxTexture2* This); KTX_API khr_df_primaries_e KTX_APIENTRY ktxTexture2_GetPrimaries_e(ktxTexture2* This); +KTX_API ktx_bool_t KTX_APIENTRY +ktxTexture2_IsHDR(ktxTexture2* This); + KTX_API ktx_bool_t KTX_APIENTRY ktxTexture2_NeedsTranscoding(ktxTexture2* This); From 47345553a97efa390622901e9b9e7a3d3bea362f Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Wed, 18 Mar 2026 22:48:37 +0900 Subject: [PATCH 03/13] Remove prefices from enumeration names. --- .../pyktx/khr_df_color_model.py | 86 +++++++++--------- .../python_binding/pyktx/khr_df_primaries.py | 32 +++---- .../pyktx/khr_df_transfer_function.py | 88 +++++++++---------- 3 files changed, 103 insertions(+), 103 deletions(-) diff --git a/interface/python_binding/pyktx/khr_df_color_model.py b/interface/python_binding/pyktx/khr_df_color_model.py index 501460d70c..cfe5d4bc55 100644 --- a/interface/python_binding/pyktx/khr_df_color_model.py +++ b/interface/python_binding/pyktx/khr_df_color_model.py @@ -6,57 +6,57 @@ class KhrDfColorModel(IntEnum): """Model in which the color coordinate space is defined.""" - KHR_DF_MODEL_UNSPECIFIED = 0 + UNSPECIFIED = 0 """No interpretation of color channels defined.""" - KHR_DF_MODEL_RGBSDA = 1 + RGBSDA = 1 """Color primaries (red, green, blue) + alpha, depth and stencil.""" - KHR_DF_MODEL_YUVSDA = 2 + YUVSDA = 2 """Color differences (Y', Cb, Cr) + alpha, depth and stencil.""" - KHR_DF_MODEL_YIQSDA = 3 + YIQSDA = 3 """Color differences (Y', I, Q) + alpha, depth and stencil.""" - KHR_DF_MODEL_LABSDA = 4 + LABSDA = 4 """Perceptual color (CIE L*a*b*) + alpha, depth and stencil.""" - KHR_DF_MODEL_CMYKA = 5 + CMYKA = 5 """Subtractive colors (cyan, magenta, yellow, black) + alpha.""" - KHR_DF_MODEL_XYZW = 6 + XYZW = 6 """Non-color coordinate data (X, Y, Z, W).""" - KHR_DF_MODEL_HSVA_ANG = 7 + HSVA_ANG = 7 """Hue, saturation, value, hue angle on color circle, plus alpha.""" - KHR_DF_MODEL_HSLA_ANG = 8 + HSLA_ANG = 8 """Hue, saturation, lightness, hue angle on color circle, plus alpha.""" - KHR_DF_MODEL_HSVA_HEX = 9 + HSVA_HEX = 9 """Hue, saturation, value, hue on color hexagon, plus alpha.""" - KHR_DF_MODEL_HSLA_HEX = 10 + HSLA_HEX = 10 """Hue, saturation, lightness, hue on color hexagon, plus alpha.""" - KHR_DF_MODEL_YCGCOA = 11 + YCGCOA = 11 """Lightweight approximate color difference (luma, orange, green).""" - KHR_DF_MODEL_YCCBCCRC = 12 + YCCBCCRC = 12 """ITU BT.2020 constant luminance YcCbcCrc.""" - KHR_DF_MODEL_ICTCP = 13 + ICTCP = 13 """ITU BT.2100 constant intensity ICtCp.""" - KHR_DF_MODEL_CIEXYZ = 14 + CIEXYZ = 14 """CIE 1931 XYZ color coordinates (X, Y, Z).""" - KHR_DF_MODEL_CIEXYY = 15 + CIEXYY = 15 """CIE 1931 xyY color coordinates (X, Y, Y).""" - KHR_DF_MODEL_DXT1A = 128 - KHR_DF_MODEL_BC1A = 128 + DXT1A = 128 + BC1A = 128 """Compressed formats start at 128.""" """ Direct3D (and S3) compressed formats. @@ -65,39 +65,39 @@ class KhrDfColorModel(IntEnum): DXT1/BC1 with a cosited alpha sample is transparent. """ - KHR_DF_MODEL_DXT2 = 129 - KHR_DF_MODEL_DXT3 = 129 - KHR_DF_MODEL_BC2 = 129 + DXT2 = 129 + DXT3 = 129 + BC2 = 129 """DXT2/DXT3/BC2, with explicit 4-bit alpha.""" - KHR_DF_MODEL_DXT4 = 130 - KHR_DF_MODEL_DXT5 = 130 - KHR_DF_MODEL_BC3 = 130 + DXT4 = 130 + DXT5 = 130 + BC3 = 130 """DXT4/DXT5/BC3, with interpolated alpha.""" - KHR_DF_MODEL_ATI1N = 131 - KHR_DF_MODEL_DXT5A = 131 - KHR_DF_MODEL_BC4 = 131 + ATI1N = 131 + DXT5A = 131 + BC4 = 131 """ATI1n/DXT5A/BC4 - single channel interpolated 8-bit data. (The UNORM/SNORM variation is recorded in the channel data).""" - KHR_DF_MODEL_ATI2N_XY = 132 - KHR_DF_MODEL_DXN = 132 - KHR_DF_MODEL_BC5 = 132 + ATI2N_XY = 132 + DXN = 132 + BC5 = 132 """ATI2n_XY/DXN/BC5 - two channel interpolated 8-bit data. (The UNORM/SNORM variation is recorded in the channel data).""" - KHR_DF_MODEL_BC6H = 133 + BC6H = 133 """BC6H - DX11 format for 16-bit float channels.""" - KHR_DF_MODEL_BC7 = 134 + BC7 = 134 """BC7 - DX11 format.""" - KHR_DF_MODEL_ETC1 = 160 + ETC1 = 160 """A format of ETC1 indicates that the format shall be decodable by an ETC1-compliant decoder and not rely on ETC2 features.""" - KHR_DF_MODEL_ETC2 = 161 + ETC2 = 161 """A format of ETC2 is permitted to use ETC2 encodings on top of the baseline ETC1 specification. The ETC2 format has channels "red", "green", "RGB" and "alpha", @@ -105,22 +105,22 @@ class KhrDfColorModel(IntEnum): Punch-through alpha can be distinguished from full alpha by the plane size in bytes required for the texel block.""" - KHR_DF_MODEL_ASTC = 162 + ASTC = 162 """Adaptive Scalable Texture Compression.""" """ASTC HDR vs LDR is determined by the float flag in the channel.""" """ASTC block size can be distinguished by texel block size.""" - KHR_DF_MODEL_ETC1S = 163 + ETC1S = 163 """ETC1S is a simplified subset of ETC1.""" - KHR_DF_MODEL_PVRTC = 164 - KHR_DF_MODEL_PVRTC2 = 165 + PVRTC = 164 + PVRTC2 = 165 """PowerVR Texture Compression.""" - KHR_DF_MODEL_UASTC = 166 - KHR_DF_MODEL_UASTC_LDR_4x4 = 166 - KHR_DF_MODEL_UASTC_HDR_4x4 = 167 - KHR_DF_MODEL_UASTC_HDR_6x6 = 168 + UASTC = 166 + UASTC_LDR_4x4 = 166 + UASTC_HDR_4x4 = 167 + UASTC_HDR_6x6 = 168 """UASTC for BASIS supercompression.""" - KHR_DF_MODEL_MAX = 0xFF + MAX = 0xFF diff --git a/interface/python_binding/pyktx/khr_df_primaries.py b/interface/python_binding/pyktx/khr_df_primaries.py index ba8f669f33..ef20144a9f 100644 --- a/interface/python_binding/pyktx/khr_df_primaries.py +++ b/interface/python_binding/pyktx/khr_df_primaries.py @@ -6,46 +6,46 @@ class KhrDfPrimaries(IntEnum): """The primary colors of an image.""" - KHR_DF_PRIMARIES_UNSPECIFIED = 0 + UNSPECIFIED = 0 """No color primaries defined""" - KHR_DF_PRIMARIES_BT709 = 1 + BT709 = 1 """Color primaries of ITU-R BT.709 and sRGB""" - KHR_DF_PRIMARIES_SRGB = 1 - """Synonym for KHR_DF_PRIMARIES_BT709""" + SRGB = 1 + """Synonym for BT709""" - KHR_DF_PRIMARIES_BT601_EBU = 2 + BT601_EBU = 2 """Color primaries of ITU-R BT.601 (625-line EBU variant)""" - KHR_DF_PRIMARIES_BT601_SMPTE = 3 + BT601_SMPTE = 3 """Color primaries of ITU-R BT.601 (525-line SMPTE C variant)""" - KHR_DF_PRIMARIES_BT2020 = 4 + BT2020 = 4 """Color primaries of ITU-R BT.2020""" - KHR_DF_PRIMARIES_BT2100 = 4 + BT2100 = 4 """ITU-R BT.2100 uses the same primaries as BT.2020""" - KHR_DF_PRIMARIES_CIEXYZ = 5 + CIEXYZ = 5 """CIE theoretical color coordinate space""" - KHR_DF_PRIMARIES_ACES = 6 + ACES = 6 """Academy Color Encoding System primaries""" - KHR_DF_PRIMARIES_ACESCC = 7 + ACESCC = 7 """Color primaries of ACEScc""" - KHR_DF_PRIMARIES_NTSC1953 = 8 + NTSC1953 = 8 """Legacy NTSC 1953 primaries""" - KHR_DF_PRIMARIES_PAL525 = 9 + PAL525 = 9 """Legacy PAL 525-line primaries""" - KHR_DF_PRIMARIES_DISPLAYP3 = 10 + DISPLAYP3 = 10 """Color primaries of Display P3""" - KHR_DF_PRIMARIES_ADOBERGB = 11 + ADOBERGB = 11 """Color primaries of Adobe RGB (1998)""" - KHR_DF_PRIMARIES_MAX = 0xFF + MAX = 0xFF diff --git a/interface/python_binding/pyktx/khr_df_transfer_function.py b/interface/python_binding/pyktx/khr_df_transfer_function.py index 716b43a2b0..633e05c798 100644 --- a/interface/python_binding/pyktx/khr_df_transfer_function.py +++ b/interface/python_binding/pyktx/khr_df_transfer_function.py @@ -6,89 +6,89 @@ class KhrDfTransferFunction(IntEnum): """The transfer function of an image.""" - TRANSFER_UNSPECIFIED = 0 + UNSPECIFIED = 0 """No transfer function defined.""" - TRANSFER_LINEAR = 1 + LINEAR = 1 """Linear transfer function (value proportional to intensity.""" - TRANSFER_SRGB = 2 - TRANSFER_SRGB_EOTF = 2 - TRANSFER_SCRGB = 2 - TRANSFER_SCRGB_EOTF = 2 + SRGB = 2 + SRGB_EOTF = 2 + SCRGB = 2 + SCRGB_EOTF = 2 """Perceptually-linear transfer function of sRGB (~2.2); also used for scRGB.""" - TRANSFER_ITU = 3 - TRANSFER_ITU_OETF = 3 - TRANSFER_BT601 = 3 - TRANSFER_BT601_OETF = 3 - TRANSFER_BT709 = 3 - TRANSFER_BT709_OETF = 3 - TRANSFER_BT2020 = 3 - TRANSFER_BT2020_OETF = 3 + ITU = 3 + ITU_OETF = 3 + BT601 = 3 + BT601_OETF = 3 + BT709 = 3 + BT709_OETF = 3 + BT2020 = 3 + BT2020_OETF = 3 """Perceptually-linear transfer function of ITU BT.601, BT.709 and BT.2020 (~1/.45).""" - TRANSFER_SMTPE170M = 3 - TRANSFER_SMTPE170M_OETF = 3 - TRANSFER_SMTPE170M_EOTF = 3 + SMTPE170M = 3 + SMTPE170M_OETF = 3 + SMTPE170M_EOTF = 3 """SMTPE170M (digital NTSC) defines an alias for the ITU transfer function (~1/.45) and a linear OOTF.""" - TRANSFER_NTSC = 4 - TRANSFER_NTSC_EOTF = 4 + NTSC = 4 + NTSC_EOTF = 4 """Perceptually-linear gamma function of original NTSC (simple 2.2 gamma).""" - TRANSFER_SLOG = 5 - TRANSFER_SLOG_OETF = 5 + SLOG = 5 + SLOG_OETF = 5 """Sony S-log used by Sony video cameras.""" - TRANSFER_SLOG2 = 6 - TRANSFER_SLOG2_OETF = 6 + SLOG2 = 6 + SLOG2_OETF = 6 """Sony S-log 2 used by Sony video cameras.""" - TRANSFER_BT1886 = 7 - TRANSFER_BT1886_EOTF = 7 + BT1886 = 7 + BT1886_EOTF = 7 """ITU BT.1886 EOTF.""" - TRANSFER_HLG_OETF = 8 + HLG_OETF = 8 """ITU BT.2100 HLG OETF (typical scene-referred content), linear light normalized 0..1.""" - TRANSFER_HLG_EOTF = 9 + HLG_EOTF = 9 """ITU BT.2100 HLG EOTF (nominal HDR display of HLG content), linear light normalized 0..1.""" - TRANSFER_PQ_EOTF = 10 + PQ_EOTF = 10 """ITU BT.2100 PQ EOTF (typical HDR display-referred PQ content).""" - TRANSFER_PQ_OETF = 11 + PQ_OETF = 11 """ITU BT.2100 PQ OETF (nominal scene described by PQ HDR content).""" - TRANSFER_DCIP3 = 12 - TRANSFER_DCIP3_EOTF = 12 + DCIP3 = 12 + DCIP3_EOTF = 12 """DCI P3 transfer function.""" - TRANSFER_PAL_OETF = 13 + PAL_OETF = 13 """Legacy PAL OETF.""" - TRANSFER_PAL625_EOTF = 14 + PAL625_EOTF = 14 """Legacy PAL 625-line EOTF.""" - TRANSFER_ST240 = 15 - TRANSFER_ST240_OETF = 15 - TRANSFER_ST240_EOTF = 15 + ST240 = 15 + ST240_OETF = 15 + ST240_EOTF = 15 """Legacy ST240 transfer function.""" - TRANSFER_ACESCC = 16 - TRANSFER_ACESCC_OETF = 16 + ACESCC = 16 + ACESCC_OETF = 16 """ACEScc transfer function.""" - TRANSFER_ACESCCT = 17 - TRANSFER_ACESCCT_OETF = 17 + ACESCCT = 17 + ACESCCT_OETF = 17 """ACEScct transfer function.""" - TRANSFER_ADOBERGB = 18 - TRANSFER_ADOBERGB_EOTF = 18 + ADOBERGB = 18 + ADOBERGB_EOTF = 18 """Adobe RGB (1998) transfer function.""" - TRANSFER_HLG_UNNORMALIZED_OETF = 19 + HLG_UNNORMALIZED_OETF = 19 """Legacy ITU BT.2100 HLG OETF (typical scene-referred content), linear light normalized 0..12.""" - TRANSFER_MAX = 0xFF + MAX = 0xFF From d3caaf87c2ac6ced80318bf3c3f62a194f4f12f7 Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Wed, 18 Mar 2026 22:50:36 +0900 Subject: [PATCH 04/13] Move properties from virtual functions to ktx_texture. --- interface/python_binding/buildscript.py | 4 ++-- interface/python_binding/pyktx/ktx_texture.py | 18 ++++++++++++++++++ interface/python_binding/pyktx/ktx_texture2.py | 18 ------------------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/interface/python_binding/buildscript.py b/interface/python_binding/buildscript.py index 1b63334b54..5e281fd296 100644 --- a/interface/python_binding/buildscript.py +++ b/interface/python_binding/buildscript.py @@ -82,6 +82,8 @@ void *src, size_t srcSize); bool ktxTexture_IsHDR(ktxTexture *); + bool ktxTexture_IsTranscodable(ktxTexture *); + bool ktxTexture_NeedsTranscoding(ktxTexture *); int ktxTexture2_DecodeAstc(void *); int ktxTexture2_TranscodeBasis(void *, int outputFormat, int transcodeFlags); int ktxTexture2_DeflateZstd(void *, uint32_t compressionLevel); @@ -89,8 +91,6 @@ uint32_t ktxTexture2_GetPrimaries_e(void *); uint32_t ktxTexture2_GetTransferFunction_e(void *); bool ktxTexture2_GetPremultipliedAlpha(void *); - bool ktxTexture2_IsTranscodable(void *); - bool ktxTexture2_NeedsTranscoding(void *); int ktxHashList_AddKVPair(ktxHashList *, const char *key, unsigned int valueLen, const void *value); int ktxHashList_DeleteKVPair(ktxHashList *, const char *key); diff --git a/interface/python_binding/pyktx/ktx_texture.py b/interface/python_binding/pyktx/ktx_texture.py index 2da6b15c52..4c46167adf 100644 --- a/interface/python_binding/pyktx/ktx_texture.py +++ b/interface/python_binding/pyktx/ktx_texture.py @@ -140,6 +140,24 @@ def data_size_uncompressed(self) -> int: return lib.ktxTexture_GetDataSizeUncompressed(self._ptr) + @property + def is_hdr(self) -> bool: + """Whether the images are in an HDR format.""" + + return lib.ktxTexture_IsHDR(self._ptr) + + @property + def is_transcodable(self) -> bool: + """If the images are in a format that can be transcoded.""" + + return lib.ktxTexture_IsTranscodable(self._ptr) + + @property + def needs_transcoding(self) -> bool: + """If the images are in a format that must be transcoded.""" + + return lib.ktxTexture_NeedsTranscoding(self._ptr) + def row_pitch(self, level: int) -> int: """ Return pitch between rows of a texture image level in bytes. diff --git a/interface/python_binding/pyktx/ktx_texture2.py b/interface/python_binding/pyktx/ktx_texture2.py index 1b6c6c6ce8..4894c06bc2 100644 --- a/interface/python_binding/pyktx/ktx_texture2.py +++ b/interface/python_binding/pyktx/ktx_texture2.py @@ -97,24 +97,6 @@ def premultipled_alpha(self) -> bool: return lib.ktxTexture2_GetPremultipliedAlpha(self._ptr) - @property - def is_hdr(self) -> bool: - """Whether the images are in an HDR format.""" - - return lib.ktxTexture_IsHDR(self._ptr) - - @property - def is_transcodable(self) -> bool: - """If the images are in a format that can be transcoded.""" - - return lib.ktxTexture2_IsTranscodable(self._ptr) - - @property - def needs_transcoding(self) -> bool: - """If the images are in a format that must be transcoded.""" - - return lib.ktxTexture2_NeedsTranscoding(self._ptr) - def compress_astc(self, params: Union[int, KtxAstcParams]) -> None: """ Encode and compress a ktx texture with uncompressed images to ASTC. From b362987d9242e43bbb110ff5f904a4a9674fedca Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Wed, 18 Mar 2026 22:51:43 +0900 Subject: [PATCH 05/13] Add simple test of property queries. --- interface/python_binding/tests/test_ktx_texture2.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/interface/python_binding/tests/test_ktx_texture2.py b/interface/python_binding/tests/test_ktx_texture2.py index 90b92a3b95..606c2fd125 100644 --- a/interface/python_binding/tests/test_ktx_texture2.py +++ b/interface/python_binding/tests/test_ktx_texture2.py @@ -103,3 +103,13 @@ def test_create(self): texture = KtxTexture2.create(info, KtxTextureCreateStorage.ALLOC) self.assertEqual(texture.vk_format, VkFormat.VK_FORMAT_ASTC_4x4_SRGB_BLOCK) texture.set_image_from_memory(0, 0, 0, bytes(texture.data_size)) + + def test_queries(self): + test_ktx_file = os.path.join(__test_images__, 'ktx2/alpha_simple_blze.ktx2') + texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) + self.assertTrue(texture.needs_transcoding) + self.assertTrue(texture.is_transcodable) + self.assertFalse(texture.is_hdr); + self.assertEqual(texture.color_model, KhrDfColorModel.ETC1S) + self.assertEqual(texture.primaries, KhrDfPrimaries.BT709) + self.assertEqual(texture.transfer_function, KhrDfTransferFunction.SRGB) From 17accb30e66570467dc7563b27c4197766a38e0a Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Thu, 19 Mar 2026 11:23:16 +0900 Subject: [PATCH 06/13] Match khr_df.h names. --- .../pyktx/{khr_df_color_model.py => khr_df_model.py} | 2 +- ...hr_df_transfer_function.py => khr_df_transfer.py} | 2 +- interface/python_binding/pyktx/ktx_texture2.py | 12 ++++++------ interface/python_binding/tests/test_ktx_texture2.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) rename interface/python_binding/pyktx/{khr_df_color_model.py => khr_df_model.py} (99%) rename interface/python_binding/pyktx/{khr_df_transfer_function.py => khr_df_transfer.py} (98%) diff --git a/interface/python_binding/pyktx/khr_df_color_model.py b/interface/python_binding/pyktx/khr_df_model.py similarity index 99% rename from interface/python_binding/pyktx/khr_df_color_model.py rename to interface/python_binding/pyktx/khr_df_model.py index cfe5d4bc55..2693c96c5b 100644 --- a/interface/python_binding/pyktx/khr_df_color_model.py +++ b/interface/python_binding/pyktx/khr_df_model.py @@ -3,7 +3,7 @@ from enum import IntEnum -class KhrDfColorModel(IntEnum): +class KhrDfModel(IntEnum): """Model in which the color coordinate space is defined.""" UNSPECIFIED = 0 diff --git a/interface/python_binding/pyktx/khr_df_transfer_function.py b/interface/python_binding/pyktx/khr_df_transfer.py similarity index 98% rename from interface/python_binding/pyktx/khr_df_transfer_function.py rename to interface/python_binding/pyktx/khr_df_transfer.py index 633e05c798..140613fce2 100644 --- a/interface/python_binding/pyktx/khr_df_transfer_function.py +++ b/interface/python_binding/pyktx/khr_df_transfer.py @@ -3,7 +3,7 @@ from enum import IntEnum -class KhrDfTransferFunction(IntEnum): +class KhrDfTransfer(IntEnum): """The transfer function of an image.""" UNSPECIFIED = 0 diff --git a/interface/python_binding/pyktx/ktx_texture2.py b/interface/python_binding/pyktx/ktx_texture2.py index 4894c06bc2..5efc4e36d5 100644 --- a/interface/python_binding/pyktx/ktx_texture2.py +++ b/interface/python_binding/pyktx/ktx_texture2.py @@ -1,9 +1,9 @@ # Copyright (c) 2023, Shukant Pal and Contributors # SPDX-License-Identifier: Apache-2.0 -from .khr_df_color_model import KhrDfColorModel +from .khr_df_model import KhrDfModel from .khr_df_primaries import KhrDfPrimaries -from .khr_df_transfer_function import KhrDfTransferFunction +from .khr_df_transfer import KhrDfTransfer from .ktx_astc_params import KtxAstcParams from .ktx_basis_params import KtxBasisParams from .ktx_error_code import KtxErrorCode, KtxError @@ -74,10 +74,10 @@ def supercompression_scheme(self) -> KtxSupercmpScheme: return KtxSupercmpScheme(lib.PY_ktxTexture2_get_supercompressionScheme(self._ptr)) @property - def color_model(self) -> KhrDfColorModel: + def color_model(self) -> KhrDfModel: """The color model of the images.""" - return KhrDfColorModel(lib.ktxTexture2_GetColorModel_e(self._ptr)) + return KhrDfModel(lib.ktxTexture2_GetColorModel_e(self._ptr)) @property def primaries(self) -> KhrDfPrimaries: @@ -86,10 +86,10 @@ def primaries(self) -> KhrDfPrimaries: return KhrDfPrimaries(lib.ktxTexture2_GetPrimaries_e(self._ptr)) @property - def transfer_function(self) -> KhrDfTransferFunction: + def transfer_function(self) -> KhrDfTransfer: """The transfer function of the images.""" - return KhrDfTransferFunction(lib.ktxTexture2_GetTransferFunction_e(self._ptr)) + return KhrDfTransfer(lib.ktxTexture2_GetTransferFunction_e(self._ptr)) @property def premultipled_alpha(self) -> bool: diff --git a/interface/python_binding/tests/test_ktx_texture2.py b/interface/python_binding/tests/test_ktx_texture2.py index 606c2fd125..bcb7b36b84 100644 --- a/interface/python_binding/tests/test_ktx_texture2.py +++ b/interface/python_binding/tests/test_ktx_texture2.py @@ -110,6 +110,6 @@ def test_queries(self): self.assertTrue(texture.needs_transcoding) self.assertTrue(texture.is_transcodable) self.assertFalse(texture.is_hdr); - self.assertEqual(texture.color_model, KhrDfColorModel.ETC1S) + self.assertEqual(texture.color_model, KhrDfModel.ETC1S) self.assertEqual(texture.primaries, KhrDfPrimaries.BT709) - self.assertEqual(texture.transfer_function, KhrDfTransferFunction.SRGB) + self.assertEqual(texture.transfer_function, KhrDfTransfer.SRGB) From d3c1e972989df2c69df6be41be1982d619d0b94c Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Thu, 19 Mar 2026 11:53:45 +0900 Subject: [PATCH 07/13] Add num_layers property. --- interface/python_binding/buildscript.py | 1 + interface/python_binding/pyktx/ktx_texture.c | 1 + interface/python_binding/pyktx/ktx_texture.h | 1 + interface/python_binding/pyktx/ktx_texture.py | 6 ++++++ 4 files changed, 9 insertions(+) diff --git a/interface/python_binding/buildscript.py b/interface/python_binding/buildscript.py index 5e281fd296..c7d8d681e8 100644 --- a/interface/python_binding/buildscript.py +++ b/interface/python_binding/buildscript.py @@ -113,6 +113,7 @@ uint32_t PY_ktxTexture_get_baseHeight(ktxTexture *); uint32_t PY_ktxTexture_get_baseDepth(ktxTexture *); uint32_t PY_ktxTexture_get_numDimensions(ktxTexture *); + uint32_t PY_ktxTexture_get_numLayers(ktxTexture *); uint32_t PY_ktxTexture_get_numLevels(ktxTexture *); uint32_t PY_ktxTexture_get_numFaces(ktxTexture *); uint32_t PY_ktxTexture_get_kvDataLen(ktxTexture *); diff --git a/interface/python_binding/pyktx/ktx_texture.c b/interface/python_binding/pyktx/ktx_texture.c index 2479c996b6..f6bb0e9f05 100644 --- a/interface/python_binding/pyktx/ktx_texture.c +++ b/interface/python_binding/pyktx/ktx_texture.c @@ -88,6 +88,7 @@ KTX_IMPL(ktx_uint32_t, baseWidth); KTX_IMPL(ktx_uint32_t, baseHeight); KTX_IMPL(ktx_uint32_t, baseDepth); KTX_IMPL(ktx_uint32_t, numDimensions); +KTX_IMPL(ktx_uint32_t, numLayers); KTX_IMPL(ktx_uint32_t, numLevels); KTX_IMPL(ktx_uint32_t, numFaces); KTX_IMPL(ktx_uint32_t, kvDataLen); diff --git a/interface/python_binding/pyktx/ktx_texture.h b/interface/python_binding/pyktx/ktx_texture.h index 9be170c263..c3f75a01df 100644 --- a/interface/python_binding/pyktx/ktx_texture.h +++ b/interface/python_binding/pyktx/ktx_texture.h @@ -46,6 +46,7 @@ KTX_GETTER(ktx_uint32_t, baseWidth); KTX_GETTER(ktx_uint32_t, baseHeight); KTX_GETTER(ktx_uint32_t, baseDepth); KTX_GETTER(ktx_uint32_t, numDimensions); +KTX_GETTER(ktx_uint32_t, numLayers); KTX_GETTER(ktx_uint32_t, numLevels); KTX_GETTER(ktx_uint32_t, numFaces); KTX_GETTER(ktx_uint32_t, kvDataLen); diff --git a/interface/python_binding/pyktx/ktx_texture.py b/interface/python_binding/pyktx/ktx_texture.py index 4c46167adf..986ca042a7 100644 --- a/interface/python_binding/pyktx/ktx_texture.py +++ b/interface/python_binding/pyktx/ktx_texture.py @@ -85,6 +85,12 @@ def num_dimensions(self) -> int: return lib.PY_ktxTexture_get_numDimensions(self._ptr) + @property + def num_layers(self) -> int: + """Number of layers in the texture.""" + + return lib.PY_ktxTexture_get_numLayers(self._ptr) + @property def num_levels(self) -> int: """Number of mip levels in the texture.""" From 603d6f62442a0ea6318edac882fa747ea93733f7 Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Thu, 19 Mar 2026 12:29:22 +0900 Subject: [PATCH 08/13] Fix CompressBasisEx interface. --- interface/python_binding/buildscript.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/python_binding/buildscript.py b/interface/python_binding/buildscript.py index c7d8d681e8..13686bb914 100644 --- a/interface/python_binding/buildscript.py +++ b/interface/python_binding/buildscript.py @@ -165,7 +165,7 @@ bool perceptual, char *inputSwizzle); int PY_ktxTexture2_CompressBasisEx(void *texture, - bool uastc, + uint32_t codec, bool verbose, bool noSSE, uint32_t threadCount, From 558aa9e7eeff9770fa82061d12f94d3cfbc928af Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Thu, 19 Mar 2026 12:30:35 +0900 Subject: [PATCH 09/13] Add tests. Now tests queries, astc compress, with and without params,astc decode and basis compress with and without params. --- .../python_binding/tests/test_ktx_texture2.py | 68 ++++++++++++++++++- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/interface/python_binding/tests/test_ktx_texture2.py b/interface/python_binding/tests/test_ktx_texture2.py index bcb7b36b84..707f22c5c5 100644 --- a/interface/python_binding/tests/test_ktx_texture2.py +++ b/interface/python_binding/tests/test_ktx_texture2.py @@ -11,8 +11,9 @@ def test_create_from_named_file(self): test_ktx_file = os.path.join(__test_images__, 'ktx2/alpha_complex_straight.ktx2') texture = KtxTexture2.create_from_named_file(test_ktx_file) - self.assertEqual(texture.num_levels, 1) self.assertEqual(texture.num_faces, 1) + self.assertEqual(texture.num_layers, 1) + self.assertEqual(texture.num_levels, 1) self.assertEqual(texture.vk_format, VkFormat.VK_FORMAT_R8G8B8A8_SRGB) self.assertEqual(texture.base_width, 256) self.assertEqual(texture.base_height, 256) @@ -23,6 +24,7 @@ def test_create_from_named_file_mipmapped(self): texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.NO_FLAGS) self.assertEqual(texture.vk_format, VkFormat.VK_FORMAT_ASTC_8x8_SRGB_BLOCK) + self.assertEqual(texture.num_layers, 1) self.assertEqual(texture.num_levels, 11) self.assertEqual(texture.base_width, 1024) self.assertEqual(texture.base_height, 1024) @@ -76,14 +78,30 @@ def test_compress_basis(self): test_ktx_file = os.path.join(__test_images__, 'ktx2/r8g8b8a8_srgb_array_7_mip.ktx2') texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) + self.assertEqual(texture.color_model, KhrDfModel.RGBSDA) self.assertEqual(texture.is_compressed, False) self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.NONE) - texture.compress_basis(KtxBasisParams(quality_level=1)) + texture.compress_basis(1) + self.assertEqual(texture.color_model, KhrDfModel.ETC1S) self.assertEqual(texture.is_compressed, True) self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.BASIS_LZ) + def test_compress_basis_with_params(self): + test_ktx_file = os.path.join(__test_images__, 'ktx2/r8g8b8a8_srgb_array_7_mip.ktx2') + texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) + + self.assertEqual(texture.color_model, KhrDfModel.RGBSDA) + self.assertEqual(texture.is_compressed, False) + self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.NONE) + + texture.compress_basis(KtxBasisParams(codec=KtxBasisCodec.UASTC_LDR_4x4,uastc_rdo=True)) + + self.assertEqual(texture.color_model, KhrDfModel.UASTC) + self.assertEqual(texture.is_compressed, True) + self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.NONE) + def test_transcode_basis(self): test_ktx_file = os.path.join(__test_images__, 'ktx2/color_grid_blze.ktx2') texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) @@ -110,6 +128,52 @@ def test_queries(self): self.assertTrue(texture.needs_transcoding) self.assertTrue(texture.is_transcodable) self.assertFalse(texture.is_hdr); + self.assertFalse(texture.premultipled_alpha) self.assertEqual(texture.color_model, KhrDfModel.ETC1S) self.assertEqual(texture.primaries, KhrDfPrimaries.BT709) self.assertEqual(texture.transfer_function, KhrDfTransfer.SRGB) + + def test_compress_astc(self): + test_ktx_file = os.path.join(__test_images__, 'ktx2/r8g8b8a8_srgb_array_7_mip.ktx2') + texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) + self.assertFalse(texture.is_compressed) + self.assertEqual(texture.num_layers, 7) + self.assertEqual(texture.transfer_function, KhrDfTransfer.SRGB) + texture.compress_astc(KtxPackAstcQualityLevels.FAST) + self.assertTrue(texture.is_compressed) + self.assertEqual(texture.color_model, KhrDfModel.ASTC) + self.assertEqual(texture.num_layers, 7) + self.assertEqual(texture.transfer_function, KhrDfTransfer.SRGB) + + def test_compress_astc_with_params(self): + test_ktx_file = os.path.join(__test_images__, 'ktx2/r8g8b8a8_srgb_array_7_mip.ktx2') + texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) + self.assertFalse(texture.is_compressed) + self.assertEqual(texture.num_layers, 7) + self.assertEqual(texture.transfer_function, KhrDfTransfer.SRGB) + texture.compress_astc(KtxAstcParams(quality_level=KtxPackAstcQualityLevels.FAST, + block_dimension=KtxPackAstcBlockDimension.D8x8)) + self.assertTrue(texture.is_compressed) + self.assertEqual(texture.color_model, KhrDfModel.ASTC) + self.assertEqual(texture.num_layers, 7) + self.assertEqual(texture.transfer_function, KhrDfTransfer.SRGB) + + def test_decode_astc(self): + test_ktx_file = os.path.join(__test_images__, 'ktx2/astc_8x8_unorm_array_7.ktx2') + texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) + self.assertEqual(texture.color_model, KhrDfModel.ASTC) + self.assertTrue(texture.is_compressed) + self.assertTrue(texture.is_array) + self.assertFalse(texture.is_hdr); + self.assertFalse(texture.premultipled_alpha) + self.assertEqual(texture.num_layers, 7) + self.assertFalse(texture.needs_transcoding) + texture.decode_astc(); + self.assertFalse(texture.is_compressed) + self.assertEqual(texture.color_model, KhrDfModel.RGBSDA) + self.assertEqual(texture.transfer_function, KhrDfTransfer.LINEAR) + self.assertTrue(texture.is_array) + self.assertFalse(texture.is_hdr); + self.assertFalse(texture.premultipled_alpha) + self.assertEqual(texture.num_layers, 7) + self.assertFalse(texture.needs_transcoding) From 833beadcef3e37450fc482ded386211f11c3fc94 Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Thu, 19 Mar 2026 18:01:08 +0900 Subject: [PATCH 10/13] Polish the documentation. --- interface/python_binding/conf.py | 1 + .../python_binding/pyktx/ktx_basis_params.py | 39 ++++++++++--------- interface/python_binding/pyktx/ktx_texture.py | 9 ++++- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/interface/python_binding/conf.py b/interface/python_binding/conf.py index a9ed752eae..16961d64ee 100644 --- a/interface/python_binding/conf.py +++ b/interface/python_binding/conf.py @@ -41,6 +41,7 @@ 'sphinx.ext.napoleon', ] +autodoc_member_order = 'bysource' templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] diff --git a/interface/python_binding/pyktx/ktx_basis_params.py b/interface/python_binding/pyktx/ktx_basis_params.py index be865b4f8e..f35fb6a13a 100644 --- a/interface/python_binding/pyktx/ktx_basis_params.py +++ b/interface/python_binding/pyktx/ktx_basis_params.py @@ -7,10 +7,10 @@ @dataclass class KtxBasisParams: - """Data for passing extended params to KtxTexture2.compressBasis().""" + """Struct for passing extended params to KtxTexture2.compressBasis().""" codec: int = KtxBasisCodec.ETC1S - """Basis Universal codec to use. Default is ETC1S/BasisLZ.""" + """Basis Universal codec to use. Default is KtxBasisCodec.ETC1S/BasisLZ.""" verbose: bool = False """If true, prints Basis Universal encoder operation details to stdout. Not recommended for GUI apps.""" @@ -21,9 +21,12 @@ class KtxBasisParams: thread_count: int = 1 """Number of threads used for compression. Default is 1.""" + # Here and in the descriptions of all other codec specific options we abuse the + # fact that ':'in a docstring is used to indicate the preceding text is a + # type so that the target codec is clearly flagged on a separate "Type: " line. etc1s_compression_level: int = 0 """ - Encoding speed vs. quality tradeoff for etc1s. Range is [0,5]. + ETC1S/BasisLZ: Encoding speed vs. quality tradeoff for etc1s. Range is [0,5]. Higher values are slower, but give higher quality. There is no default. Callers must explicitly set this value. Callers can use @@ -32,7 +35,7 @@ class KtxBasisParams: quality_level: int = 0 """ - Compression quality. Range is [1,255]. + ETC1S/BasisLZ and UASTC LDR 4x4: Compression quality. Range is [1,255]. Lower gives better compression/lower quality/faster. Higher gives less compression/higher quality/slower. @@ -49,7 +52,7 @@ class KtxBasisParams: max_endpoints: int = 0 """ - Manually set the max number of color endpoint clusters. + ETC1S/BasisLZ: Manually set the max number of color endpoint clusters. Range is [1,16128]. Default is 0, unset. If this is set, max_selectors must also be set, otherwise the value will be ignored. @@ -57,7 +60,7 @@ class KtxBasisParams: endpoint_rdo_threshold: int = 0 """ - Set endpoint RDO quality threshold. The default is 1.25. + ETC1S/BasisLZ: Set endpoint RDO quality threshold. The default is 1.25. Lower is higher quality but less quality per output bit (try [1.0,3.0]. This will override the value chosen by quality_level. @@ -65,7 +68,7 @@ class KtxBasisParams: max_selectors: int = 0 """ - Manually set the max number of color selector clusters. Range is [1,16128]. + ETC1S/BasisLZ: Manually set the max number of color selector clusters. Range is [1,16128]. Default is 0, unset. If this is set, max_endpoints must also be set, otherwise the value will be ignored. @@ -73,7 +76,7 @@ class KtxBasisParams: selector_rdo_threshold: int = 0 """ - Set selector RDO quality threshold. The default is 1.5. + ETC1S/BasisLZ: Set selector RDO quality threshold. The default is 1.5. Lower is higher quality but less quality per output bit (try [1.0,3.0]). This will override the value chosen by @c qualityLevel. @@ -113,32 +116,32 @@ class KtxBasisParams: no_endpoint_rdo: bool = False """ - Disable endpoint rate distortion optimizations. + ETC1S/BasisLZ: Disable endpoint rate distortion optimizations. Slightly faster, less noisy output, but lower quality per output bit. """ no_selector_rdo: bool = False """ - Disable selector rate distortion optimizations. + ETC1S/BasisLZ: Disable selector rate distortion optimizations. Slightly faster, less noisy output, but lower quality per output bit. """ uastc_flags: int = KtxPackUastcFlagBits.FASTEST """ - A set of KtxPackUastcFlagBits controlling UASTC encoding. + UASTC LDR 4x4: A set of KtxPackUastcFlagBits controlling UASTC encoding. The most important value is the level given in the least-significant 4 bits which selects a speed vs quality tradeoff. """ uastc_rdo: bool = False - """Enable Rate Distortion Optimization (RDO) post-processing.""" + """UASTC LDR 4x4: Enable Rate Distortion Optimization (RDO) post-processing.""" uastc_rdo_quality_scalar: float = 0. """ - UASTC RDO quality scalar (lambda). + UASTC LDR 4x4: RDO quality scalar (lambda). Lower values yield higher quality/larger LZ compressed files, higher values yield lower quality/smaller LZ compressed files. A good range to @@ -147,29 +150,29 @@ class KtxBasisParams: uastc_rdo_dict_size: int = 0 """ - UASTC RDO dictionary size in bytes. Default is 4096. Lower + UASTC LDR 4x4: RDO dictionary size in bytes. Default is 4096. Lower values=faster, but give less compression. Range is [64,65536]. """ uastc_rdo_max_smooth_block_error_scale: float = 10. """ - UASTC RDO max smooth block error scale. Range is [1,300]. + UASTC LDR 4x4: RDO max smooth block error scale. Range is [1,300]. Default is 10.0, 1.0 is disabled. Larger values suppress more artifacts (and allocate more bits) on smooth blocks. """ uastc_rdo_max_smooth_block_std_dev: float = 18. """ - UASTC RDO max smooth block standard deviation. Range is + UASTC LDR 4x4: RDO max smooth block standard deviation. Range is [.01,65536.0]. Default is 18.0. Larger values expand the range of blocks considered smooth. """ uastc_rdo_dont_favor_simpler_modes: bool = False - """Do not favor simpler UASTC modes in RDO mode.""" + """UASTC LDR 4x4: Do not favor simpler UASTC modes in RDO mode.""" uastc_rdo_no_multithreading: bool = False - """Disable RDO multithreading (slightly higher compression, deterministic).""" + """UASTC LDR 4x4: Disable RDO multithreading (slightly higher compression, deterministic).""" # UASTC HDR params diff --git a/interface/python_binding/pyktx/ktx_texture.py b/interface/python_binding/pyktx/ktx_texture.py index 986ca042a7..8ea919890b 100644 --- a/interface/python_binding/pyktx/ktx_texture.py +++ b/interface/python_binding/pyktx/ktx_texture.py @@ -79,9 +79,14 @@ def base_depth(self) -> int: return lib.PY_ktxTexture_get_baseDepth(self._ptr) + # A colon in a docstring indicates the preceding text is the type. There is + # no way to escape it. See https://github.com/sphinx-doc/sphinx/issues/9273. + # Therefore we use a MODIFIER LETTER COLON '꞉', Unicode: U+A789, + # UTF-8: EA 9E 89 here and in any other place we want to a colon in the + # description. @property def num_dimensions(self) -> int: - """Number of dimensions in the texture: 1, 2 or 3.""" + """Number of dimensions in the texture꞉ 1, 2 or 3.""" return lib.PY_ktxTexture_get_numDimensions(self._ptr) @@ -99,7 +104,7 @@ def num_levels(self) -> int: @property def num_faces(self) -> int: - """Number of faces: 6 for cube maps, 1 otherwise.""" + """Number of faces꞉ 6 for cube maps, 1 otherwise.""" return lib.PY_ktxTexture_get_numFaces(self._ptr) From fb808b83f115204585c62a331b85571fe6eadd32 Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Thu, 19 Mar 2026 18:26:37 +0900 Subject: [PATCH 11/13] Add UASTC_HDR_6x6_INTERMEDIATE. --- interface/python_binding/pyktx/ktx_supercmp_scheme.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/python_binding/pyktx/ktx_supercmp_scheme.py b/interface/python_binding/pyktx/ktx_supercmp_scheme.py index e7634171f2..00ca13ee2a 100644 --- a/interface/python_binding/pyktx/ktx_supercmp_scheme.py +++ b/interface/python_binding/pyktx/ktx_supercmp_scheme.py @@ -18,3 +18,6 @@ class KtxSupercmpScheme(IntEnum): ZLIB = 3 """ZLIB supercompression.""" + + UASTC_HDR_6x6_INTERMEDIATE = 4 + """UASTC HDR 6x6 Intermediate supercompression.""" From 5f396c7f1ca1bba1a37c9316bc05ba659b397298 Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Thu, 19 Mar 2026 18:27:08 +0900 Subject: [PATCH 12/13] Add test of HDR compression. --- .../python_binding/tests/test_ktx_texture2.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/interface/python_binding/tests/test_ktx_texture2.py b/interface/python_binding/tests/test_ktx_texture2.py index 707f22c5c5..7100183ca7 100644 --- a/interface/python_binding/tests/test_ktx_texture2.py +++ b/interface/python_binding/tests/test_ktx_texture2.py @@ -99,9 +99,26 @@ def test_compress_basis_with_params(self): texture.compress_basis(KtxBasisParams(codec=KtxBasisCodec.UASTC_LDR_4x4,uastc_rdo=True)) self.assertEqual(texture.color_model, KhrDfModel.UASTC) + self.assertEqual(texture.color_model, KhrDfModel.UASTC_LDR_4x4) self.assertEqual(texture.is_compressed, True) self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.NONE) + def test_compress_basis_hdr(self): + test_ktx_file = os.path.join(__test_images__, 'ktx2/Desk_small_zstd_15.ktx2') + texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) + + self.assertEqual(texture.color_model, KhrDfModel.RGBSDA) + self.assertEqual(texture.is_compressed, False) + self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.NONE) + + texture.compress_basis(KtxBasisParams(codec=KtxBasisCodec.UASTC_HDR_6x6_INTERMEDIATE, + uastc_hdr_lambda=100., + uastc_hdr_level=3)) + + self.assertEqual(texture.color_model, KhrDfModel.UASTC_HDR_6x6) + self.assertEqual(texture.is_compressed, True) + self.assertEqual(texture.supercompression_scheme, KtxSupercmpScheme.UASTC_HDR_6x6_INTERMEDIATE) + def test_transcode_basis(self): test_ktx_file = os.path.join(__test_images__, 'ktx2/color_grid_blze.ktx2') texture = KtxTexture2.create_from_named_file(test_ktx_file, KtxTextureCreateFlagBits.LOAD_IMAGE_DATA_BIT) From 834071681fd5ef5ad73a8efae2b622d82720db8d Mon Sep 17 00:00:00 2001 From: Mark Callow Date: Thu, 19 Mar 2026 18:35:51 +0900 Subject: [PATCH 13/13] Add small HDR file for pyktx tests. --- tests/resources/genktx2 | 2 ++ tests/resources/ktx2/Desk_small_zstd_15.ktx2 | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 tests/resources/ktx2/Desk_small_zstd_15.ktx2 diff --git a/tests/resources/genktx2 b/tests/resources/genktx2 index 485e71be58..fad242a4ba 100755 --- a/tests/resources/genktx2 +++ b/tests/resources/genktx2 @@ -141,3 +141,5 @@ $ktx convert --testrun -t ktx ktx/pattern_02_bc2.ktx ktx2/pattern_02_bc2.ktx2 # HDR images $ktx create --testrun --format R16G16B16_SFLOAT --encode uastc-hdr-4x4 --uastc-hdr-ultra-quant --zstd 15 input/exr/Desk.exr ktx2/Desk_uastc_hdr4x4_zstd_15.ktx2 $ktx create --testrun --format R16G16B16_SFLOAT --encode uastc-hdr-6x6i --uastc-hdr-lambda 300 input/exr/Desk.exr ktx2/Desk_uastc_hdr6x6i.ktx2 +# Small uncompressed file for the pyktx tests. +$ktx create --testrun --format R16G16B16_SFLOAT --scale 0.25 --zstd 15 input/exr/Desk.exr ktx2/Desk_small_zstd_15.ktx2 diff --git a/tests/resources/ktx2/Desk_small_zstd_15.ktx2 b/tests/resources/ktx2/Desk_small_zstd_15.ktx2 new file mode 100644 index 0000000000..7528b3b9b4 --- /dev/null +++ b/tests/resources/ktx2/Desk_small_zstd_15.ktx2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2337d10de15d343f360511a9f055741b81c7f46b5364249ee16b25f6c4df324 +size 194396