Skip to content

Commit 82a15cd

Browse files
committed
feat: improved immage processing
1 parent 5b717a0 commit 82a15cd

6 files changed

+98
-15
lines changed

src/exif_reader.rs

+21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use exif::{Exif, In, Reader, Tag};
33

44
use crate::{geo_location, WebDavClient, WebDavResource};
55
use crate::geo_location::GeoLocation;
6+
use crate::image_processor::ImageOrientation;
67

78
pub fn get_exif_date(exif_data: &Exif) -> Option<NaiveDateTime> {
89
let mut exif_date: Option<NaiveDateTime> = detect_exif_date(
@@ -58,10 +59,12 @@ pub fn fill_exif_data(web_dav_client: &WebDavClient, resource: &WebDavResource)
5859

5960
let mut taken_date = None;
6061
let mut location = None;
62+
let mut orientation = None;
6163

6264
if let Some(exif_data) = maybe_exif_data {
6365
taken_date = get_exif_date(&exif_data);
6466
location = detect_location(&exif_data);
67+
orientation = detect_orientation(&exif_data);
6568
}
6669

6770
if taken_date.is_none() {
@@ -70,6 +73,7 @@ pub fn fill_exif_data(web_dav_client: &WebDavClient, resource: &WebDavResource)
7073

7174
augmented_resource.taken = taken_date;
7275
augmented_resource.location = location;
76+
augmented_resource.orientation = orientation;
7377

7478
augmented_resource
7579
}
@@ -88,6 +92,23 @@ fn detect_location(exif_data: &Exif) -> Option<GeoLocation> {
8892
None
8993
}
9094

95+
fn detect_orientation(exif_data: &Exif) -> Option<ImageOrientation> {
96+
let maybe_orientation = exif_data.get_field(Tag::Orientation, In::PRIMARY)
97+
.and_then(|field| field.value.get_uint(0));
98+
99+
match maybe_orientation {
100+
Some(1) => Some(ImageOrientation { rotation: 0, mirror_vertically: false }),
101+
Some(2) => Some(ImageOrientation { rotation: 0, mirror_vertically: true }),
102+
Some(3) => Some(ImageOrientation { rotation: 180, mirror_vertically: false }),
103+
Some(4) => Some(ImageOrientation { rotation: 180, mirror_vertically: true }),
104+
Some(5) => Some(ImageOrientation { rotation: 90, mirror_vertically: true }),
105+
Some(6) => Some(ImageOrientation { rotation: 90, mirror_vertically: false }),
106+
Some(7) => Some(ImageOrientation { rotation: 270, mirror_vertically: true }),
107+
Some(8) => Some(ImageOrientation { rotation: 270, mirror_vertically: false }),
108+
_ => None,
109+
}
110+
}
111+
91112
fn detect_date_by_name(resource_path: &str) -> Option<NaiveDateTime> {
92113
let parsed: Vec<NaiveDate> = resource_path
93114
.replace('/', "_")

src/image_processor.rs

+46-5
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,61 @@ use std::time::Instant;
33

44
use image::imageops::FilterType;
55
use image::io::Reader as ImageReader;
6+
use serde::{Deserialize, Serialize};
67

7-
pub fn scale(resource_data: Vec<u8>, display_width: u32, display_height: u32) -> Vec<u8> {
8-
let img = ImageReader::new(Cursor::new(resource_data))
8+
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
9+
pub struct ImageOrientation {
10+
pub rotation: u16,
11+
pub mirror_vertically: bool,
12+
}
13+
14+
/// Adjusts the image to fit optimal to the browser resolution
15+
/// Also fixes the orientation delivered by the exif image rotation
16+
/// src: https://sirv.com/help/articles/rotate-photos-to-be-upright/
17+
pub fn optimize_image(resource_data: Vec<u8>, display_width: u32, display_height: u32, image_orientation: Option<ImageOrientation>) -> Vec<u8> {
18+
let mut s = Instant::now();
19+
20+
let original_image = ImageReader::new(Cursor::new(resource_data))
921
.with_guessed_format().unwrap()
1022
.decode().unwrap();
23+
println!("loading from memory {}s!", s.elapsed().as_millis());
24+
s = Instant::now();
1125

12-
let resize = img.resize(
26+
let resized = original_image.resize(
1327
display_width,
1428
display_height,
15-
FilterType::Nearest
29+
FilterType::Nearest,
1630
);
31+
println!("Resize {}s!", s.elapsed().as_millis());
32+
s = Instant::now();
33+
34+
let fixed_orientation = if let Some(orientation) = image_orientation {
35+
let rotated = match orientation.rotation {
36+
90 => resized.rotate90(),
37+
180 => resized.rotate180(),
38+
270 => resized.rotate270(),
39+
_ => resized,
40+
};
41+
println!("rotation {:?}: {}s!", orientation.rotation, s.elapsed().as_millis());
42+
s = Instant::now();
43+
44+
let mirrored = if orientation.mirror_vertically {
45+
rotated.flipv()
46+
} else {
47+
rotated
48+
};
49+
println!("mirroring {}s!", s.elapsed().as_millis());
50+
s = Instant::now();
51+
52+
mirrored
53+
} else {
54+
resized
55+
};
1756

1857
let mut bytes: Vec<u8> = Vec::new();
19-
resize.write_to(&mut bytes, image::ImageOutputFormat::Png).unwrap();
58+
fixed_orientation.write_to(&mut bytes, image::ImageOutputFormat::Png).unwrap();
59+
println!("writing bytes {}s!", s.elapsed().as_millis());
60+
println!("====!");
2061

2162
bytes
2263
}

src/image_processor_test.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ fn scale_image() {
1414
).unwrap().bytes().unwrap().to_vec();
1515

1616
// WHEN resolving the city name
17-
let scaled_image_buf = image_processor::scale(image_data, 1024, 786);
17+
let scaled_image_buf = image_processor::optimize_image(
18+
image_data,
19+
1024,
20+
786,
21+
None
22+
);
1823

1924
// THEN the resolved city name should be Koblenz
2025
let scaled_image = ImageReader::new(Cursor::new(scaled_image_buf))

src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ async fn main() -> std::io::Result<()> {
7272
println!("Stopping Scheduler 🕐️");
7373
scheduler_handle.stop();
7474

75+
println!("Stopping Application 😵️");
7576
// Done, let's get out here
7677
http_server_result
7778
}

src/resource_endpoint.rs

+20-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use actix_http::Response as HttpResponse;
22
use actix_web::web;
33
use evmap::ReadHandle;
44

5-
use crate::{get, image_processor, resource_processor, WebDavClient};
5+
use crate::{get, image_processor, resource_processor, WebDavClient, WebDavResource};
66

77
#[get("")]
88
pub async fn list_resources(kv_reader: web::Data<ReadHandle<String, String>>) -> HttpResponse {
@@ -29,12 +29,20 @@ pub async fn get_resource(resources_id: web::Path<(String, u32, u32)>, kv_reader
2929
let display_width = path_params.1;
3030
let display_height = path_params.2;
3131

32-
let resource_data = kv_reader.get_one(resource_id)
32+
let web_dav_resource = kv_reader.get_one(resource_id)
3333
.map(|value| value.to_string())
34-
.and_then(|resource_json_string| serde_json::from_str(resource_json_string.as_str()).ok())
34+
.and_then(|resource_json_string| serde_json::from_str(resource_json_string.as_str()).ok());
35+
let orientation = web_dav_resource.clone().and_then(|web_dav_resource: WebDavResource| web_dav_resource.orientation);
36+
37+
let resource_data = web_dav_resource
3538
.map(|web_dav_resource| web_dav_client.request_resource_data(&web_dav_resource))
3639
.and_then(|web_response| web_response.bytes().ok())
37-
.map(|resource_data| image_processor::scale(resource_data.to_vec(), display_width, display_height));
40+
.map(|resource_data| image_processor::optimize_image(
41+
resource_data.to_vec(),
42+
display_width,
43+
display_height,
44+
orientation,
45+
));
3846

3947
if let Some(resource_data) = resource_data {
4048
HttpResponse::Ok()
@@ -47,17 +55,22 @@ pub async fn get_resource(resources_id: web::Path<(String, u32, u32)>, kv_reader
4755

4856
#[get("{resource_id}/{display_width}/{display_height}/base64")]
4957
pub async fn get_resource_base64(resources_id: web::Path<(String, u32, u32)>, kv_reader: web::Data<ReadHandle<String, String>>, web_dav_client: web::Data<WebDavClient>) -> HttpResponse {
58+
// TODO request caching
59+
5060
let path_params = resources_id.0;
5161
let resource_id = path_params.0.as_str();
5262
let display_width = path_params.1;
5363
let display_height = path_params.2;
5464

55-
let base64_image = kv_reader.get_one(resource_id)
65+
let web_dav_resource = kv_reader.get_one(resource_id)
5666
.map(|value| value.to_string())
57-
.and_then(|resource_json_string| serde_json::from_str(resource_json_string.as_str()).ok())
67+
.and_then(|resource_json_string| serde_json::from_str(resource_json_string.as_str()).ok());
68+
let orientation = web_dav_resource.clone().and_then(|web_dav_resource: WebDavResource| web_dav_resource.orientation);
69+
70+
let base64_image = web_dav_resource
5871
.map(|web_dav_resource| web_dav_client.request_resource_data(&web_dav_resource))
5972
.and_then(|web_response| web_response.bytes().ok())
60-
.map(|resource_data| image_processor::scale(resource_data.to_vec(), display_width, display_height))
73+
.map(|resource_data| image_processor::optimize_image(resource_data.to_vec(), display_width, display_height, orientation))
6174
.map(|scaled_image| base64::encode(&scaled_image))
6275
.map(|base64_string| format!("data:image/png;base64,{}", base64_string));
6376

src/web_dav_client.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize};
1212

1313
use crate::{exif_reader, resource_processor};
1414
use crate::geo_location::GeoLocation;
15+
use crate::image_processor::ImageOrientation;
1516

1617
#[derive(Clone)]
1718
pub struct WebDavClient {
@@ -31,6 +32,7 @@ pub struct WebDavResource {
3132
pub last_modified: NaiveDateTime,
3233
pub taken: Option<NaiveDateTime>,
3334
pub location: Option<GeoLocation>,
35+
pub orientation: Option<ImageOrientation>,
3436
}
3537

3638
impl WebDavResource {
@@ -125,8 +127,7 @@ fn parse_propfind_result(web_dav_client: &WebDavClient, xml: String) -> Vec<WebD
125127
.collect();
126128

127129
xml_nodes.par_iter()
128-
// TODO: allow all type of media and convert on get, to an image
129-
.filter(|resource| resource.content_type.eq("image/jpeg"))
130+
.filter(|resource| resource.content_type.starts_with("image/"))
130131
.filter(|resource| !resource.path.contains("thumbnail"))
131132
.map(|resource| exif_reader::fill_exif_data(web_dav_client, resource))
132133
.collect()
@@ -173,5 +174,6 @@ fn parse_resource_node(response_node: Node) -> Option<WebDavResource> {
173174
).unwrap(),
174175
taken: None,
175176
location: None,
177+
orientation: None,
176178
})
177179
}

0 commit comments

Comments
 (0)