-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtextures_bindings.cpp
More file actions
518 lines (436 loc) · 44.8 KB
/
Copy pathtextures_bindings.cpp
File metadata and controls
518 lines (436 loc) · 44.8 KB
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 Fernando Sahmkow
//
// AUTOGENERATED by tools/codegen — do not edit by hand.
// Regenerate via: python -m tools.codegen.codegen textures --backend pybind11
//
// Source headers and `@bind` annotations live under include/whiteout/.
//
// Include order matters here:
// 1. pybind11/pybind11.h sets up the base library
// 2. project headers define whiteout::u32 et al used inside MAKE_OPAQUE
// 3. PYBIND11_MAKE_OPAQUE opts out of stl.h's auto-conversion for our vectors
// 4. pybind11/stl.h, stl_bind.h honor the opaque declarations
#include <pybind11/pybind11.h>
#include <array>
#include <cstdint>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
#include <whiteout/common_types.h>
#include <whiteout/textures/texture.h>
#include <whiteout/textures/blp/parser.h>
#include <whiteout/textures/blp/writer.h>
#include <whiteout/textures/png/parser.h>
#include <whiteout/textures/png/writer.h>
#include <whiteout/textures/jpeg/parser.h>
#include <whiteout/textures/jpeg/writer.h>
#include <whiteout/textures/dds/parser.h>
#include <whiteout/textures/dds/writer.h>
#include <whiteout/textures/bmp/parser.h>
#include <whiteout/textures/bmp/writer.h>
#include <whiteout/textures/tga/parser.h>
#include <whiteout/textures/tga/writer.h>
#include <whiteout/textures/tiff/parser.h>
#include <whiteout/textures/tiff/writer.h>
#include <whiteout/textures/gif/writer.h>
#include <whiteout/interfaces.h>
PYBIND11_MAKE_OPAQUE(std::vector<std::string>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::u8>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::textures::Channel>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::textures::Texture>);
PYBIND11_MAKE_OPAQUE(std::vector<whiteout::textures::png::ApngFrame>);
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <pybind11/operators.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
void bind_textures(py::module_& m) {
py::enum_<whiteout::textures::PixelFormat>(m, "PixelFormat", R"doc(GPU pixel / block-compression format.
Uncompressed formats store one pixel per "block"; BCn formats store a 4×4 pixel tile per block.)doc")
.value("R8", whiteout::textures::PixelFormat::R8, R"doc(8-bit single channel (1 byte per pixel).)doc")
.value("R16", whiteout::textures::PixelFormat::R16, R"doc(16-bit single channel UNORM (2 bytes per pixel).)doc")
.value("R32_F", whiteout::textures::PixelFormat::R32F, R"doc(Single-precision single channel (4 bytes per pixel).)doc")
.value("RG8", whiteout::textures::PixelFormat::RG8, R"doc(8-bit dual channel (2 bytes per pixel).)doc")
.value("RG16", whiteout::textures::PixelFormat::RG16, R"doc(16-bit dual channel UNORM (4 bytes per pixel).)doc")
.value("RG32_F", whiteout::textures::PixelFormat::RG32F, R"doc(Single-precision dual channel (8 bytes per pixel).)doc")
.value("RGBA8", whiteout::textures::PixelFormat::RGBA8, R"doc(8-bit RGBA (4 bytes per pixel).)doc")
.value("RGBA16", whiteout::textures::PixelFormat::RGBA16, R"doc(16-bit RGBA UNORM (8 bytes per pixel).)doc")
.value("RGBA32_F", whiteout::textures::PixelFormat::RGBA32F, R"doc(Single-precision RGBA (16 bytes per pixel).)doc")
.value("BC1", whiteout::textures::PixelFormat::BC1, R"doc(DXT1 – 8 bytes per 4×4 block (RGB + optional 1-bit alpha).)doc")
.value("BC2", whiteout::textures::PixelFormat::BC2, R"doc(DXT3 – 16 bytes per 4×4 block (explicit 4-bit alpha).)doc")
.value("BC3", whiteout::textures::PixelFormat::BC3, R"doc(DXT5 – 16 bytes per 4×4 block (interpolated alpha).)doc")
.value("BC4", whiteout::textures::PixelFormat::BC4, R"doc(Single-channel – 8 bytes per 4×4 block.)doc")
.value("BC5", whiteout::textures::PixelFormat::BC5, R"doc(Dual-channel – 16 bytes per 4×4 block.)doc")
.value("BC6_H", whiteout::textures::PixelFormat::BC6H, R"doc(HDR RGB – 16 bytes per 4×4 block (half-float output).)doc")
.value("BC7", whiteout::textures::PixelFormat::BC7, R"doc(High-quality RGBA – 16 bytes per 4×4 block.)doc")
;
py::enum_<whiteout::textures::TextureType>(m, "TextureType", R"doc(Dimensionality / topology of a texture resource.)doc")
.value("TEXTURE2_D", whiteout::textures::TextureType::Texture2D, R"doc(Standard 2D image (1 layer).)doc")
.value("TEXTURE3_D", whiteout::textures::TextureType::Texture3D, R"doc(Volume texture (depth > 1, depth halves each mip).)doc")
.value("TEXTURE_CUBE", whiteout::textures::TextureType::TextureCube, R"doc(Cube map (6 square layers, one per face).)doc")
.value("TEXTURE2_D_ARRAY", whiteout::textures::TextureType::Texture2DArray, R"doc(Array of 2D images (arraySize layers).)doc")
.value("TEXTURE_CUBE_ARRAY", whiteout::textures::TextureType::TextureCubeArray, R"doc(Array of cube maps (6 × arraySize layers).)doc")
;
py::class_<whiteout::textures::png::ApngFrameInfo>(m, "PngApngFrameInfo", R"doc(Per-frame metadata for an animated PNG (APNG).)doc")
.def(py::init<>())
.def(py::init([](whiteout::u32 width, whiteout::u32 height, whiteout::u32 x_offset, whiteout::u32 y_offset, whiteout::u32 delay_ms, whiteout::u32 dispose_op, whiteout::u32 blend_op) {
return whiteout::textures::png::ApngFrameInfo{width, height, x_offset, y_offset, delay_ms, dispose_op, blend_op};
}), py::arg("width"), py::arg("height"), py::arg("x_offset"), py::arg("y_offset"), py::arg("delay_ms"), py::arg("dispose_op"), py::arg("blend_op"))
.def("__repr__", [](const whiteout::textures::png::ApngFrameInfo& self) {
std::ostringstream oss;
oss << "PngApngFrameInfo(";
oss << "width=" << self.width << ", ";
oss << "height=" << self.height << ", ";
oss << "x_offset=" << self.xOffset << ", ";
oss << "y_offset=" << self.yOffset << ", ";
oss << "delay_ms=" << self.delayMs << ", ";
oss << "dispose_op=" << self.disposeOp << ", ";
oss << "blend_op=" << self.blendOp << ")";
return oss.str();
})
.def_readwrite("width", &whiteout::textures::png::ApngFrameInfo::width, R"doc(Frame sub-rectangle width.)doc")
.def_readwrite("height", &whiteout::textures::png::ApngFrameInfo::height, R"doc(Frame sub-rectangle height.)doc")
.def_readwrite("x_offset", &whiteout::textures::png::ApngFrameInfo::xOffset, R"doc(Frame sub-rectangle X offset on the canvas.)doc")
.def_readwrite("y_offset", &whiteout::textures::png::ApngFrameInfo::yOffset, R"doc(Frame sub-rectangle Y offset on the canvas.)doc")
.def_readwrite("delay_ms", &whiteout::textures::png::ApngFrameInfo::delayMs, R"doc(Frame display duration in milliseconds.)doc")
.def_readwrite("dispose_op", &whiteout::textures::png::ApngFrameInfo::disposeOp, R"doc(0 = NONE, 1 = BACKGROUND, 2 = PREVIOUS.)doc")
.def_readwrite("blend_op", &whiteout::textures::png::ApngFrameInfo::blendOp, R"doc(0 = SOURCE, 1 = OVER.)doc")
;
py::class_<whiteout::textures::png::ApngFrame>(m, "PngApngFrame", R"doc(One frame of an animated PNG (APNG), with its display duration.)doc")
.def(py::init<>())
.def_readwrite("image", &whiteout::textures::png::ApngFrame::image, R"doc(Full-canvas frame image (converted to RGBA8 on write).)doc")
.def_readwrite("delay_ms", &whiteout::textures::png::ApngFrame::delayMs, R"doc(Display duration in milliseconds.)doc")
;
py::class_<whiteout::textures::png::ApngSaveOptions>(m, "PngApngSaveOptions", R"doc(Options controlling animated PNG (APNG) encoding.)doc")
.def(py::init<>())
.def(py::init([](whiteout::u32 loop_count) {
return whiteout::textures::png::ApngSaveOptions{loop_count};
}), py::arg("loop_count"))
.def("__repr__", [](const whiteout::textures::png::ApngSaveOptions& self) {
std::ostringstream oss;
oss << "PngApngSaveOptions(";
oss << "loop_count=" << self.loopCount << ")";
return oss.str();
})
.def_readwrite("loop_count", &whiteout::textures::png::ApngSaveOptions::loopCount, R"doc(Number of times to loop; 0 means loop forever.)doc")
;
py::class_<whiteout::textures::gif::SaveOptions>(m, "GifSaveOptions", R"doc(Per-write options for GIF encoding.)doc")
.def(py::init<>())
.def(py::init([](whiteout::u16 delay_cs, whiteout::u16 loop_count, bool dither, whiteout::f32 dither_strength, bool transparent) {
return whiteout::textures::gif::SaveOptions{delay_cs, loop_count, dither, dither_strength, transparent};
}), py::arg("delay_cs"), py::arg("loop_count"), py::arg("dither"), py::arg("dither_strength"), py::arg("transparent"))
.def("__repr__", [](const whiteout::textures::gif::SaveOptions& self) {
std::ostringstream oss;
oss << "GifSaveOptions(";
oss << "delay_cs=" << self.delayCs << ", ";
oss << "loop_count=" << self.loopCount << ", ";
oss << "dither=" << self.dither << ", ";
oss << "dither_strength=" << self.ditherStrength << ", ";
oss << "transparent=" << self.transparent << ")";
return oss.str();
})
.def_readwrite("delay_cs", &whiteout::textures::gif::SaveOptions::delayCs, R"doc(Delay between frames in centiseconds (1/100 s). 0 = unspecified.)doc")
.def_readwrite("loop_count", &whiteout::textures::gif::SaveOptions::loopCount, R"doc(Number of times the animation should loop. 0 = loop forever.)doc")
.def_readwrite("dither", &whiteout::textures::gif::SaveOptions::dither, R"doc(Enable blue-noise ordered dithering when mapping pixels to the palette.)doc")
.def_readwrite("dither_strength", &whiteout::textures::gif::SaveOptions::ditherStrength, R"doc(Dither strength in [0, 1]. 0 = no visible dithering, 1 = full.)doc")
.def_readwrite("transparent", &whiteout::textures::gif::SaveOptions::transparent, R"doc(Emit a transparent background. Pixels whose source alpha is below 50% become the GIF's transparent palette index; the rest are quantised normally. GIF transparency is 1-bit, so partially-covered (anti- aliased) edge pixels are forced fully opaque or fully transparent.)doc")
;
py::class_<whiteout::textures::Texture>(m, "Texture", R"doc(Format-agnostic GPU texture container
Texture is the central interchange object used by every format-specific parser and writer in the library. It owns a contiguous pixel-data buffer and a mip chain describing the layout of every mip level and layer.
Use the static factory methods (`create2D`, `create3D`, `createCube`) to allocate a new texture, or obtain one from a parser.
Supports in-place and copying format conversion between all PixelFormat values (uncompressed ↔ BCn) via `format()` and `copyAsFormat()`.
Uses the PImpl (Pointer to Implementation) idiom to hide internals.)doc")
.def(py::init<>())
.def("format", py::overload_cast<whiteout::textures::PixelFormat>(&whiteout::textures::Texture::format), py::arg("new_fmt"), R"doc(Convert this texture to a new pixel format in-place.
Replaces the internal data with the converted result. Equivalent to `*this = copyAsFormat(new_fmt)`.
@param new_fmt Target pixel format.)doc")
.def("format", py::overload_cast<>(&whiteout::textures::Texture::format, py::const_), R"doc(@return The pixel format of the stored data.)doc")
.def("copy_as_format", &whiteout::textures::Texture::copyAsFormat, py::arg("new_fmt"), py::arg("pool") = nullptr, R"doc(Return a copy of this texture converted to a different pixel format.
Conversion path: - Same format → plain copy. - BCn → decoded to native format (R8 for BC4, RG8 for BC5, RGBA32F for BC6H, RGBA8 for others), then recurse. - Uncompressed → uncompressed → per-pixel conversion. - Uncompressed → BCn → encode via the appropriate codec.
@param new_fmt Target pixel format. @param pool Optional WorkerPool for parallel BCn encode/decode work. Ignored for purely uncompressed-to-uncompressed conversions. @return A new Texture with the converted data.)doc")
.def("swap_channels", &whiteout::textures::Texture::swapChannels, py::arg("a"), py::arg("b"), R"doc(Swap two channels in-place across all mip levels and array layers.
Operates directly on the stored pixel data without any intermediate copy. Supports all uncompressed PixelFormats (R*, RG*, RGBA*).
Failure conditions (returns false): - The texture uses a BCn block-compressed format. - Either channel is not present in the current pixel format (e.g. Channel::B on an RG8 texture).
@param a First channel to swap. @param b Second channel to swap. @return true on success (including when @p a == @p b, which is a no-op), false when the operation is not valid for this texture.)doc")
.def("invert_channel", &whiteout::textures::Texture::invertChannel, py::arg("ch"), R"doc(Invert a single channel in-place across all mip levels and array layers.
Each sample value @c v is replaced with @c max_value - v, where @c max_value is the maximum representable value for the channel's underlying type (255 for u8, 65535 for u16, 1.0 for f32).
Operates directly on the stored pixel data without any intermediate copy. Supports all uncompressed PixelFormats (R*, RG*, RGBA*).
Failure conditions (returns false): - The texture uses a BCn block-compressed format. - The requested channel is not present in the current pixel format (e.g. Channel::B on an RG8 texture).
@param ch Channel to invert. @return true on success, false when the operation is not valid for this texture.)doc")
.def("expand_normal", &whiteout::textures::Texture::expandNormal, py::arg("xChannel"), py::arg("yChannel"), py::arg("zChannel"), R"doc(Reconstruct the Z component of a tangent-space normal map in-place.
Interprets channels @p a and @p b as the packed X and Y components of a unit normal vector, computes Z = sqrt(max(0, 1 - x² - y²)), and writes the result back to channel @p c.
Channel values are decoded from the UNORM [0, 1] storage convention to the signed [-1, 1] range before the computation (i.e. x = 2v - 1), and the reconstructed Z is re-encoded as (z + 1) / 2 before being written. This matches the encoding used by all other normal-map utilities in the library.
Operates directly on the stored pixel data without any intermediate copy. Supports all uncompressed PixelFormats (R*, RG*, RGBA*).
Failure conditions (returns false): - The texture uses a BCn block-compressed format. - Any of the three channel indices is not present in the current pixel format (e.g. Channel::B on an RG8 texture).
@param xChannel Channel storing the packed X component (source, read-only). @param yChannel Channel storing the packed Y component (source, read-only). @param zChannel Channel to receive the reconstructed Z component (write target). @return true on success, false when the operation is not valid for this texture.)doc")
.def("fill_channel", &whiteout::textures::Texture::fillChannel, py::arg("target"), py::arg("value"), R"doc(Fill a single channel with a constant value across all mip levels and array layers.
The floating-point value is quantised to the channel's underlying type (clamped to [0, 255] for u8, [0, 65535] for u16, stored directly for f32).
Returns false for BCn formats or if the channel index exceeds the format's channel count.
@param target Channel to fill. @param value Value to write (interpreted as [0, 1] for integer formats). @return true on success, false when the operation is not valid.)doc")
.def("split_channels", &whiteout::textures::Texture::splitChannels, py::arg("channels"), R"doc(Split selected channels into individual single-channel textures.
Each requested channel produces a separate Texture with a single-channel format matching the source bit depth (R8, R16, or R32F). All mip levels and layers are copied. The returned textures inherit the source's sRGB flag but their kind is set to TextureKind::Other.
Returns std::nullopt if the source is BCn-compressed or if any requested channel index exceeds the source channel count.
@param channels Channels to extract (e.g. {Channel::R, Channel::G}). @return One Texture per requested channel, or std::nullopt on failure.)doc")
.def_static("merge_channels",
[](const std::vector<whiteout::textures::Texture>& sources, const std::vector<whiteout::textures::Channel>& targetChannels) {
return whiteout::textures::Texture::mergeChannels(sources, targetChannels);
}, py::arg("sources"), py::arg("targetChannels"), R"doc(Merge single-channel textures into one multi-channel texture.
Each source texture is written into the corresponding target channel of a new RGBA-width texture whose bit depth matches the sources (RGBA8, RGBA16, or RGBA32F). All sources must share the same format, dimensions, mip count, and texture type. Channels not covered by the input list are zero-filled.
@param sources Single-channel textures to combine. @param targetChannels Destination channel for each source (same length as @p sources). @return The combined RGBA texture, or std::nullopt on failure.)doc")
.def("copy_from_normal_to_rgba",
[](whiteout::textures::Texture& self, whiteout::interfaces::WorkerPool* pool) {
return self.copyFromNormalToRGBA(pool);
}, py::arg("pool") = nullptr, R"doc(Return a copy of a 2-channel normal map expanded to RGBA8.
Only supported for textures whose kind() is TextureKind::Normal and whose format is RG8, RG16, RG32F, or BC5. The returned texture keeps the original shape, mip chain, kind, and sRGB flag, but stores data as RGBA8 with Z reconstructed from the packed X/Y normal in R/G.
@param pool Optional WorkerPool for parallel BCn decode work when the source texture is compressed. @return Expanded RGBA8 texture, or std::nullopt when unsupported.)doc")
.def("generate_mipmaps", py::overload_cast<whiteout::u32, whiteout::interfaces::WorkerPool*>(&whiteout::textures::Texture::generateMipmaps), py::arg("newMipCount"), py::arg("pool") = nullptr, R"doc(Generate all mip levels from the base image (mip 0).
Every mip level is generated directly from the original full-resolution image using an appropriately-sized filter kernel, rather than cascading from the previous mip level. This eliminates cumulative blur.
Selects the best filter and pipeline for the texture's kind(): - Diffuse / Albedo — Lanczos3; sRGB linearize/delinearize when isSrgb() is true. - Normal — Kaiser(β=6) with unpack / Toksvig / renormalize / pack. - Specular — Kaiser(β=6); sRGB linearize/delinearize when isSrgb(). - Roughness — Kaiser(β=6.5) variance-preserving: r→r², filter, √. - Gloss — convert to roughness, apply variance filter, convert back. - Metalness — Kaiser(β=5.5) mean filtering. - AmbientOcclusion — Kaiser(β=6) mean filtering. - Emissive — Lanczos3; sRGB linearize/delinearize when isSrgb(). - ORM (deprecated) — same as Multikind with R=AO/G=Roughness/B=Metalness. - Multikind — per-channel kind-appropriate pipeline; each channel's kind is queried via channelKind(). Unused channels use a box filter. - AlphaMask — Box filter; no sRGB conversion (linear mask data). - Lightmap — Lanczos3; clamp channels to [0, ∞) (no sRGB). - EnvironmentPBR — GGX importance-sampled convolution (equirectangular); roughness increases with each mip level. - EnvironmentLegacy — Solid-angle-weighted spherical Kaiser convolution (equirectangular); no roughness encoding. - Other — Box filter; sRGB linearize/delinearize when isSrgb().
The texture must use an uncompressed pixel format. BCn textures should be decompressed first. No-op if the texture has ≤ 1 mip.
@param newMipCount Desired number of mip levels in the output texture. Pass kKeepMipCount (0) to preserve the existing mip count. Must be between 1 and computeMaxMipCount(width, height, depth). When 1, the mip chain is truncated to the base level only and the function returns immediately. @param pool Optional WorkerPool used to parallelize mip generation across mip levels and layers. If null, generation runs on the calling thread. @return std::nullopt on success; std::optional<std::string> with error message on failure. No exceptions are thrown.)doc")
.def("generate_mipmaps", py::overload_cast<whiteout::interfaces::WorkerPool*>(&whiteout::textures::Texture::generateMipmaps), py::arg("pool") = nullptr, R"doc(@overload Preserves existing mip count; optional worker pool.)doc")
.def("downscale", &whiteout::textures::Texture::downscale, py::arg("levels") = whiteout::u32{}, py::arg("pool") = nullptr, R"doc(Downscale the texture by dropping leading mip levels.
Increases the mip count by @p levels (clamped to the maximum), regenerates all mip levels from the base image, then drops the first @p levels mips — effectively halving the resolution @p levels times while preserving the original mip chain length.
The texture must use an uncompressed pixel format (same requirement as generateMipmaps). Returns an error if @p levels would reduce every dimension to zero.
@param levels Number of mip levels to drop (default 1). @param pool Optional WorkerPool for parallel mip generation. @return std::nullopt on success; error message on failure.)doc")
.def_static("create2_d", &whiteout::textures::Texture::create2D, py::arg("fmt"), py::arg("width"), py::arg("height"), py::arg("mipCount") = whiteout::u32{}, R"doc(Create a 2D texture. @param fmt Pixel format. @param width Width in pixels. @param height Height in pixels. @param mipCount Number of mip levels (0 = auto-compute full chain). @return A zero-filled Texture with the requested layout.)doc")
.def_static("create3_d", &whiteout::textures::Texture::create3D, py::arg("fmt"), py::arg("width"), py::arg("height"), py::arg("depth"), py::arg("mipCount") = whiteout::u32{}, R"doc(Create a 3D (volume) texture. @param fmt Pixel format. @param width Width in pixels. @param height Height in pixels. @param depth Depth in slices. @param mipCount Number of mip levels (0 = auto-compute full chain). @return A zero-filled Texture with the requested layout.)doc")
.def_static("create_cube", &whiteout::textures::Texture::createCube, py::arg("fmt"), py::arg("size"), py::arg("mipCount") = whiteout::u32{}, R"doc(Create a cube-map texture. @param fmt Pixel format. @param size Face edge length in pixels (faces are square). @param mipCount Number of mip levels (0 = auto-compute full chain). @return A zero-filled Texture with 6 layers.)doc")
.def_static("create2_d_array", &whiteout::textures::Texture::create2DArray, py::arg("fmt"), py::arg("width"), py::arg("height"), py::arg("arraySize"), py::arg("mipCount") = whiteout::u32{}, R"doc(Create a 2D texture array. @param fmt Pixel format. @param width Width in pixels. @param height Height in pixels. @param arraySize Number of array slices (must be ≥ 1). @param mipCount Number of mip levels (0 = auto-compute full chain). @return A zero-filled Texture with @p arraySize layers.)doc")
.def_static("create_cube_array", &whiteout::textures::Texture::createCubeArray, py::arg("fmt"), py::arg("size"), py::arg("arraySize"), py::arg("mipCount") = whiteout::u32{}, R"doc(Create a cube-map texture array. @param fmt Pixel format. @param size Face edge length in pixels (faces are square). @param arraySize Number of cube-map entries in the array (must be ≥ 1). The final layer count is 6 × @p arraySize. @param mipCount Number of mip levels (0 = auto-compute full chain). @return A zero-filled Texture with 6 × arraySize layers.)doc")
.def("type", &whiteout::textures::Texture::type, R"doc(@return The texture dimensionality / topology.)doc")
.def("kind", &whiteout::textures::Texture::kind, R"doc(@return The semantic kind of this texture.)doc")
.def("set_kind", &whiteout::textures::Texture::setKind, py::arg("k"), R"doc(Set the semantic kind of this texture. @note TextureKind::Unused is not valid as a top-level kind; use setChannelKind() on a Multikind texture for per-channel Unused.)doc")
.def("channel_kind", &whiteout::textures::Texture::channelKind, py::arg("ch"), R"doc(@return The per-channel kind for channel @p ch.
Only meaningful when kind() == TextureKind::Multikind. Returns TextureKind::Other by default for all other kinds. @param ch Channel to query (R/G/B/A).)doc")
.def("set_channel_kind", &whiteout::textures::Texture::setChannelKind, py::arg("ch"), py::arg("kind"), R"doc(Set the per-channel kind for channel @p ch.
Only meaningful when kind() == TextureKind::Multikind. TextureKind::Unused is permitted here to mark a channel as unused. @param ch Channel to configure. @param kind Kind to assign, including TextureKind::Unused.)doc")
.def("channel_default", &whiteout::textures::Texture::channelDefault, py::arg("ch"), R"doc(@return The default fill value for channel @p ch.
This value is used by consumers (e.g. channel merging, material baking) when the channel carries no source data. Defaults to 1.0f for all channels. @param ch Channel to query (R/G/B/A).)doc")
.def("set_channel_default", &whiteout::textures::Texture::setChannelDefault, py::arg("ch"), py::arg("value"), R"doc(Set the default fill value for channel @p ch.
The value is stored as-is (normalised [0, 1] float for integer formats, linear scale for f32 formats). No clamping is applied at storage time. @param ch Channel to configure (R/G/B/A). @param value Default fill value; 1.0f by convention.)doc")
.def("is_srgb", &whiteout::textures::Texture::isSrgb, R"doc(@return True if the texture data is in sRGB colour space.)doc")
.def("set_srgb", &whiteout::textures::Texture::setSrgb, py::arg("srgb"), R"doc(Mark the texture as sRGB or linear.)doc")
.def("width", &whiteout::textures::Texture::width, R"doc(@return Base mip width in pixels.)doc")
.def("height", &whiteout::textures::Texture::height, R"doc(@return Base mip height in pixels.)doc")
.def("depth", &whiteout::textures::Texture::depth, R"doc(@return Base mip depth (1 for 2D / cube textures).)doc")
.def("layer_count", &whiteout::textures::Texture::layerCount, R"doc(@return Number of array layers. - Texture2D / Texture3D: 1. - TextureCube: 6. - Texture2DArray: arraySize(). - TextureCubeArray: 6 × arraySize().)doc")
.def("array_size", &whiteout::textures::Texture::arraySize, R"doc(@return Number of array slices (1 for non-array textures). For a TextureCubeArray, this is the number of cube-maps in the array (the layer count is 6 × this value).)doc")
.def("mip_count", &whiteout::textures::Texture::mipCount, R"doc(@return Number of mip levels per layer.)doc")
.def("mip_level", &whiteout::textures::Texture::mipLevel, py::arg("mip"), py::arg("layer") = whiteout::u32{}, R"doc(Get the mip-level descriptor for a given mip index and layer. @param mip Mip level index (0 = base). @param layer Array layer index (0 for 2D / 3D textures). @return Reference to the MipLevel struct.)doc")
.def("data_size", &whiteout::textures::Texture::dataSize, R"doc(@return Total byte size of the pixel-data buffer.)doc")
.def("data", py::overload_cast<>(&whiteout::textures::Texture::data, py::const_), R"doc(@return Read-only span over the entire pixel-data buffer.)doc")
.def("mip_data", py::overload_cast<whiteout::u32, whiteout::u32>(&whiteout::textures::Texture::mipData, py::const_), py::arg("mip"), py::arg("layer") = whiteout::u32{}, R"doc(Get a read-only span for a specific mip / layer. @param mip Mip level index. @param layer Array layer (default 0).)doc")
.def("take_data",
[](whiteout::textures::Texture& self) {
auto __v = self.takeData();
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, R"doc(Move the data vector out of the texture (destructive).
After this call the texture's dimensions and mip chain are cleared. @return The owned pixel-data buffer.)doc")
.def("set_data", &whiteout::textures::Texture::setData, py::arg("new_data"), R"doc(Replace the pixel-data buffer.
The new buffer must match the existing allocation size. @param new_data Replacement data.)doc")
;
py::class_<whiteout::textures::blp::Parser>(m, "BlpParser", R"doc(Parser for BLP texture files
The Parser reads binary BLP files and converts them into the Texture structure. It can handle both BLP1 (Warcraft III) and BLP2 (World of Warcraft) variants. Parsing is non-throwing — issues are collected via `hasIssues()` / `getIssues()` and `parse()` returns `std::nullopt` on failure.
Uses the PImpl (Pointer to Implementation) idiom to hide implementation details.)doc")
.def(py::init<>())
.def("parse",
[](whiteout::textures::blp::Parser& self, py::bytes __py_bytes_0) {
std::string __s_0 = __py_bytes_0;
std::span<const whiteout::u8> buffer(reinterpret_cast<const whiteout::u8*>(__s_0.data()), __s_0.size());
return self.parse(buffer);
}, py::arg("buffer"), R"doc(Parse a BLP file from memory buffer @param buffer Memory buffer containing BLP data @return Parsed texture data, or std::nullopt on failure @throws std::runtime_error If parsing fails in strict mode)doc")
.def("has_issues", &whiteout::textures::blp::Parser::hasIssues, R"doc(Check if parsing encountered any issues @return True if there were warnings or recoverable errors)doc")
.def("get_issues", &whiteout::textures::blp::Parser::getIssues, R"doc(Get list of issues encountered during parsing @return Vector of issue description strings)doc")
;
py::class_<whiteout::textures::blp::Writer>(m, "BlpWriter", R"doc(Writer for BLP texture files
The Writer takes a Texture and encodes it into BLP1 or BLP2 binary format. It supports palettized, JPEG, DXT, and BGRA encodings.
Uses the PImpl (Pointer to Implementation) idiom to hide implementation details.)doc")
.def(py::init<>())
.def(py::init<whiteout::interfaces::WorkerPool*>(), py::arg("pool"))
.def("write",
[](whiteout::textures::blp::Writer& self, const whiteout::textures::Texture& texture) {
auto __v = self.write(texture);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("texture"), R"doc(Write a BLP file to a byte buffer with default options)doc")
.def("has_issues", &whiteout::textures::blp::Writer::hasIssues, R"doc(Check if writing encountered any issues @return True if there were warnings or recoverable errors)doc")
.def("get_issues", &whiteout::textures::blp::Writer::getIssues, R"doc(Get list of issues encountered during writing @return Vector of issue description strings)doc")
;
py::class_<whiteout::textures::png::Parser>(m, "PngParser", R"doc(Reads a PNG file or byte buffer and decodes it into a Texture.
Animated PNG (APNG) is supported: `parse()` still returns the single default image, while the animation frames are exposed via `isAnimated()`, `frameCount()`, `frame()`, `frameDelayMs()` and `frameInfo()`.)doc")
.def(py::init<>())
.def("parse",
[](whiteout::textures::png::Parser& self, py::bytes __py_bytes_0) {
std::string __s_0 = __py_bytes_0;
std::span<const whiteout::u8> buffer(reinterpret_cast<const whiteout::u8*>(__s_0.data()), __s_0.size());
return self.parse(buffer);
}, py::arg("buffer"), R"doc(Parse a PNG byte buffer.)doc")
.def("has_issues", &whiteout::textures::png::Parser::hasIssues, R"doc(@return true if the last parse produced any issues.)doc")
.def("get_issues", &whiteout::textures::png::Parser::getIssues, R"doc(@return accumulated issues from the last parse call.)doc")
.def("is_animated", &whiteout::textures::png::Parser::isAnimated, R"doc(@return true if the last parsed PNG carried APNG animation chunks.)doc")
.def("frame_count", &whiteout::textures::png::Parser::frameCount, R"doc(@return number of animation frames (0 when not animated).)doc")
.def("loop_count", &whiteout::textures::png::Parser::loopCount, R"doc(@return APNG loop count from the `acTL` chunk; 0 means loop forever.)doc")
.def("frame", &whiteout::textures::png::Parser::frame, py::arg("index"), R"doc(@return animation frame @p index, fully composited to the canvas size as an RGBA8 texture. In lenient mode an out-of-range index yields an empty texture; in strict mode it throws. @param index Zero-based frame index.)doc")
.def("frame_delay_ms", &whiteout::textures::png::Parser::frameDelayMs, py::arg("index"), R"doc(@return display duration of frame @p index in milliseconds. @param index Zero-based frame index.)doc")
.def("frame_info", &whiteout::textures::png::Parser::frameInfo, py::arg("index"), R"doc(@return raw per-frame metadata for frame @p index. @param index Zero-based frame index.)doc")
;
py::class_<whiteout::textures::png::Writer>(m, "PngWriter", R"doc(Encodes a Texture into PNG format.
In addition to single-image PNG, the writer can emit an animated PNG (APNG) from a sequence of frames via `writeAnimated()`. Each frame is written full-canvas with no inter-frame optimisation.)doc")
.def(py::init<>())
.def("write",
[](whiteout::textures::png::Writer& self, const whiteout::textures::Texture& texture) {
auto __v = self.write(texture);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("texture"), R"doc(Serialize the texture to a PNG byte buffer.)doc")
.def("write_animated",
[](whiteout::textures::png::Writer& self, const std::vector<whiteout::textures::png::ApngFrame>& frames, const whiteout::textures::png::ApngSaveOptions& opts) {
auto __v = self.writeAnimated(frames, opts);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("frames"), py::arg("opts") = whiteout::textures::png::ApngSaveOptions{}, R"doc(Serialize a sequence of frames into an animated PNG (APNG) byte buffer.
All frames must share the same dimensions (frame 0 defines the canvas). Each frame is emitted full-canvas with disposal NONE and blend SOURCE. Returns an empty buffer on failure (lenient mode). @param frames Ordered animation frames; must be non-empty. @param opts Encoding options (loop count).)doc")
.def("has_issues", &whiteout::textures::png::Writer::hasIssues, R"doc(@return true if the last write produced any issues.)doc")
.def("get_issues", &whiteout::textures::png::Writer::getIssues, R"doc(@return accumulated issues from the last write call.)doc")
;
py::class_<whiteout::textures::jpeg::Parser>(m, "JpegParser", R"doc(Reads a JPEG file or byte buffer and decodes it into a Texture.)doc")
.def(py::init<>())
.def(py::init<whiteout::interfaces::WorkerPool*>(), py::arg("pool"))
.def("parse",
[](whiteout::textures::jpeg::Parser& self, py::bytes __py_bytes_0) {
std::string __s_0 = __py_bytes_0;
std::span<const whiteout::u8> buffer(reinterpret_cast<const whiteout::u8*>(__s_0.data()), __s_0.size());
return self.parse(buffer);
}, py::arg("buffer"), R"doc(Parse a JPEG byte buffer.)doc")
.def("has_issues", &whiteout::textures::jpeg::Parser::hasIssues, R"doc(@return true if the last parse produced any issues.)doc")
.def("get_issues", &whiteout::textures::jpeg::Parser::getIssues, R"doc(@return accumulated issues from the last parse call.)doc")
;
py::class_<whiteout::textures::jpeg::Writer>(m, "JpegWriter", R"doc(Encodes a Texture into JPEG format.)doc")
.def(py::init<>())
.def(py::init<whiteout::i32, whiteout::interfaces::WorkerPool*, bool>(), py::arg("quality"), py::arg("pool"), py::arg("progressive"))
.def("write",
[](whiteout::textures::jpeg::Writer& self, const whiteout::textures::Texture& texture) {
auto __v = self.write(texture);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("texture"), R"doc(Serialize the texture to a JPEG byte buffer.)doc")
.def("has_issues", &whiteout::textures::jpeg::Writer::hasIssues, R"doc(@return true if the last write produced any issues.)doc")
.def("get_issues", &whiteout::textures::jpeg::Writer::getIssues, R"doc(@return accumulated issues from the last write call.)doc")
;
py::class_<whiteout::textures::dds::Parser>(m, "DdsParser", R"doc(Reads a DDS file or byte buffer and decodes it into a Texture.)doc")
.def(py::init<>())
.def("parse",
[](whiteout::textures::dds::Parser& self, py::bytes __py_bytes_0) {
std::string __s_0 = __py_bytes_0;
std::span<const whiteout::u8> buffer(reinterpret_cast<const whiteout::u8*>(__s_0.data()), __s_0.size());
return self.parse(buffer);
}, py::arg("buffer"), R"doc(Parse a DDS byte buffer.)doc")
.def("has_issues", &whiteout::textures::dds::Parser::hasIssues, R"doc(@return true if the last parse produced any issues.)doc")
.def("get_issues", &whiteout::textures::dds::Parser::getIssues, R"doc(@return accumulated issues from the last parse call.)doc")
;
py::class_<whiteout::textures::dds::Writer>(m, "DdsWriter", R"doc(Encodes a Texture into DDS format.)doc")
.def(py::init<>())
.def("write",
[](whiteout::textures::dds::Writer& self, const whiteout::textures::Texture& texture) {
auto __v = self.write(texture);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("texture"), R"doc(Serialize the texture to a DDS byte buffer.)doc")
.def("has_issues", &whiteout::textures::dds::Writer::hasIssues, R"doc(@return true if the last write produced any issues.)doc")
.def("get_issues", &whiteout::textures::dds::Writer::getIssues, R"doc(@return accumulated issues from the last write call.)doc")
;
py::class_<whiteout::textures::bmp::Parser>(m, "BmpParser", R"doc(Reads a BMP file or byte buffer and decodes it into a Texture.)doc")
.def(py::init<>())
.def("parse",
[](whiteout::textures::bmp::Parser& self, py::bytes __py_bytes_0) {
std::string __s_0 = __py_bytes_0;
std::span<const whiteout::u8> buffer(reinterpret_cast<const whiteout::u8*>(__s_0.data()), __s_0.size());
return self.parse(buffer);
}, py::arg("buffer"), R"doc(Parse a BMP byte buffer.)doc")
.def("has_issues", &whiteout::textures::bmp::Parser::hasIssues, R"doc(@return true if the last parse produced any issues.)doc")
.def("get_issues", &whiteout::textures::bmp::Parser::getIssues, R"doc(@return accumulated issues from the last parse call.)doc")
;
py::class_<whiteout::textures::bmp::Writer>(m, "BmpWriter", R"doc(Encodes a Texture into BMP format.)doc")
.def(py::init<>())
.def("write",
[](whiteout::textures::bmp::Writer& self, const whiteout::textures::Texture& texture) {
auto __v = self.write(texture);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("texture"), R"doc(Serialize the texture to a BMP byte buffer.)doc")
.def("has_issues", &whiteout::textures::bmp::Writer::hasIssues, R"doc(@return true if the last write produced any issues.)doc")
.def("get_issues", &whiteout::textures::bmp::Writer::getIssues, R"doc(@return accumulated issues from the last write call.)doc")
;
py::class_<whiteout::textures::tga::Parser>(m, "TgaParser", R"doc(Reads a TGA file or byte buffer and decodes it into a Texture.)doc")
.def(py::init<>())
.def("parse",
[](whiteout::textures::tga::Parser& self, py::bytes __py_bytes_0) {
std::string __s_0 = __py_bytes_0;
std::span<const whiteout::u8> buffer(reinterpret_cast<const whiteout::u8*>(__s_0.data()), __s_0.size());
return self.parse(buffer);
}, py::arg("buffer"), R"doc(Parse a TGA byte buffer.)doc")
.def("has_issues", &whiteout::textures::tga::Parser::hasIssues, R"doc(@return true if the last parse produced any issues.)doc")
.def("get_issues", &whiteout::textures::tga::Parser::getIssues, R"doc(@return accumulated issues from the last parse call.)doc")
;
py::class_<whiteout::textures::tga::Writer>(m, "TgaWriter", R"doc(Encodes a Texture into TGA format.)doc")
.def(py::init<>())
.def("write",
[](whiteout::textures::tga::Writer& self, const whiteout::textures::Texture& texture) {
auto __v = self.write(texture);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("texture"), R"doc(Serialize the texture to a TGA byte buffer.)doc")
.def("has_issues", &whiteout::textures::tga::Writer::hasIssues, R"doc(@return true if the last write produced any issues.)doc")
.def("get_issues", &whiteout::textures::tga::Writer::getIssues, R"doc(@return accumulated issues from the last write call.)doc")
;
py::class_<whiteout::textures::tiff::Parser>(m, "TiffParser", R"doc(Reads a TIFF file or byte buffer and decodes it into a Texture.)doc")
.def(py::init<>())
.def("parse",
[](whiteout::textures::tiff::Parser& self, py::bytes __py_bytes_0) {
std::string __s_0 = __py_bytes_0;
std::span<const whiteout::u8> buffer(reinterpret_cast<const whiteout::u8*>(__s_0.data()), __s_0.size());
return self.parse(buffer);
}, py::arg("buffer"), R"doc(Parse a TIFF byte buffer.)doc")
.def("has_issues", &whiteout::textures::tiff::Parser::hasIssues, R"doc(@return true if the last parse produced any issues.)doc")
.def("get_issues", &whiteout::textures::tiff::Parser::getIssues, R"doc(@return accumulated issues from the last parse call.)doc")
;
py::class_<whiteout::textures::tiff::Writer>(m, "TiffWriter", R"doc(Encodes a Texture into TIFF format.)doc")
.def(py::init<>())
.def("write",
[](whiteout::textures::tiff::Writer& self, const whiteout::textures::Texture& texture) {
auto __v = self.write(texture);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("texture"), R"doc(Serialize the texture to a TIFF byte buffer.)doc")
.def("has_issues", &whiteout::textures::tiff::Writer::hasIssues, R"doc(@return true if the last write produced any issues.)doc")
.def("get_issues", &whiteout::textures::tiff::Writer::getIssues, R"doc(@return accumulated issues from the last write call.)doc")
;
py::class_<whiteout::textures::gif::Writer>(m, "GifWriter", R"doc(Encodes a sequence of Texture frames into GIF89a format.
Unlike the single-image writers (BMP, TGA, …), this writer accepts a vector of frames. It does **not** inherit from `textures::Writer`.)doc")
.def(py::init<>())
.def(py::init<whiteout::interfaces::WorkerPool*>(), py::arg("pool"))
.def("write", py::overload_cast<const std::string&, const std::vector<whiteout::textures::Texture>&>(&whiteout::textures::gif::Writer::write), py::arg("filePath"), py::arg("frames"), R"doc(Write frames to a GIF file on disk using default options.)doc")
.def("write",
[](whiteout::textures::gif::Writer& self, const std::vector<whiteout::textures::Texture>& frames) {
auto __v = self.write(frames);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("frames"), R"doc(Write frames to a GIF byte buffer using default options.)doc")
.def("write", py::overload_cast<const std::string&, const std::vector<whiteout::textures::Texture>&, const whiteout::textures::gif::SaveOptions&>(&whiteout::textures::gif::Writer::write), py::arg("filePath"), py::arg("frames"), py::arg("opts"), R"doc(Write frames to a GIF file on disk with explicit options.)doc")
.def("write",
[](whiteout::textures::gif::Writer& self, const std::vector<whiteout::textures::Texture>& frames, const whiteout::textures::gif::SaveOptions& opts) {
auto __v = self.write(frames, opts);
return py::bytes(
reinterpret_cast<const char*>(__v.data()), __v.size());
}, py::arg("frames"), py::arg("opts"), R"doc(Write frames to a GIF byte buffer with explicit options.)doc")
.def("has_issues", &whiteout::textures::gif::Writer::hasIssues, R"doc(@return true if the last write produced any issues.)doc")
.def("get_issues", &whiteout::textures::gif::Writer::getIssues, R"doc(@return accumulated issues from the last write call.)doc")
;
py::bind_vector<std::vector<whiteout::textures::Channel>>(m, "VectorChannel");
py::bind_vector<std::vector<whiteout::textures::Texture>>(m, "VectorTexture");
py::bind_vector<std::vector<whiteout::textures::png::ApngFrame>>(m, "VectorApngFrame");
}