3use image::GenericImageView;
5use crate::errors::ThumbnailError;
7/// Represents the target dimensions of the thumbnail.
8pub enum TargetDimension {
15 /// Fit the original dimensions into the target constraint
16 /// while maintaining the original aspect ratio.
17 fn fit(&self, orig_dims: (u32, u32)) -> (u32, u32) {
18 let (img_w, img_h) = orig_dims;
21 TargetDimension::MaxWidth(max_w) if max_w >= img_w => orig_dims,
22 TargetDimension::MaxHeight(max_h) if max_h >= img_h => orig_dims,
24 TargetDimension::MaxWidth(max_w) => (
26 (((max_w as f64) * (img_h as f64) / (img_w as f64)).round() as u32).max(1),
28 TargetDimension::MaxHeight(max_h) => (
29 (((max_h as f64) * (img_w as f64) / (img_h as f64)).round() as u32).max(1),
33 TargetDimension::BoundingBox(max_w, max_h) => {
34 // Determine constraint by comparing aspect ratio
35 if (max_w as f64) / (max_h as f64) >= (img_w as f64) / (img_h as f64) {
36 TargetDimension::MaxHeight(max_h).fit(orig_dims)
38 TargetDimension::MaxWidth(max_w).fit(orig_dims)
45/// Read the image at the given path and calculate its target thumbnail
48/// Leaves the image at its original size if it's already smaller than
49/// the target constraints.
50pub fn thumbnail_dimensions(
51 path: impl AsRef<Path>,
52 target: TargetDimension,
53) -> Result<(u32, u32), ThumbnailError> {
54 let img = image::open(path)?;
56 Ok(target.fit(img.dimensions()))
60mod test_thumbnail_dimensions {
63 macro_rules! target_fit_tests {
64 ($($name:ident: $value:expr,)*) => {
68 let (input, target, expected) = $value;
70 let dimensions = target.fit(input);
71 assert_eq!(dimensions, expected);
78 width_lt: ((100, 200), TargetDimension::MaxWidth(50), ( 50, 100)),
79 width_eq: ((100, 200), TargetDimension::MaxWidth(100), (100, 200)),
80 width_gt: ((100, 200), TargetDimension::MaxWidth(200), (100, 200)),
82 height_lt: ((100, 200), TargetDimension::MaxHeight(100), ( 50, 100)),
83 height_eq: ((100, 200), TargetDimension::MaxHeight(200), (100, 200)),
84 height_gt: ((100, 200), TargetDimension::MaxHeight(400), (100, 200)),
86 // bounding box which is larger than the image in one or both
88 bbox_larger_w: ((100, 200), TargetDimension::BoundingBox(500, 200), (100, 200)),
89 bbox_larger_h: ((100, 200), TargetDimension::BoundingBox(100, 500), (100, 200)),
90 bbox_larger_wh: ((100, 200), TargetDimension::BoundingBox(500, 500), (100, 200)),
92 // bounding box with an equal aspect ratio to the image
93 bbox_equal_lt: ((100, 200), TargetDimension::BoundingBox( 50, 100), ( 50, 100)),
94 bbox_equal_eq: ((100, 200), TargetDimension::BoundingBox(100, 200), (100, 200)),
95 bbox_equal_gt: ((100, 200), TargetDimension::BoundingBox(200, 400), (100, 200)),
97 // bounding box which is skinnier than the image
98 bbox_skinnier_lt: ((100, 200), TargetDimension::BoundingBox(10, 100), (10, 20)),
99 bbox_skinnier_eq: ((100, 200), TargetDimension::BoundingBox(20, 200), (20, 40)),
100 bbox_skinnier_gt: ((100, 200), TargetDimension::BoundingBox(40, 400), (40, 80)),
102 // bounding box which is wider than the image
103 bbox_wider_lt: ((100, 200), TargetDimension::BoundingBox(100, 20), (10, 20)),
104 bbox_wider_eq: ((100, 200), TargetDimension::BoundingBox(200, 40), (20, 40)),
105 bbox_wider_gt: ((100, 200), TargetDimension::BoundingBox(400, 80), (40, 80)),
107 // case to ensure we do floating point division correctly, and
108 // aren't making rounding errors
109 fp_width: ((500, 333), TargetDimension::MaxWidth(300), (300, 200)),
110 fp_height: ((333, 500), TargetDimension::MaxHeight(300), (200, 300)),
112 // extreme images that could be rounded to zero
113 extreme_width: ((10000, 1), TargetDimension::MaxWidth(10), (10, 1)),
114 extreme_height: ((1, 10000), TargetDimension::MaxHeight(10), (1, 10)),
118 fn errors_if_image_does_not_exist() {
119 let p = "src/tests/doesnotexist.png";
121 let target = TargetDimension::MaxWidth(50);
123 let dimensions = thumbnail_dimensions(&p, target);
124 assert!(dimensions.is_err());
128 fn errors_if_cannot_read_image() {
131 let target = TargetDimension::MaxWidth(50);
133 let dimensions = thumbnail_dimensions(&p, target);
134 assert!(dimensions.is_err());