Skip to content

Commit 6a63ff5

Browse files
committed
wip: image pipeline example
1 parent 8015c6d commit 6a63ff5

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed

Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ flate2 = "1.0"
432432
serde = { version = "1", features = ["derive"] }
433433
serde_json = "1"
434434
bytemuck = "1.7"
435+
image = "0.25.2"
435436
bevy_render = { path = "crates/bevy_render", version = "0.15.0-dev", default-features = false }
436437
# Needed to poll Task examples
437438
futures-lite = "2.0.1"
@@ -1591,6 +1592,17 @@ description = "Demonstrates how to wait for multiple assets to be loaded."
15911592
category = "Assets"
15921593
wasm = true
15931594

1595+
[[example]]
1596+
name = "reflect_deserialized_asset"
1597+
path = "examples/asset/reflect_deserialized_asset.rs"
1598+
doc-scrape-examples = true
1599+
1600+
[package.metadata.example.reflect_deserialized_asset]
1601+
name = "Reflect-deserialized asset"
1602+
description = "Loading an asset via reflection which refers to other assets or a type-erased value"
1603+
category = "Assets"
1604+
wasm = true
1605+
15941606
# Async Tasks
15951607
[[example]]
15961608
name = "async_compute"
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//! Let's imagine we want to make a system where we can take an image, pass it
2+
//! through a series of operations (a pipeline), and get back a new transformed
3+
//! image.
4+
//!
5+
//! We want to define the pipeline using an asset, and the operations should be
6+
//! fully dynamic - users should be able to register their own operation types,
7+
//! and the asset loader can deserialize them without any extra setup.
8+
9+
use std::fmt::Debug;
10+
11+
use bevy::{
12+
asset::{io::Reader, AssetLoader, LoadContext, RenderAssetUsages},
13+
prelude::*,
14+
reflect::TypeRegistryArc,
15+
};
16+
use image::{ColorType, DynamicImage};
17+
use thiserror::Error;
18+
19+
/// Series of [`ImageOperation`]s which may be applied to an image.
20+
#[derive(Debug, Asset)]
21+
// we can't automatically derive Reflect yet - see <https://github.com/bevyengine/bevy/pull/15532>
22+
// so we manually implement the Reflect traits below.
23+
// #[derive(Reflect)]
24+
#[derive(TypePath)]
25+
struct ImagePipeline {
26+
ops: Vec<Box<dyn ImageOperation>>,
27+
}
28+
29+
impl ImagePipeline {
30+
fn apply(&self, image: DynamicImage, image_assets: &Assets<Image>) -> DynamicImage {
31+
let mut ctx = ImageOperationContext {
32+
current: image,
33+
image_assets,
34+
};
35+
for op in &self.ops {
36+
op.apply(&mut ctx);
37+
}
38+
ctx.current
39+
}
40+
}
41+
42+
/// Applies an operation to an image in the pipeline.
43+
#[reflect_trait]
44+
trait ImageOperation: Debug + Send + Sync + Reflect {
45+
/// Applies the operation.
46+
fn apply(&self, ctx: &mut ImageOperationContext<'_>);
47+
}
48+
49+
struct ImageOperationContext<'a> {
50+
current: DynamicImage,
51+
image_assets: &'a Assets<Image>,
52+
}
53+
54+
// operation implementations
55+
56+
/// Overwrites the current image with another image asset.
57+
#[derive(Debug, Clone, Reflect)]
58+
struct Load {
59+
// Since this is a `Handle`, when we deserialize this in the asset loader,
60+
// we will also start a load for the asset that this handle points to.
61+
image: Handle<Image>,
62+
}
63+
64+
impl ImageOperation for Load {
65+
fn apply(&self, ctx: &mut ImageOperationContext<'_>) {
66+
if let Some(image) = ctx.image_assets.get(&self.image) {
67+
ctx.current = image
68+
.clone()
69+
.try_into_dynamic()
70+
.expect("image should be in a supported format");
71+
}
72+
}
73+
}
74+
75+
/// Inverts all pixels in the image.
76+
#[derive(Debug, Clone, Reflect)]
77+
struct Invert;
78+
79+
impl ImageOperation for Invert {
80+
fn apply(&self, ctx: &mut ImageOperationContext<'_>) {
81+
ctx.current.invert();
82+
}
83+
}
84+
85+
/// Applies a Gaussian blur to the image.
86+
#[derive(Debug, Clone, Reflect)]
87+
struct Blur {
88+
/// Blur intensity.
89+
sigma: f32,
90+
}
91+
92+
impl ImageOperation for Blur {
93+
fn apply(&self, ctx: &mut ImageOperationContext<'_>) {
94+
ctx.current = ctx.current.blur(self.sigma);
95+
}
96+
}
97+
98+
// asset loader
99+
100+
#[derive(Debug)]
101+
struct ImagePipelineLoader {
102+
type_registry: TypeRegistryArc,
103+
}
104+
105+
#[derive(Debug, Error)]
106+
enum ImagePipelineLoaderError {}
107+
108+
impl FromWorld for ImagePipelineLoader {
109+
fn from_world(world: &mut World) -> Self {
110+
let type_registry = world.resource::<AppTypeRegistry>();
111+
Self {
112+
type_registry: type_registry.0.clone(),
113+
}
114+
}
115+
}
116+
117+
impl AssetLoader for ImagePipelineLoader {
118+
type Asset = ImagePipeline;
119+
type Settings = ();
120+
type Error = ImagePipelineLoaderError;
121+
122+
async fn load(
123+
&self,
124+
reader: &mut dyn Reader,
125+
settings: &Self::Settings,
126+
load_context: &mut LoadContext<'_>,
127+
) -> Result<Self::Asset, Self::Error> {
128+
// let mut bytes = Vec::new();
129+
// reader.read_to_end(&mut bytes).await?;
130+
131+
// let mut ron_deserializer = ron::Deserializer::from_bytes(&bytes)?;
132+
133+
todo!()
134+
}
135+
}
136+
137+
// app logic
138+
139+
fn main() -> AppExit {
140+
App::new()
141+
.add_plugins(DefaultPlugins)
142+
.init_asset::<ImagePipeline>()
143+
.init_asset_loader::<ImagePipelineLoader>()
144+
.init_resource::<DemoImagePipeline>()
145+
.add_systems(Startup, setup)
146+
.add_systems(Update, make_demo_image)
147+
.run()
148+
}
149+
150+
#[derive(Debug, Default, Resource)]
151+
struct DemoImagePipeline(Handle<ImagePipeline>);
152+
153+
#[derive(Debug, Component)]
154+
struct DemoImage;
155+
156+
fn setup(
157+
mut commands: Commands,
158+
asset_server: Res<AssetServer>,
159+
mut demo_image_pipeline: ResMut<DemoImagePipeline>,
160+
mut _todo: ResMut<Assets<ImagePipeline>>,
161+
) {
162+
// demo_image_pipeline.0 = asset_server.load("data/demo_image_pipeline.imgpipe.ron");
163+
demo_image_pipeline.0 = _todo.add(ImagePipeline {
164+
ops: vec![
165+
Box::new(Load {
166+
image: asset_server.load("textures/Ryfjallet_cubemap.png"),
167+
}),
168+
Box::new(Invert),
169+
Box::new(Blur { sigma: 2.0 }),
170+
],
171+
});
172+
173+
// draw the demo image
174+
commands.spawn(Camera2d);
175+
commands.spawn((
176+
SpriteBundle {
177+
texture: Handle::default(),
178+
..default()
179+
},
180+
DemoImage,
181+
));
182+
}
183+
184+
fn make_demo_image(
185+
mut demo_images: Query<&mut Handle<Image>>,
186+
image_pipeline_assets: Res<Assets<ImagePipeline>>,
187+
mut image_assets: ResMut<Assets<Image>>,
188+
demo_image_pipeline: Res<DemoImagePipeline>,
189+
) {
190+
let Some(demo_image_pipeline) = image_pipeline_assets.get(&demo_image_pipeline.0) else {
191+
info!("Image pipeline not loaded yet");
192+
return;
193+
};
194+
195+
let dyn_image = DynamicImage::new(1, 1, ColorType::Rgba8);
196+
let dyn_image = demo_image_pipeline.apply(dyn_image, &image_assets);
197+
let image = Image::from_dynamic(dyn_image, true, RenderAssetUsages::RENDER_WORLD);
198+
let image_handle = image_assets.add(image);
199+
200+
for mut demo_image in &mut demo_images {
201+
*demo_image = image_handle.clone();
202+
}
203+
}

0 commit comments

Comments
 (0)