Skip to main content

maple_render_core/
renders.rs

1use std::collections::HashMap;
2
3use crate::{
4    error::{Error, Result},
5    input::{Input, Inputs},
6    render::{Render, RenderQuality},
7    repository::Repository,
8};
9
10pub struct Renders {
11    repo: Repository,
12    inputs: Inputs,
13    renders: HashMap<i32, Render>,
14    target_width: i32,
15    target_height: i32,
16    quality: RenderQuality,
17    peak_cache_count: usize,
18}
19
20impl Renders {
21    pub fn new(repo: Repository, inputs: Inputs, quality: RenderQuality) -> Self {
22        Renders {
23            repo,
24            inputs,
25            renders: HashMap::new(),
26            target_width: -1,
27            target_height: -1,
28            quality,
29            peak_cache_count: 0,
30        }
31    }
32
33    pub fn set_size(&mut self, w: i32, h: i32) {
34        self.target_width = w;
35        self.target_height = h;
36    }
37
38    fn check(&self) -> Result<()> {
39        if self.inputs.is_empty() {
40            return Err(Error::NoInputs);
41        }
42        Ok(())
43    }
44
45    pub fn get_render(&mut self, index: i32) -> Result<&Render> {
46        self.check()?;
47
48        if !self.renders.contains_key(&index) {
49            let mut render = Render::new(self.quality);
50            let mapping = self.repo.take_mapping(index)?;
51            render.attach_mapping(mapping);
52            render.apply_scaled(&self.inputs, self.target_width, self.target_height)?;
53
54            self.renders.insert(index, render);
55
56            if self.renders.len() > self.peak_cache_count {
57                self.peak_cache_count = self.renders.len();
58            }
59        }
60
61        Ok(self.renders.get(&index).unwrap())
62    }
63
64    pub fn get_render_mut(&mut self, index: i32) -> Result<&mut Render> {
65        self.check()?;
66
67        if !self.renders.contains_key(&index) {
68            let mut render = Render::new(self.quality);
69            let mapping = self.repo.take_mapping(index)?;
70            render.attach_mapping(mapping);
71            render.apply_scaled(&self.inputs, self.target_width, self.target_height)?;
72
73            self.renders.insert(index, render);
74
75            if self.renders.len() > self.peak_cache_count {
76                self.peak_cache_count = self.renders.len();
77            }
78        }
79
80        Ok(self.renders.get_mut(&index).unwrap())
81    }
82
83    pub fn remove_render(&mut self, index: i32) {
84        self.renders.remove(&index);
85        self.repo.remove_mapping(index);
86    }
87
88    pub fn remove_mapping(&mut self, index: i32) {
89        self.repo.remove_mapping(index);
90    }
91
92    pub fn length(&self) -> u32 {
93        self.repo.length()
94    }
95
96    pub fn peak(&self) -> usize {
97        self.peak_cache_count
98    }
99
100    pub fn repo(&self) -> &Repository {
101        &self.repo
102    }
103
104    pub fn repo_mut(&mut self) -> &mut Repository {
105        &mut self.repo
106    }
107
108    pub fn inputs(&self) -> &Inputs {
109        &self.inputs
110    }
111
112    pub fn inputs_mut(&mut self) -> &mut Inputs {
113        &mut self.inputs
114    }
115
116    pub fn auto_zoom(&mut self) -> Result<bool> {
117        self.check()?;
118
119        let mut render = Render::new(self.quality);
120        let mapping = self.repo.get_mapping(0)?.clone();
121        render.attach_mapping(mapping);
122
123        render.auto_zoom(&mut self.inputs)
124    }
125
126    /// Optimal positioning using Delaunay triangulation
127    pub fn scan(&mut self, frame: i32) -> Result<()> {
128        let frame = if frame < 0 { 0 } else { frame };
129
130        self.check()?;
131
132        if self.inputs.is_empty() {
133            return Ok(());
134        }
135
136        let num_inputs = self.inputs.len();
137
138        for k in 0..num_inputs {
139            let mut render = Render::new(self.quality);
140            let mapping = self.repo.get_mapping(frame)?.clone();
141            render.attach_mapping(mapping);
142
143            let input = &self.inputs[k];
144            let pts = render.get_cloud(input)?;
145
146            let layer = input.layer;
147            let filtered_pts: Vec<_> = pts.iter().filter(|p| p.layer == layer).collect();
148
149            if filtered_pts.is_empty() {
150                continue;
151            }
152
153            let mut x0 = filtered_pts[0].x;
154            let mut y0 = filtered_pts[0].y;
155            let mut x1 = x0;
156            let mut y1 = y0;
157            let mut tx = 0.0f64;
158            let mut ty = 0.0f64;
159
160            for pt in &filtered_pts {
161                if pt.x < x0 {
162                    x0 = pt.x;
163                }
164                if pt.x > x1 {
165                    x1 = pt.x;
166                }
167                if pt.y < y0 {
168                    y0 = pt.y;
169                }
170                if pt.y > y1 {
171                    y1 = pt.y;
172                }
173                tx += pt.x;
174                ty += pt.y;
175            }
176
177            let ct = filtered_pts.len() as f64;
178            if ct > 0.0 {
179                let _ = tx / ct;
180                let _ = ty / ct;
181            }
182
183            tx = (x0 + x1) / 2.0;
184            ty = (y0 + y1) / 2.0;
185
186            let dx = x0 + (x1 - x0);
187            let dy = y0 + (y1 - y0);
188
189            let pts_shifted: Vec<_> = filtered_pts
190                .iter()
191                .map(|p| delaunator::Point { x: p.x + dx, y: p.y + dy })
192                .collect();
193
194            if pts_shifted.len() < 3 {
195                continue;
196            }
197
198            let triangulation = delaunator::triangulate(&pts_shifted);
199
200            let input_img = input.get();
201            let input_ref = &self.inputs[k];
202
203            let result = self.tweak_scale(
204                input_img,
205                x0 + dx,
206                y0 + dy,
207                x1 + dx,
208                y1 + dy,
209                dx,
210                dy,
211                tx + dx,
212                ty + dy,
213                &triangulation,
214                &pts_shifted,
215                input_ref,
216            );
217
218            if let Some((scale, _x_adj, y_adj)) = result {
219                let input_mut = &mut self.inputs.get_mut()[k];
220                input_mut.xs /= scale;
221                input_mut.ys /= scale;
222                input_mut.in_y0 += y_adj;
223            }
224        }
225
226        Ok(())
227    }
228
229    fn tweak_scale(
230        &self,
231        img: &image::RgbaImage,
232        x0: f64,
233        y0: f64,
234        x1: f64,
235        y1: f64,
236        dx: f64,
237        dy: f64,
238        tx: f64,
239        ty: f64,
240        triangulation: &delaunator::Triangulation,
241        points: &[delaunator::Point],
242        input: &Input,
243    ) -> Option<(f64, f64, f64)> {
244        let mut actives = 0;
245        let mut ix0 = 0.0f64;
246        let mut iy0 = 0.0f64;
247        let mut ix1 = 0.0f64;
248        let mut iy1 = 0.0f64;
249        let mut first = true;
250
251        for (x, y, pixel) in img.enumerate_pixels() {
252            if Self::is_active(pixel) {
253                actives += 1;
254                let xf = x as f64;
255                let yf = y as f64;
256                if first {
257                    ix0 = xf;
258                    ix1 = xf;
259                    iy0 = yf;
260                    iy1 = yf;
261                    first = false;
262                } else {
263                    if xf > ix1 {
264                        ix1 = xf;
265                    }
266                    if xf < ix0 {
267                        ix0 = xf;
268                    }
269                    if yf > iy1 {
270                        iy1 = yf;
271                    }
272                    if yf < iy0 {
273                        iy0 = yf;
274                    }
275                }
276            }
277        }
278
279        if actives >= (img.width() * img.height() * 9 / 10) as i32 {
280            return None;
281        }
282
283        let ix = (ix0 + ix1) / 2.0;
284        let iy = (iy0 + iy1) / 2.0;
285        let xt = tx - dx;
286        let yt = ty - dy;
287
288        // Binary search for optimal scale
289        let blo = 0.1f64;
290        let bhi = 6.0f64;
291        let mut lo = blo;
292        let mut hi = bhi;
293        let mut good_scale = 0.01f64;
294        let mut decent_scale = 1.0f64;
295        let mut decent_scale_value = -1.0f64;
296
297        while (lo - hi).abs() > 0.01 {
298            let curr = (lo + hi) / 2.0;
299            let (nin, nout) = self.tweak_one(
300                img,
301                x0,
302                y0,
303                x1,
304                y1,
305                dx,
306                dy,
307                xt,
308                yt,
309                ix,
310                iy,
311                triangulation,
312                points,
313                curr,
314            );
315
316            let q = if nout == 0 { 1.0 } else { nin as f64 / (nin + nout) as f64 };
317            let v = Self::eval(q, curr);
318
319            if v > decent_scale_value {
320                decent_scale = curr;
321                decent_scale_value = v;
322            }
323
324            if q > 0.9999 {
325                if curr > good_scale {
326                    good_scale = curr;
327                }
328                lo = curr;
329            } else {
330                hi = curr;
331            }
332        }
333
334        // Linear search for refinement
335        let steps = 20;
336        for i in 1..=steps {
337            let curr = blo + (bhi - blo) * (i as f64 / steps as f64);
338            let (nin, nout) = self.tweak_one(
339                img,
340                x0,
341                y0,
342                x1,
343                y1,
344                dx,
345                dy,
346                xt,
347                yt,
348                ix,
349                iy,
350                triangulation,
351                points,
352                curr,
353            );
354
355            let q = if nout == 0 { 1.0 } else { nin as f64 / (nin + nout) as f64 };
356            let v = Self::eval(q, curr);
357
358            if v > decent_scale_value {
359                decent_scale = curr;
360                decent_scale_value = v;
361            }
362        }
363
364        good_scale = decent_scale * 0.97;
365
366        let y_adj = Self::remap(good_scale, yt, iy, input.in_y0, input);
367
368        Some((good_scale, 0.0, y_adj))
369    }
370
371    fn eval(q: f64, curr: f64) -> f64 {
372        q * curr.sqrt() + if q >= 0.9999 { 1.0 } else { 0.0 }
373    }
374
375    fn remap(scale: f64, v: f64, i: f64, delta: f64, input: &Input) -> f64 {
376        let scale = 1.0 / scale;
377        i - (scale * v - input.in_scale as f64 / 2.0 * (scale - 1.0) - scale * delta) - delta
378    }
379
380    fn tweak_one(
381        &self,
382        img: &image::RgbaImage,
383        x0: f64,
384        y0: f64,
385        x1: f64,
386        y1: f64,
387        dx: f64,
388        dy: f64,
389        xt: f64,
390        yt: f64,
391        ix: f64,
392        iy: f64,
393        triangulation: &delaunator::Triangulation,
394        points: &[delaunator::Point],
395        scale: f64,
396    ) -> (i32, i32) {
397        let mut nin = 0;
398        let mut nout = 0;
399
400        for (x, y, pixel) in img.enumerate_pixels() {
401            if Self::is_active(pixel) {
402                let xx = dx + (x as f64 - ix) * scale + xt;
403                let yy = dy + (y as f64 - iy) * scale + yt;
404
405                let dud = if xx < x0 || xx > x1 || yy < y0 || yy > y1 {
406                    true
407                } else {
408                    !Self::point_in_triangulation(xx, yy, triangulation, points, x0, y0, x1, y1)
409                };
410
411                if dud {
412                    nout += 1;
413                } else {
414                    nin += 1;
415                }
416            }
417        }
418
419        (nin, nout)
420    }
421
422    fn is_active(pixel: &image::Rgba<u8>) -> bool {
423        pixel[3] > 25 && (pixel[0] < 250 || pixel[1] < 250 || pixel[2] < 250)
424    }
425
426    fn point_in_triangulation(
427        x: f64,
428        y: f64,
429        triangulation: &delaunator::Triangulation,
430        points: &[delaunator::Point],
431        x0: f64,
432        y0: f64,
433        x1: f64,
434        y1: f64,
435    ) -> bool {
436        let num_triangles = triangulation.triangles.len() / 3;
437
438        for t in 0..num_triangles {
439            let i0 = triangulation.triangles[t * 3];
440            let i1 = triangulation.triangles[t * 3 + 1];
441            let i2 = triangulation.triangles[t * 3 + 2];
442
443            let p0 = &points[i0];
444            let p1 = &points[i1];
445            let p2 = &points[i2];
446
447            // Skip degenerate triangles
448            let d01 = ((p0.x - p1.x).powi(2) + (p0.y - p1.y).powi(2)).sqrt();
449            let d12 = ((p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sqrt();
450            let d20 = ((p2.x - p0.x).powi(2) + (p2.y - p0.y).powi(2)).sqrt();
451
452            if d01 > 100.0 || d12 > 100.0 || d20 > 100.0 {
453                continue;
454            }
455
456            if p0.x < x0
457                || p0.x > x1
458                || p0.y < y0
459                || p0.y > y1
460                || p1.x < x0
461                || p1.x > x1
462                || p1.y < y0
463                || p1.y > y1
464                || p2.x < x0
465                || p2.x > x1
466                || p2.y < y0
467                || p2.y > y1
468            {
469                continue;
470            }
471
472            if Self::point_in_triangle(x, y, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y) {
473                return true;
474            }
475        }
476
477        false
478    }
479
480    fn point_in_triangle(
481        x: f64,
482        y: f64,
483        x0: f64,
484        y0: f64,
485        x1: f64,
486        y1: f64,
487        x2: f64,
488        y2: f64,
489    ) -> bool {
490        let area = 0.5 * (-y1 * x2 + y0 * (-x1 + x2) + x0 * (y1 - y2) + x1 * y2);
491        let s = (y0 * x2 - x0 * y2 + (y2 - y0) * x + (x0 - x2) * y) / (2.0 * area);
492        let t = (x0 * y1 - y0 * x1 + (y0 - y1) * x + (x1 - x0) * y) / (2.0 * area);
493
494        s >= 0.0 && t >= 0.0 && (1.0 - s - t) >= 0.0
495    }
496}