diff --git a/interface/python_binding/buildscript.py b/interface/python_binding/buildscript.py index 2a1e04759f..13686bb914 100644 --- a/interface/python_binding/buildscript.py +++ b/interface/python_binding/buildscript.py @@ -81,11 +81,16 @@ uint32_t faceSlice, 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); - 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_NeedsTranscoding(void *); int ktxHashList_AddKVPair(ktxHashList *, const char *key, unsigned int valueLen, const void *value); int ktxHashList_DeleteKVPair(ktxHashList *, const char *key); @@ -108,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 *); @@ -159,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, @@ -182,7 +188,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/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/khr_df_model.py b/interface/python_binding/pyktx/khr_df_model.py new file mode 100644 index 0000000000..2693c96c5b --- /dev/null +++ b/interface/python_binding/pyktx/khr_df_model.py @@ -0,0 +1,126 @@ +# Copyright (c) 2026, Mark Callow +# SPDX-License-Identifier: Apache-2.0 + +from enum import IntEnum + +class KhrDfModel(IntEnum): + """Model in which the color coordinate space is defined.""" + + UNSPECIFIED = 0 + """No interpretation of color channels defined.""" + + RGBSDA = 1 + """Color primaries (red, green, blue) + alpha, depth and stencil.""" + + YUVSDA = 2 + """Color differences (Y', Cb, Cr) + alpha, depth and stencil.""" + + YIQSDA = 3 + """Color differences (Y', I, Q) + alpha, depth and stencil.""" + + LABSDA = 4 + """Perceptual color (CIE L*a*b*) + alpha, depth and stencil.""" + + CMYKA = 5 + """Subtractive colors (cyan, magenta, yellow, black) + alpha.""" + + XYZW = 6 + """Non-color coordinate data (X, Y, Z, W).""" + + HSVA_ANG = 7 + """Hue, saturation, value, hue angle on color circle, plus alpha.""" + + HSLA_ANG = 8 + """Hue, saturation, lightness, hue angle on color circle, plus alpha.""" + + HSVA_HEX = 9 + """Hue, saturation, value, hue on color hexagon, plus alpha.""" + + HSLA_HEX = 10 + """Hue, saturation, lightness, hue on color hexagon, plus alpha.""" + + YCGCOA = 11 + """Lightweight approximate color difference (luma, orange, green).""" + + YCCBCCRC = 12 + """ITU BT.2020 constant luminance YcCbcCrc.""" + + ICTCP = 13 + """ITU BT.2100 constant intensity ICtCp.""" + + CIEXYZ = 14 + """CIE 1931 XYZ color coordinates (X, Y, Z).""" + + CIEXYY = 15 + """CIE 1931 xyY color coordinates (X, Y, Y).""" + + + DXT1A = 128 + 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. + """ + + DXT2 = 129 + DXT3 = 129 + BC2 = 129 + """DXT2/DXT3/BC2, with explicit 4-bit alpha.""" + + DXT4 = 130 + DXT5 = 130 + BC3 = 130 + """DXT4/DXT5/BC3, with interpolated alpha.""" + + 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).""" + + 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).""" + + BC6H = 133 + """BC6H - DX11 format for 16-bit float channels.""" + + BC7 = 134 + """BC7 - DX11 format.""" + + ETC1 = 160 + """A format of ETC1 indicates that the format shall be decodable + by an ETC1-compliant decoder and not rely on ETC2 features.""" + + 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.""" + + 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.""" + + ETC1S = 163 + """ETC1S is a simplified subset of ETC1.""" + + PVRTC = 164 + PVRTC2 = 165 + """PowerVR Texture Compression.""" + + UASTC = 166 + UASTC_LDR_4x4 = 166 + UASTC_HDR_4x4 = 167 + UASTC_HDR_6x6 = 168 + """UASTC for BASIS supercompression.""" + + 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..ef20144a9f --- /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.""" + + UNSPECIFIED = 0 + """No color primaries defined""" + + BT709 = 1 + """Color primaries of ITU-R BT.709 and sRGB""" + + SRGB = 1 + """Synonym for BT709""" + + BT601_EBU = 2 + """Color primaries of ITU-R BT.601 (625-line EBU variant)""" + + BT601_SMPTE = 3 + """Color primaries of ITU-R BT.601 (525-line SMPTE C variant)""" + + BT2020 = 4 + """Color primaries of ITU-R BT.2020""" + + BT2100 = 4 + """ITU-R BT.2100 uses the same primaries as BT.2020""" + + CIEXYZ = 5 + """CIE theoretical color coordinate space""" + + ACES = 6 + """Academy Color Encoding System primaries""" + + ACESCC = 7 + """Color primaries of ACEScc""" + + NTSC1953 = 8 + """Legacy NTSC 1953 primaries""" + + PAL525 = 9 + """Legacy PAL 525-line primaries""" + + DISPLAYP3 = 10 + """Color primaries of Display P3""" + + ADOBERGB = 11 + """Color primaries of Adobe RGB (1998)""" + + MAX = 0xFF diff --git a/interface/python_binding/pyktx/khr_df_transfer.py b/interface/python_binding/pyktx/khr_df_transfer.py new file mode 100644 index 0000000000..140613fce2 --- /dev/null +++ b/interface/python_binding/pyktx/khr_df_transfer.py @@ -0,0 +1,94 @@ +# Copyright (c) 2026, Mark Callow +# SPDX-License-Identifier: Apache-2.0 + +from enum import IntEnum + +class KhrDfTransfer(IntEnum): + """The transfer function of an image.""" + + UNSPECIFIED = 0 + """No transfer function defined.""" + + LINEAR = 1 + """Linear transfer function (value proportional to intensity.""" + + SRGB = 2 + SRGB_EOTF = 2 + SCRGB = 2 + SCRGB_EOTF = 2 + """Perceptually-linear transfer function of sRGB (~2.2); also used for scRGB.""" + + 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).""" + + 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.""" + + NTSC = 4 + NTSC_EOTF = 4 + """Perceptually-linear gamma function of original NTSC (simple 2.2 gamma).""" + + SLOG = 5 + SLOG_OETF = 5 + """Sony S-log used by Sony video cameras.""" + + SLOG2 = 6 + SLOG2_OETF = 6 + """Sony S-log 2 used by Sony video cameras.""" + + BT1886 = 7 + BT1886_EOTF = 7 + """ITU BT.1886 EOTF.""" + + HLG_OETF = 8 + """ITU BT.2100 HLG OETF (typical scene-referred content), linear light normalized 0..1.""" + + HLG_EOTF = 9 + """ITU BT.2100 HLG EOTF (nominal HDR display of HLG content), linear light normalized 0..1.""" + + PQ_EOTF = 10 + """ITU BT.2100 PQ EOTF (typical HDR display-referred PQ content).""" + + PQ_OETF = 11 + """ITU BT.2100 PQ OETF (nominal scene described by PQ HDR content).""" + + DCIP3 = 12 + DCIP3_EOTF = 12 + """DCI P3 transfer function.""" + + PAL_OETF = 13 + """Legacy PAL OETF.""" + + PAL625_EOTF = 14 + """Legacy PAL 625-line EOTF.""" + + ST240 = 15 + ST240_OETF = 15 + ST240_EOTF = 15 + """Legacy ST240 transfer function.""" + + ACESCC = 16 + ACESCC_OETF = 16 + """ACEScc transfer function.""" + + ACESCCT = 17 + ACESCCT_OETF = 17 + """ACEScct transfer function.""" + + ADOBERGB = 18 + ADOBERGB_EOTF = 18 + """Adobe RGB (1998) transfer function.""" + + HLG_UNNORMALIZED_OETF = 19 + """Legacy ITU BT.2100 HLG OETF (typical scene-referred content), linear light normalized 0..12.""" + + 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..f35fb6a13a 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().""" + """Struct 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 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,26 +150,68 @@ 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 + + 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_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.""" 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 2da6b15c52..8ea919890b 100644 --- a/interface/python_binding/pyktx/ktx_texture.py +++ b/interface/python_binding/pyktx/ktx_texture.py @@ -79,12 +79,23 @@ 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) + @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.""" @@ -93,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) @@ -140,6 +151,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.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..5efc4e36d5 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_model import KhrDfModel +from .khr_df_primaries import KhrDfPrimaries +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 @@ -71,22 +74,28 @@ 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) -> KhrDfModel: + """The color model of the images.""" - return lib.ktxTexture2_GetOETF(self._ptr) + return KhrDfModel(lib.ktxTexture2_GetColorModel_e(self._ptr)) @property - def premultipled_alpha(self) -> bool: - """Whether the RGB components have been premultiplied by the alpha component.""" + def primaries(self) -> KhrDfPrimaries: + """The color primaries of the images.""" - return lib.ktxTexture2_GetPremultipliedAlpha(self._ptr) + return KhrDfPrimaries(lib.ktxTexture2_GetPrimaries_e(self._ptr)) + + @property + def transfer_function(self) -> KhrDfTransfer: + """The transfer function of the images.""" + + return KhrDfTransfer(lib.ktxTexture2_GetTransferFunction_e(self._ptr)) @property - def needs_transcoding(self) -> bool: - """If the images are in a transcodable format.""" + def premultipled_alpha(self) -> bool: + """Whether the RGB components have been premultiplied by the alpha component.""" - return lib.ktxTexture2_NeedsTranscoding(self._ptr) + return lib.ktxTexture2_GetPremultipliedAlpha(self._ptr) def compress_astc(self, params: Union[int, KtxAstcParams]) -> None: """ @@ -135,7 +144,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 +167,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/interface/python_binding/tests/test_ktx_texture2.py b/interface/python_binding/tests/test_ktx_texture2.py index 90b92a3b95..7100183ca7 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,47 @@ 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.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) @@ -103,3 +138,59 @@ 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.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) diff --git a/lib/include/ktx.h b/lib/include/ktx.h index 964f3d7a3f..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); @@ -1573,26 +1579,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; 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