Skip to main content

maa_framework/
buffer.rs

1//! Safe buffer types for FFI data exchange.
2//!
3//! These buffers provide RAII-based memory management for data passed between
4//! Rust and the C API. All buffers are automatically deallocated when dropped.
5//!
6//! # Buffer Types
7//!
8//! - [`MaaStringBuffer`] - UTF-8 string data
9//! - [`MaaImageBuffer`] - Image data (supports PNG/JPEG encoding)
10//! - [`MaaStringListBuffer`] - List of strings
11//! - [`MaaImageListBuffer`] - List of images
12//! - [`MaaRectBuffer`] - Rectangle coordinates
13
14use std::ffi::CString;
15
16use std::ptr::NonNull;
17use std::slice;
18use std::str;
19
20use crate::{sys, MaaError, MaaResult};
21
22/// Macro to implement common lifecycle methods for buffers.
23macro_rules! impl_buffer_lifecycle {
24    ($name:ident, $sys_type:ty, $create_fn:path, $destroy_fn:path) => {
25        unsafe impl Send for $name {}
26
27        impl $name {
28            /// Create a new buffer.
29            pub fn new() -> MaaResult<Self> {
30                let handle = unsafe { $create_fn() };
31                NonNull::new(handle)
32                    .map(|ptr| Self {
33                        handle: ptr,
34                        own: true,
35                    })
36                    .ok_or(MaaError::NullPointer)
37            }
38
39            /// Create from an existing handle.
40            ///
41            /// # Safety
42            ///
43            /// This function assumes the handle is valid. The returned buffer will
44            /// NOT take ownership of the handle (it won't be destroyed on drop).
45            /// Use this when you are borrowing a handle from the C API.
46            pub unsafe fn from_raw(handle: *mut $sys_type) -> Self {
47                Self {
48                    handle: NonNull::new_unchecked(handle),
49                    own: false,
50                }
51            }
52
53            /// Create from an existing handle safely (checks for null).
54            /// Returns `None` if handle is null.
55            pub fn from_handle(handle: *mut $sys_type) -> Option<Self> {
56                NonNull::new(handle).map(|ptr| Self {
57                    handle: ptr,
58                    own: false,
59                })
60            }
61
62            /// Get the underlying raw handle.
63            #[inline]
64            pub fn as_ptr(&self) -> *mut $sys_type {
65                self.handle.as_ptr()
66            }
67
68            /// Get the underlying raw handle (alias for `as_ptr`).
69            #[inline]
70            pub fn raw(&self) -> *mut $sys_type {
71                self.handle.as_ptr()
72            }
73        }
74
75        impl Drop for $name {
76            fn drop(&mut self) {
77                if self.own {
78                    unsafe { $destroy_fn(self.handle.as_ptr()) }
79                }
80            }
81        }
82
83        impl Default for $name {
84            fn default() -> Self {
85                Self::new().expect(concat!("Failed to create ", stringify!($name)))
86            }
87        }
88
89        impl std::fmt::Debug for $name {
90            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91                f.debug_struct(stringify!($name))
92                    .field("handle", &self.handle)
93                    .field("own", &self.own)
94                    .finish()
95            }
96        }
97    };
98}
99
100/// A string buffer for UTF-8 text data.
101///
102/// Used for passing strings between Rust and the C API.
103/// Automatically freed when dropped.
104pub struct MaaStringBuffer {
105    handle: NonNull<sys::MaaStringBuffer>,
106    own: bool,
107}
108
109impl_buffer_lifecycle!(
110    MaaStringBuffer,
111    sys::MaaStringBuffer,
112    sys::MaaStringBufferCreate,
113    sys::MaaStringBufferDestroy
114);
115
116impl MaaStringBuffer {
117    /// Set the buffer content from a string.
118    pub fn set<S: AsRef<str>>(&mut self, content: S) -> MaaResult<()> {
119        let s = content.as_ref();
120        let c_str = CString::new(s)?;
121        let ret = unsafe { sys::MaaStringBufferSet(self.handle.as_ptr(), c_str.as_ptr()) };
122        crate::common::check_bool(ret)
123    }
124
125    /// Set the buffer content from raw bytes (zero-copy if possible).
126    ///
127    /// This uses `MaaStringBufferSetEx` which accepts a length, allowing
128    /// passing bytes without explicit null termination if the API supports it.
129    pub fn set_ex<B: AsRef<[u8]>>(&mut self, content: B) -> MaaResult<()> {
130        let bytes = content.as_ref();
131        let ret = unsafe {
132            sys::MaaStringBufferSetEx(
133                self.handle.as_ptr(),
134                bytes.as_ptr() as *const _,
135                bytes.len() as u64,
136            )
137        };
138        crate::common::check_bool(ret)
139    }
140
141    /// Get the buffer content as a string slice.
142    pub fn as_str(&self) -> &str {
143        let bytes = self.as_bytes();
144        if bytes.is_empty() {
145            ""
146        } else {
147            str::from_utf8(bytes).unwrap_or("")
148        }
149    }
150
151    /// Get the buffer content as a byte slice.
152    pub fn as_bytes(&self) -> &[u8] {
153        unsafe {
154            let ptr = sys::MaaStringBufferGet(self.handle.as_ptr());
155            let size = sys::MaaStringBufferSize(self.handle.as_ptr()) as usize;
156
157            if ptr.is_null() || size == 0 {
158                &[]
159            } else {
160                slice::from_raw_parts(ptr as *const u8, size)
161            }
162        }
163    }
164
165    /// Check if the buffer is empty.
166    pub fn is_empty(&self) -> bool {
167        unsafe { sys::MaaStringBufferIsEmpty(self.handle.as_ptr()) != 0 }
168    }
169
170    /// Clear the buffer.
171    pub fn clear(&mut self) -> MaaResult<()> {
172        let ret = unsafe { sys::MaaStringBufferClear(self.handle.as_ptr()) };
173        crate::common::check_bool(ret)
174    }
175
176    /// Get the size of the buffer content (excluding null terminator).
177    pub fn len(&self) -> usize {
178        unsafe { sys::MaaStringBufferSize(self.handle.as_ptr()) as usize }
179    }
180}
181
182impl std::fmt::Display for MaaStringBuffer {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        f.write_str(self.as_str())
185    }
186}
187
188impl From<&str> for MaaStringBuffer {
189    fn from(s: &str) -> Self {
190        let mut buf = Self::new().expect("Failed to create MaaStringBuffer");
191        buf.set(s).expect("Failed to set string buffer content");
192        buf
193    }
194}
195
196impl AsRef<str> for MaaStringBuffer {
197    fn as_ref(&self) -> &str {
198        self.as_str()
199    }
200}
201
202/// Image buffer for storing and manipulating image data.
203///
204/// Supports raw BGR data and encoded formats (PNG/JPEG).
205/// Compatible with OpenCV image types.
206pub struct MaaImageBuffer {
207    handle: NonNull<sys::MaaImageBuffer>,
208    own: bool,
209}
210
211impl_buffer_lifecycle!(
212    MaaImageBuffer,
213    sys::MaaImageBuffer,
214    sys::MaaImageBufferCreate,
215    sys::MaaImageBufferDestroy
216);
217
218impl MaaImageBuffer {
219    /// Check if the buffer is empty (contains no image data).
220    pub fn is_empty(&self) -> bool {
221        unsafe { sys::MaaImageBufferIsEmpty(self.handle.as_ptr()) != 0 }
222    }
223
224    /// Clear the buffer, releasing any image data.
225    pub fn clear(&mut self) -> MaaResult<()> {
226        let ret = unsafe { sys::MaaImageBufferClear(self.handle.as_ptr()) };
227        crate::common::check_bool(ret)
228    }
229
230    /// Get the image width in pixels.
231    pub fn width(&self) -> i32 {
232        unsafe { sys::MaaImageBufferWidth(self.handle.as_ptr()) }
233    }
234
235    /// Get the image height in pixels.
236    pub fn height(&self) -> i32 {
237        unsafe { sys::MaaImageBufferHeight(self.handle.as_ptr()) }
238    }
239
240    /// Get the number of color channels (e.g., 3 for RGB, 4 for RGBA).
241    pub fn channels(&self) -> i32 {
242        unsafe { sys::MaaImageBufferChannels(self.handle.as_ptr()) }
243    }
244
245    /// Get the OpenCV image type constant.
246    pub fn image_type(&self) -> i32 {
247        unsafe { sys::MaaImageBufferType(self.handle.as_ptr()) }
248    }
249
250    /// Get the encoded image data as a byte vector (PNG format).
251    pub fn to_vec(&self) -> Option<Vec<u8>> {
252        unsafe {
253            let ptr = sys::MaaImageBufferGetEncoded(self.handle.as_ptr());
254            let size = sys::MaaImageBufferGetEncodedSize(self.handle.as_ptr());
255            if !ptr.is_null() && size > 0 {
256                let slice = slice::from_raw_parts(ptr, size as usize);
257                Some(slice.to_vec())
258            } else {
259                None
260            }
261        }
262    }
263
264    /// Get the raw image data (BGR format, row-major).
265    pub fn raw_data(&self) -> Option<&[u8]> {
266        unsafe {
267            let ptr = sys::MaaImageBufferGetRawData(self.handle.as_ptr());
268            if ptr.is_null() {
269                return None;
270            }
271            let w = self.width() as usize;
272            let h = self.height() as usize;
273            let c = self.channels() as usize;
274            if w == 0 || h == 0 || c == 0 {
275                return None;
276            }
277            Some(slice::from_raw_parts(ptr as *const u8, w * h * c))
278        }
279    }
280
281    /// Set the image from raw BGR data.
282    pub fn set_raw_data(
283        &mut self,
284        data: &[u8],
285        width: i32,
286        height: i32,
287        img_type: i32,
288    ) -> MaaResult<()> {
289        let ret = unsafe {
290            sys::MaaImageBufferSetRawData(
291                self.handle.as_ptr(),
292                data.as_ptr() as *mut std::ffi::c_void,
293                width,
294                height,
295                img_type,
296            )
297        };
298        crate::common::check_bool(ret)
299    }
300
301    /// Set the image from raw BGR data (assuming 3 channels, CV_8UC3).
302    pub fn set(&mut self, data: &[u8], width: i32, height: i32) -> MaaResult<()> {
303        self.set_raw_data(data, width, height, 16)
304    }
305
306    /// Set the image from encoded data (PNG/JPEG).
307    pub fn set_encoded(&mut self, data: &[u8]) -> MaaResult<()> {
308        let ret = unsafe {
309            sys::MaaImageBufferSetEncoded(
310                self.handle.as_ptr(),
311                data.as_ptr() as *mut u8,
312                data.len() as u64,
313            )
314        };
315        crate::common::check_bool(ret)
316    }
317
318    /// Resize the image.
319    ///
320    /// * `width`: Target width, 0 for auto aspect ratio.
321    /// * `height`: Target height, 0 for auto aspect ratio.
322    pub fn resize(&mut self, width: i32, height: i32) -> MaaResult<()> {
323        let ret = unsafe { sys::MaaImageBufferResize(self.handle.as_ptr(), width, height) };
324        crate::common::check_bool(ret)
325    }
326
327    /// Convert to `image` crate's `DynamicImage`.
328    #[cfg(feature = "image")]
329    pub fn to_dynamic_image(&self) -> MaaResult<image::DynamicImage> {
330        let encoded = self.to_vec().ok_or(MaaError::ImageConversionError)?;
331        image::load_from_memory(&encoded).map_err(|_| MaaError::ImageConversionError)
332    }
333
334    /// Create an image buffer from `image` crate's `DynamicImage`.
335    #[cfg(feature = "image")]
336    pub fn from_dynamic_image(img: &image::DynamicImage) -> MaaResult<Self> {
337        use std::io::Cursor;
338
339        let mut bytes = Vec::new();
340        img.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)
341            .map_err(|_| MaaError::ImageConversionError)?;
342
343        let mut buffer = Self::new()?;
344        buffer.set_encoded(&bytes)?;
345        Ok(buffer)
346    }
347
348    /// Create an image buffer from an RGB image.
349    #[cfg(feature = "image")]
350    pub fn from_rgb_image(img: &image::RgbImage) -> MaaResult<Self> {
351        Self::from_dynamic_image(&image::DynamicImage::ImageRgb8(img.clone()))
352    }
353
354    /// Create an image buffer from an RGBA image.
355    #[cfg(feature = "image")]
356    pub fn from_rgba_image(img: &image::RgbaImage) -> MaaResult<Self> {
357        Self::from_dynamic_image(&image::DynamicImage::ImageRgba8(img.clone()))
358    }
359}
360
361/// A list buffer for storing multiple images.
362///
363/// Provides indexed access and iteration over a collection of [`MaaImageBuffer`]s.
364pub struct MaaImageListBuffer {
365    handle: NonNull<sys::MaaImageListBuffer>,
366    own: bool,
367}
368
369impl_buffer_lifecycle!(
370    MaaImageListBuffer,
371    sys::MaaImageListBuffer,
372    sys::MaaImageListBufferCreate,
373    sys::MaaImageListBufferDestroy
374);
375
376impl MaaImageListBuffer {
377    /// Returns the number of images in the list.
378    pub fn len(&self) -> usize {
379        unsafe { sys::MaaImageListBufferSize(self.handle.as_ptr()) as usize }
380    }
381
382    /// Returns `true` if the list contains no images.
383    pub fn is_empty(&self) -> bool {
384        unsafe { sys::MaaImageListBufferIsEmpty(self.handle.as_ptr()) != 0 }
385    }
386
387    /// Get image at index. Returns None if index out of bounds.
388    /// Note: The returned buffer is a view into this list (non-owning).
389    pub fn at(&self, index: usize) -> Option<MaaImageBuffer> {
390        unsafe {
391            let ptr = sys::MaaImageListBufferAt(self.handle.as_ptr(), index as u64);
392            MaaImageBuffer::from_handle(ptr as *mut sys::MaaImageBuffer)
393        }
394    }
395
396    /// Appends an image to the end of the list.
397    pub fn append(&self, image: &MaaImageBuffer) -> MaaResult<()> {
398        let ret = unsafe { sys::MaaImageListBufferAppend(self.handle.as_ptr(), image.as_ptr()) };
399        crate::common::check_bool(ret)
400    }
401
402    /// Set the content of this list, replacing existing content.
403    pub fn set(&self, data: &[&MaaImageBuffer]) -> MaaResult<()> {
404        self.clear()?;
405        for img in data {
406            self.append(img)?;
407        }
408        Ok(())
409    }
410
411    /// Removes the image at the specified index.
412    pub fn remove(&self, index: usize) -> MaaResult<()> {
413        let ret = unsafe { sys::MaaImageListBufferRemove(self.handle.as_ptr(), index as u64) };
414        crate::common::check_bool(ret)
415    }
416
417    /// Removes all images from the list.
418    pub fn clear(&self) -> MaaResult<()> {
419        let ret = unsafe { sys::MaaImageListBufferClear(self.handle.as_ptr()) };
420        crate::common::check_bool(ret)
421    }
422
423    /// Get all images as a vector of `MaaImageBuffer` handles.
424    pub fn to_vec(&self) -> Vec<MaaImageBuffer> {
425        (0..self.len()).filter_map(|i| self.at(i)).collect()
426    }
427
428    /// Get an iterator over the images in the list.
429    pub fn iter(&self) -> impl Iterator<Item = MaaImageBuffer> + '_ {
430        (0..self.len()).filter_map(move |i| self.at(i))
431    }
432
433    /// Get all images as a vector of encoded byte vectors.
434    pub fn to_vec_of_vec(&self) -> Vec<Vec<u8>> {
435        self.iter().filter_map(|img| img.to_vec()).collect()
436    }
437
438    /// Get all images as raw BGR data vectors + metadata.
439    pub fn to_raw_vecs(&self) -> Vec<(Vec<u8>, i32, i32, i32, i32)> {
440        self.iter()
441            .filter_map(|img| {
442                img.raw_data().map(|data| {
443                    (
444                        data.to_vec(),
445                        img.width(),
446                        img.height(),
447                        img.channels(),
448                        img.image_type(),
449                    )
450                })
451            })
452            .collect()
453    }
454}
455
456/// A list buffer for storing multiple strings.
457///
458/// Provides indexed access and iteration over a collection of UTF-8 strings.
459pub struct MaaStringListBuffer {
460    handle: NonNull<sys::MaaStringListBuffer>,
461    own: bool,
462}
463
464impl_buffer_lifecycle!(
465    MaaStringListBuffer,
466    sys::MaaStringListBuffer,
467    sys::MaaStringListBufferCreate,
468    sys::MaaStringListBufferDestroy
469);
470
471impl MaaStringListBuffer {
472    /// Returns the number of strings in the list.
473    pub fn len(&self) -> usize {
474        unsafe { sys::MaaStringListBufferSize(self.handle.as_ptr()) as usize }
475    }
476
477    /// Returns `true` if the list contains no strings.
478    pub fn is_empty(&self) -> bool {
479        unsafe { sys::MaaStringListBufferIsEmpty(self.handle.as_ptr()) != 0 }
480    }
481
482    /// Appends a string to the end of the list.
483    pub fn append(&self, s: &str) -> MaaResult<()> {
484        let mut str_buf = MaaStringBuffer::new()?;
485        str_buf.set(s)?;
486        let ret = unsafe { sys::MaaStringListBufferAppend(self.handle.as_ptr(), str_buf.as_ptr()) };
487        crate::common::check_bool(ret)
488    }
489
490    /// Set the content of this list, replacing existing content.
491    pub fn set<S: AsRef<str>>(&self, data: &[S]) -> MaaResult<()> {
492        self.clear()?;
493        for s in data {
494            self.append(s.as_ref())?;
495        }
496        Ok(())
497    }
498
499    /// Removes the string at the specified index.
500    pub fn remove(&self, index: usize) -> MaaResult<()> {
501        let ret = unsafe { sys::MaaStringListBufferRemove(self.handle.as_ptr(), index as u64) };
502        crate::common::check_bool(ret)
503    }
504
505    /// Removes all strings from the list.
506    pub fn clear(&self) -> MaaResult<()> {
507        let ret = unsafe { sys::MaaStringListBufferClear(self.handle.as_ptr()) };
508        crate::common::check_bool(ret)
509    }
510
511    /// Get an iterator over the strings in the list.
512    ///
513    /// The iterator yields `&str` slices borrowing from the buffer.
514    /// This is zero-cost and safe.
515    pub fn iter(&self) -> impl Iterator<Item = &str> + '_ {
516        (0..self.len()).map(move |i| unsafe {
517            let ptr = sys::MaaStringListBufferAt(self.handle.as_ptr(), i as u64);
518            // ptr is MaaStringBufferHandle (not null usually if index is valid)
519            if ptr.is_null() {
520                return "";
521            }
522            let str_ptr = sys::MaaStringBufferGet(ptr as *mut sys::MaaStringBuffer);
523            let size = sys::MaaStringBufferSize(ptr as *mut sys::MaaStringBuffer) as usize;
524            if str_ptr.is_null() || size == 0 {
525                ""
526            } else {
527                let slice = slice::from_raw_parts(str_ptr as *const u8, size);
528                str::from_utf8(slice).unwrap_or("")
529            }
530        })
531    }
532
533    /// Collects all strings into a `Vec<String>`.
534    pub fn to_vec(&self) -> Vec<String> {
535        self.iter().map(|s| s.to_string()).collect()
536    }
537}
538
539/// Rect buffer for passing rectangle coordinates between Rust and C API.
540///
541/// Stores x, y position and width, height dimensions.
542pub struct MaaRectBuffer {
543    handle: NonNull<sys::MaaRect>,
544    own: bool,
545}
546
547impl_buffer_lifecycle!(
548    MaaRectBuffer,
549    sys::MaaRect,
550    sys::MaaRectCreate,
551    sys::MaaRectDestroy
552);
553
554impl MaaRectBuffer {
555    /// Get the rect values.
556    pub fn get(&self) -> crate::common::Rect {
557        unsafe {
558            crate::common::Rect {
559                x: sys::MaaRectGetX(self.handle.as_ptr()),
560                y: sys::MaaRectGetY(self.handle.as_ptr()),
561                width: sys::MaaRectGetW(self.handle.as_ptr()),
562                height: sys::MaaRectGetH(self.handle.as_ptr()),
563            }
564        }
565    }
566
567    /// Set the rect values.
568    pub fn set(&mut self, rect: &crate::common::Rect) -> MaaResult<()> {
569        let ret = unsafe {
570            sys::MaaRectSet(
571                self.handle.as_ptr(),
572                rect.x,
573                rect.y,
574                rect.width,
575                rect.height,
576            )
577        };
578        crate::common::check_bool(ret)
579    }
580}
581
582impl From<crate::common::Rect> for MaaRectBuffer {
583    fn from(rect: crate::common::Rect) -> Self {
584        let mut buf = Self::new().expect("Failed to create MaaRectBuffer");
585        buf.set(&rect).expect("Failed to set rect buffer");
586        buf
587    }
588}
589
590impl From<MaaRectBuffer> for crate::common::Rect {
591    fn from(buf: MaaRectBuffer) -> Self {
592        buf.get()
593    }
594}
595
596impl From<&MaaRectBuffer> for crate::common::Rect {
597    fn from(buf: &MaaRectBuffer) -> Self {
598        buf.get()
599    }
600}