Skip to main content

fastpack_core/imaging/
scale.rs

1use anyhow::{Result, bail};
2use image::{GenericImageView, imageops::FilterType};
3
4use crate::types::{
5    config::ScaleMode,
6    rect::{Point, Size, SourceRect},
7    sprite::{NinePatch, Sprite},
8};
9
10/// Produce a copy of `sprite` scaled by `factor` using the given resampling mode.
11///
12/// `factor < 1.0` shrinks; `factor > 1.0` enlarges. When `factor` is exactly
13/// 1.0 the sprite is returned as-is (cloned, no resampling).
14///
15/// Pixel art modes (`Scale2x`, `Scale3x`, `Hq2x`, `Eagle`) are not yet
16/// implemented and return an error; use `Smooth` or `Fast` for now.
17pub fn scale_sprite(sprite: &Sprite, factor: f32, mode: ScaleMode) -> Result<Sprite> {
18    if (factor - 1.0).abs() < f32::EPSILON {
19        return Ok(sprite.clone());
20    }
21
22    match mode {
23        ScaleMode::Scale2x | ScaleMode::Scale3x | ScaleMode::Hq2x | ScaleMode::Eagle => {
24            bail!(
25                "pixel art scale modes (scale2x/scale3x/hq2x/eagle) are not yet implemented; \
26                 use smooth or fast"
27            );
28        }
29        ScaleMode::Smooth | ScaleMode::Fast => {}
30    }
31
32    let filter = match mode {
33        ScaleMode::Smooth => FilterType::Lanczos3,
34        ScaleMode::Fast => FilterType::Nearest,
35        _ => unreachable!(),
36    };
37
38    let (w, h) = sprite.image.dimensions();
39    let new_w = ((w as f32 * factor).round() as u32).max(1);
40    let new_h = ((h as f32 * factor).round() as u32).max(1);
41    let scaled_image = sprite.image.resize_exact(new_w, new_h, filter);
42
43    let original_size = Size {
44        w: ((sprite.original_size.w as f32 * factor).round() as u32).max(1),
45        h: ((sprite.original_size.h as f32 * factor).round() as u32).max(1),
46    };
47
48    let trim_rect = sprite.trim_rect.as_ref().map(|tr| SourceRect {
49        x: (tr.x as f32 * factor).round() as i32,
50        y: (tr.y as f32 * factor).round() as i32,
51        w: ((tr.w as f32 * factor).round() as u32).max(1),
52        h: ((tr.h as f32 * factor).round() as u32).max(1),
53    });
54
55    let nine_patch = sprite.nine_patch.map(|np| NinePatch {
56        top: (np.top as f32 * factor).round() as u32,
57        right: (np.right as f32 * factor).round() as u32,
58        bottom: (np.bottom as f32 * factor).round() as u32,
59        left: (np.left as f32 * factor).round() as u32,
60    });
61
62    let polygon = sprite.polygon.as_ref().map(|pts| {
63        pts.iter()
64            .map(|p| Point {
65                x: p.x * factor,
66                y: p.y * factor,
67            })
68            .collect()
69    });
70
71    Ok(Sprite {
72        id: sprite.id.clone(),
73        source_path: sprite.source_path.clone(),
74        image: scaled_image,
75        trim_rect,
76        original_size,
77        polygon,
78        nine_patch,
79        pivot: sprite.pivot,
80        content_hash: sprite.content_hash,
81        extrude: (sprite.extrude as f32 * factor).round() as u32,
82        alias_of: sprite.alias_of.clone(),
83    })
84}