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; #[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 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 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 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 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 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}