mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3453 from lehni/update/canvas-upload
Canvas: each update copies the rendered frame whole instead of pixel by pixel
This commit is contained in:
Generated
+1
@@ -5022,6 +5022,7 @@ dependencies = [
|
||||
name = "vectorcraft-ui-egui"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"craft-ui",
|
||||
"egui",
|
||||
"egui_extras",
|
||||
|
||||
@@ -33,7 +33,9 @@ vectorcraft-pdf = { workspace = true }
|
||||
vectorcraft-cad = { workspace = true }
|
||||
vectorcraft-eps = { workspace = true }
|
||||
vectorcraft-format = { workspace = true }
|
||||
egui = { workspace = true }
|
||||
egui = { workspace = true, features = ["bytemuck"] }
|
||||
# With egui's `bytemuck`: copies rendered rasters into egui images whole (`canvas::color_image`).
|
||||
bytemuck = "1"
|
||||
egui_extras = { workspace = true }
|
||||
image = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -3114,9 +3114,22 @@ fn kurbo_flatten(p: &BezPath, tol: f64, f: &mut impl FnMut(PathEl)) {
|
||||
vectorcraft_geom::kurbo::flatten(p.elements().iter().copied(), tol, f);
|
||||
}
|
||||
|
||||
/// The rendered raster `img` as an egui image. Both are premultiplied RGBA8, so its bytes are copied
|
||||
/// over whole instead of pixel by pixel, which costs milliseconds for a canvas on a large screen.
|
||||
/// A raster whose bytes don't match its size gives an empty image of that size.
|
||||
pub(crate) fn color_image(img: &vectorcraft_render::Rendered) -> egui::ColorImage {
|
||||
let size = [img.width as usize, img.height as usize];
|
||||
let mut pixels = vec![egui::Color32::TRANSPARENT; size[0] * size[1]];
|
||||
let bytes: &mut [u8] = bytemuck::cast_slice_mut(&mut pixels);
|
||||
if bytes.len() == img.pixels.len() {
|
||||
bytes.copy_from_slice(&img.pixels);
|
||||
}
|
||||
egui::ColorImage::new(size, pixels)
|
||||
}
|
||||
|
||||
/// Upload the art's raster; Pixel Preview's document pixels keep hard edges when magnified.
|
||||
fn upload(app: &mut VectorcraftApp, ctx: &egui::Context, img: &vectorcraft_render::Rendered, pixel: bool) {
|
||||
let color = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
let color = color_image(img);
|
||||
let options = if pixel { egui::TextureOptions::NEAREST } else { egui::TextureOptions::LINEAR };
|
||||
match &mut app.canvas.texture {
|
||||
Some(tex) => tex.set(color, options),
|
||||
@@ -3433,6 +3446,23 @@ mod tests {
|
||||
use vectorcraft_engine::Session;
|
||||
use vectorcraft_geom::Shape as _;
|
||||
|
||||
/// A rendered raster becomes the image egui's own conversion makes of it, half-transparent
|
||||
/// edges and all; one whose bytes don't match its size becomes an empty image of that size.
|
||||
#[test]
|
||||
fn rendered_rasters_become_the_same_egui_images() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &serde_json::json!({})).unwrap();
|
||||
s.execute("shape.ellipse", &serde_json::json!({"x": 10, "y": 10, "width": 60, "height": 40})).unwrap();
|
||||
s.execute("paint.setFill", &serde_json::json!({"color": "#3366cc"})).unwrap();
|
||||
let doc = s.active().unwrap().doc.clone();
|
||||
let img = vectorcraft_render::Renderer::new().render(&doc, 97, 61, Affine::IDENTITY, &vectorcraft_render::RenderOptions::default());
|
||||
assert!(img.pixels.as_chunks::<4>().0.iter().any(|p| p[3] != 0 && p[3] != 255), "partly covered pixels");
|
||||
let egui = egui::ColorImage::from_rgba_premultiplied([97, 61], &img.pixels);
|
||||
assert_eq!(color_image(&img), egui);
|
||||
let short = vectorcraft_render::Rendered { width: 3, height: 2, pixels: vec![255; 5] };
|
||||
assert_eq!(color_image(&short), egui::ColorImage::new([3, 2], vec![egui::Color32::TRANSPARENT; 6]));
|
||||
}
|
||||
|
||||
/// With VECTORCRAFT_SYSTEM_CURSORS set the glyphs aren't drawn and the system's cursors stand in: those of
|
||||
/// the Pen and the other drawing tools are the system crosshair already, so Use Precise Cursors makes no
|
||||
/// difference to them there.
|
||||
|
||||
@@ -119,7 +119,7 @@ fn preview(ui: &egui::Ui, app: &VectorcraftApp, name: &str, r: egui::Rect) {
|
||||
let px = (r.width() * ui.ctx().pixels_per_point()).round() as u32;
|
||||
let tex = CACHE.with(|c| c.borrow().get(&key).cloned()).or_else(|| {
|
||||
let img = RENDERER.with(|rr| rr.borrow_mut().render_node_thumbnail(&st.doc, &art, px.max(16), Some([255, 255, 255, 255])))?;
|
||||
let color = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
let color = crate::canvas::color_image(&img);
|
||||
let tex = ui.ctx().load_texture(format!("graph-design-{key}"), color, egui::TextureOptions::LINEAR);
|
||||
CACHE.with(|c| {
|
||||
let mut c = c.borrow_mut();
|
||||
|
||||
@@ -77,7 +77,7 @@ pub(crate) fn paint_thumb(ui: &Ui, st: &DocState, a: &ExportAsset, r: Rect) {
|
||||
rr.borrow_mut().render_node_thumbnail_with(&st.doc, &group, px, None, &opts)
|
||||
}
|
||||
})?;
|
||||
let color = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
let color = crate::canvas::color_image(&img);
|
||||
let tex = ui.ctx().load_texture(format!("asset-thumb-{}", a.id), color, egui::TextureOptions::LINEAR);
|
||||
CACHE.with(|c| {
|
||||
let mut c = c.borrow_mut();
|
||||
|
||||
@@ -349,7 +349,7 @@ fn render(
|
||||
}
|
||||
}
|
||||
}
|
||||
egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels)
|
||||
crate::canvas::color_image(&img)
|
||||
}
|
||||
|
||||
pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
|
||||
@@ -171,7 +171,7 @@ fn render(app: &VectorcraftApp, paints: Paints, g: &GraphicStyle, preview: Previ
|
||||
let layer = doc.layers[0].id;
|
||||
doc.insert(Some(layer), 0, app.session.styled(&shape, g)).ok()?;
|
||||
let img = RENDERER.with(|r| r.borrow_mut().render_region(&doc, vectorcraft_geom::Rect::new(0.0, 0.0, SIDE, SIDE), px as f64 / SIDE, true));
|
||||
Some(egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels))
|
||||
Some(crate::canvas::color_image(&img))
|
||||
}
|
||||
|
||||
/// The thumbnail of style `g` (`look`: its [`look_hash`]) at `size` points.
|
||||
@@ -239,7 +239,7 @@ fn object_preview(app: &VectorcraftApp, ctx: &egui::Context, g: &GraphicStyle, l
|
||||
}
|
||||
let styled = app.session.styled(n, g);
|
||||
let img = RENDERER.with(|r| r.borrow_mut().render_node_thumbnail(&st.doc, &styled, px, Some([255; 4])))?;
|
||||
let color = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
let color = crate::canvas::color_image(&img);
|
||||
let tex = ctx.load_texture("gs-large-object", color, egui::TextureOptions::LINEAR);
|
||||
ctx.data_mut(|d| d.insert_temp(slot, (key, tex.clone())));
|
||||
Some(tex)
|
||||
|
||||
@@ -1408,7 +1408,7 @@ fn small_node(n: &Node, limit: usize) -> bool {
|
||||
impl Thumbs {
|
||||
/// Keep `img` (None: nothing drawn) as the thumbnail of row `key` showing `node`.
|
||||
fn upload(&mut self, ctx: &egui::Context, key: (u64, NodeId), node: Weak<Node>, px: u32, img: Option<&vectorcraft_render::Rendered>, ms: f64) {
|
||||
let color = img.map(|img| egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels));
|
||||
let color = img.map(crate::canvas::color_image);
|
||||
let used = ctx.cumulative_pass_nr();
|
||||
let name = format!("layer-thumb-{}", key.1.0);
|
||||
match self.entries.get_mut(&key) {
|
||||
|
||||
@@ -54,7 +54,7 @@ thread_local! {
|
||||
impl Cache {
|
||||
/// Upload image `r` showing `key` over `region`.
|
||||
fn upload(&mut self, ctx: &egui::Context, key: ImageKey, region: DRect, r: &vectorcraft_render::Rendered, ms: f64) {
|
||||
let img = egui::ColorImage::from_rgba_premultiplied([r.width as usize, r.height as usize], &r.pixels);
|
||||
let img = crate::canvas::color_image(r);
|
||||
match &mut self.tex {
|
||||
Some(tx) => tx.set(img, egui::TextureOptions::LINEAR),
|
||||
None => self.tex = Some(ctx.load_texture("navigator", img, egui::TextureOptions::LINEAR)),
|
||||
|
||||
@@ -194,7 +194,7 @@ fn pattern_thumb(app: &VectorcraftApp, ui: &Ui, r: Rect, paint: &Paint) {
|
||||
);
|
||||
let tex = CACHE.with(|c| c.borrow().get(&key).cloned()).unwrap_or_else(|| {
|
||||
let tex = vectorcraft_render::render_pattern_swatch(&st.doc, pattern, px).map(|img| {
|
||||
let color = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
let color = crate::canvas::color_image(&img);
|
||||
ui.ctx().load_texture(format!("pattern-swatch-{pattern}-{px}"), color, egui::TextureOptions::LINEAR)
|
||||
});
|
||||
CACHE.with(|c| {
|
||||
|
||||
@@ -38,7 +38,7 @@ fn thumb(ui: &Ui, doc: &Document, name: &str, size: f32) -> Option<egui::Texture
|
||||
let l = d.layers[0].id;
|
||||
d.insert(Some(l), 0, Node::new(id, NodeKind::SymbolInstance { symbol: name.into(), xf })).ok()?;
|
||||
let img = RENDERER.with(|r| r.borrow_mut().render_thumbnail(&d, id, px))?;
|
||||
let color = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
let color = crate::canvas::color_image(&img);
|
||||
let tex = ui.ctx().load_texture(format!("sym-{name}-{px}"), color, egui::TextureOptions::LINEAR);
|
||||
CACHE.with(|c| {
|
||||
let mut c = c.borrow_mut();
|
||||
|
||||
@@ -263,7 +263,7 @@ fn node_thumb(app: &VectorcraftApp, ui: &Ui, slot: &str, n: &vectorcraft_doc::No
|
||||
let tex = match cached {
|
||||
Some((k, rev, tex)) if k == key && (rev == st.revision || busy) => Some(tex),
|
||||
_ => RENDERER.with(|rr| rr.borrow_mut().render_node_thumbnail_with(&st.doc, n, px, bg, &super::layers::thumb_options())).map(|img| {
|
||||
let color = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
let color = crate::canvas::color_image(&img);
|
||||
let tex = ui.ctx().load_texture(slot, color, egui::TextureOptions::LINEAR);
|
||||
ui.ctx().data_mut(|d| d.insert_temp(slot_id, (key, st.revision, tex.clone())));
|
||||
tex
|
||||
|
||||
@@ -2242,7 +2242,7 @@ pub fn doc_preview(ui: &Ui, key: &str, size: Vec2, build: impl FnOnce(f64, f64)
|
||||
}
|
||||
let doc = build(w, h)?;
|
||||
let img = RENDERER.with(|r| r.borrow_mut().render_region(&doc, vectorcraft_geom::Rect::new(0.0, 0.0, w, h), ppp, false));
|
||||
let color = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
let color = crate::canvas::color_image(&img);
|
||||
let tex = ui.ctx().load_texture(format!("preview-{key}"), color, egui::TextureOptions::LINEAR);
|
||||
CACHE.with(|c| {
|
||||
let mut c = c.borrow_mut();
|
||||
@@ -2469,7 +2469,7 @@ pub fn region_texture(
|
||||
) -> egui::TextureHandle {
|
||||
let scale = px / region.width().max(region.height()).max(1.0);
|
||||
let img = renderer.render_region(doc, region, scale, true);
|
||||
let color = egui::ColorImage::from_rgba_premultiplied([img.width as usize, img.height as usize], &img.pixels);
|
||||
let color = crate::canvas::color_image(&img);
|
||||
ctx.load_texture(name, color, egui::TextureOptions::LINEAR)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user