├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── interval_func.rs ├── lib.rs ├── main.rs └── sorting.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | 3 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 4 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 5 | Cargo.lock 6 | 7 | # These are backup files generated by rustfmt 8 | **/*.rs.bk 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pixelsort" 3 | version = "0.1.0" 4 | authors = ["Lukas Wirth "] 5 | description = "Bring your pixels back in order." 6 | license = "MIT/Apache-2.0" 7 | readme = "README.md" 8 | repository = "https://github.com/Veykril/pixelsort.git" 9 | edition = "2018" 10 | 11 | [features] 12 | default = ["rand", "imageproc"] 13 | 14 | [dependencies.rand] 15 | version = "0.7" 16 | optional = true 17 | 18 | [dependencies.imageproc] 19 | version = "0.20" 20 | optional = true 21 | 22 | [dependencies.image] 23 | version = "0.23" 24 | default-features = false 25 | features = ["jpeg", "png"] 26 | 27 | [dependencies.clap] 28 | version = "2.33" 29 | default-features = false 30 | features = ["suggestions", "color"] 31 | 32 | [dependencies.inversion-list] 33 | git = "https://github.com/Veykril/inversion-list" 34 | rev = "f04eb0550b10d40a3bff72daf70b06d539ab9cdb" -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Lukas Wirth 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Lukas Wirth 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pixelsort 2 | 3 | Pixel sorting for images implemented in pure rust. 4 | 5 | Inspired by [satyarth](https://github.com/satyarth/pixelsort)'s pixel sorting implementation in python. 6 | 7 | ## License 8 | 9 | Licensed under either of 10 | 11 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 12 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 13 | 14 | at your option. 15 | 16 | ### Contribution 17 | 18 | Unless you explicitly state otherwise, any contribution intentionally 19 | submitted for inclusion in the work by you, as defined in the Apache-2.0 20 | license, shall be dual licensed as above, without any additional terms or 21 | conditions. -------------------------------------------------------------------------------- /src/interval_func.rs: -------------------------------------------------------------------------------- 1 | use image::GenericImageView; 2 | 3 | use inversion_list::InversionList; 4 | 5 | pub fn mask(intervals: &mut [InversionList], mask: &image::GrayImage) { 6 | for (row, set) in mask.rows().zip(intervals) { 7 | let mut pixels = row.enumerate(); 8 | while let Some((last_white, _)) = pixels.find(|(_, pixel)| **pixel == image::Luma([255])) { 9 | set.split(last_white); 10 | let first_white = 11 | if let Some((pos, _)) = pixels.find(|(_, pixel)| **pixel == image::Luma([0])) { 12 | pos 13 | } else { 14 | if let Some((_, to_remove)) = set.split(last_white) { 15 | set.remove_range_at(to_remove); 16 | } 17 | break; 18 | }; 19 | if let Some((to_remove, _)) = set.split(first_white) { 20 | set.remove_range_at(to_remove); 21 | } 22 | } 23 | } 24 | } 25 | 26 | #[cfg(feature = "rand")] 27 | pub fn random(intervals: &mut [InversionList], lower: usize, upper: usize) { 28 | use rand::Rng; 29 | for set in intervals { 30 | let width = set.end().unwrap(); 31 | let mut acc = 0; 32 | while acc < width { 33 | acc += rand::thread_rng().gen_range(lower, upper); 34 | set.split(acc); 35 | } 36 | } 37 | } 38 | 39 | pub fn threshold(intervals: &mut [InversionList], image: &I, low: u8, high: u8) 40 | where 41 | P: image::Pixel, 42 | I: GenericImageView, 43 | { 44 | let mut gray = image::imageops::colorops::grayscale(image); 45 | for pixel in gray.pixels_mut() { 46 | if (low..high).contains(&pixel.0[0]) { 47 | *pixel = image::Luma([255]); 48 | } else { 49 | *pixel = image::Luma([0]); 50 | } 51 | } 52 | mask(intervals, &gray); 53 | } 54 | 55 | pub fn split_equal(intervals: &mut [InversionList], part_count: usize) { 56 | if let Some(width) = intervals.len().checked_div(part_count) { 57 | for set in intervals { 58 | for id in 0..part_count { 59 | set.split(id * width); 60 | } 61 | } 62 | } 63 | } 64 | 65 | #[cfg(feature = "imageproc")] 66 | pub fn edges_canny( 67 | intervals: &mut [InversionList], 68 | image: &I, 69 | low_thresh: f32, 70 | high_thresh: f32, 71 | ) where 72 | P: image::Pixel, 73 | I: GenericImageView, 74 | { 75 | let gray = image::imageops::colorops::grayscale(image); 76 | let edges = imageproc::edges::canny(&gray, low_thresh, high_thresh); 77 | mask(intervals, &edges); 78 | } 79 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use image::{GenericImage, GenericImageView, Pixel}; 2 | 3 | pub mod sorting; 4 | 5 | pub mod interval_func; 6 | use inversion_list::InversionList; 7 | 8 | pub fn sort_image(image: &mut I, intervals: Vec, sorting_function: SF) 9 | where 10 | I: GenericImage + GenericImageView, 11 | P: Pixel, 12 | SF: FnMut(&P) -> u32 + Clone, 13 | { 14 | // allocate buffer outside to prevent frequent reallocations 15 | let mut scratch = Vec::new(); 16 | for (row, set) in intervals 17 | .into_iter() 18 | .enumerate() 19 | .take(image.height() as usize) 20 | { 21 | for range in set.ranges() { 22 | let mut sub = image.sub_image( 23 | range.start as u32, 24 | row as u32, 25 | range.end as u32 - range.start as u32, 26 | 1, 27 | ); 28 | scratch.extend(sub.pixels().map(|(_, _, pixel)| pixel)); 29 | scratch.sort_by_key(sorting_function.clone()); 30 | for (x, pixel) in scratch.drain(..).enumerate() { 31 | // SAFETY: if we were to put a pixel outside of its bounds we would've panicked at the pixels() collection 32 | unsafe { sub.unsafe_put_pixel(x as u32, 0, pixel) }; 33 | //unsafe { sub.unsafe_put_pixel(x as u32, 0, Pixel::from_channels(255, 0, 0, 255)) }; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg}; 2 | use image::imageops; 3 | 4 | use std::path::{Path, PathBuf}; 5 | use std::str; 6 | 7 | use inversion_list::InversionList; 8 | use pixelsort::interval_func; 9 | use pixelsort::sorting; 10 | 11 | #[derive(Clone, Copy)] 12 | pub enum SortingMode { 13 | Lightness, 14 | Intensity, 15 | Minimum, 16 | Maximum, 17 | } 18 | 19 | impl SortingMode { 20 | pub fn function

