Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/2d/cpu_draw.rs
6592 views
1
//! Example of how to draw to a texture from the CPU.
2
//!
3
//! You can set the values of individual pixels to whatever you want.
4
//! Bevy provides user-friendly APIs that work with [`Color`]
5
//! values and automatically perform any necessary conversions and encoding
6
//! into the texture's native pixel format.
7
8
use bevy::asset::RenderAssetUsages;
9
use bevy::color::{color_difference::EuclideanDistance, palettes::css};
10
use bevy::prelude::*;
11
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
12
use rand::{Rng, SeedableRng};
13
use rand_chacha::ChaCha8Rng;
14
15
const IMAGE_WIDTH: u32 = 256;
16
const IMAGE_HEIGHT: u32 = 256;
17
18
fn main() {
19
App::new()
20
.add_plugins(DefaultPlugins)
21
// In this example, we will use a fixed timestep to draw a pattern on the screen
22
// one pixel at a time, so the pattern will gradually emerge over time, and
23
// the speed at which it appears is not tied to the framerate.
24
// Let's make the fixed update very fast, so it doesn't take too long. :)
25
.insert_resource(Time::<Fixed>::from_hz(1024.0))
26
.add_systems(Startup, setup)
27
.add_systems(FixedUpdate, draw)
28
.run();
29
}
30
31
/// Store the image handle that we will draw to, here.
32
#[derive(Resource)]
33
struct MyProcGenImage(Handle<Image>);
34
35
#[derive(Resource)]
36
struct SeededRng(ChaCha8Rng);
37
38
fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
39
commands.spawn(Camera2d);
40
41
// Create an image that we are going to draw into
42
let mut image = Image::new_fill(
43
// 2D image of size 256x256
44
Extent3d {
45
width: IMAGE_WIDTH,
46
height: IMAGE_HEIGHT,
47
depth_or_array_layers: 1,
48
},
49
TextureDimension::D2,
50
// Initialize it with a beige color
51
&(css::BEIGE.to_u8_array()),
52
// Use the same encoding as the color we set
53
TextureFormat::Rgba8UnormSrgb,
54
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
55
);
56
57
// To make it extra fancy, we can set the Alpha of each pixel,
58
// so that it fades out in a circular fashion.
59
for y in 0..IMAGE_HEIGHT {
60
for x in 0..IMAGE_WIDTH {
61
let center = Vec2::new(IMAGE_WIDTH as f32 / 2.0, IMAGE_HEIGHT as f32 / 2.0);
62
let max_radius = IMAGE_HEIGHT.min(IMAGE_WIDTH) as f32 / 2.0;
63
let r = Vec2::new(x as f32, y as f32).distance(center);
64
let a = 1.0 - (r / max_radius).clamp(0.0, 1.0);
65
66
// Here we will set the A value by accessing the raw data bytes.
67
// (it is the 4th byte of each pixel, as per our `TextureFormat`)
68
69
// Find our pixel by its coordinates
70
let pixel_bytes = image.pixel_bytes_mut(UVec3::new(x, y, 0)).unwrap();
71
// Convert our f32 to u8
72
pixel_bytes[3] = (a * u8::MAX as f32) as u8;
73
}
74
}
75
76
// Add it to Bevy's assets, so it can be used for rendering
77
// this will give us a handle we can use
78
// (to display it in a sprite, or as part of UI, etc.)
79
let handle = images.add(image);
80
81
// Create a sprite entity using our image
82
commands.spawn(Sprite::from_image(handle.clone()));
83
commands.insert_resource(MyProcGenImage(handle));
84
85
// We're seeding the PRNG here to make this example deterministic for testing purposes.
86
// This isn't strictly required in practical use unless you need your app to be deterministic.
87
let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
88
commands.insert_resource(SeededRng(seeded_rng));
89
}
90
91
/// Every fixed update tick, draw one more pixel to make a spiral pattern
92
fn draw(
93
my_handle: Res<MyProcGenImage>,
94
mut images: ResMut<Assets<Image>>,
95
// Used to keep track of where we are
96
mut i: Local<u32>,
97
mut draw_color: Local<Color>,
98
mut seeded_rng: ResMut<SeededRng>,
99
) {
100
if *i == 0 {
101
// Generate a random color on first run.
102
*draw_color = Color::linear_rgb(
103
seeded_rng.0.random(),
104
seeded_rng.0.random(),
105
seeded_rng.0.random(),
106
);
107
}
108
109
// Get the image from Bevy's asset storage.
110
let image = images.get_mut(&my_handle.0).expect("Image not found");
111
112
// Compute the position of the pixel to draw.
113
114
let center = Vec2::new(IMAGE_WIDTH as f32 / 2.0, IMAGE_HEIGHT as f32 / 2.0);
115
let max_radius = IMAGE_HEIGHT.min(IMAGE_WIDTH) as f32 / 2.0;
116
let rot_speed = 0.0123;
117
let period = 0.12345;
118
119
let r = ops::sin(*i as f32 * period) * max_radius;
120
let xy = Vec2::from_angle(*i as f32 * rot_speed) * r + center;
121
let (x, y) = (xy.x as u32, xy.y as u32);
122
123
// Get the old color of that pixel.
124
let old_color = image.get_color_at(x, y).unwrap();
125
126
// If the old color is our current color, change our drawing color.
127
let tolerance = 1.0 / 255.0;
128
if old_color.distance(&draw_color) <= tolerance {
129
*draw_color = Color::linear_rgb(
130
seeded_rng.0.random(),
131
seeded_rng.0.random(),
132
seeded_rng.0.random(),
133
);
134
}
135
136
// Set the new color, but keep old alpha value from image.
137
image
138
.set_color_at(x, y, draw_color.with_alpha(old_color.alpha()))
139
.unwrap();
140
141
*i += 1;
142
}
143
144