1#![warn(missing_docs)]
7use fidget_core::{
8 eval::Function,
9 render::{ImageSize, RenderHandle, ThreadPool, TileSizes},
10 shape::{Shape, ShapeVars},
11};
12use nalgebra::{Const, OPoint, Point2, Vector2};
13use rayon::prelude::*;
14use zerocopy::{Immutable, IntoBytes};
15
16pub mod effects;
17pub mod pixel;
18pub mod voxel;
19
20#[derive(Copy, Clone, Debug)]
21pub(crate) struct Tile<const N: usize> {
22 pub corner: OPoint<usize, Const<N>>,
24}
25
26impl<const N: usize> Tile<N> {
27 #[inline]
29 pub(crate) fn new(corner: OPoint<usize, Const<N>>) -> Tile<N> {
30 Tile { corner }
31 }
32
33 pub(crate) fn add(&self, pos: Vector2<usize>) -> Point2<usize> {
37 let corner = Point2::new(self.corner[0], self.corner[1]);
38 corner + pos
39 }
40}
41
42#[derive(Copy, Clone)]
47pub(crate) struct TileSizesRef<'a>(&'a [usize]);
48
49impl<'a> std::ops::Index<usize> for TileSizesRef<'a> {
50 type Output = usize;
51
52 fn index(&self, i: usize) -> &Self::Output {
53 &self.0[i]
54 }
55}
56
57impl TileSizesRef<'_> {
58 fn new(tiles: &TileSizes, max_size: usize) -> TileSizesRef<'_> {
60 let i = tiles
61 .iter()
62 .position(|t| *t < max_size)
63 .unwrap_or(tiles.len())
64 .saturating_sub(1);
65 TileSizesRef(&tiles[i..])
66 }
67
68 pub fn last(&self) -> usize {
70 *self.0.last().unwrap()
71 }
72
73 pub fn get(&self, i: usize) -> Option<usize> {
75 self.0.get(i).copied()
76 }
77
78 #[inline]
83 pub(crate) fn pixel_offset(&self, pos: Point2<usize>) -> usize {
84 let x = pos.x % self.0[0];
86 let y = pos.y % self.0[0];
87
88 x + y * self.0[0]
90 }
91}
92
93pub(crate) fn render_tiles<'a, F: Function, W: RenderWorker<'a, F>, C>(
100 shape: Shape<F>,
101 vars: &'a ShapeVars<f32>,
102 render_config: &'a W::Config,
103 eval_config: &'a C,
104 tile_sizes: TileSizesRef<'a>,
105) -> Option<Vec<(Tile<2>, W::Output)>>
106where
107 W::Config: Send + Sync,
108 C: EvalConfig + Send + Sync,
109{
110 use rayon::prelude::*;
111
112 let mut tiles = vec![];
113 let t = tile_sizes[0];
114 let width = render_config.width() as usize;
115 let height = render_config.height() as usize;
116 for i in 0..width.div_ceil(t) {
117 for j in 0..height.div_ceil(t) {
118 tiles.push(Tile::new(Point2::new(
119 i * tile_sizes[0],
120 j * tile_sizes[0],
121 )));
122 }
123 }
124
125 let mut rh = RenderHandle::new(shape);
126
127 let _ = rh.i_tape(&mut vec![]); let ts = tile_sizes;
129 let init = || {
130 let rh = rh.clone();
131 let worker = W::new(render_config, ts, vars);
132 (worker, rh)
133 };
134
135 match eval_config.threads() {
136 None => {
137 let mut worker = W::new(render_config, tile_sizes, vars);
138 tiles
139 .into_iter()
140 .map(|tile| {
141 if eval_config.is_cancelled() {
142 Err(())
143 } else {
144 let pixels = worker.render_tile(&mut rh, tile);
145 Ok((tile, pixels))
146 }
147 })
148 .collect::<Result<Vec<_>, ()>>()
149 .ok()
150 }
151
152 Some(p) => p.run(|| {
153 tiles
154 .into_par_iter()
155 .map_init(init, |(w, rh), tile| {
156 if eval_config.is_cancelled() {
157 Err(())
158 } else {
159 let pixels = w.render_tile(rh, tile);
160 Ok((tile, pixels))
161 }
162 })
163 .collect::<Result<Vec<_>, ()>>()
164 .ok()
165 }),
166 }
167}
168
169pub(crate) trait EvalConfig {
171 fn threads(&self) -> Option<&ThreadPool>;
172 fn is_cancelled(&self) -> bool;
173}
174
175pub trait RenderSize {
177 fn width(&self) -> u32;
179 fn height(&self) -> u32;
181}
182
183pub(crate) trait RenderWorker<'a, F: Function> {
185 type Config: RenderSize;
186 type Output: Send;
187
188 fn new(
192 cfg: &'a Self::Config,
193 tile_sizes: TileSizesRef<'a>,
194 vars: &'a ShapeVars<f32>,
195 ) -> Self;
196
197 fn render_tile(
199 &mut self,
200 shape: &mut RenderHandle<F>,
201 tile: Tile<2>,
202 ) -> Self::Output;
203}
204
205#[derive(Clone)]
222pub struct Image<P, S = ImageSize> {
223 data: Vec<P>,
224 size: S,
225}
226
227impl RenderSize for pixel::RenderSize {
228 fn width(&self) -> u32 {
229 self.width()
230 }
231 fn height(&self) -> u32 {
232 self.height()
233 }
234}
235
236impl RenderSize for voxel::RenderSize {
237 fn width(&self) -> u32 {
238 self.width()
239 }
240 fn height(&self) -> u32 {
241 self.height()
242 }
243}
244
245impl<P: Send, S: RenderSize + Sync> Image<P, S> {
246 pub fn apply_effect<F: Fn(usize, usize) -> P + Send + Sync>(
251 &mut self,
252 f: F,
253 threads: Option<&ThreadPool>,
254 ) {
255 let r = |(y, row): (usize, &mut [P])| {
256 for (x, v) in row.iter_mut().enumerate() {
257 *v = f(x, y);
258 }
259 };
260
261 if let Some(threads) = threads {
262 threads.run(|| {
263 self.data
264 .par_chunks_mut(self.size.width() as usize)
265 .enumerate()
266 .for_each(r)
267 })
268 } else {
269 self.data
270 .chunks_mut(self.size.width() as usize)
271 .enumerate()
272 .for_each(r)
273 }
274 }
275}
276
277impl<P: IntoBytes + Immutable, S: RenderSize> Image<P, S> {
278 pub fn as_bytes(&self) -> &[u8] {
280 self.data.as_bytes()
281 }
282}
283
284impl<P, S: Default> Default for Image<P, S> {
285 fn default() -> Self {
286 Image {
287 data: vec![],
288 size: S::default(),
289 }
290 }
291}
292
293impl<P: Default + Clone, S: RenderSize> Image<P, S> {
294 pub fn new(size: S) -> Self {
296 Self {
297 data: vec![
298 P::default();
299 size.width() as usize * size.height() as usize
300 ],
301 size,
302 }
303 }
304}
305
306impl<P, S: Clone> Image<P, S> {
307 pub fn size(&self) -> S {
309 self.size.clone()
310 }
311
312 pub fn map<T, F: Fn(&P) -> T>(&self, f: F) -> Image<T, S> {
314 let data = self.data.iter().map(f).collect();
315 Image {
316 data,
317 size: self.size.clone(),
318 }
319 }
320
321 pub fn as_slice(&self) -> &[P] {
323 &self.data
324 }
325
326 pub fn take(self) -> (Vec<P>, S) {
328 (self.data, self.size)
329 }
330}
331
332impl<P, S: RenderSize> Image<P, S> {
333 pub fn width(&self) -> usize {
335 self.size.width() as usize
336 }
337
338 pub fn height(&self) -> usize {
340 self.size.height() as usize
341 }
342
343 fn decode_position(&self, pos: (usize, usize)) -> usize {
347 let (row, col) = pos;
348 assert!(
349 row < self.height(),
350 "row ({row}) must be less than image height ({})",
351 self.height()
352 );
353 assert!(
354 col < self.width(),
355 "column ({col}) must be less than image width ({})",
356 self.width()
357 );
358 row * self.width() + col
359 }
360
361 pub fn build(data: Vec<P>, size: S) -> Result<Self, BadPixelCount> {
365 let expected = u64::from(size.width()) * u64::from(size.height());
366 let actual = data.len();
367 if expected != actual as u64 {
368 return Err(BadPixelCount {
369 expected,
370 actual,
371 width: size.width(),
372 height: size.height(),
373 });
374 }
375 Ok(Self { data, size })
376 }
377}
378
379impl<P, S> Image<P, S> {
380 pub fn iter(&self) -> impl Iterator<Item = &P> + '_ {
382 self.data.iter()
383 }
384
385 pub fn len(&self) -> usize {
387 self.data.len()
388 }
389
390 pub fn is_empty(&self) -> bool {
392 self.data.is_empty()
393 }
394}
395
396impl<'a, P: 'a, S> IntoIterator for &'a Image<P, S> {
397 type Item = &'a P;
398 type IntoIter = std::slice::Iter<'a, P>;
399 fn into_iter(self) -> Self::IntoIter {
400 self.data.iter()
401 }
402}
403
404impl<P, S> IntoIterator for Image<P, S> {
405 type Item = P;
406 type IntoIter = std::vec::IntoIter<P>;
407 fn into_iter(self) -> Self::IntoIter {
408 self.data.into_iter()
409 }
410}
411
412impl<P, S> std::ops::Index<usize> for Image<P, S> {
413 type Output = P;
414 fn index(&self, index: usize) -> &Self::Output {
415 &self.data[index]
416 }
417}
418
419impl<P, S> std::ops::IndexMut<usize> for Image<P, S> {
420 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
421 &mut self.data[index]
422 }
423}
424
425macro_rules! define_image_index {
426 ($ty:ty) => {
427 impl<P, S> std::ops::Index<$ty> for Image<P, S> {
428 type Output = [P];
429 fn index(&self, index: $ty) -> &Self::Output {
430 &self.data[index]
431 }
432 }
433
434 impl<P, S> std::ops::IndexMut<$ty> for Image<P, S> {
435 fn index_mut(&mut self, index: $ty) -> &mut Self::Output {
436 &mut self.data[index]
437 }
438 }
439 };
440}
441
442define_image_index!(std::ops::Range<usize>);
443define_image_index!(std::ops::RangeTo<usize>);
444define_image_index!(std::ops::RangeFrom<usize>);
445define_image_index!(std::ops::RangeInclusive<usize>);
446define_image_index!(std::ops::RangeToInclusive<usize>);
447define_image_index!(std::ops::RangeFull);
448
449impl<P, S: RenderSize> std::ops::Index<(usize, usize)> for Image<P, S> {
451 type Output = P;
452 fn index(&self, pos: (usize, usize)) -> &Self::Output {
453 let index = self.decode_position(pos);
454 &self.data[index]
455 }
456}
457
458impl<P, S: RenderSize> std::ops::IndexMut<(usize, usize)> for Image<P, S> {
459 fn index_mut(&mut self, pos: (usize, usize)) -> &mut Self::Output {
460 let index = self.decode_position(pos);
461 &mut self.data[index]
462 }
463}
464
465impl<P: Default + Copy + Clone> Image<P, voxel::RenderSize> {
466 pub fn depth(&self) -> usize {
468 self.size.depth() as usize
469 }
470}
471
472pub type ColorImage = Image<[u8; 3]>;
474
475#[derive(thiserror::Error, Debug, PartialEq)]
477#[error(
478 "bad pixel count: expected {expected} ({width} × {height}), got {actual}"
479)]
480pub struct BadPixelCount {
481 pub expected: u64,
483 pub actual: usize,
485 pub width: u32,
487 pub height: u32,
489}
490
491#[cfg(test)]
492mod test {
493 use super::*;
494
495 #[test]
496 fn image_construction() {
497 let i = Image::build(vec![1, 2, 3, 4, 5, 6], ImageSize::new(2, 3));
498 assert!(i.is_ok());
499
500 let i = Image::build(vec![1, 2, 3, 4, 5, 6], ImageSize::new(3, 2));
501 assert!(i.is_ok());
502
503 let i = Image::build(vec![1, 2, 3, 4, 5], ImageSize::new(2, 3));
504 let Err(e) = i else {
505 panic!("expected error, got valid image");
506 };
507 assert_eq!(
508 e,
509 BadPixelCount {
510 expected: 6,
511 actual: 5,
512 width: 2,
513 height: 3,
514 }
515 );
516 }
517}