use crate::drawing_context_impl::{CreateInfo, DrawingContextImpl, VelloBackdrop};
use crate::helpers::image_saving_helper;
use crate::helpers::pixel_format_helper::{scene_size, to_shared_image};
use crate::i_drawable_bitmap_impl::IDrawableBitmapImpl;
use crate::immutable_bitmap::ImmutableBitmap;
use crate::scene::{create_scene_sink, VelloSceneBrush, VelloSceneImage};
use crate::vello_options::VelloRenderingMode;
use ferroui_base::media::imaging::BitmapEncoderOptions;
use ferroui_base::platform::{IBitmapImpl, IDrawingContextImpl, IDrawingContextLayerImpl, RenderTargetProperties};
use ferroui_base::{PixelSize, Vector};
use peniko::{BlendMode, Extend, ImageData, ImageQuality};
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::io::{self, Write};
use std::rc::Rc;
#[derive(Clone, Default)]
pub struct SurfaceRenderTargetCreateInfo {
pub width: i32,
pub height: i32,
pub dpi: Vector,
pub rendering_modes: Vec<VelloRenderingMode>,
pub use_scaled_drawing: bool,
}
struct SurfacePixels {
data: Option<std::sync::Arc<Vec<u8>>>,
has_content: bool,
image: Option<ImageData>,
}
pub struct SurfaceRenderTarget {
use_scaled_drawing: bool,
pixels: Rc<RefCell<SurfacePixels>>,
rendering_modes: Vec<VelloRenderingMode>,
dpi: Vector,
pixel_size: PixelSize,
version: Rc<Cell<i32>>,
}
impl SurfaceRenderTarget {
pub fn new(create_info: SurfaceRenderTargetCreateInfo) -> Self {
let pixel_size = PixelSize::new(create_info.width.max(1), create_info.height.max(1));
let (width, height) = scene_size(pixel_size);
crate::perf::count(crate::perf::Phase::SurfaceInMemory, u64::from(width) * u64::from(height) * 4);
Self {
use_scaled_drawing: create_info.use_scaled_drawing,
pixels: Rc::new(RefCell::new(SurfacePixels {
data: Some(std::sync::Arc::new(vec![0u8; width as usize * height as usize * 4])),
has_content: false,
image: None,
})),
rendering_modes: create_info.rendering_modes,
dpi: create_info.dpi,
pixel_size,
version: Rc::new(Cell::new(1)),
}
}
pub fn properties(&self) -> RenderTargetProperties {
RenderTargetProperties::default()
}
pub fn has_render_context_affinity(&self) -> bool {
false
}
fn image_of(pixels: &mut SurfacePixels, pixel_size: PixelSize) -> Option<ImageData> {
if pixels.image.is_none() {
pixels.image = Some(to_shared_image(pixels.data.as_ref()?.clone(), pixel_size));
}
pixels.image.clone()
}
}
impl IBitmapImpl for SurfaceRenderTarget {
fn dpi(&self) -> Vector {
self.dpi
}
fn pixel_size(&self) -> PixelSize {
self.pixel_size
}
fn version(&self) -> i32 {
self.version.get()
}
fn save(&self, stream: &mut dyn Write, options: &BitmapEncoderOptions) -> io::Result<()> {
let image = self.image().ok_or_else(|| io::Error::other("SurfaceRenderTarget has been disposed"))?;
image_saving_helper::save_image(&image, stream, options)
}
fn dispose(&self) {
let mut pixels = self.pixels.borrow_mut();
pixels.data = None;
pixels.image = None;
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl IDrawingContextLayerImpl for SurfaceRenderTarget {
fn blit(&self, context: &mut dyn IDrawingContextImpl) {
let context = context
.as_any_mut()
.downcast_mut::<DrawingContextImpl>()
.unwrap_or_else(|| panic!("The layer can only be blitted onto a drawing context of the Vello backend"));
if let Some(image) = self.image() {
context.draw_image_in_device_space(image, BlendMode::default());
}
}
fn can_blit(&self) -> bool {
true
}
fn is_corrupted(&self) -> bool {
false
}
fn create_shared_snapshot(&self) -> std::sync::Arc<ferroui_base::platform::SharedBitmapImpl> {
let image = self.image().unwrap_or_else(|| panic!("SurfaceRenderTarget has been disposed"));
std::sync::Arc::new(ImmutableBitmap::from_image(image))
}
fn create_drawing_context(&self) -> Box<dyn IDrawingContextImpl> {
Box::new(self.create_drawing_context_with(Vec::new()))
}
}
impl SurfaceRenderTarget {
pub fn has_content(&self) -> bool {
self.pixels.borrow().has_content
}
pub fn create_drawing_context_with(&self, disposables: Vec<Box<dyn FnOnce()>>) -> DrawingContextImpl {
let (width, height) = scene_size(self.pixel_size);
let sink = create_scene_sink(&self.rendering_modes, width, height);
let backdrop = self.pixels.borrow().has_content.then(|| {
let (pixels, pixel_size) = (self.pixels.clone(), self.pixel_size);
VelloBackdrop {
composes_over_target: sink.rendering_mode() == VelloRenderingMode::Cpu,
brush: Box::new(move || {
Self::image_of(&mut pixels.borrow_mut(), pixel_size).map(|image| {
VelloSceneBrush::Image(VelloSceneImage {
image,
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
alpha: 1.0,
})
})
}),
}
});
let pixels = self.pixels.clone();
let create_info = CreateInfo {
sink,
backdrop,
on_finished: Box::new(move |sink| {
let mut pixels = pixels.borrow_mut();
pixels.image = None;
pixels.has_content = true;
if let Some(data) = pixels.data.as_mut() {
if std::sync::Arc::strong_count(data) > 1 {
crate::perf::count(crate::perf::Phase::PixelCopy, data.len() as u64);
}
sink.render_to_pixels(&mut std::sync::Arc::make_mut(data)[..]);
}
}),
scale_drawing_to_dpi: self.use_scaled_drawing,
dpi: self.dpi,
rendering_modes: self.rendering_modes.clone(),
};
let version = self.version.clone();
let mut all: Vec<Box<dyn FnOnce()>> = vec![Box::new(move || version.set(version.get() + 1))];
all.extend(disposables);
DrawingContextImpl::new(create_info, all)
}
}
impl IDrawableBitmapImpl for SurfaceRenderTarget {
fn image(&self) -> Option<ImageData> {
Self::image_of(&mut self.pixels.borrow_mut(), self.pixel_size)
}
}