Skip to main content

whiteout/
textures.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Fernando Sahmkow
3// AUTOGENERATED by tools/codegen/emit_rust.py — do not edit.
4// Regenerate via:  python -m tools.codegen.codegen textures --backend rust
5
6#![allow(clippy::too_many_arguments)]
7
8// Which of these a module needs depends on its shapes; the modules that
9// have no span accessors would otherwise trip the unused-import lint.
10#[allow(unused_imports)]
11use crate::support::{BorrowedSlice, Bytes};
12
13/// GPU pixel / block-compression format.
14///
15/// Uncompressed formats store one pixel per "block"; BCn formats store a 4×4 pixel tile per block.
16#[repr(i32)]
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub enum PixelFormat {
19    /// 8-bit single channel (1 byte per pixel).
20    R8 = 0,
21    /// 16-bit single channel UNORM (2 bytes per pixel).
22    R16 = 1,
23    /// Single-precision single channel (4 bytes per pixel).
24    R32F = 2,
25    /// 8-bit dual channel (2 bytes per pixel).
26    RG8 = 3,
27    /// 16-bit dual channel UNORM (4 bytes per pixel).
28    RG16 = 4,
29    /// Single-precision dual channel (8 bytes per pixel).
30    RG32F = 5,
31    /// 8-bit RGBA (4 bytes per pixel).
32    RGBA8 = 6,
33    /// 16-bit RGBA UNORM (8 bytes per pixel).
34    RGBA16 = 7,
35    /// Single-precision RGBA (16 bytes per pixel).
36    RGBA32F = 8,
37    /// DXT1 – 8 bytes per 4×4 block (RGB + optional 1-bit alpha).
38    BC1 = 9,
39    /// DXT3 – 16 bytes per 4×4 block (explicit 4-bit alpha).
40    BC2 = 10,
41    /// DXT5 – 16 bytes per 4×4 block (interpolated alpha).
42    BC3 = 11,
43    /// Single-channel – 8 bytes per 4×4 block.
44    BC4 = 12,
45    /// Dual-channel – 16 bytes per 4×4 block.
46    BC5 = 13,
47    /// HDR RGB – 16 bytes per 4×4 block (half-float output).
48    BC6H = 14,
49    /// High-quality RGBA – 16 bytes per 4×4 block.
50    BC7 = 15,
51}
52
53impl TryFrom<i32> for PixelFormat {
54    type Error = crate::Error;
55    fn try_from(v: i32) -> Result<Self, crate::Error> {
56        match v {
57            0 => Ok(PixelFormat::R8),
58            1 => Ok(PixelFormat::R16),
59            2 => Ok(PixelFormat::R32F),
60            3 => Ok(PixelFormat::RG8),
61            4 => Ok(PixelFormat::RG16),
62            5 => Ok(PixelFormat::RG32F),
63            6 => Ok(PixelFormat::RGBA8),
64            7 => Ok(PixelFormat::RGBA16),
65            8 => Ok(PixelFormat::RGBA32F),
66            9 => Ok(PixelFormat::BC1),
67            10 => Ok(PixelFormat::BC2),
68            11 => Ok(PixelFormat::BC3),
69            12 => Ok(PixelFormat::BC4),
70            13 => Ok(PixelFormat::BC5),
71            14 => Ok(PixelFormat::BC6H),
72            15 => Ok(PixelFormat::BC7),
73            other => Err(crate::Error::UnknownEnum {
74                name: "PixelFormat",
75                value: other,
76            }),
77        }
78    }
79}
80
81/// Semantic role of a texture in a material.
82#[repr(i32)]
83#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
84pub enum TextureKind {
85    /// Unknown or application-specific usage.
86    Other = 0,
87    /// Diffuse / base colour (legacy).
88    Diffuse = 1,
89    /// Tangent-space normal map.
90    Normal = 2,
91    /// Specular intensity / colour.
92    Specular = 3,
93    /// ORM packed texture (R=AO, G=Roughness, B=Metalness, A=Unused).
94    ORM = 4,
95    /// PBR base colour (albedo).
96    Albedo = 5,
97    /// Roughness (single channel).
98    Roughness = 6,
99    /// Metalness (single channel).
100    Metalness = 7,
101    /// Ambient occlusion (single channel).
102    AmbientOcclusion = 8,
103    /// Gloss / smoothness (single channel).
104    Gloss = 9,
105    /// Emissive colour / intensity.
106    Emissive = 10,
107    /// Opacity / alpha mask (single channel, linear).
108    AlphaMask = 11,
109    /// Hard binary mask (0 or 1); alpha-coverage-preserving filter.
110    BinaryMask = 12,
111    /// Smooth transparency mask; alpha-coverage-preserving, continuous values.
112    TransparencyMask = 13,
113    /// Blend weight mask; alpha-coverage-preserving with soft transitions.
114    BlendMask = 14,
115    /// Lightmap or baked light contribution (HDR colour).
116    Lightmap = 15,
117    /// Environment / reflection map (equirectangular, GGX prefiltered).
118    EnvironmentPBR = 16,
119    /// Environment map (equirectangular, spherical Kaiser-filtered).
120    EnvironmentLegacy = 17,
121    /// Packed multi-channel texture where each channel carries a distinct semantic role.  Use setChannelKind() / channelKind() to assign and query the per-channel kinds.  generateMipmaps() will apply a kind-appropriate filter to every channel independently.
122    Multikind = 18,
123    /// Channel is not used and carries no semantic meaning.  Only valid as a per-channel kind on a Multikind texture (set via setChannelKind()). generateMipmaps() applies a plain box filter to Unused channels.
124    Unused = 19,
125}
126
127impl TryFrom<i32> for TextureKind {
128    type Error = crate::Error;
129    fn try_from(v: i32) -> Result<Self, crate::Error> {
130        match v {
131            0 => Ok(TextureKind::Other),
132            1 => Ok(TextureKind::Diffuse),
133            2 => Ok(TextureKind::Normal),
134            3 => Ok(TextureKind::Specular),
135            4 => Ok(TextureKind::ORM),
136            5 => Ok(TextureKind::Albedo),
137            6 => Ok(TextureKind::Roughness),
138            7 => Ok(TextureKind::Metalness),
139            8 => Ok(TextureKind::AmbientOcclusion),
140            9 => Ok(TextureKind::Gloss),
141            10 => Ok(TextureKind::Emissive),
142            11 => Ok(TextureKind::AlphaMask),
143            12 => Ok(TextureKind::BinaryMask),
144            13 => Ok(TextureKind::TransparencyMask),
145            14 => Ok(TextureKind::BlendMask),
146            15 => Ok(TextureKind::Lightmap),
147            16 => Ok(TextureKind::EnvironmentPBR),
148            17 => Ok(TextureKind::EnvironmentLegacy),
149            18 => Ok(TextureKind::Multikind),
150            19 => Ok(TextureKind::Unused),
151            other => Err(crate::Error::UnknownEnum {
152                name: "TextureKind",
153                value: other,
154            }),
155        }
156    }
157}
158
159/// Dimensionality / topology of a texture resource.
160#[repr(i32)]
161#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
162pub enum TextureType {
163    /// Standard 2D image (1 layer).
164    Texture2D = 0,
165    /// Volume texture (depth > 1, depth halves each mip).
166    Texture3D = 1,
167    /// Cube map (6 square layers, one per face).
168    TextureCube = 2,
169    /// Array of 2D images (arraySize layers).
170    Texture2DArray = 3,
171    /// Array of cube maps (6 × arraySize layers).
172    TextureCubeArray = 4,
173}
174
175impl TryFrom<i32> for TextureType {
176    type Error = crate::Error;
177    fn try_from(v: i32) -> Result<Self, crate::Error> {
178        match v {
179            0 => Ok(TextureType::Texture2D),
180            1 => Ok(TextureType::Texture3D),
181            2 => Ok(TextureType::TextureCube),
182            3 => Ok(TextureType::Texture2DArray),
183            4 => Ok(TextureType::TextureCubeArray),
184            other => Err(crate::Error::UnknownEnum {
185                name: "TextureType",
186                value: other,
187            }),
188        }
189    }
190}
191
192/// Individual colour / data channel within a pixel.
193///
194/// The numeric value matches the zero-based channel index used by every uncompressed PixelFormat (R=0, G=1, B=2, A=3).
195#[repr(i32)]
196#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
197pub enum Channel {
198    /// Red   (or single-channel value for R* formats).
199    R = 0,
200    /// Green (or second channel for RG* formats).
201    G = 1,
202    /// Blue  (RGBA* formats only).
203    B = 2,
204    /// Alpha (RGBA* formats only).
205    A = 3,
206}
207
208impl TryFrom<i32> for Channel {
209    type Error = crate::Error;
210    fn try_from(v: i32) -> Result<Self, crate::Error> {
211        match v {
212            0 => Ok(Channel::R),
213            1 => Ok(Channel::G),
214            2 => Ok(Channel::B),
215            3 => Ok(Channel::A),
216            other => Err(crate::Error::UnknownEnum {
217                name: "Channel",
218                value: other,
219            }),
220        }
221    }
222}
223
224/// An owned list of [`Texture`] produced by the library.
225pub struct TextureList {
226    raw: core::ptr::NonNull<ffi::whiteout_TextureList>,
227}
228
229impl TextureList {
230    /// # Safety
231    /// `raw` must be a live list this value takes ownership of.
232    #[allow(dead_code)]
233    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_TextureList) -> Option<Self> {
234        core::ptr::NonNull::new(raw).map(|raw| TextureList { raw })
235    }
236
237    pub fn len(&self) -> usize {
238        // SAFETY: the list is live for `&self`.
239        unsafe { ffi::whiteout_textures_TextureList_size(self.raw.as_ptr()) }
240    }
241
242    pub fn is_empty(&self) -> bool {
243        self.len() == 0
244    }
245
246    /// Borrow element `index`. `None` when out of range.
247    pub fn get(&self, index: usize) -> Option<crate::support::Ref<'_, Texture>> {
248        if index >= self.len() {
249            return None;
250        }
251        // SAFETY: index checked; the pointer is interior to the list and
252        // is never freed by the `Ref`.
253        unsafe {
254            Some(crate::support::Ref::new(Texture {
255                raw: core::ptr::NonNull::new_unchecked(ffi::whiteout_textures_TextureList_at(
256                    self.raw.as_ptr(),
257                    index,
258                )),
259            }))
260        }
261    }
262
263    pub fn iter(&self) -> impl ExactSizeIterator<Item = crate::support::Ref<'_, Texture>> {
264        (0..self.len()).map(move |i| self.get(i).expect("index below len"))
265    }
266}
267
268impl Drop for TextureList {
269    fn drop(&mut self) {
270        // SAFETY: the list was transferred to us and is freed once.
271        unsafe { ffi::whiteout_textures_TextureList_delete(self.raw.as_ptr()) }
272    }
273}
274
275impl core::fmt::Debug for TextureList {
276    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
277        f.debug_struct("TextureList")
278            .field("len", &self.len())
279            .finish()
280    }
281}
282
283// SAFETY: a plain heap vector with no thread affinity, owned exclusively.
284unsafe impl Send for TextureList {}
285
286/// Describes a single mip level within a Texture's data buffer.
287pub struct MipLevel {
288    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_MipLevel>,
289}
290
291impl Drop for MipLevel {
292    fn drop(&mut self) {
293        // SAFETY: `raw` came from a native constructor and Drop runs once.
294        unsafe { ffi::whiteout_textures_MipLevel_delete(self.raw.as_ptr()) }
295    }
296}
297
298impl MipLevel {
299    /// # Safety
300    /// `raw` must be a live handle this value takes ownership of.
301    #[allow(dead_code)] // used by whichever methods return this type
302    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_MipLevel) -> Option<Self> {
303        core::ptr::NonNull::new(raw).map(|raw| MipLevel { raw })
304    }
305}
306
307// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
308// is deliberately NOT implemented — the C++ types make no documented
309// guarantee about concurrent use, and claiming one we haven't verified
310// would be unsound. See `@bind thread_safe` in the plan.
311unsafe impl Send for MipLevel {}
312
313impl core::fmt::Debug for MipLevel {
314    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
315        f.debug_struct("MipLevel").finish_non_exhaustive()
316    }
317}
318
319impl MipLevel {
320    /// # Panics
321    /// Panics if the native allocation fails.
322    pub fn new() -> Self {
323        // SAFETY: the native constructor returns a live handle; a null here
324        // means the library is unusable.
325        unsafe {
326            let raw = ffi::whiteout_textures_MipLevel_new();
327            Self::from_raw(raw).expect("native MipLevel allocation failed")
328        }
329    }
330
331    /// Width of this mip in pixels.
332    pub fn width(&self) -> u32 {
333        // SAFETY: plain scalar read through a live handle.
334        unsafe { ffi::whiteout_textures_MipLevel_get_width(self.raw.as_ptr()) }
335    }
336
337    pub fn set_width(&mut self, value: u32) {
338        // SAFETY: plain scalar write through a live handle.
339        unsafe { ffi::whiteout_textures_MipLevel_set_width(self.raw.as_ptr(), value) }
340    }
341
342    /// Height of this mip in pixels.
343    pub fn height(&self) -> u32 {
344        // SAFETY: plain scalar read through a live handle.
345        unsafe { ffi::whiteout_textures_MipLevel_get_height(self.raw.as_ptr()) }
346    }
347
348    pub fn set_height(&mut self, value: u32) {
349        // SAFETY: plain scalar write through a live handle.
350        unsafe { ffi::whiteout_textures_MipLevel_set_height(self.raw.as_ptr(), value) }
351    }
352
353    /// Depth of this mip (always 1 for 2D / cube textures).
354    pub fn depth(&self) -> u32 {
355        // SAFETY: plain scalar read through a live handle.
356        unsafe { ffi::whiteout_textures_MipLevel_get_depth(self.raw.as_ptr()) }
357    }
358
359    pub fn set_depth(&mut self, value: u32) {
360        // SAFETY: plain scalar write through a live handle.
361        unsafe { ffi::whiteout_textures_MipLevel_set_depth(self.raw.as_ptr(), value) }
362    }
363
364    /// Byte offset into the Texture data buffer.
365    pub fn offset(&self) -> u64 {
366        // SAFETY: plain scalar read through a live handle.
367        unsafe { ffi::whiteout_textures_MipLevel_get_offset(self.raw.as_ptr()) }
368    }
369
370    pub fn set_offset(&mut self, value: u64) {
371        // SAFETY: plain scalar write through a live handle.
372        unsafe { ffi::whiteout_textures_MipLevel_set_offset(self.raw.as_ptr(), value) }
373    }
374
375    /// Byte size of this mip's data.
376    pub fn size(&self) -> u64 {
377        // SAFETY: plain scalar read through a live handle.
378        unsafe { ffi::whiteout_textures_MipLevel_get_size(self.raw.as_ptr()) }
379    }
380
381    pub fn set_size(&mut self, value: u64) {
382        // SAFETY: plain scalar write through a live handle.
383        unsafe { ffi::whiteout_textures_MipLevel_set_size(self.raw.as_ptr(), value) }
384    }
385}
386
387impl Default for MipLevel {
388    fn default() -> Self {
389        Self::new()
390    }
391}
392
393/// Format-agnostic GPU texture container
394///
395/// 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.
396///
397/// Use the static factory methods (`create2D`, `create3D`, `createCube`) to allocate a new texture, or obtain one from a parser.
398///
399/// Supports in-place and copying format conversion between all PixelFormat values (uncompressed ↔ BCn) via `format()` and `copyAsFormat()`.
400///
401/// Uses the PImpl (Pointer to Implementation) idiom to hide internals.
402pub struct Texture {
403    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_Texture>,
404}
405
406impl Drop for Texture {
407    fn drop(&mut self) {
408        // SAFETY: `raw` came from a native constructor and Drop runs once.
409        unsafe { ffi::whiteout_textures_Texture_delete(self.raw.as_ptr()) }
410    }
411}
412
413impl Texture {
414    /// # Safety
415    /// `raw` must be a live handle this value takes ownership of.
416    #[allow(dead_code)] // used by whichever methods return this type
417    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_Texture) -> Option<Self> {
418        core::ptr::NonNull::new(raw).map(|raw| Texture { raw })
419    }
420}
421
422// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
423// is deliberately NOT implemented — the C++ types make no documented
424// guarantee about concurrent use, and claiming one we haven't verified
425// would be unsound. See `@bind thread_safe` in the plan.
426unsafe impl Send for Texture {}
427
428impl core::fmt::Debug for Texture {
429    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
430        f.debug_struct("Texture").finish_non_exhaustive()
431    }
432}
433
434impl Texture {
435    /// # Panics
436    /// Panics if the native allocation fails.
437    pub fn new() -> Self {
438        // SAFETY: the native constructor returns a live handle; a null here
439        // means the library is unusable.
440        unsafe {
441            let raw = ffi::whiteout_textures_Texture_new();
442            Self::from_raw(raw).expect("native Texture allocation failed")
443        }
444    }
445
446    /// Convert this texture to a new pixel format in-place.
447    ///
448    /// Replaces the internal data with the converted result. Equivalent to `*this = copyAsFormat(new_fmt)`.
449    ///
450    /// @param new_fmt Target pixel format.
451    pub fn convert_to(&mut self, new_fmt: PixelFormat) {
452        // SAFETY: handle is live for the duration of the call.
453        unsafe {
454            ffi::whiteout_textures_Texture_format(self.raw.as_ptr(), new_fmt as i32);
455        }
456    }
457
458    /// @return The pixel format of the stored data.
459    pub fn format(&self) -> PixelFormat {
460        // SAFETY: handle is live for the duration of the call.
461        unsafe {
462            PixelFormat::try_from(ffi::whiteout_textures_Texture_format_overload2(
463                self.raw.as_ptr(),
464            ))
465            .expect("unknown enum discriminant from the native library (ABI version skew)")
466        }
467    }
468
469    /// Return a copy of this texture converted to a different pixel format.
470    ///
471    /// 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.
472    ///
473    /// @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.
474    pub fn copy_as_format(
475        &self,
476        new_fmt: PixelFormat,
477        pool: Option<&crate::interfaces::HostWorkerPool>,
478    ) -> Option<Texture> {
479        // SAFETY: handle is live for the duration of the call.
480        unsafe {
481            Texture::from_raw(ffi::whiteout_textures_Texture_copyAsFormat(
482                self.raw.as_ptr(),
483                new_fmt as i32,
484                pool.map_or(core::ptr::null_mut(), |v| v.as_ptr()),
485            ))
486        }
487    }
488
489    /// Swap two channels in-place across all mip levels and array layers.
490    ///
491    /// Operates directly on the stored pixel data without any intermediate copy. Supports all uncompressed PixelFormats (R*, RG*, RGBA*).
492    ///
493    /// 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).
494    ///
495    /// @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.
496    pub fn swap_channels(&mut self, a: Channel, b: Channel) -> bool {
497        // SAFETY: handle is live for the duration of the call.
498        unsafe {
499            ffi::whiteout_textures_Texture_swapChannels(self.raw.as_ptr(), a as i32, b as i32) != 0
500        }
501    }
502
503    /// Invert a single channel in-place across all mip levels and array layers.
504    ///
505    /// 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).
506    ///
507    /// Operates directly on the stored pixel data without any intermediate copy. Supports all uncompressed PixelFormats (R*, RG*, RGBA*).
508    ///
509    /// 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).
510    ///
511    /// @param ch Channel to invert. @return true on success, false when the operation is not valid for this texture.
512    pub fn invert_channel(&mut self, ch: Channel) -> bool {
513        // SAFETY: handle is live for the duration of the call.
514        unsafe { ffi::whiteout_textures_Texture_invertChannel(self.raw.as_ptr(), ch as i32) != 0 }
515    }
516
517    /// Reconstruct the Z component of a tangent-space normal map in-place.
518    ///
519    /// 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.
520    ///
521    /// 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.
522    ///
523    /// Operates directly on the stored pixel data without any intermediate copy. Supports all uncompressed PixelFormats (R*, RG*, RGBA*).
524    ///
525    /// 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).
526    ///
527    /// @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.
528    pub fn expand_normal(
529        &mut self,
530        x_channel: Channel,
531        y_channel: Channel,
532        z_channel: Channel,
533    ) -> bool {
534        // SAFETY: handle is live for the duration of the call.
535        unsafe {
536            ffi::whiteout_textures_Texture_expandNormal(
537                self.raw.as_ptr(),
538                x_channel as i32,
539                y_channel as i32,
540                z_channel as i32,
541            ) != 0
542        }
543    }
544
545    /// Fill a single channel with a constant value across all mip levels and array layers.
546    ///
547    /// 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).
548    ///
549    /// Returns false for BCn formats or if the channel index exceeds the format's channel count.
550    ///
551    /// @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.
552    pub fn fill_channel(&mut self, target: Channel, value: f32) -> bool {
553        // SAFETY: handle is live for the duration of the call.
554        unsafe {
555            ffi::whiteout_textures_Texture_fillChannel(self.raw.as_ptr(), target as i32, value) != 0
556        }
557    }
558
559    /// Split selected channels into individual single-channel textures.
560    ///
561    /// 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.
562    ///
563    /// Returns std::nullopt if the source is BCn-compressed or if any requested channel index exceeds the source channel count.
564    ///
565    /// @param channels Channels to extract (e.g. {Channel::R, Channel::G}). @return One Texture per requested channel, or std::nullopt on failure.
566    pub fn split_channels(&self, channels: &[Channel]) -> Option<TextureList> {
567        // SAFETY: the native side transfers ownership of
568        // the list; null means the operation produced none.
569        unsafe {
570            TextureList::from_raw(ffi::whiteout_textures_Texture_splitChannels(
571                self.raw.as_ptr(),
572                channels.as_ptr() as *const i32,
573                channels.len(),
574            ))
575        }
576    }
577
578    /// Merge single-channel textures into one multi-channel texture.
579    ///
580    /// 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.
581    ///
582    /// @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.
583    pub fn merge_channels(sources: &[&Texture], target_channels: &[Channel]) -> Option<Texture> {
584        let sources_ptrs: Vec<_> = sources.iter().map(|v| v.raw.as_ptr()).collect();
585        // SAFETY: handle is live for the duration of the call.
586        unsafe {
587            Texture::from_raw(ffi::whiteout_textures_Texture_mergeChannels(
588                sources_ptrs.as_ptr(),
589                sources.len(),
590                target_channels.as_ptr() as *const i32,
591                target_channels.len(),
592            ))
593        }
594    }
595
596    /// Return a copy of a 2-channel normal map expanded to RGBA8.
597    ///
598    /// 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.
599    ///
600    /// @param pool Optional WorkerPool for parallel BCn decode work when the source texture is compressed. @return Expanded RGBA8 texture, or std::nullopt when unsupported.
601    pub fn copy_from_normal_to_rgba(
602        &self,
603        pool: Option<&crate::interfaces::HostWorkerPool>,
604    ) -> Option<Texture> {
605        // SAFETY: handle is live for the duration of the call.
606        unsafe {
607            Texture::from_raw(ffi::whiteout_textures_Texture_copyFromNormalToRGBA(
608                self.raw.as_ptr(),
609                pool.map_or(core::ptr::null_mut(), |v| v.as_ptr()),
610            ))
611        }
612    }
613
614    /// Generate all mip levels from the base image (mip 0).
615    ///
616    /// 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.
617    ///
618    /// 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().
619    ///
620    /// The texture must use an uncompressed pixel format.  BCn textures should be decompressed first.  No-op if the texture has ≤ 1 mip.
621    ///
622    /// @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.
623    pub fn generate_mipmaps(
624        &mut self,
625        new_mip_count: u32,
626        pool: Option<&crate::interfaces::HostWorkerPool>,
627    ) -> Option<String> {
628        // SAFETY: handle is live for the duration of the call.
629        unsafe {
630            crate::support::take_string_opt(ffi::whiteout_textures_Texture_generateMipmaps(
631                self.raw.as_ptr(),
632                new_mip_count,
633                pool.map_or(core::ptr::null_mut(), |v| v.as_ptr()),
634            ))
635        }
636    }
637
638    /// @overload Preserves existing mip count; optional worker pool.
639    pub fn generate_mipmaps_default(
640        &mut self,
641        pool: Option<&crate::interfaces::HostWorkerPool>,
642    ) -> Option<String> {
643        // SAFETY: handle is live for the duration of the call.
644        unsafe {
645            crate::support::take_string_opt(ffi::whiteout_textures_Texture_generateMipmaps_pool(
646                self.raw.as_ptr(),
647                pool.map_or(core::ptr::null_mut(), |v| v.as_ptr()),
648            ))
649        }
650    }
651
652    /// Downscale the texture by dropping leading mip levels.
653    ///
654    /// 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.
655    ///
656    /// The texture must use an uncompressed pixel format (same requirement as generateMipmaps).  Returns an error if @p levels would reduce every dimension to zero.
657    ///
658    /// @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.
659    pub fn downscale(
660        &mut self,
661        levels: u32,
662        pool: Option<&crate::interfaces::HostWorkerPool>,
663    ) -> Option<String> {
664        // SAFETY: handle is live for the duration of the call.
665        unsafe {
666            crate::support::take_string_opt(ffi::whiteout_textures_Texture_downscale(
667                self.raw.as_ptr(),
668                levels,
669                pool.map_or(core::ptr::null_mut(), |v| v.as_ptr()),
670            ))
671        }
672    }
673
674    /// 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.
675    pub fn create_2d(fmt: PixelFormat, width: u32, height: u32, mip_count: u32) -> Option<Texture> {
676        // SAFETY: handle is live for the duration of the call.
677        unsafe {
678            Texture::from_raw(ffi::whiteout_textures_Texture_create2D(
679                fmt as i32, width, height, mip_count,
680            ))
681        }
682    }
683
684    /// 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.
685    pub fn create_3d(
686        fmt: PixelFormat,
687        width: u32,
688        height: u32,
689        depth: u32,
690        mip_count: u32,
691    ) -> Option<Texture> {
692        // SAFETY: handle is live for the duration of the call.
693        unsafe {
694            Texture::from_raw(ffi::whiteout_textures_Texture_create3D(
695                fmt as i32, width, height, depth, mip_count,
696            ))
697        }
698    }
699
700    /// 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.
701    pub fn create_cube(fmt: PixelFormat, size: u32, mip_count: u32) -> Option<Texture> {
702        // SAFETY: handle is live for the duration of the call.
703        unsafe {
704            Texture::from_raw(ffi::whiteout_textures_Texture_createCube(
705                fmt as i32, size, mip_count,
706            ))
707        }
708    }
709
710    /// 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.
711    pub fn create_2d_array(
712        fmt: PixelFormat,
713        width: u32,
714        height: u32,
715        array_size: u32,
716        mip_count: u32,
717    ) -> Option<Texture> {
718        // SAFETY: handle is live for the duration of the call.
719        unsafe {
720            Texture::from_raw(ffi::whiteout_textures_Texture_create2DArray(
721                fmt as i32, width, height, array_size, mip_count,
722            ))
723        }
724    }
725
726    /// 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.
727    pub fn create_cube_array(
728        fmt: PixelFormat,
729        size: u32,
730        array_size: u32,
731        mip_count: u32,
732    ) -> Option<Texture> {
733        // SAFETY: handle is live for the duration of the call.
734        unsafe {
735            Texture::from_raw(ffi::whiteout_textures_Texture_createCubeArray(
736                fmt as i32, size, array_size, mip_count,
737            ))
738        }
739    }
740
741    /// @return The texture dimensionality / topology.
742    pub fn texture_type(&self) -> TextureType {
743        // SAFETY: handle is live for the duration of the call.
744        unsafe {
745            TextureType::try_from(ffi::whiteout_textures_Texture_type(self.raw.as_ptr()))
746                .expect("unknown enum discriminant from the native library (ABI version skew)")
747        }
748    }
749
750    /// @return The semantic kind of this texture.
751    pub fn kind(&self) -> TextureKind {
752        // SAFETY: handle is live for the duration of the call.
753        unsafe {
754            TextureKind::try_from(ffi::whiteout_textures_Texture_kind(self.raw.as_ptr()))
755                .expect("unknown enum discriminant from the native library (ABI version skew)")
756        }
757    }
758
759    /// 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.
760    pub fn set_kind(&mut self, k: TextureKind) {
761        // SAFETY: handle is live for the duration of the call.
762        unsafe {
763            ffi::whiteout_textures_Texture_setKind(self.raw.as_ptr(), k as i32);
764        }
765    }
766
767    /// @return The per-channel kind for channel @p ch.
768    ///
769    /// Only meaningful when kind() == TextureKind::Multikind. Returns TextureKind::Other by default for all other kinds. @param ch Channel to query (R/G/B/A).
770    pub fn channel_kind(&self, ch: Channel) -> TextureKind {
771        // SAFETY: handle is live for the duration of the call.
772        unsafe {
773            TextureKind::try_from(ffi::whiteout_textures_Texture_channelKind(
774                self.raw.as_ptr(),
775                ch as i32,
776            ))
777            .expect("unknown enum discriminant from the native library (ABI version skew)")
778        }
779    }
780
781    /// Set the per-channel kind for channel @p ch.
782    ///
783    /// 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.
784    pub fn set_channel_kind(&mut self, ch: Channel, kind: TextureKind) {
785        // SAFETY: handle is live for the duration of the call.
786        unsafe {
787            ffi::whiteout_textures_Texture_setChannelKind(
788                self.raw.as_ptr(),
789                ch as i32,
790                kind as i32,
791            );
792        }
793    }
794
795    /// @return The default fill value for channel @p ch.
796    ///
797    /// 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).
798    pub fn channel_default(&self, ch: Channel) -> f32 {
799        // SAFETY: handle is live for the duration of the call.
800        unsafe { ffi::whiteout_textures_Texture_channelDefault(self.raw.as_ptr(), ch as i32) }
801    }
802
803    /// Set the default fill value for channel @p ch.
804    ///
805    /// 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.
806    pub fn set_channel_default(&mut self, ch: Channel, value: f32) {
807        // SAFETY: handle is live for the duration of the call.
808        unsafe {
809            ffi::whiteout_textures_Texture_setChannelDefault(self.raw.as_ptr(), ch as i32, value);
810        }
811    }
812
813    /// @return True if the texture data is in sRGB colour space.
814    pub fn is_srgb(&self) -> bool {
815        // SAFETY: handle is live for the duration of the call.
816        unsafe { ffi::whiteout_textures_Texture_isSrgb(self.raw.as_ptr()) != 0 }
817    }
818
819    /// Mark the texture as sRGB or linear.
820    pub fn set_srgb(&mut self, srgb: bool) {
821        // SAFETY: handle is live for the duration of the call.
822        unsafe {
823            ffi::whiteout_textures_Texture_setSrgb(self.raw.as_ptr(), if srgb { 1 } else { 0 });
824        }
825    }
826
827    /// @return Base mip width in pixels.
828    pub fn width(&self) -> u32 {
829        // SAFETY: handle is live for the duration of the call.
830        unsafe { ffi::whiteout_textures_Texture_width(self.raw.as_ptr()) }
831    }
832
833    /// @return Base mip height in pixels.
834    pub fn height(&self) -> u32 {
835        // SAFETY: handle is live for the duration of the call.
836        unsafe { ffi::whiteout_textures_Texture_height(self.raw.as_ptr()) }
837    }
838
839    /// @return Base mip depth (1 for 2D / cube textures).
840    pub fn depth(&self) -> u32 {
841        // SAFETY: handle is live for the duration of the call.
842        unsafe { ffi::whiteout_textures_Texture_depth(self.raw.as_ptr()) }
843    }
844
845    /// @return Number of array layers. - Texture2D / Texture3D: 1. - TextureCube: 6. - Texture2DArray: arraySize(). - TextureCubeArray: 6 × arraySize().
846    pub fn layer_count(&self) -> u32 {
847        // SAFETY: handle is live for the duration of the call.
848        unsafe { ffi::whiteout_textures_Texture_layerCount(self.raw.as_ptr()) }
849    }
850
851    /// @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).
852    pub fn array_size(&self) -> u32 {
853        // SAFETY: handle is live for the duration of the call.
854        unsafe { ffi::whiteout_textures_Texture_arraySize(self.raw.as_ptr()) }
855    }
856
857    /// @return Number of mip levels per layer.
858    pub fn mip_count(&self) -> u32 {
859        // SAFETY: handle is live for the duration of the call.
860        unsafe { ffi::whiteout_textures_Texture_mipCount(self.raw.as_ptr()) }
861    }
862
863    /// 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.
864    pub fn mip_level(&self, mip: u32, layer: u32) -> Option<crate::support::Ref<'_, MipLevel>> {
865        // SAFETY: the native side returns an interior
866        // pointer borrowed from `self`; `Ref` derefs to it
867        // and never frees it.
868        unsafe {
869            core::ptr::NonNull::new(ffi::whiteout_textures_Texture_mipLevel(
870                self.raw.as_ptr(),
871                mip,
872                layer,
873            ))
874            .map(|raw| crate::support::Ref::new(MipLevel { raw }))
875        }
876    }
877
878    /// @return Total byte size of the pixel-data buffer.
879    pub fn data_size(&self) -> u64 {
880        // SAFETY: handle is live for the duration of the call.
881        unsafe { ffi::whiteout_textures_Texture_dataSize(self.raw.as_ptr()) }
882    }
883
884    /// @return Read-only span over the entire pixel-data buffer.
885    pub fn data(&self) -> BorrowedSlice<'_> {
886        let mut __size: usize = 0;
887        // SAFETY: the returned pointer borrows `self`; the
888        // lifetime on BorrowedSlice keeps it from outliving us.
889        unsafe {
890            let __b = ffi::whiteout_textures_Texture_data(self.raw.as_ptr());
891            __size = __b.size;
892            BorrowedSlice::new(__b.data, __size)
893        }
894    }
895
896    /// Get a read-only span for a specific mip / layer. @param mip   Mip level index. @param layer Array layer (default 0).
897    pub fn mip_data(&self, mip: u32, layer: u32) -> BorrowedSlice<'_> {
898        let mut __size: usize = 0;
899        // SAFETY: the returned pointer borrows `self`; the
900        // lifetime on BorrowedSlice keeps it from outliving us.
901        unsafe {
902            let __b = ffi::whiteout_textures_Texture_mipData(self.raw.as_ptr(), mip, layer);
903            __size = __b.size;
904            BorrowedSlice::new(__b.data, __size)
905        }
906    }
907
908    /// Move the data vector out of the texture (destructive).
909    ///
910    /// After this call the texture's dimensions and mip chain are cleared. @return The owned pixel-data buffer.
911    pub fn take_data(&mut self) -> Bytes {
912        // SAFETY: handle is live for the duration of the call.
913        unsafe {
914            Bytes::from_raw(ffi::whiteout_textures_Texture_takeData(self.raw.as_ptr()))
915                .unwrap_or_else(Bytes::empty)
916        }
917    }
918
919    /// Replace the pixel-data buffer.
920    ///
921    /// The new buffer must match the existing allocation size. @param new_data Replacement data.
922    pub fn set_data(&mut self, new_data: &[u8]) {
923        // SAFETY: handle is live for the duration of the call.
924        unsafe {
925            ffi::whiteout_textures_Texture_setData(
926                self.raw.as_ptr(),
927                new_data.as_ptr(),
928                new_data.len(),
929            );
930        }
931    }
932}
933
934impl Texture {
935    /// Mutable, zero-copy view of the underlying buffer.
936    ///
937    /// Writes land directly in the C++ allocation — nothing is marshalled.
938    /// The borrow of `self` is what makes that safe: the buffer cannot be
939    /// resized or freed while this slice exists.
940    pub fn data_mut(&mut self) -> &mut [u8] {
941        let mut size: usize = 0;
942        // SAFETY: the pointer borrows `self` mutably for the returned
943        // lifetime, so no aliasing or reallocation can occur meanwhile.
944        unsafe {
945            let p = tier_a::whiteout_v_Texture_data_mut(self.raw.as_ptr().cast(), &mut size);
946            if p.is_null() || size == 0 {
947                &mut []
948            } else {
949                core::slice::from_raw_parts_mut(p, size)
950            }
951        }
952    }
953
954    /// Mutable, zero-copy view of the underlying buffer.
955    ///
956    /// Writes land directly in the C++ allocation — nothing is marshalled.
957    /// The borrow of `self` is what makes that safe: the buffer cannot be
958    /// resized or freed while this slice exists.
959    pub fn mip_data_mut(&mut self, mip: u32, layer: u32) -> &mut [u8] {
960        let mut size: usize = 0;
961        // SAFETY: the pointer borrows `self` mutably for the returned
962        // lifetime, so no aliasing or reallocation can occur meanwhile.
963        unsafe {
964            let p = tier_a::whiteout_v_Texture_mipData_mut(
965                self.raw.as_ptr().cast(),
966                mip,
967                layer,
968                &mut size,
969            );
970            if p.is_null() || size == 0 {
971                &mut []
972            } else {
973                core::slice::from_raw_parts_mut(p, size)
974            }
975        }
976    }
977}
978
979impl Default for Texture {
980    fn default() -> Self {
981        Self::new()
982    }
983}
984
985/// Parser for BLP texture files
986///
987/// 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.
988///
989/// Uses the PImpl (Pointer to Implementation) idiom to hide implementation details.
990pub struct BlpParser {
991    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_BlpParser>,
992}
993
994impl Drop for BlpParser {
995    fn drop(&mut self) {
996        // SAFETY: `raw` came from a native constructor and Drop runs once.
997        unsafe { ffi::whiteout_textures_BlpParser_delete(self.raw.as_ptr()) }
998    }
999}
1000
1001impl BlpParser {
1002    /// # Safety
1003    /// `raw` must be a live handle this value takes ownership of.
1004    #[allow(dead_code)] // used by whichever methods return this type
1005    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_BlpParser) -> Option<Self> {
1006        core::ptr::NonNull::new(raw).map(|raw| BlpParser { raw })
1007    }
1008}
1009
1010// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1011// is deliberately NOT implemented — the C++ types make no documented
1012// guarantee about concurrent use, and claiming one we haven't verified
1013// would be unsound. See `@bind thread_safe` in the plan.
1014unsafe impl Send for BlpParser {}
1015
1016impl core::fmt::Debug for BlpParser {
1017    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1018        f.debug_struct("BlpParser").finish_non_exhaustive()
1019    }
1020}
1021
1022impl BlpParser {
1023    /// # Panics
1024    /// Panics if the native allocation fails.
1025    pub fn new() -> Self {
1026        // SAFETY: the native constructor returns a live handle; a null here
1027        // means the library is unusable.
1028        unsafe {
1029            let raw = ffi::whiteout_textures_BlpParser_new();
1030            Self::from_raw(raw).expect("native BlpParser allocation failed")
1031        }
1032    }
1033
1034    /// 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
1035    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
1036        // SAFETY: handle is live for the duration of the call.
1037        unsafe {
1038            Texture::from_raw(ffi::whiteout_textures_BlpParser_parse(
1039                self.raw.as_ptr(),
1040                buffer.as_ptr(),
1041                buffer.len(),
1042            ))
1043        }
1044    }
1045
1046    /// Check if parsing encountered any issues @return True if there were warnings or recoverable errors
1047    pub fn has_issues(&self) -> bool {
1048        // SAFETY: handle is live for the duration of the call.
1049        unsafe { ffi::whiteout_textures_BlpParser_hasIssues(self.raw.as_ptr()) != 0 }
1050    }
1051
1052    /// Get list of issues encountered during parsing @return Vector of issue description strings
1053    pub fn issues(&self) -> Vec<String> {
1054        // SAFETY: index stays below the reported count.
1055        unsafe {
1056            let n = ffi::whiteout_textures_BlpParser_getIssues_count(self.raw.as_ptr());
1057            (0..n)
1058                .map(|i| {
1059                    crate::support::take_string(ffi::whiteout_textures_BlpParser_getIssues_at(
1060                        self.raw.as_ptr(),
1061                        i,
1062                    ))
1063                })
1064                .collect()
1065        }
1066    }
1067}
1068
1069impl Default for BlpParser {
1070    fn default() -> Self {
1071        Self::new()
1072    }
1073}
1074
1075/// Writer for BLP texture files
1076///
1077/// The Writer takes a Texture and encodes it into BLP1 or BLP2 binary format. It supports palettized, JPEG, DXT, and BGRA encodings.
1078///
1079/// Uses the PImpl (Pointer to Implementation) idiom to hide implementation details.
1080pub struct BlpWriter {
1081    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_BlpWriter>,
1082}
1083
1084impl Drop for BlpWriter {
1085    fn drop(&mut self) {
1086        // SAFETY: `raw` came from a native constructor and Drop runs once.
1087        unsafe { ffi::whiteout_textures_BlpWriter_delete(self.raw.as_ptr()) }
1088    }
1089}
1090
1091impl BlpWriter {
1092    /// # Safety
1093    /// `raw` must be a live handle this value takes ownership of.
1094    #[allow(dead_code)] // used by whichever methods return this type
1095    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_BlpWriter) -> Option<Self> {
1096        core::ptr::NonNull::new(raw).map(|raw| BlpWriter { raw })
1097    }
1098}
1099
1100// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1101// is deliberately NOT implemented — the C++ types make no documented
1102// guarantee about concurrent use, and claiming one we haven't verified
1103// would be unsound. See `@bind thread_safe` in the plan.
1104unsafe impl Send for BlpWriter {}
1105
1106impl core::fmt::Debug for BlpWriter {
1107    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1108        f.debug_struct("BlpWriter").finish_non_exhaustive()
1109    }
1110}
1111
1112impl BlpWriter {
1113    /// # Panics
1114    /// Panics if the native allocation fails.
1115    pub fn new() -> Self {
1116        // SAFETY: the native constructor returns a live handle; a null here
1117        // means the library is unusable.
1118        unsafe {
1119            let raw = ffi::whiteout_textures_BlpWriter_new();
1120            Self::from_raw(raw).expect("native BlpWriter allocation failed")
1121        }
1122    }
1123
1124    /// Write a BLP file to a byte buffer with default options
1125    pub fn write(&mut self, texture: &Texture) -> Bytes {
1126        // SAFETY: handle is live for the duration of the call.
1127        unsafe {
1128            Bytes::from_raw(ffi::whiteout_textures_BlpWriter_write(
1129                self.raw.as_ptr(),
1130                texture.raw.as_ptr(),
1131            ))
1132            .unwrap_or_else(Bytes::empty)
1133        }
1134    }
1135
1136    /// Check if writing encountered any issues @return True if there were warnings or recoverable errors
1137    pub fn has_issues(&self) -> bool {
1138        // SAFETY: handle is live for the duration of the call.
1139        unsafe { ffi::whiteout_textures_BlpWriter_hasIssues(self.raw.as_ptr()) != 0 }
1140    }
1141
1142    /// Get list of issues encountered during writing @return Vector of issue description strings
1143    pub fn issues(&self) -> Vec<String> {
1144        // SAFETY: index stays below the reported count.
1145        unsafe {
1146            let n = ffi::whiteout_textures_BlpWriter_getIssues_count(self.raw.as_ptr());
1147            (0..n)
1148                .map(|i| {
1149                    crate::support::take_string(ffi::whiteout_textures_BlpWriter_getIssues_at(
1150                        self.raw.as_ptr(),
1151                        i,
1152                    ))
1153                })
1154                .collect()
1155        }
1156    }
1157}
1158
1159impl Default for BlpWriter {
1160    fn default() -> Self {
1161        Self::new()
1162    }
1163}
1164
1165/// Per-frame metadata for an animated PNG (APNG).
1166pub struct PngApngFrameInfo {
1167    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_PngApngFrameInfo>,
1168}
1169
1170impl Drop for PngApngFrameInfo {
1171    fn drop(&mut self) {
1172        // SAFETY: `raw` came from a native constructor and Drop runs once.
1173        unsafe { ffi::whiteout_textures_PngApngFrameInfo_delete(self.raw.as_ptr()) }
1174    }
1175}
1176
1177impl PngApngFrameInfo {
1178    /// # Safety
1179    /// `raw` must be a live handle this value takes ownership of.
1180    #[allow(dead_code)] // used by whichever methods return this type
1181    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_PngApngFrameInfo) -> Option<Self> {
1182        core::ptr::NonNull::new(raw).map(|raw| PngApngFrameInfo { raw })
1183    }
1184}
1185
1186// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1187// is deliberately NOT implemented — the C++ types make no documented
1188// guarantee about concurrent use, and claiming one we haven't verified
1189// would be unsound. See `@bind thread_safe` in the plan.
1190unsafe impl Send for PngApngFrameInfo {}
1191
1192impl core::fmt::Debug for PngApngFrameInfo {
1193    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1194        f.debug_struct("PngApngFrameInfo").finish_non_exhaustive()
1195    }
1196}
1197
1198impl PngApngFrameInfo {
1199    /// # Panics
1200    /// Panics if the native allocation fails.
1201    pub fn new() -> Self {
1202        // SAFETY: the native constructor returns a live handle; a null here
1203        // means the library is unusable.
1204        unsafe {
1205            let raw = ffi::whiteout_textures_PngApngFrameInfo_new();
1206            Self::from_raw(raw).expect("native PngApngFrameInfo allocation failed")
1207        }
1208    }
1209
1210    /// Frame sub-rectangle width.
1211    pub fn width(&self) -> u32 {
1212        // SAFETY: plain scalar read through a live handle.
1213        unsafe { ffi::whiteout_textures_PngApngFrameInfo_get_width(self.raw.as_ptr()) }
1214    }
1215
1216    pub fn set_width(&mut self, value: u32) {
1217        // SAFETY: plain scalar write through a live handle.
1218        unsafe { ffi::whiteout_textures_PngApngFrameInfo_set_width(self.raw.as_ptr(), value) }
1219    }
1220
1221    /// Frame sub-rectangle height.
1222    pub fn height(&self) -> u32 {
1223        // SAFETY: plain scalar read through a live handle.
1224        unsafe { ffi::whiteout_textures_PngApngFrameInfo_get_height(self.raw.as_ptr()) }
1225    }
1226
1227    pub fn set_height(&mut self, value: u32) {
1228        // SAFETY: plain scalar write through a live handle.
1229        unsafe { ffi::whiteout_textures_PngApngFrameInfo_set_height(self.raw.as_ptr(), value) }
1230    }
1231
1232    /// Frame sub-rectangle X offset on the canvas.
1233    pub fn x_offset(&self) -> u32 {
1234        // SAFETY: plain scalar read through a live handle.
1235        unsafe { ffi::whiteout_textures_PngApngFrameInfo_get_xOffset(self.raw.as_ptr()) }
1236    }
1237
1238    pub fn set_x_offset(&mut self, value: u32) {
1239        // SAFETY: plain scalar write through a live handle.
1240        unsafe { ffi::whiteout_textures_PngApngFrameInfo_set_xOffset(self.raw.as_ptr(), value) }
1241    }
1242
1243    /// Frame sub-rectangle Y offset on the canvas.
1244    pub fn y_offset(&self) -> u32 {
1245        // SAFETY: plain scalar read through a live handle.
1246        unsafe { ffi::whiteout_textures_PngApngFrameInfo_get_yOffset(self.raw.as_ptr()) }
1247    }
1248
1249    pub fn set_y_offset(&mut self, value: u32) {
1250        // SAFETY: plain scalar write through a live handle.
1251        unsafe { ffi::whiteout_textures_PngApngFrameInfo_set_yOffset(self.raw.as_ptr(), value) }
1252    }
1253
1254    /// Frame display duration in milliseconds.
1255    pub fn delay_ms(&self) -> u32 {
1256        // SAFETY: plain scalar read through a live handle.
1257        unsafe { ffi::whiteout_textures_PngApngFrameInfo_get_delayMs(self.raw.as_ptr()) }
1258    }
1259
1260    pub fn set_delay_ms(&mut self, value: u32) {
1261        // SAFETY: plain scalar write through a live handle.
1262        unsafe { ffi::whiteout_textures_PngApngFrameInfo_set_delayMs(self.raw.as_ptr(), value) }
1263    }
1264
1265    /// 0 = NONE, 1 = BACKGROUND, 2 = PREVIOUS.
1266    pub fn dispose_op(&self) -> u32 {
1267        // SAFETY: plain scalar read through a live handle.
1268        unsafe { ffi::whiteout_textures_PngApngFrameInfo_get_disposeOp(self.raw.as_ptr()) }
1269    }
1270
1271    pub fn set_dispose_op(&mut self, value: u32) {
1272        // SAFETY: plain scalar write through a live handle.
1273        unsafe { ffi::whiteout_textures_PngApngFrameInfo_set_disposeOp(self.raw.as_ptr(), value) }
1274    }
1275
1276    /// 0 = SOURCE, 1 = OVER.
1277    pub fn blend_op(&self) -> u32 {
1278        // SAFETY: plain scalar read through a live handle.
1279        unsafe { ffi::whiteout_textures_PngApngFrameInfo_get_blendOp(self.raw.as_ptr()) }
1280    }
1281
1282    pub fn set_blend_op(&mut self, value: u32) {
1283        // SAFETY: plain scalar write through a live handle.
1284        unsafe { ffi::whiteout_textures_PngApngFrameInfo_set_blendOp(self.raw.as_ptr(), value) }
1285    }
1286}
1287
1288impl Default for PngApngFrameInfo {
1289    fn default() -> Self {
1290        Self::new()
1291    }
1292}
1293
1294/// Reads a PNG file or byte buffer and decodes it into a Texture.
1295///
1296/// 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()`.
1297pub struct PngParser {
1298    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_PngParser>,
1299}
1300
1301impl Drop for PngParser {
1302    fn drop(&mut self) {
1303        // SAFETY: `raw` came from a native constructor and Drop runs once.
1304        unsafe { ffi::whiteout_textures_PngParser_delete(self.raw.as_ptr()) }
1305    }
1306}
1307
1308impl PngParser {
1309    /// # Safety
1310    /// `raw` must be a live handle this value takes ownership of.
1311    #[allow(dead_code)] // used by whichever methods return this type
1312    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_PngParser) -> Option<Self> {
1313        core::ptr::NonNull::new(raw).map(|raw| PngParser { raw })
1314    }
1315}
1316
1317// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1318// is deliberately NOT implemented — the C++ types make no documented
1319// guarantee about concurrent use, and claiming one we haven't verified
1320// would be unsound. See `@bind thread_safe` in the plan.
1321unsafe impl Send for PngParser {}
1322
1323impl core::fmt::Debug for PngParser {
1324    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1325        f.debug_struct("PngParser").finish_non_exhaustive()
1326    }
1327}
1328
1329impl PngParser {
1330    /// # Panics
1331    /// Panics if the native allocation fails.
1332    pub fn new() -> Self {
1333        // SAFETY: the native constructor returns a live handle; a null here
1334        // means the library is unusable.
1335        unsafe {
1336            let raw = ffi::whiteout_textures_PngParser_new();
1337            Self::from_raw(raw).expect("native PngParser allocation failed")
1338        }
1339    }
1340
1341    /// Parse a PNG byte buffer.
1342    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
1343        // SAFETY: handle is live for the duration of the call.
1344        unsafe {
1345            Texture::from_raw(ffi::whiteout_textures_PngParser_parse(
1346                self.raw.as_ptr(),
1347                buffer.as_ptr(),
1348                buffer.len(),
1349            ))
1350        }
1351    }
1352
1353    /// @return true if the last parse produced any issues.
1354    pub fn has_issues(&self) -> bool {
1355        // SAFETY: handle is live for the duration of the call.
1356        unsafe { ffi::whiteout_textures_PngParser_hasIssues(self.raw.as_ptr()) != 0 }
1357    }
1358
1359    /// @return accumulated issues from the last parse call.
1360    pub fn issues(&self) -> Vec<String> {
1361        // SAFETY: index stays below the reported count.
1362        unsafe {
1363            let n = ffi::whiteout_textures_PngParser_getIssues_count(self.raw.as_ptr());
1364            (0..n)
1365                .map(|i| {
1366                    crate::support::take_string(ffi::whiteout_textures_PngParser_getIssues_at(
1367                        self.raw.as_ptr(),
1368                        i,
1369                    ))
1370                })
1371                .collect()
1372        }
1373    }
1374
1375    /// @return true if the last parsed PNG carried APNG animation chunks.
1376    pub fn is_animated(&self) -> bool {
1377        // SAFETY: handle is live for the duration of the call.
1378        unsafe { ffi::whiteout_textures_PngParser_isAnimated(self.raw.as_ptr()) != 0 }
1379    }
1380
1381    /// @return number of animation frames (0 when not animated).
1382    pub fn frame_count(&self) -> u32 {
1383        // SAFETY: handle is live for the duration of the call.
1384        unsafe { ffi::whiteout_textures_PngParser_frameCount(self.raw.as_ptr()) }
1385    }
1386
1387    /// @return APNG loop count from the `acTL` chunk; 0 means loop forever.
1388    pub fn loop_count(&self) -> u32 {
1389        // SAFETY: handle is live for the duration of the call.
1390        unsafe { ffi::whiteout_textures_PngParser_loopCount(self.raw.as_ptr()) }
1391    }
1392
1393    /// @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.
1394    pub fn frame(&self, index: u32) -> Option<crate::support::Ref<'_, Texture>> {
1395        // SAFETY: the native side returns an interior
1396        // pointer borrowed from `self`; `Ref` derefs to it
1397        // and never frees it.
1398        unsafe {
1399            core::ptr::NonNull::new(ffi::whiteout_textures_PngParser_frame(
1400                self.raw.as_ptr(),
1401                index,
1402            ))
1403            .map(|raw| crate::support::Ref::new(Texture { raw }))
1404        }
1405    }
1406
1407    /// @return display duration of frame @p index in milliseconds. @param index Zero-based frame index.
1408    pub fn frame_delay_ms(&self, index: u32) -> u32 {
1409        // SAFETY: handle is live for the duration of the call.
1410        unsafe { ffi::whiteout_textures_PngParser_frameDelayMs(self.raw.as_ptr(), index) }
1411    }
1412
1413    /// @return raw per-frame metadata for frame @p index. @param index Zero-based frame index.
1414    pub fn frame_info(&self, index: u32) -> Option<crate::support::Ref<'_, PngApngFrameInfo>> {
1415        // SAFETY: the native side returns an interior
1416        // pointer borrowed from `self`; `Ref` derefs to it
1417        // and never frees it.
1418        unsafe {
1419            core::ptr::NonNull::new(ffi::whiteout_textures_PngParser_frameInfo(
1420                self.raw.as_ptr(),
1421                index,
1422            ))
1423            .map(|raw| crate::support::Ref::new(PngApngFrameInfo { raw }))
1424        }
1425    }
1426}
1427
1428impl Default for PngParser {
1429    fn default() -> Self {
1430        Self::new()
1431    }
1432}
1433
1434/// One frame of an animated PNG (APNG), with its display duration.
1435pub struct PngApngFrame {
1436    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_PngApngFrame>,
1437}
1438
1439impl Drop for PngApngFrame {
1440    fn drop(&mut self) {
1441        // SAFETY: `raw` came from a native constructor and Drop runs once.
1442        unsafe { ffi::whiteout_textures_PngApngFrame_delete(self.raw.as_ptr()) }
1443    }
1444}
1445
1446impl PngApngFrame {
1447    /// # Safety
1448    /// `raw` must be a live handle this value takes ownership of.
1449    #[allow(dead_code)] // used by whichever methods return this type
1450    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_PngApngFrame) -> Option<Self> {
1451        core::ptr::NonNull::new(raw).map(|raw| PngApngFrame { raw })
1452    }
1453}
1454
1455// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1456// is deliberately NOT implemented — the C++ types make no documented
1457// guarantee about concurrent use, and claiming one we haven't verified
1458// would be unsound. See `@bind thread_safe` in the plan.
1459unsafe impl Send for PngApngFrame {}
1460
1461impl core::fmt::Debug for PngApngFrame {
1462    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1463        f.debug_struct("PngApngFrame").finish_non_exhaustive()
1464    }
1465}
1466
1467impl PngApngFrame {
1468    /// # Panics
1469    /// Panics if the native allocation fails.
1470    pub fn new() -> Self {
1471        // SAFETY: the native constructor returns a live handle; a null here
1472        // means the library is unusable.
1473        unsafe {
1474            let raw = ffi::whiteout_textures_PngApngFrame_new();
1475            Self::from_raw(raw).expect("native PngApngFrame allocation failed")
1476        }
1477    }
1478
1479    /// Full-canvas frame image (converted to RGBA8 on write).
1480    /// Borrows the field in place — no copy, no allocation.
1481    pub fn image(&self) -> crate::support::Ref<'_, Texture> {
1482        // SAFETY: an interior pointer into `self`, valid for this
1483        // borrow and never freed by the `Ref`.
1484        unsafe {
1485            crate::support::Ref::new(Texture {
1486                raw: core::ptr::NonNull::new_unchecked(
1487                    ffi::whiteout_textures_PngApngFrame_get_image(self.raw.as_ptr()),
1488                ),
1489            })
1490        }
1491    }
1492
1493    pub fn image_mut(&mut self) -> crate::support::RefMut<'_, Texture> {
1494        // SAFETY: as above; `&mut self` guarantees exclusivity.
1495        unsafe {
1496            crate::support::RefMut::new(Texture {
1497                raw: core::ptr::NonNull::new_unchecked(
1498                    ffi::whiteout_textures_PngApngFrame_get_image(self.raw.as_ptr()),
1499                ),
1500            })
1501        }
1502    }
1503
1504    /// Display duration in milliseconds.
1505    pub fn delay_ms(&self) -> u32 {
1506        // SAFETY: plain scalar read through a live handle.
1507        unsafe { ffi::whiteout_textures_PngApngFrame_get_delayMs(self.raw.as_ptr()) }
1508    }
1509
1510    pub fn set_delay_ms(&mut self, value: u32) {
1511        // SAFETY: plain scalar write through a live handle.
1512        unsafe { ffi::whiteout_textures_PngApngFrame_set_delayMs(self.raw.as_ptr(), value) }
1513    }
1514}
1515
1516impl Default for PngApngFrame {
1517    fn default() -> Self {
1518        Self::new()
1519    }
1520}
1521
1522/// Options controlling animated PNG (APNG) encoding.
1523#[derive(Clone, Debug, PartialEq)]
1524pub struct PngApngSaveOptions {
1525    /// Number of times to loop; 0 means loop forever.
1526    pub loop_count: u32,
1527}
1528
1529impl Default for PngApngSaveOptions {
1530    fn default() -> Self {
1531        // SAFETY: `_new` always returns a live handle; freed before return.
1532        unsafe {
1533            let h = ffi::whiteout_textures_PngApngSaveOptions_new();
1534            let out = PngApngSaveOptions {
1535                loop_count: ffi::whiteout_textures_PngApngSaveOptions_get_loopCount(h),
1536            };
1537            ffi::whiteout_textures_PngApngSaveOptions_delete(h);
1538            out
1539        }
1540    }
1541}
1542
1543impl PngApngSaveOptions {
1544    /// Build a native handle carrying these values. Caller frees it.
1545    #[allow(dead_code)] // consumed once the methods taking these options bind
1546    pub(crate) unsafe fn to_native(&self) -> *mut ffi::whiteout_PngApngSaveOptions {
1547        unsafe {
1548            let h = ffi::whiteout_textures_PngApngSaveOptions_new();
1549            ffi::whiteout_textures_PngApngSaveOptions_set_loopCount(h, self.loop_count);
1550            h
1551        }
1552    }
1553
1554    /// Free a handle produced by [`Self::to_native`].
1555    ///
1556    /// # Safety
1557    /// `h` must have come from `to_native` and not been freed already.
1558    #[allow(dead_code)]
1559    pub(crate) unsafe fn free_native(h: *mut ffi::whiteout_PngApngSaveOptions) {
1560        unsafe { ffi::whiteout_textures_PngApngSaveOptions_delete(h) }
1561    }
1562}
1563
1564/// Encodes a Texture into PNG format.
1565///
1566/// 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.
1567pub struct PngWriter {
1568    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_PngWriter>,
1569}
1570
1571impl Drop for PngWriter {
1572    fn drop(&mut self) {
1573        // SAFETY: `raw` came from a native constructor and Drop runs once.
1574        unsafe { ffi::whiteout_textures_PngWriter_delete(self.raw.as_ptr()) }
1575    }
1576}
1577
1578impl PngWriter {
1579    /// # Safety
1580    /// `raw` must be a live handle this value takes ownership of.
1581    #[allow(dead_code)] // used by whichever methods return this type
1582    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_PngWriter) -> Option<Self> {
1583        core::ptr::NonNull::new(raw).map(|raw| PngWriter { raw })
1584    }
1585}
1586
1587// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1588// is deliberately NOT implemented — the C++ types make no documented
1589// guarantee about concurrent use, and claiming one we haven't verified
1590// would be unsound. See `@bind thread_safe` in the plan.
1591unsafe impl Send for PngWriter {}
1592
1593impl core::fmt::Debug for PngWriter {
1594    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1595        f.debug_struct("PngWriter").finish_non_exhaustive()
1596    }
1597}
1598
1599impl PngWriter {
1600    /// # Panics
1601    /// Panics if the native allocation fails.
1602    pub fn new() -> Self {
1603        // SAFETY: the native constructor returns a live handle; a null here
1604        // means the library is unusable.
1605        unsafe {
1606            let raw = ffi::whiteout_textures_PngWriter_new();
1607            Self::from_raw(raw).expect("native PngWriter allocation failed")
1608        }
1609    }
1610
1611    /// Serialize the texture to a PNG byte buffer.
1612    pub fn write(&mut self, texture: &Texture) -> Bytes {
1613        // SAFETY: handle is live for the duration of the call.
1614        unsafe {
1615            Bytes::from_raw(ffi::whiteout_textures_PngWriter_write(
1616                self.raw.as_ptr(),
1617                texture.raw.as_ptr(),
1618            ))
1619            .unwrap_or_else(Bytes::empty)
1620        }
1621    }
1622
1623    /// Serialize a sequence of frames into an animated PNG (APNG) byte buffer.
1624    ///
1625    /// 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).
1626    pub fn write_animated(&mut self, frames: &[&PngApngFrame], opts: &PngApngSaveOptions) -> Bytes {
1627        let frames_ptrs: Vec<_> = frames.iter().map(|v| v.raw.as_ptr()).collect();
1628        let opts_native = unsafe { opts.to_native() };
1629        // SAFETY: handle is live for the call; the staged
1630        // option handles are freed immediately after.
1631        unsafe {
1632            let __r = Bytes::from_raw(ffi::whiteout_textures_PngWriter_writeAnimated(
1633                self.raw.as_ptr(),
1634                frames_ptrs.as_ptr(),
1635                frames.len(),
1636                opts_native,
1637            ))
1638            .unwrap_or_else(Bytes::empty);
1639            PngApngSaveOptions::free_native(opts_native);
1640            __r
1641        }
1642    }
1643
1644    /// @return true if the last write produced any issues.
1645    pub fn has_issues(&self) -> bool {
1646        // SAFETY: handle is live for the duration of the call.
1647        unsafe { ffi::whiteout_textures_PngWriter_hasIssues(self.raw.as_ptr()) != 0 }
1648    }
1649
1650    /// @return accumulated issues from the last write call.
1651    pub fn issues(&self) -> Vec<String> {
1652        // SAFETY: index stays below the reported count.
1653        unsafe {
1654            let n = ffi::whiteout_textures_PngWriter_getIssues_count(self.raw.as_ptr());
1655            (0..n)
1656                .map(|i| {
1657                    crate::support::take_string(ffi::whiteout_textures_PngWriter_getIssues_at(
1658                        self.raw.as_ptr(),
1659                        i,
1660                    ))
1661                })
1662                .collect()
1663        }
1664    }
1665}
1666
1667impl Default for PngWriter {
1668    fn default() -> Self {
1669        Self::new()
1670    }
1671}
1672
1673/// Reads a JPEG file or byte buffer and decodes it into a Texture.
1674pub struct JpegParser {
1675    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_JpegParser>,
1676}
1677
1678impl Drop for JpegParser {
1679    fn drop(&mut self) {
1680        // SAFETY: `raw` came from a native constructor and Drop runs once.
1681        unsafe { ffi::whiteout_textures_JpegParser_delete(self.raw.as_ptr()) }
1682    }
1683}
1684
1685impl JpegParser {
1686    /// # Safety
1687    /// `raw` must be a live handle this value takes ownership of.
1688    #[allow(dead_code)] // used by whichever methods return this type
1689    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_JpegParser) -> Option<Self> {
1690        core::ptr::NonNull::new(raw).map(|raw| JpegParser { raw })
1691    }
1692}
1693
1694// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1695// is deliberately NOT implemented — the C++ types make no documented
1696// guarantee about concurrent use, and claiming one we haven't verified
1697// would be unsound. See `@bind thread_safe` in the plan.
1698unsafe impl Send for JpegParser {}
1699
1700impl core::fmt::Debug for JpegParser {
1701    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1702        f.debug_struct("JpegParser").finish_non_exhaustive()
1703    }
1704}
1705
1706impl JpegParser {
1707    /// # Panics
1708    /// Panics if the native allocation fails.
1709    pub fn new() -> Self {
1710        // SAFETY: the native constructor returns a live handle; a null here
1711        // means the library is unusable.
1712        unsafe {
1713            let raw = ffi::whiteout_textures_JpegParser_new();
1714            Self::from_raw(raw).expect("native JpegParser allocation failed")
1715        }
1716    }
1717
1718    /// Parse a JPEG byte buffer.
1719    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
1720        // SAFETY: handle is live for the duration of the call.
1721        unsafe {
1722            Texture::from_raw(ffi::whiteout_textures_JpegParser_parse(
1723                self.raw.as_ptr(),
1724                buffer.as_ptr(),
1725                buffer.len(),
1726            ))
1727        }
1728    }
1729
1730    /// @return true if the last parse produced any issues.
1731    pub fn has_issues(&self) -> bool {
1732        // SAFETY: handle is live for the duration of the call.
1733        unsafe { ffi::whiteout_textures_JpegParser_hasIssues(self.raw.as_ptr()) != 0 }
1734    }
1735
1736    /// @return accumulated issues from the last parse call.
1737    pub fn issues(&self) -> Vec<String> {
1738        // SAFETY: index stays below the reported count.
1739        unsafe {
1740            let n = ffi::whiteout_textures_JpegParser_getIssues_count(self.raw.as_ptr());
1741            (0..n)
1742                .map(|i| {
1743                    crate::support::take_string(ffi::whiteout_textures_JpegParser_getIssues_at(
1744                        self.raw.as_ptr(),
1745                        i,
1746                    ))
1747                })
1748                .collect()
1749        }
1750    }
1751}
1752
1753impl Default for JpegParser {
1754    fn default() -> Self {
1755        Self::new()
1756    }
1757}
1758
1759/// Encodes a Texture into JPEG format.
1760pub struct JpegWriter {
1761    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_JpegWriter>,
1762}
1763
1764impl Drop for JpegWriter {
1765    fn drop(&mut self) {
1766        // SAFETY: `raw` came from a native constructor and Drop runs once.
1767        unsafe { ffi::whiteout_textures_JpegWriter_delete(self.raw.as_ptr()) }
1768    }
1769}
1770
1771impl JpegWriter {
1772    /// # Safety
1773    /// `raw` must be a live handle this value takes ownership of.
1774    #[allow(dead_code)] // used by whichever methods return this type
1775    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_JpegWriter) -> Option<Self> {
1776        core::ptr::NonNull::new(raw).map(|raw| JpegWriter { raw })
1777    }
1778}
1779
1780// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1781// is deliberately NOT implemented — the C++ types make no documented
1782// guarantee about concurrent use, and claiming one we haven't verified
1783// would be unsound. See `@bind thread_safe` in the plan.
1784unsafe impl Send for JpegWriter {}
1785
1786impl core::fmt::Debug for JpegWriter {
1787    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1788        f.debug_struct("JpegWriter").finish_non_exhaustive()
1789    }
1790}
1791
1792impl JpegWriter {
1793    /// # Panics
1794    /// Panics if the native allocation fails.
1795    pub fn new() -> Self {
1796        // SAFETY: the native constructor returns a live handle; a null here
1797        // means the library is unusable.
1798        unsafe {
1799            let raw = ffi::whiteout_textures_JpegWriter_new();
1800            Self::from_raw(raw).expect("native JpegWriter allocation failed")
1801        }
1802    }
1803
1804    /// Serialize the texture to a JPEG byte buffer.
1805    pub fn write(&mut self, texture: &Texture) -> Bytes {
1806        // SAFETY: handle is live for the duration of the call.
1807        unsafe {
1808            Bytes::from_raw(ffi::whiteout_textures_JpegWriter_write(
1809                self.raw.as_ptr(),
1810                texture.raw.as_ptr(),
1811            ))
1812            .unwrap_or_else(Bytes::empty)
1813        }
1814    }
1815
1816    /// @return true if the last write produced any issues.
1817    pub fn has_issues(&self) -> bool {
1818        // SAFETY: handle is live for the duration of the call.
1819        unsafe { ffi::whiteout_textures_JpegWriter_hasIssues(self.raw.as_ptr()) != 0 }
1820    }
1821
1822    /// @return accumulated issues from the last write call.
1823    pub fn issues(&self) -> Vec<String> {
1824        // SAFETY: index stays below the reported count.
1825        unsafe {
1826            let n = ffi::whiteout_textures_JpegWriter_getIssues_count(self.raw.as_ptr());
1827            (0..n)
1828                .map(|i| {
1829                    crate::support::take_string(ffi::whiteout_textures_JpegWriter_getIssues_at(
1830                        self.raw.as_ptr(),
1831                        i,
1832                    ))
1833                })
1834                .collect()
1835        }
1836    }
1837}
1838
1839impl Default for JpegWriter {
1840    fn default() -> Self {
1841        Self::new()
1842    }
1843}
1844
1845/// Reads a DDS file or byte buffer and decodes it into a Texture.
1846pub struct DdsParser {
1847    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_DdsParser>,
1848}
1849
1850impl Drop for DdsParser {
1851    fn drop(&mut self) {
1852        // SAFETY: `raw` came from a native constructor and Drop runs once.
1853        unsafe { ffi::whiteout_textures_DdsParser_delete(self.raw.as_ptr()) }
1854    }
1855}
1856
1857impl DdsParser {
1858    /// # Safety
1859    /// `raw` must be a live handle this value takes ownership of.
1860    #[allow(dead_code)] // used by whichever methods return this type
1861    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_DdsParser) -> Option<Self> {
1862        core::ptr::NonNull::new(raw).map(|raw| DdsParser { raw })
1863    }
1864}
1865
1866// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1867// is deliberately NOT implemented — the C++ types make no documented
1868// guarantee about concurrent use, and claiming one we haven't verified
1869// would be unsound. See `@bind thread_safe` in the plan.
1870unsafe impl Send for DdsParser {}
1871
1872impl core::fmt::Debug for DdsParser {
1873    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1874        f.debug_struct("DdsParser").finish_non_exhaustive()
1875    }
1876}
1877
1878impl DdsParser {
1879    /// # Panics
1880    /// Panics if the native allocation fails.
1881    pub fn new() -> Self {
1882        // SAFETY: the native constructor returns a live handle; a null here
1883        // means the library is unusable.
1884        unsafe {
1885            let raw = ffi::whiteout_textures_DdsParser_new();
1886            Self::from_raw(raw).expect("native DdsParser allocation failed")
1887        }
1888    }
1889
1890    /// Parse a DDS byte buffer.
1891    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
1892        // SAFETY: handle is live for the duration of the call.
1893        unsafe {
1894            Texture::from_raw(ffi::whiteout_textures_DdsParser_parse(
1895                self.raw.as_ptr(),
1896                buffer.as_ptr(),
1897                buffer.len(),
1898            ))
1899        }
1900    }
1901
1902    /// @return true if the last parse produced any issues.
1903    pub fn has_issues(&self) -> bool {
1904        // SAFETY: handle is live for the duration of the call.
1905        unsafe { ffi::whiteout_textures_DdsParser_hasIssues(self.raw.as_ptr()) != 0 }
1906    }
1907
1908    /// @return accumulated issues from the last parse call.
1909    pub fn issues(&self) -> Vec<String> {
1910        // SAFETY: index stays below the reported count.
1911        unsafe {
1912            let n = ffi::whiteout_textures_DdsParser_getIssues_count(self.raw.as_ptr());
1913            (0..n)
1914                .map(|i| {
1915                    crate::support::take_string(ffi::whiteout_textures_DdsParser_getIssues_at(
1916                        self.raw.as_ptr(),
1917                        i,
1918                    ))
1919                })
1920                .collect()
1921        }
1922    }
1923}
1924
1925impl Default for DdsParser {
1926    fn default() -> Self {
1927        Self::new()
1928    }
1929}
1930
1931/// Encodes a Texture into DDS format.
1932pub struct DdsWriter {
1933    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_DdsWriter>,
1934}
1935
1936impl Drop for DdsWriter {
1937    fn drop(&mut self) {
1938        // SAFETY: `raw` came from a native constructor and Drop runs once.
1939        unsafe { ffi::whiteout_textures_DdsWriter_delete(self.raw.as_ptr()) }
1940    }
1941}
1942
1943impl DdsWriter {
1944    /// # Safety
1945    /// `raw` must be a live handle this value takes ownership of.
1946    #[allow(dead_code)] // used by whichever methods return this type
1947    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_DdsWriter) -> Option<Self> {
1948        core::ptr::NonNull::new(raw).map(|raw| DdsWriter { raw })
1949    }
1950}
1951
1952// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
1953// is deliberately NOT implemented — the C++ types make no documented
1954// guarantee about concurrent use, and claiming one we haven't verified
1955// would be unsound. See `@bind thread_safe` in the plan.
1956unsafe impl Send for DdsWriter {}
1957
1958impl core::fmt::Debug for DdsWriter {
1959    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1960        f.debug_struct("DdsWriter").finish_non_exhaustive()
1961    }
1962}
1963
1964impl DdsWriter {
1965    /// # Panics
1966    /// Panics if the native allocation fails.
1967    pub fn new() -> Self {
1968        // SAFETY: the native constructor returns a live handle; a null here
1969        // means the library is unusable.
1970        unsafe {
1971            let raw = ffi::whiteout_textures_DdsWriter_new();
1972            Self::from_raw(raw).expect("native DdsWriter allocation failed")
1973        }
1974    }
1975
1976    /// Serialize the texture to a DDS byte buffer.
1977    pub fn write(&mut self, texture: &Texture) -> Bytes {
1978        // SAFETY: handle is live for the duration of the call.
1979        unsafe {
1980            Bytes::from_raw(ffi::whiteout_textures_DdsWriter_write(
1981                self.raw.as_ptr(),
1982                texture.raw.as_ptr(),
1983            ))
1984            .unwrap_or_else(Bytes::empty)
1985        }
1986    }
1987
1988    /// @return true if the last write produced any issues.
1989    pub fn has_issues(&self) -> bool {
1990        // SAFETY: handle is live for the duration of the call.
1991        unsafe { ffi::whiteout_textures_DdsWriter_hasIssues(self.raw.as_ptr()) != 0 }
1992    }
1993
1994    /// @return accumulated issues from the last write call.
1995    pub fn issues(&self) -> Vec<String> {
1996        // SAFETY: index stays below the reported count.
1997        unsafe {
1998            let n = ffi::whiteout_textures_DdsWriter_getIssues_count(self.raw.as_ptr());
1999            (0..n)
2000                .map(|i| {
2001                    crate::support::take_string(ffi::whiteout_textures_DdsWriter_getIssues_at(
2002                        self.raw.as_ptr(),
2003                        i,
2004                    ))
2005                })
2006                .collect()
2007        }
2008    }
2009}
2010
2011impl Default for DdsWriter {
2012    fn default() -> Self {
2013        Self::new()
2014    }
2015}
2016
2017/// Reads a BMP file or byte buffer and decodes it into a Texture.
2018pub struct BmpParser {
2019    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_BmpParser>,
2020}
2021
2022impl Drop for BmpParser {
2023    fn drop(&mut self) {
2024        // SAFETY: `raw` came from a native constructor and Drop runs once.
2025        unsafe { ffi::whiteout_textures_BmpParser_delete(self.raw.as_ptr()) }
2026    }
2027}
2028
2029impl BmpParser {
2030    /// # Safety
2031    /// `raw` must be a live handle this value takes ownership of.
2032    #[allow(dead_code)] // used by whichever methods return this type
2033    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_BmpParser) -> Option<Self> {
2034        core::ptr::NonNull::new(raw).map(|raw| BmpParser { raw })
2035    }
2036}
2037
2038// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2039// is deliberately NOT implemented — the C++ types make no documented
2040// guarantee about concurrent use, and claiming one we haven't verified
2041// would be unsound. See `@bind thread_safe` in the plan.
2042unsafe impl Send for BmpParser {}
2043
2044impl core::fmt::Debug for BmpParser {
2045    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2046        f.debug_struct("BmpParser").finish_non_exhaustive()
2047    }
2048}
2049
2050impl BmpParser {
2051    /// # Panics
2052    /// Panics if the native allocation fails.
2053    pub fn new() -> Self {
2054        // SAFETY: the native constructor returns a live handle; a null here
2055        // means the library is unusable.
2056        unsafe {
2057            let raw = ffi::whiteout_textures_BmpParser_new();
2058            Self::from_raw(raw).expect("native BmpParser allocation failed")
2059        }
2060    }
2061
2062    /// Parse a BMP byte buffer.
2063    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
2064        // SAFETY: handle is live for the duration of the call.
2065        unsafe {
2066            Texture::from_raw(ffi::whiteout_textures_BmpParser_parse(
2067                self.raw.as_ptr(),
2068                buffer.as_ptr(),
2069                buffer.len(),
2070            ))
2071        }
2072    }
2073
2074    /// @return true if the last parse produced any issues.
2075    pub fn has_issues(&self) -> bool {
2076        // SAFETY: handle is live for the duration of the call.
2077        unsafe { ffi::whiteout_textures_BmpParser_hasIssues(self.raw.as_ptr()) != 0 }
2078    }
2079
2080    /// @return accumulated issues from the last parse call.
2081    pub fn issues(&self) -> Vec<String> {
2082        // SAFETY: index stays below the reported count.
2083        unsafe {
2084            let n = ffi::whiteout_textures_BmpParser_getIssues_count(self.raw.as_ptr());
2085            (0..n)
2086                .map(|i| {
2087                    crate::support::take_string(ffi::whiteout_textures_BmpParser_getIssues_at(
2088                        self.raw.as_ptr(),
2089                        i,
2090                    ))
2091                })
2092                .collect()
2093        }
2094    }
2095}
2096
2097impl Default for BmpParser {
2098    fn default() -> Self {
2099        Self::new()
2100    }
2101}
2102
2103/// Encodes a Texture into BMP format.
2104pub struct BmpWriter {
2105    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_BmpWriter>,
2106}
2107
2108impl Drop for BmpWriter {
2109    fn drop(&mut self) {
2110        // SAFETY: `raw` came from a native constructor and Drop runs once.
2111        unsafe { ffi::whiteout_textures_BmpWriter_delete(self.raw.as_ptr()) }
2112    }
2113}
2114
2115impl BmpWriter {
2116    /// # Safety
2117    /// `raw` must be a live handle this value takes ownership of.
2118    #[allow(dead_code)] // used by whichever methods return this type
2119    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_BmpWriter) -> Option<Self> {
2120        core::ptr::NonNull::new(raw).map(|raw| BmpWriter { raw })
2121    }
2122}
2123
2124// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2125// is deliberately NOT implemented — the C++ types make no documented
2126// guarantee about concurrent use, and claiming one we haven't verified
2127// would be unsound. See `@bind thread_safe` in the plan.
2128unsafe impl Send for BmpWriter {}
2129
2130impl core::fmt::Debug for BmpWriter {
2131    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2132        f.debug_struct("BmpWriter").finish_non_exhaustive()
2133    }
2134}
2135
2136impl BmpWriter {
2137    /// # Panics
2138    /// Panics if the native allocation fails.
2139    pub fn new() -> Self {
2140        // SAFETY: the native constructor returns a live handle; a null here
2141        // means the library is unusable.
2142        unsafe {
2143            let raw = ffi::whiteout_textures_BmpWriter_new();
2144            Self::from_raw(raw).expect("native BmpWriter allocation failed")
2145        }
2146    }
2147
2148    /// Serialize the texture to a BMP byte buffer.
2149    pub fn write(&mut self, texture: &Texture) -> Bytes {
2150        // SAFETY: handle is live for the duration of the call.
2151        unsafe {
2152            Bytes::from_raw(ffi::whiteout_textures_BmpWriter_write(
2153                self.raw.as_ptr(),
2154                texture.raw.as_ptr(),
2155            ))
2156            .unwrap_or_else(Bytes::empty)
2157        }
2158    }
2159
2160    /// @return true if the last write produced any issues.
2161    pub fn has_issues(&self) -> bool {
2162        // SAFETY: handle is live for the duration of the call.
2163        unsafe { ffi::whiteout_textures_BmpWriter_hasIssues(self.raw.as_ptr()) != 0 }
2164    }
2165
2166    /// @return accumulated issues from the last write call.
2167    pub fn issues(&self) -> Vec<String> {
2168        // SAFETY: index stays below the reported count.
2169        unsafe {
2170            let n = ffi::whiteout_textures_BmpWriter_getIssues_count(self.raw.as_ptr());
2171            (0..n)
2172                .map(|i| {
2173                    crate::support::take_string(ffi::whiteout_textures_BmpWriter_getIssues_at(
2174                        self.raw.as_ptr(),
2175                        i,
2176                    ))
2177                })
2178                .collect()
2179        }
2180    }
2181}
2182
2183impl Default for BmpWriter {
2184    fn default() -> Self {
2185        Self::new()
2186    }
2187}
2188
2189/// Reads a TGA file or byte buffer and decodes it into a Texture.
2190pub struct TgaParser {
2191    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_TgaParser>,
2192}
2193
2194impl Drop for TgaParser {
2195    fn drop(&mut self) {
2196        // SAFETY: `raw` came from a native constructor and Drop runs once.
2197        unsafe { ffi::whiteout_textures_TgaParser_delete(self.raw.as_ptr()) }
2198    }
2199}
2200
2201impl TgaParser {
2202    /// # Safety
2203    /// `raw` must be a live handle this value takes ownership of.
2204    #[allow(dead_code)] // used by whichever methods return this type
2205    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_TgaParser) -> Option<Self> {
2206        core::ptr::NonNull::new(raw).map(|raw| TgaParser { raw })
2207    }
2208}
2209
2210// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2211// is deliberately NOT implemented — the C++ types make no documented
2212// guarantee about concurrent use, and claiming one we haven't verified
2213// would be unsound. See `@bind thread_safe` in the plan.
2214unsafe impl Send for TgaParser {}
2215
2216impl core::fmt::Debug for TgaParser {
2217    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2218        f.debug_struct("TgaParser").finish_non_exhaustive()
2219    }
2220}
2221
2222impl TgaParser {
2223    /// # Panics
2224    /// Panics if the native allocation fails.
2225    pub fn new() -> Self {
2226        // SAFETY: the native constructor returns a live handle; a null here
2227        // means the library is unusable.
2228        unsafe {
2229            let raw = ffi::whiteout_textures_TgaParser_new();
2230            Self::from_raw(raw).expect("native TgaParser allocation failed")
2231        }
2232    }
2233
2234    /// Parse a TGA byte buffer.
2235    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
2236        // SAFETY: handle is live for the duration of the call.
2237        unsafe {
2238            Texture::from_raw(ffi::whiteout_textures_TgaParser_parse(
2239                self.raw.as_ptr(),
2240                buffer.as_ptr(),
2241                buffer.len(),
2242            ))
2243        }
2244    }
2245
2246    /// @return true if the last parse produced any issues.
2247    pub fn has_issues(&self) -> bool {
2248        // SAFETY: handle is live for the duration of the call.
2249        unsafe { ffi::whiteout_textures_TgaParser_hasIssues(self.raw.as_ptr()) != 0 }
2250    }
2251
2252    /// @return accumulated issues from the last parse call.
2253    pub fn issues(&self) -> Vec<String> {
2254        // SAFETY: index stays below the reported count.
2255        unsafe {
2256            let n = ffi::whiteout_textures_TgaParser_getIssues_count(self.raw.as_ptr());
2257            (0..n)
2258                .map(|i| {
2259                    crate::support::take_string(ffi::whiteout_textures_TgaParser_getIssues_at(
2260                        self.raw.as_ptr(),
2261                        i,
2262                    ))
2263                })
2264                .collect()
2265        }
2266    }
2267}
2268
2269impl Default for TgaParser {
2270    fn default() -> Self {
2271        Self::new()
2272    }
2273}
2274
2275/// Encodes a Texture into TGA format.
2276pub struct TgaWriter {
2277    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_TgaWriter>,
2278}
2279
2280impl Drop for TgaWriter {
2281    fn drop(&mut self) {
2282        // SAFETY: `raw` came from a native constructor and Drop runs once.
2283        unsafe { ffi::whiteout_textures_TgaWriter_delete(self.raw.as_ptr()) }
2284    }
2285}
2286
2287impl TgaWriter {
2288    /// # Safety
2289    /// `raw` must be a live handle this value takes ownership of.
2290    #[allow(dead_code)] // used by whichever methods return this type
2291    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_TgaWriter) -> Option<Self> {
2292        core::ptr::NonNull::new(raw).map(|raw| TgaWriter { raw })
2293    }
2294}
2295
2296// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2297// is deliberately NOT implemented — the C++ types make no documented
2298// guarantee about concurrent use, and claiming one we haven't verified
2299// would be unsound. See `@bind thread_safe` in the plan.
2300unsafe impl Send for TgaWriter {}
2301
2302impl core::fmt::Debug for TgaWriter {
2303    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2304        f.debug_struct("TgaWriter").finish_non_exhaustive()
2305    }
2306}
2307
2308impl TgaWriter {
2309    /// # Panics
2310    /// Panics if the native allocation fails.
2311    pub fn new() -> Self {
2312        // SAFETY: the native constructor returns a live handle; a null here
2313        // means the library is unusable.
2314        unsafe {
2315            let raw = ffi::whiteout_textures_TgaWriter_new();
2316            Self::from_raw(raw).expect("native TgaWriter allocation failed")
2317        }
2318    }
2319
2320    /// Serialize the texture to a TGA byte buffer.
2321    pub fn write(&mut self, texture: &Texture) -> Bytes {
2322        // SAFETY: handle is live for the duration of the call.
2323        unsafe {
2324            Bytes::from_raw(ffi::whiteout_textures_TgaWriter_write(
2325                self.raw.as_ptr(),
2326                texture.raw.as_ptr(),
2327            ))
2328            .unwrap_or_else(Bytes::empty)
2329        }
2330    }
2331
2332    /// @return true if the last write produced any issues.
2333    pub fn has_issues(&self) -> bool {
2334        // SAFETY: handle is live for the duration of the call.
2335        unsafe { ffi::whiteout_textures_TgaWriter_hasIssues(self.raw.as_ptr()) != 0 }
2336    }
2337
2338    /// @return accumulated issues from the last write call.
2339    pub fn issues(&self) -> Vec<String> {
2340        // SAFETY: index stays below the reported count.
2341        unsafe {
2342            let n = ffi::whiteout_textures_TgaWriter_getIssues_count(self.raw.as_ptr());
2343            (0..n)
2344                .map(|i| {
2345                    crate::support::take_string(ffi::whiteout_textures_TgaWriter_getIssues_at(
2346                        self.raw.as_ptr(),
2347                        i,
2348                    ))
2349                })
2350                .collect()
2351        }
2352    }
2353}
2354
2355impl Default for TgaWriter {
2356    fn default() -> Self {
2357        Self::new()
2358    }
2359}
2360
2361/// Reads a TIFF file or byte buffer and decodes it into a Texture.
2362pub struct TiffParser {
2363    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_TiffParser>,
2364}
2365
2366impl Drop for TiffParser {
2367    fn drop(&mut self) {
2368        // SAFETY: `raw` came from a native constructor and Drop runs once.
2369        unsafe { ffi::whiteout_textures_TiffParser_delete(self.raw.as_ptr()) }
2370    }
2371}
2372
2373impl TiffParser {
2374    /// # Safety
2375    /// `raw` must be a live handle this value takes ownership of.
2376    #[allow(dead_code)] // used by whichever methods return this type
2377    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_TiffParser) -> Option<Self> {
2378        core::ptr::NonNull::new(raw).map(|raw| TiffParser { raw })
2379    }
2380}
2381
2382// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2383// is deliberately NOT implemented — the C++ types make no documented
2384// guarantee about concurrent use, and claiming one we haven't verified
2385// would be unsound. See `@bind thread_safe` in the plan.
2386unsafe impl Send for TiffParser {}
2387
2388impl core::fmt::Debug for TiffParser {
2389    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2390        f.debug_struct("TiffParser").finish_non_exhaustive()
2391    }
2392}
2393
2394impl TiffParser {
2395    /// # Panics
2396    /// Panics if the native allocation fails.
2397    pub fn new() -> Self {
2398        // SAFETY: the native constructor returns a live handle; a null here
2399        // means the library is unusable.
2400        unsafe {
2401            let raw = ffi::whiteout_textures_TiffParser_new();
2402            Self::from_raw(raw).expect("native TiffParser allocation failed")
2403        }
2404    }
2405
2406    /// Parse a TIFF byte buffer.
2407    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
2408        // SAFETY: handle is live for the duration of the call.
2409        unsafe {
2410            Texture::from_raw(ffi::whiteout_textures_TiffParser_parse(
2411                self.raw.as_ptr(),
2412                buffer.as_ptr(),
2413                buffer.len(),
2414            ))
2415        }
2416    }
2417
2418    /// @return true if the last parse produced any issues.
2419    pub fn has_issues(&self) -> bool {
2420        // SAFETY: handle is live for the duration of the call.
2421        unsafe { ffi::whiteout_textures_TiffParser_hasIssues(self.raw.as_ptr()) != 0 }
2422    }
2423
2424    /// @return accumulated issues from the last parse call.
2425    pub fn issues(&self) -> Vec<String> {
2426        // SAFETY: index stays below the reported count.
2427        unsafe {
2428            let n = ffi::whiteout_textures_TiffParser_getIssues_count(self.raw.as_ptr());
2429            (0..n)
2430                .map(|i| {
2431                    crate::support::take_string(ffi::whiteout_textures_TiffParser_getIssues_at(
2432                        self.raw.as_ptr(),
2433                        i,
2434                    ))
2435                })
2436                .collect()
2437        }
2438    }
2439}
2440
2441impl Default for TiffParser {
2442    fn default() -> Self {
2443        Self::new()
2444    }
2445}
2446
2447/// Encodes a Texture into TIFF format.
2448pub struct TiffWriter {
2449    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_TiffWriter>,
2450}
2451
2452impl Drop for TiffWriter {
2453    fn drop(&mut self) {
2454        // SAFETY: `raw` came from a native constructor and Drop runs once.
2455        unsafe { ffi::whiteout_textures_TiffWriter_delete(self.raw.as_ptr()) }
2456    }
2457}
2458
2459impl TiffWriter {
2460    /// # Safety
2461    /// `raw` must be a live handle this value takes ownership of.
2462    #[allow(dead_code)] // used by whichever methods return this type
2463    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_TiffWriter) -> Option<Self> {
2464        core::ptr::NonNull::new(raw).map(|raw| TiffWriter { raw })
2465    }
2466}
2467
2468// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2469// is deliberately NOT implemented — the C++ types make no documented
2470// guarantee about concurrent use, and claiming one we haven't verified
2471// would be unsound. See `@bind thread_safe` in the plan.
2472unsafe impl Send for TiffWriter {}
2473
2474impl core::fmt::Debug for TiffWriter {
2475    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2476        f.debug_struct("TiffWriter").finish_non_exhaustive()
2477    }
2478}
2479
2480impl TiffWriter {
2481    /// # Panics
2482    /// Panics if the native allocation fails.
2483    pub fn new() -> Self {
2484        // SAFETY: the native constructor returns a live handle; a null here
2485        // means the library is unusable.
2486        unsafe {
2487            let raw = ffi::whiteout_textures_TiffWriter_new();
2488            Self::from_raw(raw).expect("native TiffWriter allocation failed")
2489        }
2490    }
2491
2492    /// Serialize the texture to a TIFF byte buffer.
2493    pub fn write(&mut self, texture: &Texture) -> Bytes {
2494        // SAFETY: handle is live for the duration of the call.
2495        unsafe {
2496            Bytes::from_raw(ffi::whiteout_textures_TiffWriter_write(
2497                self.raw.as_ptr(),
2498                texture.raw.as_ptr(),
2499            ))
2500            .unwrap_or_else(Bytes::empty)
2501        }
2502    }
2503
2504    /// @return true if the last write produced any issues.
2505    pub fn has_issues(&self) -> bool {
2506        // SAFETY: handle is live for the duration of the call.
2507        unsafe { ffi::whiteout_textures_TiffWriter_hasIssues(self.raw.as_ptr()) != 0 }
2508    }
2509
2510    /// @return accumulated issues from the last write call.
2511    pub fn issues(&self) -> Vec<String> {
2512        // SAFETY: index stays below the reported count.
2513        unsafe {
2514            let n = ffi::whiteout_textures_TiffWriter_getIssues_count(self.raw.as_ptr());
2515            (0..n)
2516                .map(|i| {
2517                    crate::support::take_string(ffi::whiteout_textures_TiffWriter_getIssues_at(
2518                        self.raw.as_ptr(),
2519                        i,
2520                    ))
2521                })
2522                .collect()
2523        }
2524    }
2525}
2526
2527impl Default for TiffWriter {
2528    fn default() -> Self {
2529        Self::new()
2530    }
2531}
2532
2533/// Per-write options for GIF encoding.
2534#[derive(Clone, Debug, PartialEq)]
2535pub struct GifSaveOptions {
2536    /// Delay between frames in centiseconds (1/100 s).  0 = unspecified.
2537    pub delay_cs: u16,
2538    /// Number of times the animation should loop.  0 = loop forever.
2539    pub loop_count: u16,
2540    /// Enable blue-noise ordered dithering when mapping pixels to the palette.
2541    pub dither: bool,
2542    /// Dither strength in `[0, 1]`.  0 = no visible dithering, 1 = full.
2543    pub dither_strength: f32,
2544    /// 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.
2545    pub transparent: bool,
2546}
2547
2548impl Default for GifSaveOptions {
2549    fn default() -> Self {
2550        // SAFETY: `_new` always returns a live handle; freed before return.
2551        unsafe {
2552            let h = ffi::whiteout_textures_GifSaveOptions_new();
2553            let out = GifSaveOptions {
2554                delay_cs: ffi::whiteout_textures_GifSaveOptions_get_delayCs(h),
2555                loop_count: ffi::whiteout_textures_GifSaveOptions_get_loopCount(h),
2556                dither: ffi::whiteout_textures_GifSaveOptions_get_dither(h) != 0,
2557                dither_strength: ffi::whiteout_textures_GifSaveOptions_get_ditherStrength(h),
2558                transparent: ffi::whiteout_textures_GifSaveOptions_get_transparent(h) != 0,
2559            };
2560            ffi::whiteout_textures_GifSaveOptions_delete(h);
2561            out
2562        }
2563    }
2564}
2565
2566impl GifSaveOptions {
2567    /// Build a native handle carrying these values. Caller frees it.
2568    #[allow(dead_code)] // consumed once the methods taking these options bind
2569    pub(crate) unsafe fn to_native(&self) -> *mut ffi::whiteout_GifSaveOptions {
2570        unsafe {
2571            let h = ffi::whiteout_textures_GifSaveOptions_new();
2572            ffi::whiteout_textures_GifSaveOptions_set_delayCs(h, self.delay_cs);
2573            ffi::whiteout_textures_GifSaveOptions_set_loopCount(h, self.loop_count);
2574            ffi::whiteout_textures_GifSaveOptions_set_dither(h, if self.dither { 1 } else { 0 });
2575            ffi::whiteout_textures_GifSaveOptions_set_ditherStrength(h, self.dither_strength);
2576            ffi::whiteout_textures_GifSaveOptions_set_transparent(
2577                h,
2578                if self.transparent { 1 } else { 0 },
2579            );
2580            h
2581        }
2582    }
2583
2584    /// Free a handle produced by [`Self::to_native`].
2585    ///
2586    /// # Safety
2587    /// `h` must have come from `to_native` and not been freed already.
2588    #[allow(dead_code)]
2589    pub(crate) unsafe fn free_native(h: *mut ffi::whiteout_GifSaveOptions) {
2590        unsafe { ffi::whiteout_textures_GifSaveOptions_delete(h) }
2591    }
2592}
2593
2594/// Encodes a sequence of Texture frames into GIF89a format.
2595///
2596/// Unlike the single-image writers (BMP, TGA, …), this writer accepts a vector of frames.  It does **not** inherit from `textures::Writer`.
2597pub struct GifWriter {
2598    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_GifWriter>,
2599}
2600
2601impl Drop for GifWriter {
2602    fn drop(&mut self) {
2603        // SAFETY: `raw` came from a native constructor and Drop runs once.
2604        unsafe { ffi::whiteout_textures_GifWriter_delete(self.raw.as_ptr()) }
2605    }
2606}
2607
2608impl GifWriter {
2609    /// # Safety
2610    /// `raw` must be a live handle this value takes ownership of.
2611    #[allow(dead_code)] // used by whichever methods return this type
2612    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_GifWriter) -> Option<Self> {
2613        core::ptr::NonNull::new(raw).map(|raw| GifWriter { raw })
2614    }
2615}
2616
2617// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2618// is deliberately NOT implemented — the C++ types make no documented
2619// guarantee about concurrent use, and claiming one we haven't verified
2620// would be unsound. See `@bind thread_safe` in the plan.
2621unsafe impl Send for GifWriter {}
2622
2623impl core::fmt::Debug for GifWriter {
2624    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2625        f.debug_struct("GifWriter").finish_non_exhaustive()
2626    }
2627}
2628
2629impl GifWriter {
2630    /// # Panics
2631    /// Panics if the native allocation fails.
2632    pub fn new() -> Self {
2633        // SAFETY: the native constructor returns a live handle; a null here
2634        // means the library is unusable.
2635        unsafe {
2636            let raw = ffi::whiteout_textures_GifWriter_new();
2637            Self::from_raw(raw).expect("native GifWriter allocation failed")
2638        }
2639    }
2640
2641    /// Write frames to a GIF file on disk using default options.
2642    pub fn write(&mut self, file_path: &str, frames: &[&Texture]) {
2643        let file_path_cstr = std::ffi::CString::new(file_path).unwrap_or_default();
2644        let frames_ptrs: Vec<_> = frames.iter().map(|v| v.raw.as_ptr()).collect();
2645        // SAFETY: handle is live for the duration of the call.
2646        unsafe {
2647            ffi::whiteout_textures_GifWriter_write(
2648                self.raw.as_ptr(),
2649                file_path_cstr.as_ptr(),
2650                frames_ptrs.as_ptr(),
2651                frames.len(),
2652            );
2653        }
2654    }
2655
2656    /// Write frames to a GIF byte buffer using default options.
2657    pub fn write_frames(&mut self, frames: &[&Texture]) -> Bytes {
2658        let frames_ptrs: Vec<_> = frames.iter().map(|v| v.raw.as_ptr()).collect();
2659        // SAFETY: handle is live for the duration of the call.
2660        unsafe {
2661            Bytes::from_raw(ffi::whiteout_textures_GifWriter_write_frames(
2662                self.raw.as_ptr(),
2663                frames_ptrs.as_ptr(),
2664                frames.len(),
2665            ))
2666            .unwrap_or_else(Bytes::empty)
2667        }
2668    }
2669
2670    /// Write frames to a GIF file on disk with explicit options.
2671    pub fn write_file_path_frames_opts(
2672        &mut self,
2673        file_path: &str,
2674        frames: &[&Texture],
2675        opts: &GifSaveOptions,
2676    ) {
2677        let file_path_cstr = std::ffi::CString::new(file_path).unwrap_or_default();
2678        let frames_ptrs: Vec<_> = frames.iter().map(|v| v.raw.as_ptr()).collect();
2679        let opts_native = unsafe { opts.to_native() };
2680        // SAFETY: handle is live for the call; the staged
2681        // option handles are freed immediately after.
2682        unsafe {
2683            ffi::whiteout_textures_GifWriter_write_filePath_frames_opts(
2684                self.raw.as_ptr(),
2685                file_path_cstr.as_ptr(),
2686                frames_ptrs.as_ptr(),
2687                frames.len(),
2688                opts_native,
2689            );
2690            GifSaveOptions::free_native(opts_native);
2691        }
2692    }
2693
2694    /// Write frames to a GIF byte buffer with explicit options.
2695    pub fn write_frames_opts(&mut self, frames: &[&Texture], opts: &GifSaveOptions) -> Bytes {
2696        let frames_ptrs: Vec<_> = frames.iter().map(|v| v.raw.as_ptr()).collect();
2697        let opts_native = unsafe { opts.to_native() };
2698        // SAFETY: handle is live for the call; the staged
2699        // option handles are freed immediately after.
2700        unsafe {
2701            let __r = Bytes::from_raw(ffi::whiteout_textures_GifWriter_write_frames_opts(
2702                self.raw.as_ptr(),
2703                frames_ptrs.as_ptr(),
2704                frames.len(),
2705                opts_native,
2706            ))
2707            .unwrap_or_else(Bytes::empty);
2708            GifSaveOptions::free_native(opts_native);
2709            __r
2710        }
2711    }
2712
2713    /// @return true if the last write produced any issues.
2714    pub fn has_issues(&self) -> bool {
2715        // SAFETY: handle is live for the duration of the call.
2716        unsafe { ffi::whiteout_textures_GifWriter_hasIssues(self.raw.as_ptr()) != 0 }
2717    }
2718
2719    /// @return accumulated issues from the last write call.
2720    pub fn issues(&self) -> Vec<String> {
2721        // SAFETY: index stays below the reported count.
2722        unsafe {
2723            let n = ffi::whiteout_textures_GifWriter_getIssues_count(self.raw.as_ptr());
2724            (0..n)
2725                .map(|i| {
2726                    crate::support::take_string(ffi::whiteout_textures_GifWriter_getIssues_at(
2727                        self.raw.as_ptr(),
2728                        i,
2729                    ))
2730                })
2731                .collect()
2732        }
2733    }
2734}
2735
2736impl Default for GifWriter {
2737    fn default() -> Self {
2738        Self::new()
2739    }
2740}
2741
2742/// Value-ABI mutable span accessors (`bindings/c/whiteout_v.h`).
2743#[doc(hidden)]
2744pub mod tier_a {
2745    #![allow(missing_debug_implementations)]
2746
2747    #[repr(C)]
2748    pub struct Opaque {
2749        _private: [u8; 0],
2750    }
2751
2752    extern "C" {
2753        pub fn whiteout_v_Texture_data_mut(self_: *mut Opaque, out_size: *mut usize) -> *mut u8;
2754        pub fn whiteout_v_Texture_mipData_mut(
2755            self_: *mut Opaque,
2756            mip: u32,
2757            layer: u32,
2758            out_size: *mut usize,
2759        ) -> *mut u8;
2760    }
2761}
2762
2763#[doc(hidden)]
2764pub mod ffi {
2765    #![allow(missing_debug_implementations)]
2766
2767    #[allow(unused_imports)]
2768    use crate::support::{RawBytes, RawCString};
2769
2770    #[repr(C)]
2771    pub struct whiteout_TextureList {
2772        _private: [u8; 0],
2773    }
2774    #[repr(C)]
2775    pub struct whiteout_MipLevel {
2776        _private: [u8; 0],
2777    }
2778    #[repr(C)]
2779    pub struct whiteout_Texture {
2780        _private: [u8; 0],
2781    }
2782    #[repr(C)]
2783    pub struct whiteout_BlpParser {
2784        _private: [u8; 0],
2785    }
2786    #[repr(C)]
2787    pub struct whiteout_BlpWriter {
2788        _private: [u8; 0],
2789    }
2790    #[repr(C)]
2791    pub struct whiteout_PngApngFrameInfo {
2792        _private: [u8; 0],
2793    }
2794    #[repr(C)]
2795    pub struct whiteout_PngParser {
2796        _private: [u8; 0],
2797    }
2798    #[repr(C)]
2799    pub struct whiteout_PngApngFrame {
2800        _private: [u8; 0],
2801    }
2802    #[repr(C)]
2803    pub struct whiteout_PngApngSaveOptions {
2804        _private: [u8; 0],
2805    }
2806    #[repr(C)]
2807    pub struct whiteout_PngWriter {
2808        _private: [u8; 0],
2809    }
2810    #[repr(C)]
2811    pub struct whiteout_JpegParser {
2812        _private: [u8; 0],
2813    }
2814    #[repr(C)]
2815    pub struct whiteout_JpegWriter {
2816        _private: [u8; 0],
2817    }
2818    #[repr(C)]
2819    pub struct whiteout_DdsParser {
2820        _private: [u8; 0],
2821    }
2822    #[repr(C)]
2823    pub struct whiteout_DdsWriter {
2824        _private: [u8; 0],
2825    }
2826    #[repr(C)]
2827    pub struct whiteout_BmpParser {
2828        _private: [u8; 0],
2829    }
2830    #[repr(C)]
2831    pub struct whiteout_BmpWriter {
2832        _private: [u8; 0],
2833    }
2834    #[repr(C)]
2835    pub struct whiteout_TgaParser {
2836        _private: [u8; 0],
2837    }
2838    #[repr(C)]
2839    pub struct whiteout_TgaWriter {
2840        _private: [u8; 0],
2841    }
2842    #[repr(C)]
2843    pub struct whiteout_TiffParser {
2844        _private: [u8; 0],
2845    }
2846    #[repr(C)]
2847    pub struct whiteout_TiffWriter {
2848        _private: [u8; 0],
2849    }
2850    #[repr(C)]
2851    pub struct whiteout_GifSaveOptions {
2852        _private: [u8; 0],
2853    }
2854    #[repr(C)]
2855    pub struct whiteout_GifWriter {
2856        _private: [u8; 0],
2857    }
2858
2859    extern "C" {
2860        pub fn whiteout_textures_TextureList_size(self_: *mut whiteout_TextureList) -> usize;
2861        pub fn whiteout_textures_TextureList_at(
2862            self_: *mut whiteout_TextureList,
2863            index: usize,
2864        ) -> *mut whiteout_Texture;
2865        pub fn whiteout_textures_TextureList_delete(self_: *mut whiteout_TextureList);
2866        // MipLevel
2867        pub fn whiteout_textures_MipLevel_new() -> *mut whiteout_MipLevel;
2868        pub fn whiteout_textures_MipLevel_delete(self_: *mut whiteout_MipLevel);
2869        pub fn whiteout_textures_MipLevel_get_width(self_: *mut whiteout_MipLevel) -> u32;
2870        pub fn whiteout_textures_MipLevel_set_width(self_: *mut whiteout_MipLevel, value: u32);
2871        pub fn whiteout_textures_MipLevel_get_height(self_: *mut whiteout_MipLevel) -> u32;
2872        pub fn whiteout_textures_MipLevel_set_height(self_: *mut whiteout_MipLevel, value: u32);
2873        pub fn whiteout_textures_MipLevel_get_depth(self_: *mut whiteout_MipLevel) -> u32;
2874        pub fn whiteout_textures_MipLevel_set_depth(self_: *mut whiteout_MipLevel, value: u32);
2875        pub fn whiteout_textures_MipLevel_get_offset(self_: *mut whiteout_MipLevel) -> u64;
2876        pub fn whiteout_textures_MipLevel_set_offset(self_: *mut whiteout_MipLevel, value: u64);
2877        pub fn whiteout_textures_MipLevel_get_size(self_: *mut whiteout_MipLevel) -> u64;
2878        pub fn whiteout_textures_MipLevel_set_size(self_: *mut whiteout_MipLevel, value: u64);
2879        // Texture
2880        pub fn whiteout_textures_Texture_new() -> *mut whiteout_Texture;
2881        pub fn whiteout_textures_Texture_delete(self_: *mut whiteout_Texture);
2882        pub fn whiteout_textures_Texture_format(self_: *mut whiteout_Texture, new_fmt: i32);
2883        pub fn whiteout_textures_Texture_format_overload2(self_: *mut whiteout_Texture) -> i32;
2884        pub fn whiteout_textures_Texture_copyAsFormat(
2885            self_: *mut whiteout_Texture,
2886            new_fmt: i32,
2887            pool: *mut core::ffi::c_void,
2888        ) -> *mut whiteout_Texture;
2889        pub fn whiteout_textures_Texture_swapChannels(
2890            self_: *mut whiteout_Texture,
2891            a: i32,
2892            b: i32,
2893        ) -> i32;
2894        pub fn whiteout_textures_Texture_invertChannel(
2895            self_: *mut whiteout_Texture,
2896            ch: i32,
2897        ) -> i32;
2898        pub fn whiteout_textures_Texture_expandNormal(
2899            self_: *mut whiteout_Texture,
2900            x_channel: i32,
2901            y_channel: i32,
2902            z_channel: i32,
2903        ) -> i32;
2904        pub fn whiteout_textures_Texture_fillChannel(
2905            self_: *mut whiteout_Texture,
2906            target: i32,
2907            value: f32,
2908        ) -> i32;
2909        pub fn whiteout_textures_Texture_splitChannels(
2910            self_: *mut whiteout_Texture,
2911            channels: *const i32,
2912            channels_size: usize,
2913        ) -> *mut whiteout_TextureList;
2914        pub fn whiteout_textures_Texture_mergeChannels(
2915            sources: *const *mut whiteout_Texture,
2916            sources_size: usize,
2917            target_channels: *const i32,
2918            target_channels_size: usize,
2919        ) -> *mut whiteout_Texture;
2920        pub fn whiteout_textures_Texture_copyFromNormalToRGBA(
2921            self_: *mut whiteout_Texture,
2922            pool: *mut core::ffi::c_void,
2923        ) -> *mut whiteout_Texture;
2924        pub fn whiteout_textures_Texture_generateMipmaps(
2925            self_: *mut whiteout_Texture,
2926            new_mip_count: u32,
2927            pool: *mut core::ffi::c_void,
2928        ) -> RawCString;
2929        pub fn whiteout_textures_Texture_generateMipmaps_pool(
2930            self_: *mut whiteout_Texture,
2931            pool: *mut core::ffi::c_void,
2932        ) -> RawCString;
2933        pub fn whiteout_textures_Texture_downscale(
2934            self_: *mut whiteout_Texture,
2935            levels: u32,
2936            pool: *mut core::ffi::c_void,
2937        ) -> RawCString;
2938        pub fn whiteout_textures_Texture_create2D(
2939            fmt: i32,
2940            width: u32,
2941            height: u32,
2942            mip_count: u32,
2943        ) -> *mut whiteout_Texture;
2944        pub fn whiteout_textures_Texture_create3D(
2945            fmt: i32,
2946            width: u32,
2947            height: u32,
2948            depth: u32,
2949            mip_count: u32,
2950        ) -> *mut whiteout_Texture;
2951        pub fn whiteout_textures_Texture_createCube(
2952            fmt: i32,
2953            size: u32,
2954            mip_count: u32,
2955        ) -> *mut whiteout_Texture;
2956        pub fn whiteout_textures_Texture_create2DArray(
2957            fmt: i32,
2958            width: u32,
2959            height: u32,
2960            array_size: u32,
2961            mip_count: u32,
2962        ) -> *mut whiteout_Texture;
2963        pub fn whiteout_textures_Texture_createCubeArray(
2964            fmt: i32,
2965            size: u32,
2966            array_size: u32,
2967            mip_count: u32,
2968        ) -> *mut whiteout_Texture;
2969        pub fn whiteout_textures_Texture_type(self_: *mut whiteout_Texture) -> i32;
2970        pub fn whiteout_textures_Texture_kind(self_: *mut whiteout_Texture) -> i32;
2971        pub fn whiteout_textures_Texture_setKind(self_: *mut whiteout_Texture, k: i32);
2972        pub fn whiteout_textures_Texture_channelKind(self_: *mut whiteout_Texture, ch: i32) -> i32;
2973        pub fn whiteout_textures_Texture_setChannelKind(
2974            self_: *mut whiteout_Texture,
2975            ch: i32,
2976            kind: i32,
2977        );
2978        pub fn whiteout_textures_Texture_channelDefault(
2979            self_: *mut whiteout_Texture,
2980            ch: i32,
2981        ) -> f32;
2982        pub fn whiteout_textures_Texture_setChannelDefault(
2983            self_: *mut whiteout_Texture,
2984            ch: i32,
2985            value: f32,
2986        );
2987        pub fn whiteout_textures_Texture_isSrgb(self_: *mut whiteout_Texture) -> i32;
2988        pub fn whiteout_textures_Texture_setSrgb(self_: *mut whiteout_Texture, srgb: i32);
2989        pub fn whiteout_textures_Texture_width(self_: *mut whiteout_Texture) -> u32;
2990        pub fn whiteout_textures_Texture_height(self_: *mut whiteout_Texture) -> u32;
2991        pub fn whiteout_textures_Texture_depth(self_: *mut whiteout_Texture) -> u32;
2992        pub fn whiteout_textures_Texture_layerCount(self_: *mut whiteout_Texture) -> u32;
2993        pub fn whiteout_textures_Texture_arraySize(self_: *mut whiteout_Texture) -> u32;
2994        pub fn whiteout_textures_Texture_mipCount(self_: *mut whiteout_Texture) -> u32;
2995        pub fn whiteout_textures_Texture_mipLevel(
2996            self_: *mut whiteout_Texture,
2997            mip: u32,
2998            layer: u32,
2999        ) -> *mut whiteout_MipLevel;
3000        pub fn whiteout_textures_Texture_dataSize(self_: *mut whiteout_Texture) -> u64;
3001        pub fn whiteout_textures_Texture_data(self_: *mut whiteout_Texture) -> RawBytes;
3002        pub fn whiteout_textures_Texture_mipData(
3003            self_: *mut whiteout_Texture,
3004            mip: u32,
3005            layer: u32,
3006        ) -> RawBytes;
3007        pub fn whiteout_textures_Texture_takeData(self_: *mut whiteout_Texture) -> RawBytes;
3008        pub fn whiteout_textures_Texture_setData(
3009            self_: *mut whiteout_Texture,
3010            new_data: *const u8,
3011            new_data_size: usize,
3012        );
3013        // BlpParser
3014        pub fn whiteout_textures_BlpParser_new() -> *mut whiteout_BlpParser;
3015        pub fn whiteout_textures_BlpParser_delete(self_: *mut whiteout_BlpParser);
3016        pub fn whiteout_textures_BlpParser_parse(
3017            self_: *mut whiteout_BlpParser,
3018            buffer: *const u8,
3019            buffer_size: usize,
3020        ) -> *mut whiteout_Texture;
3021        pub fn whiteout_textures_BlpParser_hasIssues(self_: *mut whiteout_BlpParser) -> i32;
3022        pub fn whiteout_textures_BlpParser_getIssues_count(self_: *mut whiteout_BlpParser)
3023            -> usize;
3024        pub fn whiteout_textures_BlpParser_getIssues_at(
3025            self_: *mut whiteout_BlpParser,
3026            index: usize,
3027        ) -> RawCString;
3028        // BlpWriter
3029        pub fn whiteout_textures_BlpWriter_new() -> *mut whiteout_BlpWriter;
3030        pub fn whiteout_textures_BlpWriter_new_pool(
3031            _0: *mut core::ffi::c_void,
3032        ) -> *mut whiteout_BlpWriter;
3033        pub fn whiteout_textures_BlpWriter_delete(self_: *mut whiteout_BlpWriter);
3034        pub fn whiteout_textures_BlpWriter_write(
3035            self_: *mut whiteout_BlpWriter,
3036            texture: *mut whiteout_Texture,
3037        ) -> RawBytes;
3038        pub fn whiteout_textures_BlpWriter_hasIssues(self_: *mut whiteout_BlpWriter) -> i32;
3039        pub fn whiteout_textures_BlpWriter_getIssues_count(self_: *mut whiteout_BlpWriter)
3040            -> usize;
3041        pub fn whiteout_textures_BlpWriter_getIssues_at(
3042            self_: *mut whiteout_BlpWriter,
3043            index: usize,
3044        ) -> RawCString;
3045        // PngApngFrameInfo
3046        pub fn whiteout_textures_PngApngFrameInfo_new() -> *mut whiteout_PngApngFrameInfo;
3047        pub fn whiteout_textures_PngApngFrameInfo_delete(self_: *mut whiteout_PngApngFrameInfo);
3048        pub fn whiteout_textures_PngApngFrameInfo_get_width(
3049            self_: *mut whiteout_PngApngFrameInfo,
3050        ) -> u32;
3051        pub fn whiteout_textures_PngApngFrameInfo_set_width(
3052            self_: *mut whiteout_PngApngFrameInfo,
3053            value: u32,
3054        );
3055        pub fn whiteout_textures_PngApngFrameInfo_get_height(
3056            self_: *mut whiteout_PngApngFrameInfo,
3057        ) -> u32;
3058        pub fn whiteout_textures_PngApngFrameInfo_set_height(
3059            self_: *mut whiteout_PngApngFrameInfo,
3060            value: u32,
3061        );
3062        pub fn whiteout_textures_PngApngFrameInfo_get_xOffset(
3063            self_: *mut whiteout_PngApngFrameInfo,
3064        ) -> u32;
3065        pub fn whiteout_textures_PngApngFrameInfo_set_xOffset(
3066            self_: *mut whiteout_PngApngFrameInfo,
3067            value: u32,
3068        );
3069        pub fn whiteout_textures_PngApngFrameInfo_get_yOffset(
3070            self_: *mut whiteout_PngApngFrameInfo,
3071        ) -> u32;
3072        pub fn whiteout_textures_PngApngFrameInfo_set_yOffset(
3073            self_: *mut whiteout_PngApngFrameInfo,
3074            value: u32,
3075        );
3076        pub fn whiteout_textures_PngApngFrameInfo_get_delayMs(
3077            self_: *mut whiteout_PngApngFrameInfo,
3078        ) -> u32;
3079        pub fn whiteout_textures_PngApngFrameInfo_set_delayMs(
3080            self_: *mut whiteout_PngApngFrameInfo,
3081            value: u32,
3082        );
3083        pub fn whiteout_textures_PngApngFrameInfo_get_disposeOp(
3084            self_: *mut whiteout_PngApngFrameInfo,
3085        ) -> u32;
3086        pub fn whiteout_textures_PngApngFrameInfo_set_disposeOp(
3087            self_: *mut whiteout_PngApngFrameInfo,
3088            value: u32,
3089        );
3090        pub fn whiteout_textures_PngApngFrameInfo_get_blendOp(
3091            self_: *mut whiteout_PngApngFrameInfo,
3092        ) -> u32;
3093        pub fn whiteout_textures_PngApngFrameInfo_set_blendOp(
3094            self_: *mut whiteout_PngApngFrameInfo,
3095            value: u32,
3096        );
3097        // PngParser
3098        pub fn whiteout_textures_PngParser_new() -> *mut whiteout_PngParser;
3099        pub fn whiteout_textures_PngParser_delete(self_: *mut whiteout_PngParser);
3100        pub fn whiteout_textures_PngParser_parse(
3101            self_: *mut whiteout_PngParser,
3102            buffer: *const u8,
3103            buffer_size: usize,
3104        ) -> *mut whiteout_Texture;
3105        pub fn whiteout_textures_PngParser_hasIssues(self_: *mut whiteout_PngParser) -> i32;
3106        pub fn whiteout_textures_PngParser_getIssues_count(self_: *mut whiteout_PngParser)
3107            -> usize;
3108        pub fn whiteout_textures_PngParser_getIssues_at(
3109            self_: *mut whiteout_PngParser,
3110            index: usize,
3111        ) -> RawCString;
3112        pub fn whiteout_textures_PngParser_isAnimated(self_: *mut whiteout_PngParser) -> i32;
3113        pub fn whiteout_textures_PngParser_frameCount(self_: *mut whiteout_PngParser) -> u32;
3114        pub fn whiteout_textures_PngParser_loopCount(self_: *mut whiteout_PngParser) -> u32;
3115        pub fn whiteout_textures_PngParser_frame(
3116            self_: *mut whiteout_PngParser,
3117            index: u32,
3118        ) -> *mut whiteout_Texture;
3119        pub fn whiteout_textures_PngParser_frameDelayMs(
3120            self_: *mut whiteout_PngParser,
3121            index: u32,
3122        ) -> u32;
3123        pub fn whiteout_textures_PngParser_frameInfo(
3124            self_: *mut whiteout_PngParser,
3125            index: u32,
3126        ) -> *mut whiteout_PngApngFrameInfo;
3127        // PngApngFrame
3128        pub fn whiteout_textures_PngApngFrame_new() -> *mut whiteout_PngApngFrame;
3129        pub fn whiteout_textures_PngApngFrame_delete(self_: *mut whiteout_PngApngFrame);
3130        pub fn whiteout_textures_PngApngFrame_get_image(
3131            self_: *mut whiteout_PngApngFrame,
3132        ) -> *mut whiteout_Texture;
3133        pub fn whiteout_textures_PngApngFrame_set_image(
3134            self_: *mut whiteout_PngApngFrame,
3135            value: *const whiteout_Texture,
3136        );
3137        pub fn whiteout_textures_PngApngFrame_get_delayMs(self_: *mut whiteout_PngApngFrame)
3138            -> u32;
3139        pub fn whiteout_textures_PngApngFrame_set_delayMs(
3140            self_: *mut whiteout_PngApngFrame,
3141            value: u32,
3142        );
3143        // PngApngSaveOptions
3144        pub fn whiteout_textures_PngApngSaveOptions_new() -> *mut whiteout_PngApngSaveOptions;
3145        pub fn whiteout_textures_PngApngSaveOptions_delete(self_: *mut whiteout_PngApngSaveOptions);
3146        pub fn whiteout_textures_PngApngSaveOptions_get_loopCount(
3147            self_: *mut whiteout_PngApngSaveOptions,
3148        ) -> u32;
3149        pub fn whiteout_textures_PngApngSaveOptions_set_loopCount(
3150            self_: *mut whiteout_PngApngSaveOptions,
3151            value: u32,
3152        );
3153        // PngWriter
3154        pub fn whiteout_textures_PngWriter_new() -> *mut whiteout_PngWriter;
3155        pub fn whiteout_textures_PngWriter_delete(self_: *mut whiteout_PngWriter);
3156        pub fn whiteout_textures_PngWriter_write(
3157            self_: *mut whiteout_PngWriter,
3158            texture: *mut whiteout_Texture,
3159        ) -> RawBytes;
3160        pub fn whiteout_textures_PngWriter_writeAnimated(
3161            self_: *mut whiteout_PngWriter,
3162            frames: *const *mut whiteout_PngApngFrame,
3163            frames_size: usize,
3164            opts: *mut whiteout_PngApngSaveOptions,
3165        ) -> RawBytes;
3166        pub fn whiteout_textures_PngWriter_hasIssues(self_: *mut whiteout_PngWriter) -> i32;
3167        pub fn whiteout_textures_PngWriter_getIssues_count(self_: *mut whiteout_PngWriter)
3168            -> usize;
3169        pub fn whiteout_textures_PngWriter_getIssues_at(
3170            self_: *mut whiteout_PngWriter,
3171            index: usize,
3172        ) -> RawCString;
3173        // JpegParser
3174        pub fn whiteout_textures_JpegParser_new() -> *mut whiteout_JpegParser;
3175        pub fn whiteout_textures_JpegParser_new_pool(
3176            _0: *mut core::ffi::c_void,
3177        ) -> *mut whiteout_JpegParser;
3178        pub fn whiteout_textures_JpegParser_delete(self_: *mut whiteout_JpegParser);
3179        pub fn whiteout_textures_JpegParser_parse(
3180            self_: *mut whiteout_JpegParser,
3181            buffer: *const u8,
3182            buffer_size: usize,
3183        ) -> *mut whiteout_Texture;
3184        pub fn whiteout_textures_JpegParser_hasIssues(self_: *mut whiteout_JpegParser) -> i32;
3185        pub fn whiteout_textures_JpegParser_getIssues_count(
3186            self_: *mut whiteout_JpegParser,
3187        ) -> usize;
3188        pub fn whiteout_textures_JpegParser_getIssues_at(
3189            self_: *mut whiteout_JpegParser,
3190            index: usize,
3191        ) -> RawCString;
3192        // JpegWriter
3193        pub fn whiteout_textures_JpegWriter_new() -> *mut whiteout_JpegWriter;
3194        pub fn whiteout_textures_JpegWriter_new_quality_pool_progressive(
3195            _0: *mut core::ffi::c_void,
3196            _1: *mut core::ffi::c_void,
3197            _2: *mut core::ffi::c_void,
3198        ) -> *mut whiteout_JpegWriter;
3199        pub fn whiteout_textures_JpegWriter_delete(self_: *mut whiteout_JpegWriter);
3200        pub fn whiteout_textures_JpegWriter_write(
3201            self_: *mut whiteout_JpegWriter,
3202            texture: *mut whiteout_Texture,
3203        ) -> RawBytes;
3204        pub fn whiteout_textures_JpegWriter_hasIssues(self_: *mut whiteout_JpegWriter) -> i32;
3205        pub fn whiteout_textures_JpegWriter_getIssues_count(
3206            self_: *mut whiteout_JpegWriter,
3207        ) -> usize;
3208        pub fn whiteout_textures_JpegWriter_getIssues_at(
3209            self_: *mut whiteout_JpegWriter,
3210            index: usize,
3211        ) -> RawCString;
3212        // DdsParser
3213        pub fn whiteout_textures_DdsParser_new() -> *mut whiteout_DdsParser;
3214        pub fn whiteout_textures_DdsParser_delete(self_: *mut whiteout_DdsParser);
3215        pub fn whiteout_textures_DdsParser_parse(
3216            self_: *mut whiteout_DdsParser,
3217            buffer: *const u8,
3218            buffer_size: usize,
3219        ) -> *mut whiteout_Texture;
3220        pub fn whiteout_textures_DdsParser_hasIssues(self_: *mut whiteout_DdsParser) -> i32;
3221        pub fn whiteout_textures_DdsParser_getIssues_count(self_: *mut whiteout_DdsParser)
3222            -> usize;
3223        pub fn whiteout_textures_DdsParser_getIssues_at(
3224            self_: *mut whiteout_DdsParser,
3225            index: usize,
3226        ) -> RawCString;
3227        // DdsWriter
3228        pub fn whiteout_textures_DdsWriter_new() -> *mut whiteout_DdsWriter;
3229        pub fn whiteout_textures_DdsWriter_delete(self_: *mut whiteout_DdsWriter);
3230        pub fn whiteout_textures_DdsWriter_write(
3231            self_: *mut whiteout_DdsWriter,
3232            texture: *mut whiteout_Texture,
3233        ) -> RawBytes;
3234        pub fn whiteout_textures_DdsWriter_hasIssues(self_: *mut whiteout_DdsWriter) -> i32;
3235        pub fn whiteout_textures_DdsWriter_getIssues_count(self_: *mut whiteout_DdsWriter)
3236            -> usize;
3237        pub fn whiteout_textures_DdsWriter_getIssues_at(
3238            self_: *mut whiteout_DdsWriter,
3239            index: usize,
3240        ) -> RawCString;
3241        // BmpParser
3242        pub fn whiteout_textures_BmpParser_new() -> *mut whiteout_BmpParser;
3243        pub fn whiteout_textures_BmpParser_delete(self_: *mut whiteout_BmpParser);
3244        pub fn whiteout_textures_BmpParser_parse(
3245            self_: *mut whiteout_BmpParser,
3246            buffer: *const u8,
3247            buffer_size: usize,
3248        ) -> *mut whiteout_Texture;
3249        pub fn whiteout_textures_BmpParser_hasIssues(self_: *mut whiteout_BmpParser) -> i32;
3250        pub fn whiteout_textures_BmpParser_getIssues_count(self_: *mut whiteout_BmpParser)
3251            -> usize;
3252        pub fn whiteout_textures_BmpParser_getIssues_at(
3253            self_: *mut whiteout_BmpParser,
3254            index: usize,
3255        ) -> RawCString;
3256        // BmpWriter
3257        pub fn whiteout_textures_BmpWriter_new() -> *mut whiteout_BmpWriter;
3258        pub fn whiteout_textures_BmpWriter_delete(self_: *mut whiteout_BmpWriter);
3259        pub fn whiteout_textures_BmpWriter_write(
3260            self_: *mut whiteout_BmpWriter,
3261            texture: *mut whiteout_Texture,
3262        ) -> RawBytes;
3263        pub fn whiteout_textures_BmpWriter_hasIssues(self_: *mut whiteout_BmpWriter) -> i32;
3264        pub fn whiteout_textures_BmpWriter_getIssues_count(self_: *mut whiteout_BmpWriter)
3265            -> usize;
3266        pub fn whiteout_textures_BmpWriter_getIssues_at(
3267            self_: *mut whiteout_BmpWriter,
3268            index: usize,
3269        ) -> RawCString;
3270        // TgaParser
3271        pub fn whiteout_textures_TgaParser_new() -> *mut whiteout_TgaParser;
3272        pub fn whiteout_textures_TgaParser_delete(self_: *mut whiteout_TgaParser);
3273        pub fn whiteout_textures_TgaParser_parse(
3274            self_: *mut whiteout_TgaParser,
3275            buffer: *const u8,
3276            buffer_size: usize,
3277        ) -> *mut whiteout_Texture;
3278        pub fn whiteout_textures_TgaParser_hasIssues(self_: *mut whiteout_TgaParser) -> i32;
3279        pub fn whiteout_textures_TgaParser_getIssues_count(self_: *mut whiteout_TgaParser)
3280            -> usize;
3281        pub fn whiteout_textures_TgaParser_getIssues_at(
3282            self_: *mut whiteout_TgaParser,
3283            index: usize,
3284        ) -> RawCString;
3285        // TgaWriter
3286        pub fn whiteout_textures_TgaWriter_new() -> *mut whiteout_TgaWriter;
3287        pub fn whiteout_textures_TgaWriter_delete(self_: *mut whiteout_TgaWriter);
3288        pub fn whiteout_textures_TgaWriter_write(
3289            self_: *mut whiteout_TgaWriter,
3290            texture: *mut whiteout_Texture,
3291        ) -> RawBytes;
3292        pub fn whiteout_textures_TgaWriter_hasIssues(self_: *mut whiteout_TgaWriter) -> i32;
3293        pub fn whiteout_textures_TgaWriter_getIssues_count(self_: *mut whiteout_TgaWriter)
3294            -> usize;
3295        pub fn whiteout_textures_TgaWriter_getIssues_at(
3296            self_: *mut whiteout_TgaWriter,
3297            index: usize,
3298        ) -> RawCString;
3299        // TiffParser
3300        pub fn whiteout_textures_TiffParser_new() -> *mut whiteout_TiffParser;
3301        pub fn whiteout_textures_TiffParser_delete(self_: *mut whiteout_TiffParser);
3302        pub fn whiteout_textures_TiffParser_parse(
3303            self_: *mut whiteout_TiffParser,
3304            buffer: *const u8,
3305            buffer_size: usize,
3306        ) -> *mut whiteout_Texture;
3307        pub fn whiteout_textures_TiffParser_hasIssues(self_: *mut whiteout_TiffParser) -> i32;
3308        pub fn whiteout_textures_TiffParser_getIssues_count(
3309            self_: *mut whiteout_TiffParser,
3310        ) -> usize;
3311        pub fn whiteout_textures_TiffParser_getIssues_at(
3312            self_: *mut whiteout_TiffParser,
3313            index: usize,
3314        ) -> RawCString;
3315        // TiffWriter
3316        pub fn whiteout_textures_TiffWriter_new() -> *mut whiteout_TiffWriter;
3317        pub fn whiteout_textures_TiffWriter_delete(self_: *mut whiteout_TiffWriter);
3318        pub fn whiteout_textures_TiffWriter_write(
3319            self_: *mut whiteout_TiffWriter,
3320            texture: *mut whiteout_Texture,
3321        ) -> RawBytes;
3322        pub fn whiteout_textures_TiffWriter_hasIssues(self_: *mut whiteout_TiffWriter) -> i32;
3323        pub fn whiteout_textures_TiffWriter_getIssues_count(
3324            self_: *mut whiteout_TiffWriter,
3325        ) -> usize;
3326        pub fn whiteout_textures_TiffWriter_getIssues_at(
3327            self_: *mut whiteout_TiffWriter,
3328            index: usize,
3329        ) -> RawCString;
3330        // GifSaveOptions
3331        pub fn whiteout_textures_GifSaveOptions_new() -> *mut whiteout_GifSaveOptions;
3332        pub fn whiteout_textures_GifSaveOptions_delete(self_: *mut whiteout_GifSaveOptions);
3333        pub fn whiteout_textures_GifSaveOptions_get_delayCs(
3334            self_: *mut whiteout_GifSaveOptions,
3335        ) -> u16;
3336        pub fn whiteout_textures_GifSaveOptions_set_delayCs(
3337            self_: *mut whiteout_GifSaveOptions,
3338            value: u16,
3339        );
3340        pub fn whiteout_textures_GifSaveOptions_get_loopCount(
3341            self_: *mut whiteout_GifSaveOptions,
3342        ) -> u16;
3343        pub fn whiteout_textures_GifSaveOptions_set_loopCount(
3344            self_: *mut whiteout_GifSaveOptions,
3345            value: u16,
3346        );
3347        pub fn whiteout_textures_GifSaveOptions_get_dither(
3348            self_: *mut whiteout_GifSaveOptions,
3349        ) -> i32;
3350        pub fn whiteout_textures_GifSaveOptions_set_dither(
3351            self_: *mut whiteout_GifSaveOptions,
3352            value: i32,
3353        );
3354        pub fn whiteout_textures_GifSaveOptions_get_ditherStrength(
3355            self_: *mut whiteout_GifSaveOptions,
3356        ) -> f32;
3357        pub fn whiteout_textures_GifSaveOptions_set_ditherStrength(
3358            self_: *mut whiteout_GifSaveOptions,
3359            value: f32,
3360        );
3361        pub fn whiteout_textures_GifSaveOptions_get_transparent(
3362            self_: *mut whiteout_GifSaveOptions,
3363        ) -> i32;
3364        pub fn whiteout_textures_GifSaveOptions_set_transparent(
3365            self_: *mut whiteout_GifSaveOptions,
3366            value: i32,
3367        );
3368        // GifWriter
3369        pub fn whiteout_textures_GifWriter_new() -> *mut whiteout_GifWriter;
3370        pub fn whiteout_textures_GifWriter_new_pool(
3371            _0: *mut core::ffi::c_void,
3372        ) -> *mut whiteout_GifWriter;
3373        pub fn whiteout_textures_GifWriter_delete(self_: *mut whiteout_GifWriter);
3374        pub fn whiteout_textures_GifWriter_write(
3375            self_: *mut whiteout_GifWriter,
3376            file_path: *const core::ffi::c_char,
3377            frames: *const *mut whiteout_Texture,
3378            frames_size: usize,
3379        );
3380        pub fn whiteout_textures_GifWriter_write_frames(
3381            self_: *mut whiteout_GifWriter,
3382            frames: *const *mut whiteout_Texture,
3383            frames_size: usize,
3384        ) -> RawBytes;
3385        pub fn whiteout_textures_GifWriter_write_filePath_frames_opts(
3386            self_: *mut whiteout_GifWriter,
3387            file_path: *const core::ffi::c_char,
3388            frames: *const *mut whiteout_Texture,
3389            frames_size: usize,
3390            opts: *mut whiteout_GifSaveOptions,
3391        );
3392        pub fn whiteout_textures_GifWriter_write_frames_opts(
3393            self_: *mut whiteout_GifWriter,
3394            frames: *const *mut whiteout_Texture,
3395            frames_size: usize,
3396            opts: *mut whiteout_GifSaveOptions,
3397        ) -> RawBytes;
3398        pub fn whiteout_textures_GifWriter_hasIssues(self_: *mut whiteout_GifWriter) -> i32;
3399        pub fn whiteout_textures_GifWriter_getIssues_count(self_: *mut whiteout_GifWriter)
3400            -> usize;
3401        pub fn whiteout_textures_GifWriter_getIssues_at(
3402            self_: *mut whiteout_GifWriter,
3403            index: usize,
3404        ) -> RawCString;
3405    }
3406}