Skip to main content

whiteout/
textures.rs

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