Hello,
I'm building a small project where I'm using opencv-rs and manually link to raylib (for fun and learning rust).
It happens to be that one can copy the pointer to the data of an opencv mat to a raylib image and if the format is correct it works.
OpenCV mat is reference counted matrix and if no copy exists it deallocates the memory which I suspect is quite involved since many (image) formats are supported.
Raylib provides an UnloadImage
function which accepts the raylib image struct, which holds a void pointer to the image data. The deallocation is also quite complex, for the same reasons as for the OpenCV mat.
Therefore I don't want to implement the deallocations myself, just organize the deallocation in a way no memory is leaked or pointer is double freed
I'v created the following structs, Image which wraps the FFI save Raylib Image (RImage)
pub struct Image {
image: RImage,
mat: Option<Mat>,
}
impl From<Mat> for Image {
fn from(mat: Mat) -> Self {
Self {
image: (&mat).into(),
mat: Some(mat),
}
}
}
impl Clone for Image {
fn clone(&self) -> Self {
Self {
image: self.image.clone(),
mat: None,
}
}
}
unsafe impl Send for Image {}
impl Drop for Image {
fn drop(&mut self) {
if let Some(_mat) = &mut self.mat {
// following values won't be leaked
let _ = self.image.data;
let _ = self.image.width;
let _ = self.image.height;
let _ = self.image.mipmaps;
let _ = self.image.format;
println!("OpenCV mat unloaded")
} else {
unsafe {
UnloadImage(RImage {
data: self.image.data,
width: self.image.width,
height: self.image.height,
mipmaps: self.image.mipmaps,
format: self.image.format,
})
};
println!("Raylib image unloaded")
}
}
}
#[repr(C)]
pub struct RImage {
pub data: *mut c_void,
pub width: c_int, // Image base width
pub height: c_int, // Image base height
pub mipmaps: c_int, // Mipmap levels, 1 by default
pub format: PixelFormat, // Data format (PixelFormat type)
}
unsafe impl Send for RImage {}
impl Clone for RImage {
fn clone(&self) -> Self {
// does a deep clone/copy of the image data
unsafe {
ImageCopy(RImage {
data: self.data,
width: self.width,
height: self.height,
mipmaps: self.mipmaps,
format: self.format,
})
}
}
}
impl From<&Mat> for RImage {
fn from(m: &Mat) -> Self {
let f = match m.typ() {
CV_8UC3 => PixelFormat::PIXELFORMAT_UNCOMPRESSED_R8G8B8,
_ => panic!("Matrix Type not supported\n {:#?}", m),
};
let size = m.size().unwrap();
// evil pointer copy
let data = m.data() as *mut c_void;
Self {
data,
width: size.width,
height: size.height,
mipmaps: 1,
format: f,
}
}
}
From what I understand, which can be wrong, the drop function is called when a variable goes out of scope of the owner. Since my Image wrapper takes ownership of the OpenCV mat (if there is one) and how I implemented the drop trait, it should call drop on the OpenCV mat, correct?
I pull the mat out of the Option and then do nothing with it which should call the OpenCV drop function when it goes out of scope, which internally calls the c++ bindings. OpenCV then internally manages all the reference counting stuff. The UnloadImage
function is not called.
If there is none only the UnloadImage
function ist called. Thus not resulting in a double free.
English isn't my first language, so if I can clarify something just ask!
Thank you