use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::cell::RefCell;
use core::mem::ManuallyDrop;
use nami::SignalExt;
use waterui_graphics::Picture;
use waterui_graphics::scene2d_cpu::{Rasterizer, RgbaBitmap};
#[cfg(feature = "c-api")]
use crate::reactive::WuiComputed;
use crate::{IntoFFI, WuiStr, ffi_computed};
pub struct WuiPictureHandle(pub(crate) Picture);
impl core::fmt::Debug for WuiPictureHandle {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("WuiPictureHandle").finish_non_exhaustive()
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiPicture {
pub picture: *mut WuiPictureHandle,
pub width: f32,
pub height: f32,
pub label: WuiStr,
}
impl IntoFFI for Picture {
type FFI = WuiPicture;
fn into_ffi(self) -> Self::FFI {
let size = self.size();
let label = self.label().cloned().unwrap_or_default();
WuiPicture {
picture: Box::into_raw(Box::new(WuiPictureHandle(self))),
width: size.width,
height: size.height,
label: label.into_ffi(),
}
}
}
ffi_view!(Picture, WuiPicture, picture);
#[repr(C)]
#[derive(Debug)]
pub struct WuiBitmap {
pub width: u32,
pub height: u32,
pub data: *mut u8,
pub len: usize,
pub capacity: usize,
}
impl IntoFFI for RgbaBitmap {
type FFI = WuiBitmap;
fn into_ffi(self) -> Self::FFI {
let (width, height) = (self.width(), self.height());
let mut data = ManuallyDrop::new(self.into_data());
WuiBitmap {
width,
height,
data: data.as_mut_ptr(),
len: data.len(),
capacity: data.capacity(),
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_drop_bitmap(bitmap: WuiBitmap) {
drop(unsafe { Vec::from_raw_parts(bitmap.data, bitmap.len, bitmap.capacity) });
}
ffi_computed!(RgbaBitmap, WuiBitmap, bitmap);
pub(crate) fn bitmap_signal(picture: &Picture, scale: f32) -> waterui::Computed<RgbaBitmap> {
let (width, height) = picture.pixel_size(scale);
#[expect(
clippy::cast_precision_loss,
reason = "pixel_size bounds both sides to 65535, which f32 holds exactly"
)]
let transform = picture.transform_to(width as f32, height as f32);
let rasterizer = Rc::new(RefCell::new(Rasterizer::new(width, height)));
picture
.recording()
.map(move |recording| rasterizer.borrow_mut().rasterize(&recording, transform))
.computed()
}
#[cfg(feature = "c-api")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_picture_bitmap(
picture: *const WuiPictureHandle,
scale: f32,
) -> *mut WuiComputed<RgbaBitmap> {
let picture = unsafe { &(*picture).0 };
bitmap_signal(picture, scale).into_ffi()
}
#[cfg(feature = "c-api")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_drop_picture(picture: *mut WuiPictureHandle) {
drop(unsafe { Box::from_raw(picture) });
}
#[cfg(test)]
mod tests {
use super::*;
use kurbo::{Affine, Rect, Shape};
use peniko::{Brush, Color, Fill};
use waterui::Signal;
use waterui_core::layout::Size;
use waterui_core::{binding, constant};
fn square(color: Color) -> alloc::sync::Arc<waterui_graphics::SceneRecording> {
Picture::record(|scene| {
scene.fill(
Fill::NonZero,
Affine::IDENTITY,
&Brush::Solid(color),
None,
&Rect::new(0.0, 0.0, 10.0, 10.0).to_path(0.1),
);
})
}
#[test]
fn a_bitmap_signal_rasterises_at_the_display_scale() {
let picture = Picture::new(Size::new(10.0, 5.0), constant(square(Color::BLACK)));
let bitmap = bitmap_signal(&picture, 3.0).get();
assert_eq!((bitmap.width(), bitmap.height()), (30, 15));
assert_eq!(bitmap.data().len(), 30 * 15 * 4);
assert_eq!(&bitmap.data()[..4], &[0, 0, 0, 255]);
}
#[test]
fn a_new_drawing_reaches_the_bitmap_signal() {
let tint = binding(Color::BLACK);
let picture = Picture::new(Size::new(10.0, 10.0), tint.map(square));
let bitmaps = bitmap_signal(&picture, 1.0);
assert_eq!(&bitmaps.get().data()[..4], &[0, 0, 0, 255]);
tint.set(Color::WHITE);
assert_eq!(&bitmaps.get().data()[..4], &[255, 255, 255, 255]);
}
#[test]
fn a_bitmap_crosses_the_boundary_and_comes_back_whole() {
let picture = Picture::new(Size::new(2.0, 2.0), constant(square(Color::BLACK)));
let ffi = bitmap_signal(&picture, 1.0).get().into_ffi();
assert_eq!((ffi.width, ffi.height, ffi.len), (2, 2, 16));
unsafe { waterui_drop_bitmap(ffi) };
}
}