Skip to content

Commit

Permalink
Prevent image resolution from being lowered when already low. (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
MaarifaMaarifa authored Nov 23, 2023
1 parent 40ddc54 commit 790587b
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 79 deletions.
91 changes: 91 additions & 0 deletions src/core/api/tv_maze/image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use std::io::Write;

use bytes::Bytes;
use tracing::error;

const POSTER_WIDTH: u32 = 480;
const POSTER_HEIGHT: u32 = 853;
const BACKGROUND_WIDTH: u32 = 1280;
const BACKGROUND_HEIGHT: u32 = 720;

pub enum ImageResolution {
Original(ImageKind),
Medium,
}

#[derive(Clone, Copy)]
pub enum ImageKind {
Poster,
Background,
}

/// Loads the image from the provided url
///
/// Since Original images from TvMaze may have extremely high resolution up to 4k which can cause `wgpu` to crash,
/// this function will thumbnail the original image to the size that is good enough to be displayed in the GUI.
pub async fn load_image(image_url: String, image_resolution: ImageResolution) -> Option<Bytes> {
loop {
match reqwest::get(&image_url).await {
Ok(response) => {
if let Ok(bytes) = response.bytes().await {
let image = image::load_from_memory(&bytes)
.map_err(|err| error!("failed to load image from the api: {}", err))
.ok()?;

break match image_resolution {
ImageResolution::Original(image_kind) => {
if should_lower_resolution(&image, image_kind) {
lower_image_resolution(image, image_kind)
} else {
Some(bytes)
}
}
ImageResolution::Medium => Some(bytes),
};
}
}
Err(ref err) => {
if err.is_request() {
super::random_async_sleep().await;
} else {
break None;
}
}
}
}
}

fn should_lower_resolution(image: &image::DynamicImage, image_kind: ImageKind) -> bool {
match image_kind {
ImageKind::Poster => image.height() > POSTER_HEIGHT || image.width() > POSTER_WIDTH,
ImageKind::Background => {
image.height() > BACKGROUND_HEIGHT || image.width() > BACKGROUND_WIDTH
}
}
}

fn lower_image_resolution(
image: image::DynamicImage,
image_kind: ImageKind,
) -> Option<bytes::Bytes> {
let img = match image_kind {
ImageKind::Poster => image.thumbnail(POSTER_WIDTH, POSTER_WIDTH),
ImageKind::Background => image.thumbnail(BACKGROUND_WIDTH, BACKGROUND_HEIGHT),
};

let mut writer = std::io::BufWriter::new(vec![]);

let mut jpeg_encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut writer, 100);

jpeg_encoder
.encode_image(&img)
.map_err(|err| error!("failed to encode image: {}", err))
.ok()?;

writer
.flush()
.map_err(|err| error!("failed to flush image bytes: {}", err))
.ok()?;

Some(bytes::Bytes::copy_from_slice(writer.get_ref()))
}
66 changes: 1 addition & 65 deletions src/core/api/tv_maze/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use std::io::Write;

use bytes::Bytes;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::error;

pub mod episodes_information;
pub mod image;
pub mod seasons_list;
pub mod series_information;
pub mod series_searching;
Expand Down Expand Up @@ -50,67 +47,6 @@ pub struct Image {
pub medium_image_url: String,
}

pub enum ImageType {
Original(OriginalType),
Medium,
}

pub enum OriginalType {
Poster,
Background,
}

/// Loads the image from the provided url
///
/// Since Original images from TvMaze may have extremely high resolution up to 4k which can cause `wgpu` to crash,
/// this function will thumbnail the original image to the size that is good enough to be displayed in the GUI.
pub async fn load_image(image_url: String, image_type: ImageType) -> Option<Bytes> {
loop {
match reqwest::get(&image_url).await {
Ok(response) => {
if let Ok(bytes) = response.bytes().await {
break Some(if let ImageType::Original(original_type) = image_type {
let img = image::load_from_memory(&bytes)
.map_err(|err| error!("failed to load image from the api: {}", err))
.ok()?;

let img = match original_type {
OriginalType::Poster => img.thumbnail(480, 853),
OriginalType::Background => img.thumbnail(1280, 720),
};

let mut writer = std::io::BufWriter::new(vec![]);

let mut jpeg_encoder =
image::codecs::jpeg::JpegEncoder::new_with_quality(&mut writer, 100);

jpeg_encoder
.encode_image(&img)
.map_err(|err| error!("failed to encode image: {}", err))
.ok()?;

writer
.flush()
.map_err(|err| error!("failed to flush image bytes: {}", err))
.ok()?;

bytes::Bytes::copy_from_slice(writer.get_ref())
} else {
bytes
});
}
}
Err(ref err) => {
if err.is_request() {
random_async_sleep().await;
} else {
break None;
}
}
}
}
}

