feat: raster image render pipeline (IMAGE command + GPU textured quad)

- Add IMAGE / IMAGEATTACH / IM command with async file-picker (rfd)
- src/scene/image_model.rs: ImageModel with RGBA pixel data + quad corners
- src/scene/pipeline/image_gpu.rs: ImageGpu with wgpu texture, sampler, opacity uniform
- src/scene/pipeline/mod.rs: image render pipeline (pass 2, between hatches and meshes)
- src/scene/render.rs: include images in Primitive, call upload_images in prepare
- src/scene/mod.rs: images HashMap, populate_images_from_document, cache on add_entity
- src/modules/home/draw/raster_image.rs: ImageCommand (two-click: origin + width, aspect-correct preview)
- src/io/mod.rs: pick_image_file() async helper (file dialog + dimension decode)
- app: ImagePick / ImagePickResult messages, populate on file open and undo/redo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-04-06 23:19:08 +03:00
commit 4312b6c3a5
39 changed files with 702 additions and 15 deletions

View file

@ -12,7 +12,7 @@ use iced::{Event, Rectangle, Size};
use super::pipeline::viewcube::{hover_id, VIEWCUBE_PX};
use super::pipeline::Pipeline;
use super::tessellate;
use super::{HatchModel, MeshModel, Scene, Uniforms, WireModel};
use super::{HatchModel, ImageModel, MeshModel, Scene, Uniforms, WireModel};
// ── Camera hover state (shader::Program::State) ───────────────────────────
@ -27,6 +27,7 @@ pub struct CameraState {
pub struct Primitive {
pub(super) wires: Vec<WireModel>,
pub(super) hatches: Vec<HatchModel>,
pub(super) images: Vec<ImageModel>,
pub(super) meshes: Vec<MeshModel>,
pub(super) uniforms: Uniforms,
/// Camera rotation matrix derived from the quaternion.
@ -59,6 +60,7 @@ impl<Msg: std::fmt::Debug + Clone> shader::Program<Msg> for Scene {
Primitive {
wires: all_wires,
hatches: self.synced_hatch_models(),
images: self.images.values().cloned().collect(),
meshes: self.meshes.values().cloned().collect(),
uniforms: Uniforms::new(&cam, bounds),
cam_rotation: cam.view_rotation_mat(),
@ -123,6 +125,7 @@ impl shader::Primitive for Primitive {
pipeline.viewcube.ensure_depth_texture(device, size);
pipeline.upload_uniforms(queue, &self.uniforms);
pipeline.upload_hatches(device, &self.hatches);
pipeline.upload_images(device, queue, &self.images);
pipeline.upload_meshes(device, &self.meshes);
pipeline.upload_wires(device, &self.wires);
let logical = viewport.logical_size();