use anyhow::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use skia_safe::{Canvas, Paint, Rect};
use crate::engine::renderer::asset_cache;
use crate::error::RustmotionError;
use crate::layout::{Constraints, LayoutNode};
use crate::schema::{ImageFit, LayerStyle, Size};
use crate::traits::{RenderContext, TimingConfig, Widget};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct Image {
pub src: String,
#[serde(default)]
pub size: Option<Size>,
#[serde(default)]
pub fit: ImageFit,
#[serde(flatten)]
pub timing: TimingConfig,
#[serde(default)]
pub style: LayerStyle,
}
crate::impl_traits!(Image {
Animatable => style,
Timed => timing,
Styled => style,
});
impl Widget for Image {
fn render(&self, canvas: &Canvas, layout: &LayoutNode, _ctx: &RenderContext, _props: &crate::engine::animator::AnimatedProperties) -> Result<()> {
let cache = asset_cache();
let img = if let Some(cached) = cache.get(&self.src) {
cached.clone()
} else {
let data = std::fs::read(&self.src)
.map_err(|e| RustmotionError::ImageLoad { path: self.src.clone(), reason: e.to_string() })?;
let skia_data = skia_safe::Data::new_copy(&data);
let decoded = skia_safe::Image::from_encoded(skia_data)
.ok_or_else(|| RustmotionError::ImageDecode { path: self.src.clone() })?;
cache.insert(self.src.clone(), decoded.clone());
decoded
};
let img_w = img.width() as f32;
let img_h = img.height() as f32;
let target_w = layout.width;
let target_h = layout.height;
let (draw_w, draw_h, offset_x, offset_y) = match self.fit {
ImageFit::Fill => (target_w, target_h, 0.0, 0.0),
ImageFit::Contain => {
let scale = (target_w / img_w).min(target_h / img_h);
let w = img_w * scale;
let h = img_h * scale;
(w, h, (target_w - w) / 2.0, (target_h - h) / 2.0)
}
ImageFit::Cover => {
let scale = (target_w / img_w).max(target_h / img_h);
let w = img_w * scale;
let h = img_h * scale;
(w, h, (target_w - w) / 2.0, (target_h - h) / 2.0)
}
};
let dst = Rect::from_xywh(offset_x, offset_y, draw_w, draw_h);
let paint = Paint::default();
if matches!(self.fit, ImageFit::Cover) {
canvas.save();
canvas.clip_rect(
Rect::from_xywh(0.0, 0.0, target_w, target_h),
skia_safe::ClipOp::Intersect,
true,
);
canvas.draw_image_rect(img, None, dst, &paint);
canvas.restore();
} else {
canvas.draw_image_rect(img, None, dst, &paint);
}
Ok(())
}
fn measure(&self, _constraints: &Constraints) -> (f32, f32) {
match &self.size {
Some(s) => (s.width, s.height),
None => (100.0, 100.0),
}
}
}