Add embedable images (#564)

* Add embedable bitmaps

* Initial work on blob urls

* Finish implementing data url

* Fix some bugs

* Rename bitmap to image

* Fix loading image on document load

* Add transform properties for image

* Remove some logging

* Add image dimensions

* Implement system copy and paste

* Fix pasting images

* Fix test

* Address code review

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2022-03-27 11:43:41 +01:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 0ee492a857
commit 51c31f042b
23 changed files with 462 additions and 59 deletions
+100
View File
@@ -0,0 +1,100 @@
use super::layer_info::LayerData;
use super::style::ViewMode;
use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::LayerId;
use glam::{DAffine2, DMat2, DVec2};
use kurbo::{Affine, BezPath, Shape as KurboShape};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
fn glam_to_kurbo(transform: DAffine2) -> Affine {
Affine::new(transform.to_cols_array())
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ImageLayer {
pub mime: String,
pub image_data: Vec<u8>,
#[serde(skip)]
pub blob_url: Option<String>,
#[serde(skip)]
pub dimensions: DVec2,
}
impl LayerData for ImageLayer {
fn render(&mut self, svg: &mut String, _svg_defs: &mut String, transforms: &mut Vec<DAffine2>, view_mode: ViewMode) {
let transform = self.transform(transforms, view_mode);
let inverse = transform.inverse();
if !inverse.is_finite() {
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
return;
}
let _ = writeln!(svg, r#"<g transform="matrix("#);
inverse.to_cols_array().iter().enumerate().for_each(|(i, entry)| {
let _ = svg.write_str(&(entry.to_string() + if i == 5 { "" } else { "," }));
});
let _ = svg.write_str(r#")">"#);
let svg_transform = transform
.to_cols_array()
.iter()
.enumerate()
.map(|(i, entry)| entry.to_string() + if i == 5 { "" } else { "," })
.collect::<String>();
let _ = write!(
svg,
r#"<image width="{}" height="{}" transform="matrix({})" xlink:href="{}" />"#,
self.dimensions.x,
self.dimensions.y,
svg_transform,
self.blob_url.as_ref().unwrap_or(&String::new())
);
let _ = svg.write_str("</g>");
}
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
let mut path = self.bounds();
if transform.matrix2 == DMat2::ZERO {
return None;
}
path.apply_affine(glam_to_kurbo(transform));
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
Some([(x0, y0).into(), (x1, y1).into()])
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
if intersect_quad_bez_path(quad, &self.bounds(), true) {
intersections.push(path.clone());
}
}
}
impl ImageLayer {
pub fn new(mime: String, image_data: Vec<u8>) -> Self {
let blob_url = None;
let dimensions = DVec2::ONE;
Self {
mime,
image_data,
blob_url,
dimensions,
}
}
pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 {
let start = match mode {
ViewMode::Outline => 0,
_ => (transforms.len() as i32 - 1).max(0) as usize,
};
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
}
fn bounds(&self) -> BezPath {
kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(self.dimensions.x, self.dimensions.y)).to_path(0.)
}
}
+11
View File
@@ -1,5 +1,6 @@
use super::blend_mode::BlendMode;
use super::folder_layer::FolderLayer;
use super::image_layer::ImageLayer;
use super::shape_layer::ShapeLayer;
use super::style::{PathStyle, ViewMode};
use super::text_layer::TextLayer;
@@ -16,6 +17,7 @@ pub enum LayerDataType {
Folder(FolderLayer),
Shape(ShapeLayer),
Text(TextLayer),
Image(ImageLayer),
}
impl LayerDataType {
@@ -24,6 +26,7 @@ impl LayerDataType {
LayerDataType::Shape(s) => s,
LayerDataType::Folder(f) => f,
LayerDataType::Text(t) => t,
LayerDataType::Image(i) => i,
}
}
@@ -32,6 +35,7 @@ impl LayerDataType {
LayerDataType::Shape(s) => s,
LayerDataType::Folder(f) => f,
LayerDataType::Text(t) => t,
LayerDataType::Image(i) => i,
}
}
}
@@ -193,6 +197,13 @@ impl Layer {
}
}
pub fn as_image_mut(&mut self) -> Result<&mut ImageLayer, DocumentError> {
match &mut self.data {
LayerDataType::Image(img) => Ok(img),
_ => Err(DocumentError::NotAnImage),
}
}
pub fn style(&self) -> Result<&PathStyle, DocumentError> {
match &self.data {
LayerDataType::Shape(s) => Ok(&s.style),
+1
View File
@@ -1,5 +1,6 @@
pub mod blend_mode;
pub mod folder_layer;
pub mod image_layer;
pub mod layer_info;
pub mod shape_layer;
pub mod style;