image_overlay/
overlay.rs

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