tuwunel_service/media/thumbnail/
dimension.rs1use std::{cmp::min, num::Saturating as Sat};
7
8use ruma::{UInt, media::Method};
9use tuwunel_core::{Result, checked, err, implement};
10
11#[derive(Debug)]
16pub struct Dim {
17 pub width: u32,
19
20 pub height: u32,
22
23 pub method: Method,
25}
26
27#[implement(Dim)]
32pub fn from_ruma(width: UInt, height: UInt, method: Option<Method>) -> Result<Self> {
33 let width = width
34 .try_into()
35 .map_err(|e| err!(Request(InvalidParam("Width is invalid: {e:?}"))))?;
36
37 let height = height
38 .try_into()
39 .map_err(|e| err!(Request(InvalidParam("Height is invalid: {e:?}"))))?;
40
41 Ok(Self::new(width, height, method))
42}
43
44#[implement(Dim)]
48#[inline]
49#[must_use]
50pub fn new(width: u32, height: u32, method: Option<Method>) -> Self {
51 Self {
52 width,
53 height,
54 method: method.unwrap_or(Method::Scale),
55 }
56}
57
58#[implement(Dim)]
63pub fn scaled(&self, image: &Self) -> Result<Self> {
64 let image_width = image.width;
65 let image_height = image.height;
66
67 let width = min(self.width, image_width);
68 let height = min(self.height, image_height);
69
70 let use_width = Sat(width) * Sat(image_height) < Sat(height) * Sat(image_width);
71
72 let x = if use_width {
73 let dividend = (Sat(height) * Sat(image_width)).0;
74 checked!(dividend / image_height)?
75 } else {
76 width
77 };
78
79 let y = if !use_width {
80 let dividend = (Sat(width) * Sat(image_height)).0;
81 checked!(dividend / image_width)?
82 } else {
83 height
84 };
85
86 Ok(Self {
87 width: x,
88 height: y,
89 method: Method::Scale,
90 })
91}
92
93#[implement(Dim)]
98pub fn is_passthrough(&self, source: &Self) -> Result<bool> {
99 if self.width > source.width || self.height > source.height {
100 return Ok(true);
101 }
102
103 let (width, height) = if self.crop() {
104 (self.width, self.height)
105 } else {
106 let scaled = self.scaled(source)?;
107
108 (scaled.width, scaled.height)
109 };
110
111 Ok(width == source.width && height == source.height)
112}
113
114#[implement(Dim)]
120#[must_use]
121pub fn normalized(&self) -> Self {
122 match (self.width, self.height) {
123 | (0..=32, 0..=32) => Self::new(32, 32, Some(Method::Crop)),
124 | (0..=96, 0..=96) => Self::new(96, 96, Some(Method::Crop)),
125 | (0..=320, 0..=240) => Self::new(320, 240, Some(Method::Scale)),
126 | (0..=640, 0..=480) => Self::new(640, 480, Some(Method::Scale)),
127 | (0..=800, 0..=600) => Self::largest(),
128 | _ => Self::default(),
129 }
130}
131
132#[implement(Dim)]
137#[inline]
138#[must_use]
139pub fn largest() -> Self { Self::new(800, 600, Some(Method::Scale)) }
140
141#[implement(Dim)]
146#[inline]
147#[must_use]
148pub fn crop(&self) -> bool { self.method == Method::Crop }
149
150#[implement(Dim)]
156#[inline]
157#[must_use]
158pub fn is_original(&self) -> bool { self.width == 0 && self.height == 0 }
159
160impl Default for Dim {
161 #[inline]
162 fn default() -> Self {
163 Self {
164 width: 0,
165 height: 0,
166 method: Method::Scale,
167 }
168 }
169}