fix resolutions for exif rotated images
Some checks failed
test / cargo test (push) Failing after 1m25s
Some checks failed
test / cargo test (push) Failing after 1m25s
This commit is contained in:
@@ -19,7 +19,7 @@ tower-http = { version = "0.6.0", features = ["trace", "fs"] }
|
|||||||
tower-livereload = "0.9.2"
|
tower-livereload = "0.9.2"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
image = "0.25.2"
|
image = "0.25.6"
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.86"
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
syntect = "5.2.0"
|
syntect = "5.2.0"
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use image::image_dimensions;
|
use image::{image_dimensions, ImageReader};
|
||||||
use indoc::formatdoc;
|
use indoc::formatdoc;
|
||||||
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
|
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
|
||||||
use syntect::{highlighting::ThemeSet, html::highlighted_html_for_string, parsing::SyntaxSet};
|
use syntect::{highlighting::ThemeSet, html::highlighted_html_for_string, parsing::SyntaxSet};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::picture_generator::{
|
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);
|
pub const MAX_BLOG_IMAGE_RESOLUTION: (u32, u32) = (1280, 860);
|
||||||
@@ -65,8 +66,16 @@ pub fn parse_markdown<T: fmt::Display>(
|
|||||||
|
|
||||||
let dev_only_img_path =
|
let dev_only_img_path =
|
||||||
Path::new("static/").join(dest_url.strip_prefix("/").unwrap_or(&dest_url));
|
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(
|
let (max_width, max_height) = get_max_resolution(
|
||||||
img_dimensions,
|
img_dimensions,
|
||||||
MAX_BLOG_IMAGE_RESOLUTION.0,
|
MAX_BLOG_IMAGE_RESOLUTION.0,
|
||||||
|
@@ -16,7 +16,6 @@ pub fn generate_images(
|
|||||||
formats.par_iter().for_each(|format| {
|
formats.par_iter().for_each(|format| {
|
||||||
resolutions.par_iter().for_each(|resolution| {
|
resolutions.par_iter().for_each(|resolution| {
|
||||||
let (width, height, _) = *resolution;
|
let (width, height, _) = *resolution;
|
||||||
// let image = image.clone();
|
|
||||||
let resized = image.resize_to_fill(width, height, FilterType::Triangle);
|
let resized = image.resize_to_fill(width, height, FilterType::Triangle);
|
||||||
let file_name = path_to_generated.file_name().unwrap().to_str().unwrap();
|
let file_name = path_to_generated.file_name().unwrap().to_str().unwrap();
|
||||||
let save_path = Path::new("./")
|
let save_path = Path::new("./")
|
||||||
|
@@ -8,6 +8,7 @@ use super::{
|
|||||||
picture_markup_generator::{get_export_formats, get_generated_file_name, get_image_path},
|
picture_markup_generator::{get_export_formats, get_generated_file_name, get_image_path},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Used directly in templates
|
||||||
pub fn generate_image_with_src(
|
pub fn generate_image_with_src(
|
||||||
orig_img_path: &str,
|
orig_img_path: &str,
|
||||||
width: u32,
|
width: u32,
|
||||||
|
@@ -4,7 +4,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use image::{image_dimensions, ImageReader};
|
use image::{image_dimensions, ImageDecoder, ImageReader};
|
||||||
use indoc::formatdoc;
|
use indoc::formatdoc;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -14,6 +14,7 @@ use super::{
|
|||||||
|
|
||||||
pub const PIXEL_DENSITIES: [f32; 5] = [1., 1.5, 2., 3., 4.];
|
pub const PIXEL_DENSITIES: [f32; 5] = [1., 1.5, 2., 3., 4.];
|
||||||
|
|
||||||
|
/// Used by markdown generator
|
||||||
pub fn generate_picture_markup(
|
pub fn generate_picture_markup(
|
||||||
orig_img_path: &str,
|
orig_img_path: &str,
|
||||||
width: u32,
|
width: u32,
|
||||||
@@ -29,6 +30,7 @@ pub fn generate_picture_markup(
|
|||||||
"".to_string()
|
"".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Here the resolution is already correct
|
||||||
if exported_formats.is_empty() {
|
if exported_formats.is_empty() {
|
||||||
return Ok(formatdoc!(
|
return Ok(formatdoc!(
|
||||||
r#"<img
|
r#"<img
|
||||||
@@ -45,8 +47,14 @@ pub fn generate_picture_markup(
|
|||||||
let disk_img_path =
|
let disk_img_path =
|
||||||
Path::new("static/").join(orig_img_path.strip_prefix("/").unwrap_or(orig_img_path));
|
Path::new("static/").join(orig_img_path.strip_prefix("/").unwrap_or(orig_img_path));
|
||||||
|
|
||||||
|
// Here we have a problem. The resolution is swapped but we want to generate images with original dimensions which are not here.
|
||||||
let orig_img_dimensions = image_dimensions(&disk_img_path)?;
|
let orig_img_dimensions = image_dimensions(&disk_img_path)?;
|
||||||
let resolutions = get_resolutions(orig_img_dimensions, width, height);
|
let resolutions = get_resolutions(
|
||||||
|
orig_img_dimensions,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
should_swap_dimensions(&disk_img_path),
|
||||||
|
);
|
||||||
let path_to_generated_arc = Arc::new(path_to_generated);
|
let path_to_generated_arc = Arc::new(path_to_generated);
|
||||||
let path_to_generated_clone = Arc::clone(&path_to_generated_arc);
|
let path_to_generated_clone = Arc::clone(&path_to_generated_arc);
|
||||||
let resolutions_arc = Arc::new(resolutions);
|
let resolutions_arc = Arc::new(resolutions);
|
||||||
@@ -54,23 +62,19 @@ pub fn generate_picture_markup(
|
|||||||
let exported_formats_arc = Arc::new(exported_formats);
|
let exported_formats_arc = Arc::new(exported_formats);
|
||||||
let exported_formats_clone = Arc::clone(&exported_formats_arc);
|
let exported_formats_clone = Arc::clone(&exported_formats_arc);
|
||||||
|
|
||||||
// AI? Which data escapes?
|
|
||||||
rayon::spawn(move || {
|
rayon::spawn(move || {
|
||||||
let orig_img = ImageReader::open(&disk_img_path)
|
let orig_img = ImageReader::open(&disk_img_path)
|
||||||
.with_context(|| format!("Failed to read instrs from {:?}", &disk_img_path))
|
.with_context(|| format!("Failed to read instrs from {:?}", &disk_img_path))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.decode()
|
.decode()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let path_to_generated = path_to_generated_clone.as_ref();
|
|
||||||
let resolutions = resolutions_clone.as_ref();
|
|
||||||
let exported_formats = exported_formats_clone.as_ref();
|
|
||||||
|
|
||||||
let result = generate_images(
|
let result = generate_images(
|
||||||
&orig_img,
|
&orig_img,
|
||||||
&disk_img_path,
|
&disk_img_path,
|
||||||
path_to_generated,
|
&path_to_generated_clone,
|
||||||
resolutions,
|
&resolutions_clone,
|
||||||
exported_formats,
|
&exported_formats_clone,
|
||||||
)
|
)
|
||||||
.with_context(|| "Failed to generate images".to_string());
|
.with_context(|| "Failed to generate images".to_string());
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
@@ -130,11 +134,19 @@ pub fn get_image_path(path: &Path, resolution: &(u32, u32, f32), format: &Export
|
|||||||
format!("{path_name}_{width}x{height}.{extension}")
|
format!("{path_name}_{width}x{height}.{extension}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take original resolution of photo and
|
||||||
|
/// exif data is not taken into consideration therefore we don't need to do anything here regarding to swapping width-height
|
||||||
fn get_resolutions(
|
fn get_resolutions(
|
||||||
(orig_width, orig_height): (u32, u32),
|
(orig_width, orig_height): (u32, u32),
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
swap_dimensions: bool,
|
||||||
) -> Vec<(u32, u32, f32)> {
|
) -> Vec<(u32, u32, f32)> {
|
||||||
|
let (width, height) = if swap_dimensions {
|
||||||
|
(height, width)
|
||||||
|
} else {
|
||||||
|
(width, height)
|
||||||
|
};
|
||||||
let mut resolutions: Vec<(u32, u32, f32)> = vec![];
|
let mut resolutions: Vec<(u32, u32, f32)> = vec![];
|
||||||
for pixel_density in PIXEL_DENSITIES {
|
for pixel_density in PIXEL_DENSITIES {
|
||||||
let (density_width, density_height) = (
|
let (density_width, density_height) = (
|
||||||
@@ -159,22 +171,22 @@ fn get_resolutions(
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_get_resolutions() {
|
fn test_get_resolutions() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_resolutions((320, 200), 320, 200),
|
get_resolutions((320, 200), 320, 200, false),
|
||||||
vec![(320, 200, 1.)],
|
vec![(320, 200, 1.)],
|
||||||
"Only original size fits"
|
"Only original size fits"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
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.)],
|
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"
|
"Should only create sizes that fits and fill the max possible for the last one - width"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_resolutions((400, 600), 300, 200),
|
get_resolutions((400, 600), 300, 200, false),
|
||||||
vec![(300, 200, 1.), (400, 266, 1.5)],
|
vec![(300, 200, 1.), (400, 266, 1.5)],
|
||||||
"Should only create sizes that fits and fill the max possible for the last one - height"
|
"Should only create sizes that fits and fill the max possible for the last one - height"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_resolutions((1200, 900), 320, 200),
|
get_resolutions((1200, 900), 320, 200, false),
|
||||||
vec![
|
vec![
|
||||||
(320, 200, 1.),
|
(320, 200, 1.),
|
||||||
(480, 300, 1.5),
|
(480, 300, 1.5),
|
||||||
@@ -184,6 +196,7 @@ fn test_get_resolutions() {
|
|||||||
],
|
],
|
||||||
"Should create all possible sizes, with the last one maxed"
|
"Should create all possible sizes, with the last one maxed"
|
||||||
);
|
);
|
||||||
|
// TODO add test for swapping
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strip_prefixes(path: &Path) -> &Path {
|
fn strip_prefixes(path: &Path) -> &Path {
|
||||||
@@ -257,6 +270,23 @@ pub fn get_export_formats(orig_img_path: &Path) -> Vec<ExportFormat> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
#[test]
|
||||||
fn test_get_export_formats() {
|
fn test_get_export_formats() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
Reference in New Issue
Block a user