Skip to content

Commit

Permalink
Fix for some images not appearing with the correct orientation #134 (A…
Browse files Browse the repository at this point in the history
  • Loading branch information
ArturKovacs authored Aug 10, 2020
1 parent 9a1ee12 commit cecddb8
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 31 deletions.
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ rand = "0.7"
lexical-sort = "0.3"
trash = "1.0"
clap = { version = "2.33", default-features = false }
kamadak-exif = "0.5.1"

[dependencies.libavif-image]
version = "0.4"
Expand Down
87 changes: 79 additions & 8 deletions src/image_cache/image_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod errors {
Io(io::Error) #[doc = "Error during IO"];
TextureCreationError(texture::TextureCreationError);
ImageLoadError(image::ImageError);
ExifError(exif::Error);
}
}
}
Expand Down Expand Up @@ -64,6 +65,50 @@ pub fn detect_format(path: &Path) -> Result<ImgFormat> {
Ok(ImgFormat::Image(ImageFormat::from_path(path)?))
}

fn detect_orientation(path: &Path) -> Result<f32> {
let file = std::fs::File::open(path)?;
let mut bufreader = std::io::BufReader::new(&file);
let exifreader = exif::Reader::new();
let exif = exifreader.read_from_container(&mut bufreader)?;
if let Some(orientation) = exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY) {
if let exif::Value::Short(ref shorts) = orientation.value {
if let Some(&exif_orientation) = shorts.get(0) {
use std::f32::consts::PI;
// According to page 30 of http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf
match exif_orientation {
1 => Ok(0.0),
2 => {
eprintln!("Image is flipped according to the exif data. This is not yet supported.");
Ok(0.0)
}
3 => Ok(PI),
4 => {
eprintln!("Image is flipped according to the exif data. This is not yet supported.");
Ok(PI)
}
5 => {
eprintln!("Image is flipped according to the exif data. This is not yet supported.");
Ok(PI * 0.5)
}
6 => Ok(PI * -0.5),
7 => {
eprintln!("Image is flipped according to the exif data. This is not yet supported.");
Ok(PI * -0.5)
}
8 => Ok(PI * 0.5),
_ => unreachable!(),
}
} else {
Ok(0.0)
}
} else {
Err("EXIF orientation was expected to be of type 'short' but it wasn't".into())
}
} else {
Ok(0.0)
}
}

