comfy_core/
blood_canvas.rs

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            // Calculate scaling factors
141            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                    // Determine the corresponding pixel in the source image
147                    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}