#![allow(unused_imports)]
#![allow(dead_code)]
use crate::{context::Context2D, font_library::FontLibrary, utils::*};
use neon::{prelude::*, types::buffer::TypedArray};
use skia_safe::{
AlphaType, ColorSpace, ColorType, Data, FontMgr, ISize, Image as SkImage, ImageInfo, Picture,
PictureRecorder, Rect, Size,
image::images,
svg::{self, Length, LengthUnit},
};
use std::cell::RefCell;
pub type BoxedImage = JsBox<RefCell<Image>>;
impl Finalize for Image {}
pub struct Image {
src: String,
pub autosized: bool,
pub content: Content,
}
impl Default for Image {
fn default() -> Self {
Image {
content: Content::Loading,
autosized: false,
src: "".to_string(),
}
}
}
#[derive(Default)]
pub enum Content {
Bitmap(SkImage),
Vector(Picture, Size),
#[default]
Loading,
Broken,
}
impl Clone for Content {
fn clone(&self) -> Self {
match self {
Content::Bitmap(img) => Content::Bitmap(img.clone()),
Content::Vector(pict, size) => Content::Vector(pict.clone(), *size),
_ => Content::default(),
}
}
}
impl Content {
pub fn from_context(ctx: &mut Context2D, use_vector: bool) -> Self {
match use_vector {
true => ctx
.get_picture()
.map(|p| Content::Vector(p, ctx.bounds.size())),
false => ctx.get_image().map(Content::Bitmap),
}
.unwrap_or_default()
}
pub fn from_image_data(image_data: ImageData) -> Self {
let info = image_data.image_info();
images::raster_from_data(&info, &image_data.buffer, info.min_row_bytes())
.map(Content::Bitmap)
.unwrap_or_default()
}
pub fn size(&self) -> Size {
match &self {
Content::Bitmap(img) => img.dimensions().into(),
Content::Vector(_, size) => *size,
_ => Size::new_empty(),
}
}
pub fn is_complete(&self) -> bool {
!matches!(self, Content::Loading)
}
pub fn is_drawable(&self) -> bool {
!matches!(self, Content::Loading | Content::Broken)
}
pub fn snap_rects_to_bounds(&self, mut src: Rect, mut dst: Rect) -> (Rect, Rect) {
let scale_x = dst.width() / src.width();
let scale_y = dst.height() / src.height();
let size = self.size();
if src.left < 0.0 {
dst.left += -src.left * scale_x;
src.left = 0.0;
}
if src.top < 0.0 {
dst.top += -src.top * scale_y;
src.top = 0.0;
}
if src.right > size.width {
dst.right -= (src.right - size.width) * scale_x;
src.right = size.width;
}
if src.bottom > size.height {
dst.bottom -= (src.bottom - size.height) * scale_y;
src.bottom = size.height;
}
(src, dst)
}
}
#[derive(Debug)]
pub struct ImageData {
pub width: f32,
pub height: f32,
pub buffer: Data,
color_type: ColorType,
color_space: ColorSpace,
}
impl ImageData {
pub fn new(
buffer: Data,
width: f32,
height: f32,
color_type: String,
color_space: String,
) -> Self {
let color_type = to_color_type(&color_type);
let color_space = to_color_space(&color_space);
Self {
buffer,
width,
height,
color_type,
color_space,
}
}
pub fn image_info(&self) -> ImageInfo {
ImageInfo::new(
(self.width as _, self.height as _),
self.color_type,
AlphaType::Unpremul,
self.color_space.clone(),
)
}
}
pub fn new(mut cx: FunctionContext) -> JsResult<BoxedImage> {
let this = RefCell::new(Image::default());
Ok(cx.boxed(this))
}
pub fn get_src(mut cx: FunctionContext) -> JsResult<JsString> {
let this = cx.argument::<BoxedImage>(0)?;
let this = this.borrow();
Ok(cx.string(&this.src))
}
pub fn set_src(mut cx: FunctionContext) -> JsResult<JsUndefined> {
let this = cx.argument::<BoxedImage>(0)?;
let mut this = this.borrow_mut();
let src = cx.argument::<JsString>(1)?.value(&mut cx);
this.src = src;
Ok(cx.undefined())
}
pub fn set_data<'a>(mut cx: FunctionContext<'a>) -> NeonResult<Handle<'a, JsBoolean>> {
let this = cx.argument::<BoxedImage>(0)?;
let mut this = this.borrow_mut();
let buffer = cx.argument::<JsBuffer>(1)?;
let data = Data::new_copy(buffer.as_slice(&cx));
if let Some(raw_info) = opt_image_info_arg(&mut cx, 2)? {
this.content = match images::raster_from_data(&raw_info, data, raw_info.min_row_bytes()) {
Some(image) => Content::Bitmap(image),
None => Content::Broken,
}
} else if let Some(image) = images::deferred_from_encoded_data(&data, None) {
this.content = Content::Bitmap(image);
} else if let Ok(mut dom) =
svg::Dom::from_bytes(&data, FontLibrary::with_shared(|lib| lib.font_mgr()))
{
let root = dom.root();
let mut size = root.intrinsic_size();
if size.is_empty() {
this.autosized = true;
let Length {
value: width,
unit: w_unit,
} = root.width();
let Length {
value: height,
unit: h_unit,
} = root.height();
size = match ((width, w_unit), (height, h_unit)) {
((100.0, LengthUnit::Percentage), (height, LengthUnit::Number)) => {
(*height, *height).into()
}
((width, LengthUnit::Number), (100.0, LengthUnit::Percentage)) => {
(*width, *width).into()
}
_ => {
let aspect = root
.view_box()
.map(|vb| vb.width() / vb.height())
.unwrap_or(1.0);
(150.0 * aspect, 150.0).into()
}
};
};
let bounds = Rect::from_size(size);
let mut compositor = PictureRecorder::new();
dom.set_container_size(bounds.size());
dom.render(compositor.begin_recording(bounds, true));
this.content = match compositor.finish_recording_as_picture(None) {
Some(picture) => Content::Vector(picture, size),
None => Content::Broken,
};
} else {
this.content = Content::Broken
}
Ok(cx.boolean(this.content.is_drawable()))
}
pub fn get_width(mut cx: FunctionContext) -> JsResult<JsValue> {
let this = cx.argument::<BoxedImage>(0)?;
let this = this.borrow();
Ok(cx.number(this.content.size().width).upcast())
}
pub fn get_height(mut cx: FunctionContext) -> JsResult<JsValue> {
let this = cx.argument::<BoxedImage>(0)?;
let this = this.borrow();
Ok(cx.number(this.content.size().height).upcast())
}
pub fn get_complete(mut cx: FunctionContext) -> JsResult<JsBoolean> {
let this = cx.argument::<BoxedImage>(0)?;
let this = this.borrow();
Ok(cx.boolean(this.content.is_complete()))
}
pub fn pixels(mut cx: FunctionContext) -> JsResult<JsValue> {
let this = cx.argument::<BoxedImage>(0)?;
let this = this.borrow_mut();
let (color_type, color_space) = image_data_settings_arg(&mut cx, 1);
let info = ImageInfo::new(
this.content.size().to_floor(),
color_type,
AlphaType::Unpremul,
color_space,
);
let mut pixels = cx.buffer(info.bytes_per_pixel() * (info.width() * info.height()) as usize)?;
match &this.content {
Content::Bitmap(image) => {
match image.read_pixels(
&info,
pixels.as_mut_slice(&mut cx),
info.min_row_bytes(),
(0, 0),
skia_safe::image::CachingHint::Allow,
) {
true => Ok(pixels.upcast()),
false => Ok(cx.undefined().upcast()),
}
}
_ => Ok(cx.undefined().upcast()),
}
}