1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
//!
//! Safe wrapper for wxBitmap.
use std::marker::PhantomData;
use std::os::raw::{c_int, c_uchar};
use std::rc::Rc;
use wxdragon_sys as ffi;
/// Represents a platform-dependent bitmap image.
#[derive(Debug)] // Keep Debug if useful, or remove if pointer isn't meaningful for debug
pub struct Bitmap {
ptr: *mut ffi::wxd_Bitmap_t,
owned: bool, // Tracks whether Rust owns this bitmap and should destroy it
// Prevent Send/Sync: wxWidgets objects are not thread-safe and must stay on UI thread.
_nosend_nosync: PhantomData<Rc<()>>,
}
impl Bitmap {
/// Using wxNullBitmap to represent an invalid/empty bitmap.
///
/// # Example
/// ```rust
/// # use wxdragon::prelude::*;
/// // Get an empty bitmap (non-owning wrapper; Drop will not free it)
/// let empty = Bitmap::null_bitmap();
/// assert!(!empty.is_ok());
/// ```
/// Returns a non-owning wrapper around wxNullBitmap.
/// This value will not free the underlying object on Drop.
pub fn null_bitmap() -> Self {
unsafe { Bitmap::from(ffi::wxd_Bitmap_GetNull()) }
}
/// Checks if this bitmap is wxNullBitmap.
pub fn is_null_bitmap(&self) -> bool {
std::ptr::eq(self.ptr, unsafe { ffi::wxd_Bitmap_GetNull() })
}
/// Creates a new empty bitmap with the specified width and height.
pub fn new(width: i32, height: i32) -> Option<Self> {
if width <= 0 || height <= 0 {
return None;
}
// Create RGBA data (4 bytes per pixel)
let pixel_count = (width * height * 4) as usize;
let data = vec![0; pixel_count]; // All zeros for a fully transparent bitmap
Self::from_rgba(&data, width as u32, height as u32)
}
/// Creates a new bitmap from raw RGBA pixel data.
///
/// # Arguments
/// * `data` - A slice containing the raw RGBA pixel data (4 bytes per pixel).
/// * `width` - The width of the image in pixels.
/// * `height` - The height of the image in pixels.
///
/// Returns `None` if the bitmap creation fails (e.g., invalid dimensions, memory allocation error).
pub fn from_rgba(data: &[u8], width: u32, height: u32) -> Option<Self> {
let expected_len = (width * height * 4) as usize;
if data.len() != expected_len || width == 0 || height == 0 {
log::error!(
"Bitmap::from_rgba: Invalid data length or dimensions. Expected {}, got {}, w={}, h={}",
expected_len,
data.len(),
width,
height
);
return None;
}
let data = data.as_ptr() as *const c_uchar;
let ptr = unsafe { ffi::wxd_Bitmap_CreateFromRGBA(data, width as c_int, height as c_int) };
if ptr.is_null() {
return None;
}
Some(Bitmap {
ptr,
owned: true,
_nosend_nosync: PhantomData,
}) // We own bitmaps created this way
}
/// Returns `true` if this bitmap is owned by Rust and will be automatically destroyed when dropped.
///
/// Returns `false` if the bitmap is managed elsewhere (e.g., by wxWidgets or another owner)
/// and should not be destroyed by Rust code. This is important for avoiding double-free or
/// use-after-free bugs when working with bitmaps obtained from external sources.
///
/// Typically, bitmaps created via Rust constructors (such as [`Bitmap::from_rgba`]) are owned,
/// while those wrapping external pointers are not.
pub fn is_owned(&self) -> bool {
self.owned
}
/// Returns the width of the bitmap in pixels.
pub fn get_width(&self) -> i32 {
if self.as_const_ptr().is_null() {
return 0;
}
unsafe { ffi::wxd_Bitmap_GetWidth(self.as_const_ptr()) as i32 }
}
/// Returns the height of the bitmap in pixels.
pub fn get_height(&self) -> i32 {
if self.as_const_ptr().is_null() {
return 0;
}
unsafe { ffi::wxd_Bitmap_GetHeight(self.as_const_ptr()) as i32 }
}
/// Checks if the bitmap is valid.
pub fn is_ok(&self) -> bool {
if self.as_const_ptr().is_null() {
return false;
}
unsafe { ffi::wxd_Bitmap_IsOk(self.as_const_ptr()) }
}
/// Extracts the raw RGBA pixel data from the bitmap.
///
/// Returns a vector containing RGBA pixel data where each pixel is represented
/// by 4 consecutive bytes: R, G, B, A. The data is ordered row by row from
/// top to bottom, left to right within each row.
///
/// # Returns
/// - `Some(Vec<u8>)` containing RGBA data if extraction succeeds
/// - `None` if the bitmap is invalid or extraction fails
///
/// # Example
/// ```rust
/// # use wxdragon::prelude::*;
/// # fn example() -> Option<()> {
/// let bitmap = Bitmap::new(100, 100)?;
/// let rgba_data = bitmap.get_rgba_data()?;
///
/// // Each pixel takes 4 bytes (RGBA)
/// assert_eq!(rgba_data.len(), 100 * 100 * 4);
///
/// // Use with image crate:
/// // let img = image::RgbaImage::from_raw(100, 100, rgba_data)?;
/// # Some(())
/// # }
/// ```
pub fn get_rgba_data(&self) -> Option<Vec<u8>> {
if self.as_const_ptr().is_null() || !self.is_ok() {
return None;
}
let (mut width, mut height) = (0_usize, 0_usize);
let data_ptr = unsafe { ffi::wxd_Bitmap_GetRGBAData(self.as_const_ptr(), &mut width, &mut height) };
if data_ptr.is_null() {
return None;
}
let data_len = width * height * 4; // 4 bytes per pixel (RGBA)
// Copy the data from C++ allocated memory to Rust Vec
let rgba_data = unsafe { std::slice::from_raw_parts(data_ptr, data_len).to_vec() };
// Free the C++ allocated memory
unsafe { ffi::wxd_Bitmap_FreeRGBAData(data_ptr) };
Some(rgba_data)
}
/// Returns a const raw pointer to the underlying wxd_Bitmap_t.
///
/// Ownership notes:
/// - This does not transfer ownership; the pointer is only valid while this `Bitmap` (or another owner) keeps it alive.
/// - Do not destroy this pointer yourself; the owner (or a caller who used `into_raw_mut`) is responsible.
pub fn as_const_ptr(&self) -> *const ffi::wxd_Bitmap_t {
self.ptr as *const _
}
/// Returns a mutable raw pointer to the underlying wxd_Bitmap_t.
///
/// Use with care; prefer safe methods when possible.
///
/// Ownership notes:
/// - This does not transfer ownership. If you mutate through this pointer, ensure exclusive access and maintain invariants.
/// - Do not destroy the returned pointer here; use `into_raw_mut` if you must assume ownership and destroy manually.
pub fn as_mut_ptr(&mut self) -> *mut ffi::wxd_Bitmap_t {
self.ptr
}
/// Consumes self and returns a raw mutable pointer, transferring ownership to the caller.
///
/// After calling this, you must NOT use the original `Bitmap` again.
///
/// Caller responsibilities:
/// - You now own the pointer and must destroy it exactly once with `wxd_Bitmap_Destroy`.
/// - Ensure the pointer is not used after destruction.
pub fn into_raw_mut(self) -> *mut ffi::wxd_Bitmap_t {
self.try_into()
.expect("into_raw_mut can only be called on owning Bitmap instances")
}
/// Consumes a borrowed (non-owning) wrapper and returns a raw const pointer without taking ownership.
///
/// Panics if called on an owning wrapper to avoid leaking the owned resource.
///
/// Caller responsibilities:
/// - This does NOT transfer ownership; do not destroy the returned pointer.
/// - The pointer remains valid only while the original owner keeps it alive.
pub fn into_raw_const(self) -> *const ffi::wxd_Bitmap_t {
self.try_into()
.expect("into_raw_const must only be used on non-owning (borrowed) wrappers")
}
}
impl Clone for Bitmap {
fn clone(&self) -> Self {
let cloned_ptr = unsafe { ffi::wxd_Bitmap_Clone(self.ptr) };
if cloned_ptr.is_null() {
panic!(
"Failed to clone wxBitmap: wxd_Bitmap_Clone returned null. Original: {:?}",
self.ptr
);
}
// A cloned bitmap is always owned by Rust
Bitmap {
ptr: cloned_ptr,
owned: true,
_nosend_nosync: PhantomData,
}
}
}
impl From<*const ffi::wxd_Bitmap_t> for Bitmap {
/// Creates a non-owning Bitmap wrapper from a raw pointer.
/// The pointer must be valid for the lifetime of the Bitmap object.
fn from(ptr: *const ffi::wxd_Bitmap_t) -> Self {
assert!(!ptr.is_null(), "invalid null pointer passed to Bitmap::from");
Bitmap {
ptr: ptr as *mut _,
owned: false,
_nosend_nosync: PhantomData,
}
}
}
impl From<*mut ffi::wxd_Bitmap_t> for Bitmap {
/// Creates an owning Bitmap wrapper from a raw pointer.
/// The pointer must be valid and Rust will take ownership of it.
fn from(ptr: *mut ffi::wxd_Bitmap_t) -> Self {
assert!(!ptr.is_null(), "invalid null pointer passed to Bitmap::from");
Bitmap {
ptr,
owned: true,
_nosend_nosync: PhantomData,
}
}
}
impl TryFrom<Bitmap> for *const ffi::wxd_Bitmap_t {
type Error = std::io::Error;
fn try_from(bitmap: Bitmap) -> Result<Self, Self::Error> {
if bitmap.owned {
Err(std::io::Error::other(
"Cannot convert owned Bitmap to raw pointer without transferring ownership",
))
} else {
let ptr = bitmap.ptr as *const _;
std::mem::forget(bitmap);
Ok(ptr)
}
}
}
impl TryFrom<Bitmap> for *mut ffi::wxd_Bitmap_t {
type Error = std::io::Error;
fn try_from(bitmap: Bitmap) -> Result<Self, Self::Error> {
if bitmap.owned {
let ptr = bitmap.ptr;
std::mem::forget(bitmap); // Prevent Drop from freeing it
Ok(ptr)
} else {
Err(std::io::Error::other("Cannot convert unowned Bitmap to mutable raw pointer"))
}
}
}
impl Drop for Bitmap {
/// Destroys the associated C++ wxBitmap object if Rust owns the bitmap.
fn drop(&mut self) {
if !self.ptr.is_null() && self.owned {
unsafe { ffi::wxd_Bitmap_Destroy(self.as_mut_ptr()) };
self.ptr = std::ptr::null_mut();
}
}
}
#[cfg(test)]
mod tests {
use super::Bitmap;
use wxdragon_sys as ffi;
#[test]
fn bitmap_from_rgba_clone_into_raw_then_destroy() {
let (w, h) = (2u32, 2u32);
let rgba = vec![255u8; (w * h * 4) as usize];
// Create an owned bitmap from RGBA data
let bmp = Bitmap::from_rgba(&rgba, w, h).expect("failed to create bitmap from rgba");
assert!(bmp.is_owned(), "bitmap from_rgba should be owned");
assert!(bmp.is_ok(), "created bitmap should be ok");
// Clone the bitmap; clone is also owned
let clone = bmp.clone();
assert!(clone.is_owned(), "cloned bitmap should be owned");
assert!(clone.is_ok(), "cloned bitmap should be ok");
// Transfer ownership out of the clone and destroy manually
let raw = clone.into_raw_mut();
assert!(!raw.is_null(), "raw pointer from into_raw_mut should be non-null");
unsafe { ffi::wxd_Bitmap_Destroy(raw) };
// When this test ends, `bmp` will be dropped and should destroy its own handle.
// If ownership transfer or Drop were incorrect, this test would double-free or leak.
}
}