From 8e3e8952a6187c75284d166adcb8fa1351d1d25a Mon Sep 17 00:00:00 2001 From: Michal Vanko Date: Wed, 9 Jul 2025 22:43:02 +0200 Subject: [PATCH] fix resolutions for exif rotated images --- Cargo.toml | 2 +- src/filters/markdown.rs | 15 ++++- src/picture_generator/image_generator.rs | 1 - src/picture_generator/image_src_generator.rs | 1 + .../picture_markup_generator.rs | 56 ++++++++++++++----- 5 files changed, 57 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a336c82..2b4ce18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ tower-http = { version = "0.6.0", features = ["trace", "fs"] } tower-livereload = "0.9.2" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -image = "0.25.2" +image = "0.25.6" anyhow = "1.0.86" rayon = "1.10.0" syntect = "5.2.0" diff --git a/src/filters/markdown.rs b/src/filters/markdown.rs index 5582038..d699ddb 100644 --- a/src/filters/markdown.rs +++ b/src/filters/markdown.rs @@ -1,14 +1,15 @@ use core::fmt; use std::path::Path; -use image::image_dimensions; +use image::{image_dimensions, ImageReader}; use indoc::formatdoc; use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd}; use syntect::{highlighting::ThemeSet, html::highlighted_html_for_string, parsing::SyntaxSet}; use tracing::{debug, error}; use crate::picture_generator::{ - picture_markup_generator::generate_picture_markup, resolutions::get_max_resolution, + picture_markup_generator::{generate_picture_markup, should_swap_dimensions}, + resolutions::get_max_resolution, }; pub const MAX_BLOG_IMAGE_RESOLUTION: (u32, u32) = (1280, 860); @@ -65,8 +66,16 @@ pub fn parse_markdown( 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(); + // We need to take the exif rotation into consideration here + let img_dimensions = { + let orig_img_dimensions = image_dimensions(&dev_only_img_path).unwrap(); + if should_swap_dimensions(&dev_only_img_path) { + (orig_img_dimensions.1, orig_img_dimensions.0) + } else { + orig_img_dimensions + } + }; let (max_width, max_height) = get_max_resolution( img_dimensions, MAX_BLOG_IMAGE_RESOLUTION.0, diff --git a/src/picture_generator/image_generator.rs b/src/picture_generator/image_generator.rs index b74dc9e..7f2b1e7 100644 --- a/src/picture_generator/image_generator.rs +++ b/src/picture_generator/image_generator.rs @@ -16,7 +16,6 @@ pub fn generate_images( formats.par_iter().for_each(|format| { resolutions.par_iter().for_each(|resolution| { let (width, height, _) = *resolution; - // let image = image.clone(); let resized = image.resize_to_fill(width, height, FilterType::Triangle); let file_name = path_to_generated.file_name().unwrap().to_str().unwrap(); let save_path = Path::new("./") diff --git a/src/picture_generator/image_src_generator.rs b/src/picture_generator/image_src_generator.rs index aa0dab5..621aaa8 100644 --- a/src/picture_generator/image_src_generator.rs +++ b/src/picture_generator/image_src_generator.rs @@ -8,6 +8,7 @@ use super::{ picture_markup_generator::{get_export_formats, get_generated_file_name, get_image_path}, }; +/// Used directly in templates pub fn generate_image_with_src( orig_img_path: &str, width: u32, diff --git a/src/picture_generator/picture_markup_generator.rs b/src/picture_generator/picture_markup_generator.rs index 4c291a8..1af6546 100644 --- a/src/picture_generator/picture_markup_generator.rs +++ b/src/picture_generator/picture_markup_generator.rs @@ -4,7 +4,7 @@ use std::{ }; use anyhow::Context; -use image::{image_dimensions, ImageReader}; +use image::{image_dimensions, ImageDecoder, ImageReader}; use indoc::formatdoc; use super::{ @@ -14,6 +14,7 @@ use super::{ pub const PIXEL_DENSITIES: [f32; 5] = [1., 1.5, 2., 3., 4.]; +/// Used by markdown generator pub fn generate_picture_markup( orig_img_path: &str, width: u32, @@ -29,6 +30,7 @@ pub fn generate_picture_markup( "".to_string() }; + // Here the resolution is already correct if exported_formats.is_empty() { return Ok(formatdoc!( r#" Vec<(u32, u32, f32)> { + let (width, height) = if swap_dimensions { + (height, width) + } else { + (width, height) + }; let mut resolutions: Vec<(u32, u32, f32)> = vec![]; for pixel_density in PIXEL_DENSITIES { let (density_width, density_height) = ( @@ -159,22 +171,22 @@ fn get_resolutions( #[test] fn test_get_resolutions() { assert_eq!( - get_resolutions((320, 200), 320, 200), + get_resolutions((320, 200), 320, 200, false), vec![(320, 200, 1.)], "Only original size fits" ); assert_eq!( - get_resolutions((500, 400), 320, 200), + get_resolutions((500, 400), 320, 200, false), vec![(320, 200, 1.), (480, 300, 1.5), (500, 312, 2.)], "Should only create sizes that fits and fill the max possible for the last one - width" ); assert_eq!( - get_resolutions((400, 600), 300, 200), + get_resolutions((400, 600), 300, 200, false), vec![(300, 200, 1.), (400, 266, 1.5)], "Should only create sizes that fits and fill the max possible for the last one - height" ); assert_eq!( - get_resolutions((1200, 900), 320, 200), + get_resolutions((1200, 900), 320, 200, false), vec![ (320, 200, 1.), (480, 300, 1.5), @@ -184,6 +196,7 @@ fn test_get_resolutions() { ], "Should create all possible sizes, with the last one maxed" ); + // TODO add test for swapping } fn strip_prefixes(path: &Path) -> &Path { @@ -257,6 +270,23 @@ pub fn get_export_formats(orig_img_path: &Path) -> Vec { } } +pub fn should_swap_dimensions(img_path: &Path) -> bool { + let orientation = ImageReader::open(img_path) + .unwrap() + .into_decoder() + .unwrap() + .orientation() + .unwrap(); + + matches!( + orientation, + image::metadata::Orientation::Rotate90 + | image::metadata::Orientation::Rotate270 + | image::metadata::Orientation::Rotate90FlipH + | image::metadata::Orientation::Rotate270FlipH + ) +} + #[test] fn test_get_export_formats() { assert_eq!(