1use crate::*;
2
3use image::RgbaImage;
4use std::fmt::Debug;
5
6#[derive(Debug)]
7pub struct CanvasBlock {
8 pub image: RgbaImage,
9 pub handle: TextureHandle,
10 pub modified: bool,
11}
12
13pub const CANVAS_BLOCK_SIZE: i32 = 1024;
14const BLOCK_SIZE: i32 = CANVAS_BLOCK_SIZE;
15const PIXELS_PER_WORLD_UNIT: i32 = 16;
16
17pub const fn blood_block_world_size() -> i32 {
18 BLOCK_SIZE / PIXELS_PER_WORLD_UNIT
19}
20
21#[derive(Debug)]
22pub struct BloodCanvas {
23 pub creator: Arc<AtomicRefCell<dyn TextureCreator + Send + Sync + 'static>>,
24 pub blocks: HashMap<IVec2, CanvasBlock>,
25}
26
27impl BloodCanvas {
28 pub fn new(
29 creator: Arc<AtomicRefCell<dyn TextureCreator + Send + Sync + 'static>>,
30 ) -> Self {
31 Self { creator, blocks: HashMap::default() }
32 }
33
34 pub fn get_pixel(&mut self, position: Vec2) -> Color {
35 let position = position * PIXELS_PER_WORLD_UNIT as f32;
36 self.get_pixel_internal(position.x as i32, position.y as i32)
37 }
38
39 pub fn set_pixel(&mut self, position: Vec2, color: Color) {
40 let position = position * PIXELS_PER_WORLD_UNIT as f32;
41
42 self.set_pixel_internal(position.x as i32, position.y as i32, color)
43 }
44
45 fn get_block(&mut self, x: i32, y: i32) -> &mut CanvasBlock {
46 let key = ivec2(x, y);
47
48 self.blocks.entry(key).or_insert_with(|| {
49 let image = DynamicImage::ImageRgba8(RgbaImage::new(
50 BLOCK_SIZE as u32,
51 BLOCK_SIZE as u32,
52 ))
53 .to_rgba8();
54
55 let name = format!("blood-canvas-{}-{}", x, y);
56
57 let handle =
58 self.creator.borrow_mut().handle_from_image(&name, &image);
59
60 CanvasBlock { handle, image, modified: false }
61 })
62 }
63
64 pub fn circle_at_internal(
65 &mut self,
66 position: Vec2,
67 radius: i32,
68 pixel_prob: f32,
69 color: fn() -> Color,
70 ) {
71 let position = position * PIXELS_PER_WORLD_UNIT as f32;
72
73 let x = position.x as i32;
74 let y = position.y as i32;
75
76 for dx in -radius..radius {
77 for dy in -radius..radius {
78 if dx * dx + dy * dy < radius * radius && flip_coin(pixel_prob)
79 {
80 self.set_pixel_internal(x + dx, y + dy, color());
81 }
82 }
83 }
84 }
85
86 fn get_pixel_internal(&mut self, x: i32, y: i32) -> Color {
87 let bx = (x as f32 / BLOCK_SIZE as f32).floor() as i32;
88 let by = (y as f32 / BLOCK_SIZE as f32).floor() as i32;
89
90 let block = self.get_block(bx, by);
91
92 block.modified = true;
93 let px = block.image.get_pixel(
94 (x - bx * BLOCK_SIZE) as u32,
95 (y - by * BLOCK_SIZE) as u32,
96 );
97
98 Into::<Color>::into(*px)
99 }
100
101 fn set_pixel_internal(&mut self, x: i32, y: i32, color: Color) {
102 let bx = (x as f32 / BLOCK_SIZE as f32).floor() as i32;
103 let by = (y as f32 / BLOCK_SIZE as f32).floor() as i32;
104
105 let block = self.get_block(bx, by);
106
107 block.modified = true;
108 block.image.put_pixel(
109 (x - bx * BLOCK_SIZE) as u32,
110 (y - by * BLOCK_SIZE) as u32,
111 image::Rgba([
112 (color.r * 255.0) as u8,
113 (color.g * 255.0) as u8,
114 (color.b * 255.0) as u8,
115 (color.a * 255.0) as u8,
116 ]),
117 );
118 }
119
120 pub fn blit_at_sized(
121 &mut self,
122 texture: TextureHandle,
123 position: Vec2,
124 source_rect: Option<IRect>,
125 tint: Color,
126 dest_size: Vec2,
127 ) {
128 let assets = ASSETS.borrow_mut();
129 let image_map = assets.texture_image_map.lock();
130
131 if let Some(image) = image_map.get(&texture).cloned() {
132 drop(image_map);
133 drop(assets);
134
135 let source = source_rect.unwrap_or(IRect::new(
136 ivec2(0, 0),
137 ivec2(image.width() as i32, image.height() as i32),
138 ));
139
140 let scale_x = dest_size.x / source.size.x as f32;
142 let scale_y = dest_size.y / source.size.y as f32;
143
144 for x in 0..dest_size.x as i32 {
145 for y in 0..dest_size.y as i32 {
146 let src_x =
148 ((x as f32 / scale_x) + source.offset.x as f32) as u32;
149 let src_y =
150 ((y as f32 / scale_y) + source.offset.y as f32) as u32;
151
152 if src_x < image.width() && src_y < image.height() {
153 let px = image.get_pixel(src_x, src_y);
154
155 if px.0[3] > 0 {
156 self.set_pixel(
157 position + vec2(x as f32, y as f32) / 16.0,
158 Into::<Color>::into(*px) * tint,
159 );
160 }
161 }
162 }
163 }
164 }
165 }
166
167 pub fn blit_at(
168 &mut self,
169 texture: TextureHandle,
170 position: Vec2,
171 source_rect: Option<IRect>,
172 tint: Color,
173 flip_x: bool,
174 flip_y: bool,
175 ) {
176 let tint = tint.to_srgb();
177
178 let assets = ASSETS.borrow_mut();
179 let image_map = assets.texture_image_map.lock();
180
181 if let Some(image) = image_map.get(&texture).cloned() {
182 drop(image_map);
183 drop(assets);
184
185 let rect = source_rect.unwrap_or(IRect::new(
186 ivec2(0, 0),
187 ivec2(image.width() as i32, image.height() as i32),
188 ));
189
190 let size_offset = rect.size.as_vec2() / 2.0;
191
192 for x in 0..rect.size.x {
193 for y in 0..rect.size.y {
194 let mut read_x = x + rect.offset.x;
195 let mut read_y = y + rect.offset.y;
196
197 if flip_x {
198 read_x = rect.offset.x + rect.size.x - x - 1;
199 }
200
201 if !flip_y {
202 read_y = rect.offset.y + rect.size.y - y - 1;
203 }
204
205 let src_px = image.get_pixel(read_x as u32, read_y as u32);
206
207 if src_px.0[3] > 0 {
208 let px_pos = position + vec2(x as f32, y as f32) / 16.0 -
209 size_offset / 16.0;
210
211 if tint.a < 1.0 {
212 let existing = self.get_pixel(px_pos);
213
214 let tinted = Into::<Color>::into(*src_px)
215 .linear_space_tint(tint.alpha(1.0));
216
217 self.set_pixel(
218 px_pos,
219 existing.lerp(tinted, tint.a),
220 );
221 } else {
222 self.set_pixel(
223 px_pos,
224 Into::<Color>::into(*src_px)
225 .linear_space_tint(tint),
226 );
227 }
228 }
229 }
230 }
231 }
232 }
233}
234
235pub trait TextureCreator: Debug {
236 fn handle_from_size(
237 &self,
238 name: &str,
239 size: UVec2,
240 fill: Color,
241 ) -> TextureHandle;
242
243 fn handle_from_image(&self, name: &str, image: &RgbaImage)
244 -> TextureHandle;
245
246 fn update_texture(&self, image: &RgbaImage, texture: TextureHandle);
247 fn update_texture_region(
248 &self,
249 handle: TextureHandle,
250 image: &RgbaImage,
251 region: IRect,
252 );
253}