simple_blit/
funcs.rs

1use crate::{point, size, Point, Size, Surface, SurfaceMut};
2
3/// Transformations that can be applied when blitting.
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6#[non_exhaustive]
7pub enum Transform {
8    /// Scales the destination.
9    /// Only upscaling is supported.
10    UpScale {
11        /// Scale x factor.
12        x: u32,
13        /// Scale y factor.
14        y: u32,
15    },
16
17    /// Rotates the destination 90 degrees clockwise.
18    Rotate90Cw,
19    /// Rotates the destination 90 degrees counter-clockwise.
20    Rotate90Ccw,
21    /// Rotates the destination 180 degrees.
22    Rotate180,
23
24    /// Flips the destination horizontally.
25    FlipHorizontal,
26    /// Flips the destination vertically.
27    FlipVertical,
28    /// /// Flips the destination horizontally and vertically.
29    FlipBoth,
30}
31
32impl Transform {
33    #[inline]
34    #[allow(dead_code)]
35    fn apply((pt, size): (Point, Size), this: &Self) -> (Point, Size) {
36        use Transform::*;
37
38        let pt = match this {
39            UpScale { x, y } => point(pt.x * x, pt.y * y),
40
41            FlipHorizontal => point(reversed(pt.x, size.x), pt.y),
42            FlipVertical => point(pt.x, reversed(pt.y, size.y)),
43            FlipBoth => point(reversed(pt.x, size.x), reversed(pt.y, size.y)),
44
45            Rotate90Ccw => point(pt.y, reversed(pt.x, size.x)),
46            Rotate90Cw => point(reversed(pt.y, size.y), pt.x),
47            Rotate180 => point(reversed(pt.x, size.x), reversed(pt.y, size.y)),
48        };
49
50        (pt, Self::apply_size(size, this))
51    }
52
53    #[inline]
54    fn unapply((pt, size): (Point, Size), this: &Self) -> (Point, Size) {
55        use Transform::*;
56
57        let pt = match this {
58            UpScale { x, y } => point(pt.x / x, pt.y / y),
59
60            // unchanged
61            FlipHorizontal => point(reversed(pt.x, size.x), pt.y),
62            FlipVertical => point(pt.x, reversed(pt.y, size.y)),
63            FlipBoth => point(reversed(pt.x, size.x), reversed(pt.y, size.y)),
64            Rotate180 => point(reversed(pt.x, size.x), reversed(pt.y, size.y)),
65
66            // swapped between each other
67            Rotate90Cw => point(pt.y, reversed(pt.x, size.x)),
68            Rotate90Ccw => point(reversed(pt.y, size.y), pt.x),
69        };
70
71        (pt, Self::unapply_size(size, this))
72    }
73
74    #[inline]
75    fn apply_size(s: Size, this: &Self) -> Size {
76        use Transform::*;
77
78        match this {
79            UpScale { x, y } => size(s.x * x, s.y * y),
80            Rotate90Cw | Rotate90Ccw => size(s.y, s.x),
81            _ => s,
82        }
83    }
84
85    #[inline]
86    fn unapply_size(s: Size, this: &Self) -> Size {
87        use Transform::*;
88
89        match this {
90            UpScale { x, y } => size(s.x / x, s.y / y),
91            Rotate90Cw | Rotate90Ccw => size(s.y, s.x),
92            _ => s,
93        }
94    }
95}
96
97#[inline]
98fn reversed(coord: u32, size: u32) -> u32 {
99    size.saturating_sub(coord).saturating_sub(1)
100}
101
102/// Blit part of one surface to another (generalized function).
103///
104/// You can use `sub_surface` or `offset_surface` functions to limit the copied area.
105/// `f` is called for each pair of values, the last argument is the value's source position.
106/// The transforms are done in order.
107pub fn blit_with<D, S>(
108    mut dest: impl SurfaceMut<D>,
109    src: impl Surface<S>,
110    transforms: &[Transform],
111    mut func: impl FnMut(&mut D, &S, Point),
112) {
113    let copy_size = src.surface_size();
114    let transformed_copy_size = transforms.iter().fold(copy_size, Transform::apply_size);
115
116    for iy in 0..transformed_copy_size.y {
117        for ix in 0..transformed_copy_size.x {
118            let dest_val_pos = point(ix, iy);
119
120            let dest = if let Some(dest) = dest.surface_get_mut(dest_val_pos) {
121                dest
122            } else {
123                continue;
124            };
125
126            let (src_val_pos, _untransformed_copy_size) = transforms
127                .iter()
128                .rev()
129                .fold((point(ix, iy), transformed_copy_size), Transform::unapply);
130
131            let src = if let Some(src) = src.surface_get(src_val_pos) {
132                src
133            } else {
134                continue;
135            };
136
137            (func)(dest, src, src_val_pos);
138        }
139    }
140}
141
142/// Blit part of one surface to another, cloning the values.
143///
144/// You can use `sub_surface` or `offset_surface` functions to limit the copied area.
145/// The transforms are done in order.
146#[inline]
147pub fn blit<T: Clone>(dest: impl SurfaceMut<T>, src: impl Surface<T>, transforms: &[Transform]) {
148    blit_with(dest, src, transforms, |dest, src, _| {
149        dest.clone_from(src);
150    });
151}
152
153/// Blit part of one surface to another, ignoring the `mask` values.
154///
155/// You can use `sub_surface` or `offset_surface` functions to limit the copied area.
156/// The transforms are done in order.
157#[inline]
158pub fn blit_masked<T: Clone + PartialEq>(
159    dest: impl SurfaceMut<T>,
160    src: impl Surface<T>,
161    transforms: &[Transform],
162    mask: &T,
163) {
164    blit_with(dest, src, transforms, |dest, src, _| {
165        if src != mask {
166            dest.clone_from(src);
167        }
168    });
169}
170
171/// Blit part of one surface to another, converting the values.
172///
173/// You can use `sub_surface` or `offset_surface` functions to limit the copied area.
174/// The transforms are done in order.
175#[inline]
176pub fn blit_convert<D: From<S>, S: Clone>(
177    dest: impl SurfaceMut<D>,
178    src: impl Surface<S>,
179    transforms: &[Transform],
180) {
181    blit_with(dest, src, transforms, |dest, src, _| {
182        *dest = D::from(src.clone());
183    });
184}