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