diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4c58266..3e5c17f 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -32,17 +32,10 @@ jobs: linux-cli-${{github.ref}}-${{github.sha}} - name: Install basic libraries - run: sudo apt-get update; sudo apt install libgtk-4-dev libasound2-dev -y - - - name: Build CLI Debug - run: cargo build --bin czkawka_cli - env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-C debuginfo=0" - if: ${{ matrix.type == 'debug'}} + run: sudo apt-get update; sudo apt install libgtk-4-dev libheif-dev -y - name: Build CLI Release - run: cargo build --release --bin czkawka_cli + run: cargo build --release --bin czkawka_cli --features heif env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0" @@ -150,21 +143,26 @@ jobs: linux-gui-${{github.ref}}-${{github.sha}} - name: Install Gtk, Mingw, unzip, zip and wget - run: sudo apt-get update; sudo apt install libgtk-4-dev libasound2-dev fuse libfuse2 -y + run: sudo apt-get update; sudo apt install libgtk-4-dev fuse libfuse2 + if: ${{ matrix.toolchain == '1.60.0' }} - - name: Build GUI Debug - run: cargo build --bin czkawka_gui - env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-C debuginfo=0" - if: ${{ matrix.type == 'debug'}} + - name: Install Gtk, Mingw, unzip, zip and wget and libheif + run: sudo apt-get update; sudo apt install libgtk-4-dev fuse libfuse2 libheif-dev -y + if: ${{ matrix.toolchain == 'stable' }} - name: Build GUI Release run: cargo build --release --bin czkawka_gui env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0" - if: ${{ matrix.type == 'release'}} + if: ${{ (matrix.type == 'release') && (matrix.toolchain == '1.60.0') }} + + - name: Build GUI Release + run: cargo build --release --bin czkawka_gui --features heif + env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-C debuginfo=0" + if: ${{ (matrix.type == 'release') && (matrix.toolchain == 'stable') }} - name: Store Linux GUI uses: actions/upload-artifact@v2 @@ -214,7 +212,7 @@ jobs: linux-appimage-gui-${{github.ref}}-${{github.sha}} - name: Install Gtk, - run: sudo apt-get update; sudo apt install libgtk-4-dev libasound2-dev librsvg2-dev wget fuse libfuse2 -y + run: sudo apt-get update; sudo apt install libgtk-4-dev libheif-dev librsvg2-dev wget fuse libfuse2 -y - name: Build GUI Release run: cargo build --release --bin czkawka_gui diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 0f2a666..7f8ca7a 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -31,15 +31,8 @@ jobs: restore-keys: | mac-cli-${{github.ref}}-${{github.sha}} - - name: Build CLI Debug - run: cargo build --bin czkawka_cli - env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-C debuginfo=0 -D warnings" - if: ${{ matrix.type == 'debug'}} - - name: Build CLI Release - run: cargo build --release --bin czkawka_cli + run: cargo build --release --bin czkawka_cli --features heif env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0 -D warnings" @@ -73,17 +66,10 @@ jobs: run: rm '/usr/local/bin/2to3' - name: Install GTK4 - run: brew install rust gtk4 - - - name: Build GUI Debug - run: cargo build --bin czkawka_gui - env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-C debuginfo=0 -D warnings" - if: ${{ matrix.type == 'debug'}} + run: brew install rust gtk4 libheif - name: Build GUI Release - run: cargo build --release --bin czkawka_gui + run: cargo build --release --bin czkawka_gui --features heif env: CARGO_INCREMENTAL: 0 RUSTFLAGS: "-C debuginfo=0 -D warnings" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 65b9b32..f25b6b8 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -28,8 +28,8 @@ jobs: components: rustfmt, clippy override: true - - name: Install Gtk - run: sudo apt-get update; sudo apt install -y libgtk-4-dev libasound2-dev + - name: Install Gtk 4 + run: sudo apt-get update; sudo apt install -y libgtk-4-dev libheif-dev -y - name: Check the format run: cargo fmt --all -- --check diff --git a/Cargo.lock b/Cargo.lock index de5abac..2c2d141 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,9 +224,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "bytemuck" @@ -461,6 +461,7 @@ dependencies = [ name = "czkawka_core" version = "4.1.0" dependencies = [ + "anyhow", "audio_checker", "bincode", "bitflags", @@ -479,6 +480,7 @@ dependencies = [ "image_hasher", "imagepipe", "infer", + "libheif-rs", "lofty", "mime_guess", "once_cell", @@ -718,7 +720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide 0.5.1", + "miniz_oxide 0.5.3", ] [[package]] @@ -917,9 +919,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a2fc0bd03d59383fc10b71a8cb731a1fac2998732a36a0c03e9b1de1513218" +checksum = "4fabb7cf843c26b085a5d68abb95d0c0bf27a9ae2eeff9c4adb503a1eb580876" dependencies = [ "bitflags", "cairo-rs", @@ -933,9 +935,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34a7e60e0c0103cffeaf7baf9dc05358dca74fd350a0c4d61c6c3083ca9fdf1" +checksum = "efe7dcb44f5c00aeabff3f69abfc5673de46559070f89bd3fbb7b66485d9cef2" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1098,9 +1100,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d5a47a78c682bb67496b562495ed84972c0512ba0654888c4dc92b80a85bd3" +checksum = "05e9020d333280b3aa38d496495bfa9b50712eebf1ad63f0ec5bcddb5eb61be4" dependencies = [ "bitflags", "cairo-rs", @@ -1114,9 +1116,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.4.2" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31d21d7ce02ba261bb24c50c4ab238a10b41a2c97c32afffae29471b7cca69b" +checksum = "7add39ccf60078508c838643a2dcc91f045c46ed63b5ea6ab701b2e25bda3fea" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -1130,9 +1132,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5d40303dabe4608fc260de2bd7563da6f85bc90af956323f0cd8ae0abcfe03" +checksum = "c64f0c2a3d80e899dc3febddad5bac193ffcf74a0fd7e31037f30dd34d6f7396" dependencies = [ "bitflags", "cairo-rs", @@ -1153,9 +1155,9 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f3c4aa605fb3d78205c7aef0eeaa6db61d8cc4dd05a465dc6ffdfdaee84f825" +checksum = "fafbcc920af4eb677d7d164853e7040b9de5a22379c596f570190c675d45f7a7" dependencies = [ "anyhow", "proc-macro-crate", @@ -1168,9 +1170,9 @@ dependencies = [ [[package]] name = "gtk4-sys" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c47c075e8f795c38f6e9a47b51a73eab77b325f83c0154979ed4d4245c36490d" +checksum = "5bc8006eea634b7c72da3ff79e24606e45f21b3b832a3c5a1f543f5f97eb0f63" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1517,6 +1519,25 @@ version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +[[package]] +name = "libheif-rs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a28e98f7e0b934f2d240bbd438b02d82d2bbce34a97d5cf25767e2c2ff249f7" +dependencies = [ + "enumn", + "libheif-sys", +] + +[[package]] +name = "libheif-sys" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9426b3c799fe53b9636aac80ddc60efa5060c1959faee2e9d3edf0da6b8536a" +dependencies = [ + "libc", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -1643,9 +1664,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", ] @@ -1800,12 +1821,12 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "open" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0524af9508f9b5c4eb41dce095860456727748f63b478d625f119a70e0d764a" +checksum = "f2423ffbf445b82e58c3b1543655968923dd06f85432f10be2bb4f1b7122f98c" dependencies = [ "pathdiff", - "winapi", + "windows-sys", ] [[package]] @@ -1841,9 +1862,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", @@ -2010,7 +2031,7 @@ dependencies = [ "bitflags", "crc32fast", "deflate 1.0.0", - "miniz_oxide 0.5.1", + "miniz_oxide 0.5.3", ] [[package]] @@ -2716,9 +2737,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" dependencies = [ "proc-macro2", "quote", @@ -2999,9 +3020,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93bbc61e655a4833cf400d0d15bf3649313422fa7572886ad6dab16d79886365" +checksum = "c6d5d669b51467dcf7b2f1a796ce0f955f05f01cafda6c19d6e95f730df29238" [[package]] name = "vec_map" diff --git a/czkawka_cli/Cargo.toml b/czkawka_cli/Cargo.toml index 8153416..0a7cf9b 100644 --- a/czkawka_cli/Cargo.toml +++ b/czkawka_cli/Cargo.toml @@ -9,8 +9,16 @@ homepage = "https://github.com/qarmin/czkawka" repository = "https://github.com/qarmin/czkawka" [dependencies] -czkawka_core = { path = "../czkawka_core", version = "4.1.0" } structopt = "0.3.26" # For enum types -image_hasher = "1.0.0" \ No newline at end of file +image_hasher = "1.0.0" + +[dependencies.czkawka_core] +path = "../czkawka_core" +version = "4.1.0" +features = [] + +[features] +default = [] +heif = ["czkawka_core/heif"] \ No newline at end of file diff --git a/czkawka_core/Cargo.toml b/czkawka_core/Cargo.toml index 8016e76..fced438 100644 --- a/czkawka_core/Cargo.toml +++ b/czkawka_core/Cargo.toml @@ -63,4 +63,11 @@ imagepipe = "0.5.0" # Checking for invalid extensions mime_guess = "2.0.4" -infer = "0.8.0" \ No newline at end of file +infer = "0.8.0" + +libheif-rs = { version = "0.15.0", optional = true } +anyhow = { version = "1.0.57", optional = true } + +[features] +default = [] +heif = ["dep:libheif-rs", "dep:anyhow"] \ No newline at end of file diff --git a/czkawka_core/src/common.rs b/czkawka_core/src/common.rs index e82c263..9ec88d1 100644 --- a/czkawka_core/src/common.rs +++ b/czkawka_core/src/common.rs @@ -9,20 +9,26 @@ use directories_next::ProjectDirs; use image::{DynamicImage, ImageBuffer, Rgb}; use imagepipe::{ImageSource, Pipeline}; +#[cfg(feature = "heif")] +use anyhow::Result; +#[cfg(feature = "heif")] +use libheif_rs::{Channel, ColorSpace, HeifContext, RgbChroma}; + /// Class for common functions used across other class/functions pub const RAW_IMAGE_EXTENSIONS: &[&str] = &[ ".mrw", ".arw", ".srf", ".sr2", ".mef", ".orf", ".srw", ".erf", ".kdc", ".kdc", ".dcs", ".rw2", ".raf", ".dcr", ".dng", ".pef", ".crw", ".iiq", ".3fr", ".nrw", ".nef", ".mos", ".cr2", ".ari", ]; pub const IMAGE_RS_EXTENSIONS: &[&str] = &[ - ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".tga", ".ff", ".jif", ".jfi", ".webp", ".gif", ".ico", ".exr", ".hdr", + ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".tga", ".ff", ".jif", ".jfi", ".webp", ".gif", ".ico", ".exr", ]; -pub const IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS: &[&str] = &[".jpg", ".jpeg", ".png", ".tiff", ".tif", ".tga", ".ff", ".jif", ".jfi", ".bmp", ".webp", ".exr", ".hdr"]; +pub const IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS: &[&str] = &[".jpg", ".jpeg", ".png", ".tiff", ".tif", ".tga", ".ff", ".jif", ".jfi", ".bmp", ".webp", ".exr"]; pub const IMAGE_RS_BROKEN_FILES_EXTENSIONS: &[&str] = &[ - ".jpg", ".jpeg", ".png", ".tiff", ".tif", ".tga", ".ff", ".jif", ".jfi", ".gif", ".bmp", ".ico", ".jfif", ".jpe", ".pnz", ".dib", ".webp", ".exr", ".hdr", + ".jpg", ".jpeg", ".png", ".tiff", ".tif", ".tga", ".ff", ".jif", ".jfi", ".gif", ".bmp", ".ico", ".jfif", ".jpe", ".pnz", ".dib", ".webp", ".exr", ]; +pub const HEIC_EXTENSIONS: &[&str] = &[".heif", ".heifs", ".heic", ".heics", ".avci", ".avcs", ".avif", ".avifs"]; pub const ZIP_FILES_EXTENSIONS: &[&str] = &[".zip"]; @@ -96,6 +102,20 @@ pub fn open_cache_folder(cache_file_name: &str, save_to_cache: bool, use_json: b None } +#[cfg(feature = "heif")] +pub fn get_dynamic_image_from_heic(path: &str) -> Result { + let im = HeifContext::read_from_file(path)?; + let handle = im.primary_image_handle()?; + let image = handle.decode(ColorSpace::Rgb(RgbChroma::Rgb), false)?; + let width = image.width(Channel::Interleaved).map_err(|e| anyhow::anyhow!("{}", e))?; + let height = image.height(Channel::Interleaved).map_err(|e| anyhow::anyhow!("{}", e))?; + let planes = image.planes(); + let interleaved_plane = planes.interleaved.unwrap(); + ImageBuffer::from_raw(width, height, interleaved_plane.data.to_owned()) + .map(DynamicImage::ImageRgb8) + .ok_or_else(|| anyhow::anyhow!("Failed to create image buffer")) +} + pub fn get_dynamic_image_from_raw_image(path: impl AsRef + std::fmt::Debug) -> Option { let file_handler = match OpenOptions::new().read(true).open(&path) { Ok(t) => t, diff --git a/czkawka_core/src/similar_images.rs b/czkawka_core/src/similar_images.rs index 2c81090..a933f93 100644 --- a/czkawka_core/src/similar_images.rs +++ b/czkawka_core/src/similar_images.rs @@ -18,7 +18,10 @@ use image_hasher::{FilterType, HashAlg, HasherConfig}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; -use crate::common::{get_dynamic_image_from_raw_image, open_cache_folder, Common, IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, LOOP_DURATION, RAW_IMAGE_EXTENSIONS}; +#[cfg(feature = "heif")] +use crate::common::get_dynamic_image_from_heic; +use crate::common::{get_dynamic_image_from_raw_image, open_cache_folder, Common, HEIC_EXTENSIONS, IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, LOOP_DURATION, RAW_IMAGE_EXTENSIONS}; + use crate::common_directory::Directories; use crate::common_extensions::Extensions; use crate::common_items::ExcludedItems; @@ -279,9 +282,11 @@ impl SimilarImages { if !self.allowed_extensions.using_custom_extensions() { self.allowed_extensions.extend_allowed_extensions(IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS); self.allowed_extensions.extend_allowed_extensions(RAW_IMAGE_EXTENSIONS); + #[cfg(feature = "heif")] + self.allowed_extensions.extend_allowed_extensions(HEIC_EXTENSIONS); } else { self.allowed_extensions - .validate_allowed_extensions(&[IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, RAW_IMAGE_EXTENSIONS].concat()); + .validate_allowed_extensions(&[IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS, RAW_IMAGE_EXTENSIONS, HEIC_EXTENSIONS].concat()); if !self.allowed_extensions.using_custom_extensions() { return true; } @@ -562,13 +567,29 @@ impl SimilarImages { let image; - if !IMAGE_RS_SIMILAR_IMAGES_EXTENSIONS.iter().any(|e| file_name_lowercase.ends_with(e)) { - image = match get_dynamic_image_from_raw_image(&file_entry.path) { - Some(t) => t, - None => - return Some(Some((file_entry, Vec::new()))) - }; - } else { + #[allow(clippy::never_loop)] // Required to implement nice if/else + 'krztyna: loop { + if RAW_IMAGE_EXTENSIONS.iter().any(|e| file_name_lowercase.ends_with(e)) { + image = match get_dynamic_image_from_raw_image(&file_entry.path) { + Some(t) => t, + None => + return Some(Some((file_entry, Vec::new()))) + }; + break 'krztyna; + } + + # [cfg(feature = "heif")] + if HEIC_EXTENSIONS.iter().any(|e| file_name_lowercase.ends_with(e)) { + image = match get_dynamic_image_from_heic(&file_entry.path.to_string_lossy().to_string()) { + Ok(t) => t, + Err(_) => { + return Some(Some((file_entry, Vec::new()))); + } + }; + break 'krztyna; + } + + // Normal image extension, when any other fail, not using if/else let result = panic::catch_unwind(|| { match image::open(file_entry.path.clone()) { Ok(t) => Ok(t), @@ -590,8 +611,9 @@ impl SimilarImages { println!("Image-rs library crashed when opening \"{:?}\" image, please check if problem happens with latest image-rs version(this can be checked via https://github.com/qarmin/ImageOpening tool) and if it is not reported, please report bug here - https://github.com/image-rs/image/issues", file_entry.path); return Some(Some((file_entry, Vec::new()))); } - } + break 'krztyna; + } let dimensions = image.dimensions(); diff --git a/czkawka_gui/Cargo.toml b/czkawka_gui/Cargo.toml index 8cc1fde..55a5d0a 100644 --- a/czkawka_gui/Cargo.toml +++ b/czkawka_gui/Cargo.toml @@ -9,7 +9,6 @@ homepage = "https://github.com/qarmin/czkawka" repository = "https://github.com/qarmin/czkawka" [dependencies] -czkawka_core = { path = "../czkawka_core", version = "4.1.0"} gdk4 = "0.4.7" glib = "0.15.11" @@ -57,3 +56,11 @@ version = "0.4.7" default-features = false # just in case features = ["v4_6"] +[dependencies.czkawka_core] +path = "../czkawka_core" +version = "4.1.0" +features = [] + +[features] +default = [] +heif = ["czkawka_core/heif"] \ No newline at end of file diff --git a/czkawka_gui/src/connect_things/connect_button_compare.rs b/czkawka_gui/src/connect_things/connect_button_compare.rs index 80552cc..b4b7907 100644 --- a/czkawka_gui/src/connect_things/connect_button_compare.rs +++ b/czkawka_gui/src/connect_things/connect_button_compare.rs @@ -1,17 +1,24 @@ use std::cell::RefCell; use std::rc::Rc; +#[cfg(feature = "heif")] +use czkawka_core::common::get_dynamic_image_from_heic; +use czkawka_core::common::HEIC_EXTENSIONS; use gdk4::gdk_pixbuf::{InterpType, Pixbuf}; use gtk4::prelude::*; -use gtk4::{CheckButton, Image, ListStore, Orientation, ScrolledWindow, TreeIter, TreeModel, TreePath, TreeSelection, Widget}; +use gtk4::{Align, CheckButton, Image, ListStore, Orientation, ScrolledWindow, TreeIter, TreeModel, TreePath, TreeSelection, Widget}; +use image::DynamicImage; use crate::flg; use crate::gui_structs::gui_data::GuiData; -use crate::help_functions::{count_number_of_groups, get_all_children, get_full_name_from_path_name, get_max_file_name, resize_pixbuf_dimension, NotebookObject, NOTEBOOKS_INFOS}; +use crate::help_functions::{ + count_number_of_groups, get_all_children, get_full_name_from_path_name, get_max_file_name, get_pixbuf_from_dynamic_image, resize_pixbuf_dimension, NotebookObject, + NOTEBOOKS_INFOS, +}; use crate::localizer_core::generate_translation_hashmap; const BIG_PREVIEW_SIZE: i32 = 600; -const SMALL_PREVIEW_SIZE: i32 = 100; +const SMALL_PREVIEW_SIZE: i32 = 130; pub fn connect_button_compare(gui_data: &GuiData) { let button_compare = gui_data.bottom_buttons.buttons_compare.clone(); @@ -88,7 +95,6 @@ pub fn connect_button_compare(gui_data: &GuiData) { let image_compare_left = gui_data.compare_images.image_compare_left.clone(); let image_compare_right = gui_data.compare_images.image_compare_right.clone(); window_compare.connect_close_request(move |window_compare| { - // TODO GTK4 window_compare.hide(); *shared_image_cache.borrow_mut() = Vec::new(); *shared_current_path.borrow_mut() = None; @@ -347,35 +353,85 @@ fn generate_cache_for_results(vector_with_path: Vec<(String, String, TreePath)>) let small_img = Image::new(); let big_img = 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, - }; + let mut pixbuf = get_pixbuf_from_dynamic_image(&DynamicImage::new_rgb8(1, 1)).unwrap(); + let name_lowercase = name.to_lowercase(); + let is_heic = HEIC_EXTENSIONS.iter().any(|extension| name_lowercase.ends_with(extension)); + let is_webp = name.to_lowercase().ends_with(".webp"); - big_img.set_from_pixbuf(Some(&pixbuf_big)); - small_img.set_from_pixbuf(Some(&pixbuf_small)); + if is_heic || is_webp { + #[allow(clippy::never_loop)] + 'czystka: loop { + #[cfg(feature = "heif")] + if is_heic { + match get_dynamic_image_from_heic(&full_path) { + Ok(t) => { + match get_pixbuf_from_dynamic_image(&t) { + Ok(t) => { + pixbuf = t; + } + Err(e) => { + println!("Failed to open image {}, reason {}", full_path, e); + } + }; + } + Err(e) => { + println!("Failed to open image {}, reason {}", full_path, e); + } + }; + break 'czystka; + } + if is_webp { + match image::open(&full_path) { + Ok(t) => { + match get_pixbuf_from_dynamic_image(&t) { + Ok(t) => { + pixbuf = t; + } + Err(e) => { + println!("Failed to open image {}, reason {}", full_path, e); + } + }; + } + Err(e) => { + println!("Failed to open image {}, reason {}", full_path, e); + } + }; + break 'czystka; + } + break 'czystka; + } + } else { + match Pixbuf::from_file(&full_path) { + Ok(t) => { + pixbuf = t; + } + Err(e) => { + println!("Failed to open image {}, reason {}", full_path, e); + } + }; + } + + #[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; } - } - Err(e) => { - println!("Failed to open image {}, reason {}", full_path, e); - } - }; + 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_from_pixbuf(Some(&pixbuf_big)); + small_img.set_from_pixbuf(Some(&pixbuf_small)); + break; + } cache_all_images.push((full_path, name, big_img, small_img, tree_path)); } @@ -474,10 +530,11 @@ fn populate_similar_scrolled_view( column_selection: i32, ) { scrolled_window.set_child(None::<&Widget>); - scrolled_window.set_propagate_natural_height(true); let all_gtk_box = gtk4::Box::new(Orientation::Horizontal, 5); all_gtk_box.set_widget_name("all_box"); + all_gtk_box.set_halign(Align::Fill); + all_gtk_box.set_valign(Align::Fill); for (number, (path, _name, big_thumbnail, small_thumbnail, tree_path)) in image_cache.iter().enumerate() { let small_box = gtk4::Box::new(Orientation::Vertical, 3); @@ -534,6 +591,17 @@ fn populate_similar_scrolled_view( smaller_box.append(&button_right); small_box.append(&smaller_box); + small_box.set_halign(Align::Fill); + small_box.set_valign(Align::Fill); + small_box.set_hexpand_set(true); + small_box.set_vexpand_set(true); + small_thumbnail.set_halign(Align::Fill); + small_thumbnail.set_valign(Align::Fill); + small_thumbnail.set_hexpand(true); + small_thumbnail.set_hexpand_set(true); + small_thumbnail.set_vexpand(true); + small_thumbnail.set_vexpand_set(true); + small_box.append(small_thumbnail); all_gtk_box.append(&small_box); diff --git a/czkawka_gui/src/help_functions.rs b/czkawka_gui/src/help_functions.rs index bcda6e6..a3760e7 100644 --- a/czkawka_gui/src/help_functions.rs +++ b/czkawka_gui/src/help_functions.rs @@ -3,8 +3,12 @@ use std::collections::HashMap; use std::path::PathBuf; use gdk4::gdk_pixbuf::{InterpType, Pixbuf}; +use glib::Error; use gtk4::prelude::*; use gtk4::{ListStore, TextView, TreeView, Widget}; +use image::codecs::jpeg::JpegEncoder; +use image::{DynamicImage, EncodableLayout}; +use once_cell::sync::OnceCell; use czkawka_core::bad_extensions::BadExtensions; use czkawka_core::big_file::BigFile; @@ -857,3 +861,16 @@ pub fn set_icon_of_button>(button: &P, data: &'static [u8]) { let pixbuf = pixbuf.scale_simple(SIZE_OF_ICON, SIZE_OF_ICON, TYPE_OF_INTERPOLATION).unwrap(); image.set_from_pixbuf(Some(&pixbuf)); } + +static mut IMAGE_PREVIEW_ARRAY: OnceCell> = OnceCell::new(); +pub fn get_pixbuf_from_dynamic_image(dynamic_image: &DynamicImage) -> Result { + let mut output = Vec::new(); + JpegEncoder::new(&mut output).encode_image(dynamic_image).unwrap(); + let arra; + unsafe { + IMAGE_PREVIEW_ARRAY.take(); + IMAGE_PREVIEW_ARRAY.set(output).unwrap(); + arra = IMAGE_PREVIEW_ARRAY.get().unwrap().as_bytes(); + } + Pixbuf::from_read(arra) +} diff --git a/czkawka_gui/src/initialize_gui.rs b/czkawka_gui/src/initialize_gui.rs index 40b18e5..9f5dc62 100644 --- a/czkawka_gui/src/initialize_gui.rs +++ b/czkawka_gui/src/initialize_gui.rs @@ -8,7 +8,9 @@ use gtk4::gdk_pixbuf::InterpType; use gtk4::prelude::*; use gtk4::{CheckButton, Image, SelectionMode, TextView, TreeView}; -use czkawka_core::common::{IMAGE_RS_EXTENSIONS, RAW_IMAGE_EXTENSIONS}; +#[cfg(feature = "heif")] +use czkawka_core::common::get_dynamic_image_from_heic; +use czkawka_core::common::{HEIC_EXTENSIONS, IMAGE_RS_EXTENSIONS, RAW_IMAGE_EXTENSIONS}; use czkawka_core::similar_images::SIMILAR_VALUES; use czkawka_core::similar_videos::MAX_TOLERANCE; @@ -699,27 +701,85 @@ fn show_preview( } } + let is_heic; + let is_webp; if let Some(extension) = Path::new(&name).extension() { let extension = format!(".{}", extension.to_string_lossy().to_lowercase()); - if !RAW_IMAGE_EXTENSIONS.contains(&extension.as_str()) && !IMAGE_RS_EXTENSIONS.contains(&extension.as_str()) { + is_heic = HEIC_EXTENSIONS.contains(&extension.as_str()); + is_webp = ".webp" == extension; + if !RAW_IMAGE_EXTENSIONS.contains(&extension.as_str()) && !IMAGE_RS_EXTENSIONS.contains(&extension.as_str()) && !is_heic { break 'dir; } } else { break 'dir; } + let mut pixbuf = if is_heic || is_webp { + let image = if is_heic { + #[cfg(feature = "heif")] + match get_dynamic_image_from_heic(file_name) { + Ok(t) => t, + Err(e) => { + add_text_to_text_view( + text_view_errors, + flg!( + "preview_image_opening_failure", + generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", e.to_string())]) + ) + .as_str(), + ); + break 'dir; + } + } - let mut pixbuf = match Pixbuf::from_file(file_name) { - Ok(pixbuf) => pixbuf, - Err(e) => { - add_text_to_text_view( - text_view_errors, - flg!( - "preview_image_opening_failure", - generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", e.to_string())]) - ) - .as_str(), - ); - break 'dir; + #[cfg(not(feature = "heif"))] + panic!("") + } else if is_webp { + match image::open(file_name) { + Ok(t) => t, + Err(e) => { + add_text_to_text_view( + text_view_errors, + flg!( + "preview_image_opening_failure", + generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", e.to_string())]) + ) + .as_str(), + ); + break 'dir; + } + } + } else { + panic!(""); + }; + + match get_pixbuf_from_dynamic_image(&image) { + Ok(t) => t, + Err(e) => { + add_text_to_text_view( + text_view_errors, + flg!( + "preview_image_opening_failure", + generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", e.to_string())]) + ) + .as_str(), + ); + break 'dir; + } + } + } else { + match Pixbuf::from_file(file_name) { + Ok(pixbuf) => pixbuf, + Err(e) => { + add_text_to_text_view( + text_view_errors, + flg!( + "preview_image_opening_failure", + generate_translation_hashmap(vec![("name", file_name.to_string()), ("reason", e.to_string())]) + ) + .as_str(), + ); + break 'dir; + } } }; diff --git a/czkawka_gui/ui/compare_images.ui b/czkawka_gui/ui/compare_images.ui index e05165b..735140b 100644 --- a/czkawka_gui/ui/compare_images.ui +++ b/czkawka_gui/ui/compare_images.ui @@ -11,14 +11,7 @@ - - center - 1 - Group XD/PER XD (99 images in current group) - - - - + 1 1 @@ -29,7 +22,14 @@ - + + center + 1 + Group XD/PER XD (99 images in current group) + + + + 1 1 @@ -63,16 +63,22 @@ 1 1 - + + 100 + - + + 100 + 1 + 150 + 150 diff --git a/czkawka_gui/ui/czkawka.cmb b/czkawka_gui/ui/czkawka.cmb index 09d6bfc..548c1ee 100755 --- a/czkawka_gui/ui/czkawka.cmb +++ b/czkawka_gui/ui/czkawka.cmb @@ -24,10 +24,10 @@ (4,1,"GtkDialog","window_compare",None,None,None,None,None), (4,2,"GtkBox",None,1,None,None,None,None), (4,3,"GtkBox",None,2,None,None,None,None), - (4,4,"GtkLabel","label_group_info",3,None,None,None,None), - (4,5,"GtkButton","button_go_next_compare_group",3,None,None,None,1), + (4,4,"GtkLabel","label_group_info",3,None,None,None,1), + (4,5,"GtkButton","button_go_next_compare_group",3,None,None,None,2), (4,6,"GtkImage",None,5,None,None,None,None), - (4,7,"GtkButton","button_go_previous_compare_group",3,None,None,None,2), + (4,7,"GtkButton","button_go_previous_compare_group",3,None,None,None,None), (4,8,"GtkImage",None,7,None,None,None,None), (4,9,"GtkBox",None,2,None,None,None,1), (4,10,"GtkCheckButton","check_button_left_preview_text",9,None,None,None,None), @@ -260,8 +260,6 @@ (8,26,"GtkLabel",None,24,None,None,None,1), (9,1,"GtkDialog","window_settings",None,None,None,None,None), (9,3,"GtkBox","potatoo",1,None,None,None,None), - (9,4,"GtkBox",None,3,None,None,None,None), - (9,5,"GtkComboBoxText",None,4,None,None,None,None), (9,6,"GtkNotebook","notebook_settings",3,None,None,None,1), (9,7,"GtkNotebookPage",None,6,None,None,None,None), (9,8,"GtkBox",None,7,None,None,None,None), @@ -336,6 +334,10 @@ (4,11,"GtkWidget","focusable","1",None,None,None,None,None), (4,12,"GtkBox","homogeneous","1",None,None,None,None,None), (4,12,"GtkWidget","vexpand","1",None,None,None,None,None), + (4,13,"GtkWidget","height-request","100",None,None,None,None,None), + (4,14,"GtkWidget","height-request","100",None,None,None,None,None), + (4,15,"GtkScrolledWindow","max-content-height","150",None,None,None,None,None), + (4,15,"GtkScrolledWindow","min-content-height","150",None,None,None,None,None), (4,15,"GtkWidget","focusable","1",None,None,None,None,None), (5,1,"GtkAdjustment","page-increment","10",None,None,None,None,None), (5,1,"GtkAdjustment","step-increment","1",None,None,None,None,None), @@ -803,8 +805,6 @@ (9,1,"GtkWindow","title","Czkawka Options",1,None,None,None,None), (9,3,"GtkOrientable","orientation","vertical",None,None,None,None,None), (9,3,"GtkWidget","vexpand","1",None,None,None,None,None), - (9,4,"GtkOrientable","orientation","vertical",None,None,None,None,None), - (9,5,"GtkWidget","valign","center",None,None,None,None,None), (9,6,"GtkNotebook","tab-pos","left",None,None,None,None,None), (9,6,"GtkWidget","focusable","1",None,None,None,None,None), (9,6,"GtkWidget","vexpand","1",None,None,None,None,None), diff --git a/czkawka_gui/ui/settings.ui b/czkawka_gui/ui/settings.ui index 1007595..7cf49dd 100644 --- a/czkawka_gui/ui/settings.ui +++ b/czkawka_gui/ui/settings.ui @@ -10,16 +10,6 @@ vertical 1 - - - vertical - - - center - - - - 1 diff --git a/instructions/Compilation.md b/instructions/Compilation.md index 0aa1405..c22a47a 100644 --- a/instructions/Compilation.md +++ b/instructions/Compilation.md @@ -6,6 +6,8 @@ If you only want the terminal version without a GUI, just skip all the packages FFmpeg is not included here because it is not needed to build - it is dynamically loaded. +Support for heif images is optional and require to install libheif library. + | Program | Min | What for | |---------|------|-------------------------------------------------------------------------------| @@ -26,12 +28,12 @@ sudo yum install gtk3-devel glib2-devel ``` #### macOS -You need to install Rust via Homebrew and GTK Libraries +You need to install Rust via Homebrew, GTK Libraries and optionally heif library(to have support for heic files, which are quite popular on mac) ```shell /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" brew install rustup rustup-init -brew install gtk+3 adwaita-icon-theme librsvg +brew install gtk+3 adwaita-icon-theme librsvg libheif ``` ### Windows diff --git a/instructions/Installation.md b/instructions/Installation.md index f6aece5..3354f04 100644 --- a/instructions/Installation.md +++ b/instructions/Installation.md @@ -29,7 +29,7 @@ One very straight-forward way to do this is by using [Homebrew](https://brew.sh/ Installation in the terminal: ```shell /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -brew install gtk+3 adwaita-icon-theme ffmpeg librsvg +brew install gtk+3 adwaita-icon-theme ffmpeg librsvg libheif ``` After that, go to the location where you downloaded Czkawka and add the `executable` permission to this file. ```shell @@ -43,7 +43,7 @@ At the end execute it: **Warning** Prebuilt binaries are available only for x86_64, so if you use ARM machine like e.g. Mac M1, you need to compile manually app or install special version of required libraries which can be done via this: ```shell -arch -x86_64 /usr/local/bin/brew install gtk+3 adwaita-icon-theme ffmpeg librsvg +arch -x86_64 /usr/local/bin/brew install gtk+3 adwaita-icon-theme ffmpeg librsvg libheif ``` ### Windows diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 422da87..d524a94 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: czkawka # you probably want to 'snapcraft register ' -base: core20 # the base snap is the execution environment for this snap +base: core22 # the base snap is the execution environment for this snap version: '4.1.0' # just for humans, typically '1.2+git' or '1.3.2' summary: Czkawka - fast data cleaner written in Rust # 79 char long summary description: |