1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
use crate::choices::ScalingMode;
use image::{imageops::FilterType, DynamicImage, ImageBuffer, Luma};
/// Provides image scaling operations with various resizing methods
///
/// Supports multiple scaling modes to fit images into target regions while preserving
/// aspect ratio or filling space as needed. All operations work on 16-bit grayscale images.
pub struct ImageScaler;
impl ImageScaler {
/// Scales an image to fit within a specified region using the selected scaling mode
///
/// # Arguments
/// * `img` - Input 16-bit grayscale image to scale
/// * `start_pos` - (x, y) coordinates of region start point (top-left corner)
/// * `end_pos` - (x, y) coordinates of region end point (bottom-right corner)
/// * `scaling_mode` - [`ScalingMode`] strategy to use for resizing
///
/// # Returns
/// New image buffer sized to the region dimensions (width = |end_x - start_x|, height = |end_y - start_y|)
///
/// # Note
/// Region dimensions are enforced to be at least 10x10 pixels. Coordinates can be in any order
/// (top-left to bottom-right or bottom-right to top-left) since absolute differences are used.
///
/// # Scaling Modes
/// - **Stretch**: Ignores aspect ratio, stretches to exact region size
/// - **Fit**: Maintains aspect ratio, fits entirely within region with letterboxing
/// - **Fill**: Maintains aspect ratio, fills entire region with cropping
/// - **Center**: No scaling, centers original image with padding
/// - **Tile**: Repeats image in both directions like a mosaic
///
/// # Example
/// ```ignore
/// let scaled = ImageScaler::scale_image_to_region(
/// &input_img,
/// (100, 100),
/// (300, 200),
/// ScalingMode::Fit
/// );
/// ```
pub fn scale_image_to_region(
img: &ImageBuffer<Luma<u16>, Vec<u16>>,
start_pos: (i32, i32),
end_pos: (i32, i32),
scaling_mode: ScalingMode,
) -> ImageBuffer<Luma<u16>, Vec<u16>> {
let region_width = (end_pos.0 - start_pos.0).unsigned_abs();
let region_height = (end_pos.1 - start_pos.1).unsigned_abs();
let region_width = region_width.max(10);
let region_height = region_height.max(10);
let img_width = img.width();
let img_height = img.height();
println!(
"Original image: {}x{}, Target region: {}x{}",
img_width, img_height, region_width, region_height
);
match scaling_mode {
ScalingMode::Stretch => DynamicImage::ImageLuma16(img.clone())
.resize_exact(region_width, region_height, FilterType::Lanczos3)
.to_luma16(),
ScalingMode::Fit => {
let scale_x = region_width as f64 / img_width as f64;
let scale_y = region_height as f64 / img_height as f64;
let scale = scale_x.min(scale_y);
let new_width = (img_width as f64 * scale) as u32;
let new_height = (img_height as f64 * scale) as u32;
let scaled_img = DynamicImage::ImageLuma16(img.clone())
.resize(new_width, new_height, FilterType::Lanczos3)
.to_luma16();
let mut canvas =
ImageBuffer::from_pixel(region_width, region_height, Luma([255u16]));
let offset_x = (region_width - new_width) / 2;
let offset_y = (region_height - new_height) / 2;
for (x, y, pixel) in scaled_img.enumerate_pixels() {
if offset_x + x < region_width && offset_y + y < region_height {
canvas.put_pixel(offset_x + x, offset_y + y, *pixel);
}
}
canvas
}
ScalingMode::Fill => {
let scale_x = region_width as f64 / img_width as f64;
let scale_y = region_height as f64 / img_height as f64;
let scale = scale_x.max(scale_y);
let new_width = (img_width as f64 * scale) as u32;
let new_height = (img_height as f64 * scale) as u32;
let scaled_img = DynamicImage::ImageLuma16(img.clone())
.resize(new_width, new_height, FilterType::Lanczos3)
.to_luma16();
let crop_x = if new_width > region_width {
(new_width - region_width) / 2
} else {
0
};
let crop_y = if new_height > region_height {
(new_height - region_height) / 2
} else {
0
};
let mut result = ImageBuffer::new(region_width, region_height);
for (x, y, pixel) in result.enumerate_pixels_mut() {
let source_x = crop_x + x;
let source_y = crop_y + y;
if source_x < new_width && source_y < new_height {
*pixel = *scaled_img.get_pixel(source_x, source_y);
} else {
*pixel = Luma([255u16]);
}
}
result
}
ScalingMode::Center => {
let mut canvas =
ImageBuffer::from_pixel(region_width, region_height, Luma([255u16]));
let offset_x = if region_width > img_width {
(region_width - img_width) / 2
} else {
0
};
let offset_y = if region_height > img_height {
(region_height - img_height) / 2
} else {
0
};
for (x, y, pixel) in img.enumerate_pixels() {
let dest_x = offset_x + x;
let dest_y = offset_y + y;
if dest_x < region_width && dest_y < region_height {
canvas.put_pixel(dest_x, dest_y, *pixel);
}
}
canvas
}
ScalingMode::Tile => {
let mut canvas = ImageBuffer::new(region_width, region_height);
for (x, y, pixel) in canvas.enumerate_pixels_mut() {
let source_x = x % img_width;
let source_y = y % img_height;
*pixel = *img.get_pixel(source_x, source_y);
}
canvas
}
}
}
}