1
0
Fork 0
mirror of synced 2024-04-28 09:33:30 +12:00

Add support for multiple hashes in similar images to GUI (#448)

* Add support for multiple hashes in similar images to GUI

* Needs a little more testing

* General fixes
This commit is contained in:
Rafał Mikrut 2021-11-19 06:35:26 +01:00 committed by GitHub
parent 20c89f44f0
commit 96e7af0d25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 561 additions and 135 deletions

1
Cargo.lock generated
View file

@ -502,6 +502,7 @@ dependencies = [
"gtk",
"humansize",
"image",
"img_hash",
"open",
"trash",
"winapi",

View file

@ -125,7 +125,7 @@ pub enum Commands {
hash_alg: HashAlg,
#[structopt(short = "f", long, default_value = "Lanczos3", parse(try_from_str = parse_similar_image_filter), help="Hash algorithm (allowed: Lanczos3, Nearest, Triangle, Faussian, Catmullrom)")]
image_filter: FilterType,
#[structopt(short = "c", long, default_value = "8", parse(try_from_str = parse_image_hash_size), help="Hash size (allowed: 4, 8, 16, 32)")]
#[structopt(short = "c", long, default_value = "8", parse(try_from_str = parse_image_hash_size), help="Hash size (allowed: 4, 8, 16)")]
hash_size: u8,
},
#[structopt(name = "zeroed", about = "Finds zeroed files", help_message = HELP_MESSAGE, after_help = "EXAMPLE:\n czkawka zeroed -d /home/rafal -e /home/rafal/Pulpit -f results.txt")]
@ -368,8 +368,7 @@ fn parse_image_hash_size(src: &str) -> Result<u8, String> {
"4" => 4,
"8" => 8,
"16" => 16,
"32" => 32,
_ => return Err("Couldn't parse the image hash size (allowed: 4, 8, 16, 32)".to_string()),
_ => return Err("Couldn't parse the image hash size (allowed: 4, 8, 16)".to_string()),
};
Ok(hash_size)
}

View file

@ -22,6 +22,13 @@ use std::thread::sleep;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::{fs, mem, thread};
// TODO check for better values
pub const SIMILAR_VALUES: [[u32; 6]; 3] = [
[0, 1, 2, 3, 4, 5], // 4 - Max 16
[0, 2, 5, 7, 14, 20], // 8 - Max 256
[2, 5, 10, 20, 40, 80], // 16 - Max 65536
];
#[derive(Debug)]
pub struct ProgressData {
pub current_stage: u8,
@ -36,8 +43,6 @@ pub enum Similarity {
Similar(u32),
}
const MAX_SIMILARITY: u32 = 12;
#[derive(Clone, Debug)]
pub struct FileEntry {
pub path: PathBuf,
@ -134,7 +139,7 @@ impl SimilarImages {
pub fn set_hash_size(&mut self, hash_size: u8) {
self.hash_size = match hash_size {
4 | 8 | 16 | 32 | 64 => hash_size,
4 | 8 | 16 => hash_size,
e => {
panic!("Invalid value of hash size {}", e);
}
@ -313,7 +318,7 @@ impl SimilarImages {
.to_lowercase();
// Checking allowed image extensions
let allowed_image_extensions = [".jpg", ".jpeg", ".png" /*, ".bmp"*/, ".tiff", ".tif", ".pnm", ".tga", ".ff" /*, ".gif"*/, ".jif", ".jfi", ".webp"];
let allowed_image_extensions = [".jpg", ".jpeg", ".png" /*, ".bmp"*/, ".tiff", ".tif", ".pnm", ".tga", ".ff" /*, ".gif"*/, ".jif", ".jfi" /*, ".webp"*/]; // webp cannot be seen in preview, gif needs to be enabled after releasing image crate 0.24.0, bmp needs to be fixed in image crate
if !allowed_image_extensions.iter().any(|e| file_name_lowercase.ends_with(e)) {
continue 'dir;
}
@ -455,10 +460,17 @@ impl SimilarImages {
let hash = hasher.hash_image(&image);
let buf: Vec<u8> = hash.as_bytes().to_vec();
if buf.iter().all(|e| *e == 0) {
// A little broken image
return Some(None);
// Images with hashes with full of 0 or 255 usually means that algorithm fails to decode them because e.g. contains a log of alpha channel
{
if buf.iter().all(|e| *e == 0) {
return Some(None);
}
if buf.iter().all(|e| *e == 255) {
return Some(None);
}
}
file_entry.hash = buf.clone();
Some(Some((file_entry, buf)))
@ -512,7 +524,15 @@ impl SimilarImages {
let mut this_time_check_hashes;
let mut master_of_group: BTreeSet<Vec<u8>> = Default::default(); // Lista wszystkich głównych hashy, które odpowiadają za porównywanie
for current_similarity in 0..=MAX_SIMILARITY {
// TODO optimize this for big temp_max_similarity values
let temp_max_similarity = match self.hash_size {
4 => SIMILAR_VALUES[0][5],
8 => SIMILAR_VALUES[1][5],
16 => SIMILAR_VALUES[1][5],
_ => panic!(),
};
for current_similarity in 0..=temp_max_similarity {
this_time_check_hashes = available_hashes.clone();
if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() {
@ -757,6 +777,8 @@ fn load_hashes_from_file(text_messages: &mut Messages, hash_size: u8, hash_alg:
let mut hashmap_loaded_entries: BTreeMap<String, FileEntry> = Default::default();
let number_of_results: usize = hash_size as usize * hash_size as usize / 8;
// Read the file line by line using the lines() iterator from std::io::BufRead.
for (index, line) in reader.lines().enumerate() {
let line = match line {
@ -767,14 +789,21 @@ fn load_hashes_from_file(text_messages: &mut Messages, hash_size: u8, hash_alg:
}
};
let uuu = line.split("//").collect::<Vec<&str>>();
if uuu.len() != 12 {
text_messages.warnings.push(format!("Found invalid data in line {} - ({}) in cache file {}", index + 1, line, cache_file.display()));
if uuu.len() != (number_of_results + 4) {
text_messages.warnings.push(format!(
"Found invalid data in line {} - ({}) in cache file {}, expected {} values, found {}",
index + 1,
line,
cache_file.display(),
uuu.len(),
number_of_results + 4
));
continue;
}
// Don't load cache data if destination file not exists
if Path::new(uuu[0]).exists() {
let mut hash: Vec<u8> = Vec::new();
for i in 0..hash_size {
for i in 0..number_of_results {
hash.push(match uuu[4 + i as usize].parse::<u8>() {
Ok(t) => t,
Err(e) => {
@ -843,127 +872,74 @@ fn get_cache_file(hash_size: &u8, hash_alg: &HashAlg, image_filter: &FilterType)
format!("cache_similar_images_{}_{}_{}.txt", hash_size, convert_algorithm_to_string(hash_alg), convert_filters_to_string(image_filter))
}
// TODO check for better values
pub fn get_string_from_similarity(similarity: &Similarity, hash_size: u8) -> String {
let index_preset = match hash_size {
4 => 0,
8 => 1,
16 => 2,
_ => panic!(),
};
match similarity {
Similarity::None => {
panic!()
}
Similarity::Similar(h) => match hash_size {
4 => {
if *h == 0 {
Similarity::Similar(h) => {
#[cfg(debug_assertions)]
{
if *h <= SIMILAR_VALUES[index_preset][0] {
format!("Very High {}", *h)
} else if *h <= 1 {
} else if *h <= SIMILAR_VALUES[index_preset][1] {
format!("High {}", *h)
} else if *h <= 2 {
} else if *h <= SIMILAR_VALUES[index_preset][2] {
format!("Medium {}", *h)
} else if *h <= 3 {
} else if *h <= SIMILAR_VALUES[index_preset][3] {
format!("Small {}", *h)
} else if *h <= 4 {
} else if *h <= SIMILAR_VALUES[index_preset][4] {
format!("Very Small {}", *h)
} else if *h <= 5 {
} else if *h <= SIMILAR_VALUES[index_preset][5] {
format!("Minimal {}", *h)
} else {
panic!();
}
}
8 => {
if *h == 0 {
format!("Very High {}", *h)
} else if *h <= 1 {
format!("High {}", *h)
} else if *h <= 3 {
format!("Medium {}", *h)
} else if *h <= 5 {
format!("Small {}", *h)
} else if *h <= 8 {
format!("Very Small {}", *h)
} else if *h <= 12 {
format!("Minimal {}", *h)
#[cfg(not(debug_assertions))]
{
if *h <= SIMILAR_VALUES[index_preset][0] {
format!("Very High")
} else if *h <= SIMILAR_VALUES[index_preset][1] {
format!("High")
} else if *h <= SIMILAR_VALUES[index_preset][2] {
format!("Medium")
} else if *h <= SIMILAR_VALUES[index_preset][3] {
format!("Small")
} else if *h <= SIMILAR_VALUES[index_preset][4] {
format!("Very Small")
} else if *h <= SIMILAR_VALUES[index_preset][5] {
format!("Minimal")
} else {
panic!();
}
}
16 => {
if *h <= 2 {
format!("Very High {}", *h)
} else if *h <= 7 {
format!("High {}", *h)
} else if *h <= 11 {
format!("Medium {}", *h)
} else if *h <= 17 {
format!("Small {}", *h)
} else if *h <= 23 {
format!("Very Small {}", *h)
} else if *h <= 44 {
format!("Minimal {}", *h)
} else {
panic!();
}
}
32 => {
if *h <= 10 {
format!("Very High {}", *h)
} else if *h <= 30 {
format!("High {}", *h)
} else if *h <= 50 {
format!("Medium {}", *h)
} else if *h <= 90 {
format!("Small {}", *h)
} else if *h <= 120 {
format!("Very Small {}", *h)
} else if *h <= 180 {
format!("Minimal {}", *h)
} else {
panic!();
}
}
_ => {
panic!("Not supported hash size");
}
},
}
}
}
pub fn return_similarity_from_similarity_preset(similarity_preset: &SimilarityPreset, hash_size: u8) -> Similarity {
match hash_size {
4 => match similarity_preset {
SimilarityPreset::VeryHigh => Similarity::Similar(0),
SimilarityPreset::High => Similarity::Similar(1),
SimilarityPreset::Medium => Similarity::Similar(2),
SimilarityPreset::Small => Similarity::Similar(3),
SimilarityPreset::VerySmall => Similarity::Similar(4),
SimilarityPreset::Minimal => Similarity::Similar(4),
SimilarityPreset::None => panic!(""),
},
8 => match similarity_preset {
SimilarityPreset::VeryHigh => Similarity::Similar(0),
SimilarityPreset::High => Similarity::Similar(1),
SimilarityPreset::Medium => Similarity::Similar(3),
SimilarityPreset::Small => Similarity::Similar(5),
SimilarityPreset::VerySmall => Similarity::Similar(8),
SimilarityPreset::Minimal => Similarity::Similar(12),
SimilarityPreset::None => panic!(""),
},
16 => match similarity_preset {
SimilarityPreset::VeryHigh => Similarity::Similar(2),
SimilarityPreset::High => Similarity::Similar(7),
SimilarityPreset::Medium => Similarity::Similar(11),
SimilarityPreset::Small => Similarity::Similar(17),
SimilarityPreset::VerySmall => Similarity::Similar(23),
SimilarityPreset::Minimal => Similarity::Similar(44),
SimilarityPreset::None => panic!(""),
},
32 => match similarity_preset {
SimilarityPreset::VeryHigh => Similarity::Similar(10),
SimilarityPreset::High => Similarity::Similar(30),
SimilarityPreset::Medium => Similarity::Similar(50),
SimilarityPreset::Small => Similarity::Similar(90),
SimilarityPreset::VerySmall => Similarity::Similar(120),
SimilarityPreset::Minimal => Similarity::Similar(180),
SimilarityPreset::None => panic!(""),
},
let index_preset = match hash_size {
4 => 0,
8 => 1,
16 => 2,
_ => panic!(),
};
match similarity_preset {
SimilarityPreset::VeryHigh => Similarity::Similar(SIMILAR_VALUES[index_preset][0]),
SimilarityPreset::High => Similarity::Similar(SIMILAR_VALUES[index_preset][1]),
SimilarityPreset::Medium => Similarity::Similar(SIMILAR_VALUES[index_preset][2]),
SimilarityPreset::Small => Similarity::Similar(SIMILAR_VALUES[index_preset][3]),
SimilarityPreset::VerySmall => Similarity::Similar(SIMILAR_VALUES[index_preset][4]),
SimilarityPreset::Minimal => Similarity::Similar(SIMILAR_VALUES[index_preset][5]),
SimilarityPreset::None => panic!(""),
}
}

View file

@ -31,6 +31,9 @@ open = "2.0.1"
# To get image preview
image = "0.23.14"
# To get image_hash types
img_hash = "3.2.0"
# Move files to trash
trash = "1.3.0"

View file

@ -9,22 +9,25 @@ const INSTRUCTION_SITE: &str = "https://github.com/qarmin/czkawka/blob/master/in
pub fn connect_about_buttons(gui_data: &GuiData) {
let button_donation = gui_data.about.button_donation.clone();
button_donation.connect_clicked(move |_| {
if let Err(e) = open::that(SPONSOR_SITE) {
println!("Failed to open sponsor site: {}, reason {}", SPONSOR_SITE, e)
};
open::that_in_background(SPONSOR_SITE);
// if let Err(e) = open::that(SPONSOR_SITE) {
// println!("Failed to open sponsor site: {}, reason {}", SPONSOR_SITE, e)
// };
});
let button_instruction = gui_data.about.button_instruction.clone();
button_instruction.connect_clicked(move |_| {
if let Err(e) = open::that(INSTRUCTION_SITE) {
println!("Failed to open instruction site: {}, reason {}", INSTRUCTION_SITE, e)
};
open::that_in_background(INSTRUCTION_SITE);
// if let Err(e) = open::that(INSTRUCTION_SITE) {
// println!("Failed to open instruction site: {}, reason {}", INSTRUCTION_SITE, e)
// };
});
let button_repository = gui_data.about.button_repository.clone();
button_repository.connect_clicked(move |_| {
if let Err(e) = open::that(REPOSITORY_SITE) {
println!("Failed to open repository site: {}, reason {}", REPOSITORY_SITE, e)
};
open::that_in_background(REPOSITORY_SITE);
// if let Err(e) = open::that(REPOSITORY_SITE) {
// println!("Failed to open repository site: {}, reason {}", REPOSITORY_SITE, e)
// };
});
}

View file

@ -17,6 +17,7 @@ use czkawka_core::zeroed::ZeroedFiles;
use glib::Sender;
use gtk::prelude::*;
use gtk::WindowPosition;
use img_hash::{FilterType, HashAlg};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
@ -91,6 +92,19 @@ pub fn connect_button_search(
let check_button_settings_hide_hard_links = gui_data.settings.check_button_settings_hide_hard_links.clone();
let check_button_settings_use_cache = gui_data.settings.check_button_settings_use_cache.clone();
let entry_settings_cache_file_minimal_size = gui_data.settings.entry_settings_cache_file_minimal_size.clone();
let radio_button_similar_hash_size_4 = gui_data.main_notebook.radio_button_similar_hash_size_4.clone();
let radio_button_similar_hash_size_8 = gui_data.main_notebook.radio_button_similar_hash_size_8.clone();
let radio_button_similar_hash_size_16 = gui_data.main_notebook.radio_button_similar_hash_size_16.clone();
let radio_button_resize_algorithm_catmullrom = gui_data.main_notebook.radio_button_resize_algorithm_catmullrom.clone();
let radio_button_resize_algorithm_lanczos3 = gui_data.main_notebook.radio_button_resize_algorithm_lanczos3.clone();
let radio_button_resize_algorithm_nearest = gui_data.main_notebook.radio_button_resize_algorithm_nearest.clone();
let radio_button_resize_algorithm_triangle = gui_data.main_notebook.radio_button_resize_algorithm_triangle.clone();
let radio_button_resize_algorithm_gaussian = gui_data.main_notebook.radio_button_resize_algorithm_gaussian.clone();
let radio_button_similar_hash_algorithm_gradient = gui_data.main_notebook.radio_button_similar_hash_algorithm_gradient.clone();
let radio_button_similar_hash_algorithm_blockhash = gui_data.main_notebook.radio_button_similar_hash_algorithm_blockhash.clone();
let radio_button_similar_hash_algorithm_mean = gui_data.main_notebook.radio_button_similar_hash_algorithm_mean.clone();
let radio_button_similar_hash_algorithm_vertgradient = gui_data.main_notebook.radio_button_similar_hash_algorithm_vertgradient.clone();
let radio_button_similar_hash_algorithm_doublegradient = gui_data.main_notebook.radio_button_similar_hash_algorithm_doublegradient.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));
@ -268,6 +282,47 @@ pub fn connect_button_search(
get_list_store(&tree_view_similar_images_finder).clear();
let hash_size;
if radio_button_similar_hash_size_4.is_active() {
hash_size = 4;
} else if radio_button_similar_hash_size_8.is_active() {
hash_size = 8;
} else if radio_button_similar_hash_size_16.is_active() {
hash_size = 16;
} else {
panic!("No radio button is pressed");
}
let image_filter;
if radio_button_resize_algorithm_catmullrom.is_active() {
image_filter = FilterType::CatmullRom;
} else if radio_button_resize_algorithm_lanczos3.is_active() {
image_filter = FilterType::Lanczos3;
} else if radio_button_resize_algorithm_nearest.is_active() {
image_filter = FilterType::Nearest;
} else if radio_button_resize_algorithm_triangle.is_active() {
image_filter = FilterType::Triangle;
} else if radio_button_resize_algorithm_gaussian.is_active() {
image_filter = FilterType::Gaussian;
} else {
panic!("No radio button is pressed");
}
let hash_alg;
if radio_button_similar_hash_algorithm_blockhash.is_active() {
hash_alg = HashAlg::Blockhash;
} else if radio_button_similar_hash_algorithm_gradient.is_active() {
hash_alg = HashAlg::Gradient;
} else if radio_button_similar_hash_algorithm_mean.is_active() {
hash_alg = HashAlg::Mean;
} else if radio_button_similar_hash_algorithm_vertgradient.is_active() {
hash_alg = HashAlg::VertGradient;
} else if radio_button_similar_hash_algorithm_doublegradient.is_active() {
hash_alg = HashAlg::DoubleGradient;
} else {
panic!("No radio button is pressed");
}
let minimal_file_size = entry_similar_images_minimal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 16);
let maximal_file_size = entry_similar_images_maximal_size.text().as_str().parse::<u64>().unwrap_or(1024 * 1024 * 1024 * 1024);
@ -286,6 +341,9 @@ pub fn connect_button_search(
sf.set_maximal_file_size(maximal_file_size);
sf.set_similarity(similarity);
sf.set_use_cache(use_cache);
sf.set_hash_alg(hash_alg);
sf.set_hash_size(hash_size);
sf.set_image_filter(image_filter);
sf.find_similar_images(Some(&stop_receiver), Some(&futures_sender_similar_images));
let _ = glib_stop_sender.send(Message::SimilarImages(sf));
});

View file

@ -42,6 +42,9 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver<
let buttons_names = gui_data.bottom_buttons.buttons_names.clone();
let window_progress = gui_data.progress_window.window_progress.clone();
let taskbar_state = gui_data.taskbar_state.clone();
let radio_button_similar_hash_size_4 = gui_data.main_notebook.radio_button_similar_hash_size_4.clone();
let radio_button_similar_hash_size_8 = gui_data.main_notebook.radio_button_similar_hash_size_8.clone();
let radio_button_similar_hash_size_16 = gui_data.main_notebook.radio_button_similar_hash_size_16.clone();
let main_context = glib::MainContext::default();
let _guard = main_context.acquire().unwrap();
@ -56,6 +59,17 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver<
// Restore clickability to main notebook
notebook_main.set_sensitive(true);
let hash_size;
if radio_button_similar_hash_size_4.is_active() {
hash_size = 4;
} else if radio_button_similar_hash_size_8.is_active() {
hash_size = 8;
} else if radio_button_similar_hash_size_16.is_active() {
hash_size = 16;
} else {
panic!("No radio button is pressed");
}
match msg {
Message::Duplicates(df) => {
if df.get_stopped_search() {
@ -523,7 +537,7 @@ pub fn connect_compute_results(gui_data: &GuiData, glib_stop_receiver: Receiver<
let values: [(u32, &dyn ToValue); 12] = [
(0, &true),
(1, &false),
(2, &(similar_images::get_string_from_similarity(&file_entry.similarity, 8).to_string())), // TODO use proper hash value
(2, &(similar_images::get_string_from_similarity(&file_entry.similarity, hash_size).to_string())),
(3, &file_entry.size.file_size(options::BINARY).unwrap()),
(4, &file_entry.size),
(5, &file_entry.dimensions),

View file

@ -0,0 +1,32 @@
extern crate gtk;
use crate::gui_data::GuiData;
use czkawka_core::similar_images::SIMILAR_VALUES;
use gtk::prelude::*;
pub fn connect_similar_image_size_change(gui_data: &GuiData) {
// This should set values to max possible value like in return_similarity_from_similarity_preset and get_string_from_similarity
{
let radio_button_similar_hash_size_4 = gui_data.main_notebook.radio_button_similar_hash_size_4.clone();
let scale_similarity = gui_data.main_notebook.scale_similarity.clone();
radio_button_similar_hash_size_4.connect_clicked(move |_| {
scale_similarity.set_range(0_f64, SIMILAR_VALUES[0][5] as f64);
scale_similarity.set_fill_level(SIMILAR_VALUES[0][5] as f64);
});
}
{
let radio_button_similar_hash_size_8 = gui_data.main_notebook.radio_button_similar_hash_size_8.clone();
let scale_similarity = gui_data.main_notebook.scale_similarity.clone();
radio_button_similar_hash_size_8.connect_clicked(move |_| {
scale_similarity.set_range(0_f64, SIMILAR_VALUES[1][5] as f64);
scale_similarity.set_fill_level(SIMILAR_VALUES[1][5] as f64);
});
}
{
let radio_button_similar_hash_size_16 = gui_data.main_notebook.radio_button_similar_hash_size_16.clone();
let scale_similarity = gui_data.main_notebook.scale_similarity.clone();
radio_button_similar_hash_size_16.connect_clicked(move |_| {
scale_similarity.set_range(0_f64, SIMILAR_VALUES[2][5] as f64);
scale_similarity.set_fill_level(SIMILAR_VALUES[2][5] as f64);
});
}
}

View file

@ -162,9 +162,11 @@ fn common_open_function(tree_view: &gtk::TreeView, column_name: i32, column_path
}
}
if let Err(e) = open::that(&end_path) {
println!("Failed to open {} - Error {}", end_path, e);
}
open::that_in_background(&end_path);
// if let Err(e) = open::that(&end_path) {
// println!("Failed to open {} - Error {}", end_path, e);
// }
}
}

View file

@ -56,6 +56,22 @@ pub struct GuiMainNotebook {
pub radio_button_hash_type_crc32: gtk::RadioButton,
pub radio_button_hash_type_xxh3: gtk::RadioButton,
pub radio_button_resize_algorithm_lanczos3: gtk::RadioButton,
pub radio_button_resize_algorithm_nearest: gtk::RadioButton,
pub radio_button_resize_algorithm_triangle: gtk::RadioButton,
pub radio_button_resize_algorithm_gaussian: gtk::RadioButton,
pub radio_button_resize_algorithm_catmullrom: gtk::RadioButton,
pub radio_button_similar_hash_algorithm_gradient: gtk::RadioButton,
pub radio_button_similar_hash_algorithm_blockhash: gtk::RadioButton,
pub radio_button_similar_hash_algorithm_mean: gtk::RadioButton,
pub radio_button_similar_hash_algorithm_vertgradient: gtk::RadioButton,
pub radio_button_similar_hash_algorithm_doublegradient: gtk::RadioButton,
pub radio_button_similar_hash_size_4: gtk::RadioButton,
pub radio_button_similar_hash_size_8: gtk::RadioButton,
pub radio_button_similar_hash_size_16: gtk::RadioButton,
pub image_preview_similar_images: gtk::Image,
pub image_preview_duplicates: gtk::Image,
}
@ -114,6 +130,22 @@ impl GuiMainNotebook {
let radio_button_hash_type_crc32: gtk::RadioButton = builder.object("radio_button_hash_type_crc32").unwrap();
let radio_button_hash_type_xxh3: gtk::RadioButton = builder.object("radio_button_hash_type_xxh3").unwrap();
let radio_button_resize_algorithm_lanczos3: gtk::RadioButton = builder.object("radio_button_resize_algorithm_lanczos3").unwrap();
let radio_button_resize_algorithm_nearest: gtk::RadioButton = builder.object("radio_button_resize_algorithm_nearest").unwrap();
let radio_button_resize_algorithm_triangle: gtk::RadioButton = builder.object("radio_button_resize_algorithm_triangle").unwrap();
let radio_button_resize_algorithm_gaussian: gtk::RadioButton = builder.object("radio_button_resize_algorithm_gaussian").unwrap();
let radio_button_resize_algorithm_catmullrom: gtk::RadioButton = builder.object("radio_button_resize_algorithm_catmullrom").unwrap();
let radio_button_similar_hash_algorithm_gradient: gtk::RadioButton = builder.object("radio_button_similar_hash_algorithm_gradient").unwrap();
let radio_button_similar_hash_algorithm_blockhash: gtk::RadioButton = builder.object("radio_button_similar_hash_algorithm_blockhash").unwrap();
let radio_button_similar_hash_algorithm_mean: gtk::RadioButton = builder.object("radio_button_similar_hash_algorithm_mean").unwrap();
let radio_button_similar_hash_algorithm_vertgradient: gtk::RadioButton = builder.object("radio_button_similar_hash_algorithm_vertgradient").unwrap();
let radio_button_similar_hash_algorithm_doublegradient: gtk::RadioButton = builder.object("radio_button_similar_hash_algorithm_doublegradient").unwrap();
let radio_button_similar_hash_size_4: gtk::RadioButton = builder.object("radio_button_similar_hash_size_4").unwrap();
let radio_button_similar_hash_size_8: gtk::RadioButton = builder.object("radio_button_similar_hash_size_8").unwrap();
let radio_button_similar_hash_size_16: gtk::RadioButton = builder.object("radio_button_similar_hash_size_16").unwrap();
let image_preview_similar_images: gtk::Image = builder.object("image_preview_similar_images").unwrap();
let image_preview_duplicates: gtk::Image = builder.object("image_preview_duplicates").unwrap();
@ -157,6 +189,19 @@ impl GuiMainNotebook {
radio_button_hash_type_blake3,
radio_button_hash_type_crc32,
radio_button_hash_type_xxh3,
radio_button_resize_algorithm_lanczos3,
radio_button_resize_algorithm_nearest,
radio_button_resize_algorithm_triangle,
radio_button_resize_algorithm_gaussian,
radio_button_resize_algorithm_catmullrom,
radio_button_similar_hash_algorithm_gradient,
radio_button_similar_hash_algorithm_blockhash,
radio_button_similar_hash_algorithm_mean,
radio_button_similar_hash_algorithm_vertgradient,
radio_button_similar_hash_algorithm_doublegradient,
radio_button_similar_hash_size_4,
radio_button_similar_hash_size_8,
radio_button_similar_hash_size_16,
image_preview_similar_images,
entry_duplicate_maximal_size,
entry_same_music_maximal_size,

View file

@ -3,6 +3,7 @@ use crate::create_tree_view::*;
use crate::double_click_opening::*;
use crate::gui_data::*;
use crate::help_functions::*;
use czkawka_core::similar_images::SIMILAR_VALUES;
use directories_next::ProjectDirs;
use gtk::prelude::*;
use gtk::{CheckButton, Image, SelectionMode, TextView, TreeView};
@ -54,7 +55,8 @@ pub fn initialize_gui(gui_data: &mut GuiData) {
// Set step increment
{
scale_similarity.set_range(0_f64, 12_f64);
scale_similarity.set_range(0_f64, SIMILAR_VALUES[1][5] as f64); // This defaults to value of minimal size of hash 8
scale_similarity.set_fill_level(SIMILAR_VALUES[1][5] as f64);
scale_similarity.adjustment().set_step_increment(1_f64);
}

View file

@ -18,6 +18,7 @@ mod connect_popovers;
mod connect_progress_window;
mod connect_selection_of_directories;
mod connect_settings;
mod connect_similar_image_size_change;
mod create_tree_view;
mod double_click_opening;
mod gui_about;
@ -58,6 +59,7 @@ use crate::connect_popovers::*;
use crate::connect_progress_window::*;
use crate::connect_selection_of_directories::*;
use crate::connect_settings::*;
use crate::connect_similar_image_size_change::*;
use crate::gui_data::*;
use crate::initialize_gui::*;
use crate::saving_loading::*;
@ -147,6 +149,7 @@ fn main() {
connect_settings(&gui_data);
connect_button_about(&gui_data);
connect_about_buttons(&gui_data);
connect_similar_image_size_change(&gui_data);
// Quit the program when X in main window was clicked
{

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2
<!-- Generated with glade 3.39.0
The MIT License (MIT)
@ -911,7 +911,6 @@ Author: Rafał Mikrut
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>
@ -1087,6 +1086,280 @@ Author: Rafał Mikrut
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Resize algorithm:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_resize_algorithm_lanczos3">
<property name="label" translatable="yes">Lanczos3</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_resize_algorithm_nearest">
<property name="label" translatable="yes">Nearest</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_resize_algorithm_lanczos3</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_resize_algorithm_triangle">
<property name="label" translatable="yes">Triangle</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_resize_algorithm_lanczos3</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_resize_algorithm_gaussian">
<property name="label" translatable="yes">Gaussian</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_resize_algorithm_lanczos3</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_resize_algorithm_catmullrom">
<property name="label" translatable="yes">CatmullRom</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_resize_algorithm_lanczos3</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Hash type:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_similar_hash_algorithm_gradient">
<property name="label" translatable="yes">Gradient</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_similar_hash_algorithm_blockhash">
<property name="label" translatable="yes">Blockhash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_similar_hash_algorithm_gradient</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_similar_hash_algorithm_mean">
<property name="label" translatable="yes">Mean</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_similar_hash_algorithm_gradient</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_similar_hash_algorithm_vertgradient">
<property name="label" translatable="yes">VertGradient</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_similar_hash_algorithm_gradient</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_similar_hash_algorithm_doublegradient">
<property name="label" translatable="yes">DoubleGradient</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_similar_hash_algorithm_gradient</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Hash size:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_similar_hash_size_4">
<property name="label" translatable="yes">4</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_similar_hash_size_8</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_similar_hash_size_8">
<property name="label" translatable="yes">8</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio_button_similar_hash_size_16">
<property name="label" translatable="yes">16</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="active">True</property>
<property name="draw-indicator">True</property>
<property name="group">radio_button_similar_hash_size_8</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
@ -1162,7 +1435,7 @@ Author: Rafał Mikrut
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">3</property>
</packing>
</child>
<child>
@ -1224,7 +1497,7 @@ Author: Rafał Mikrut
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">4</property>
</packing>
</child>
<child>
@ -1239,7 +1512,7 @@ Author: Rafał Mikrut
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">5</property>
</packing>
</child>
</object>
@ -1256,7 +1529,6 @@ Author: Rafał Mikrut
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="valign">center</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="expand">False</property>

View file

@ -69,7 +69,7 @@ By default, all tools only write about results to console, but it is possible wi
## Config/Cache files
Currently, Czkawka stores few config and cache files on disk:
- `czkawka_gui_config.txt` - stores configuration of GUI which may be loaded at startup
- `cache_similar_image.txt` - stores cache data and hashes which may be used later without needing to compute image hash again - editing this file may cause app crashes.
- `cache_similar_image_SIZE_HASH_FILTER.txt` - stores cache data and hashes which may be used later without needing to compute image hash again - editing this file manually is not recommended, but it is allowed. Each algorithms uses its own file, because hashes are completely different in each.
- `cache_broken_files.txt` - stores cache data of broken files
- `cache_duplicates_Blake3.txt` - stores cache data of duplicated files, to not suffer too big of a performance hit when saving/loading file, only already fully hashed files bigger than 5MB are stored. Similar files with replaced `Blake3` to e.g. `SHA256` may be shown, when support for new hashes will be introduced in Czkawka.
@ -89,7 +89,7 @@ Windows - `C:\Users\Username\AppData\Local\Qarmin\Czkawka\cache`
- **Manually adding multiple directories**
You can manually edit config file `czkawka_gui_config.txt` and add/remove/change directories as you want. After setting required values, configuration must be loaded to Czkawka.
- **Slow checking of little number similar images**
If you checked before a large number of images (several tens of thousands) and they are still present on the disk, then the required information about all of them is loaded from and saved to the cache, even if you are working with only few image files. You can rename cache file `cache_similar_image.txt`(to be able to use it again) or delete it - cache will then regenerate but with smaller number of entries and this way it should load and save a lot of faster.
If you checked before a large number of images (several tens of thousands) and they are still present on the disk, then the required information about all of them is loaded from and saved to the cache, even if you are working with only few image files. You can rename one of cache file which starts from `cache_similar_image`(to be able to use it again) or delete it - cache will then regenerate but with smaller number of entries and this way it should load and save a lot of faster.
- **Not all columns are visible**
For now it is possible that some columns will not be visible when some are too wide. There are 2 workarounds for now
- View can be scrolled via horizontal scroll bar
@ -163,11 +163,13 @@ For each file inside the given path its size is read and then after sorting the
### Temporary Files
Searching for temporary files only involves comparing their extensions with a previously prepared list.
Currently files with these extensions are considered temporary files -
Currently files with these extensions are considered temporary files -
```
["#", "thumbs.db", ".bak", "~", ".tmp", ".temp", ".ds_store", ".crdownload", ".part", ".cache", ".dmp", ".download", ".partial"]
```
This only removes the most basic temporary files, for more I suggest to use BleachBit.
### Zeroed Files
Zeroed files very often are results of e.g. incorrect file downloads.
@ -224,7 +226,21 @@ Computed hash data is then thrown into a special tree that allows to compare has
Next these hashes are saved to file, to be able to open images without needing to hash it more times.
Finally, each hash is compared with the others and if the distance between them is less than the maximum distance specified by the user, the images are considered similar and thrown from the pool of images to be searched.
Finally, each hash is compared with the others and if the distance between them is less than the maximum distance specified by the user, the images are considered similar and thrown from the pool of images to be searched.
It is possible to choose one of 5 types of hashes - `Gradient`, `Mean`, `VertGradient`, `Blockhash`, `DoubleGradient`.
Before calculating hashes usually images are resized with specific algorithm(`Lanczos3`, `Gaussian`, `CatmullRom`, `Triangle`, `Nearest`) to e.g. 8x8 or 16x16 image(allowed sizes - `4x4`, `8x8`, `16x16`), which allows simplifying later computations. Both size and filter can be adjusted in application.
Each configuration saves results to different cache files to save users from invalid results.
Some images broke hash functions and create hashes full of `0` or `255`, so these images are silently excluded(probably proper error reporting should be provided).
You can test each algorithm with provided CLI tool, just put to folder `test.jpg` file and run inside this command `czkawka_cli tester -i`
Some tidbits:
- Smaller hash size not always means that calculating it will take more time
- `Blockhash` is the only algorithm that don't resize images before hashing
- `Nearest` resize algorithm can be faster even 5 times than any other available but provide worse results
### Broken Files
This tool finds files which are corrupted or have an invalid extension.