diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index 8345258..9324934 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -109,6 +109,7 @@ pub struct SimilarImages { delete_outdated_cache: bool, exclude_images_with_same_size: bool, use_reference_folders: bool, + fast_comparing: bool, } /// Info struck with helpful information's about results @@ -151,6 +152,7 @@ impl SimilarImages { delete_outdated_cache: true, exclude_images_with_same_size: false, use_reference_folders: false, + fast_comparing: false, } } @@ -179,6 +181,10 @@ impl SimilarImages { self.image_filter = image_filter; } + pub fn set_fast_comparing(&mut self, fast_comparing: bool) { + self.fast_comparing = fast_comparing; + } + pub fn get_stopped_search(&self) -> bool { self.stopped_search } @@ -633,7 +639,10 @@ impl SimilarImages { let progress_send = progress_sender.clone(); let progress_thread_run = progress_thread_run.clone(); let atomic_mode_counter = atomic_mode_counter.clone(); - let all_images = similarity as usize * available_hashes.len(); + let all_images = match self.fast_comparing { + false => similarity as usize * available_hashes.len(), + true => available_hashes.len(), + }; thread::spawn(move || loop { progress_send .unbounded_send(ProgressData { @@ -653,7 +662,7 @@ impl SimilarImages { }; //// PROGRESS THREAD END if similarity >= 1 { - for current_similarity in 1..=similarity { + if self.fast_comparing { this_time_check_hashes = available_hashes.clone(); if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { @@ -670,7 +679,7 @@ impl SimilarImages { let vector_with_found_similar_hashes = self .bktree .find(&hash, similarity) - .filter(|(similarity, hash)| (*similarity == current_similarity) && !master_of_group.contains(*hash) && available_hashes.contains_key(*hash)) + .filter(|(_similarity, hash)| !master_of_group.contains(*hash) && available_hashes.contains_key(*hash)) .collect::>(); // Not found any hash with specific distance @@ -701,15 +710,74 @@ impl SimilarImages { } } - vector_with_found_similar_hashes.iter().for_each(|(_similarity, other_hash)| { + vector_with_found_similar_hashes.iter().for_each(|(similarity, other_hash)| { let mut vec_fe = available_hashes.remove(*other_hash).unwrap(); for fe in &mut vec_fe { - fe.similarity = Similarity::Similar(current_similarity) + fe.similarity = Similarity::Similar(*similarity) } collected_similar_images.get_mut(&hash).unwrap().append(&mut vec_fe); }); } + } else { + for current_similarity in 1..=similarity { + this_time_check_hashes = available_hashes.clone(); + + if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + return false; + } + + for (hash, vec_file_entry) in this_time_check_hashes.into_iter() { + atomic_mode_counter.fetch_add(1, Ordering::Relaxed); + + // Finds hashes with specific distance to + let vector_with_found_similar_hashes = self + .bktree + .find(&hash, similarity) + .filter(|(similarity, hash)| (*similarity == current_similarity) && !master_of_group.contains(*hash) && available_hashes.contains_key(*hash)) + .collect::>(); + + // Not found any hash with specific distance + if vector_with_found_similar_hashes.is_empty() { + continue; + } + + // Current checked hash isn't in any group of similarity, so we create one, because found similar images + if !master_of_group.contains(&hash) { + master_of_group.insert(hash.clone()); + collected_similar_images.insert(hash.clone(), Vec::new()); + + let mut things: Vec = vec_file_entry + .into_iter() + .map(|mut fe| { + fe.similarity = Similarity::Similar(0); + fe + }) + .collect(); + collected_similar_images.get_mut(&hash).unwrap().append(&mut things); + + // This shouldn't be executed too much times, so it should be quite fast to check this + if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + // End thread which send info to gui + progress_thread_run.store(false, Ordering::Relaxed); + progress_thread_handle.join().unwrap(); + return false; + } + } + + vector_with_found_similar_hashes.iter().for_each(|(_similarity, other_hash)| { + let mut vec_fe = available_hashes.remove(*other_hash).unwrap(); + for fe in &mut vec_fe { + fe.similarity = Similarity::Similar(current_similarity) + } + + collected_similar_images.get_mut(&hash).unwrap().append(&mut vec_fe); + }); + } + } } } diff --git a/czkawka_gui/src/connect_button_search.rs b/czkawka_gui/src/connect_button_search.rs index 5f59a3b..bf9e7fd 100644 --- a/czkawka_gui/src/connect_button_search.rs +++ b/czkawka_gui/src/connect_button_search.rs @@ -101,6 +101,7 @@ pub fn connect_button_search( let button_settings = gui_data.header.button_settings.clone(); let button_app_info = gui_data.header.button_app_info.clone(); let check_button_music_approximate_comparison = gui_data.main_notebook.check_button_music_approximate_comparison.clone(); + let check_button_image_fast_compare = gui_data.main_notebook.check_button_image_fast_compare.clone(); buttons_search_clone.connect_clicked(move |_| { let included_directories = get_path_buf_from_vector_of_strings(get_string_from_list_store(&tree_view_included_directories, ColumnsIncludedDirectory::Path as i32, None)); @@ -292,6 +293,8 @@ pub fn connect_button_search( let delete_outdated_cache = check_button_settings_similar_images_delete_outdated_cache.is_active(); + let fast_compare = check_button_image_fast_compare.is_active(); + let futures_sender_similar_images = futures_sender_similar_images.clone(); // Find similar images thread::spawn(move || { @@ -312,6 +315,7 @@ pub fn connect_button_search( sf.set_allowed_extensions(allowed_extensions); sf.set_delete_outdated_cache(delete_outdated_cache); sf.set_exclude_images_with_same_size(ignore_same_size); + sf.set_fast_comparing(fast_compare); sf.find_similar_images(Some(&stop_receiver), Some(&futures_sender_similar_images)); let _ = glib_stop_sender.send(Message::SimilarImages(sf)); }); diff --git a/czkawka_gui/src/gui_main_notebook.rs b/czkawka_gui/src/gui_main_notebook.rs index c7b4086..bb00e22 100644 --- a/czkawka_gui/src/gui_main_notebook.rs +++ b/czkawka_gui/src/gui_main_notebook.rs @@ -85,6 +85,8 @@ pub struct GuiMainNotebook { pub check_button_image_ignore_same_size: gtk::CheckButton, pub check_button_video_ignore_same_size: gtk::CheckButton, + pub check_button_image_fast_compare: gtk::CheckButton, + pub label_image_similarity: gtk::Label, pub label_image_similarity_max: gtk::Label, @@ -206,6 +208,8 @@ impl GuiMainNotebook { let scale_similarity_similar_images: gtk::Scale = builder.object("scale_similarity_similar_images").unwrap(); let scale_similarity_similar_videos: gtk::Scale = builder.object("scale_similarity_similar_videos").unwrap(); + let check_button_image_fast_compare: gtk::CheckButton = builder.object("check_button_image_fast_compare").unwrap(); + let combo_box_image_resize_algorithm: gtk::ComboBoxText = builder.object("combo_box_image_resize_algorithm").unwrap(); let combo_box_image_hash_algorithm: gtk::ComboBoxText = builder.object("combo_box_image_hash_algorithm").unwrap(); let combo_box_image_hash_size: gtk::ComboBoxText = builder.object("combo_box_image_hash_size").unwrap(); @@ -292,6 +296,7 @@ impl GuiMainNotebook { combo_box_duplicate_hash_type, combo_box_image_hash_size, check_button_video_ignore_same_size, + check_button_image_fast_compare, } } @@ -354,6 +359,10 @@ impl GuiMainNotebook { self.check_button_image_ignore_same_size.set_label(&fl!("check_button_general_same_size")); self.check_button_video_ignore_same_size.set_label(&fl!("check_button_general_same_size")); + self.check_button_image_fast_compare.set_label(&fl!("main_notebook_image_fast_compare")); + self.check_button_image_fast_compare + .set_tooltip_text(Some(&fl!("main_notebook_image_fast_compare_tooltip"))); + { let hash_size_index = self.combo_box_image_hash_size.active().unwrap() as usize; let hash_size = IMAGES_HASH_SIZE_COMBO_BOX[hash_size_index]; diff --git a/czkawka_gui/ui/main_window.glade b/czkawka_gui/ui/main_window.glade index 0e63ba2..df4a3e6 100644 --- a/czkawka_gui/ui/main_window.glade +++ b/czkawka_gui/ui/main_window.glade @@ -1070,6 +1070,21 @@ Author: RafaƂ Mikrut 4 + + + Fast compare + True + True + False + 7 + True + + + False + True + 5 + + False diff --git a/i18n/en/czkawka_gui.ftl b/i18n/en/czkawka_gui.ftl index 7cd45b2..f262137 100644 --- a/i18n/en/czkawka_gui.ftl +++ b/i18n/en/czkawka_gui.ftl @@ -65,11 +65,19 @@ image_hash_size_tooltip = Default value for hash is 8 bytes, which allows to find very similar and different images. 16 and 32 hashes should be used only for nearly identical images. 64 bytes hash shouldn't be used, except situation where really small differences are needed to find image_resize_filter_tooltip = - To compute hash of image, library must first resize it. Depend on choosen algorithm, resulted image will looks little different. The fastest algotithm to use, but also one which gives the worst results is Nearest. + To compute hash of image, library must first resize it. Depend on chosen algorithm, resulted image will looks little different. The fastest algorithm to use, but also one which gives the worst results is Nearest. image_hash_alg_tooltip = Users can choose one from many algorithms of calculating hash. Each have both strong and weaker points and will give sometimes better and sometimes worse results for different images, so to choose the best one, manual testing is required. +main_notebook_image_fast_compare = Fast compare +main_notebook_image_fast_compare_tooltip = + Speedup searching and comparing hashes. + + In opposite to normal mode where each hash is compared to each other x times, where x is similarity which user choose, in this mode always only one comparing is used. + + This option is recommended when comparing >10000 images with non 0(Very High) similarity. + main_notebook_duplicates = Duplicate Files main_notebook_empty_directories = Empty Directories main_notebook_big_files = Big Files