├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── debian ├── changelog ├── compat ├── control ├── copyright ├── rules ├── source │ └── format ├── whatschanging.desktop └── whatschanging.install ├── examples ├── example_1.png └── example_2.png ├── icon.png ├── icon.svg └── src └── bin └── whatschanging.rs /.gitignore: -------------------------------------------------------------------------------- 1 | debian/whatschanging 2 | debian/files 3 | debian/debhelper-build-stamp 4 | debian/whatschanging.debhelper.log 5 | debian/whatschanging.substvars 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "whatschanging" 3 | version = "0.0.1" 4 | license = "BSD" 5 | authors = ["Jérémie Ferry alias mothsART "] 6 | description = "Compare 2 pictures." 7 | keywords = [ "gtk", "linux", "diff", "pictures" ] 8 | repository = "https://github.com/mothsART/whatschanging" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | glib = "^0" 13 | gdk = "^0" 14 | gdk-pixbuf = "^0" 15 | gtk = "^0" 16 | pango = "^0" 17 | cairo-rs = { version = "^0", features = ["png"] } 18 | 19 | [features] 20 | #default = ["gtk_3_22"] 21 | gtk_3_10 = ["gtk/v3_10"] 22 | gtk_3_16 = ["gtk_3_10", "gtk/v3_16"] 23 | gtk_3_18 = ["gtk_3_16", "gtk/v3_18"] #for CI tools 24 | gtk_3_20 = ["gtk_3_18", "gtk/v3_20"] #for CI tools 25 | gtk_3_22 = ["gtk_3_20", "gtk/v3_22"] #for CI tools 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2015, The Gtk-rs Project Developers. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Whatschanging 2 | 3 | [![LICENSE](https://img.shields.io/badge/license-BSD-blue.svg)](LICENSE) 4 | 5 | Compare 2 pictures. 6 | 7 | French presentation : [https://mothsart.github.io/diff-image.html](https://mothsart.github.io/diff-image.html) 8 | 9 | ```bash 10 | cargo run 11 | ``` 12 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | whatschanging (0.0.1) xenial; urgency=medium 2 | 3 | * Initial release 4 | 5 | -- Jérémie Ferry Mon, 19 Mar 2018 09:39:01 -0600 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: whatschanging 2 | Section: utils 3 | Priority: optional 4 | Maintainer: Jérémie Ferry 5 | Build-Depends: 6 | debhelper (>=9), 7 | cargo, 8 | quilt, 9 | libgtk-3-dev 10 | Standards-Version: 4.1.1 11 | Homepage: https://github.com/mothsART/whatschanging 12 | Vcs-Browser: https://github.com/mothsART/whatschanging.git 13 | Vcs-Git: https://github.com/mothsART/whatschanging.git 14 | 15 | Package: whatschanging 16 | Architecture: any 17 | Pre-Depends: dpkg (>= 1.17.14) 18 | Depends: ${shlib:Depends} 19 | Description: Compare 2 pictures. 20 | French presentation : https://mothsart.github.io/diff-image.html 21 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Ferry Jérémie and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | %: 5 | dh $@ 6 | 7 | override_dh_clean: 8 | dh_clean 9 | 10 | override_dh_auto_build: 11 | cargo build --release --frozen --verbose 12 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/whatschanging.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Name=WhatsChanging 4 | Comment=Compare 2 pictures. 5 | Exec=whatschanging 6 | Icon=/usr/share/applications/whatschanging/icon.png 7 | Terminal=false 8 | Type=Application 9 | Categories=Graphics;Illustration; 10 | Keywords=diff;picture; 11 | Name[fr_FR]=whatschanging 12 | -------------------------------------------------------------------------------- /debian/whatschanging.install: -------------------------------------------------------------------------------- 1 | target/release/whatschanging usr/bin 2 | 3 | debian/whatschanging.desktop usr/share/applications 4 | icon.png usr/share/applications/whatschanging 5 | 6 | README.md usr/share/doc/whatschanging 7 | 8 | LICENSE usr/share/licenses/whatschanging 9 | -------------------------------------------------------------------------------- /examples/example_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mothsART/whatschanging/f63f800154c9013b50104635c7d919c9841fc090/examples/example_1.png -------------------------------------------------------------------------------- /examples/example_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mothsART/whatschanging/f63f800154c9013b50104635c7d919c9841fc090/examples/example_2.png -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mothsART/whatschanging/f63f800154c9013b50104635c7d919c9841fc090/icon.png -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 44 | 48 | 52 | 56 | 60 | 64 | 67 | 68 | 70 | 71 | 73 | image/svg+xml 74 | 76 | 77 | 78 | 79 | 80 | 84 | 91 | 98 | 105 | 111 | 117 | 123 | 130 | 136 | 143 | 151 | 159 | 166 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /src/bin/whatschanging.rs: -------------------------------------------------------------------------------- 1 | extern crate gtk; 2 | extern crate gdk; 3 | extern crate gdk_pixbuf; 4 | 5 | use std::cell::RefCell; 6 | use std::rc::Rc; 7 | use std::path::PathBuf; 8 | use gtk::{FileChooserAction, FileChooserButton}; 9 | use gtk::prelude::*; 10 | use gtk::DrawingArea; 11 | use gdk_pixbuf::Pixbuf; 12 | use gdk::ContextExt; 13 | 14 | type DrawingAreaRc = Rc>; 15 | type FileChooserButtonRc = Rc>; 16 | type PathBufRc = Rc>>; 17 | type PixbufRc = Rc>>; 18 | 19 | #[derive(Debug)] 20 | struct Diff { 21 | width: i32, 22 | height: i32, 23 | img1: PixbufRc, 24 | img2: PixbufRc, 25 | channel: i32, 26 | rowstride: i32 27 | } 28 | 29 | fn str_to_pixbuf(path: PathBufRc, width: i32, height: i32) -> Option { 30 | match path.borrow().clone() { 31 | None => { 32 | None 33 | } 34 | Some(p) => { 35 | Some(Pixbuf::new_from_file_at_size(p.to_str().unwrap(), width, height).unwrap()) 36 | } 37 | } 38 | } 39 | 40 | impl Diff { 41 | pub fn new(path1: PathBufRc, path2: PathBufRc, width: i32, height: i32) -> Diff { 42 | let img1 = PixbufRc::new(RefCell::new( 43 | str_to_pixbuf(path1, width, height) 44 | )); 45 | let img2 = PixbufRc::new(RefCell::new( 46 | str_to_pixbuf(path2, width, height) 47 | )); 48 | let channel = 3; 49 | let rowstride = width * ((3 * 8 + 7) / 8); 50 | Diff { 51 | width: width, 52 | height: height, 53 | img1: img1, 54 | img2: img2, 55 | channel: channel, 56 | rowstride: rowstride 57 | } 58 | } 59 | 60 | fn get_colors(&self, buf: &mut [u8], x: i32, y: i32, rowstride: i32) -> (u8, u8, u8) { 61 | ( 62 | buf[(x * self.channel + y * rowstride) as usize], 63 | buf[(x * self.channel + y * rowstride + 1) as usize], 64 | buf[(x * self.channel + y * rowstride + 2) as usize] 65 | ) 66 | } 67 | 68 | pub fn compare(&mut self) -> Result, String> { 69 | let img1 = self.img1.borrow().clone().unwrap(); 70 | let img2 = self.img2.borrow().clone().unwrap(); 71 | if img1.get_byte_length() != img2.get_byte_length() { 72 | return Err("Fichiers de tailles différents : impossible de les comparer.".to_string()); 73 | } 74 | let buf1; 75 | let buf2; 76 | unsafe { 77 | buf1 = img1.get_pixels(); 78 | buf2 = img2.get_pixels(); 79 | } 80 | let mut vec = Vec::new(); 81 | let rowstride = img1.get_rowstride(); 82 | for y in 0..self.height { 83 | for x in 0..self.width { 84 | let pix_buf1 = self.get_colors(buf1, x, y, rowstride); 85 | let pix_buf2 = self.get_colors(buf2, x, y, rowstride); 86 | if pix_buf1 == pix_buf2 { 87 | vec.push(0); 88 | vec.push(0); 89 | vec.push(0); 90 | } 91 | else { 92 | vec.push(0); 93 | vec.push(255); 94 | vec.push(0); 95 | } 96 | } 97 | } 98 | Ok(vec) 99 | } 100 | 101 | pub fn result(&mut self) -> Result, String> { 102 | if self.img1.borrow().clone() == None || self.img2.borrow().clone() == None { 103 | return Ok(None); 104 | } 105 | else { 106 | match self.compare() { 107 | Ok(vec) => { 108 | let last_row_len = self.width * ((3 * 8 + 7) / 8); 109 | let mut has_alpha = false; 110 | if self.channel > 3 { 111 | has_alpha = true; 112 | } 113 | Ok(Some(Pixbuf::new_from_vec( 114 | vec, 115 | 0, 116 | has_alpha, 117 | 8, 118 | self.width, 119 | self.height, 120 | last_row_len 121 | ))) 122 | }, 123 | Err(err) => { 124 | Err(err) 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | fn display_pictures(drawing_area: >k::DrawingArea, file1: PathBufRc, file2: PathBufRc) { 132 | drawing_area.connect_draw(move |_w, cr| { 133 | let mut diff = Diff::new( 134 | file1.clone(), 135 | file2.clone(), 136 | 346, 137 | 382 138 | ); 139 | match &diff.img1.borrow().clone() { 140 | &None => { } 141 | &Some(ref i) => { 142 | cr.scale(1., 1.); 143 | cr.set_source_pixbuf(&i, 0f64, 0f64); 144 | cr.paint(); 145 | } 146 | } 147 | match &diff.img2.borrow().clone() { 148 | &None => { } 149 | &Some(ref i) => { 150 | cr.translate(350., 0.); 151 | cr.scale(1., 1.); 152 | cr.set_source_pixbuf(&i, 0f64, 0f64); 153 | cr.paint(); 154 | } 155 | } 156 | match diff.result() { 157 | Err(err) => { 158 | println!("{}", err); 159 | } 160 | Ok(option_pixbuf) => { 161 | match option_pixbuf { 162 | None => { }, 163 | Some(p) => { 164 | cr.paint_with_alpha(0.5); 165 | cr.translate(350., 0.); 166 | cr.set_source_pixbuf(&p, 0f64, 0f64); 167 | cr.paint(); 168 | } 169 | } 170 | } 171 | } 172 | Inhibit(false) 173 | }); 174 | } 175 | 176 | fn main() { 177 | if gtk::init().is_err() { 178 | println!("Failed to initialize GTK."); 179 | return; 180 | } 181 | let window = gtk::Window::new(gtk::WindowType::Toplevel); 182 | window.set_title("Whatschanging : compare 2 pictures"); 183 | window.set_position(gtk::WindowPosition::Center); 184 | let drawing_area = DrawingAreaRc::new(RefCell::new( 185 | Box::new(DrawingArea::new)() 186 | )); 187 | let drawing_area_borrow = drawing_area.borrow().clone(); 188 | window.connect_delete_event(|_, _| { 189 | gtk::main_quit(); 190 | Inhibit(false) 191 | }); 192 | let container_box = gtk::Box::new(gtk::Orientation::Vertical, 0); 193 | let file_box = gtk::Box::new(gtk::Orientation::Horizontal, 0); 194 | let file_chooser_one = FileChooserButtonRc::new(RefCell::new( 195 | FileChooserButton::new("Fichier 1", FileChooserAction::Open) 196 | )); 197 | let file_chooser_one_borrow = file_chooser_one.borrow().clone(); 198 | file_box.add(&file_chooser_one_borrow); 199 | let file_chooser_two = FileChooserButtonRc::new(RefCell::new( 200 | FileChooserButton::new("Fichier 2", FileChooserAction::Open) 201 | )); 202 | let file_chooser_two_borrow = file_chooser_two.borrow().clone(); 203 | file_box.add(&file_chooser_two_borrow); 204 | container_box.add(&file_box); 205 | drawing_area_borrow.set_size_request(1046, 382); 206 | container_box.add(&drawing_area_borrow); 207 | file_chooser_one_borrow.connect_file_set(move |file| { 208 | let path_buf1 = PathBufRc::new(RefCell::new( 209 | file.get_filename().clone() 210 | )); 211 | let path_buf2 = PathBufRc::new(RefCell::new( 212 | file_chooser_two.borrow().clone().get_filename().clone() 213 | )); 214 | display_pictures( 215 | &drawing_area_borrow, 216 | path_buf1, 217 | path_buf2 218 | ); 219 | }); 220 | file_chooser_two_borrow.connect_file_set(move |file| { 221 | let path_buf1 = PathBufRc::new(RefCell::new( 222 | file_chooser_one.borrow().clone().get_filename().clone() 223 | )); 224 | let path_buf2 = PathBufRc::new(RefCell::new( 225 | file.get_filename().clone() 226 | )); 227 | display_pictures( 228 | &drawing_area.borrow().clone(), 229 | path_buf1, 230 | path_buf2 231 | ); 232 | }); 233 | window.add(&container_box); 234 | window.show_all(); 235 | gtk::main(); 236 | } 237 | --------------------------------------------------------------------------------