bevy/examples/2d/cpu_draw.rs

138 lines
5 KiB
Rust
Raw Normal View History

Add `Image` methods for easy access to a pixel's color (#10392) # Objective If you want to draw / generate images from the CPU, such as: - to create procedurally-generated assets - for games whose artstyle is best implemented by poking pixels directly from the CPU, instead of using shaders It is currently very unergonomic to do in Bevy, because you have to deal with the raw bytes inside `image.data`, take care of the pixel format, etc. ## Solution This PR adds some helper methods to `Image` for pixel manipulation. These methods allow you to use Bevy's user-friendly `Color` struct to read and write the colors of pixels, at arbitrary coordinates (specified as `UVec3` to support any texture dimension). They handle encoding/decoding to the `Image`s `TextureFormat`, incl. any sRGB conversion. While we are at it, also add methods to help with direct access to the raw bytes. It is now easy to compute the offset where the bytes of a specific pixel coordinate are found, or to just get a Rust slice to access them. Caveat: `Color` roundtrips are obviously going to be lossy for non-float `TextureFormat`s. Using `set_color_at` followed by `get_color_at` will return a different value, due to the data conversions involved (such as `f32` -> `u8` -> `f32` for the common `Rgba8UnormSrgb` texture format). Be careful when comparing colors (such as checking for a color you wrote before)! Also adding a new example: `cpu_draw` (under `2d`), to showcase these new APIs. --- ## Changelog ### Added - `Image` APIs for easy access to the colors of specific pixels. --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: ltdk <usr@ltdk.xyz>
2024-10-07 14:38:41 +00:00
//! Example of how to draw to a texture from the CPU.
//!
//! You can set the values of individual pixels to whatever you want.
//! Bevy provides user-friendly APIs that work with [`Color`](bevy::color::Color)
//! values and automatically perform any necessary conversions and encoding
//! into the texture's native pixel format.
use bevy::color::{color_difference::EuclideanDistance, palettes::css};
use bevy::prelude::*;
use bevy::render::{
render_asset::RenderAssetUsages,
render_resource::{Extent3d, TextureDimension, TextureFormat},
};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
Add `Image` methods for easy access to a pixel's color (#10392) # Objective If you want to draw / generate images from the CPU, such as: - to create procedurally-generated assets - for games whose artstyle is best implemented by poking pixels directly from the CPU, instead of using shaders It is currently very unergonomic to do in Bevy, because you have to deal with the raw bytes inside `image.data`, take care of the pixel format, etc. ## Solution This PR adds some helper methods to `Image` for pixel manipulation. These methods allow you to use Bevy's user-friendly `Color` struct to read and write the colors of pixels, at arbitrary coordinates (specified as `UVec3` to support any texture dimension). They handle encoding/decoding to the `Image`s `TextureFormat`, incl. any sRGB conversion. While we are at it, also add methods to help with direct access to the raw bytes. It is now easy to compute the offset where the bytes of a specific pixel coordinate are found, or to just get a Rust slice to access them. Caveat: `Color` roundtrips are obviously going to be lossy for non-float `TextureFormat`s. Using `set_color_at` followed by `get_color_at` will return a different value, due to the data conversions involved (such as `f32` -> `u8` -> `f32` for the common `Rgba8UnormSrgb` texture format). Be careful when comparing colors (such as checking for a color you wrote before)! Also adding a new example: `cpu_draw` (under `2d`), to showcase these new APIs. --- ## Changelog ### Added - `Image` APIs for easy access to the colors of specific pixels. --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: ltdk <usr@ltdk.xyz>
2024-10-07 14:38:41 +00:00
const IMAGE_WIDTH: u32 = 256;
const IMAGE_HEIGHT: u32 = 256;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// In this example, we will use a fixed timestep to draw a pattern on the screen
// one pixel at a time, so the pattern will gradually emerge over time, and
// the speed at which it appears is not tied to the framerate.
// Let's make the fixed update very fast, so it doesn't take too long. :)
.insert_resource(Time::<Fixed>::from_hz(1024.0))
.add_systems(Startup, setup)
.add_systems(FixedUpdate, draw)
.run();
}
/// Store the image handle that we will draw to, here.
#[derive(Resource)]
struct MyProcGenImage(Handle<Image>);
#[derive(Resource)]
struct SeededRng(ChaCha8Rng);
Add `Image` methods for easy access to a pixel's color (#10392) # Objective If you want to draw / generate images from the CPU, such as: - to create procedurally-generated assets - for games whose artstyle is best implemented by poking pixels directly from the CPU, instead of using shaders It is currently very unergonomic to do in Bevy, because you have to deal with the raw bytes inside `image.data`, take care of the pixel format, etc. ## Solution This PR adds some helper methods to `Image` for pixel manipulation. These methods allow you to use Bevy's user-friendly `Color` struct to read and write the colors of pixels, at arbitrary coordinates (specified as `UVec3` to support any texture dimension). They handle encoding/decoding to the `Image`s `TextureFormat`, incl. any sRGB conversion. While we are at it, also add methods to help with direct access to the raw bytes. It is now easy to compute the offset where the bytes of a specific pixel coordinate are found, or to just get a Rust slice to access them. Caveat: `Color` roundtrips are obviously going to be lossy for non-float `TextureFormat`s. Using `set_color_at` followed by `get_color_at` will return a different value, due to the data conversions involved (such as `f32` -> `u8` -> `f32` for the common `Rgba8UnormSrgb` texture format). Be careful when comparing colors (such as checking for a color you wrote before)! Also adding a new example: `cpu_draw` (under `2d`), to showcase these new APIs. --- ## Changelog ### Added - `Image` APIs for easy access to the colors of specific pixels. --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: ltdk <usr@ltdk.xyz>
2024-10-07 14:38:41 +00:00
fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
// spawn a camera
commands.spawn(Camera2d);
// create an image that we are going to draw into
let mut image = Image::new_fill(
// 2D image of size 256x256
Extent3d {
width: IMAGE_WIDTH,
height: IMAGE_HEIGHT,
depth_or_array_layers: 1,
},
TextureDimension::D2,
// Initialize it with a beige color
&(css::BEIGE.to_u8_array()),
// Use the same encoding as the color we set
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
);
// to make it extra fancy, we can set the Alpha of each pixel
// so that it fades out in a circular fashion
for y in 0..IMAGE_HEIGHT {
for x in 0..IMAGE_WIDTH {
let center = Vec2::new(IMAGE_WIDTH as f32 / 2.0, IMAGE_HEIGHT as f32 / 2.0);
let max_radius = IMAGE_HEIGHT.min(IMAGE_WIDTH) as f32 / 2.0;
let r = Vec2::new(x as f32, y as f32).distance(center);
let a = 1.0 - (r / max_radius).clamp(0.0, 1.0);
// here we will set the A value by accessing the raw data bytes
// (it is the 4th byte of each pixel, as per our `TextureFormat`)
// find our pixel by its coordinates
let pixel_bytes = image.pixel_bytes_mut(UVec3::new(x, y, 0)).unwrap();
// convert our f32 to u8
pixel_bytes[3] = (a * u8::MAX as f32) as u8;
}
}
// add it to Bevy's assets, so it can be used for rendering
// this will give us a handle we can use
// (to display it in a sprite, or as part of UI, etc.)
let handle = images.add(image);
// create a sprite entity using our image
commands.spawn(Sprite::from_image(handle.clone()));
Add `Image` methods for easy access to a pixel's color (#10392) # Objective If you want to draw / generate images from the CPU, such as: - to create procedurally-generated assets - for games whose artstyle is best implemented by poking pixels directly from the CPU, instead of using shaders It is currently very unergonomic to do in Bevy, because you have to deal with the raw bytes inside `image.data`, take care of the pixel format, etc. ## Solution This PR adds some helper methods to `Image` for pixel manipulation. These methods allow you to use Bevy's user-friendly `Color` struct to read and write the colors of pixels, at arbitrary coordinates (specified as `UVec3` to support any texture dimension). They handle encoding/decoding to the `Image`s `TextureFormat`, incl. any sRGB conversion. While we are at it, also add methods to help with direct access to the raw bytes. It is now easy to compute the offset where the bytes of a specific pixel coordinate are found, or to just get a Rust slice to access them. Caveat: `Color` roundtrips are obviously going to be lossy for non-float `TextureFormat`s. Using `set_color_at` followed by `get_color_at` will return a different value, due to the data conversions involved (such as `f32` -> `u8` -> `f32` for the common `Rgba8UnormSrgb` texture format). Be careful when comparing colors (such as checking for a color you wrote before)! Also adding a new example: `cpu_draw` (under `2d`), to showcase these new APIs. --- ## Changelog ### Added - `Image` APIs for easy access to the colors of specific pixels. --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: ltdk <usr@ltdk.xyz>
2024-10-07 14:38:41 +00:00
commands.insert_resource(MyProcGenImage(handle));
// We're seeding the PRNG here to make this example deterministic for testing purposes.
// This isn't strictly required in practical use unless you need your app to be deterministic.
let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
commands.insert_resource(SeededRng(seeded_rng));
Add `Image` methods for easy access to a pixel's color (#10392) # Objective If you want to draw / generate images from the CPU, such as: - to create procedurally-generated assets - for games whose artstyle is best implemented by poking pixels directly from the CPU, instead of using shaders It is currently very unergonomic to do in Bevy, because you have to deal with the raw bytes inside `image.data`, take care of the pixel format, etc. ## Solution This PR adds some helper methods to `Image` for pixel manipulation. These methods allow you to use Bevy's user-friendly `Color` struct to read and write the colors of pixels, at arbitrary coordinates (specified as `UVec3` to support any texture dimension). They handle encoding/decoding to the `Image`s `TextureFormat`, incl. any sRGB conversion. While we are at it, also add methods to help with direct access to the raw bytes. It is now easy to compute the offset where the bytes of a specific pixel coordinate are found, or to just get a Rust slice to access them. Caveat: `Color` roundtrips are obviously going to be lossy for non-float `TextureFormat`s. Using `set_color_at` followed by `get_color_at` will return a different value, due to the data conversions involved (such as `f32` -> `u8` -> `f32` for the common `Rgba8UnormSrgb` texture format). Be careful when comparing colors (such as checking for a color you wrote before)! Also adding a new example: `cpu_draw` (under `2d`), to showcase these new APIs. --- ## Changelog ### Added - `Image` APIs for easy access to the colors of specific pixels. --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: ltdk <usr@ltdk.xyz>
2024-10-07 14:38:41 +00:00
}
/// Every fixed update tick, draw one more pixel to make a spiral pattern
fn draw(
my_handle: Res<MyProcGenImage>,
mut images: ResMut<Assets<Image>>,
// used to keep track of where we are
mut i: Local<u32>,
mut draw_color: Local<Color>,
mut seeded_rng: ResMut<SeededRng>,
Add `Image` methods for easy access to a pixel's color (#10392) # Objective If you want to draw / generate images from the CPU, such as: - to create procedurally-generated assets - for games whose artstyle is best implemented by poking pixels directly from the CPU, instead of using shaders It is currently very unergonomic to do in Bevy, because you have to deal with the raw bytes inside `image.data`, take care of the pixel format, etc. ## Solution This PR adds some helper methods to `Image` for pixel manipulation. These methods allow you to use Bevy's user-friendly `Color` struct to read and write the colors of pixels, at arbitrary coordinates (specified as `UVec3` to support any texture dimension). They handle encoding/decoding to the `Image`s `TextureFormat`, incl. any sRGB conversion. While we are at it, also add methods to help with direct access to the raw bytes. It is now easy to compute the offset where the bytes of a specific pixel coordinate are found, or to just get a Rust slice to access them. Caveat: `Color` roundtrips are obviously going to be lossy for non-float `TextureFormat`s. Using `set_color_at` followed by `get_color_at` will return a different value, due to the data conversions involved (such as `f32` -> `u8` -> `f32` for the common `Rgba8UnormSrgb` texture format). Be careful when comparing colors (such as checking for a color you wrote before)! Also adding a new example: `cpu_draw` (under `2d`), to showcase these new APIs. --- ## Changelog ### Added - `Image` APIs for easy access to the colors of specific pixels. --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: ltdk <usr@ltdk.xyz>
2024-10-07 14:38:41 +00:00
) {
if *i == 0 {
// Generate a random color on first run.
*draw_color = Color::linear_rgb(seeded_rng.0.gen(), seeded_rng.0.gen(), seeded_rng.0.gen());
Add `Image` methods for easy access to a pixel's color (#10392) # Objective If you want to draw / generate images from the CPU, such as: - to create procedurally-generated assets - for games whose artstyle is best implemented by poking pixels directly from the CPU, instead of using shaders It is currently very unergonomic to do in Bevy, because you have to deal with the raw bytes inside `image.data`, take care of the pixel format, etc. ## Solution This PR adds some helper methods to `Image` for pixel manipulation. These methods allow you to use Bevy's user-friendly `Color` struct to read and write the colors of pixels, at arbitrary coordinates (specified as `UVec3` to support any texture dimension). They handle encoding/decoding to the `Image`s `TextureFormat`, incl. any sRGB conversion. While we are at it, also add methods to help with direct access to the raw bytes. It is now easy to compute the offset where the bytes of a specific pixel coordinate are found, or to just get a Rust slice to access them. Caveat: `Color` roundtrips are obviously going to be lossy for non-float `TextureFormat`s. Using `set_color_at` followed by `get_color_at` will return a different value, due to the data conversions involved (such as `f32` -> `u8` -> `f32` for the common `Rgba8UnormSrgb` texture format). Be careful when comparing colors (such as checking for a color you wrote before)! Also adding a new example: `cpu_draw` (under `2d`), to showcase these new APIs. --- ## Changelog ### Added - `Image` APIs for easy access to the colors of specific pixels. --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: ltdk <usr@ltdk.xyz>
2024-10-07 14:38:41 +00:00
}
// Get the image from Bevy's asset storage.
let image = images.get_mut(&my_handle.0).expect("Image not found");
// Compute the position of the pixel to draw.
let center = Vec2::new(IMAGE_WIDTH as f32 / 2.0, IMAGE_HEIGHT as f32 / 2.0);
let max_radius = IMAGE_HEIGHT.min(IMAGE_WIDTH) as f32 / 2.0;
let rot_speed = 0.0123;
let period = 0.12345;
let r = ops::sin(*i as f32 * period) * max_radius;
let xy = Vec2::from_angle(*i as f32 * rot_speed) * r + center;
let (x, y) = (xy.x as u32, xy.y as u32);
// Get the old color of that pixel.
let old_color = image.get_color_at(x, y).unwrap();
// If the old color is our current color, change our drawing color.
let tolerance = 1.0 / 255.0;
if old_color.distance(&draw_color) <= tolerance {
*draw_color = Color::linear_rgb(seeded_rng.0.gen(), seeded_rng.0.gen(), seeded_rng.0.gen());
Add `Image` methods for easy access to a pixel's color (#10392) # Objective If you want to draw / generate images from the CPU, such as: - to create procedurally-generated assets - for games whose artstyle is best implemented by poking pixels directly from the CPU, instead of using shaders It is currently very unergonomic to do in Bevy, because you have to deal with the raw bytes inside `image.data`, take care of the pixel format, etc. ## Solution This PR adds some helper methods to `Image` for pixel manipulation. These methods allow you to use Bevy's user-friendly `Color` struct to read and write the colors of pixels, at arbitrary coordinates (specified as `UVec3` to support any texture dimension). They handle encoding/decoding to the `Image`s `TextureFormat`, incl. any sRGB conversion. While we are at it, also add methods to help with direct access to the raw bytes. It is now easy to compute the offset where the bytes of a specific pixel coordinate are found, or to just get a Rust slice to access them. Caveat: `Color` roundtrips are obviously going to be lossy for non-float `TextureFormat`s. Using `set_color_at` followed by `get_color_at` will return a different value, due to the data conversions involved (such as `f32` -> `u8` -> `f32` for the common `Rgba8UnormSrgb` texture format). Be careful when comparing colors (such as checking for a color you wrote before)! Also adding a new example: `cpu_draw` (under `2d`), to showcase these new APIs. --- ## Changelog ### Added - `Image` APIs for easy access to the colors of specific pixels. --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com> Co-authored-by: François <mockersf@gmail.com> Co-authored-by: ltdk <usr@ltdk.xyz>
2024-10-07 14:38:41 +00:00
}
// Set the new color, but keep old alpha value from image.
image
.set_color_at(x, y, draw_color.with_alpha(old_color.alpha()))
.unwrap();
*i += 1;
}