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 TEX file or byte buffer and decodes it into a Texture.
2018pub struct TexParser {
2019    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_TexParser>,
2020}
2021
2022impl Drop for TexParser {
2023    fn drop(&mut self) {
2024        // SAFETY: `raw` came from a native constructor and Drop runs once.
2025        unsafe { ffi::whiteout_textures_TexParser_delete(self.raw.as_ptr()) }
2026    }
2027}
2028
2029impl TexParser {
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_TexParser) -> Option<Self> {
2034        core::ptr::NonNull::new(raw).map(|raw| TexParser { 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 TexParser {}
2043
2044impl core::fmt::Debug for TexParser {
2045    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2046        f.debug_struct("TexParser").finish_non_exhaustive()
2047    }
2048}
2049
2050impl TexParser {
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_TexParser_new();
2058            Self::from_raw(raw).expect("native TexParser allocation failed")
2059        }
2060    }
2061
2062    /// Parse a TEX file from disk.
2063    pub fn parse(&mut self, file_path: &str) -> Option<Texture> {
2064        let file_path_cstr = std::ffi::CString::new(file_path).unwrap_or_default();
2065        // SAFETY: handle is live for the duration of the call.
2066        unsafe {
2067            Texture::from_raw(ffi::whiteout_textures_TexParser_parse(
2068                self.raw.as_ptr(),
2069                file_path_cstr.as_ptr(),
2070            ))
2071        }
2072    }
2073
2074    /// Parse a TEX byte buffer.
2075    pub fn parse_buffer(&mut self, buffer: &[u8]) -> Option<Texture> {
2076        // SAFETY: handle is live for the duration of the call.
2077        unsafe {
2078            Texture::from_raw(ffi::whiteout_textures_TexParser_parse_buffer(
2079                self.raw.as_ptr(),
2080                buffer.as_ptr(),
2081                buffer.len(),
2082            ))
2083        }
2084    }
2085
2086    /// Parse a D4 TEX file from two file paths (metadata .tex + pixel payload).
2087    pub fn parse_tex_file_path_payload_file_path(
2088        &mut self,
2089        tex_file_path: &str,
2090        payload_file_path: &str,
2091    ) -> Option<Texture> {
2092        let tex_file_path_cstr = std::ffi::CString::new(tex_file_path).unwrap_or_default();
2093        let payload_file_path_cstr = std::ffi::CString::new(payload_file_path).unwrap_or_default();
2094        // SAFETY: handle is live for the duration of the call.
2095        unsafe {
2096            Texture::from_raw(
2097                ffi::whiteout_textures_TexParser_parse_texFilePath_payloadFilePath(
2098                    self.raw.as_ptr(),
2099                    tex_file_path_cstr.as_ptr(),
2100                    payload_file_path_cstr.as_ptr(),
2101                ),
2102            )
2103        }
2104    }
2105
2106    /// Parse a D4 TEX file from two byte buffers (metadata + pixel payload).
2107    pub fn parse_tex_data_payload_data(
2108        &mut self,
2109        tex_data: &[u8],
2110        payload_data: &[u8],
2111    ) -> Option<Texture> {
2112        // SAFETY: handle is live for the duration of the call.
2113        unsafe {
2114            Texture::from_raw(ffi::whiteout_textures_TexParser_parse_texData_payloadData(
2115                self.raw.as_ptr(),
2116                tex_data.as_ptr(),
2117                tex_data.len(),
2118                payload_data.as_ptr(),
2119                payload_data.len(),
2120            ))
2121        }
2122    }
2123
2124    /// Parse a D4 TEX with hi-res + low-res payloads from file paths.
2125    pub fn parse_tex_file_path_hi_res_payload_file_path_low_res_payload_file_path(
2126        &mut self,
2127        tex_file_path: &str,
2128        hi_res_payload_file_path: &str,
2129        low_res_payload_file_path: &str,
2130    ) -> Option<Texture> {
2131        let tex_file_path_cstr = std::ffi::CString::new(tex_file_path).unwrap_or_default();
2132        let hi_res_payload_file_path_cstr =
2133            std::ffi::CString::new(hi_res_payload_file_path).unwrap_or_default();
2134        let low_res_payload_file_path_cstr =
2135            std::ffi::CString::new(low_res_payload_file_path).unwrap_or_default();
2136        // SAFETY: handle is live for the duration of the call.
2137        unsafe {
2138            Texture::from_raw(ffi::whiteout_textures_TexParser_parse_texFilePath_hiResPayloadFilePath_lowResPayloadFilePath(self.raw.as_ptr(), tex_file_path_cstr.as_ptr(), hi_res_payload_file_path_cstr.as_ptr(), low_res_payload_file_path_cstr.as_ptr()))
2139        }
2140    }
2141
2142    /// @return true if the last parse produced any issues.
2143    pub fn has_issues(&self) -> bool {
2144        // SAFETY: handle is live for the duration of the call.
2145        unsafe { ffi::whiteout_textures_TexParser_hasIssues(self.raw.as_ptr()) != 0 }
2146    }
2147
2148    /// @return accumulated issues from the last parse call.
2149    pub fn issues(&self) -> Vec<String> {
2150        // SAFETY: index stays below the reported count.
2151        unsafe {
2152            let n = ffi::whiteout_textures_TexParser_getIssues_count(self.raw.as_ptr());
2153            (0..n)
2154                .map(|i| {
2155                    crate::support::take_string(ffi::whiteout_textures_TexParser_getIssues_at(
2156                        self.raw.as_ptr(),
2157                        i,
2158                    ))
2159                })
2160                .collect()
2161        }
2162    }
2163}
2164
2165impl Default for TexParser {
2166    fn default() -> Self {
2167        Self::new()
2168    }
2169}
2170
2171/// Reads a Diablo II: Resurrected `.texture` file and decodes it into a Texture.
2172pub struct D2rTextureParser {
2173    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_D2rTextureParser>,
2174}
2175
2176impl Drop for D2rTextureParser {
2177    fn drop(&mut self) {
2178        // SAFETY: `raw` came from a native constructor and Drop runs once.
2179        unsafe { ffi::whiteout_textures_D2rTextureParser_delete(self.raw.as_ptr()) }
2180    }
2181}
2182
2183impl D2rTextureParser {
2184    /// # Safety
2185    /// `raw` must be a live handle this value takes ownership of.
2186    #[allow(dead_code)] // used by whichever methods return this type
2187    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_D2rTextureParser) -> Option<Self> {
2188        core::ptr::NonNull::new(raw).map(|raw| D2rTextureParser { raw })
2189    }
2190}
2191
2192// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2193// is deliberately NOT implemented — the C++ types make no documented
2194// guarantee about concurrent use, and claiming one we haven't verified
2195// would be unsound. See `@bind thread_safe` in the plan.
2196unsafe impl Send for D2rTextureParser {}
2197
2198impl core::fmt::Debug for D2rTextureParser {
2199    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2200        f.debug_struct("D2rTextureParser").finish_non_exhaustive()
2201    }
2202}
2203
2204impl D2rTextureParser {
2205    /// # Panics
2206    /// Panics if the native allocation fails.
2207    pub fn new() -> Self {
2208        // SAFETY: the native constructor returns a live handle; a null here
2209        // means the library is unusable.
2210        unsafe {
2211            let raw = ffi::whiteout_textures_D2rTextureParser_new();
2212            Self::from_raw(raw).expect("native D2rTextureParser allocation failed")
2213        }
2214    }
2215
2216    /// Parse a `.texture` byte buffer.
2217    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
2218        // SAFETY: handle is live for the duration of the call.
2219        unsafe {
2220            Texture::from_raw(ffi::whiteout_textures_D2rTextureParser_parse(
2221                self.raw.as_ptr(),
2222                buffer.as_ptr(),
2223                buffer.len(),
2224            ))
2225        }
2226    }
2227
2228    /// @return true if @p buffer has a valid `.texture` header and known format.
2229    pub fn detect(&self, buffer: &[u8]) -> bool {
2230        // SAFETY: handle is live for the duration of the call.
2231        unsafe {
2232            ffi::whiteout_textures_D2rTextureParser_detect(
2233                self.raw.as_ptr(),
2234                buffer.as_ptr(),
2235                buffer.len(),
2236            ) != 0
2237        }
2238    }
2239
2240    /// @return true if the last parse produced any issues.
2241    pub fn has_issues(&self) -> bool {
2242        // SAFETY: handle is live for the duration of the call.
2243        unsafe { ffi::whiteout_textures_D2rTextureParser_hasIssues(self.raw.as_ptr()) != 0 }
2244    }
2245
2246    /// @return accumulated issues from the last parse call.
2247    pub fn issues(&self) -> Vec<String> {
2248        // SAFETY: index stays below the reported count.
2249        unsafe {
2250            let n = ffi::whiteout_textures_D2rTextureParser_getIssues_count(self.raw.as_ptr());
2251            (0..n)
2252                .map(|i| {
2253                    crate::support::take_string(
2254                        ffi::whiteout_textures_D2rTextureParser_getIssues_at(self.raw.as_ptr(), i),
2255                    )
2256                })
2257                .collect()
2258        }
2259    }
2260}
2261
2262impl Default for D2rTextureParser {
2263    fn default() -> Self {
2264        Self::new()
2265    }
2266}
2267
2268/// Encodes a Texture into the Diablo II: Resurrected `.texture` format.
2269pub struct D2rTextureWriter {
2270    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_D2rTextureWriter>,
2271}
2272
2273impl Drop for D2rTextureWriter {
2274    fn drop(&mut self) {
2275        // SAFETY: `raw` came from a native constructor and Drop runs once.
2276        unsafe { ffi::whiteout_textures_D2rTextureWriter_delete(self.raw.as_ptr()) }
2277    }
2278}
2279
2280impl D2rTextureWriter {
2281    /// # Safety
2282    /// `raw` must be a live handle this value takes ownership of.
2283    #[allow(dead_code)] // used by whichever methods return this type
2284    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_D2rTextureWriter) -> Option<Self> {
2285        core::ptr::NonNull::new(raw).map(|raw| D2rTextureWriter { raw })
2286    }
2287}
2288
2289// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2290// is deliberately NOT implemented — the C++ types make no documented
2291// guarantee about concurrent use, and claiming one we haven't verified
2292// would be unsound. See `@bind thread_safe` in the plan.
2293unsafe impl Send for D2rTextureWriter {}
2294
2295impl core::fmt::Debug for D2rTextureWriter {
2296    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2297        f.debug_struct("D2rTextureWriter").finish_non_exhaustive()
2298    }
2299}
2300
2301impl D2rTextureWriter {
2302    /// # Panics
2303    /// Panics if the native allocation fails.
2304    pub fn new() -> Self {
2305        // SAFETY: the native constructor returns a live handle; a null here
2306        // means the library is unusable.
2307        unsafe {
2308            let raw = ffi::whiteout_textures_D2rTextureWriter_new();
2309            Self::from_raw(raw).expect("native D2rTextureWriter allocation failed")
2310        }
2311    }
2312
2313    /// Serialize the texture to a byte buffer (default options).
2314    pub fn write(&mut self, texture: &Texture) -> Bytes {
2315        // SAFETY: handle is live for the duration of the call.
2316        unsafe {
2317            Bytes::from_raw(ffi::whiteout_textures_D2rTextureWriter_write(
2318                self.raw.as_ptr(),
2319                texture.raw.as_ptr(),
2320            ))
2321            .unwrap_or_else(Bytes::empty)
2322        }
2323    }
2324
2325    /// @return true if the last write produced any issues.
2326    pub fn has_issues(&self) -> bool {
2327        // SAFETY: handle is live for the duration of the call.
2328        unsafe { ffi::whiteout_textures_D2rTextureWriter_hasIssues(self.raw.as_ptr()) != 0 }
2329    }
2330
2331    /// @return accumulated issues from the last write call.
2332    pub fn issues(&self) -> Vec<String> {
2333        // SAFETY: index stays below the reported count.
2334        unsafe {
2335            let n = ffi::whiteout_textures_D2rTextureWriter_getIssues_count(self.raw.as_ptr());
2336            (0..n)
2337                .map(|i| {
2338                    crate::support::take_string(
2339                        ffi::whiteout_textures_D2rTextureWriter_getIssues_at(self.raw.as_ptr(), i),
2340                    )
2341                })
2342                .collect()
2343        }
2344    }
2345}
2346
2347impl Default for D2rTextureWriter {
2348    fn default() -> Self {
2349        Self::new()
2350    }
2351}
2352
2353/// Reads a BMP file or byte buffer and decodes it into a Texture.
2354pub struct BmpParser {
2355    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_BmpParser>,
2356}
2357
2358impl Drop for BmpParser {
2359    fn drop(&mut self) {
2360        // SAFETY: `raw` came from a native constructor and Drop runs once.
2361        unsafe { ffi::whiteout_textures_BmpParser_delete(self.raw.as_ptr()) }
2362    }
2363}
2364
2365impl BmpParser {
2366    /// # Safety
2367    /// `raw` must be a live handle this value takes ownership of.
2368    #[allow(dead_code)] // used by whichever methods return this type
2369    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_BmpParser) -> Option<Self> {
2370        core::ptr::NonNull::new(raw).map(|raw| BmpParser { raw })
2371    }
2372}
2373
2374// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2375// is deliberately NOT implemented — the C++ types make no documented
2376// guarantee about concurrent use, and claiming one we haven't verified
2377// would be unsound. See `@bind thread_safe` in the plan.
2378unsafe impl Send for BmpParser {}
2379
2380impl core::fmt::Debug for BmpParser {
2381    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2382        f.debug_struct("BmpParser").finish_non_exhaustive()
2383    }
2384}
2385
2386impl BmpParser {
2387    /// # Panics
2388    /// Panics if the native allocation fails.
2389    pub fn new() -> Self {
2390        // SAFETY: the native constructor returns a live handle; a null here
2391        // means the library is unusable.
2392        unsafe {
2393            let raw = ffi::whiteout_textures_BmpParser_new();
2394            Self::from_raw(raw).expect("native BmpParser allocation failed")
2395        }
2396    }
2397
2398    /// Parse a BMP byte buffer.
2399    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
2400        // SAFETY: handle is live for the duration of the call.
2401        unsafe {
2402            Texture::from_raw(ffi::whiteout_textures_BmpParser_parse(
2403                self.raw.as_ptr(),
2404                buffer.as_ptr(),
2405                buffer.len(),
2406            ))
2407        }
2408    }
2409
2410    /// @return true if the last parse produced any issues.
2411    pub fn has_issues(&self) -> bool {
2412        // SAFETY: handle is live for the duration of the call.
2413        unsafe { ffi::whiteout_textures_BmpParser_hasIssues(self.raw.as_ptr()) != 0 }
2414    }
2415
2416    /// @return accumulated issues from the last parse call.
2417    pub fn issues(&self) -> Vec<String> {
2418        // SAFETY: index stays below the reported count.
2419        unsafe {
2420            let n = ffi::whiteout_textures_BmpParser_getIssues_count(self.raw.as_ptr());
2421            (0..n)
2422                .map(|i| {
2423                    crate::support::take_string(ffi::whiteout_textures_BmpParser_getIssues_at(
2424                        self.raw.as_ptr(),
2425                        i,
2426                    ))
2427                })
2428                .collect()
2429        }
2430    }
2431}
2432
2433impl Default for BmpParser {
2434    fn default() -> Self {
2435        Self::new()
2436    }
2437}
2438
2439/// Encodes a Texture into BMP format.
2440pub struct BmpWriter {
2441    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_BmpWriter>,
2442}
2443
2444impl Drop for BmpWriter {
2445    fn drop(&mut self) {
2446        // SAFETY: `raw` came from a native constructor and Drop runs once.
2447        unsafe { ffi::whiteout_textures_BmpWriter_delete(self.raw.as_ptr()) }
2448    }
2449}
2450
2451impl BmpWriter {
2452    /// # Safety
2453    /// `raw` must be a live handle this value takes ownership of.
2454    #[allow(dead_code)] // used by whichever methods return this type
2455    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_BmpWriter) -> Option<Self> {
2456        core::ptr::NonNull::new(raw).map(|raw| BmpWriter { raw })
2457    }
2458}
2459
2460// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2461// is deliberately NOT implemented — the C++ types make no documented
2462// guarantee about concurrent use, and claiming one we haven't verified
2463// would be unsound. See `@bind thread_safe` in the plan.
2464unsafe impl Send for BmpWriter {}
2465
2466impl core::fmt::Debug for BmpWriter {
2467    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2468        f.debug_struct("BmpWriter").finish_non_exhaustive()
2469    }
2470}
2471
2472impl BmpWriter {
2473    /// # Panics
2474    /// Panics if the native allocation fails.
2475    pub fn new() -> Self {
2476        // SAFETY: the native constructor returns a live handle; a null here
2477        // means the library is unusable.
2478        unsafe {
2479            let raw = ffi::whiteout_textures_BmpWriter_new();
2480            Self::from_raw(raw).expect("native BmpWriter allocation failed")
2481        }
2482    }
2483
2484    /// Serialize the texture to a BMP byte buffer.
2485    pub fn write(&mut self, texture: &Texture) -> Bytes {
2486        // SAFETY: handle is live for the duration of the call.
2487        unsafe {
2488            Bytes::from_raw(ffi::whiteout_textures_BmpWriter_write(
2489                self.raw.as_ptr(),
2490                texture.raw.as_ptr(),
2491            ))
2492            .unwrap_or_else(Bytes::empty)
2493        }
2494    }
2495
2496    /// @return true if the last write produced any issues.
2497    pub fn has_issues(&self) -> bool {
2498        // SAFETY: handle is live for the duration of the call.
2499        unsafe { ffi::whiteout_textures_BmpWriter_hasIssues(self.raw.as_ptr()) != 0 }
2500    }
2501
2502    /// @return accumulated issues from the last write call.
2503    pub fn issues(&self) -> Vec<String> {
2504        // SAFETY: index stays below the reported count.
2505        unsafe {
2506            let n = ffi::whiteout_textures_BmpWriter_getIssues_count(self.raw.as_ptr());
2507            (0..n)
2508                .map(|i| {
2509                    crate::support::take_string(ffi::whiteout_textures_BmpWriter_getIssues_at(
2510                        self.raw.as_ptr(),
2511                        i,
2512                    ))
2513                })
2514                .collect()
2515        }
2516    }
2517}
2518
2519impl Default for BmpWriter {
2520    fn default() -> Self {
2521        Self::new()
2522    }
2523}
2524
2525/// Reads a TGA file or byte buffer and decodes it into a Texture.
2526pub struct TgaParser {
2527    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_TgaParser>,
2528}
2529
2530impl Drop for TgaParser {
2531    fn drop(&mut self) {
2532        // SAFETY: `raw` came from a native constructor and Drop runs once.
2533        unsafe { ffi::whiteout_textures_TgaParser_delete(self.raw.as_ptr()) }
2534    }
2535}
2536
2537impl TgaParser {
2538    /// # Safety
2539    /// `raw` must be a live handle this value takes ownership of.
2540    #[allow(dead_code)] // used by whichever methods return this type
2541    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_TgaParser) -> Option<Self> {
2542        core::ptr::NonNull::new(raw).map(|raw| TgaParser { raw })
2543    }
2544}
2545
2546// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2547// is deliberately NOT implemented — the C++ types make no documented
2548// guarantee about concurrent use, and claiming one we haven't verified
2549// would be unsound. See `@bind thread_safe` in the plan.
2550unsafe impl Send for TgaParser {}
2551
2552impl core::fmt::Debug for TgaParser {
2553    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2554        f.debug_struct("TgaParser").finish_non_exhaustive()
2555    }
2556}
2557
2558impl TgaParser {
2559    /// # Panics
2560    /// Panics if the native allocation fails.
2561    pub fn new() -> Self {
2562        // SAFETY: the native constructor returns a live handle; a null here
2563        // means the library is unusable.
2564        unsafe {
2565            let raw = ffi::whiteout_textures_TgaParser_new();
2566            Self::from_raw(raw).expect("native TgaParser allocation failed")
2567        }
2568    }
2569
2570    /// Parse a TGA byte buffer.
2571    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
2572        // SAFETY: handle is live for the duration of the call.
2573        unsafe {
2574            Texture::from_raw(ffi::whiteout_textures_TgaParser_parse(
2575                self.raw.as_ptr(),
2576                buffer.as_ptr(),
2577                buffer.len(),
2578            ))
2579        }
2580    }
2581
2582    /// @return true if the last parse produced any issues.
2583    pub fn has_issues(&self) -> bool {
2584        // SAFETY: handle is live for the duration of the call.
2585        unsafe { ffi::whiteout_textures_TgaParser_hasIssues(self.raw.as_ptr()) != 0 }
2586    }
2587
2588    /// @return accumulated issues from the last parse call.
2589    pub fn issues(&self) -> Vec<String> {
2590        // SAFETY: index stays below the reported count.
2591        unsafe {
2592            let n = ffi::whiteout_textures_TgaParser_getIssues_count(self.raw.as_ptr());
2593            (0..n)
2594                .map(|i| {
2595                    crate::support::take_string(ffi::whiteout_textures_TgaParser_getIssues_at(
2596                        self.raw.as_ptr(),
2597                        i,
2598                    ))
2599                })
2600                .collect()
2601        }
2602    }
2603}
2604
2605impl Default for TgaParser {
2606    fn default() -> Self {
2607        Self::new()
2608    }
2609}
2610
2611/// Encodes a Texture into TGA format.
2612pub struct TgaWriter {
2613    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_TgaWriter>,
2614}
2615
2616impl Drop for TgaWriter {
2617    fn drop(&mut self) {
2618        // SAFETY: `raw` came from a native constructor and Drop runs once.
2619        unsafe { ffi::whiteout_textures_TgaWriter_delete(self.raw.as_ptr()) }
2620    }
2621}
2622
2623impl TgaWriter {
2624    /// # Safety
2625    /// `raw` must be a live handle this value takes ownership of.
2626    #[allow(dead_code)] // used by whichever methods return this type
2627    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_TgaWriter) -> Option<Self> {
2628        core::ptr::NonNull::new(raw).map(|raw| TgaWriter { raw })
2629    }
2630}
2631
2632// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2633// is deliberately NOT implemented — the C++ types make no documented
2634// guarantee about concurrent use, and claiming one we haven't verified
2635// would be unsound. See `@bind thread_safe` in the plan.
2636unsafe impl Send for TgaWriter {}
2637
2638impl core::fmt::Debug for TgaWriter {
2639    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2640        f.debug_struct("TgaWriter").finish_non_exhaustive()
2641    }
2642}
2643
2644impl TgaWriter {
2645    /// # Panics
2646    /// Panics if the native allocation fails.
2647    pub fn new() -> Self {
2648        // SAFETY: the native constructor returns a live handle; a null here
2649        // means the library is unusable.
2650        unsafe {
2651            let raw = ffi::whiteout_textures_TgaWriter_new();
2652            Self::from_raw(raw).expect("native TgaWriter allocation failed")
2653        }
2654    }
2655
2656    /// Serialize the texture to a TGA byte buffer.
2657    pub fn write(&mut self, texture: &Texture) -> Bytes {
2658        // SAFETY: handle is live for the duration of the call.
2659        unsafe {
2660            Bytes::from_raw(ffi::whiteout_textures_TgaWriter_write(
2661                self.raw.as_ptr(),
2662                texture.raw.as_ptr(),
2663            ))
2664            .unwrap_or_else(Bytes::empty)
2665        }
2666    }
2667
2668    /// @return true if the last write produced any issues.
2669    pub fn has_issues(&self) -> bool {
2670        // SAFETY: handle is live for the duration of the call.
2671        unsafe { ffi::whiteout_textures_TgaWriter_hasIssues(self.raw.as_ptr()) != 0 }
2672    }
2673
2674    /// @return accumulated issues from the last write call.
2675    pub fn issues(&self) -> Vec<String> {
2676        // SAFETY: index stays below the reported count.
2677        unsafe {
2678            let n = ffi::whiteout_textures_TgaWriter_getIssues_count(self.raw.as_ptr());
2679            (0..n)
2680                .map(|i| {
2681                    crate::support::take_string(ffi::whiteout_textures_TgaWriter_getIssues_at(
2682                        self.raw.as_ptr(),
2683                        i,
2684                    ))
2685                })
2686                .collect()
2687        }
2688    }
2689}
2690
2691impl Default for TgaWriter {
2692    fn default() -> Self {
2693        Self::new()
2694    }
2695}
2696
2697/// Reads a TIFF file or byte buffer and decodes it into a Texture.
2698pub struct TiffParser {
2699    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_TiffParser>,
2700}
2701
2702impl Drop for TiffParser {
2703    fn drop(&mut self) {
2704        // SAFETY: `raw` came from a native constructor and Drop runs once.
2705        unsafe { ffi::whiteout_textures_TiffParser_delete(self.raw.as_ptr()) }
2706    }
2707}
2708
2709impl TiffParser {
2710    /// # Safety
2711    /// `raw` must be a live handle this value takes ownership of.
2712    #[allow(dead_code)] // used by whichever methods return this type
2713    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_TiffParser) -> Option<Self> {
2714        core::ptr::NonNull::new(raw).map(|raw| TiffParser { raw })
2715    }
2716}
2717
2718// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2719// is deliberately NOT implemented — the C++ types make no documented
2720// guarantee about concurrent use, and claiming one we haven't verified
2721// would be unsound. See `@bind thread_safe` in the plan.
2722unsafe impl Send for TiffParser {}
2723
2724impl core::fmt::Debug for TiffParser {
2725    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2726        f.debug_struct("TiffParser").finish_non_exhaustive()
2727    }
2728}
2729
2730impl TiffParser {
2731    /// # Panics
2732    /// Panics if the native allocation fails.
2733    pub fn new() -> Self {
2734        // SAFETY: the native constructor returns a live handle; a null here
2735        // means the library is unusable.
2736        unsafe {
2737            let raw = ffi::whiteout_textures_TiffParser_new();
2738            Self::from_raw(raw).expect("native TiffParser allocation failed")
2739        }
2740    }
2741
2742    /// Parse a TIFF byte buffer.
2743    pub fn parse(&mut self, buffer: &[u8]) -> Option<Texture> {
2744        // SAFETY: handle is live for the duration of the call.
2745        unsafe {
2746            Texture::from_raw(ffi::whiteout_textures_TiffParser_parse(
2747                self.raw.as_ptr(),
2748                buffer.as_ptr(),
2749                buffer.len(),
2750            ))
2751        }
2752    }
2753
2754    /// @return true if the last parse produced any issues.
2755    pub fn has_issues(&self) -> bool {
2756        // SAFETY: handle is live for the duration of the call.
2757        unsafe { ffi::whiteout_textures_TiffParser_hasIssues(self.raw.as_ptr()) != 0 }
2758    }
2759
2760    /// @return accumulated issues from the last parse call.
2761    pub fn issues(&self) -> Vec<String> {
2762        // SAFETY: index stays below the reported count.
2763        unsafe {
2764            let n = ffi::whiteout_textures_TiffParser_getIssues_count(self.raw.as_ptr());
2765            (0..n)
2766                .map(|i| {
2767                    crate::support::take_string(ffi::whiteout_textures_TiffParser_getIssues_at(
2768                        self.raw.as_ptr(),
2769                        i,
2770                    ))
2771                })
2772                .collect()
2773        }
2774    }
2775}
2776
2777impl Default for TiffParser {
2778    fn default() -> Self {
2779        Self::new()
2780    }
2781}
2782
2783/// Encodes a Texture into TIFF format.
2784pub struct TiffWriter {
2785    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_TiffWriter>,
2786}
2787
2788impl Drop for TiffWriter {
2789    fn drop(&mut self) {
2790        // SAFETY: `raw` came from a native constructor and Drop runs once.
2791        unsafe { ffi::whiteout_textures_TiffWriter_delete(self.raw.as_ptr()) }
2792    }
2793}
2794
2795impl TiffWriter {
2796    /// # Safety
2797    /// `raw` must be a live handle this value takes ownership of.
2798    #[allow(dead_code)] // used by whichever methods return this type
2799    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_TiffWriter) -> Option<Self> {
2800        core::ptr::NonNull::new(raw).map(|raw| TiffWriter { raw })
2801    }
2802}
2803
2804// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2805// is deliberately NOT implemented — the C++ types make no documented
2806// guarantee about concurrent use, and claiming one we haven't verified
2807// would be unsound. See `@bind thread_safe` in the plan.
2808unsafe impl Send for TiffWriter {}
2809
2810impl core::fmt::Debug for TiffWriter {
2811    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2812        f.debug_struct("TiffWriter").finish_non_exhaustive()
2813    }
2814}
2815
2816impl TiffWriter {
2817    /// # Panics
2818    /// Panics if the native allocation fails.
2819    pub fn new() -> Self {
2820        // SAFETY: the native constructor returns a live handle; a null here
2821        // means the library is unusable.
2822        unsafe {
2823            let raw = ffi::whiteout_textures_TiffWriter_new();
2824            Self::from_raw(raw).expect("native TiffWriter allocation failed")
2825        }
2826    }
2827
2828    /// Serialize the texture to a TIFF byte buffer.
2829    pub fn write(&mut self, texture: &Texture) -> Bytes {
2830        // SAFETY: handle is live for the duration of the call.
2831        unsafe {
2832            Bytes::from_raw(ffi::whiteout_textures_TiffWriter_write(
2833                self.raw.as_ptr(),
2834                texture.raw.as_ptr(),
2835            ))
2836            .unwrap_or_else(Bytes::empty)
2837        }
2838    }
2839
2840    /// @return true if the last write produced any issues.
2841    pub fn has_issues(&self) -> bool {
2842        // SAFETY: handle is live for the duration of the call.
2843        unsafe { ffi::whiteout_textures_TiffWriter_hasIssues(self.raw.as_ptr()) != 0 }
2844    }
2845
2846    /// @return accumulated issues from the last write call.
2847    pub fn issues(&self) -> Vec<String> {
2848        // SAFETY: index stays below the reported count.
2849        unsafe {
2850            let n = ffi::whiteout_textures_TiffWriter_getIssues_count(self.raw.as_ptr());
2851            (0..n)
2852                .map(|i| {
2853                    crate::support::take_string(ffi::whiteout_textures_TiffWriter_getIssues_at(
2854                        self.raw.as_ptr(),
2855                        i,
2856                    ))
2857                })
2858                .collect()
2859        }
2860    }
2861}
2862
2863impl Default for TiffWriter {
2864    fn default() -> Self {
2865        Self::new()
2866    }
2867}
2868
2869/// Per-write options for GIF encoding.
2870#[derive(Clone, Debug, PartialEq)]
2871pub struct GifSaveOptions {
2872    /// Delay between frames in centiseconds (1/100 s).  0 = unspecified.
2873    pub delay_cs: u16,
2874    /// Number of times the animation should loop.  0 = loop forever.
2875    pub loop_count: u16,
2876    /// Enable blue-noise ordered dithering when mapping pixels to the palette.
2877    pub dither: bool,
2878    /// Dither strength in `[0, 1]`.  0 = no visible dithering, 1 = full.
2879    pub dither_strength: f32,
2880    /// 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.
2881    pub transparent: bool,
2882}
2883
2884impl Default for GifSaveOptions {
2885    fn default() -> Self {
2886        // SAFETY: `_new` always returns a live handle; freed before return.
2887        unsafe {
2888            let h = ffi::whiteout_textures_GifSaveOptions_new();
2889            let out = GifSaveOptions {
2890                delay_cs: ffi::whiteout_textures_GifSaveOptions_get_delayCs(h),
2891                loop_count: ffi::whiteout_textures_GifSaveOptions_get_loopCount(h),
2892                dither: ffi::whiteout_textures_GifSaveOptions_get_dither(h) != 0,
2893                dither_strength: ffi::whiteout_textures_GifSaveOptions_get_ditherStrength(h),
2894                transparent: ffi::whiteout_textures_GifSaveOptions_get_transparent(h) != 0,
2895            };
2896            ffi::whiteout_textures_GifSaveOptions_delete(h);
2897            out
2898        }
2899    }
2900}
2901
2902impl GifSaveOptions {
2903    /// Build a native handle carrying these values. Caller frees it.
2904    #[allow(dead_code)] // consumed once the methods taking these options bind
2905    pub(crate) unsafe fn to_native(&self) -> *mut ffi::whiteout_GifSaveOptions {
2906        unsafe {
2907            let h = ffi::whiteout_textures_GifSaveOptions_new();
2908            ffi::whiteout_textures_GifSaveOptions_set_delayCs(h, self.delay_cs);
2909            ffi::whiteout_textures_GifSaveOptions_set_loopCount(h, self.loop_count);
2910            ffi::whiteout_textures_GifSaveOptions_set_dither(h, if self.dither { 1 } else { 0 });
2911            ffi::whiteout_textures_GifSaveOptions_set_ditherStrength(h, self.dither_strength);
2912            ffi::whiteout_textures_GifSaveOptions_set_transparent(
2913                h,
2914                if self.transparent { 1 } else { 0 },
2915            );
2916            h
2917        }
2918    }
2919
2920    /// Free a handle produced by [`Self::to_native`].
2921    ///
2922    /// # Safety
2923    /// `h` must have come from `to_native` and not been freed already.
2924    #[allow(dead_code)]
2925    pub(crate) unsafe fn free_native(h: *mut ffi::whiteout_GifSaveOptions) {
2926        unsafe { ffi::whiteout_textures_GifSaveOptions_delete(h) }
2927    }
2928}
2929
2930/// Encodes a sequence of Texture frames into GIF89a format.
2931///
2932/// Unlike the single-image writers (BMP, TGA, …), this writer accepts a vector of frames.  It does **not** inherit from `textures::Writer`.
2933pub struct GifWriter {
2934    pub(crate) raw: core::ptr::NonNull<ffi::whiteout_GifWriter>,
2935}
2936
2937impl Drop for GifWriter {
2938    fn drop(&mut self) {
2939        // SAFETY: `raw` came from a native constructor and Drop runs once.
2940        unsafe { ffi::whiteout_textures_GifWriter_delete(self.raw.as_ptr()) }
2941    }
2942}
2943
2944impl GifWriter {
2945    /// # Safety
2946    /// `raw` must be a live handle this value takes ownership of.
2947    #[allow(dead_code)] // used by whichever methods return this type
2948    pub(crate) unsafe fn from_raw(raw: *mut ffi::whiteout_GifWriter) -> Option<Self> {
2949        core::ptr::NonNull::new(raw).map(|raw| GifWriter { raw })
2950    }
2951}
2952
2953// SAFETY: handles are plain heap pointers with no thread affinity. `Sync`
2954// is deliberately NOT implemented — the C++ types make no documented
2955// guarantee about concurrent use, and claiming one we haven't verified
2956// would be unsound. See `@bind thread_safe` in the plan.
2957unsafe impl Send for GifWriter {}
2958
2959impl core::fmt::Debug for GifWriter {
2960    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2961        f.debug_struct("GifWriter").finish_non_exhaustive()
2962    }
2963}
2964
2965impl GifWriter {
2966    /// # Panics
2967    /// Panics if the native allocation fails.
2968    pub fn new() -> Self {
2969        // SAFETY: the native constructor returns a live handle; a null here
2970        // means the library is unusable.
2971        unsafe {
2972            let raw = ffi::whiteout_textures_GifWriter_new();
2973            Self::from_raw(raw).expect("native GifWriter allocation failed")
2974        }
2975    }
2976
2977    /// Write frames to a GIF file on disk using default options.
2978    pub fn write(&mut self, file_path: &str, frames: &[&Texture]) {
2979        let file_path_cstr = std::ffi::CString::new(file_path).unwrap_or_default();
2980        let frames_ptrs: Vec<_> = frames.iter().map(|v| v.raw.as_ptr()).collect();
2981        // SAFETY: handle is live for the duration of the call.
2982        unsafe {
2983            ffi::whiteout_textures_GifWriter_write(
2984                self.raw.as_ptr(),
2985                file_path_cstr.as_ptr(),
2986                frames_ptrs.as_ptr(),
2987                frames.len(),
2988            );
2989        }
2990    }
2991
2992    /// Write frames to a GIF byte buffer using default options.
2993    pub fn write_frames(&mut self, frames: &[&Texture]) -> Bytes {
2994        let frames_ptrs: Vec<_> = frames.iter().map(|v| v.raw.as_ptr()).collect();
2995        // SAFETY: handle is live for the duration of the call.
2996        unsafe {
2997            Bytes::from_raw(ffi::whiteout_textures_GifWriter_write_frames(
2998                self.raw.as_ptr(),
2999                frames_ptrs.as_ptr(),
3000                frames.len(),
3001            ))
3002            .unwrap_or_else(Bytes::empty)
3003        }
3004    }
3005
3006    /// Write frames to a GIF file on disk with explicit options.
3007    pub fn write_file_path_frames_opts(
3008        &mut self,
3009        file_path: &str,
3010        frames: &[&Texture],
3011        opts: &GifSaveOptions,
3012    ) {
3013        let file_path_cstr = std::ffi::CString::new(file_path).unwrap_or_default();
3014        let frames_ptrs: Vec<_> = frames.iter().map(|v| v.raw.as_ptr()).collect();
3015        let opts_native = unsafe { opts.to_native() };
3016        // SAFETY: handle is live for the call; the staged
3017        // option handles are freed immediately after.
3018        unsafe {
3019            ffi::whiteout_textures_GifWriter_write_filePath_frames_opts(
3020                self.raw.as_ptr(),
3021                file_path_cstr.as_ptr(),
3022                frames_ptrs.as_ptr(),
3023                frames.len(),
3024                opts_native,
3025            );
3026            GifSaveOptions::free_native(opts_native);
3027        }
3028    }
3029
3030    /// Write frames to a GIF byte buffer with explicit options.
3031    pub fn write_frames_opts(&mut self, frames: &[&Texture], opts: &GifSaveOptions) -> Bytes {
3032        let frames_ptrs: Vec<_> = frames.iter().map(|v| v.raw.as_ptr()).collect();
3033        let opts_native = unsafe { opts.to_native() };
3034        // SAFETY: handle is live for the call; the staged
3035        // option handles are freed immediately after.
3036        unsafe {
3037            let __r = Bytes::from_raw(ffi::whiteout_textures_GifWriter_write_frames_opts(
3038                self.raw.as_ptr(),
3039                frames_ptrs.as_ptr(),
3040                frames.len(),
3041                opts_native,
3042            ))
3043            .unwrap_or_else(Bytes::empty);
3044            GifSaveOptions::free_native(opts_native);
3045            __r
3046        }
3047    }
3048
3049    /// @return true if the last write produced any issues.
3050    pub fn has_issues(&self) -> bool {
3051        // SAFETY: handle is live for the duration of the call.
3052        unsafe { ffi::whiteout_textures_GifWriter_hasIssues(self.raw.as_ptr()) != 0 }
3053    }
3054
3055    /// @return accumulated issues from the last write call.
3056    pub fn issues(&self) -> Vec<String> {
3057        // SAFETY: index stays below the reported count.
3058        unsafe {
3059            let n = ffi::whiteout_textures_GifWriter_getIssues_count(self.raw.as_ptr());
3060            (0..n)
3061                .map(|i| {
3062                    crate::support::take_string(ffi::whiteout_textures_GifWriter_getIssues_at(
3063                        self.raw.as_ptr(),
3064                        i,
3065                    ))
3066                })
3067                .collect()
3068        }
3069    }
3070}
3071
3072impl Default for GifWriter {
3073    fn default() -> Self {
3074        Self::new()
3075    }
3076}
3077
3078/// Value-ABI mutable span accessors (`bindings/c/whiteout_v.h`).
3079#[doc(hidden)]
3080pub mod tier_a {
3081    #![allow(missing_debug_implementations)]
3082
3083    #[repr(C)]
3084    pub struct Opaque {
3085        _private: [u8; 0],
3086    }
3087
3088    extern "C" {
3089        pub fn whiteout_v_Texture_data_mut(self_: *mut Opaque, out_size: *mut usize) -> *mut u8;
3090        pub fn whiteout_v_Texture_mipData_mut(
3091            self_: *mut Opaque,
3092            mip: u32,
3093            layer: u32,
3094            out_size: *mut usize,
3095        ) -> *mut u8;
3096    }
3097}
3098
3099#[doc(hidden)]
3100pub mod ffi {
3101    #![allow(missing_debug_implementations)]
3102
3103    #[allow(unused_imports)]
3104    use crate::support::{RawBytes, RawCString};
3105
3106    #[repr(C)]
3107    pub struct whiteout_TextureList {
3108        _private: [u8; 0],
3109    }
3110    #[repr(C)]
3111    pub struct whiteout_MipLevel {
3112        _private: [u8; 0],
3113    }
3114    #[repr(C)]
3115    pub struct whiteout_Texture {
3116        _private: [u8; 0],
3117    }
3118    #[repr(C)]
3119    pub struct whiteout_BlpParser {
3120        _private: [u8; 0],
3121    }
3122    #[repr(C)]
3123    pub struct whiteout_BlpWriter {
3124        _private: [u8; 0],
3125    }
3126    #[repr(C)]
3127    pub struct whiteout_PngApngFrameInfo {
3128        _private: [u8; 0],
3129    }
3130    #[repr(C)]
3131    pub struct whiteout_PngParser {
3132        _private: [u8; 0],
3133    }
3134    #[repr(C)]
3135    pub struct whiteout_PngApngFrame {
3136        _private: [u8; 0],
3137    }
3138    #[repr(C)]
3139    pub struct whiteout_PngApngSaveOptions {
3140        _private: [u8; 0],
3141    }
3142    #[repr(C)]
3143    pub struct whiteout_PngWriter {
3144        _private: [u8; 0],
3145    }
3146    #[repr(C)]
3147    pub struct whiteout_JpegParser {
3148        _private: [u8; 0],
3149    }
3150    #[repr(C)]
3151    pub struct whiteout_JpegWriter {
3152        _private: [u8; 0],
3153    }
3154    #[repr(C)]
3155    pub struct whiteout_DdsParser {
3156        _private: [u8; 0],
3157    }
3158    #[repr(C)]
3159    pub struct whiteout_DdsWriter {
3160        _private: [u8; 0],
3161    }
3162    #[repr(C)]
3163    pub struct whiteout_TexParser {
3164        _private: [u8; 0],
3165    }
3166    #[repr(C)]
3167    pub struct whiteout_D2rTextureParser {
3168        _private: [u8; 0],
3169    }
3170    #[repr(C)]
3171    pub struct whiteout_D2rTextureWriter {
3172        _private: [u8; 0],
3173    }
3174    #[repr(C)]
3175    pub struct whiteout_BmpParser {
3176        _private: [u8; 0],
3177    }
3178    #[repr(C)]
3179    pub struct whiteout_BmpWriter {
3180        _private: [u8; 0],
3181    }
3182    #[repr(C)]
3183    pub struct whiteout_TgaParser {
3184        _private: [u8; 0],
3185    }
3186    #[repr(C)]
3187    pub struct whiteout_TgaWriter {
3188        _private: [u8; 0],
3189    }
3190    #[repr(C)]
3191    pub struct whiteout_TiffParser {
3192        _private: [u8; 0],
3193    }
3194    #[repr(C)]
3195    pub struct whiteout_TiffWriter {
3196        _private: [u8; 0],
3197    }
3198    #[repr(C)]
3199    pub struct whiteout_GifSaveOptions {
3200        _private: [u8; 0],
3201    }
3202    #[repr(C)]
3203    pub struct whiteout_GifWriter {
3204        _private: [u8; 0],
3205    }
3206
3207    extern "C" {
3208        pub fn whiteout_textures_TextureList_size(self_: *mut whiteout_TextureList) -> usize;
3209        pub fn whiteout_textures_TextureList_at(
3210            self_: *mut whiteout_TextureList,
3211            index: usize,
3212        ) -> *mut whiteout_Texture;
3213        pub fn whiteout_textures_TextureList_delete(self_: *mut whiteout_TextureList);
3214        // MipLevel
3215        pub fn whiteout_textures_MipLevel_new() -> *mut whiteout_MipLevel;
3216        pub fn whiteout_textures_MipLevel_delete(self_: *mut whiteout_MipLevel);
3217        pub fn whiteout_textures_MipLevel_get_width(self_: *mut whiteout_MipLevel) -> u32;
3218        pub fn whiteout_textures_MipLevel_set_width(self_: *mut whiteout_MipLevel, value: u32);
3219        pub fn whiteout_textures_MipLevel_get_height(self_: *mut whiteout_MipLevel) -> u32;
3220        pub fn whiteout_textures_MipLevel_set_height(self_: *mut whiteout_MipLevel, value: u32);
3221        pub fn whiteout_textures_MipLevel_get_depth(self_: *mut whiteout_MipLevel) -> u32;
3222        pub fn whiteout_textures_MipLevel_set_depth(self_: *mut whiteout_MipLevel, value: u32);
3223        pub fn whiteout_textures_MipLevel_get_offset(self_: *mut whiteout_MipLevel) -> u64;
3224        pub fn whiteout_textures_MipLevel_set_offset(self_: *mut whiteout_MipLevel, value: u64);
3225        pub fn whiteout_textures_MipLevel_get_size(self_: *mut whiteout_MipLevel) -> u64;
3226        pub fn whiteout_textures_MipLevel_set_size(self_: *mut whiteout_MipLevel, value: u64);
3227        // Texture
3228        pub fn whiteout_textures_Texture_new() -> *mut whiteout_Texture;
3229        pub fn whiteout_textures_Texture_delete(self_: *mut whiteout_Texture);
3230        pub fn whiteout_textures_Texture_format(self_: *mut whiteout_Texture, new_fmt: i32);
3231        pub fn whiteout_textures_Texture_format_overload2(self_: *mut whiteout_Texture) -> i32;
3232        pub fn whiteout_textures_Texture_copyAsFormat(
3233            self_: *mut whiteout_Texture,
3234            new_fmt: i32,
3235            pool: *mut core::ffi::c_void,
3236        ) -> *mut whiteout_Texture;
3237        pub fn whiteout_textures_Texture_swapChannels(
3238            self_: *mut whiteout_Texture,
3239            a: i32,
3240            b: i32,
3241        ) -> i32;
3242        pub fn whiteout_textures_Texture_invertChannel(
3243            self_: *mut whiteout_Texture,
3244            ch: i32,
3245        ) -> i32;
3246        pub fn whiteout_textures_Texture_expandNormal(
3247            self_: *mut whiteout_Texture,
3248            x_channel: i32,
3249            y_channel: i32,
3250            z_channel: i32,
3251        ) -> i32;
3252        pub fn whiteout_textures_Texture_fillChannel(
3253            self_: *mut whiteout_Texture,
3254            target: i32,
3255            value: f32,
3256        ) -> i32;
3257        pub fn whiteout_textures_Texture_splitChannels(
3258            self_: *mut whiteout_Texture,
3259            channels: *const i32,
3260            channels_size: usize,
3261        ) -> *mut whiteout_TextureList;
3262        pub fn whiteout_textures_Texture_mergeChannels(
3263            sources: *const *mut whiteout_Texture,
3264            sources_size: usize,
3265            target_channels: *const i32,
3266            target_channels_size: usize,
3267        ) -> *mut whiteout_Texture;
3268        pub fn whiteout_textures_Texture_copyFromNormalToRGBA(
3269            self_: *mut whiteout_Texture,
3270            pool: *mut core::ffi::c_void,
3271        ) -> *mut whiteout_Texture;
3272        pub fn whiteout_textures_Texture_generateMipmaps(
3273            self_: *mut whiteout_Texture,
3274            new_mip_count: u32,
3275            pool: *mut core::ffi::c_void,
3276        ) -> RawCString;
3277        pub fn whiteout_textures_Texture_generateMipmaps_pool(
3278            self_: *mut whiteout_Texture,
3279            pool: *mut core::ffi::c_void,
3280        ) -> RawCString;
3281        pub fn whiteout_textures_Texture_downscale(
3282            self_: *mut whiteout_Texture,
3283            levels: u32,
3284            pool: *mut core::ffi::c_void,
3285        ) -> RawCString;
3286        pub fn whiteout_textures_Texture_create2D(
3287            fmt: i32,
3288            width: u32,
3289            height: u32,
3290            mip_count: u32,
3291        ) -> *mut whiteout_Texture;
3292        pub fn whiteout_textures_Texture_create3D(
3293            fmt: i32,
3294            width: u32,
3295            height: u32,
3296            depth: u32,
3297            mip_count: u32,
3298        ) -> *mut whiteout_Texture;
3299        pub fn whiteout_textures_Texture_createCube(
3300            fmt: i32,
3301            size: u32,
3302            mip_count: u32,
3303        ) -> *mut whiteout_Texture;
3304        pub fn whiteout_textures_Texture_create2DArray(
3305            fmt: i32,
3306            width: u32,
3307            height: u32,
3308            array_size: u32,
3309            mip_count: u32,
3310        ) -> *mut whiteout_Texture;
3311        pub fn whiteout_textures_Texture_createCubeArray(
3312            fmt: i32,
3313            size: u32,
3314            array_size: u32,
3315            mip_count: u32,
3316        ) -> *mut whiteout_Texture;
3317        pub fn whiteout_textures_Texture_type(self_: *mut whiteout_Texture) -> i32;
3318        pub fn whiteout_textures_Texture_kind(self_: *mut whiteout_Texture) -> i32;
3319        pub fn whiteout_textures_Texture_setKind(self_: *mut whiteout_Texture, k: i32);
3320        pub fn whiteout_textures_Texture_channelKind(self_: *mut whiteout_Texture, ch: i32) -> i32;
3321        pub fn whiteout_textures_Texture_setChannelKind(
3322            self_: *mut whiteout_Texture,
3323            ch: i32,
3324            kind: i32,
3325        );
3326        pub fn whiteout_textures_Texture_channelDefault(
3327            self_: *mut whiteout_Texture,
3328            ch: i32,
3329        ) -> f32;
3330        pub fn whiteout_textures_Texture_setChannelDefault(
3331            self_: *mut whiteout_Texture,
3332            ch: i32,
3333            value: f32,
3334        );
3335        pub fn whiteout_textures_Texture_isSrgb(self_: *mut whiteout_Texture) -> i32;
3336        pub fn whiteout_textures_Texture_setSrgb(self_: *mut whiteout_Texture, srgb: i32);
3337        pub fn whiteout_textures_Texture_width(self_: *mut whiteout_Texture) -> u32;
3338        pub fn whiteout_textures_Texture_height(self_: *mut whiteout_Texture) -> u32;
3339        pub fn whiteout_textures_Texture_depth(self_: *mut whiteout_Texture) -> u32;
3340        pub fn whiteout_textures_Texture_layerCount(self_: *mut whiteout_Texture) -> u32;
3341        pub fn whiteout_textures_Texture_arraySize(self_: *mut whiteout_Texture) -> u32;
3342        pub fn whiteout_textures_Texture_mipCount(self_: *mut whiteout_Texture) -> u32;
3343        pub fn whiteout_textures_Texture_mipLevel(
3344            self_: *mut whiteout_Texture,
3345            mip: u32,
3346            layer: u32,
3347        ) -> *mut whiteout_MipLevel;
3348        pub fn whiteout_textures_Texture_dataSize(self_: *mut whiteout_Texture) -> u64;
3349        pub fn whiteout_textures_Texture_data(self_: *mut whiteout_Texture) -> RawBytes;
3350        pub fn whiteout_textures_Texture_mipData(
3351            self_: *mut whiteout_Texture,
3352            mip: u32,
3353            layer: u32,
3354        ) -> RawBytes;
3355        pub fn whiteout_textures_Texture_takeData(self_: *mut whiteout_Texture) -> RawBytes;
3356        pub fn whiteout_textures_Texture_setData(
3357            self_: *mut whiteout_Texture,
3358            new_data: *const u8,
3359            new_data_size: usize,
3360        );
3361        // BlpParser
3362        pub fn whiteout_textures_BlpParser_new() -> *mut whiteout_BlpParser;
3363        pub fn whiteout_textures_BlpParser_delete(self_: *mut whiteout_BlpParser);
3364        pub fn whiteout_textures_BlpParser_parse(
3365            self_: *mut whiteout_BlpParser,
3366            buffer: *const u8,
3367            buffer_size: usize,
3368        ) -> *mut whiteout_Texture;
3369        pub fn whiteout_textures_BlpParser_hasIssues(self_: *mut whiteout_BlpParser) -> i32;
3370        pub fn whiteout_textures_BlpParser_getIssues_count(self_: *mut whiteout_BlpParser)
3371            -> usize;
3372        pub fn whiteout_textures_BlpParser_getIssues_at(
3373            self_: *mut whiteout_BlpParser,
3374            index: usize,
3375        ) -> RawCString;
3376        // BlpWriter
3377        pub fn whiteout_textures_BlpWriter_new() -> *mut whiteout_BlpWriter;
3378        pub fn whiteout_textures_BlpWriter_new_pool(
3379            _0: *mut core::ffi::c_void,
3380        ) -> *mut whiteout_BlpWriter;
3381        pub fn whiteout_textures_BlpWriter_delete(self_: *mut whiteout_BlpWriter);
3382        pub fn whiteout_textures_BlpWriter_write(
3383            self_: *mut whiteout_BlpWriter,
3384            texture: *mut whiteout_Texture,
3385        ) -> RawBytes;
3386        pub fn whiteout_textures_BlpWriter_hasIssues(self_: *mut whiteout_BlpWriter) -> i32;
3387        pub fn whiteout_textures_BlpWriter_getIssues_count(self_: *mut whiteout_BlpWriter)
3388            -> usize;
3389        pub fn whiteout_textures_BlpWriter_getIssues_at(
3390            self_: *mut whiteout_BlpWriter,
3391            index: usize,
3392        ) -> RawCString;
3393        // PngApngFrameInfo
3394        pub fn whiteout_textures_PngApngFrameInfo_new() -> *mut whiteout_PngApngFrameInfo;
3395        pub fn whiteout_textures_PngApngFrameInfo_delete(self_: *mut whiteout_PngApngFrameInfo);
3396        pub fn whiteout_textures_PngApngFrameInfo_get_width(
3397            self_: *mut whiteout_PngApngFrameInfo,
3398        ) -> u32;
3399        pub fn whiteout_textures_PngApngFrameInfo_set_width(
3400            self_: *mut whiteout_PngApngFrameInfo,
3401            value: u32,
3402        );
3403        pub fn whiteout_textures_PngApngFrameInfo_get_height(
3404            self_: *mut whiteout_PngApngFrameInfo,
3405        ) -> u32;
3406        pub fn whiteout_textures_PngApngFrameInfo_set_height(
3407            self_: *mut whiteout_PngApngFrameInfo,
3408            value: u32,
3409        );
3410        pub fn whiteout_textures_PngApngFrameInfo_get_xOffset(
3411            self_: *mut whiteout_PngApngFrameInfo,
3412        ) -> u32;
3413        pub fn whiteout_textures_PngApngFrameInfo_set_xOffset(
3414            self_: *mut whiteout_PngApngFrameInfo,
3415            value: u32,
3416        );
3417        pub fn whiteout_textures_PngApngFrameInfo_get_yOffset(
3418            self_: *mut whiteout_PngApngFrameInfo,
3419        ) -> u32;
3420        pub fn whiteout_textures_PngApngFrameInfo_set_yOffset(
3421            self_: *mut whiteout_PngApngFrameInfo,
3422            value: u32,
3423        );
3424        pub fn whiteout_textures_PngApngFrameInfo_get_delayMs(
3425            self_: *mut whiteout_PngApngFrameInfo,
3426        ) -> u32;
3427        pub fn whiteout_textures_PngApngFrameInfo_set_delayMs(
3428            self_: *mut whiteout_PngApngFrameInfo,
3429            value: u32,
3430        );
3431        pub fn whiteout_textures_PngApngFrameInfo_get_disposeOp(
3432            self_: *mut whiteout_PngApngFrameInfo,
3433        ) -> u32;
3434        pub fn whiteout_textures_PngApngFrameInfo_set_disposeOp(
3435            self_: *mut whiteout_PngApngFrameInfo,
3436            value: u32,
3437        );
3438        pub fn whiteout_textures_PngApngFrameInfo_get_blendOp(
3439            self_: *mut whiteout_PngApngFrameInfo,
3440        ) -> u32;
3441        pub fn whiteout_textures_PngApngFrameInfo_set_blendOp(
3442            self_: *mut whiteout_PngApngFrameInfo,
3443            value: u32,
3444        );
3445        // PngParser
3446        pub fn whiteout_textures_PngParser_new() -> *mut whiteout_PngParser;
3447        pub fn whiteout_textures_PngParser_delete(self_: *mut whiteout_PngParser);
3448        pub fn whiteout_textures_PngParser_parse(
3449            self_: *mut whiteout_PngParser,
3450            buffer: *const u8,
3451            buffer_size: usize,
3452        ) -> *mut whiteout_Texture;
3453        pub fn whiteout_textures_PngParser_hasIssues(self_: *mut whiteout_PngParser) -> i32;
3454        pub fn whiteout_textures_PngParser_getIssues_count(self_: *mut whiteout_PngParser)
3455            -> usize;
3456        pub fn whiteout_textures_PngParser_getIssues_at(
3457            self_: *mut whiteout_PngParser,
3458            index: usize,
3459        ) -> RawCString;
3460        pub fn whiteout_textures_PngParser_isAnimated(self_: *mut whiteout_PngParser) -> i32;
3461        pub fn whiteout_textures_PngParser_frameCount(self_: *mut whiteout_PngParser) -> u32;
3462        pub fn whiteout_textures_PngParser_loopCount(self_: *mut whiteout_PngParser) -> u32;
3463        pub fn whiteout_textures_PngParser_frame(
3464            self_: *mut whiteout_PngParser,
3465            index: u32,
3466        ) -> *mut whiteout_Texture;
3467        pub fn whiteout_textures_PngParser_frameDelayMs(
3468            self_: *mut whiteout_PngParser,
3469            index: u32,
3470        ) -> u32;
3471        pub fn whiteout_textures_PngParser_frameInfo(
3472            self_: *mut whiteout_PngParser,
3473            index: u32,
3474        ) -> *mut whiteout_PngApngFrameInfo;
3475        // PngApngFrame
3476        pub fn whiteout_textures_PngApngFrame_new() -> *mut whiteout_PngApngFrame;
3477        pub fn whiteout_textures_PngApngFrame_delete(self_: *mut whiteout_PngApngFrame);
3478        pub fn whiteout_textures_PngApngFrame_get_image(
3479            self_: *mut whiteout_PngApngFrame,
3480        ) -> *mut whiteout_Texture;
3481        pub fn whiteout_textures_PngApngFrame_set_image(
3482            self_: *mut whiteout_PngApngFrame,
3483            value: *const whiteout_Texture,
3484        );
3485        pub fn whiteout_textures_PngApngFrame_get_delayMs(self_: *mut whiteout_PngApngFrame)
3486            -> u32;
3487        pub fn whiteout_textures_PngApngFrame_set_delayMs(
3488            self_: *mut whiteout_PngApngFrame,
3489            value: u32,
3490        );
3491        // PngApngSaveOptions
3492        pub fn whiteout_textures_PngApngSaveOptions_new() -> *mut whiteout_PngApngSaveOptions;
3493        pub fn whiteout_textures_PngApngSaveOptions_delete(self_: *mut whiteout_PngApngSaveOptions);
3494        pub fn whiteout_textures_PngApngSaveOptions_get_loopCount(
3495            self_: *mut whiteout_PngApngSaveOptions,
3496        ) -> u32;
3497        pub fn whiteout_textures_PngApngSaveOptions_set_loopCount(
3498            self_: *mut whiteout_PngApngSaveOptions,
3499            value: u32,
3500        );
3501        // PngWriter
3502        pub fn whiteout_textures_PngWriter_new() -> *mut whiteout_PngWriter;
3503        pub fn whiteout_textures_PngWriter_delete(self_: *mut whiteout_PngWriter);
3504        pub fn whiteout_textures_PngWriter_write(
3505            self_: *mut whiteout_PngWriter,
3506            texture: *mut whiteout_Texture,
3507        ) -> RawBytes;
3508        pub fn whiteout_textures_PngWriter_writeAnimated(
3509            self_: *mut whiteout_PngWriter,
3510            frames: *const *mut whiteout_PngApngFrame,
3511            frames_size: usize,
3512            opts: *mut whiteout_PngApngSaveOptions,
3513        ) -> RawBytes;
3514        pub fn whiteout_textures_PngWriter_hasIssues(self_: *mut whiteout_PngWriter) -> i32;
3515        pub fn whiteout_textures_PngWriter_getIssues_count(self_: *mut whiteout_PngWriter)
3516            -> usize;
3517        pub fn whiteout_textures_PngWriter_getIssues_at(
3518            self_: *mut whiteout_PngWriter,
3519            index: usize,
3520        ) -> RawCString;
3521        // JpegParser
3522        pub fn whiteout_textures_JpegParser_new() -> *mut whiteout_JpegParser;
3523        pub fn whiteout_textures_JpegParser_new_pool(
3524            _0: *mut core::ffi::c_void,
3525        ) -> *mut whiteout_JpegParser;
3526        pub fn whiteout_textures_JpegParser_delete(self_: *mut whiteout_JpegParser);
3527        pub fn whiteout_textures_JpegParser_parse(
3528            self_: *mut whiteout_JpegParser,
3529            buffer: *const u8,
3530            buffer_size: usize,
3531        ) -> *mut whiteout_Texture;
3532        pub fn whiteout_textures_JpegParser_hasIssues(self_: *mut whiteout_JpegParser) -> i32;
3533        pub fn whiteout_textures_JpegParser_getIssues_count(
3534            self_: *mut whiteout_JpegParser,
3535        ) -> usize;
3536        pub fn whiteout_textures_JpegParser_getIssues_at(
3537            self_: *mut whiteout_JpegParser,
3538            index: usize,
3539        ) -> RawCString;
3540        // JpegWriter
3541        pub fn whiteout_textures_JpegWriter_new() -> *mut whiteout_JpegWriter;
3542        pub fn whiteout_textures_JpegWriter_new_quality_pool_progressive(
3543            _0: *mut core::ffi::c_void,
3544            _1: *mut core::ffi::c_void,
3545            _2: *mut core::ffi::c_void,
3546        ) -> *mut whiteout_JpegWriter;
3547        pub fn whiteout_textures_JpegWriter_delete(self_: *mut whiteout_JpegWriter);
3548        pub fn whiteout_textures_JpegWriter_write(
3549            self_: *mut whiteout_JpegWriter,
3550            texture: *mut whiteout_Texture,
3551        ) -> RawBytes;
3552        pub fn whiteout_textures_JpegWriter_hasIssues(self_: *mut whiteout_JpegWriter) -> i32;
3553        pub fn whiteout_textures_JpegWriter_getIssues_count(
3554            self_: *mut whiteout_JpegWriter,
3555        ) -> usize;
3556        pub fn whiteout_textures_JpegWriter_getIssues_at(
3557            self_: *mut whiteout_JpegWriter,
3558            index: usize,
3559        ) -> RawCString;
3560        // DdsParser
3561        pub fn whiteout_textures_DdsParser_new() -> *mut whiteout_DdsParser;
3562        pub fn whiteout_textures_DdsParser_delete(self_: *mut whiteout_DdsParser);
3563        pub fn whiteout_textures_DdsParser_parse(
3564            self_: *mut whiteout_DdsParser,
3565            buffer: *const u8,
3566            buffer_size: usize,
3567        ) -> *mut whiteout_Texture;
3568        pub fn whiteout_textures_DdsParser_hasIssues(self_: *mut whiteout_DdsParser) -> i32;
3569        pub fn whiteout_textures_DdsParser_getIssues_count(self_: *mut whiteout_DdsParser)
3570            -> usize;
3571        pub fn whiteout_textures_DdsParser_getIssues_at(
3572            self_: *mut whiteout_DdsParser,
3573            index: usize,
3574        ) -> RawCString;
3575        // DdsWriter
3576        pub fn whiteout_textures_DdsWriter_new() -> *mut whiteout_DdsWriter;
3577        pub fn whiteout_textures_DdsWriter_delete(self_: *mut whiteout_DdsWriter);
3578        pub fn whiteout_textures_DdsWriter_write(
3579            self_: *mut whiteout_DdsWriter,
3580            texture: *mut whiteout_Texture,
3581        ) -> RawBytes;
3582        pub fn whiteout_textures_DdsWriter_hasIssues(self_: *mut whiteout_DdsWriter) -> i32;
3583        pub fn whiteout_textures_DdsWriter_getIssues_count(self_: *mut whiteout_DdsWriter)
3584            -> usize;
3585        pub fn whiteout_textures_DdsWriter_getIssues_at(
3586            self_: *mut whiteout_DdsWriter,
3587            index: usize,
3588        ) -> RawCString;
3589        // TexParser
3590        pub fn whiteout_textures_TexParser_new() -> *mut whiteout_TexParser;
3591        pub fn whiteout_textures_TexParser_delete(self_: *mut whiteout_TexParser);
3592        pub fn whiteout_textures_TexParser_parse(
3593            self_: *mut whiteout_TexParser,
3594            file_path: *const core::ffi::c_char,
3595        ) -> *mut whiteout_Texture;
3596        pub fn whiteout_textures_TexParser_parse_buffer(
3597            self_: *mut whiteout_TexParser,
3598            buffer: *const u8,
3599            buffer_size: usize,
3600        ) -> *mut whiteout_Texture;
3601        pub fn whiteout_textures_TexParser_parse_texFilePath_payloadFilePath(
3602            self_: *mut whiteout_TexParser,
3603            tex_file_path: *const core::ffi::c_char,
3604            payload_file_path: *const core::ffi::c_char,
3605        ) -> *mut whiteout_Texture;
3606        pub fn whiteout_textures_TexParser_parse_texData_payloadData(
3607            self_: *mut whiteout_TexParser,
3608            tex_data: *const u8,
3609            tex_data_size: usize,
3610            payload_data: *const u8,
3611            payload_data_size: usize,
3612        ) -> *mut whiteout_Texture;
3613        pub fn whiteout_textures_TexParser_parse_texFilePath_hiResPayloadFilePath_lowResPayloadFilePath(
3614            self_: *mut whiteout_TexParser,
3615            tex_file_path: *const core::ffi::c_char,
3616            hi_res_payload_file_path: *const core::ffi::c_char,
3617            low_res_payload_file_path: *const core::ffi::c_char,
3618        ) -> *mut whiteout_Texture;
3619        pub fn whiteout_textures_TexParser_hasIssues(self_: *mut whiteout_TexParser) -> i32;
3620        pub fn whiteout_textures_TexParser_getIssues_count(self_: *mut whiteout_TexParser)
3621            -> usize;
3622        pub fn whiteout_textures_TexParser_getIssues_at(
3623            self_: *mut whiteout_TexParser,
3624            index: usize,
3625        ) -> RawCString;
3626        // D2rTextureParser
3627        pub fn whiteout_textures_D2rTextureParser_new() -> *mut whiteout_D2rTextureParser;
3628        pub fn whiteout_textures_D2rTextureParser_delete(self_: *mut whiteout_D2rTextureParser);
3629        pub fn whiteout_textures_D2rTextureParser_parse(
3630            self_: *mut whiteout_D2rTextureParser,
3631            buffer: *const u8,
3632            buffer_size: usize,
3633        ) -> *mut whiteout_Texture;
3634        pub fn whiteout_textures_D2rTextureParser_detect(
3635            self_: *mut whiteout_D2rTextureParser,
3636            buffer: *const u8,
3637            buffer_size: usize,
3638        ) -> i32;
3639        pub fn whiteout_textures_D2rTextureParser_hasIssues(
3640            self_: *mut whiteout_D2rTextureParser,
3641        ) -> i32;
3642        pub fn whiteout_textures_D2rTextureParser_getIssues_count(
3643            self_: *mut whiteout_D2rTextureParser,
3644        ) -> usize;
3645        pub fn whiteout_textures_D2rTextureParser_getIssues_at(
3646            self_: *mut whiteout_D2rTextureParser,
3647            index: usize,
3648        ) -> RawCString;
3649        // D2rTextureWriter
3650        pub fn whiteout_textures_D2rTextureWriter_new() -> *mut whiteout_D2rTextureWriter;
3651        pub fn whiteout_textures_D2rTextureWriter_delete(self_: *mut whiteout_D2rTextureWriter);
3652        pub fn whiteout_textures_D2rTextureWriter_write(
3653            self_: *mut whiteout_D2rTextureWriter,
3654            texture: *mut whiteout_Texture,
3655        ) -> RawBytes;
3656        pub fn whiteout_textures_D2rTextureWriter_hasIssues(
3657            self_: *mut whiteout_D2rTextureWriter,
3658        ) -> i32;
3659        pub fn whiteout_textures_D2rTextureWriter_getIssues_count(
3660            self_: *mut whiteout_D2rTextureWriter,
3661        ) -> usize;
3662        pub fn whiteout_textures_D2rTextureWriter_getIssues_at(
3663            self_: *mut whiteout_D2rTextureWriter,
3664            index: usize,
3665        ) -> RawCString;
3666        // BmpParser
3667        pub fn whiteout_textures_BmpParser_new() -> *mut whiteout_BmpParser;
3668        pub fn whiteout_textures_BmpParser_delete(self_: *mut whiteout_BmpParser);
3669        pub fn whiteout_textures_BmpParser_parse(
3670            self_: *mut whiteout_BmpParser,
3671            buffer: *const u8,
3672            buffer_size: usize,
3673        ) -> *mut whiteout_Texture;
3674        pub fn whiteout_textures_BmpParser_hasIssues(self_: *mut whiteout_BmpParser) -> i32;
3675        pub fn whiteout_textures_BmpParser_getIssues_count(self_: *mut whiteout_BmpParser)
3676            -> usize;
3677        pub fn whiteout_textures_BmpParser_getIssues_at(
3678            self_: *mut whiteout_BmpParser,
3679            index: usize,
3680        ) -> RawCString;
3681        // BmpWriter
3682        pub fn whiteout_textures_BmpWriter_new() -> *mut whiteout_BmpWriter;
3683        pub fn whiteout_textures_BmpWriter_delete(self_: *mut whiteout_BmpWriter);
3684        pub fn whiteout_textures_BmpWriter_write(
3685            self_: *mut whiteout_BmpWriter,
3686            texture: *mut whiteout_Texture,
3687        ) -> RawBytes;
3688        pub fn whiteout_textures_BmpWriter_hasIssues(self_: *mut whiteout_BmpWriter) -> i32;
3689        pub fn whiteout_textures_BmpWriter_getIssues_count(self_: *mut whiteout_BmpWriter)
3690            -> usize;
3691        pub fn whiteout_textures_BmpWriter_getIssues_at(
3692            self_: *mut whiteout_BmpWriter,
3693            index: usize,
3694        ) -> RawCString;
3695        // TgaParser
3696        pub fn whiteout_textures_TgaParser_new() -> *mut whiteout_TgaParser;
3697        pub fn whiteout_textures_TgaParser_delete(self_: *mut whiteout_TgaParser);
3698        pub fn whiteout_textures_TgaParser_parse(
3699            self_: *mut whiteout_TgaParser,
3700            buffer: *const u8,
3701            buffer_size: usize,
3702        ) -> *mut whiteout_Texture;
3703        pub fn whiteout_textures_TgaParser_hasIssues(self_: *mut whiteout_TgaParser) -> i32;
3704        pub fn whiteout_textures_TgaParser_getIssues_count(self_: *mut whiteout_TgaParser)
3705            -> usize;
3706        pub fn whiteout_textures_TgaParser_getIssues_at(
3707            self_: *mut whiteout_TgaParser,
3708            index: usize,
3709        ) -> RawCString;
3710        // TgaWriter
3711        pub fn whiteout_textures_TgaWriter_new() -> *mut whiteout_TgaWriter;
3712        pub fn whiteout_textures_TgaWriter_delete(self_: *mut whiteout_TgaWriter);
3713        pub fn whiteout_textures_TgaWriter_write(
3714            self_: *mut whiteout_TgaWriter,
3715            texture: *mut whiteout_Texture,
3716        ) -> RawBytes;
3717        pub fn whiteout_textures_TgaWriter_hasIssues(self_: *mut whiteout_TgaWriter) -> i32;
3718        pub fn whiteout_textures_TgaWriter_getIssues_count(self_: *mut whiteout_TgaWriter)
3719            -> usize;
3720        pub fn whiteout_textures_TgaWriter_getIssues_at(
3721            self_: *mut whiteout_TgaWriter,
3722            index: usize,
3723        ) -> RawCString;
3724        // TiffParser
3725        pub fn whiteout_textures_TiffParser_new() -> *mut whiteout_TiffParser;
3726        pub fn whiteout_textures_TiffParser_delete(self_: *mut whiteout_TiffParser);
3727        pub fn whiteout_textures_TiffParser_parse(
3728            self_: *mut whiteout_TiffParser,
3729            buffer: *const u8,
3730            buffer_size: usize,
3731        ) -> *mut whiteout_Texture;
3732        pub fn whiteout_textures_TiffParser_hasIssues(self_: *mut whiteout_TiffParser) -> i32;
3733        pub fn whiteout_textures_TiffParser_getIssues_count(
3734            self_: *mut whiteout_TiffParser,
3735        ) -> usize;
3736        pub fn whiteout_textures_TiffParser_getIssues_at(
3737            self_: *mut whiteout_TiffParser,
3738            index: usize,
3739        ) -> RawCString;
3740        // TiffWriter
3741        pub fn whiteout_textures_TiffWriter_new() -> *mut whiteout_TiffWriter;
3742        pub fn whiteout_textures_TiffWriter_delete(self_: *mut whiteout_TiffWriter);
3743        pub fn whiteout_textures_TiffWriter_write(
3744            self_: *mut whiteout_TiffWriter,
3745            texture: *mut whiteout_Texture,
3746        ) -> RawBytes;
3747        pub fn whiteout_textures_TiffWriter_hasIssues(self_: *mut whiteout_TiffWriter) -> i32;
3748        pub fn whiteout_textures_TiffWriter_getIssues_count(
3749            self_: *mut whiteout_TiffWriter,
3750        ) -> usize;
3751        pub fn whiteout_textures_TiffWriter_getIssues_at(
3752            self_: *mut whiteout_TiffWriter,
3753            index: usize,
3754        ) -> RawCString;
3755        // GifSaveOptions
3756        pub fn whiteout_textures_GifSaveOptions_new() -> *mut whiteout_GifSaveOptions;
3757        pub fn whiteout_textures_GifSaveOptions_delete(self_: *mut whiteout_GifSaveOptions);
3758        pub fn whiteout_textures_GifSaveOptions_get_delayCs(
3759            self_: *mut whiteout_GifSaveOptions,
3760        ) -> u16;
3761        pub fn whiteout_textures_GifSaveOptions_set_delayCs(
3762            self_: *mut whiteout_GifSaveOptions,
3763            value: u16,
3764        );
3765        pub fn whiteout_textures_GifSaveOptions_get_loopCount(
3766            self_: *mut whiteout_GifSaveOptions,
3767        ) -> u16;
3768        pub fn whiteout_textures_GifSaveOptions_set_loopCount(
3769            self_: *mut whiteout_GifSaveOptions,
3770            value: u16,
3771        );
3772        pub fn whiteout_textures_GifSaveOptions_get_dither(
3773            self_: *mut whiteout_GifSaveOptions,
3774        ) -> i32;
3775        pub fn whiteout_textures_GifSaveOptions_set_dither(
3776            self_: *mut whiteout_GifSaveOptions,
3777            value: i32,
3778        );
3779        pub fn whiteout_textures_GifSaveOptions_get_ditherStrength(
3780            self_: *mut whiteout_GifSaveOptions,
3781        ) -> f32;
3782        pub fn whiteout_textures_GifSaveOptions_set_ditherStrength(
3783            self_: *mut whiteout_GifSaveOptions,
3784            value: f32,
3785        );
3786        pub fn whiteout_textures_GifSaveOptions_get_transparent(
3787            self_: *mut whiteout_GifSaveOptions,
3788        ) -> i32;
3789        pub fn whiteout_textures_GifSaveOptions_set_transparent(
3790            self_: *mut whiteout_GifSaveOptions,
3791            value: i32,
3792        );
3793        // GifWriter
3794        pub fn whiteout_textures_GifWriter_new() -> *mut whiteout_GifWriter;
3795        pub fn whiteout_textures_GifWriter_new_pool(
3796            _0: *mut core::ffi::c_void,
3797        ) -> *mut whiteout_GifWriter;
3798        pub fn whiteout_textures_GifWriter_delete(self_: *mut whiteout_GifWriter);
3799        pub fn whiteout_textures_GifWriter_write(
3800            self_: *mut whiteout_GifWriter,
3801            file_path: *const core::ffi::c_char,
3802            frames: *const *mut whiteout_Texture,
3803            frames_size: usize,
3804        );
3805        pub fn whiteout_textures_GifWriter_write_frames(
3806            self_: *mut whiteout_GifWriter,
3807            frames: *const *mut whiteout_Texture,
3808            frames_size: usize,
3809        ) -> RawBytes;
3810        pub fn whiteout_textures_GifWriter_write_filePath_frames_opts(
3811            self_: *mut whiteout_GifWriter,
3812            file_path: *const core::ffi::c_char,
3813            frames: *const *mut whiteout_Texture,
3814            frames_size: usize,
3815            opts: *mut whiteout_GifSaveOptions,
3816        );
3817        pub fn whiteout_textures_GifWriter_write_frames_opts(
3818            self_: *mut whiteout_GifWriter,
3819            frames: *const *mut whiteout_Texture,
3820            frames_size: usize,
3821            opts: *mut whiteout_GifSaveOptions,
3822        ) -> RawBytes;
3823        pub fn whiteout_textures_GifWriter_hasIssues(self_: *mut whiteout_GifWriter) -> i32;
3824        pub fn whiteout_textures_GifWriter_getIssues_count(self_: *mut whiteout_GifWriter)
3825            -> usize;
3826        pub fn whiteout_textures_GifWriter_getIssues_at(
3827            self_: *mut whiteout_GifWriter,
3828            index: usize,
3829        ) -> RawCString;
3830    }
3831}