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:
Dennis Kobert
2023-09-06 12:39:21 +02:00
committed by Keavon Chambers
parent 239ca698e5
commit d82f133514
33 changed files with 836 additions and 305 deletions

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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"),
}
}