image_overlay/
overlay.rs

1use crate::{blend_mode::BlendMode, rng::FastUnsecurePrng, AsRgba};
2use image::{DynamicImage, GenericImage, GenericImageView};
3
4
5/// Overlay an image at a given coordinate (x, y) with blend mode.  
6/// 
7/// # Note
8/// Do NOT use this function for DynamicImage. Use "overlay_dyn_img" insted.  
9/// Because [GenericImage for DynamicImage looses precision](https://github.com/image-rs/image/issues/1592)  and slower.  
10/// 
11/// # Usage
12/// ``````
13/// use image::{ImageBuffer, Rgba, Luma};
14/// use image_overlay::{overlay, BlendMode};
15/// 
16/// let mut dest = ImageBuffer::<Rgba<u8>, Vec<u8>>::new(100, 100);
17/// let src = ImageBuffer::<Luma<u16>, Vec<u16>>::new(100, 100);
18/// 
19/// overlay(&mut dest, &src, 0, 0, BlendMode::default());
20/// ``````
21/// 
22/// # Features
23/// This crate uses f32 as an intermediate representation by default.  
24/// However, can perform higher-precision calculations using f64 by enabling the "f64" feature.  
25pub fn overlay<B, F>(bottom: &mut B, top: &F, x: i64, y: i64, blend_mode: BlendMode) 
26where 
27    B: GenericImage::<Pixel: AsRgba>,
28    F: GenericImageView::<Pixel: AsRgba>,
29{
30    let (
31        origin_bottom_x, 
32        origin_bottom_y, 
33        origin_top_x, 
34        origin_top_y, 
35        range_width, 
36        range_height
37    ) = overlay_bounds_ext(bottom.dimensions(), top.dimensions(), x, y);
38        
39    macro_rules! overlay {
40        (($bg: pat, $fg: pat) => $blend: expr) => {{
41            for y in 0..range_height {
42                for x in 0..range_width {
43                    let (bg_x, bg_y) = (origin_bottom_x + x, origin_bottom_y + y);
44                    let (fg_x, fg_y) = (origin_top_x + x, origin_top_y + y);
45
46                    #[cfg(debug_assertions)] {
47                        let (mut _bg, _fg) = (
48                            bottom.get_pixel(bg_x, bg_y),
49                            top.get_pixel(fg_x, fg_y),
50                        );
51
52                        let ($bg, $fg) = (&mut _bg, &_fg);
53                        $blend;
54
55                        bottom.put_pixel(bg_x, bg_y, _bg);
56                    }
57                    
58                    #[cfg(not(debug_assertions))] unsafe {
59                        let (mut _bg, _fg) = (
60                            bottom.unsafe_get_pixel(bg_x, bg_y),
61                            top.unsafe_get_pixel(fg_x, fg_y),
62                        );
63
64                        let ($bg, $fg) = (&mut _bg, &_fg);
65                        $blend;
66
67                        bottom.unsafe_put_pixel(bg_x, bg_y, _bg);
68                    }
69                }
70            }
71        }};
72    }
73
74    
75    use crate::blend::*;
76
77    match blend_mode {
78        BlendMode::Dissolve => {
79            // Ensure the same state if the top image size is the same.
80            let ref mut rng = FastUnsecurePrng::new(top.width(), top.height());
81
82            overlay!((bg, fg) => blend_dissolve(bg, fg, rng));
83        }
84        BlendMode::Normal => overlay!((bg, fg) => blend_normal(bg, fg)),
85        BlendMode::Darken => overlay!((bg, fg) => blend_darken(bg, fg)),
86        BlendMode::Multiply => overlay!((bg, fg) => blend_multiply(bg, fg)),
87        BlendMode::ColorBurn => overlay!((bg, fg) => blend_color_burn(bg, fg)),
88        BlendMode::LinearBurn => overlay!((bg, fg) => blend_linear_burn(bg, fg)),
89        BlendMode::Lighten => overlay!((bg, fg) => blend_lighten(bg, fg)),
90        BlendMode::Screen => overlay!((bg, fg) => blend_screen(bg, fg)),
91        BlendMode::ColorDodge => overlay!((bg, fg) => blend_color_dodge(bg, fg)),
92        BlendMode::LinearDodge => overlay!((bg, fg) => blend_linear_dodge(bg, fg)),
93        BlendMode::Overlay => overlay!((bg, fg) => blend_overlay(bg, fg)),
94        BlendMode::SoftLight => overlay!((bg, fg) => blend_soft_light(bg, fg)),
95        BlendMode::HardLight => overlay!((bg, fg) => blend_hard_light(bg, fg)),
96        BlendMode::VividLight => overlay!((bg, fg) => blend_vivid_light(bg, fg)),
97        BlendMode::LinearLight => overlay!((bg, fg) => blend_linear_light(bg, fg)),
98        BlendMode::PinLight => overlay!((bg, fg) => blend_pin_light(bg, fg)),
99        BlendMode::HardMix => overlay!((bg, fg) => blend_hard_mix(bg, fg)),
100        BlendMode::Difference => overlay!((bg, fg) => blend_difference(bg, fg)),
101        BlendMode::Exclusion => overlay!((bg, fg) => blend_exclusion(bg, fg)),
102        BlendMode::Subtract => overlay!((bg, fg) => blend_subtract(bg, fg)),
103        BlendMode::Divide => overlay!((bg, fg) => blend_divide(bg, fg)),
104        BlendMode::DarkerColor => overlay!((bg, fg) => blend_darker_color(bg, fg)),
105        BlendMode::LighterColor => overlay!((bg, fg) => blend_lighter_color(bg, fg)),
106        BlendMode::Hue => overlay!((bg, fg) => blend_hue(bg, fg)),
107        BlendMode::Saturation => overlay!((bg, fg) => blend_saturation(bg, fg)),
108        BlendMode::Color => overlay!((bg, fg) => blend_color(bg, fg)),
109        BlendMode::Luminosity => overlay!((bg, fg) => blend_luminosity(bg, fg)),
110    }
111}
112
113/// Overlay an image at a given coordinate (x, y) with blend mode. 
114///  
115/// See "overlay" for details.
116pub fn overlay_dyn_img(bottom: &mut DynamicImage, top: &DynamicImage, x: i64, y: i64, blend_mode: BlendMode) {
117    macro_rules! dynamic_map {
118        ($dynimage: expr, $image:pat_param, $action: expr) => {{
119            match $dynimage {
120                DynamicImage::ImageLuma8($image) => $action,
121                DynamicImage::ImageLuma16($image) => $action,
122                DynamicImage::ImageLumaA8($image) => $action,
123                DynamicImage::ImageLumaA16($image) => $action,
124                DynamicImage::ImageRgb8($image) => $action,
125                DynamicImage::ImageRgb16($image) => $action,
126                DynamicImage::ImageRgb32F($image) => $action,
127                DynamicImage::ImageRgba8($image) => $action,
128                DynamicImage::ImageRgba16($image) => $action,
129                DynamicImage::ImageRgba32F($image) => $action,
130                _ => unimplemented!(),
131            }
132        }};
133    }
134
135    dynamic_map!(bottom, bottom, {
136        dynamic_map!(top, top, {
137            overlay(bottom, top, x, y, blend_mode);
138        })
139    })
140}
141
142
143/// -------------------------------------------------------
144/// THIS FUNCTION IS COPIED FROM image crate (ver. 0.25.5)
145/// -------------------------------------------------------
146/// 
147/// 
148/// 
149/// Calculate the region that can be copied from top to bottom.
150///
151/// Given image size of bottom and top image, and a point at which we want to place the top image
152/// onto the bottom image, how large can we be? Have to wary of the following issues:
153/// * Top might be larger than bottom
154/// * Overflows in the computation
155/// * Coordinates could be completely out of bounds
156///
157/// The returned value is of the form:
158///
159/// `(origin_bottom_x, origin_bottom_y, origin_top_x, origin_top_y, x_range, y_range)`
160///
161/// The main idea is to do computations on i64's and then clamp to image dimensions.
162/// In particular, we want to ensure that all these coordinate accesses are safe:
163/// 1. `bottom.get_pixel(origin_bottom_x + [0..x_range), origin_bottom_y + [0..y_range))`
164/// 2. `top.get_pixel(origin_top_y + [0..x_range), origin_top_y + [0..y_range))`
165fn overlay_bounds_ext(
166    (bottom_width, bottom_height): (u32, u32),
167    (top_width, top_height): (u32, u32),
168    x: i64,
169    y: i64,
170) -> (u32, u32, u32, u32, u32, u32) {
171    // Return a predictable value if the two images don't overlap at all.
172    if x > i64::from(bottom_width)
173        || y > i64::from(bottom_height)
174        || x.saturating_add(i64::from(top_width)) <= 0
175        || y.saturating_add(i64::from(top_height)) <= 0
176    {
177        return (0, 0, 0, 0, 0, 0);
178    }
179
180    // Find the maximum x and y coordinates in terms of the bottom image.
181    let max_x = x.saturating_add(i64::from(top_width));
182    let max_y = y.saturating_add(i64::from(top_height));
183
184    // Clip the origin and maximum coordinates to the bounds of the bottom image.
185    // Casting to a u32 is safe because both 0 and `bottom_{width,height}` fit
186    // into 32-bits.
187    let max_inbounds_x = max_x.clamp(0, i64::from(bottom_width)) as u32;
188    let max_inbounds_y = max_y.clamp(0, i64::from(bottom_height)) as u32;
189    let origin_bottom_x = x.clamp(0, i64::from(bottom_width)) as u32;
190    let origin_bottom_y = y.clamp(0, i64::from(bottom_height)) as u32;
191
192    // The range is the difference between the maximum inbounds coordinates and
193    // the clipped origin. Unchecked subtraction is safe here because both are
194    // always positive and `max_inbounds_{x,y}` >= `origin_{x,y}` due to
195    // `top_{width,height}` being >= 0.
196    let x_range = max_inbounds_x - origin_bottom_x;
197    let y_range = max_inbounds_y - origin_bottom_y;
198
199    // If x (or y) is negative, then the origin of the top image is shifted by -x (or -y).
200    let origin_top_x = x.saturating_mul(-1).clamp(0, i64::from(top_width)) as u32;
201    let origin_top_y = y.saturating_mul(-1).clamp(0, i64::from(top_height)) as u32;
202
203    (
204        origin_bottom_x,
205        origin_bottom_y,
206        origin_top_x,
207        origin_top_y,
208        x_range,
209        y_range,
210    )
211}