(self) -> fn(&P) -> u32 21 | where 22 | P: image::Pixel, 23 | { 24 | match self { 25 | SortingMode::Lightness => sorting::lightness, 26 | SortingMode::Intensity => sorting::intensity, 27 | SortingMode::Minimum => sorting::chan_max, 28 | SortingMode::Maximum => sorting::chan_min, 29 | } 30 | } 31 | } 32 | 33 | impl str::FromStr for SortingMode { 34 | type Err = String; 35 | fn from_str(s: &str) -> Result { 36 | match s { 37 | "lightness" => Ok(SortingMode::Lightness), 38 | "intensity" => Ok(SortingMode::Intensity), 39 | "minimum" => Ok(SortingMode::Minimum), 40 | "maximum" => Ok(SortingMode::Maximum), 41 | _ => Err(String::from(s)), 42 | } 43 | } 44 | } 45 | 46 | #[derive(Clone, Copy)] 47 | pub enum Rotation { 48 | Zero, 49 | Quarter, 50 | Half, 51 | NegQuarter, 52 | } 53 | 54 | impl str::FromStr for Rotation { 55 | type Err = String; 56 | fn from_str(s: &str) -> Result { 57 | let num = s 58 | .parse::() 59 | .map_err(|e| format!("{:?}", e))? 60 | .rem_euclid(360); 61 | match num { 62 | 0 => Ok(Rotation::Zero), 63 | 90 => Ok(Rotation::Quarter), 64 | 180 => Ok(Rotation::Half), 65 | 270 => Ok(Rotation::NegQuarter), 66 | _ => Err(String::from("rotation angle must be a multiple of 90")), 67 | } 68 | } 69 | } 70 | 71 | #[derive(Clone, Copy)] 72 | pub enum IntervalFunction { 73 | Full, 74 | #[cfg(feature = "imageproc")] 75 | Edges, 76 | #[cfg(feature = "rand")] 77 | Random, 78 | Threshold, 79 | SplitEqual, 80 | } 81 | 82 | impl str::FromStr for IntervalFunction { 83 | type Err = String; 84 | fn from_str(s: &str) -> Result { 85 | match s { 86 | "full" => Ok(IntervalFunction::Full), 87 | #[cfg(feature = "imageproc")] 88 | "edge" => Ok(IntervalFunction::Edges), 89 | #[cfg(feature = "rand")] 90 | "random" => Ok(IntervalFunction::Random), 91 | "threshold" => Ok(IntervalFunction::Threshold), 92 | "split" => Ok(IntervalFunction::SplitEqual), 93 | _ => Err(String::from(s)), 94 | } 95 | } 96 | } 97 | 98 | // FIXME: clean this mess up 99 | fn main() { 100 | use std::str::FromStr; 101 | let matches = App::new("pixelsort") 102 | .version(clap::crate_version!()) 103 | .author(clap::crate_authors!()) 104 | .arg( 105 | Arg::with_name("input") 106 | .help("The input image to sort.") 107 | .required(true) 108 | .takes_value(true), 109 | ) 110 | .args(&[ 111 | arg_interval(), 112 | arg_output(), 113 | arg_mask(), 114 | arg_upper(), 115 | arg_lower(), 116 | arg_rotation(), 117 | arg_num(), 118 | arg_sorting(), 119 | ]) 120 | .get_matches(); 121 | let input = Path::new(matches.value_of_os("input").unwrap()); 122 | let mut image = image::open(input) 123 | .expect("failed to read input image") 124 | .to_rgba(); 125 | let output = matches 126 | .value_of_os("output") 127 | .map(PathBuf::from) 128 | .unwrap_or_else(|| { 129 | let extension = input 130 | .extension() 131 | .and_then(std::ffi::OsStr::to_str) 132 | .unwrap_or("png"); 133 | input.with_extension(["sorted", ".", extension].concat()) 134 | }); 135 | let rotate = Rotation::from_str(matches.value_of("rotation").unwrap()).unwrap(); 136 | 137 | //rotate 138 | match rotate { 139 | Rotation::Quarter => image = imageops::rotate90(&image), 140 | Rotation::Half => image = imageops::rotate180(&image), 141 | Rotation::NegQuarter => image = imageops::rotate270(&image), 142 | Rotation::Zero => (), 143 | } 144 | let sorting_func = SortingMode::from_str(matches.value_of("sorting").unwrap()) 145 | .unwrap() 146 | .function(); 147 | let interval_func = 148 | IntervalFunction::from_str(matches.value_of("interval_func").unwrap()).unwrap(); 149 | 150 | let mut intervals = intervals_from_image(&image); 151 | if let Some(mask_path) = matches.value_of_os("mask").map(Path::new) { 152 | let mut mask = image::open(mask_path).unwrap().to_luma(); 153 | match rotate { 154 | Rotation::Quarter => mask = imageops::rotate90(&mask), 155 | Rotation::Half => mask = imageops::rotate180(&mask), 156 | Rotation::NegQuarter => mask = imageops::rotate270(&mask), 157 | Rotation::Zero => (), 158 | } 159 | interval_func::mask(&mut intervals, &mask); 160 | } 161 | 162 | let upper = matches.value_of("upper").unwrap_or_default(); 163 | let lower = matches.value_of("lower").unwrap_or_default(); 164 | 165 | match interval_func { 166 | IntervalFunction::Full => (), 167 | IntervalFunction::SplitEqual => interval_func::split_equal( 168 | &mut intervals, 169 | matches 170 | .value_of("num") 171 | .unwrap() 172 | .parse() 173 | .expect("num was not an integer"), 174 | ), 175 | #[cfg(feature = "imageproc")] 176 | IntervalFunction::Edges => interval_func::edges_canny( 177 | &mut intervals, 178 | &image, 179 | lower.parse().expect("lower was not an float"), 180 | upper.parse().expect("upper was not an float"), 181 | ), 182 | #[cfg(feature = "rand")] 183 | IntervalFunction::Random => interval_func::random( 184 | &mut intervals, 185 | lower.parse().expect("lower was not an integer"), 186 | upper.parse().expect("upper was not an integer"), 187 | ), 188 | IntervalFunction::Threshold => interval_func::threshold( 189 | &mut intervals, 190 | &image, 191 | lower.parse().expect("lower was not a byte integer"), 192 | upper.parse().expect("upper was not a byte integer"), 193 | ), 194 | }; 195 | pixelsort::sort_image(&mut image, intervals, sorting_func); 196 | // rotate back 197 | match rotate { 198 | Rotation::Quarter => image = imageops::rotate270(&image), 199 | Rotation::Half => image = imageops::rotate180(&image), 200 | Rotation::NegQuarter => image = imageops::rotate90(&image), 201 | Rotation::Zero => (), 202 | } 203 | image.save(&output).unwrap(); 204 | } 205 | 206 | fn arg_sorting() -> Arg<'static, 'static> { 207 | Arg::with_name("sorting") 208 | .short("s") 209 | .long("sorting") 210 | .help("The function to use for sorting pixels.") 211 | .long_help( 212 | "The function to use for sorting pixels.\n\ 213 | \n\ 214 | This mode defines how pixels are sorted, be it by lightness, intensity or min/maxmimum channel value of each pixel.", 215 | ) 216 | .default_value("lightness") 217 | .takes_value(true) 218 | } 219 | 220 | fn arg_num() -> Arg<'static, 'static> { 221 | Arg::with_name("num") 222 | .short("n") 223 | .long("num") 224 | .help("The number of parts to split the intervals into.") 225 | .long_help( 226 | "The number of parts to split the intervals into.\n\ 227 | \n\ 228 | Required by interval function `split`, splits the file into even intervals.", 229 | ) 230 | .required_if("interval_func", "split") 231 | .takes_value(true) 232 | } 233 | 234 | fn arg_rotation() -> Arg<'static, 'static> { 235 | Arg::with_name("rotation") 236 | .short("r") 237 | .long("rotation") 238 | .help("The rotation to apply to the image prior sorting.") 239 | .long_help( 240 | "The rotation to apply to the image(and mask) prior sorting.\n\ 241 | \n\ 242 | This value defines the angle at which pixels will be sorted. This may be any multiple of 90 degrees.\n\ 243 | To sort vertically instead of horizontally for example one would specifiy a rotation of 90 or 270 degrees.", 244 | ) 245 | .default_value("0") 246 | .takes_value(true) 247 | } 248 | 249 | fn arg_upper() -> Arg<'static, 'static> { 250 | Arg::with_name("upper") 251 | .short("u") 252 | .long("upper") 253 | .help("The upper threshold used by some interval functions.") 254 | .long_help( 255 | "The upper threshold used by some interval functions.\n\ 256 | \n\ 257 | Required by `edge` in the range of [0.0;1140.39), accepts floating point numbers.\n\ 258 | Required by `random`, defines the maximum possible size of the random intervals in integers.\n\ 259 | Required by `threshold`, defines the upper threshold a pixels lightness has to fall below to be sorted.", 260 | ) 261 | .required_ifs(&[("interval_func", "edges"), ("interval_func", "threshold"), ("interval_func", "random")]) 262 | .takes_value(true) 263 | } 264 | 265 | fn arg_lower() -> Arg<'static, 'static> { 266 | Arg::with_name("lower") 267 | .short("l") 268 | .long("lower") 269 | .help("The lower threshold used by some interval functions.") 270 | .long_help( 271 | "The lower threshold used by some interval functions.\n\ 272 | \n\ 273 | Required by `edge` in the range of [0.0;1140.39), accepts floating point numbers.\n\ 274 | Required by `random`, defines the minimum possible size of the random intervals in integers.\n\ 275 | Required by `threshold`, defines the lower threshold a pixels lightness has to surpass to be sorted.", 276 | ) 277 | .required_ifs(&[("interval_func", "edges"), ("interval_func", "threshold"), ("interval_func", "random")]) 278 | .takes_value(true) 279 | } 280 | 281 | fn arg_mask() -> Arg<'static, 'static> { 282 | Arg::with_name("mask") 283 | .short("m") 284 | .long("mask") 285 | .help("A file path to a gray image to mask parts of the input image.") 286 | .long_help( 287 | "A file path to a gray image to mask parts of the input image.\n\ 288 | White pixels may be sorted, black pixels may not.", 289 | ) 290 | .takes_value(true) 291 | } 292 | 293 | fn arg_output() -> Arg<'static, 'static> { 294 | Arg::with_name("output") 295 | .short("o") 296 | .long("output") 297 | .help("A file path to save the output image to.") 298 | .takes_value(true) 299 | } 300 | 301 | fn arg_interval() -> Arg<'static, 'static> { 302 | Arg::with_name("interval_func") 303 | .short("i") 304 | .long("interval") 305 | .help("Interval function used to seperate the image into intervals.") 306 | .possible_values(&["full", "edge", "random", "split", "threshold"]) 307 | .default_value("full") 308 | .takes_value(true) 309 | } 310 | 311 | pub fn intervals_from_image(image: &I) -> Vec { 312 | (0..image.height()) 313 | .map(|_| InversionList::from(0..image.width() as usize)) 314 | .collect() 315 | } 316 | -------------------------------------------------------------------------------- /src/sorting.rs: -------------------------------------------------------------------------------- 1 | use image::Pixel; 2 | 3 | #[inline] 4 | pub fn lightness

(pixel: &P) -> u32 5 | where 6 | P: Pixel, 7 | { 8 | pixel.to_luma()[0] as u32 9 | } 10 | 11 | #[inline] 12 | pub fn intensity

(pixel: &P) -> u32 13 | where 14 | P: Pixel, 15 | { 16 | pixel.channels().iter().map(|c| *c as u32).sum() 17 | } 18 | 19 | #[inline] 20 | pub fn chan_min

(pixel: &P) -> u32 21 | where 22 | P: Pixel, 23 | { 24 | pixel.channels().iter().copied().min().unwrap_or(0) as u32 25 | } 26 | 27 | #[inline] 28 | pub fn chan_max

(pixel: &P) -> u32 29 | where 30 | P: Pixel, 31 | { 32 | pixel.channels().iter().copied().max().unwrap_or(255) as u32 33 | } 34 | --------------------------------------------------------------------------------