Simplify and speedup preview creating (#660)

* Simplify and speedup preview creating

* Speedup also comparing images
This commit is contained in:
Rafał Mikrut 2022-04-05 16:40:41 +02:00 committed by GitHub
parent f843673c7e
commit 39b2f4bc36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 83 additions and 178 deletions

View File

@ -473,11 +473,8 @@ cache_clear_message_label_3 = This may slightly speedup loading/saving to cache.
cache_clear_message_label_4 = WARNING: Operation will remove all cached data from unplugged external drives. So each hash will need to be regenerated.
# Show preview
preview_temporary_file = Failed to open temporary image file {$name}, reason {$reason}.
preview_0_size = Cannot create preview of image {$name}, with 0 width or height.
preview_temporary_image_save = Failed to save temporary image file to {$name}, reason {$reason}.
preview_temporary_image_remove = Failed to delete temporary image file {$name}, reason {$reason}.
preview_failed_to_create_cache_dir = Failed to create dir {$name} needed by image preview, reason {$reason}.
preview_image_resize_failure = Failed to resize image {$name}.
preview_image_opening_failure = Failed to open image {$name}, reason {$reason}
# Compare images (L is short Left, R is short Right - they can't take too much space)
compare_groups_number = Group { $current_group }/{ $all_groups } ({ $images_in_group } images)

View File

@ -1,22 +1,16 @@
use crate::flg;
use czkawka_core::common::get_dynamic_image_from_raw_image;
use czkawka_core::similar_images::RAW_IMAGE_EXTENSIONS;
use gdk::gdk_pixbuf::{InterpType, Pixbuf};
use gtk::prelude::*;
use gtk::{CheckButton, Image, ListStore, Orientation, ScrolledWindow, TreeIter, TreeModel, TreePath, TreeSelection};
use image::imageops::FilterType;
use image::DynamicImage;
use std::cell::RefCell;
use std::rc::Rc;
use crate::gui_structs::gui_data::GuiData;
use crate::help_functions::{
count_number_of_groups, get_full_name_from_path_name, get_image_path_temporary, get_max_file_name, resize_dynamic_image_dimension, NotebookObject, HEADER_ROW_COLOR,
NOTEBOOKS_INFOS,
};
use crate::help_functions::{count_number_of_groups, get_full_name_from_path_name, get_max_file_name, resize_pixbuf_dimension, NotebookObject, HEADER_ROW_COLOR, NOTEBOOKS_INFOS};
use crate::localizer_core::generate_translation_hashmap;
const BIG_PREVIEW_SIZE: u32 = 600;
const SMALL_PREVIEW_SIZE: u32 = 100;
const BIG_PREVIEW_SIZE: i32 = 600;
const SMALL_PREVIEW_SIZE: i32 = 100;
pub fn connect_button_compare(gui_data: &GuiData) {
let button_compare = gui_data.bottom_buttons.buttons_compare.clone();
@ -345,37 +339,39 @@ fn generate_cache_for_results(vector_with_path: Vec<(String, String, gtk::TreePa
// For now threads cannot be used because Image and TreeIter cannot be used in threads
let mut cache_all_images = Vec::new();
for (full_path, name, tree_path) in vector_with_path {
let name_lowercase = name.to_lowercase();
let dynamic_image = if RAW_IMAGE_EXTENSIONS.iter().any(|f| name_lowercase.ends_with(f)) {
match get_dynamic_image_from_raw_image(&full_path) {
Some(t) => t,
None => {
println!("Failed to convert rawimage {}", full_path);
DynamicImage::new_rgb8(1, 1)
let small_img = gtk::Image::new();
let big_img = gtk::Image::new();
match Pixbuf::from_file(&full_path) {
Ok(pixbuf) =>
{
#[allow(clippy::never_loop)]
loop {
let pixbuf_big = match resize_pixbuf_dimension(pixbuf, (BIG_PREVIEW_SIZE, BIG_PREVIEW_SIZE), InterpType::Nearest) {
None => {
println!("Failed to resize image {}.", full_path);
break;
}
Some(pixbuf) => pixbuf,
};
let pixbuf_small = match resize_pixbuf_dimension(pixbuf_big.clone(), (SMALL_PREVIEW_SIZE, SMALL_PREVIEW_SIZE), InterpType::Nearest) {
None => {
println!("Failed to resize image {}.", full_path);
break;
}
Some(pixbuf) => pixbuf,
};
big_img.set_pixbuf(Some(&pixbuf_big));
small_img.set_pixbuf(Some(&pixbuf_small));
break;
}
}
} else {
match image::open(&full_path) {
Ok(t) => t,
Err(_) => {
println!("Failed to open image {}", full_path);
DynamicImage::new_rgb8(1, 1)
}
Err(e) => {
println!("Failed to open image {}, reason {}", full_path, e);
}
};
let big_thumbnail = resize_dynamic_image_dimension(dynamic_image, (BIG_PREVIEW_SIZE, BIG_PREVIEW_SIZE), &FilterType::Triangle);
let big_path = get_image_path_temporary("roman", 1, "jpg");
let _ = big_thumbnail.save(&big_path);
let big_img = gtk::Image::new();
big_img.set_from_file(Some(big_path));
let small_thumbnail = resize_dynamic_image_dimension(big_thumbnail, (SMALL_PREVIEW_SIZE, SMALL_PREVIEW_SIZE), &FilterType::Triangle);
let small_path = get_image_path_temporary("roman", 1, "jpg");
let _ = small_thumbnail.save(&small_path);
let small_img = gtk::Image::new();
small_img.set_from_file(Some(small_path));
cache_all_images.push((full_path, name, big_img, small_img, tree_path));
}
cache_all_images

View File

@ -1,4 +1,3 @@
use directories_next::ProjectDirs;
use gdk::gdk_pixbuf::{InterpType, Pixbuf};
use std::cmp::Ordering;
use std::collections::HashMap;
@ -6,8 +5,6 @@ use std::path::{Path, PathBuf};
use gtk::prelude::*;
use gtk::{Bin, ListStore, TextView, TreeView, Widget};
use image::imageops::FilterType;
use image::DynamicImage;
use crate::flg;
use czkawka_core::big_file::BigFile;
@ -723,16 +720,16 @@ pub fn count_number_of_groups(tree_view: &TreeView, column_color: i32) -> u32 {
number_of_selected_groups
}
pub fn resize_dynamic_image_dimension(img: DynamicImage, requested_size: (u32, u32), filter_type: &FilterType) -> DynamicImage {
let current_ratio = img.width() as f32 / img.height() as f32;
pub fn resize_pixbuf_dimension(pixbuf: Pixbuf, requested_size: (i32, i32), interp_type: InterpType) -> Option<Pixbuf> {
let current_ratio = pixbuf.width() as f32 / pixbuf.height() as f32;
let mut new_size;
match current_ratio.partial_cmp(&(requested_size.0 as f32 / requested_size.1 as f32)).unwrap() {
Ordering::Greater => {
new_size = (requested_size.0, (img.height() * requested_size.0) / img.width());
new_size = (requested_size.0, (pixbuf.height() * requested_size.0) / pixbuf.width());
new_size = (std::cmp::max(new_size.0, 1), std::cmp::max(new_size.1, 1));
}
Ordering::Less => {
new_size = ((img.width() * requested_size.1) / img.height(), requested_size.1);
new_size = ((pixbuf.width() * requested_size.1) / pixbuf.height(), requested_size.1);
new_size = (std::cmp::max(new_size.0, 1), std::cmp::max(new_size.1, 1));
}
Ordering::Equal => {
@ -740,17 +737,7 @@ pub fn resize_dynamic_image_dimension(img: DynamicImage, requested_size: (u32, u
new_size = (std::cmp::max(new_size.0, 1), std::cmp::max(new_size.1, 1));
}
}
img.resize(new_size.0, new_size.1, *filter_type)
}
pub fn get_image_path_temporary(file_name: &str, number: u32, extension: &str) -> PathBuf {
let path_buf;
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
path_buf = PathBuf::from(proj_dirs.cache_dir());
} else {
path_buf = PathBuf::new().join("/var");
}
path_buf.join(format!("{}{}.{}", file_name, number, extension))
pixbuf.scale_simple(new_size.0, new_size.1, interp_type)
}
pub fn get_max_file_name(file_name: &str, max_length: usize) -> String {

View File

@ -1,17 +1,14 @@
use std::cell::RefCell;
use std::fs;
use std::ops::Deref;
use std::path::Path;
use std::rc::Rc;
use czkawka_core::common::get_dynamic_image_from_raw_image;
use directories_next::ProjectDirs;
use gdk::gdk_pixbuf::Pixbuf;
use gtk::gdk_pixbuf::InterpType;
use gtk::prelude::*;
use gtk::{CheckButton, Image, SelectionMode, TextView, TreeView};
use image::imageops::FilterType;
use crate::flg;
use czkawka_core::similar_images::{IMAGE_RS_EXTENSIONS, RAW_IMAGE_EXTENSIONS, SIMILAR_VALUES};
use czkawka_core::similar_images::SIMILAR_VALUES;
use czkawka_core::similar_videos::MAX_TOLERANCE;
use crate::create_tree_view::*;
@ -682,130 +679,58 @@ fn show_preview(
// Only show preview when selected is only one item, because there is no method to recognize current clicked item in multiselection
if selected_rows.len() == 1 && check_button_settings_show_preview.is_active() {
let tree_path = selected_rows[0].clone();
if let Some(proj_dirs) = ProjectDirs::from("pl", "Qarmin", "Czkawka") {
// TODO labels on {} are in testing stage, so we just ignore for now this warning until found better idea how to fix this
#[allow(clippy::never_loop)]
'dir: loop {
let cache_dir = proj_dirs.cache_dir();
if cache_dir.exists() {
if !cache_dir.is_dir() {
add_text_to_text_view(
text_view_errors,
format!("Path {} doesn't point at folder, which is needed by image preview", cache_dir.display()).as_str(),
);
break 'dir;
}
} else if let Err(e) = fs::create_dir_all(cache_dir) {
// TODO labels on {} are in testing stage, so we just ignore for now this warning until found better idea how to fix this
#[allow(clippy::never_loop)]
'dir: loop {
let path = tree_model.value(&tree_model.iter(&tree_path).unwrap(), column_path).get::<String>().unwrap();
let name = tree_model.value(&tree_model.iter(&tree_path).unwrap(), column_name).get::<String>().unwrap();
let file_name = get_full_name_from_path_name(&path, &name);
let file_name = file_name.as_str();
{
let preview_path = preview_path.borrow();
let preview_path = preview_path.deref();
if file_name == preview_path {
return; // Preview is already created, no need to recreate it
}
}
let mut pixbuf = match Pixbuf::from_file(file_name) {
Ok(pixbuf) => pixbuf,
Err(e) => {
add_text_to_text_view(
text_view_errors,
flg!(
"preview_failed_to_create_cache_dir",
generate_translation_hashmap(vec![("name", cache_dir.display().to_string()), ("reason", e.to_string())])
"preview_image_opening_failure",
generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", e.to_string())])
)
.as_str(),
);
break 'dir;
}
let path = tree_model.value(&tree_model.iter(&tree_path).unwrap(), column_path).get::<String>().unwrap();
let name = tree_model.value(&tree_model.iter(&tree_path).unwrap(), column_name).get::<String>().unwrap();
};
let file_name = get_full_name_from_path_name(&path, &name);
let file_name = file_name.as_str();
if let Some(extension) = Path::new(file_name).extension() {
let extension_lowercase = format!(".{}", extension.to_string_lossy().to_lowercase());
let is_raw_image = RAW_IMAGE_EXTENSIONS.contains(&extension_lowercase.as_str());
if !IMAGE_RS_EXTENSIONS.contains(&extension_lowercase.as_str()) && !is_raw_image {
break 'dir;
}
{
let preview_path = preview_path.borrow();
let preview_path = preview_path.deref();
if file_name == preview_path {
return; // Preview is already created, no need to recreate it
}
}
let img;
if !is_raw_image {
img = match image::open(&file_name) {
Ok(t) => t,
Err(e) => {
add_text_to_text_view(
text_view_errors,
flg!(
"preview_temporary_file",
generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", e.to_string())])
)
.as_str(),
);
break 'dir;
}
};
} else {
img = match get_dynamic_image_from_raw_image(file_name) {
Some(t) => t,
None => {
add_text_to_text_view(
text_view_errors,
flg!(
"preview_temporary_file",
generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", "None".to_string())])
)
.as_str(),
);
break 'dir;
}
}
}
if img.width() == 0 || img.height() == 0 {
add_text_to_text_view(
text_view_errors,
flg!("preview_0_size", generate_translation_hashmap(vec![("name", file_name.to_string())])).as_str(),
);
break 'dir;
}
let img = resize_dynamic_image_dimension(img, (400, 400), &FilterType::Triangle); // Triangle and Nearest is the fastest
let file_dir = match is_raw_image {
true => cache_dir.join("cached_file.jpg"),
false => cache_dir.join(format!("cached_file.{}", extension.to_string_lossy().to_lowercase())),
};
if let Err(e) = img.save(&file_dir) {
add_text_to_text_view(
text_view_errors,
flg!(
"preview_temporary_image_save",
generate_translation_hashmap(vec![("name", file_dir.display().to_string()), ("reason", e.to_string())])
)
.as_str(),
);
let _ = fs::remove_file(&file_dir);
break 'dir;
}
let string_dir = file_dir.to_string_lossy().to_string();
image_preview.set_from_file(Some(string_dir));
{
let mut preview_path = preview_path.borrow_mut();
*preview_path = file_name.to_string();
}
if let Err(e) = fs::remove_file(&file_dir) {
add_text_to_text_view(
text_view_errors,
flg!(
"preview_temporary_image_remove",
generate_translation_hashmap(vec![("name", file_dir.display().to_string()), ("reason", e.to_string())])
)
.as_str(),
);
break 'dir;
}
created_image = true;
pixbuf = match resize_pixbuf_dimension(pixbuf, (400, 400), InterpType::Nearest) {
None => {
add_text_to_text_view(
text_view_errors,
flg!("preview_image_resize_failure", generate_translation_hashmap(vec![("name", file_name.to_string())])).as_str(),
);
break 'dir;
}
break 'dir;
Some(pixbuf) => pixbuf,
};
image_preview.set_pixbuf(Some(&pixbuf));
{
let mut preview_path = preview_path.borrow_mut();
*preview_path = file_name.to_string();
}
created_image = true;
break 'dir;
}
}
if created_image {