-
Notifications
You must be signed in to change notification settings - Fork 222
/
ktx_texture.py
230 lines (158 loc) · 7.37 KB
/
ktx_texture.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# Copyright (c) 2023, Shukant Pal and Contributors
# SPDX-License-Identifier: Apache-2.0
from ctypes import *
from .ktx_error_code import KtxErrorCode, KtxError
from .ktx_hash_list import KtxHashList
from pyktx.native import ffi, lib
from typing import Dict, Literal, Optional
class KtxVersionMismatchError(Exception):
"""Error thrown when reading a file with wrong KTX version."""
pass
class KtxTexture:
"""
Base class representing a texture.
ktxTextures should be created only by one of the static factory methods,
and these fields should be considered read-only.
"""
def __init__(self, ptr: c_uint64):
self._ptr = ptr
self.__kv_data_head = KtxHashList(lib.PY_ktxTexture_get_kvDataHead(ptr))
def __del__(self):
lib.free(self._ptr)
self._ptr = ffi.NULL
@property
def class_id(self) -> int:
"""Identify the class type. 1 for KtxTexture1, 2 for KtxTexture2."""
return lib.PY_ktxTexture_get_classId(self._ptr)
@property
def is_array(self) -> bool:
"""true if the texture is an array texture, i.e, a GL_TEXTURE_*_ARRAY target is to be used."""
return lib.PY_ktxTexture_get_isArray(self._ptr)
@property
def is_compressed(self) -> bool:
"""If the texture's format is a block compressed format."""
return lib.PY_ktxTexture_get_isCompressed(self._ptr)
@property
def is_cubemap(self) -> bool:
"""
True if the texture is a cubemap or cubemap array.
"""
return lib.PY_ktxTexture_get_isCubemap(self._ptr)
@property
def generate_mipmaps(self) -> bool:
"""If mipmaps should be generated for the texture when uploading to graphics APIs."""
return lib.PY_ktxTexture_get_generateMipmaps(self._ptr)
@property
def base_width(self) -> int:
"""Width of the texture's base level."""
return lib.PY_ktxTexture_get_baseWidth(self._ptr)
@property
def base_height(self) -> int:
"""Height of the texture's base level."""
return lib.PY_ktxTexture_get_baseHeight(self._ptr)
@property
def base_depth(self) -> int:
"""Depth of the texture's base level."""
return lib.PY_ktxTexture_get_baseDepth(self._ptr)
@property
def num_dimensions(self) -> int:
"""Number of dimensions in the texture: 1, 2 or 3."""
return lib.PY_ktxTexture_get_numDimensions(self._ptr)
@property
def num_levels(self) -> int:
"""Number of mip levels in the texture."""
return lib.PY_ktxTexture_get_numLevels(self._ptr)
@property
def num_faces(self) -> int:
"""Number of faces: 6 for cube maps, 1 otherwise."""
return lib.PY_ktxTexture_get_numFaces(self._ptr)
@property
def element_size(self) -> int:
"""The element size of the texture's images."""
return lib.ktxTexture_GetElementSize(self._ptr)
@property
def kv_data_raw(self) -> Optional[c_buffer]:
"""
The raw KV data buffer.
This is available only if RAW_KVDATA_BIT was used in create-flag bits.
"""
buffer = lib.PY_ktxTexture_get_kvData(self._ptr)
if buffer == ffi.NULL:
return None
return ffi.buffer(buffer, lib.PY_ktxTexture_get_kvDataLen(self._ptr))
@property
def kv_data(self) -> KtxHashList:
"""
The metadata stored in the texture as a hash-list.
This is not available if SKIP_KVDATA_BIT was used in the create-flag bits.
"""
return self.__kv_data_head
@property
def data_size(self) -> int:
"""The total size of the texture image data in bytes."""
return lib.ktxTexture_GetDataSize(self._ptr)
@property
def data_size_uncompressed(self) -> int:
"""Byte length of the texture's uncompressed image data."""
return lib.ktxTexture_GetDataSizeUncompressed(self._ptr)
def row_pitch(self, level: int) -> int:
"""
Return pitch between rows of a texture image level in bytes.
For uncompressed textures the pitch is the number of bytes between
rows of texels. For compressed textures it is the number of bytes
between rows of blocks. The value is padded to GL_UNPACK_ALIGNMENT,
if necessary. For all currently known compressed formats padding will
not be necessary.
"""
return lib.ktxTexture_GetRowPitch(self._ptr, level)
def image_size(self, level: int) -> int:
"""
Calculate & return the size in bytes of an image at the specified mip level.
For arrays, this is the size of layer, for cubemaps, the size of a face and
for 3D textures, the size of a depth slice.
The size reflects the padding of each row to KTX_GL_UNPACK_ALIGNMENT.
"""
return lib.ktxTexture_GetImageSize(self._ptr, level)
def image_offset(self, level: int, layer: int, face_slice: int) -> int:
"""
Find the offset of an image within a ktxTexture's image data.
As there is no such thing as a 3D cubemap we make the 3rd location parameter
do double duty.
"""
data = lib.PY_ktxTexture_GetImageOffset(self._ptr, level, layer, face_slice)
if int(data.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture_GetImageOffset', KtxErrorCode(data.error))
return data.offset
def data(self) -> bytes:
"""Return a buffer holding the texture image data."""
return ffi.buffer(lib.ktxTexture_GetData(self._ptr), self.data_size)
def set_image_from_memory(self, level: int, layer: int, face_slice: int, data: bytes):
"""
Set image for level, layer, faceSlice from an image in memory.
Uncompressed images in memory are expected to have their rows tightly packed
as is the norm for most image file formats. The copied image is padded as
necessary to achieve the KTX-specified row alignment. No padding is done if the
ktxTexture's is_compressed field is true. Level, layer, face_slice rather than offset
are specified to enable some validation.
"""
error = KtxErrorCode(lib.ktxTexture_SetImageFromMemory(self._ptr, level, layer, face_slice, data, len(data)))
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture_SetImageFromMemory', KtxErrorCode(int(error)))
def write_to_named_file(self, dst_name: str) -> None:
"""Save this texture to a named file in KTX format."""
error = KtxErrorCode(lib.ktxTexture_WriteToNamedFile(self._ptr, dst_name.encode('ascii')))
if int(error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture_WriteToNamedFile', KtxErrorCode(error))
def write_to_native_memory(self) -> Dict[Literal["bytes", "size"], int]:
"""Write this KTX file to block of memory in KTX format. Recommended to use write_to_memory instead."""
data = lib.PY_ktxTexture_WriteToMemory(self._ptr)
if int(data.error) != KtxErrorCode.SUCCESS:
raise KtxError('ktxTexture_WriteToMemory', KtxErrorCode(data.error))
return {"bytes": data.bytes, "size": data.size}
def write_to_memory(self) -> bytes:
"""Write this KTX file to a buffer in KTX format."""
data = self.write_to_native_memory()
native_buffer = ffi.buffer(data.get('bytes'), data.get('size'))
buffer = native_buffer[:]
lib.free(data.get('bytes'))
return buffer