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