├── img ├── cyber.jpg ├── ghost.jpg ├── samurai.jpg ├── rickmorty.jpg ├── bladerunner.jpg ├── test_color.jpg ├── logo │ ├── paleatra.png │ └── paleatra_fade.png ├── results │ ├── copy_test.jpg │ ├── cyber_cpy.jpg │ ├── ghost_cpy.jpg │ ├── ghost_pal.png │ ├── samurai_cpy.jpg │ ├── rickmorty_cpy.jpg │ ├── rickmorty_pal.png │ ├── bladerunner_cpy.jpg │ └── samurai_palette.jpg └── blade-runner-2049-movies-men-actor-wallpaper-preview.jpg ├── src ├── lib.rs ├── filters.rs ├── main.rs ├── colors.rs ├── utils.rs └── picture.rs ├── Cargo.toml ├── .gitignore ├── tests ├── utils_test.rs ├── picture_test.rs └── colors_test.rs ├── LICENSE └── README.md /img/cyber.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/cyber.jpg -------------------------------------------------------------------------------- /img/ghost.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/ghost.jpg -------------------------------------------------------------------------------- /img/samurai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/samurai.jpg -------------------------------------------------------------------------------- /img/rickmorty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/rickmorty.jpg -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod colors; 2 | pub mod utils; 3 | pub mod picture; 4 | pub mod filters; -------------------------------------------------------------------------------- /img/bladerunner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/bladerunner.jpg -------------------------------------------------------------------------------- /img/test_color.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/test_color.jpg -------------------------------------------------------------------------------- /img/logo/paleatra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/logo/paleatra.png -------------------------------------------------------------------------------- /img/results/copy_test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/results/copy_test.jpg -------------------------------------------------------------------------------- /img/results/cyber_cpy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/results/cyber_cpy.jpg -------------------------------------------------------------------------------- /img/results/ghost_cpy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/results/ghost_cpy.jpg -------------------------------------------------------------------------------- /img/results/ghost_pal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/results/ghost_pal.png -------------------------------------------------------------------------------- /img/logo/paleatra_fade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/logo/paleatra_fade.png -------------------------------------------------------------------------------- /img/results/samurai_cpy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/results/samurai_cpy.jpg -------------------------------------------------------------------------------- /img/results/rickmorty_cpy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/results/rickmorty_cpy.jpg -------------------------------------------------------------------------------- /img/results/rickmorty_pal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/results/rickmorty_pal.png -------------------------------------------------------------------------------- /img/results/bladerunner_cpy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/results/bladerunner_cpy.jpg -------------------------------------------------------------------------------- /img/results/samurai_palette.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/results/samurai_palette.jpg -------------------------------------------------------------------------------- /img/blade-runner-2049-movies-men-actor-wallpaper-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bexxmodd/paleatra/main/img/blade-runner-2049-movies-men-actor-wallpaper-preview.jpg -------------------------------------------------------------------------------- /src/filters.rs: -------------------------------------------------------------------------------- 1 | use image::DynamicImage; 2 | 3 | pub trait Filter { 4 | fn grayscale(img: &DynamicImage) -> DynamicImage { 5 | img.grayscale() 6 | } 7 | 8 | fn invert_colors(img: &DynamicImage) -> DynamicImage { 9 | let mut inv_img = img.clone(); 10 | inv_img.invert(); 11 | inv_img 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "paleatra" 3 | version = "0.0.1" 4 | authors = ["Bexx Modebadze "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | image = "0.23.14" 11 | structopt = "0.3" 12 | indicatif = "0.16.0" 13 | itertools = "0.10.0" 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | /.idea 17 | /.vscode -------------------------------------------------------------------------------- /tests/utils_test.rs: -------------------------------------------------------------------------------- 1 | extern crate paleatra; 2 | 3 | use paleatra::utils; 4 | use std::collections::HashSet; 5 | use paleatra::colors::ColorCount; 6 | use image::Rgba; 7 | 8 | #[test] 9 | fn test_get_most_freq() { 10 | let mut set: HashSet = HashSet::new(); 11 | let mut color; 12 | for i in (0..200).step_by(10) { 13 | color = ColorCount::new(Rgba([i, 10 + i, 20 + i, 0])); 14 | for _ in 0..5 + i { 15 | color.increment_count(); 16 | } 17 | set.insert(color); 18 | } 19 | 20 | let freq = utils::get_most_freq(&set, 10); 21 | let prev = &freq[0]; 22 | for c in &freq[1..] { 23 | assert!(c.0 <= prev.0, "Color counts are not sorted in descending order"); 24 | } 25 | } 26 | 27 | #[test] 28 | fn test_get_colors_from() { 29 | let img = image::open("img/test_color.jpg").unwrap(); 30 | let set = utils::get_colors_from(&img); 31 | assert_eq!(set.len(), 1); 32 | 33 | let c = ColorCount::new(Rgba([255,20,0,0])); 34 | let found = set.get(&c); 35 | assert_eq!(found.unwrap().count, 10000); 36 | } -------------------------------------------------------------------------------- /tests/picture_test.rs: -------------------------------------------------------------------------------- 1 | extern crate paleatra; 2 | 3 | use paleatra::picture; 4 | 5 | #[test] 6 | fn test_framedpic_constructor() { 7 | let _pic = picture::FramedPicture::new(200, 100, Some(10)); 8 | assert!(true); 9 | } 10 | 11 | #[test] 12 | fn test_compute_palette_size() { 13 | let pic = picture::FramedPicture::compute_palette_size(100, 9); 14 | assert_eq!(pic.0, 10); // test size 15 | assert_eq!(pic.1, 1); // test pillar 16 | } 17 | 18 | #[test] 19 | fn test_get_dimensions() { 20 | let pic = picture::FramedPicture::new(200, 100, Some(10)); 21 | let dims = pic.get_dimensions(); 22 | assert_eq!(dims.0, 220); 23 | assert_eq!(dims.1, 148); 24 | } 25 | 26 | #[test] 27 | fn test_create_palette() { 28 | let pal = picture::Palette::new(50, 10, 5); 29 | assert_eq!(pal.get_dimensions().0, 545); 30 | assert_eq!(pal.get_dimensions().1, 50); 31 | } 32 | 33 | #[test] 34 | fn test_palette_rotation() { 35 | let mut pal = picture::Palette::new(50, 10, 5); 36 | pal.rotate_90degrees(); 37 | assert_eq!(pal.get_dimensions().1, 545); 38 | assert_eq!(pal.get_dimensions().0, 50); 39 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Beka Modebadze 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 | -------------------------------------------------------------------------------- /tests/colors_test.rs: -------------------------------------------------------------------------------- 1 | extern crate paleatra; 2 | 3 | use paleatra::colors; 4 | use image::Rgba; 5 | 6 | #[test] 7 | fn test_color_construct() { 8 | let w = Rgba([255, 255, 255, 1]); 9 | let white = colors::ColorCount::new(w); 10 | assert_eq!(white.count, 1); 11 | } 12 | 13 | #[test] 14 | fn test_color_count_increment() { 15 | let w = Rgba([255, 255, 255, 1]); 16 | let mut white = colors::ColorCount::new(w); 17 | for _ in 0..10 { 18 | white.increment_count(); 19 | } 20 | assert_eq!(white.count, 11); 21 | } 22 | 23 | #[test] 24 | fn test_color_generate_hex() { 25 | let r = Rgba([255, 0, 0, 1]); 26 | let red = colors::ColorCount::new(r); 27 | assert_eq!("xFF0000", red.hex); 28 | } 29 | 30 | #[test] 31 | fn test_color_measure_diff() { 32 | let r1 = Rgba([255, 255, 255, 1]); 33 | let r3 = Rgba([250, 250, 250, 1]); 34 | let red1 = colors::ColorCount::new(r1); 35 | let red2 = colors::ColorCount::new(r1); 36 | let red3 = colors::ColorCount::new(r3); 37 | 38 | let diff0 = red1.measure_distance(&red2); 39 | let diff1 = red1.measure_distance(&red3); 40 | 41 | assert_eq!(diff0, 0); 42 | assert_eq!(diff1, 25); 43 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate image; 2 | 3 | use structopt::StructOpt; 4 | use image::{GenericImageView}; 5 | use paleatra::{ 6 | utils::{get_colors_from, get_most_freq, SaveImage, Placement}, 7 | picture::FramedPicture, 8 | }; 9 | 10 | /// simple CLI which holds terminal arguments 11 | #[derive(StructOpt)] 12 | pub struct Cli { 13 | #[structopt(parse(from_os_str))] 14 | path: std::path::PathBuf, 15 | 16 | #[structopt(parse(from_os_str))] 17 | copy: std::path::PathBuf, 18 | } 19 | 20 | 21 | fn main() { 22 | let args = Cli::from_args(); 23 | 24 | let n = 10u32; // number of colors in palette 25 | let img = image::open(&args.path).unwrap(); 26 | 27 | println!("Original image dimensions: {}x{}", 28 | img.dimensions().0, img.dimensions().1); 29 | let colors = get_colors_from(&img); 30 | 31 | let top_n = get_most_freq(&colors, n as usize); 32 | 33 | let place = Placement::Bottom; 34 | let mut imgcpy = FramedPicture::new( 35 | img.width(), img.height(), Some(n), place 36 | ); 37 | imgcpy.fill_in_palette(top_n); 38 | imgcpy.copy_img_into(n, &img); 39 | imgcpy.combine_pieces(); 40 | 41 | imgcpy.save_img(&args.copy); 42 | // palette.save(&args.copy.set_file_name("palette.png")).unwrap(); 43 | 44 | println!("Total unique pixel colors: {}", colors.len()); 45 | println!("Framed image dimensions : {}x{}", 46 | imgcpy.get_dimensions().0, imgcpy.get_dimensions().1); 47 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | Command-Line program that takes an image and produces the copy of the image with a thin frame and palette made of the 10 most frequent colors. 6 | 7 | _Current version = v.0.0.1_ 8 | 9 | # How it works 10 | 11 | Currently, the program can be used only through the command line. 12 | However, the next step will be to implement a Graphical User Interface to opening image and indicating where to save 13 | 14 | You have to clone the repo on your local machine and run cargo with two arguments: 15 | 16 | - path to the original file. 17 | - path where to store the copy of it. 18 | 19 | Both paths should include file name and extension. The extension should be of image type (e.i jpg, png, etc.) 20 | 21 | _Example_: 22 | 23 | ```bash 24 | cargo run img/rickmorty.jpg img/results/rickmorty_cpy.jpg 25 | ``` 26 | 27 | _output:_ 28 | 29 | ![rickmorty](https://github.com/bexxmodd/paleatra/blob/main/img/results/rickmorty_cpy.jpg?raw=true) 30 | 31 | You can check other examples in the `img/results` 32 | 33 | 34 | # TODO 35 | 36 | - [x] Launch alpha version 37 | 38 | - [x] Write automated tests 39 | 40 | - [ ] Expend program to take number of palette boxes as argument 41 | 42 | - [ ] Create a GUI for the program 43 | 44 | - [ ] Give option to have palette appended below, right, left, or top of the original image 45 | 46 | - [ ] Add new features 47 | 48 | ----- 49 | 50 | ## Follow Me on Social Media 51 |

52 | 53 | twitter 54 | 55 | 56 | linkedin 57 | 58 | 59 | github 60 | 61 |

62 | 63 | 64 | Repo is distributed under the MIT license. Please see the `LICENSE` for more information. 65 | -------------------------------------------------------------------------------- /src/colors.rs: -------------------------------------------------------------------------------- 1 | use image::Rgba; 2 | use std::{cmp::Ordering, 3 | hash::{Hash, Hasher}, 4 | }; 5 | 6 | /// Struct to keep track of number of occurrences of each color 7 | /// Contains the RGBA tuple and hex code of the color 8 | pub struct ColorCount { 9 | pub rgba: Rgba, // array of four colors 10 | pub hex: String, 11 | pub count: u32, 12 | } 13 | 14 | impl ColorCount { 15 | /// Constructor which generates hex code of 16 | /// the color from RGBA and sets count to 1 17 | pub fn new(rgba: Rgba) -> Self { 18 | ColorCount { 19 | rgba, 20 | hex: ColorCount::generate_hex(&rgba), 21 | count: 1 22 | } 23 | } 24 | 25 | /// Converts the RGBA tuple of the color into a hex code string 26 | /// hex is all upper case and starts with 'x'. 27 | /// example view: `x2F3FB6` 28 | /// 29 | /// # Arguments 30 | /// color - RGBA struct 31 | /// 32 | /// # Return 33 | /// hex code - of the color as a String 34 | pub fn generate_hex(color: &Rgba) -> String { 35 | let mut hexcode = "x".to_owned(); 36 | 37 | let red = format!("{:02X}", color[0]); 38 | let green = format!("{:02X}", color[1]); 39 | let blue = format!("{:02X}", color[2]); 40 | 41 | hexcode.push_str(&*red); 42 | hexcode.push_str(&*green); 43 | hexcode.push_str(&*blue); 44 | 45 | hexcode 46 | } 47 | 48 | /// Increment the color's count by one 49 | pub fn increment_count(&mut self) { 50 | self.count += 1; 51 | } 52 | 53 | /// Measure the distance from this color to other color. 54 | /// Distance is measure of how distinct is the other color from this. 55 | /// If this color is red and the other is just lighter red 56 | /// the distance will be very small, approximately |100|. 57 | /// If this color is red and the other color is blue 58 | /// the measured distance will return a much larger number 59 | /// 60 | /// # Arguments 61 | /// other: color to compare to this color 62 | /// 63 | /// # Returns 64 | /// distance as a rounded integer from this color to other 65 | pub fn measure_distance(&self, other: &ColorCount) -> i32 { 66 | let delta_r = (self.rgba[0] as i32 - other.rgba[0] as i32).pow(2); 67 | let delta_g = (self.rgba[1] as i32 - other.rgba[1] as i32).pow(2); 68 | let delta_b = (self.rgba[2] as i32 - other.rgba[2] as i32).pow(2); 69 | let delta_a = (self.rgba[3] as i32 - other.rgba[3] as i32).pow(2); 70 | 71 | let rgb_dist= (delta_r + delta_g + delta_b) / 3; 72 | (delta_a * delta_a) / 2 + rgb_dist 73 | } 74 | } 75 | 76 | impl Hash for ColorCount { 77 | fn hash(&self, state: &mut H) { 78 | self.hex.hash(state); 79 | } 80 | } 81 | 82 | impl Ord for ColorCount { 83 | fn cmp(&self, other: &Self) -> Ordering { 84 | self.count.cmp(&other.count) 85 | } 86 | } 87 | 88 | impl PartialEq for ColorCount { 89 | fn eq(&self, other: &Self) -> bool { 90 | self.hex == other.hex 91 | } 92 | } 93 | 94 | impl PartialOrd for ColorCount { 95 | fn partial_cmp(&self, other: &Self) -> Option { 96 | Some(self.cmp(other)) 97 | } 98 | } 99 | 100 | impl Eq for ColorCount {} 101 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use image::{DynamicImage, GenericImageView, ImageBuffer, Rgba}; 2 | use std::{ 3 | cmp::Reverse, 4 | collections::{BinaryHeap, HashSet}, 5 | path::PathBuf, 6 | sync::{Arc, Mutex}, 7 | thread 8 | }; 9 | use crate::colors::ColorCount; 10 | 11 | 12 | /// Grabs the `n` most frequently present elements from the Binary Tree Map 13 | /// 14 | /// # Arguments 15 | /// * set - Which is a tree set of ColorCount struct 16 | /// * n - how many most frequent entries to get 17 | /// 18 | /// # Return 19 | /// * vector - of n most frequent ColorCount structs 20 | pub fn get_most_freq(set: &HashSet, n: usize) -> Vec<&ColorCount> { 21 | let mut heap: BinaryHeap> = 22 | BinaryHeap::with_capacity(n + 1); 23 | 24 | for c in set.into_iter() { 25 | match heap.peek() { 26 | Some(v) => 27 | if v.0.1.measure_distance(c) < i32::abs(250) { 28 | continue 29 | }, 30 | _ => {} 31 | } 32 | heap.push(Reverse((c.count, c))); 33 | if heap.len() > n { 34 | heap.pop(); 35 | } 36 | } 37 | heap.into_sorted_vec().into_iter() 38 | .map(|r| r.0.1) 39 | .collect() 40 | } 41 | 42 | /// Generate ColorCount struct and calculates the number of 43 | /// occurrences of each color in a given image each image. 44 | /// 45 | /// # Arguments 46 | /// * 'img' - A `DynamicImage` to decompose 47 | /// 48 | /// # Returns 49 | /// * Binary Tree Set that contains ColorCount structs 50 | pub fn get_colors_from(img: &DynamicImage) -> HashSet { 51 | let colors = Arc::new(Mutex::new(HashSet::new())); 52 | let tmp: Arc>> = Arc::new(Mutex::new(img.pixels().collect())); 53 | let mut handles = vec![]; 54 | 55 | let slice = tmp.lock().unwrap().len() / 5; 56 | let mut start = 0usize - slice; 57 | let mut finish = 0; 58 | 59 | for _ in 0..5 { 60 | start += slice; 61 | finish += slice; 62 | let colors = Arc::clone(&colors); 63 | let tmp = Arc::clone(&tmp); 64 | let handle = thread::spawn(move || { 65 | for i in &tmp.lock().unwrap()[start..finish] { 66 | let c = ColorCount::new(i.2); 67 | let mut col_set = colors.lock().unwrap(); 68 | 69 | if !col_set.contains(&c) { 70 | col_set.insert(c); 71 | } else { 72 | match col_set.take(&c) { 73 | Some(mut v) => { 74 | v.increment_count(); 75 | col_set.insert(v); 76 | }, 77 | None => {} 78 | } 79 | } 80 | } 81 | }); 82 | handles.push(handle); 83 | } 84 | 85 | for handle in handles { 86 | handle.join().unwrap(); 87 | } 88 | let res = *colors.lock().unwrap(); 89 | res 90 | } 91 | 92 | pub trait SaveImage { 93 | 94 | /// Get image buffer reference 95 | fn get_buffer(&self) -> &ImageBuffer, Vec>; 96 | 97 | /// Saves current buffer as an image. 98 | /// Image format will be set based on the provided path, 99 | /// which is expected to include the name of the new file. 100 | /// 101 | /// # Argument 102 | /// * path - full or relative path with new filename and format 103 | fn save_img(&self, path: &PathBuf) { 104 | self.get_buffer().save(path).unwrap(); 105 | } 106 | } 107 | 108 | /// Enum to select the position of the color palette box 109 | #[derive(PartialEq)] 110 | pub enum Placement { 111 | Top, 112 | Bottom, 113 | Left, 114 | Right, 115 | } 116 | 117 | pub struct BoxShape { 118 | pub width: u32, 119 | pub height: u32, 120 | } 121 | 122 | impl BoxShape { 123 | pub fn new(w: TW, h: TH) -> Self 124 | where 125 | TW: Into, 126 | TH: Into 127 | { 128 | let width = w.into(); 129 | let height = h.into(); 130 | BoxShape { width, height } 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /src/picture.rs: -------------------------------------------------------------------------------- 1 | use crate::colors::ColorCount; 2 | use crate::filters::Filter; 3 | use crate::utils::{BoxShape, Placement, SaveImage}; 4 | use image::{imageops, DynamicImage, GenericImageView, ImageBuffer, Rgba}; 5 | 6 | /// Palette struct which holds the image buffer of the palette, 7 | /// number of boxes, box size, and empty space size between boxes 8 | /// This can be empty, or colored, and can be rotated 90 degrees. 9 | pub struct Palette { 10 | buffer: ImageBuffer, Vec>, 11 | box_size: BoxShape, 12 | n_boxes: u32, 13 | space_size: u32, 14 | } 15 | 16 | impl Palette { 17 | /// Constructor for the Palette which holds `n` number of color boxes 18 | /// 19 | /// # Arguments 20 | /// * side_length - size of each side of the box 21 | /// * n_boxes - number of color boxes 22 | /// * space_size - thickness of the empty spaces between boxes 23 | /// 24 | /// # Return 25 | /// This palette 26 | pub fn new(dims: (u32, u32), n_boxes: u32, space_size: u32) -> Self { 27 | let width = dims.0 * n_boxes + space_size * (n_boxes - 1); 28 | let box_size = BoxShape::new(dims.0, dims.1); 29 | Palette { 30 | buffer: ImageBuffer::new(width, dims.1), 31 | box_size, 32 | n_boxes, 33 | space_size, 34 | } 35 | } 36 | 37 | /// Pains the palette with `n` boxes for top `n` colors. 38 | /// Algorithm fills the boxes with colored pixels and makes jumps between 39 | /// colored boxes with empty spaces to keep color boxes separated. 40 | /// 41 | /// # Arguments 42 | /// * n - number of pillars (splits between colors) 43 | /// * top_colors - vector with top n colors from original img 44 | pub fn paint_palette(&mut self, top_colors: Vec<&ColorCount>) { 45 | let mut xp = 0; 46 | for color in top_colors { 47 | // fill box with each color 48 | for _ in 0..self.box_size.width { 49 | for yp in 0..self.box_size.height { 50 | if xp >= self.buffer.width() { 51 | break; 52 | } 53 | self.buffer.put_pixel(xp, yp, color.rgba); 54 | } 55 | xp += 1; 56 | } 57 | xp += self.space_size; // keep space between boxes 58 | } 59 | } 60 | 61 | /// Rotates palette image by 90 degrees. 62 | pub fn rotate_90degrees(&mut self) { 63 | let dims = self.buffer.dimensions(); 64 | let mut tmp: ImageBuffer, Vec> = ImageBuffer::new( 65 | dims.1, dims.0); 66 | for p in self.buffer.enumerate_pixels() { 67 | tmp.put_pixel(p.1, p.0, *p.2); 68 | } 69 | self.buffer = tmp; 70 | } 71 | 72 | /// Get the image dimensions of this image buffer in a form of tuple 73 | pub fn get_dimensions(&self) -> (u32, u32) { 74 | self.buffer.dimensions() 75 | } 76 | } 77 | 78 | /// Use to build a new image with extended boarders as 'frame' 79 | /// and a palette of `n` most frequently found colors in the original image. 80 | /// Contains `ImageBuffer` and y coordinate that divides picture and palette 81 | pub struct FramedPicture { 82 | buffer: ImageBuffer, Vec>, 83 | layer_divider: (u32, u32), 84 | palette: Palette, 85 | placement: Placement, 86 | } 87 | 88 | impl FramedPicture { 89 | /// Constructor that takes the dimensions of the original image 90 | /// and allocates additional space for frame. Then it fills the buffer 91 | /// with bright beige color and sets the divider y coordinate. 92 | /// 93 | /// Also, creates palette which will be adjacent to the original image 94 | /// 95 | /// # Arguments 96 | /// * width - of the image buffer 97 | /// * height - of the image buffer 98 | /// * n - # boxes is an optional. Default value is 10 99 | /// 100 | /// # Return 101 | /// * `FramedPicture` struct 102 | pub fn new( 103 | width: u32, height: u32, n: Option, placement: Placement) -> Self { 104 | let mut box_width = width; 105 | if placement == Placement::Left || placement == Placement::Right { 106 | box_width = height; 107 | } 108 | let dims = FramedPicture::compute_palette_size( 109 | box_width, n.unwrap_or(10)); 110 | 111 | let mut _w = 0; 112 | let mut _h = 0; 113 | if placement == Placement::Top || placement == Placement::Bottom { 114 | _w = width + 20; 115 | _h = height + 30 + dims.0; 116 | } else { 117 | _w = width + 30 + dims.0; 118 | _h = height + 20; 119 | } 120 | 121 | let tmp: ImageBuffer, Vec> = 122 | ImageBuffer::from_fn( 123 | _w, _h, |_, _| Rgba([255, 252, 234, 1])); 124 | let p = Palette::new( 125 | (dims.0, dims.0), 126 | n.unwrap_or(10), 127 | dims.1 128 | ); 129 | let divider = FramedPicture::_set_layers_divider( 130 | &placement, width, height); 131 | 132 | FramedPicture { 133 | buffer: tmp, 134 | layer_divider: divider, 135 | palette: p, 136 | placement, 137 | } 138 | } 139 | 140 | /// Functions computes and sets the divider point for buffer and palette layers 141 | /// 142 | /// # Arguments 143 | /// * placement - where palette is expected to be placed 144 | /// * height - of the original image 145 | /// * width - of the original image 146 | /// 147 | /// # Return 148 | /// Tuple with (x,y) coordinates 149 | fn _set_layers_divider( 150 | placement: &Placement, width: u32, height: u32) -> (u32, u32) { 151 | let mut y_divider = 10; 152 | let mut x_divider = 10; 153 | if placement == &Placement::Bottom { 154 | y_divider += height + 10; 155 | } else if placement == &Placement::Right { 156 | x_divider += width + 10; 157 | } 158 | (x_divider, y_divider) 159 | } 160 | 161 | /// Copies supplied dynamic image into this image buffer, 162 | /// while preserving the allocated space for frame borders 163 | /// 164 | /// # Arguments 165 | /// * size - of the frame 166 | /// * image - which will be copied into this buffer 167 | pub fn copy_img_into(&mut self, size: u32, image: &DynamicImage) { 168 | let mut starting_x = 0; 169 | let mut starting_y = 0; 170 | if self.placement == Placement::Left { 171 | starting_x = self.palette.get_dimensions().0 + size; 172 | } else if self.placement == Placement::Top { 173 | starting_y = self.palette.get_dimensions().1 + size; 174 | } 175 | 176 | for i in image.pixels() { 177 | let x = i.0 + size + starting_x; 178 | let y = i.1 + size + starting_y; 179 | let color = i.2; 180 | self.buffer.put_pixel(x, y, color); 181 | } 182 | } 183 | 184 | /// Calculate the size of each color box for palette layer. 185 | /// Each box is a square and includes the space for a pillar, 186 | /// that divides boxes 187 | /// 188 | /// # Arguments 189 | /// * length - of the whole palette 190 | /// * boxes - number of boxes in the palette 191 | /// 192 | /// # Returns 193 | /// Tuple with the size of square's sides and width of a pillar 194 | pub fn compute_palette_size(length: u32, boxes: u32) -> (u32, u32) { 195 | let size = length / (boxes + 1); 196 | let pillar = (size as f32 * 0.13) as u32; 197 | (size, pillar) 198 | } 199 | 200 | pub fn fill_in_palette(&mut self, top_colors: Vec<&ColorCount>) { 201 | self.palette.paint_palette(top_colors); 202 | if self.placement == Placement::Left 203 | || self.placement == Placement::Right { 204 | self.palette.rotate_90degrees(); 205 | } 206 | } 207 | 208 | /// Overlays the palette buffer on top of this buffer. 209 | /// This is done on an empty space preserved for palette boxes. 210 | /// 211 | /// # Arguments 212 | /// palette - image buffer that contains n boxes separated with pillars. 213 | pub fn combine_pieces(&mut self) { 214 | // let divider = 215 | // if self.placement == Placement::Top 216 | // || self.placement == Placement::Bottom { 217 | // self.layer_divider.1 218 | // } else { self.layer_divider.0 }; 219 | imageops::overlay( 220 | &mut self.buffer, 221 | self.palette.get_buffer(), 222 | self.layer_divider.0, 223 | self.layer_divider.1, 224 | ); 225 | } 226 | 227 | /// Get the image dimensions of this image buffer in a form of tuple 228 | /// 229 | /// # Returns 230 | /// Tuple of x,y dimensions of a given buffer 231 | pub fn get_dimensions(&self) -> (u32, u32) { 232 | self.buffer.dimensions() 233 | } 234 | } 235 | 236 | impl Filter for FramedPicture {} 237 | 238 | impl SaveImage for Palette { 239 | fn get_buffer(&self) -> &ImageBuffer, Vec> { 240 | &self.buffer 241 | } 242 | } 243 | 244 | impl SaveImage for FramedPicture { 245 | fn get_buffer(&self) -> &ImageBuffer, Vec> { 246 | &self.buffer 247 | } 248 | } 249 | --------------------------------------------------------------------------------