mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Lay groundwork for adaptive resolution system (#1395)
* Make transform node accept footprint as input and pass it along to its input use f32 instead of f64 and add default to document node definition * Add cull node * Fix types for Transform and Cull Nodes * Add render config struct * Add Render Node skeleton * Add Render Node to node_registry * Make types macro use macro hygiene * Place Render Node as output * Start making DownresNode footprint aware * Correctly calculate footprint in Transform Node * Add cropping and resizing to downres node * Fix Output node declaration * Fix image transform * Fix Vector Data rendering * Add concept of ImageRenderMode * Take base image size into account when calculating the final image size * Supply viewport transform to the node graph * Start adapting document graph to resolution agnosticism * Make document node short circuting not shift the input index * Apply clippy lints
This commit is contained in:
committed by
Keavon Chambers
parent
239ca698e5
commit
d82f133514
@@ -241,6 +241,9 @@ impl ComposeTypeErased {
|
||||
}
|
||||
|
||||
pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(), O> {
|
||||
downcast_node(n)
|
||||
}
|
||||
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
|
||||
DowncastBothNode::new(n)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use glam::{DAffine2, DVec2};
|
||||
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
|
||||
use graph_craft::proto::DynFuture;
|
||||
use graphene_core::raster::{Alpha, BlendMode, BlendNode, Image, ImageFrame, Linear, LinearChannel, Luminance, NoiseType, Pixel, RGBMut, Raster, RasterMut, RedGreenBlue, Sample};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::transform::{Footprint, Transform};
|
||||
|
||||
use crate::wasm_application_io::WasmEditorApi;
|
||||
use graphene_core::raster::bbox::{AxisAlignedBbox, Bbox};
|
||||
@@ -58,29 +58,50 @@ fn buffer_node<R: std::io::Read>(reader: R) -> Result<Vec<u8>, Error> {
|
||||
Ok(std::io::Read::bytes(reader).collect::<Result<Vec<_>, _>>()?)
|
||||
}
|
||||
|
||||
pub struct DownresNode<P> {
|
||||
_p: PhantomData<P>,
|
||||
pub struct DownresNode<ImageFrame> {
|
||||
image_frame: ImageFrame,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(DownresNode<_P>)]
|
||||
fn downres<_P: Pixel>(image_frame: ImageFrame<_P>) -> ImageFrame<_P> {
|
||||
let target_width = (image_frame.transform.transform_vector2((1., 0.).into()).length() as usize).min(image_frame.image.width as usize);
|
||||
let target_height = (image_frame.transform.transform_vector2((0., 1.).into()).length() as usize).min(image_frame.image.height as usize);
|
||||
#[node_macro::node_fn(DownresNode)]
|
||||
fn downres(footprint: Footprint, image_frame: ImageFrame<Color>) -> ImageFrame<Color> {
|
||||
// resize the image using the image crate
|
||||
let image = image_frame.image;
|
||||
let data = bytemuck::cast_vec(image.data);
|
||||
|
||||
let mut image = Image {
|
||||
width: target_width as u32,
|
||||
height: target_height as u32,
|
||||
data: Vec::with_capacity(target_width * target_height),
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
log::debug!("viewport_bounds: {viewport_bounds:?}");
|
||||
let bbox = Bbox::from_transform(image_frame.transform * DAffine2::from_scale(DVec2::new(image.width as f64, image.height as f64)));
|
||||
log::debug!("local_bounds: {bbox:?}");
|
||||
let bounds = viewport_bounds.intersect(&bbox.to_axis_aligned_bbox());
|
||||
log::debug!("intersection: {bounds:?}");
|
||||
let union = viewport_bounds.union(&bbox.to_axis_aligned_bbox());
|
||||
log::debug!("union: {union:?}");
|
||||
let size = bounds.size();
|
||||
|
||||
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal ImageFrame into image-rs data type.");
|
||||
|
||||
let dynamic_image: image::DynamicImage = image_buffer.into();
|
||||
let offset = (bounds.start - viewport_bounds.start).as_uvec2();
|
||||
let cropped = dynamic_image.crop_imm(offset.x, offset.y, size.x as u32, size.y as u32);
|
||||
|
||||
log::debug!("transform: {:?}", footprint.transform);
|
||||
log::debug!("size: {size:?}");
|
||||
let viewport_resolution_x = footprint.transform.transform_vector2(DVec2::X * size.x).length();
|
||||
let viewport_resolution_y = footprint.transform.transform_vector2(DVec2::Y * size.y).length();
|
||||
let nwidth = viewport_resolution_x as u32;
|
||||
let nheight = viewport_resolution_y as u32;
|
||||
log::debug!("x: {viewport_resolution_x}, y: {viewport_resolution_y}");
|
||||
|
||||
let resized = cropped.resize_exact(nwidth, nheight, image::imageops::Lanczos3);
|
||||
let buffer = resized.to_rgba32f();
|
||||
let buffer = buffer.into_raw();
|
||||
let vec = bytemuck::cast_vec(buffer);
|
||||
let image = Image {
|
||||
width: nwidth,
|
||||
height: nheight,
|
||||
data: vec,
|
||||
};
|
||||
|
||||
let scale_factor = DVec2::new(image_frame.image.width as f64, image_frame.image.height as f64) / DVec2::new(target_width as f64, target_height as f64);
|
||||
for y in 0..target_height {
|
||||
for x in 0..target_width {
|
||||
let pixel = image_frame.sample(DVec2::new(x as f64, y as f64) * scale_factor);
|
||||
image.data.push(pixel);
|
||||
}
|
||||
}
|
||||
|
||||
ImageFrame {
|
||||
image,
|
||||
transform: image_frame.transform,
|
||||
|
||||
@@ -2,13 +2,16 @@ use std::cell::RefCell;
|
||||
|
||||
use core::future::Future;
|
||||
use dyn_any::StaticType;
|
||||
use graphene_core::application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
||||
use graphene_core::application_io::{ApplicationError, ApplicationIo, ExportFormat, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::renderer::{GraphicElementRendered, RenderParams, SvgRender};
|
||||
use graphene_core::transform::Footprint;
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
use graphene_core::{
|
||||
raster::{color::SRGBA8, ImageFrame},
|
||||
Node,
|
||||
};
|
||||
use graphene_core::{Color, GraphicGroup};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use js_sys::{Object, Reflect};
|
||||
use std::collections::HashMap;
|
||||
@@ -280,3 +283,32 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
|
||||
};
|
||||
image
|
||||
}
|
||||
pub use graph_craft::document::value::RenderOutput;
|
||||
|
||||
pub struct RenderNode<Data, Surface> {
|
||||
data: Data,
|
||||
surface_handle: Surface,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(RenderNode)]
|
||||
async fn render_node<'a: 'input, F: Future<Output = GraphicGroup>>(
|
||||
editor: WasmEditorApi<'a>,
|
||||
data: impl Node<'input, Footprint, Output = F>,
|
||||
surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>,
|
||||
) -> RenderOutput {
|
||||
let footprint = editor.render_config.viewport;
|
||||
let data = self.data.eval(footprint).await;
|
||||
let mut render = SvgRender::new();
|
||||
let render_params = RenderParams::new(ViewMode::Normal, graphene_core::renderer::ImageRenderMode::Base64, None, false);
|
||||
let output_format = editor.render_config.export_format;
|
||||
|
||||
match output_format {
|
||||
ExportFormat::Svg => {
|
||||
data.render_svg(&mut render, &render_params);
|
||||
// TODO: reenable once we switch to full node graph
|
||||
//render.format_svg((0., 0.).into(), (1., 1.).into());
|
||||
RenderOutput::Svg(render.svg.to_string())
|
||||
}
|
||||
_ => todo!("Non svg render output"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user