pub fn load_image(path: &Path, image_format: ImageFormat) -> Result<image::RgbaImage> {
let reader = BufReader::new(fs::File::open(path)?);
Ok(image::load(reader, image_format)?.to_rgba())
Expand All @@ -82,14 +127,14 @@ fn load_animation(
) -> Result<impl Iterator<Item = Result<LoadResult>>> {
let frames = decoder.into_frames();

Ok(frames.into_iter().map(move |frame| {
Ok(frames.map(move |frame| {
Ok(frame.map(|frame| {
let (numerator_ms, denom_ms) = frame.delay().numer_denom_ms();
let numerator_nano = numerator_ms as u64 * 1_000_000;
let denom_nano = denom_ms as u64;
let delay_nano = numerator_nano / denom_nano;
let image = frame.into_buffer();
LoadResult::Frame { req_id, image, delay_nano }
LoadResult::Frame { req_id, image, delay_nano, angle: 0.0 }
})?)
}))
}
Expand Down Expand Up @@ -140,10 +185,24 @@ pub struct LoadRequest {
}

pub enum LoadResult {
Start { req_id: u32, metadata: fs::Metadata },
Frame { req_id: u32, image: image::RgbaImage, delay_nano: u64 },
Done { req_id: u32 },
Failed { req_id: u32 },
Start {
req_id: u32,
metadata: fs::Metadata,
},
Frame {
req_id: u32,
image: image::RgbaImage,
delay_nano: u64,

/// the angle of the orientation in radians (right handed rotation)
angle: f32,
},
Done {
req_id: u32,
},
Failed {
req_id: u32,
},
}

impl LoadResult {
Expand Down Expand Up @@ -233,6 +292,7 @@ impl ImageLoader {
fn try_load_and_send(img_sender: &Sender<LoadResult>, request: &LoadRequest) -> Result<()> {
let metadata = fs::metadata(&request.path)?;
let image_format = detect_format(&request.path)?;
let angle = detect_orientation(&request.path)?;
img_sender.send(LoadResult::Start { req_id: request.req_id, metadata }).unwrap();

match image_format {
Expand All @@ -256,22 +316,33 @@ impl ImageLoader {
req_id: request.req_id,
image,
delay_nano: 0,
angle,
})
.unwrap();
}
}
ImgFormat::Image(image_format) => {
let image = load_image(&request.path, image_format)?;
img_sender
.send(LoadResult::Frame { req_id: request.req_id, image, delay_nano: 0 })
.send(LoadResult::Frame {
req_id: request.req_id,
image,
delay_nano: 0,
angle,
})
.unwrap();
}
#[cfg(feature = "avif")]
ImgFormat::Avif => {
let buf = fs::read(&request.path)?;
let image = libavif_image::read(&buf)?.to_rgba();
img_sender
.send(LoadResult::Frame { req_id: request.req_id, image, delay_nano: 0 })
.send(LoadResult::Frame {
req_id: request.req_id,
image,
delay_nano: 0,
angle,
})
.unwrap();
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/image_cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ struct OngoingRequest {
pub struct AnimationFrameTexture {
pub texture: Rc<SrgbTexture2d>,
pub delay_nano: u64,
pub angle: f32,
}

struct CachedTexture {
Expand Down Expand Up @@ -580,7 +581,7 @@ impl ImageCache {
}
Ok(None)
}
LoadResult::Frame { req_id, image, delay_nano } => {
LoadResult::Frame { req_id, image, delay_nano, angle } => {
if let Some(request) = self.ongoing_requests.get(&req_id) {
if request.cancelled {
return Ok(None);
Expand All @@ -591,7 +592,7 @@ impl ImageCache {
let size_estimate = get_image_size_estimate(image.width(), image.height());
if let Some(entry) = self.texture_cache.get_mut(&req_id) {
let texture = Rc::new(texture_from_image(display, image)?);
let anim_frame = AnimationFrameTexture { texture, delay_nano };
let anim_frame = AnimationFrameTexture { texture, delay_nano, angle };
entry.frames.push(anim_frame.clone());
self.remaining_capacity -= size_estimate;
return Ok(Some(anim_frame));
Expand Down
2 changes: 1 addition & 1 deletion src/input_handling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub fn execute_triggered_commands(
var_map.insert("${img}", img_path);
var_map.insert("${folder}", folder_path);
for command in commands.iter() {
if keys_triggered(&command.input, input_key.as_ref(), modifiers) {
if keys_triggered(&command.input, input_key, modifiers) {
let mut cmd = Command::new(&command.program);
if let Some(ref args) = command.args {
cmd.args(args.iter().map(|arg| substitute_command_parameters(arg, &var_map)));
Expand Down
48 changes: 33 additions & 15 deletions src/picture_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use std::rc::{Rc, Weak};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

use gelatin::cgmath::{Matrix4, Vector3};
use gelatin::cgmath::{Matrix4, Rad, Vector3};
use gelatin::glium::glutin::event::{ElementState, ModifiersState, MouseButton};
use gelatin::glium::{program, texture::SrgbTexture2d, uniform, Display, Frame, Program, Surface};
use gelatin::glium::{program, uniform, Display, Frame, Program, Surface};

use gelatin::add_common_widget_functions;
use gelatin::misc::{Alignment, Length, LogicalRect, LogicalVector, WidgetPlacement};
Expand All @@ -23,6 +23,7 @@ use crate::{
bottom_bar::BottomBar,
configuration::{Cache, Configuration},
help_screen::HelpScreen,
image_cache::AnimationFrameTexture,
playback_manager::*,
};

Expand Down Expand Up @@ -90,11 +91,11 @@ impl PictureWidgetData {
let size = self.drawn_bounds.size.vec;
if let Some(texture) = self.get_texture() {
let panel_aspect = size.x / size.y;
let img_phys_w = texture.width() as f32;
let img_pyhs_h = texture.height() as f32;
let img_phys_w = texture.texture.width() as f32;
let img_pyhs_h = texture.texture.height() as f32;
let img_aspect = img_phys_w / img_pyhs_h;

let texel_size_to_fit_width = size.x / texture.width() as f32;
let texel_size_to_fit_width = size.x / texture.texture.width() as f32;
let img_texel_size = if img_aspect > panel_aspect {
// The image is relatively wider than the panel
texel_size_to_fit_width
Expand Down Expand Up @@ -175,7 +176,7 @@ impl PictureWidgetData {
display.gl_window().window().set_title(title.as_str());
}

fn get_texture(&self) -> Option<Rc<SrgbTexture2d>> {
fn get_texture(&self) -> Option<AnimationFrameTexture> {
self.playback_manager.image_texture()
}

Expand All @@ -199,8 +200,8 @@ impl PictureWidgetData {
/// Ensures that the image is within the widget, or at least touches an edge of the widget
fn apply_img_bounds(&mut self, dpi_scale: f32) {
if let Some(texture) = self.get_texture() {
let img_phys_w = texture.width() as f32 * self.img_texel_size;
let img_phys_h = texture.height() as f32 * self.img_texel_size;
let img_phys_w = texture.texture.width() as f32 * self.img_texel_size;
let img_phys_h = texture.texture.height() as f32 * self.img_texel_size;
let img_w = img_phys_w / dpi_scale;
let img_h = img_phys_h / dpi_scale;

Expand Down Expand Up @@ -441,7 +442,7 @@ impl Widget for PictureWidget {
data.render_validity.invalidate();
} else {
if let (Some(prev_tex), Some(new_tex)) = (prev_texture, new_texture) {
if !Rc::ptr_eq(&prev_tex, &new_tex) {
if !Rc::ptr_eq(&prev_tex.texture, &new_tex.texture) {
data.render_validity.invalidate();
}
}
Expand Down Expand Up @@ -472,13 +473,14 @@ impl Widget for PictureWidget {
};

if let Some(texture) = texture {
let img_w = texture.width() as f32;
let img_h = texture.height() as f32;
let img_w = texture.texture.width() as f32;
let img_h = texture.texture.height() as f32;

let img_height_over_width = img_h / img_w;
let image_display_width = data.img_texel_size * img_w / context.dpi_scale_factor;

// Model tranform

let image_display_height = image_display_width * img_height_over_width;
let img_pyhs_pos = data.img_pos.vec * context.dpi_scale_factor;
let img_phys_siz;
Expand All @@ -493,13 +495,29 @@ impl Widget for PictureWidget {
(img_pyhs_pos.y - img_phys_siz.vec.y * 0.5).floor() / context.dpi_scale_factor;
let adjusted_w = img_phys_siz.vec.x / context.dpi_scale_factor;
let adjusted_h = img_phys_siz.vec.y / context.dpi_scale_factor;
let transform = Matrix4::from_nonuniform_scale(adjusted_w, adjusted_h, 1.0);
let transform =
Matrix4::from_translation(Vector3::new(corner_x, corner_y, 0.0)) * transform;
let scaling = Matrix4::from_nonuniform_scale(adjusted_w, adjusted_h, 1.0);
let orientation;
{
let to_center = Matrix4::from_translation(Vector3::new(
-0.5 * adjusted_w,
-0.5 * adjusted_h,
0.0,
));
let rotate = Matrix4::from_angle_z(Rad(-texture.angle));
let to_corner = Matrix4::from_translation(Vector3::new(
0.5 * adjusted_w,
0.5 * adjusted_h,
0.0,
));
orientation = to_corner * rotate * to_center;
}
let translation = Matrix4::from_translation(Vector3::new(corner_x, corner_y, 0.0));
let transform = translation * orientation * scaling;
// Projection tranform
let transform = projection_transform * transform;

let sampler = texture
.texture
.sampled()
.minify_filter(
gelatin::glium::uniforms::MinifySamplerFilter::LinearMipmapLinear,
Expand Down Expand Up @@ -587,7 +605,7 @@ impl Widget for PictureWidget {
borrowed.render_validity.invalidate();
}
MouseButton::Right => {
let mut borrowed = self.data.borrow_mut();
let borrowed = self.data.borrow();
let pressed = state == ElementState::Pressed;
borrowed.left_to_pan_hint.set_visible(pressed);
}
Expand Down
10 changes: 5 additions & 5 deletions src/playback_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::time::{Duration, Instant};
use rand::seq::SliceRandom;
use rand::thread_rng;

use gelatin::glium::{texture::SrgbTexture2d, Display};
use gelatin::glium::Display;
use gelatin::window::Window;

use crate::image_cache::{self, AnimationFrameTexture, ImageCache};
Expand Down Expand Up @@ -220,7 +220,7 @@ impl PlaybackManager {
self.image_player.request_load(LoadRequest::Jump(0));
}

pub fn image_texture(&self) -> Option<Rc<SrgbTexture2d>> {
pub fn image_texture(&self) -> Option<AnimationFrameTexture> {
self.image_player.image_texture()
}

Expand All @@ -235,7 +235,7 @@ impl PlaybackManager {
let new_file = self.folder_player.image_texture();
let mut file_changed = prev_file.is_none() != new_file.is_none();
if let (Some(prev), Some(new)) = (prev_file, new_file) {
file_changed = !Rc::ptr_eq(&prev, &new);
file_changed = !Rc::ptr_eq(&prev.texture, &new.texture);
}
if file_changed {
self.image_player.start_playback_forward();
Expand Down Expand Up @@ -316,8 +316,8 @@ impl<P: Playback> ImgSequencePlayer<P> {
self.load_request = request;
}

pub fn image_texture(&self) -> Option<Rc<SrgbTexture2d>> {
self.image_texture.clone().map(|t| t.texture)
pub fn image_texture(&self) -> Option<AnimationFrameTexture> {
self.image_texture.clone()
}

pub fn update_image(
Expand Down

0 comments on commit cecddb8

Please sign in to comment.