Skip to main content

maple_render_core/
render.rs

1use std::sync::atomic::{AtomicUsize, Ordering};
2
3use image::{Rgba, RgbaImage};
4#[cfg(not(target_arch = "wasm32"))]
5use rayon::prelude::*;
6
7use crate::{
8    error::{Error, Result},
9    input::{Input, Inputs},
10    mapping::Mapping,
11    pixer::{Pixer, sample_linear_opaque, sample_linear_premultiplied},
12};
13
14static RENDER_COUNT: AtomicUsize = AtomicUsize::new(0);
15
16const RR: f64 = 2048.0; // Half of coordinate range (4096/2)
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(u8)]
20pub enum RenderQuality {
21    None,
22    Simple,
23    Sampled,
24}
25
26impl Default for RenderQuality {
27    fn default() -> Self {
28        RenderQuality::Sampled
29    }
30}
31
32#[derive(Debug, Clone)]
33pub struct CloudPoint {
34    pub layer: u8,
35    pub x: f64,
36    pub y: f64,
37}
38
39pub struct Render {
40    mapping: Option<Mapping>,
41    out: RgbaImage,
42    out_scaled: Option<RgbaImage>,
43    quality: RenderQuality,
44}
45
46impl Render {
47    pub fn new(quality: RenderQuality) -> Self {
48        Render { mapping: None, out: RgbaImage::new(1, 1), out_scaled: None, quality }
49    }
50
51    pub fn with_default_quality() -> Self {
52        Self::new(RenderQuality::Sampled)
53    }
54
55    pub fn attach_mapping(&mut self, mapping: Mapping) {
56        self.mapping = Some(mapping);
57    }
58
59    fn check(&self) -> Result<&Mapping> {
60        self.mapping.as_ref().ok_or(Error::NoMapping)
61    }
62
63    fn pre(&mut self) -> Result<()> {
64        let mapping = self.check()?;
65        self.out = mapping.neutral.clone();
66        Ok(())
67    }
68
69    /// 9-tap antialiased sampling with standard UV map (parallelized by row chunks)
70    fn add(&mut self, input: &Input) -> Result<()> {
71        let mapping = self.mapping.as_ref().ok_or(Error::NoMapping)?;
72
73        let active_scale = input.in_scale as f64 / 2.0;
74        let w = mapping.light.width() as i32;
75        let h = mapping.light.height() as i32;
76
77        if (mapping.map1.width() as i32) < w {
78            return Ok(());
79        }
80
81        let off = ((mapping.map2.height() as i32 - h) / 2) as i32;
82
83        if mapping.map1.width() != mapping.neutral.width() {
84            return Ok(());
85        }
86        if mapping.map2.width() != mapping.neutral.width() {
87            return Ok(());
88        }
89
90        let out_w = self.out.width() as usize;
91        let _out_h = self.out.height() as usize;
92        let input_img = input.get();
93        let input_opaque = input.is_opaque();
94
95        let light_raw = mapping.light.as_raw();
96        let dark_raw = mapping.dark.as_raw();
97        let map1_raw = mapping.map1.as_raw();
98        let map2_raw = mapping.map2.as_raw();
99
100        let row_stride = out_w * 4;
101        let map_stride = mapping.map1.width() as usize * 4;
102        let out_raw = self.out.as_mut();
103
104        #[cfg(not(target_arch = "wasm32"))]
105        let iter = out_raw.par_chunks_mut(row_stride).enumerate();
106        #[cfg(target_arch = "wasm32")]
107        let iter = out_raw.chunks_mut(row_stride).enumerate();
108
109        iter.for_each(|(y, row)| {
110            let map_y_base = (y as i32 + off) as u32;
111            if map_y_base >= mapping.map1.height() || map_y_base >= mapping.map2.height() {
112                return;
113            }
114
115            for x in 0..out_w {
116                let idx = x * 4;
117                let light_idx = y * row_stride + idx;
118                let map_idx = map_y_base as usize * map_stride + idx;
119
120                let light_pixel = &light_raw[light_idx..light_idx + 4];
121                let dark_pixel = &dark_raw[light_idx..light_idx + 4];
122                let map_pixel = &map1_raw[map_idx..map_idx + 4];
123                let sel_pixel = &map2_raw[map_idx..map_idx + 4];
124
125                if sel_pixel[0] != input.layer {
126                    continue;
127                }
128
129                let act = map_pixel[3] as i32;
130                if act <= 25 {
131                    continue;
132                }
133
134                let b_val = map_pixel[2] as i32;
135                let ymod = b_val / 16;
136                let xmod = b_val % 16;
137                let x1 = map_pixel[0] as f64 + 256.0 * xmod as f64 - RR;
138                let y1 = map_pixel[1] as f64 + 256.0 * ymod as f64 - RR;
139
140                let mut x12 = x1;
141                let mut y12 = y1;
142                let mut x13 = x1;
143                let mut y13 = y1;
144
145                if (x as i32) < w - 1 && (y as i32) < h - 1 {
146                    let mdx_idx = map_idx + 4;
147                    let mdy_idx = map_idx + map_stride;
148                    let mdx = &map1_raw[mdx_idx..mdx_idx + 4];
149                    let mdy = &map1_raw[mdy_idx..mdy_idx + 4];
150
151                    if mdx[3] > 127 && mdy[3] > 127 {
152                        let idx2 = &map2_raw[mdx_idx..mdx_idx + 4];
153                        let idx3 = &map2_raw[mdy_idx..mdy_idx + 4];
154
155                        if idx2[0] == input.layer && idx3[0] == input.layer {
156                            let mod2 = mdx[2] as i32;
157                            let ymod2 = mod2 / 16;
158                            let xmod2 = mod2 % 16;
159                            x12 = mdx[0] as f64 + 256.0 * xmod2 as f64 - RR;
160                            y12 = mdx[1] as f64 + 256.0 * ymod2 as f64 - RR;
161
162                            let mod3 = mdy[2] as i32;
163                            let ymod3 = mod3 / 16;
164                            let xmod3 = mod3 % 16;
165                            x13 = mdy[0] as f64 + 256.0 * xmod3 as f64 - RR;
166                            y13 = mdy[1] as f64 + 256.0 * ymod3 as f64 - RR;
167                        }
168
169                        // Compare squared distance against the 400.0 threshold to
170                        // avoid two `sqrt` calls per pixel in this warp branch.
171                        // (da < 400  <=>  da^2 < 160000).
172                        let dax = x1 - x12;
173                        let day = y1 - y12;
174                        let dbx = x1 - x13;
175                        let dby = y1 - y13;
176                        if dax * dax + day * day > 160_000.0 || dbx * dbx + dby * dby > 160_000.0 {
177                            x12 = x1;
178                            y12 = y1;
179                            x13 = x1;
180                            y13 = y1;
181                        }
182                    }
183                }
184
185                let x1s = x1 * input.xs;
186                let y1s = y1 * input.ys;
187                let xx_rot = input.xa * x1s + input.ya * y1s;
188                let yy_rot = -input.ya * x1s + input.xa * y1s;
189                let xx = input.in_x0 + active_scale * (xx_rot + RR + input.xo) / RR;
190                let yy = input.in_y0 + active_scale * (yy_rot + RR + input.yo) / RR;
191
192                let x12s = x12 * input.xs;
193                let y12s = y12 * input.ys;
194                let xxa_rot = input.xa * x12s + input.ya * y12s;
195                let yya_rot = -input.ya * x12s + input.xa * y12s;
196                let xxa = input.in_x0 + active_scale * (xxa_rot + RR + input.xo) / RR - xx;
197                let yya = input.in_y0 + active_scale * (yya_rot + RR + input.yo) / RR - yy;
198
199                let x13s = x13 * input.xs;
200                let y13s = y13 * input.ys;
201                let xxb_rot = input.xa * x13s + input.ya * y13s;
202                let yyb_rot = -input.ya * x13s + input.xa * y13s;
203                let xxb = input.in_x0 + active_scale * (xxb_rot + RR + input.xo) / RR - xx;
204                let yyb = input.in_y0 + active_scale * (yyb_rot + RR + input.yo) / RR - yy;
205
206                let (mo, m2, m3, m4, m5, m2b, m3b, m4b, m5b) = if input_opaque {
207                    (
208                        sample_linear_opaque(input_img, xx, yy),
209                        sample_linear_opaque(input_img, xx + xxa / 2.0, yy + yya / 2.0),
210                        sample_linear_opaque(input_img, xx - xxa / 2.0, yy - yya / 2.0),
211                        sample_linear_opaque(input_img, xx + xxb / 2.0, yy + yyb / 2.0),
212                        sample_linear_opaque(input_img, xx - xxb / 2.0, yy - yyb / 2.0),
213                        sample_linear_opaque(
214                            input_img,
215                            xx + (xxa + xxb) / 2.0,
216                            yy + (yya + yyb) / 2.0,
217                        ),
218                        sample_linear_opaque(
219                            input_img,
220                            xx + (xxa - xxb) / 2.0,
221                            yy + (yya - yyb) / 2.0,
222                        ),
223                        sample_linear_opaque(
224                            input_img,
225                            xx - (xxa + xxb) / 2.0,
226                            yy - (yya + yyb) / 2.0,
227                        ),
228                        sample_linear_opaque(
229                            input_img,
230                            xx - (xxa - xxb) / 2.0,
231                            yy - (yya - yyb) / 2.0,
232                        ),
233                    )
234                } else {
235                    (
236                        sample_linear_premultiplied(input_img, xx, yy),
237                        sample_linear_premultiplied(input_img, xx + xxa / 2.0, yy + yya / 2.0),
238                        sample_linear_premultiplied(input_img, xx - xxa / 2.0, yy - yya / 2.0),
239                        sample_linear_premultiplied(input_img, xx + xxb / 2.0, yy + yyb / 2.0),
240                        sample_linear_premultiplied(input_img, xx - xxb / 2.0, yy - yyb / 2.0),
241                        sample_linear_premultiplied(
242                            input_img,
243                            xx + (xxa + xxb) / 2.0,
244                            yy + (yya + yyb) / 2.0,
245                        ),
246                        sample_linear_premultiplied(
247                            input_img,
248                            xx + (xxa - xxb) / 2.0,
249                            yy + (yya - yyb) / 2.0,
250                        ),
251                        sample_linear_premultiplied(
252                            input_img,
253                            xx - (xxa + xxb) / 2.0,
254                            yy - (yya + yyb) / 2.0,
255                        ),
256                        sample_linear_premultiplied(
257                            input_img,
258                            xx - (xxa - xxb) / 2.0,
259                            yy - (yya - yyb) / 2.0,
260                        ),
261                    )
262                };
263
264                let sc = (mo.a * 4.0
265                    + (m2.a + m3.a + m4.a + m5.a) * 2.0
266                    + (m2b.a + m3b.a + m4b.a + m5b.a))
267                    / 16.0;
268
269                let mut mo =
270                    (mo * 4.0 + (m2 + m3 + m4 + m5) * 2.0 + (m2b + m3b + m4b + m5b)) / 16.0;
271
272                if sc > 0.0001 {
273                    mo.postblend(sc);
274                } else {
275                    mo.r = 0.0;
276                    mo.g = 0.0;
277                    mo.b = 0.0;
278                    mo.a = 0.0;
279                }
280
281                let m_r = mo.r as i32;
282                let m_g = mo.g as i32;
283                let m_b = mo.b as i32;
284                let m_a = mo.a as i32;
285
286                let result_r = dark_pixel[0] as i32
287                    + ((light_pixel[0] as i32 - dark_pixel[0] as i32) * m_r) / 255;
288                let result_g = dark_pixel[1] as i32
289                    + ((light_pixel[1] as i32 - dark_pixel[1] as i32) * m_g) / 255;
290                let result_b = dark_pixel[2] as i32
291                    + ((light_pixel[2] as i32 - dark_pixel[2] as i32) * m_b) / 255;
292                let mut result_a = m_a;
293
294                if (dark_pixel[3] as i32) < result_a {
295                    result_a = dark_pixel[3] as i32;
296                }
297
298                if result_a > 0 {
299                    let idx = x * 4;
300                    if result_a > 250 {
301                        row[idx] = result_r.clamp(0, 255) as u8;
302                        row[idx + 1] = result_g.clamp(0, 255) as u8;
303                        row[idx + 2] = result_b.clamp(0, 255) as u8;
304                    } else {
305                        row[idx] = (row[idx] as i32
306                            + ((result_r - row[idx] as i32) * result_a) / 255)
307                            .clamp(0, 255) as u8;
308                        row[idx + 1] = (row[idx + 1] as i32
309                            + ((result_g - row[idx + 1] as i32) * result_a) / 255)
310                            .clamp(0, 255) as u8;
311                        row[idx + 2] = (row[idx + 2] as i32
312                            + ((result_b - row[idx + 2] as i32) * result_a) / 255)
313                            .clamp(0, 255) as u8;
314                    }
315                }
316            }
317        });
318
319        Ok(())
320    }
321
322    fn add_simple(&mut self, input: &Input) -> Result<()> {
323        let mapping = self.mapping.as_ref().ok_or(Error::NoMapping)?;
324
325        let active_scale = input.in_scale as f64 / 2.0;
326        let off = ((mapping.map2.height() as i32 - mapping.light.height() as i32) / 2) as i32;
327
328        if (mapping.map1.width() as i32) < (mapping.light.width() as i32) {
329            return Ok(());
330        }
331
332        let out_w = self.out.width();
333        let out_h = self.out.height();
334
335        for y in 0..out_h {
336            for x in 0..out_w {
337                let light_pixel = mapping.light.get_pixel(x, y);
338                let dark_pixel = mapping.dark.get_pixel(x, y);
339
340                let map_y = (y as i32 + off) as u32;
341                if map_y >= mapping.map1.height() || map_y >= mapping.map2.height() {
342                    continue;
343                }
344
345                let map_pixel = mapping.map1.get_pixel(x, map_y);
346                let sel_pixel = mapping.map2.get_pixel(x, map_y);
347
348                let b_val = map_pixel[2] as i32;
349                let ymod = b_val / 16;
350                let xmod = b_val % 16;
351                let act = map_pixel[3] as i32;
352                let x1 = (map_pixel[0] as f64 + 256.0 * xmod as f64 - RR) * input.xs;
353                let y1 = (map_pixel[1] as f64 + 256.0 * ymod as f64 - RR) * input.ys;
354
355                let xx_rot = input.xa * x1 + input.ya * y1;
356                let yy_rot = -input.ya * x1 + input.xa * y1;
357                let xx = input.in_x0 + active_scale * (xx_rot + RR + input.xo) / RR;
358                let yy = input.in_y0 + active_scale * (yy_rot + RR + input.yo) / RR;
359
360                let m = input.safe_pixel(xx as i32, yy as i32);
361
362                if sel_pixel[0] == input.layer && act > 25 {
363                    let result_r = dark_pixel[0] as i32
364                        + ((light_pixel[0] as i32 - dark_pixel[0] as i32) * m[0] as i32) / 255;
365                    let result_g = dark_pixel[1] as i32
366                        + ((light_pixel[1] as i32 - dark_pixel[1] as i32) * m[1] as i32) / 255;
367                    let result_b = dark_pixel[2] as i32
368                        + ((light_pixel[2] as i32 - dark_pixel[2] as i32) * m[2] as i32) / 255;
369                    let mut result_a = m[3] as i32;
370
371                    if (dark_pixel[3] as i32) < result_a {
372                        result_a = dark_pixel[3] as i32;
373                    }
374
375                    if result_a > 0 {
376                        let out_pixel = self.out.get_pixel_mut(x, y);
377                        if result_a > 250 {
378                            out_pixel[0] = result_r.clamp(0, 255) as u8;
379                            out_pixel[1] = result_g.clamp(0, 255) as u8;
380                            out_pixel[2] = result_b.clamp(0, 255) as u8;
381                        } else {
382                            out_pixel[0] = (out_pixel[0] as i32
383                                + ((result_r - out_pixel[0] as i32) * result_a) / 255)
384                                .clamp(0, 255) as u8;
385                            out_pixel[1] = (out_pixel[1] as i32
386                                + ((result_g - out_pixel[1] as i32) * result_a) / 255)
387                                .clamp(0, 255) as u8;
388                            out_pixel[2] = (out_pixel[2] as i32
389                                + ((result_b - out_pixel[2] as i32) * result_a) / 255)
390                                .clamp(0, 255) as u8;
391                        }
392                    }
393                }
394            }
395        }
396
397        Ok(())
398    }
399
400    /// Edge smoothing post-process
401    fn post(&mut self) -> Result<()> {
402        RENDER_COUNT.fetch_add(1, Ordering::SeqCst);
403
404        let mapping = self.mapping.as_ref().ok_or(Error::NoMapping)?;
405        let w = self.out.width() as i32;
406        let h = self.out.height() as i32;
407
408        if (mapping.map1.width() as i32) < (mapping.light.width() as i32) {
409            return Ok(());
410        }
411
412        if mapping.map1.width() != mapping.neutral.width() {
413            return Ok(());
414        }
415        if mapping.map2.width() != mapping.neutral.width() {
416            return Ok(());
417        }
418
419        // The edge-smoothing pass only does work for pixels whose `sel` map
420        // channel 2 (the "smooth" flag) is nonzero. When that channel is zero
421        // everywhere (as it is for most shipped templates), the entire pass is
422        // a no-op — but it still clones the full output image and scans every
423        // pixel. Detect that cheaply and bail out early to avoid the clone
424        // and the scan.
425        if !mapping.has_nonzero_smoothing() {
426            return Ok(());
427        }
428
429        let pre = self.out.clone();
430
431        for y in 0..h {
432            for x in 0..w {
433                let xu = x as u32;
434                let yu = y as u32;
435                let sel_pixel = mapping.map2.get_pixel(xu, yu);
436
437                if sel_pixel[2] > 0 {
438                    if x > 0 && y > 0 && x < w - 1 && y < h - 1 {
439                        let back1 = pre.get_pixel((x - 1) as u32, yu);
440                        let idx1 = mapping.map2.get_pixel((x - 1) as u32, yu);
441
442                        let back2 = pre.get_pixel((x + 1) as u32, yu);
443                        let idx2 = mapping.map2.get_pixel((x + 1) as u32, yu);
444
445                        let back3 = pre.get_pixel(xu, (y - 1) as u32);
446                        let idx3 = mapping.map2.get_pixel(xu, (y - 1) as u32);
447
448                        let back4 = pre.get_pixel(xu, (y + 1) as u32);
449                        let idx4 = mapping.map2.get_pixel(xu, (y + 1) as u32);
450
451                        let mut total = Pixer::new();
452                        let mut ct = 0.0;
453
454                        if idx1[2] < 127 {
455                            total.add_rgba(back1);
456                            ct += 1.0;
457                        }
458                        if idx2[2] < 127 {
459                            total.add_rgba(back2);
460                            ct += 1.0;
461                        }
462                        if idx3[2] < 127 {
463                            total.add_rgba(back3);
464                            ct += 1.0;
465                        }
466                        if idx4[2] < 127 {
467                            total.add_rgba(back4);
468                            ct += 1.0;
469                        }
470
471                        if ct > 0.5 {
472                            total.div(ct);
473                            let out_pixel = self.out.get_pixel_mut(xu, yu);
474                            out_pixel[0] = total.r.clamp(0.0, 255.0) as u8;
475                            out_pixel[1] = total.g.clamp(0.0, 255.0) as u8;
476                            out_pixel[2] = total.b.clamp(0.0, 255.0) as u8;
477                        }
478                    }
479                }
480            }
481        }
482
483        Ok(())
484    }
485
486    pub fn apply(&mut self, inputs: &Inputs) -> Result<()> {
487        self.apply_scaled(inputs, -1, -1)
488    }
489
490    pub fn apply_scaled(&mut self, inputs: &Inputs, w: i32, h: i32) -> Result<()> {
491        self.pre()?;
492
493        match self.quality {
494            RenderQuality::None => {}
495            RenderQuality::Simple => {
496                for input in inputs.iter() {
497                    self.add_simple(input)?;
498                }
499            }
500            RenderQuality::Sampled => {
501                for input in inputs.iter() {
502                    self.add(input)?;
503                }
504            }
505        }
506
507        self.post()?;
508
509        if w > 0 && h > 0 && (w != self.out.width() as i32 || h != self.out.height() as i32) {
510            let wi = self.out.width() as i32;
511            let hi = self.out.height() as i32;
512
513            let fi = wi as f64 / hi as f64;
514            let f = w as f64 / h as f64;
515
516            let (xo, yo, wo, ho) = if fi > f + 0.001 {
517                let wo = w;
518                let ho = (wo as f64 / fi) as i32;
519                let yo = (h - ho) / 2;
520                (0, yo, wo, ho)
521            } else if fi < f - 0.001 {
522                let ho = h;
523                let wo = (h as f64 * fi) as i32;
524                let xo = (w - wo) / 2;
525                (xo, 0, wo, ho)
526            } else {
527                (0, 0, w, h)
528            };
529
530            let mut scaled = RgbaImage::from_pixel(w as u32, h as u32, Rgba([255, 255, 255, 0]));
531
532            for dy in 0..ho {
533                for dx in 0..wo {
534                    let sx = (dx as f64 * wi as f64 / wo as f64) as u32;
535                    let sy = (dy as f64 * hi as f64 / ho as f64) as u32;
536                    let sx = sx.min(self.out.width() - 1);
537                    let sy = sy.min(self.out.height() - 1);
538                    let pixel = *self.out.get_pixel(sx, sy);
539                    scaled.put_pixel((xo + dx) as u32, (yo + dy) as u32, pixel);
540                }
541            }
542
543            for pixel in scaled.pixels_mut() {
544                pixel[3] = 255;
545            }
546
547            self.out_scaled = Some(scaled);
548            self.out = RgbaImage::new(1, 1);
549        }
550
551        Ok(())
552    }
553
554    pub fn get(&self) -> &RgbaImage {
555        self.out_scaled.as_ref().unwrap_or(&self.out)
556    }
557
558    pub fn get_mut(&mut self) -> &mut RgbaImage {
559        if self.out_scaled.is_some() { self.out_scaled.as_mut().unwrap() } else { &mut self.out }
560    }
561
562    pub fn save<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
563        self.get().save(path)?;
564        Ok(())
565    }
566
567    pub fn render_count() -> usize {
568        RENDER_COUNT.load(Ordering::SeqCst)
569    }
570
571    pub fn get_cloud(&self, input: &Input) -> Result<Vec<CloudPoint>> {
572        let mapping = self.mapping.as_ref().ok_or(Error::NoMapping)?;
573
574        let active_scale = input.in_scale as f64 / 2.0;
575        let off = ((mapping.map2.height() as i32 - mapping.light.height() as i32) / 2) as i32;
576
577        if (mapping.map1.width() as i32) < (mapping.light.width() as i32) {
578            return Ok(Vec::new());
579        }
580
581        let mut cloud = Vec::new();
582        let w = mapping.light.width();
583        let h = mapping.light.height();
584
585        for y in 0..h {
586            for x in 0..w {
587                let light_pixel = mapping.light.get_pixel(x, y);
588                let dark_pixel = mapping.dark.get_pixel(x, y);
589
590                let map_y = (y as i32 + off) as u32;
591                if map_y >= mapping.map1.height() || map_y >= mapping.map2.height() {
592                    continue;
593                }
594
595                let map_pixel = mapping.map1.get_pixel(x, map_y);
596                let sel_pixel = mapping.map2.get_pixel(x, map_y);
597
598                let b_val = map_pixel[2] as i32;
599                let ymod = b_val / 16;
600                let xmod = b_val % 16;
601                let act = map_pixel[3] as i32;
602                let x1 = (map_pixel[0] as f64 + 256.0 * xmod as f64 - RR) * input.xs;
603                let y1 = (map_pixel[1] as f64 + 256.0 * ymod as f64 - RR) * input.ys;
604
605                let xx_rot = input.xa * x1 + input.ya * y1;
606                let yy_rot = -input.ya * x1 + input.xa * y1;
607                let xx = input.in_x0 + active_scale * (xx_rot + RR + input.xo) / RR;
608                let yy = input.in_y0 + active_scale * (yy_rot + RR + input.yo) / RR;
609
610                if sel_pixel[0] != 0 && act > 25 {
611                    let del = 5i32;
612                    let r_diff = (light_pixel[0] as i32 - dark_pixel[0] as i32).abs();
613                    let g_diff = (light_pixel[1] as i32 - dark_pixel[1] as i32).abs();
614                    let b_diff = (light_pixel[2] as i32 - dark_pixel[2] as i32).abs();
615
616                    if (r_diff > del || g_diff > del || b_diff > del)
617                        && dark_pixel[3] > 100
618                        && light_pixel[3] > 100
619                    {
620                        cloud.push(CloudPoint { layer: sel_pixel[0], x: xx, y: yy });
621                    }
622                }
623            }
624        }
625
626        Ok(cloud)
627    }
628
629    pub fn auto_zoom_input(&self, input: &mut Input) -> Result<bool> {
630        let mapping = self.mapping.as_ref().ok_or(Error::NoMapping)?;
631
632        let active_scale = input.in_scale as f64 / 2.0;
633        let off = ((mapping.map2.height() as i32 - mapping.light.height() as i32) / 2) as i32;
634
635        if (mapping.map1.width() as i32) < (mapping.light.width() as i32) {
636            return Ok(false);
637        }
638
639        let mut x_min = input.width() as f64;
640        let mut x_max = 0.0f64;
641        let mut y_min = input.height() as f64;
642        let mut y_max = 0.0f64;
643
644        let w = mapping.light.width();
645        let h = mapping.light.height();
646
647        for y in 0..h {
648            for x in 0..w {
649                let map_y = (y as i32 + off) as u32;
650                if map_y >= mapping.map1.height() || map_y >= mapping.map2.height() {
651                    continue;
652                }
653
654                let map_pixel = mapping.map1.get_pixel(x, map_y);
655                let sel_pixel = mapping.map2.get_pixel(x, map_y);
656
657                let b_val = map_pixel[2] as i32;
658                let ymod = b_val / 16;
659                let xmod = b_val % 16;
660                let act = map_pixel[3] as i32;
661                let x1 = (map_pixel[0] as f64 + 256.0 * xmod as f64 - RR) * input.xs;
662                let y1 = (map_pixel[1] as f64 + 256.0 * ymod as f64 - RR) * input.ys;
663
664                let xx_rot = input.xa * x1 + input.ya * y1;
665                let yy_rot = -input.ya * x1 + input.xa * y1;
666                let xx = input.in_x0 + active_scale * (xx_rot + RR + input.xo) / RR;
667                let yy = input.in_y0 + active_scale * (yy_rot + RR + input.yo) / RR;
668
669                // Only check layer 1 for auto-zoom
670                // Only check layer 1 for auto-zoom
671                if sel_pixel[0] == 1 && act > 25 {
672                    if xx < x_min {
673                        x_min = xx;
674                    }
675                    if xx > x_max {
676                        x_max = xx;
677                    }
678                    if yy < y_min {
679                        y_min = yy;
680                    }
681                    if yy > y_max {
682                        y_max = yy;
683                    }
684                }
685            }
686        }
687
688        let hh = input.height() as f64;
689        if y_max - y_min < hh * 0.75 {
690            input.xs *= 2.0;
691            input.ys *= 2.0;
692            return Ok(true);
693        }
694
695        Ok(false)
696    }
697
698    pub fn auto_zoom(&self, inputs: &mut Inputs) -> Result<bool> {
699        let mut changed = false;
700        for input in inputs.iter_mut() {
701            if self.auto_zoom_input(input)? {
702                changed = true;
703            }
704        }
705        Ok(changed)
706    }
707}