okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

dawnrequests: fit history images over the provider's edge by appending image fit rows to the transcriptb2e29fd

main
2.3 KiB73 linesraw
1//! An image scaled down so neither edge passes a provider's limit, for
2//! screenshots and photos albedo did not render itself. The aspect ratio is
3//! kept; a JPEG stays a JPEG, anything else becomes a PNG.
4
5use image::codecs::jpeg::JpegEncoder;
6use image::imageops::FilterType;
7use image::{ImageFormat, ImageReader};
8use std::io::Cursor;
9
10pub struct Fitted {
11    pub bytes: Vec<u8>,
12    pub width: u32,
13    pub height: u32,
14    pub extension: &'static str,
15}
16
17pub fn fit(bytes: &[u8], edge: u32) -> Result<Fitted, String> {
18    let reader = ImageReader::new(Cursor::new(bytes))
19        .with_guessed_format()
20        .map_err(|e| e.to_string())?;
21    let format = reader.format().ok_or("not a PNG, JPEG, or WebP image")?;
22    let image = reader
23        .decode()
24        .map_err(|e| format!("cannot decode the image: {e}"))?;
25    let (width, height) = scaled(image.width(), image.height(), edge);
26    let resized = image.resize_exact(width, height, FilterType::Lanczos3);
27    let mut out = Cursor::new(Vec::new());
28    let extension = match format {
29        ImageFormat::Jpeg => {
30            JpegEncoder::new_with_quality(&mut out, 90)
31                .encode_image(&resized.to_rgb8())
32                .map_err(|e| e.to_string())?;
33            "jpg"
34        }
35        _ => {
36            resized
37                .write_to(&mut out, ImageFormat::Png)
38                .map_err(|e| e.to_string())?;
39            "png"
40        }
41    };
42    Ok(Fitted {
43        bytes: out.into_inner(),
44        width,
45        height,
46        extension,
47    })
48}
49
50/// The size `width` × `height` shrinks to so its longer edge is `edge`; an
51/// image already inside keeps its size.
52fn scaled(width: u32, height: u32, edge: u32) -> (u32, u32) {
53    let longer = width.max(height);
54    if longer <= edge {
55        return (width, height);
56    }
57    let shrink =
58        |side: u32| ((side as u64 * edge as u64 + longer as u64 / 2) / longer as u64).max(1) as u32;
59    (shrink(width), shrink(height))
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn the_longer_edge_lands_on_the_limit() {
68        assert_eq!(scaled(2304, 1212, 2000), (2000, 1052));
69        assert_eq!(scaled(1000, 8000, 2000), (250, 2000));
70        assert_eq!(scaled(1200, 800, 2000), (1200, 800));
71        assert_eq!(scaled(9000, 1, 2000), (2000, 1));
72    }
73}