/// T
fn try_bad_json(json_string: &str) -> Option<(String, String)> {
if let Ok(bad_response) = serde_json::from_str::<BadResponse>(json_string) {
Expand Down
6 changes: 3 additions & 3 deletions src/core/caching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ use bytes::Bytes;
use std::io::{self, ErrorKind};
use std::path;

pub use super::api::tv_maze::image::{ImageKind, ImageResolution};
use super::api::tv_maze::{series_information::SeriesMainInformation, ApiError};
pub use super::api::tv_maze::{ImageType, OriginalType};
use super::paths;
use crate::core::api::tv_maze::{self, deserialize_json};
use lazy_static::lazy_static;
Expand Down Expand Up @@ -136,7 +136,7 @@ impl Cacher {
}

/// Loads the image from the provided url
pub async fn load_image(image_url: String, image_type: ImageType) -> Option<Bytes> {
pub async fn load_image(image_url: String, image_type: ImageResolution) -> Option<Bytes> {
// Hashing the image url as a file name as the forward slashes in web urls
// mimic paths
use sha2::{Digest, Sha256};
Expand All @@ -153,7 +153,7 @@ pub async fn load_image(image_url: String, image_type: ImageType) -> Option<Byte
Err(err) => {
if err.kind() == ErrorKind::NotFound {
info!("falling back online for image with link {}", image_url);
if let Some(image_bytes) = tv_maze::load_image(image_url, image_type).await {
if let Some(image_bytes) = tv_maze::image::load_image(image_url, image_type).await {
write_cache(&image_bytes, &image_path).await;
Some(image_bytes)
} else {
Expand Down
7 changes: 3 additions & 4 deletions src/core/caching/show_images.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::io::ErrorKind;

use super::{
load_image, read_cache, write_cache, CacheFilePath, ImageType as ImageKind, OriginalType,
CACHER,
load_image, read_cache, write_cache, CacheFilePath, ImageKind, ImageResolution, CACHER,
};
use crate::core::api::tv_maze::{
deserialize_json,
Expand Down Expand Up @@ -45,7 +44,7 @@ pub async fn get_recent_banner(series_id: u32) -> Option<bytes::Bytes> {
{
return load_image(
recent_background.resolutions.original.url.clone(),
ImageKind::Original(OriginalType::Background),
ImageResolution::Original(ImageKind::Background),
)
.await;
};
Expand All @@ -58,7 +57,7 @@ pub async fn get_recent_banner(series_id: u32) -> Option<bytes::Bytes> {

load_image(
recent_banner.resolutions.original.url,
ImageKind::Original(OriginalType::Background),
ImageResolution::Original(ImageKind::Background),
)
.await
}
4 changes: 2 additions & 2 deletions src/gui/series_page/series/cast_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ mod cast_poster {
fn load_person_image(image: Option<Image>) -> Command<Message> {
if let Some(image) = image {
Command::perform(
caching::load_image(image.medium_image_url, caching::ImageType::Medium),
caching::load_image(image.medium_image_url, caching::ImageResolution::Medium),
Message::PersonImageLoaded,
)
} else {
Expand All @@ -358,7 +358,7 @@ mod cast_poster {
fn load_character_image(image: Option<Image>) -> Command<Message> {
if let Some(image) = image {
Command::perform(
caching::load_image(image.medium_image_url, caching::ImageType::Medium),
caching::load_image(image.medium_image_url, caching::ImageResolution::Medium),
Message::CharacterImageLoaded,
)
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/gui/series_page/series/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ fn load_images(series_info_image: Option<Image>, series_id: u32) -> [Command<Mes
Command::perform(
caching::load_image(
image_url.original_image_url,
caching::ImageType::Original(caching::OriginalType::Poster),
caching::ImageResolution::Original(caching::ImageKind::Poster),
),
Message::SeriesImageLoaded,
)
Expand Down
5 changes: 4 additions & 1 deletion src/gui/tabs/discover_tab/searching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,10 @@ mod search_result {
image_url
.map(|url| {
Command::perform(
caching::load_image(url.medium_image_url, caching::ImageType::Medium),
caching::load_image(
url.medium_image_url,
caching::ImageResolution::Medium,
),
Message::ImageLoaded,
)
.map(move |message| IndexedMessage::new(index, message))
Expand Down
9 changes: 6 additions & 3 deletions src/gui/troxide_widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ pub mod episode_widget {

let command = if let Some(image) = episode_image {
Command::perform(
caching::load_image(image.medium_image_url, caching::ImageType::Medium),
caching::load_image(image.medium_image_url, caching::ImageResolution::Medium),
Message::ImageLoaded,
)
.map(move |message| IndexedMessage::new(index, message))
Expand Down Expand Up @@ -321,8 +321,11 @@ pub mod series_poster {
if let Some(image) = image {
Command::perform(
async move {
caching::load_image(image.medium_image_url, caching::ImageType::Medium)
.await
caching::load_image(
image.medium_image_url,
caching::ImageResolution::Medium,
)
.await
},
GenericPosterMessage::ImageLoaded,
)
Expand Down

0 comments on commit 790587b

Please sign in to comment.