mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 03:08:11 +08:00
Display images in the SVG viewport renderer via canvases instead of base64 PNGs (#2903)
* add: move images as rendered canvases to node_graph_executor
* add: added the frontend message
* fix: bytemuck stuff
* fix: canvas element breaking
* fix: width issues
* fix: remove the old message
* npm: run lint-fix
* fix
* works finally
* fix transforms
* Fix self closing tag
* fix: reuse id
* fix: have it working with repeat instance
* cargo: fmt
* fix
* Avoid "canvas" prefix to IDs
* fix
* fix: vello issue from 6111440
* fix: gpu stuff
* fix: vello bbox
* Code review
---------
Co-authored-by: hypercube <0hypercube@gmail.com>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
hypercube
Keavon Chambers
parent
45bd031a36
commit
72f1047a27
@@ -192,12 +192,25 @@
|
||||
|
||||
const placeholders = window.document.querySelectorAll("[data-viewport] [data-canvas-placeholder]");
|
||||
// Replace the placeholders with the actual canvas elements
|
||||
placeholders.forEach((placeholder) => {
|
||||
Array.from(placeholders).forEach((placeholder) => {
|
||||
const canvasName = placeholder.getAttribute("data-canvas-placeholder");
|
||||
if (!canvasName) return;
|
||||
// Get the canvas element from the global storage
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const canvas = (window as any).imageCanvases[canvasName];
|
||||
let canvas = (window as any).imageCanvases[canvasName];
|
||||
|
||||
if (canvasName !== "0" && canvas.parentElement) {
|
||||
var newCanvas = window.document.createElement("canvas");
|
||||
var context = newCanvas.getContext("2d");
|
||||
|
||||
newCanvas.width = canvas.width;
|
||||
newCanvas.height = canvas.height;
|
||||
|
||||
context?.drawImage(canvas, 0, 0);
|
||||
|
||||
canvas = newCanvas;
|
||||
}
|
||||
|
||||
placeholder.replaceWith(canvas);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,13 +16,27 @@ use editor::messages::portfolio::utility_types::Platform;
|
||||
use editor::messages::prelude::*;
|
||||
use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::raster::color::Color;
|
||||
use js_sys::{Object, Reflect};
|
||||
use serde::Serialize;
|
||||
use serde_wasm_bindgen::{self, from_value};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData, window};
|
||||
|
||||
static IMAGE_DATA_HASH: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
fn calculate_hash<T: std::hash::Hash>(t: &T) -> u64 {
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::Hasher;
|
||||
let mut hasher = DefaultHasher::new();
|
||||
t.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
/// Set the random seed used by the editor by calling this from JS upon initialization.
|
||||
/// This is necessary because WASM doesn't have a random number generator.
|
||||
@@ -37,6 +51,75 @@ pub fn wasm_memory() -> JsValue {
|
||||
wasm_bindgen::memory()
|
||||
}
|
||||
|
||||
fn render_image_data_to_canvases(image_data: &[(u64, Image<Color>)]) {
|
||||
let window = match window() {
|
||||
Some(window) => window,
|
||||
None => {
|
||||
error!("Cannot render canvas: window object not found");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let document = window.document().expect("window should have a document");
|
||||
let window_obj = Object::from(window);
|
||||
let image_canvases_key = JsValue::from_str("imageCanvases");
|
||||
|
||||
let canvases_obj = match Reflect::get(&window_obj, &image_canvases_key) {
|
||||
Ok(obj) if !obj.is_undefined() && !obj.is_null() => obj,
|
||||
_ => {
|
||||
let new_obj = Object::new();
|
||||
if Reflect::set(&window_obj, &image_canvases_key, &new_obj).is_err() {
|
||||
error!("Failed to create and set imageCanvases object on window");
|
||||
return;
|
||||
}
|
||||
new_obj.into()
|
||||
}
|
||||
};
|
||||
let canvases_obj = Object::from(canvases_obj);
|
||||
|
||||
for (placeholder_id, image) in image_data.iter() {
|
||||
let canvas_name = placeholder_id.to_string();
|
||||
let js_key = JsValue::from_str(&canvas_name);
|
||||
|
||||
if Reflect::has(&canvases_obj, &js_key).unwrap_or(false) || image.width == 0 || image.height == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let canvas: HtmlCanvasElement = document
|
||||
.create_element("canvas")
|
||||
.expect("Failed to create canvas element")
|
||||
.dyn_into::<HtmlCanvasElement>()
|
||||
.expect("Failed to cast element to HtmlCanvasElement");
|
||||
|
||||
canvas.set_width(image.width);
|
||||
canvas.set_height(image.height);
|
||||
|
||||
let context: CanvasRenderingContext2d = canvas
|
||||
.get_context("2d")
|
||||
.expect("Failed to get 2d context")
|
||||
.expect("2d context was not found")
|
||||
.dyn_into::<CanvasRenderingContext2d>()
|
||||
.expect("Failed to cast context to CanvasRenderingContext2d");
|
||||
let u8_data: Vec<u8> = image.data.iter().flat_map(|color| color.to_rgba8_srgb()).collect();
|
||||
let clamped_u8_data = wasm_bindgen::Clamped(&u8_data[..]);
|
||||
match ImageData::new_with_u8_clamped_array_and_sh(clamped_u8_data, image.width, image.height) {
|
||||
Ok(image_data_obj) => {
|
||||
if context.put_image_data(&image_data_obj, 0., 0.).is_err() {
|
||||
error!("Failed to put image data on canvas for id: {placeholder_id}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to create ImageData for id: {placeholder_id}: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
let js_value = JsValue::from(canvas);
|
||||
|
||||
if Reflect::set(&canvases_obj, &js_key, &js_value).is_err() {
|
||||
error!("Failed to set canvas '{canvas_name}' on imageCanvases object");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
/// This struct is, via wasm-bindgen, used by JS to interact with the editor backend. It does this by calling functions, which are `impl`ed
|
||||
@@ -88,6 +171,17 @@ impl EditorHandle {
|
||||
|
||||
// Sends a FrontendMessage to JavaScript
|
||||
fn send_frontend_message_to_js(&self, mut message: FrontendMessage) {
|
||||
if let FrontendMessage::UpdateImageData { ref image_data } = message {
|
||||
let new_hash = calculate_hash(image_data);
|
||||
let prev_hash = IMAGE_DATA_HASH.load(Ordering::Relaxed);
|
||||
|
||||
if new_hash != prev_hash {
|
||||
render_image_data_to_canvases(image_data.as_slice());
|
||||
IMAGE_DATA_HASH.store(new_hash, Ordering::Relaxed);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let FrontendMessage::UpdateDocumentLayerStructure { data_buffer } = message {
|
||||
message = FrontendMessage::UpdateDocumentLayerStructureJs { data_buffer: data_buffer.into() };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user