imageproc/
seam_carving.rs1use crate::definitions::{HasBlack, Image};
7use crate::gradients::sobel_gradient_map;
8use crate::map::{map_colors, WithChannel};
9use image::{GrayImage, Luma, Pixel, Rgb};
10use std::cmp::min;
11
12pub struct VerticalSeam(Vec<u32>);
14
15pub fn shrink_width<P>(image: &Image<P>, target_width: u32) -> Image<P>
23where
25 P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16>,
26 <P as WithChannel<u16>>::Pixel: HasBlack,
27{
28 assert!(
29 target_width <= image.width(),
30 "target_width must be <= input image width"
31 );
32
33 let iterations = image.width() - target_width;
34 let mut result = image.clone();
35
36 for _ in 0..iterations {
37 let seam = find_vertical_seam(&result);
38 result = remove_vertical_seam(&result, &seam);
39 }
40
41 result
42}
43
44pub fn find_vertical_seam<P>(image: &Image<P>) -> VerticalSeam
47where
48 P: Pixel<Subpixel = u8> + WithChannel<u16> + WithChannel<i16>,
49 <P as WithChannel<u16>>::Pixel: HasBlack,
50{
51 let (width, height) = image.dimensions();
52 assert!(
53 image.width() >= 2,
54 "Cannot find seams if image width is < 2"
55 );
56
57 let mut gradients = sobel_gradient_map(image, |p| {
58 let gradient_sum: u16 = p.channels().iter().sum();
59 let gradient_mean: u16 = gradient_sum / P::CHANNEL_COUNT as u16;
60 Luma([gradient_mean as u32])
61 });
62
63 for y in 1..height {
65 for x in 0..width {
66 set_path_energy(&mut gradients, x, y);
67 }
68 }
69
70 let mut min_x = 0;
72 let mut min_energy = gradients.get_pixel(0, height - 1)[0];
73
74 for x in 1..width {
75 let c = gradients.get_pixel(x, height - 1)[0];
76 if c < min_energy {
77 min_x = x;
78 min_energy = c;
79 }
80 }
81
82 let mut seam = Vec::with_capacity(height as usize);
83
84 seam.push(min_x);
85
86 let mut last_x = min_x;
87
88 for y in (1..height).rev() {
89 let above = gradients.get_pixel(last_x, y - 1)[0];
90 if last_x > 0 {
91 let left = gradients.get_pixel(last_x - 1, y - 1)[0];
92 if left < above {
93 min_x = last_x - 1;
94 min_energy = left;
95 }
96 }
97 if last_x < width - 1 {
98 let right = gradients.get_pixel(last_x + 1, y - 1)[0];
99 if right < min_energy {
100 min_x = last_x + 1;
101 min_energy = right;
102 }
103 }
104
105 last_x = min_x;
106 seam.push(min_x);
107 }
108
109 VerticalSeam(seam)
110}
111
112fn set_path_energy(path_energies: &mut Image<Luma<u32>>, x: u32, y: u32) {
114 let above = path_energies.get_pixel(x, y - 1)[0];
115 let mut min_energy = above;
116
117 if x > 0 {
118 let above_left = path_energies.get_pixel(x - 1, y - 1)[0];
119 min_energy = min(above, above_left);
120 }
121 if x < path_energies.width() - 1 {
122 let above_right = path_energies.get_pixel(x + 1, y - 1)[0];
123 min_energy = min(min_energy, above_right);
124 }
125
126 let current = path_energies.get_pixel(x, y)[0];
127 path_energies.put_pixel(x, y, Luma([min_energy + current]));
128}
129
130pub fn remove_vertical_seam<P>(image: &Image<P>, seam: &VerticalSeam) -> Image<P>
136where
137 P: Pixel,
138{
139 assert!(
140 seam.0.len() as u32 == image.height(),
141 "seam length does not match image height"
142 );
143
144 let (width, height) = image.dimensions();
145 let mut out = Image::new(width - 1, height);
146
147 for y in 0..height {
148 let x_seam = seam.0[(height - y - 1) as usize];
149 for x in 0..x_seam {
150 out.put_pixel(x, y, *image.get_pixel(x, y));
151 }
152 for x in (x_seam + 1)..width {
153 out.put_pixel(x - 1, y, *image.get_pixel(x, y));
154 }
155 }
156
157 out
158}
159
160pub fn draw_vertical_seams(image: &GrayImage, seams: &[VerticalSeam]) -> Image<Rgb<u8>> {
163 let height = image.height();
164
165 let mut offsets = vec![vec![]; height as usize];
166 let mut out = map_colors(image, |p| p.to_rgb());
167
168 for seam in seams {
169 for (y, x) in (0..height).rev().zip(&seam.0) {
170 let mut x_original = *x;
171 for o in &offsets[y as usize] {
172 if *o < *x {
173 x_original += 1;
174 }
175 }
176 out.put_pixel(x_original, y, Rgb([255, 0, 0]));
177 offsets[y as usize].push(x_original);
178 }
179 }
180
181 out
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::utils::gray_bench_image;
188 use test::{black_box, Bencher};
189
190 macro_rules! bench_shrink_width {
191 ($name:ident, side: $s:expr, shrink_by: $m:expr) => {
192 #[bench]
193 fn $name(b: &mut Bencher) {
194 let image = gray_bench_image($s, $s);
195 b.iter(|| {
196 let filtered = shrink_width(&image, $s - $m);
197 black_box(filtered);
198 })
199 }
200 };
201 }
202
203 bench_shrink_width!(bench_shrink_width_s100_r1, side: 100, shrink_by: 1);
204 bench_shrink_width!(bench_shrink_width_s100_r4, side: 100, shrink_by: 4);
205 bench_shrink_width!(bench_shrink_width_s100_r8, side: 100, shrink_by: 8);
206}