diff --git a/Cargo.lock b/Cargo.lock index f3878680c1..0a07005a0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2698,12 +2698,23 @@ dependencies = [ "byteorder-lite", "color_quant", "gif", + "image-webp", "num-traits", "png 0.17.16", "zune-core", "zune-jpeg", ] +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + [[package]] name = "imagesize" version = "0.14.0" @@ -4256,6 +4267,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.37.5" diff --git a/Cargo.toml b/Cargo.toml index 0297028792..0c65285e0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -180,6 +180,7 @@ image = { version = "0.25", default-features = false, features = [ "jpeg", "bmp", "gif", + "webp", ] } pretty_assertions = "1.4" fern = { version = "0.7", features = ["colored"] } diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 0ad37b4dfb..b0bbc4b918 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -467,7 +467,8 @@ impl MessageHandler> for // After import, `layer_node` is set to the root group. Apply the placement transform to it // (skipped automatically when identity, so file-open with content at origin creates no Transform node). - modify_inputs.transform_set(placement_transform, TransformIn::Local, false); + modify_inputs.transform_set(placement_transform, TransformIn::Local, true); + responses.add(NodeGraphMessage::RunDocumentGraph); } } } @@ -617,8 +618,8 @@ fn import_usvg_node( usvg::Node::Path(path) => { import_usvg_path(modify_inputs, node, path, layer, graphite_gradient_stops); } - usvg::Node::Image(_image) => { - warn!("Skip image"); + usvg::Node::Image(image) => { + import_usvg_image(modify_inputs, node, image, layer); } usvg::Node::Text(text) => { let font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_std::consts::DEFAULT_FONT_STYLE.to_string()); @@ -668,8 +669,8 @@ fn import_usvg_node_inner( import_usvg_path(modify_inputs, node, path, layer, graphite_gradient_stops); 0 } - usvg::Node::Image(_image) => { - warn!("Skip image"); + usvg::Node::Image(image) => { + import_usvg_image(modify_inputs, node, image, layer); 0 } usvg::Node::Text(text) => { @@ -865,3 +866,35 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, b } }); } + +fn import_usvg_image(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node, image: &usvg::Image, layer: LayerNodeIdentifier) { + let image_data = match image.kind() { + usvg::ImageKind::JPEG(data) => data.as_slice(), + usvg::ImageKind::PNG(data) => data.as_slice(), + usvg::ImageKind::GIF(data) => data.as_slice(), + usvg::ImageKind::WEBP(data) => data.as_slice(), + _ => { + log::warn!("Unsupported SVG image format"); + return; + } + }; + + let decoded_image = match ::image::load_from_memory(image_data) { + Ok(img) => img, + Err(e) => { + log::warn!("Failed to decode SVG image data: {:?}", e); + return; + } + }; + let width = decoded_image.width(); + let height = decoded_image.height(); + let transform_node_id = modify_inputs.insert_encoded_image_data(image_data.into(), layer); + + let node_transform = usvg_transform(node.abs_transform()); + let pixel_size = DVec2::new(width as f64, height as f64); + let final_transform = node_transform * DAffine2::from_scale(pixel_size); + + if final_transform != DAffine2::IDENTITY { + transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, final_transform); + } +} diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 84774cf0f2..b523795a8e 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -300,28 +300,33 @@ impl<'a> ModifyInputsContext<'a> { self.network_interface.move_node_to_chain_start(&color_value_id, layer, &[], self.import); } - pub fn insert_image_data(&mut self, image: Image, layer: LayerNodeIdentifier) { - let transform = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) - .expect("Transform node does not exist") - .default_node_template(); + pub fn insert_image_data(&mut self, image: Image, layer: LayerNodeIdentifier) -> NodeId { + self.insert_encoded_image_data(image.to_png().into(), layer) + } + pub fn insert_encoded_image_data(&mut self, data: std::sync::Arc<[u8]>, layer: LayerNodeIdentifier) -> NodeId { let resource_id = ResourceId::new(); - self.responses.add(ResourceMessage::StoreEmbedded { - resource_id, - data: image.to_png().into(), - }); + self.responses.add_front(ResourceMessage::StoreEmbedded { resource_id, data }); + self.insert_image_resource(resource_id, layer) + } + fn insert_image_resource(&mut self, resource_id: ResourceId, layer: LayerNodeIdentifier) -> NodeId { + let transform = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) + .expect("Transform node does not exist") + .default_node_template(); let image_node = resolve_proto_node_type(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER) .expect("Image node does not exist") .node_template_input_override([Some(NodeInput::value(TaggedValue::Resource(resource_id), false))]); - let image_node_id = NodeId::new(); - self.network_interface.insert_node(image_node_id, image_node, &[]); - self.network_interface.move_node_to_chain_start(&image_node_id, layer, &[], self.import); + let image_id = NodeId::new(); + self.network_interface.insert_node(image_id, image_node, &[]); + self.network_interface.move_node_to_chain_start(&image_id, layer, &[], self.import); let transform_id = NodeId::new(); self.network_interface.insert_node(transform_id, transform, &[]); self.network_interface.move_node_to_chain_start(&transform_id, layer, &[], self.import); + + transform_id } fn get_output_layer(&self) -> Option { diff --git a/editor/src/messages/portfolio/document/resource/resource_message_handler.rs b/editor/src/messages/portfolio/document/resource/resource_message_handler.rs index 7a45060149..1677161553 100644 --- a/editor/src/messages/portfolio/document/resource/resource_message_handler.rs +++ b/editor/src/messages/portfolio/document/resource/resource_message_handler.rs @@ -32,7 +32,7 @@ impl MessageHandler> for ResourceMes let hash = ResourceHash::from(data.as_ref()); self.registry.push_source_back(&resource_id, DataSource::Embedded); self.registry.resolve(&resource_id, hash); - responses.add(ResourceStorageMessage::Store { data }); + responses.add_front(ResourceStorageMessage::Store { data }); } ResourceMessage::AddFont { resource_id, font } => { let style = fonts.font_catalog.find_font_style_in_catalog(&font); diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index ae089b9041..4d0e860c4f 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -416,7 +416,7 @@ impl NodeGraphExecutor { graphene_std::raster::Image { width: image.width, height: image.height, - data: image.data.iter().map(|&c| SRGBA8::from(c)).collect(), + data: image.data.iter().map(|&c| SRGBA8::from(c.to_unassociated_alpha())).collect(), base64_string: image.base64_string, }, )