diff --git a/axum_server/src/picture_generator/mod.rs b/axum_server/src/picture_generator/mod.rs index 2070e6f..6b06bf2 100644 --- a/axum_server/src/picture_generator/mod.rs +++ b/axum_server/src/picture_generator/mod.rs @@ -24,3 +24,4 @@ It should be used from the templates as well pub mod export_format; pub mod image_generator; pub mod picture_markup_generator; +pub mod resolutions; diff --git a/axum_server/src/picture_generator/picture_markup_generator.rs b/axum_server/src/picture_generator/picture_markup_generator.rs index 1ca1860..6137c8f 100644 --- a/axum_server/src/picture_generator/picture_markup_generator.rs +++ b/axum_server/src/picture_generator/picture_markup_generator.rs @@ -1,5 +1,4 @@ use std::{ - cmp::Ordering, path::{Path, PathBuf}, sync::Arc, }; @@ -7,7 +6,10 @@ use std::{ use anyhow::Context; use image::{image_dimensions, ImageReader}; -use super::{export_format::ExportFormat, image_generator::generate_images}; +use super::{ + export_format::ExportFormat, image_generator::generate_images, + resolutions::get_max_resolution_with_crop, +}; pub const PIXEL_DENSITIES: [f32; 5] = [1., 1.5, 2., 3., 4.]; @@ -18,15 +20,6 @@ pub fn generate_picture_markup( alt_text: &str, generate_image: bool, ) -> Result { - if !orig_img_path.starts_with("/") { - return Ok(format!( - r#"{alt_text}"# - )); - } - let exported_formats = get_export_formats(orig_img_path); let path_to_generated = get_generated_file_name(orig_img_path); @@ -123,8 +116,8 @@ fn get_resolutions( let mut resolutions: Vec<(u32, u32, f32)> = vec![]; for pixel_density in PIXEL_DENSITIES { let (density_width, density_height) = ( - (pixel_density * width as f32).floor() as u32, - (pixel_density * height as f32).floor() as u32, + (pixel_density * width as f32) as u32, + (pixel_density * height as f32) as u32, ); // The equal sign `=` was added just to prevent next occurence of the loop @@ -132,7 +125,7 @@ fn get_resolutions( // See test case #1 if density_width >= orig_width || density_height >= orig_height { let (max_width, max_height) = - get_max_resolution((orig_width, orig_height), width, height); + get_max_resolution_with_crop((orig_width, orig_height), width, height); resolutions.push((max_width, max_height, pixel_density)); break; } @@ -171,51 +164,6 @@ fn test_get_resolutions() { ); } -fn get_max_resolution( - (orig_width, orig_height): (u32, u32), - width: u32, - height: u32, -) -> (u32, u32) { - let width_scale = orig_width as f32 / width as f32; - let height_scale = orig_height as f32 / height as f32; - - let scale = match width_scale.partial_cmp(&height_scale) { - Some(Ordering::Less) => width_scale, - Some(Ordering::Greater) => height_scale, - _ => width_scale, - }; - ( - (width as f32 * scale) as u32, - (height as f32 * scale) as u32, - ) -} - -#[test] -fn test_get_max_resolution() { - assert_eq!( - get_max_resolution((320, 200), 320, 200), - (320, 200), - "Original size fits" - ); - // THINK: Real curious if this is what I want to do. Rather than use CSS to `object-cover` original image size - assert_eq!( - get_max_resolution((200, 200), 300, 200), - (200, 133), - "Image has to be smaller" - ); - - assert_eq!( - get_max_resolution((1000, 1000), 200, 100), - (1000, 500), - "width is maxed" - ); - assert_eq!( - get_max_resolution((1000, 1000), 100, 200), - (500, 1000), - "height is maxed" - ); -} - fn strip_prefixes(path: &Path) -> &Path { // Loop to remove all leading "../" components let mut parent_path = path diff --git a/axum_server/src/picture_generator/resolutions.rs b/axum_server/src/picture_generator/resolutions.rs new file mode 100644 index 0000000..6b1eb79 --- /dev/null +++ b/axum_server/src/picture_generator/resolutions.rs @@ -0,0 +1,91 @@ +pub fn get_max_resolution_with_crop( + (orig_width, orig_height): (u32, u32), + width: u32, + height: u32, +) -> (u32, u32) { + let width_scale = orig_width as f32 / width as f32; + let height_scale = orig_height as f32 / height as f32; + + let scale = width_scale.min(height_scale); + ( + (width as f32 * scale) as u32, + (height as f32 * scale) as u32, + ) +} + +#[test] +fn test_get_max_resolution_with_crop() { + assert_eq!( + get_max_resolution_with_crop((320, 200), 320, 200), + (320, 200), + "Original size fits" + ); + // THINK: Real curious if this is what I want to do. Rather than use CSS to `object-cover` original image size + assert_eq!( + get_max_resolution_with_crop((200, 200), 300, 200), + (200, 133), + "Image has to be smaller" + ); + + assert_eq!( + get_max_resolution_with_crop((1000, 1000), 200, 100), + (1000, 500), + "width is maxed" + ); + assert_eq!( + get_max_resolution_with_crop((1000, 1000), 100, 200), + (500, 1000), + "height is maxed" + ); + assert_eq!( + get_max_resolution_with_crop((300, 200), 600, 500), + (240, 200), + "image has to be scaled down" + ); +} + +pub fn get_max_resolution( + (orig_width, orig_height): (u32, u32), + max_width: u32, + max_height: u32, +) -> (u32, u32) { + // If the original dimensions are within the max dimensions, return them as is + if orig_width <= max_width && orig_height <= max_height { + return (orig_width, orig_height); + } + + let width_scale = max_width as f32 / orig_width as f32; + let height_scale = max_height as f32 / orig_height as f32; + + // Determine the scaling factor to ensure the image fits within the bounds + let scale = width_scale.min(height_scale); + + ( + (orig_width as f32 * scale).round() as u32, + (orig_height as f32 * scale).round() as u32, + ) +} + +#[test] +fn test_get_max_resolution() { + assert_eq!( + get_max_resolution((999, 675), 1000, 800), + (999, 675), + "Original size fits" + ); + assert_eq!( + get_max_resolution((1100, 400), 1000, 800), + (1000, 364), + "Image should be resized to fit within max dimensions" + ); + assert_eq!( + get_max_resolution((1100, 1200), 1000, 800), + (733, 800), + "Image should be resized to fit within max dimensions" + ); + assert_eq!( + get_max_resolution((1100, 800), 1000, 800), + (1000, 727), + "Image should be resized to fit within max dimensions" + ); +} diff --git a/axum_server/src/post_utils/post_parser.rs b/axum_server/src/post_utils/post_parser.rs index 4c6a017..434d465 100644 --- a/axum_server/src/post_utils/post_parser.rs +++ b/axum_server/src/post_utils/post_parser.rs @@ -3,12 +3,17 @@ use std::path::Path; use axum::http::StatusCode; use chrono::{DateTime, Utc}; use gray_matter::{engine::YAML, Matter}; +use image::image_dimensions; use pulldown_cmark::{Event, Options, Parser, Tag, TagEnd}; use serde::{de::DeserializeOwned, Deserialize, Deserializer}; use tokio::fs; use tracing::debug; -use crate::picture_generator::picture_markup_generator::generate_picture_markup; +use crate::picture_generator::{ + picture_markup_generator::generate_picture_markup, resolutions::get_max_resolution, +}; + +pub const MAX_BLOG_IMAGE_RESOLUTION: (u32, u32) = (1000, 800); pub fn deserialize_date<'de, D>(deserializer: D) -> Result, D::Error> where @@ -84,12 +89,32 @@ pub fn parse_html(markdown: &str, generate_images: bool) -> String { title, id, }) => { - // TODO Get image resolution + if !dest_url.starts_with("/") { + return Event::Html( + format!( + r#"{title}"# + ) + .into(), + ); + } + + let dev_only_img_path = + Path::new("../static/").join(dest_url.strip_prefix("/").unwrap_or(&dest_url)); + let img_dimensions = image_dimensions(&dev_only_img_path).unwrap(); + + let (max_width, max_height) = get_max_resolution( + img_dimensions, + MAX_BLOG_IMAGE_RESOLUTION.0, + MAX_BLOG_IMAGE_RESOLUTION.1, + ); // Place image into the content with scaled reso to a boundary let picture_markup = - generate_picture_markup(&dest_url, 500, 500, &title, generate_images).unwrap_or( - format!( + generate_picture_markup(&dest_url, max_width, max_height, &title, generate_images) + .unwrap_or(format!( r#" {alt} String { />"#, alt = title, src = dest_url, - ), - ); + )); // let picture_markup = format!( // r#" //