├── .gitignore ├── data ├── italy.png └── logo.png ├── images ├── italy.png ├── function-fill.png └── logo-medial-axis.png ├── Cargo.toml ├── README.md ├── LICENSE-MIT ├── .github └── workflows │ └── rust.yml ├── examples ├── squares.rs ├── heightmap.rs ├── function.rs └── medial_axis.rs ├── src ├── simplify.rs ├── svg.rs └── lib.rs ├── LICENSE-APACHE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /data/italy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danieledapo/marching_squares/HEAD/data/italy.png -------------------------------------------------------------------------------- /data/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danieledapo/marching_squares/HEAD/data/logo.png -------------------------------------------------------------------------------- /images/italy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danieledapo/marching_squares/HEAD/images/italy.png -------------------------------------------------------------------------------- /images/function-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danieledapo/marching_squares/HEAD/images/function-fill.png -------------------------------------------------------------------------------- /images/logo-medial-axis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danieledapo/marching_squares/HEAD/images/logo-medial-axis.png -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "marching_squares" 3 | version = "0.1.0" 4 | authors = ["Daniele D'Orazio "] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | 8 | [dependencies] 9 | 10 | [dev-dependencies] 11 | image = "0.25" 12 | marching_squares = { path = ".", features = ["svg"] } 13 | 14 | [features] 15 | default = [] 16 | svg = [] 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # marching-squares 2 | 3 | Implementation of the [marching 4 | squares](https://en.wikipedia.org/wiki/Marching_squares) algorithm to find the 5 | boundaries of shapes given a scalar field. This algorithm can also be used to 6 | generate heightmaps or to find the medial axis of a shape. 7 | 8 | To understand what the library can do take a look at the examples. 9 | 10 | ```bash 11 | $ cargo run --release --example function 12 | $ cargo run --release --example heightmap data/italy.png 13 | $ cargo run --release --example medial_axis data/logo.png 20 14 | ``` 15 | 16 | ![function-fill.png](images/function-fill.png) 17 | ![italy.png](images/italy.png) 18 | ![medial-axis.png](images/logo-medial-axis.png) 19 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Daniele D'Orazio 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 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | test: 7 | name: Test Suite 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | profile: minimal 14 | toolchain: stable 15 | override: true 16 | - uses: actions-rs/cargo@v1 17 | with: 18 | command: test 19 | 20 | fmt: 21 | name: Rustfmt 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: stable 29 | override: true 30 | - run: rustup component add rustfmt 31 | - uses: actions-rs/cargo@v1 32 | with: 33 | command: fmt 34 | args: --all -- --check 35 | 36 | clippy: 37 | name: Clippy 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v2 41 | - uses: actions-rs/toolchain@v1 42 | with: 43 | profile: minimal 44 | toolchain: stable 45 | override: true 46 | - run: rustup component add clippy 47 | - uses: actions-rs/cargo@v1 48 | with: 49 | command: clippy 50 | args: -- -D warnings 51 | -------------------------------------------------------------------------------- /examples/squares.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | 4 | use marching_squares::simplify::simplify; 5 | use marching_squares::svg; 6 | use marching_squares::{march, Field}; 7 | 8 | #[derive(Debug, Clone)] 9 | struct Example; 10 | 11 | impl Field for Example { 12 | fn dimensions(&self) -> (usize, usize) { 13 | (200, 200) 14 | } 15 | 16 | fn z_at(&self, x: usize, y: usize) -> f64 { 17 | // square 18 | let square = 10..=50; 19 | if square.contains(&x) && square.contains(&y) { 20 | let subsquare = 20..=40; 21 | if subsquare.contains(&x) && subsquare.contains(&y) { 22 | return 0.0; 23 | } 24 | 25 | return 1.0; 26 | } 27 | 28 | // rotated square 29 | let c = 100; 30 | let dx = x.max(c) - x.min(c); 31 | let dy = y.max(c) - y.min(c); 32 | if dx + dy < 50 { 33 | if dx + dy < 49 { 34 | return 0.0; 35 | } 36 | 37 | return 1.0; 38 | } 39 | 40 | 0.0 41 | } 42 | } 43 | 44 | fn main() { 45 | let contours = march(&Example {}, 0.5); 46 | 47 | let mut wireframe = svg::Document::new((0.0, 0.0, 200.0, 200.0)); 48 | let mut fill = wireframe.clone(); 49 | 50 | let mut paths = vec![]; 51 | 52 | for c in contours { 53 | let p = simplify(&c); 54 | 55 | wireframe = wireframe.push( 56 | svg::Element::polyline(p.iter().cloned()) 57 | .fill("none") 58 | .set("stroke", "black") 59 | .set("stroke-width", "0.05"), 60 | ); 61 | paths.push(p); 62 | } 63 | 64 | fill = fill.push(svg::Element::path(paths).set("stroke-width", "0.05")); 65 | 66 | for (f, d) in &[("squares-wf.svg", wireframe), ("squares-fill.svg", fill)] { 67 | let mut f = File::create(f).unwrap(); 68 | write!(f, "{}", d).unwrap(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/heightmap.rs: -------------------------------------------------------------------------------- 1 | // https://tangrams.github.io/heightmapper/ is a great tool to generate heightmaps! 2 | 3 | use std::env; 4 | use std::io::Write; 5 | use std::path::Path; 6 | 7 | use marching_squares::simplify::simplify; 8 | use marching_squares::svg; 9 | use marching_squares::{march, Field}; 10 | 11 | #[derive(Debug)] 12 | struct HeightMap { 13 | img: image::GrayImage, 14 | } 15 | 16 | impl HeightMap { 17 | fn open(p: impl AsRef) -> image::ImageResult { 18 | let img = image::open(p)?.to_luma8(); 19 | Ok(HeightMap { img }) 20 | } 21 | } 22 | 23 | impl Field for HeightMap { 24 | fn dimensions(&self) -> (usize, usize) { 25 | let (w, h) = self.img.dimensions(); 26 | (w as usize, h as usize) 27 | } 28 | 29 | fn z_at(&self, x: usize, y: usize) -> f64 { 30 | f64::from(self.img.get_pixel(x as u32, y as u32).0[0]) 31 | } 32 | } 33 | 34 | fn main() { 35 | let path = &env::args() 36 | .nth(1) 37 | .expect("please provide an input grayscale height map and optionally the number of levels"); 38 | let path = Path::new(path); 39 | 40 | let nlevels = env::args() 41 | .nth(2) 42 | .map(|s| s.parse::().expect("nlevels not a number")) 43 | .unwrap_or(50); 44 | 45 | let heightmap = HeightMap::open(path).expect("cannot load height map"); 46 | 47 | let (w, h) = heightmap.dimensions(); 48 | let mut doc = svg::Document::new((0.0, 0.0, w as f64, h as f64)); 49 | 50 | for i in 0..nlevels { 51 | let t = f64::from(i) / f64::from(nlevels - 1); 52 | let z = t * 255.0; 53 | let contours = march(&heightmap.framed(z), z) 54 | .into_iter() 55 | .map(|c| simplify(&c)); 56 | 57 | // doc = contours.fold(doc, |d, c| { 58 | // d.push( 59 | // svg::Element::polyline(c) 60 | // .fill("none") 61 | // .set("stroke", "black") 62 | // .set("stroke-width", "0.5"), 63 | // ) 64 | // }); 65 | 66 | doc = doc.push( 67 | svg::Element::path(contours) 68 | .fill("none") 69 | .set("stroke", "black") 70 | .set("stroke-width", "0.5"), 71 | ); 72 | } 73 | 74 | let mut out = std::fs::File::create(Path::new(path.file_stem().unwrap()).with_extension("svg")) 75 | .expect("cannot create output file"); 76 | 77 | write!(out, "{}", doc).expect("cannot save output"); 78 | } 79 | -------------------------------------------------------------------------------- /examples/function.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | 4 | use marching_squares::simplify::simplify; 5 | use marching_squares::svg; 6 | use marching_squares::{march, Field}; 7 | 8 | #[derive(Debug, Clone)] 9 | struct Fun { 10 | field: Vec, 11 | zrange: (f64, f64), 12 | } 13 | 14 | impl Field for Fun { 15 | fn dimensions(&self) -> (usize, usize) { 16 | (1600, 1600) 17 | } 18 | 19 | fn z_at(&self, x: usize, y: usize) -> f64 { 20 | let (_w, h) = self.dimensions(); 21 | self.field[y * h + x] 22 | } 23 | } 24 | 25 | impl Fun { 26 | fn new() -> Self { 27 | let mut fun = Fun { 28 | field: Vec::new(), 29 | zrange: (std::f64::INFINITY, std::f64::NEG_INFINITY), 30 | }; 31 | 32 | let (w, h) = fun.dimensions(); 33 | fun.field.reserve_exact(w * h); 34 | 35 | for y in 0..h { 36 | for x in 0..w { 37 | let z = fun.formula(x, y); 38 | 39 | fun.zrange = (fun.zrange.0.min(z), fun.zrange.1.max(z)); 40 | fun.field.push(z); 41 | } 42 | } 43 | 44 | fun 45 | } 46 | 47 | fn formula(&self, x: usize, y: usize) -> f64 { 48 | let scale = 150.0; 49 | 50 | let (w, h) = self.dimensions(); 51 | let x = (x as f64 - w as f64 / 2.0) / scale; 52 | let y = (y as f64 - h as f64 / 2.0) / scale; 53 | 54 | (1.3 * x).sin() * (0.9 * y).cos() + (0.8 * x).cos() * (1.9 * y).sin() + (y * 0.2 * x).cos() 55 | } 56 | } 57 | 58 | fn main() { 59 | let fun = Fun::new(); 60 | let (zmin, zmax) = fun.zrange; 61 | 62 | let c1 = (0xD3, 0x7B, 0x47); 63 | let c2 = (0x2E, 0x89, 0x72); 64 | let n = 48; 65 | 66 | let mut nofill_doc = svg::Document::new((0.0, 0.0, 1600.0, 1600.0)); 67 | let mut fill_doc = nofill_doc.clone(); 68 | 69 | for i in (0..n).into_iter().rev() { 70 | let t = f64::from(i) / f64::from(n - 1); 71 | let z = zmin + (zmax - zmin) * t; 72 | 73 | let contours = march(&fun.framed(z), z); 74 | 75 | let path = svg::Element::path(contours.into_iter().map(|c| simplify(&c))) 76 | .set("stroke", "black") 77 | .set("stroke-width", "2"); 78 | 79 | nofill_doc = nofill_doc.push(path.clone().fill("none")); 80 | fill_doc = fill_doc.push(path.fill(lerp_colors(c1, c2, t))); 81 | } 82 | 83 | for (f, d) in &[ 84 | ("function-no-fill.svg", &nofill_doc), 85 | ("function-fill.svg", &fill_doc), 86 | ] { 87 | let mut f = File::create(f).unwrap(); 88 | write!(f, "{}", d).unwrap(); 89 | } 90 | } 91 | 92 | fn lerp_colors(c1: (u8, u8, u8), c2: (u8, u8, u8), t: f64) -> String { 93 | let r = (f64::from(c1.0) + (f64::from(c2.0) - f64::from(c1.0)) * t) as u8; 94 | let g = (f64::from(c1.1) + (f64::from(c2.1) - f64::from(c1.1)) * t) as u8; 95 | let b = (f64::from(c1.2) + (f64::from(c2.2) - f64::from(c1.2)) * t) as u8; 96 | 97 | format!("#{:02x}{:02x}{:02x}", r, g, b) 98 | } 99 | -------------------------------------------------------------------------------- /src/simplify.rs: -------------------------------------------------------------------------------- 1 | /// Simplify a given polyline by reducing the amount of points that do not actually contribute a 2 | /// lot of details to the overall shape. 3 | pub fn simplify(poly: &[(f64, f64)]) -> Vec<(f64, f64)> { 4 | simplify_with_eps(poly, 1e-9) 5 | } 6 | 7 | pub fn simplify_with_eps(poly: &[(f64, f64)], eps: f64) -> Vec<(f64, f64)> { 8 | let mut r = vec![]; 9 | _simplify_with_eps(&mut r, poly, eps); 10 | r 11 | } 12 | 13 | pub fn _simplify_with_eps(r: &mut Vec<(f64, f64)>, poly: &[(f64, f64)], eps: f64) { 14 | // Ramer Douglas Peucker doesn't work with closed paths, thus simplify the open path and then 15 | // close it manually 16 | if !poly.is_empty() && poly[0] == poly[poly.len() - 1] { 17 | rdp(r, &poly[..poly.len() - 1], eps); 18 | r.push(poly[poly.len() - 1]); 19 | } else { 20 | rdp(r, poly, eps); 21 | } 22 | } 23 | 24 | /// Implementation of the [Ramer–Douglas–Peucker algorithm] to simplify an open path. 25 | /// 26 | /// [0]: https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm 27 | fn rdp(r: &mut Vec<(f64, f64)>, poly: &[(f64, f64)], eps: f64) { 28 | if poly.len() < 3 { 29 | r.extend_from_slice(poly); 30 | return; 31 | } 32 | 33 | let sp = poly[0]; 34 | let ep = *poly.last().unwrap(); 35 | 36 | let mut farthest_i = 0; 37 | let mut max_dist = f64::NEG_INFINITY; 38 | for (i, p) in poly.iter().enumerate().take(poly.len() - 1).skip(1) { 39 | let d = perpendicular_dist(*p, (sp, ep)); 40 | if d > max_dist { 41 | max_dist = d; 42 | farthest_i = i; 43 | } 44 | } 45 | 46 | if max_dist > eps { 47 | rdp(r, &poly[..=farthest_i], eps); 48 | 49 | // remove point with max dist, it will be added with the right vec 50 | r.pop(); 51 | 52 | _simplify_with_eps(r, &poly[farthest_i..], eps); 53 | } else { 54 | r.push(sp); 55 | r.push(ep); 56 | } 57 | } 58 | 59 | fn perpendicular_dist(p: (f64, f64), (s, e): ((f64, f64), (f64, f64))) -> f64 { 60 | let num = ((e.1 - s.1) * p.0 - (e.0 - s.0) * p.1 + e.0 * s.1 - e.1 * s.0).abs(); 61 | let den = ((e.0 - s.0).powi(2) + (e.1 - s.1).powi(2)).sqrt(); 62 | 63 | num / den 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn test_simplify_small_paths() { 72 | assert_eq!(simplify(&[]), vec![]); 73 | assert_eq!(simplify(&[(1.0, 1.0)]), vec![(1.0, 1.0)]); 74 | assert_eq!( 75 | simplify(&[(1.0, 1.0), (2.0, 2.0)]), 76 | vec![(1.0, 1.0), (2.0, 2.0)] 77 | ); 78 | } 79 | 80 | #[test] 81 | fn test_simplify_open_paths() { 82 | assert_eq!( 83 | simplify(&[(1.0, 1.0), (2.0, 2.0), (3.0, 3.0)]), 84 | vec![(1.0, 1.0), (3.0, 3.0)] 85 | ); 86 | 87 | assert_eq!( 88 | simplify(&[ 89 | (0.0, 0.0), 90 | (1e-20, 0.0), 91 | (1e-19, 0.0), 92 | (1.0, 1.0), 93 | (1.0, 1.0 + 1e-20), 94 | (2.0, 2.0), 95 | ]), 96 | vec![(0.0, 0.0), (2.0, 2.0)] 97 | ); 98 | } 99 | 100 | #[test] 101 | fn test_simplify_closed_paths() { 102 | assert_eq!( 103 | simplify(&[(1.0, 1.0), (2.0, 2.0), (3.0, 3.0), (1.0, 1.0)]), 104 | vec![(1.0, 1.0), (3.0, 3.0), (1.0, 1.0)] 105 | ); 106 | 107 | assert_eq!( 108 | simplify(&[ 109 | (0.0, 0.0), 110 | (1e-20, 0.0), 111 | (1e-19, 0.0), 112 | (1.0, 1.0), 113 | (1.0, 1.0 + 1e-20), 114 | (2.0, 2.0), 115 | (0.0, 0.0), 116 | ]), 117 | vec![(0.0, 0.0), (2.0, 2.0), (0.0, 0.0)] 118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /examples/medial_axis.rs: -------------------------------------------------------------------------------- 1 | // http://fourier.eng.hmc.edu/e161/lectures/morphology/node3.html 2 | 3 | use std::{env, io::Write, path::Path}; 4 | 5 | use image::{GrayImage, Luma}; 6 | 7 | use marching_squares::simplify::simplify; 8 | use marching_squares::svg; 9 | use marching_squares::{march, Field}; 10 | 11 | struct Skeleton { 12 | pub skeleton: GrayImage, 13 | } 14 | 15 | impl Skeleton { 16 | // pixels > threshold are inside the skeleton, <= outside 17 | fn new(img: GrayImage, threshold: u8) -> Self { 18 | let (mut dt, maxd) = Self::distance_transform(img, threshold); 19 | 20 | let skeleton_pts = Self::find_skeleton(&dt); 21 | 22 | if maxd > 0 { 23 | for p in dt.pixels_mut() { 24 | p[0] = (f64::from(p[0]) / f64::from(maxd) * 255.0) as u8; 25 | } 26 | } 27 | 28 | let mut skeleton = GrayImage::new(dt.width(), dt.height()); 29 | for &(x, y) in &skeleton_pts { 30 | skeleton.put_pixel(x, y, Luma([255])); 31 | } 32 | 33 | Self { skeleton } 34 | } 35 | 36 | fn distance_transform(mut img: GrayImage, threshold: u8) -> (GrayImage, u8) { 37 | for p in img.pixels_mut() { 38 | p[0] = u8::from(p.0[0] > threshold); 39 | } 40 | 41 | let mut maxd = 0; 42 | for k in 1..255 { 43 | let mut changed = false; 44 | 45 | for y in 1..img.height().saturating_sub(1) { 46 | for x in 1..img.width().saturating_sub(1) { 47 | if img.get_pixel(x, y).0[0] != k { 48 | continue; 49 | } 50 | 51 | let kk = [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)] 52 | .iter() 53 | .map(|&(x, y)| img.get_pixel(x, y).0[0]) 54 | .min() 55 | .unwrap(); 56 | 57 | img.get_pixel_mut(x, y).0[0] = kk + 1; 58 | changed = true; 59 | } 60 | } 61 | 62 | if !changed { 63 | maxd = k - 1; 64 | break; 65 | } 66 | } 67 | 68 | (img, maxd) 69 | } 70 | 71 | fn find_skeleton(dt: &GrayImage) -> Vec<(u32, u32)> { 72 | let mut points = vec![]; 73 | 74 | for y in 1..dt.height().saturating_sub(1) { 75 | for x in 1..dt.width().saturating_sub(1) { 76 | let k = dt.get_pixel(x, y).0[0]; 77 | 78 | if k == 0 { 79 | continue; 80 | } 81 | 82 | let kk = [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)] 83 | .iter() 84 | .map(|&(x, y)| dt.get_pixel(x, y).0[0]) 85 | .max() 86 | .unwrap(); 87 | 88 | if k == kk { 89 | points.push((x, y)); 90 | } 91 | } 92 | } 93 | 94 | points 95 | } 96 | } 97 | 98 | impl Field for Skeleton { 99 | fn dimensions(&self) -> (usize, usize) { 100 | let (w, h) = self.skeleton.dimensions(); 101 | (w as usize, h as usize) 102 | } 103 | 104 | fn z_at(&self, x: usize, y: usize) -> f64 { 105 | f64::from(self.skeleton.get_pixel(x as u32, y as u32).0[0]) 106 | } 107 | } 108 | 109 | fn main() { 110 | let path = &env::args() 111 | .nth(1) 112 | .expect("please provide an input image to find the medial axis of"); 113 | let path = Path::new(path); 114 | let threshold: u8 = env::args().nth(2).map_or(127, |t| t.parse().unwrap()); 115 | 116 | let img = image::open(path) 117 | .expect("cannot load input image") 118 | .to_luma8(); 119 | let skeleton = Skeleton::new(img, threshold); 120 | 121 | // skeleton.dt.save("dt.png").unwrap(); 122 | 123 | let contours = march(&skeleton, 0.5).into_iter().map(|c| simplify(&c)); 124 | 125 | let (w, h) = skeleton.dimensions(); 126 | let doc = svg::Document::new((0.0, 0.0, w as f64, h as f64)).push(svg::Element::path(contours)); 127 | 128 | let mut out = std::fs::File::create(Path::new(path.file_stem().unwrap()).with_extension("svg")) 129 | .expect("cannot create output file"); 130 | 131 | write!(out, "{}", doc).expect("cannot save output"); 132 | } 133 | -------------------------------------------------------------------------------- /src/svg.rs: -------------------------------------------------------------------------------- 1 | //! dead-simple module to write svg. It's not efficient nor pretty, but it gets the job done. 2 | 3 | use std::fmt::{Display, Formatter}; 4 | 5 | use std::collections::BTreeMap; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct Document { 9 | children: Vec, 10 | viewbox: (f64, f64, f64, f64), 11 | } 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct Element { 15 | tag: &'static str, 16 | attributes: BTreeMap<&'static str, String>, 17 | } 18 | 19 | impl Document { 20 | pub fn new(viewbox: (f64, f64, f64, f64)) -> Self { 21 | Document { 22 | viewbox, 23 | children: vec![], 24 | } 25 | } 26 | 27 | pub fn push(mut self, element: Element) -> Self { 28 | self.children.push(element); 29 | self 30 | } 31 | } 32 | 33 | impl Element { 34 | pub fn path(paths: impl IntoIterator>) -> Self { 35 | let el = Element::new("path"); 36 | 37 | let mut d = String::new(); 38 | for (i, path) in paths.into_iter().filter(|c| !c.is_empty()).enumerate() { 39 | if i > 0 { 40 | d += " "; 41 | } 42 | 43 | d += &format!("M {},{} ", path[0].0, path[0].1); 44 | for pt in path.into_iter().skip(1) { 45 | d += &format!("L {},{} ", pt.0, pt.1); 46 | } 47 | d += "Z"; 48 | } 49 | 50 | el.set("d", d) 51 | } 52 | 53 | pub fn polyline(v: impl IntoIterator) -> Self { 54 | let el = Element::new("polyline"); 55 | 56 | el.set( 57 | "points", 58 | v.into_iter() 59 | .map(|(x, y)| format!("{},{}", x, y)) 60 | .collect::>() 61 | .join(" "), 62 | ) 63 | } 64 | 65 | pub fn rect((ox, oy): (f64, f64), (width, height): (f64, f64)) -> Self { 66 | let el = Element::new("rect"); 67 | 68 | el.set("x", ox.to_string()) 69 | .set("y", oy.to_string()) 70 | .set("width", width.to_string()) 71 | .set("height", height.to_string()) 72 | } 73 | 74 | pub fn fill(self, color: impl Into) -> Self { 75 | self.set("fill", color) 76 | } 77 | 78 | pub fn set(mut self, attr: &'static str, value: impl Into) -> Self { 79 | self.attributes.insert(attr, value.into()); 80 | self 81 | } 82 | 83 | fn new(tag: &'static str) -> Self { 84 | Element { 85 | tag, 86 | attributes: BTreeMap::new(), 87 | } 88 | } 89 | } 90 | 91 | impl Display for Document { 92 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { 93 | writeln!( 94 | f, 95 | r#" 96 | 97 | "#, 98 | self.viewbox.0, self.viewbox.1, self.viewbox.2, self.viewbox.3 99 | )?; 100 | 101 | for e in &self.children { 102 | writeln!(f, "{}", e)?; 103 | } 104 | 105 | write!(f, "")?; 106 | 107 | Ok(()) 108 | } 109 | } 110 | 111 | impl Display for Element { 112 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { 113 | write!(f, "<{} ", self.tag)?; 114 | 115 | for (a, v) in &self.attributes { 116 | write!(f, r#"{}="{}" "#, a, v)?; 117 | } 118 | 119 | write!(f, "/>")?; 120 | 121 | Ok(()) 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use super::*; 128 | 129 | #[test] 130 | fn test_basic() { 131 | let doc = Document::new((0.0, 0.0, 200.0, 200.0)) 132 | .push(Element::rect((0.0, 0.0), (200.0, 200.0)).fill("red")) 133 | .push(Element::polyline(vec![ 134 | (10.0, 20.0), 135 | (50.0, 20.0), 136 | (50.0, 50.0), 137 | (10.0, 50.0), 138 | ])) 139 | .push( 140 | Element::polyline(vec![ 141 | (160.0, 20.0), 142 | (180.0, 60.0), 143 | (140.0, 30.0), 144 | (160.0, 20.0), 145 | ]) 146 | .fill("none") 147 | .set("stroke", "black"), 148 | ) 149 | .push(Element::path(vec![ 150 | vec![(0.0, 10.0), (20.0, 30.0), (20.0, 50.0), (0.0, 50.0)], 151 | vec![(20.0, 20.0), (40.0, 40.0), (20.0, 0.0)], 152 | ])) 153 | .push(Element::path(vec![vec![], vec![(0.0, 10.0)]])); 154 | 155 | assert_eq!( 156 | doc.to_string(), 157 | r#" 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | "# 166 | ); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | pub mod simplify; 4 | 5 | #[cfg(feature = "svg")] 6 | pub mod svg; 7 | 8 | /// A scalar field. 9 | pub trait Field { 10 | /// Get the width and height of the scalar field. 11 | fn dimensions(&self) -> (usize, usize); 12 | 13 | /// Calculate the z value at the given position. The position is always inside the range of 14 | /// `dimensions`. 15 | fn z_at(&self, x: usize, y: usize) -> f64; 16 | 17 | /// Helper to force a Field to have all the Z values at the boundaries of the field to be set 18 | /// to `border_z`. Useful to ensure each path is closed. 19 | fn framed(&self, border_z: f64) -> Framed 20 | where 21 | Self: Sized, 22 | { 23 | Framed { 24 | field: self, 25 | border_z, 26 | } 27 | } 28 | } 29 | 30 | /// Contours of a shape. 31 | pub type Contours = Vec>; 32 | 33 | /// A `SegmentsMap` is used to speedup contour building on the average case. It's simply a map from 34 | /// the start position of the segment rounded with integers coordinates to the list of all the 35 | /// segments that start in that position. Usually, shapes have very few segments that start at the 36 | /// same integer position thus this simple optimization allows to find the next segment in O(1) 37 | /// which is great. 38 | /// 39 | /// Note that a valid `SegmentsMap` must not have entries for an empty list of segments. 40 | type SegmentsMap = HashMap<(u64, u64), Vec<((f64, f64), (f64, f64))>>; 41 | 42 | /// Find the contours of a given scalar field using `z` as the threshold value. 43 | pub fn march(field: &impl Field, z: f64) -> Contours { 44 | let (width, height) = field.dimensions(); 45 | 46 | let mut segments: SegmentsMap = HashMap::new(); 47 | let mut add_seg = |s: (f64, f64), e| { 48 | segments 49 | .entry((s.0 as u64, s.1 as u64)) 50 | .or_default() 51 | .push((s, e)); 52 | }; 53 | 54 | // avoid calling z_at multiple times for the same cell by storing the z values for the current 55 | // row and by storing the values for the next row as soon as they're calculated. 56 | let mut current_row_zs = (0..width).map(|x| field.z_at(x, 0)).collect::>(); 57 | let mut next_row_zs = Vec::with_capacity(width); 58 | 59 | for y in 0..height.saturating_sub(1) { 60 | next_row_zs.clear(); 61 | next_row_zs.push(field.z_at(0, y + 1)); 62 | 63 | for x in 0..width.saturating_sub(1) { 64 | let ulz = current_row_zs[x]; 65 | let urz = current_row_zs[x + 1]; 66 | let blz = next_row_zs[x]; 67 | let brz = field.z_at(x + 1, y + 1); 68 | 69 | next_row_zs.push(brz); 70 | 71 | let mut case = 0; 72 | if blz > z { 73 | case |= 1; 74 | } 75 | if brz > z { 76 | case |= 2; 77 | } 78 | if urz > z { 79 | case |= 4; 80 | } 81 | if ulz > z { 82 | case |= 8; 83 | } 84 | let x = x as f64; 85 | let y = y as f64; 86 | 87 | let top = (x + fraction(z, (ulz, urz)), y); 88 | let bottom = (x + fraction(z, (blz, brz)), y + 1.0); 89 | let left = (x, y + fraction(z, (ulz, blz))); 90 | let right = (x + 1.0, y + fraction(z, (urz, brz))); 91 | 92 | match case { 93 | 0 | 15 => {} 94 | 1 => { 95 | add_seg(bottom, left); 96 | } 97 | 2 => { 98 | add_seg(right, bottom); 99 | } 100 | 3 => { 101 | add_seg(right, left); 102 | } 103 | 4 => { 104 | add_seg(top, right); 105 | } 106 | 5 => { 107 | add_seg(top, left); 108 | add_seg(bottom, right); 109 | } 110 | 6 => { 111 | add_seg(top, bottom); 112 | } 113 | 7 => { 114 | add_seg(top, left); 115 | } 116 | 8 => { 117 | add_seg(left, top); 118 | } 119 | 9 => { 120 | add_seg(bottom, top); 121 | } 122 | 10 => { 123 | add_seg(left, bottom); 124 | add_seg(right, top); 125 | } 126 | 11 => { 127 | add_seg(right, top); 128 | } 129 | 12 => { 130 | add_seg(left, right); 131 | } 132 | 13 => { 133 | add_seg(bottom, right); 134 | } 135 | 14 => { 136 | add_seg(left, bottom); 137 | } 138 | _ => unreachable!(), 139 | } 140 | } 141 | 142 | std::mem::swap(&mut current_row_zs, &mut next_row_zs); 143 | } 144 | 145 | build_contours(segments, (width as u64, height as u64)) 146 | } 147 | 148 | fn build_contours(mut segments: SegmentsMap, (w, h): (u64, u64)) -> Contours { 149 | use std::collections::hash_map::Entry; 150 | 151 | let mut contours = vec![]; 152 | 153 | let mut boundaries = segments 154 | .keys() 155 | .cloned() 156 | .filter(|s| s.0 == 0 || s.0 == w - 1 || s.1 == 0 || s.1 == h - 1) 157 | .collect::>(); 158 | 159 | while !segments.is_empty() { 160 | // prefer to start on a boundary, but if no point lie on a bounday just 161 | // pick a random one. This allows to connect open paths entirely without 162 | // breaking them in multiple chunks. 163 | let first_k = boundaries 164 | .iter() 165 | .next() 166 | .map_or_else(|| *segments.keys().next().unwrap(), |k| *k); 167 | 168 | let mut first_e = match segments.entry(first_k) { 169 | Entry::Occupied(o) => o, 170 | Entry::Vacant(_) => unreachable!(), 171 | }; 172 | 173 | let first = first_e.get_mut().pop().unwrap(); 174 | if first_e.get().is_empty() { 175 | first_e.remove_entry(); 176 | boundaries.remove(&first_k); 177 | } 178 | 179 | let mut contour = vec![first.0, first.1]; 180 | 181 | loop { 182 | let prev = contour[contour.len() - 1]; 183 | 184 | let segments_k = (prev.0 as u64, prev.1 as u64); 185 | let mut segments = match segments.entry(segments_k) { 186 | Entry::Vacant(_) => break, 187 | Entry::Occupied(o) => o, 188 | }; 189 | 190 | let next = segments 191 | .get() 192 | .iter() 193 | .enumerate() 194 | .find(|(_, (s, _))| s == &prev); 195 | 196 | match next { 197 | None => break, 198 | Some((i, seg)) => { 199 | contour.push(seg.1); 200 | 201 | segments.get_mut().swap_remove(i); 202 | if segments.get().is_empty() { 203 | segments.remove_entry(); 204 | boundaries.remove(&segments_k); 205 | } 206 | } 207 | } 208 | } 209 | 210 | contours.push(contour); 211 | } 212 | 213 | contours 214 | } 215 | 216 | fn fraction(z: f64, (z0, z1): (f64, f64)) -> f64 { 217 | if z0 == z1 { 218 | return 0.5; 219 | } 220 | 221 | let t = (z - z0) / (z1 - z0); 222 | t.clamp(0.0, 1.0) 223 | } 224 | 225 | #[derive(Debug, Clone)] 226 | pub struct Framed<'s, F> { 227 | field: &'s F, 228 | border_z: f64, 229 | } 230 | 231 | impl Field for Framed<'_, T> { 232 | fn dimensions(&self) -> (usize, usize) { 233 | self.field.dimensions() 234 | } 235 | 236 | fn z_at(&self, x: usize, y: usize) -> f64 { 237 | let (w, h) = self.dimensions(); 238 | 239 | if x == 0 || x == w.saturating_sub(1) || y == 0 || y == h.saturating_sub(1) { 240 | self.border_z + 1e-9 241 | } else { 242 | self.field.z_at(x, y) 243 | } 244 | } 245 | } 246 | 247 | #[cfg(test)] 248 | mod tests { 249 | use super::simplify::simplify; 250 | use super::*; 251 | 252 | #[test] 253 | fn test_msq_basic_square() { 254 | struct Sq; 255 | impl Field for Sq { 256 | fn dimensions(&self) -> (usize, usize) { 257 | (10, 10) 258 | } 259 | 260 | fn z_at(&self, x: usize, y: usize) -> f64 { 261 | if (0..2).contains(&x) && y == 2 { 262 | return 1.0; 263 | } 264 | 265 | let r = 2..8; 266 | if r.contains(&x) && r.contains(&y) { 267 | 1.0 268 | } else { 269 | 0.0 270 | } 271 | } 272 | } 273 | 274 | let countours = march(&Sq {}, 0.5) 275 | .into_iter() 276 | .map(|p| simplify(&p)) 277 | .collect::>(); 278 | 279 | assert_eq!( 280 | countours, 281 | vec![vec![ 282 | (0.0, 2.5), 283 | (1.0, 2.5), 284 | (1.5, 3.0), 285 | (1.5, 7.0), 286 | (2.0, 7.5), 287 | (7.0, 7.5), 288 | (7.5, 7.0), 289 | (7.5, 2.0), 290 | (7.0, 1.5), 291 | (0.0, 1.5) 292 | ]] 293 | ); 294 | } 295 | 296 | #[test] 297 | fn test_msq_everything_filled() { 298 | struct Filled; 299 | impl Field for Filled { 300 | fn dimensions(&self) -> (usize, usize) { 301 | (10, 10) 302 | } 303 | 304 | fn z_at(&self, _x: usize, _y: usize) -> f64 { 305 | 1.0 306 | } 307 | } 308 | 309 | assert!(march(&Filled {}, 2.0).is_empty()); 310 | } 311 | 312 | #[test] 313 | fn test_msq_everything_empty() { 314 | struct Empty; 315 | impl Field for Empty { 316 | fn dimensions(&self) -> (usize, usize) { 317 | (10, 10) 318 | } 319 | 320 | fn z_at(&self, _x: usize, _y: usize) -> f64 { 321 | 0.0 322 | } 323 | } 324 | 325 | assert!(march(&Empty {}, 2.0).is_empty()); 326 | assert_eq!(march(&Empty {}.framed(2.0), 2.0).len(), 1); 327 | } 328 | 329 | #[test] 330 | fn test_fraction() { 331 | assert_eq!(fraction(5.0, (5.0, 5.0)), 0.5); 332 | assert_eq!(fraction(5.0, (5.0, 10.0)), 0.0); 333 | assert_eq!(fraction(7.5, (5.0, 10.0)), 0.5); 334 | assert_eq!(fraction(0.0, (5.0, 10.0)), 0.0); 335 | assert_eq!(fraction(20.0, (5.0, 10.0)), 1.0); 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2020 Daniele D'Orazio 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aligned-vec" 13 | version = "0.5.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" 16 | 17 | [[package]] 18 | name = "anyhow" 19 | version = "1.0.86" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 22 | 23 | [[package]] 24 | name = "arbitrary" 25 | version = "1.3.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" 28 | 29 | [[package]] 30 | name = "arg_enum_proc_macro" 31 | version = "0.3.4" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" 34 | dependencies = [ 35 | "proc-macro2", 36 | "quote", 37 | "syn 2.0.76", 38 | ] 39 | 40 | [[package]] 41 | name = "arrayvec" 42 | version = "0.7.6" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.1.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 51 | 52 | [[package]] 53 | name = "av1-grain" 54 | version = "0.2.3" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" 57 | dependencies = [ 58 | "anyhow", 59 | "arrayvec", 60 | "log", 61 | "nom", 62 | "num-rational", 63 | "v_frame", 64 | ] 65 | 66 | [[package]] 67 | name = "avif-serialize" 68 | version = "0.8.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" 71 | dependencies = [ 72 | "arrayvec", 73 | ] 74 | 75 | [[package]] 76 | name = "bit_field" 77 | version = "0.10.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 80 | 81 | [[package]] 82 | name = "bitflags" 83 | version = "1.3.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 86 | 87 | [[package]] 88 | name = "bitstream-io" 89 | version = "2.5.3" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" 92 | 93 | [[package]] 94 | name = "built" 95 | version = "0.7.4" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" 98 | 99 | [[package]] 100 | name = "bumpalo" 101 | version = "3.12.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" 104 | 105 | [[package]] 106 | name = "bytemuck" 107 | version = "1.17.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" 110 | 111 | [[package]] 112 | name = "byteorder" 113 | version = "1.4.3" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 116 | 117 | [[package]] 118 | name = "byteorder-lite" 119 | version = "0.1.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" 122 | 123 | [[package]] 124 | name = "cc" 125 | version = "1.1.15" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" 128 | dependencies = [ 129 | "jobserver", 130 | "libc", 131 | "shlex", 132 | ] 133 | 134 | [[package]] 135 | name = "cfg-expr" 136 | version = "0.15.8" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" 139 | dependencies = [ 140 | "smallvec", 141 | "target-lexicon", 142 | ] 143 | 144 | [[package]] 145 | name = "cfg-if" 146 | version = "1.0.0" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 149 | 150 | [[package]] 151 | name = "color_quant" 152 | version = "1.1.0" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 155 | 156 | [[package]] 157 | name = "crc32fast" 158 | version = "1.3.2" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 161 | dependencies = [ 162 | "cfg-if", 163 | ] 164 | 165 | [[package]] 166 | name = "crossbeam-channel" 167 | version = "0.5.7" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" 170 | dependencies = [ 171 | "cfg-if", 172 | "crossbeam-utils", 173 | ] 174 | 175 | [[package]] 176 | name = "crossbeam-deque" 177 | version = "0.8.3" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" 180 | dependencies = [ 181 | "cfg-if", 182 | "crossbeam-epoch", 183 | "crossbeam-utils", 184 | ] 185 | 186 | [[package]] 187 | name = "crossbeam-epoch" 188 | version = "0.9.14" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" 191 | dependencies = [ 192 | "autocfg", 193 | "cfg-if", 194 | "crossbeam-utils", 195 | "memoffset", 196 | "scopeguard", 197 | ] 198 | 199 | [[package]] 200 | name = "crossbeam-utils" 201 | version = "0.8.15" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" 204 | dependencies = [ 205 | "cfg-if", 206 | ] 207 | 208 | [[package]] 209 | name = "crunchy" 210 | version = "0.2.2" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 213 | 214 | [[package]] 215 | name = "either" 216 | version = "1.8.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" 219 | 220 | [[package]] 221 | name = "equivalent" 222 | version = "1.0.1" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 225 | 226 | [[package]] 227 | name = "exr" 228 | version = "1.6.3" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4" 231 | dependencies = [ 232 | "bit_field", 233 | "flume", 234 | "half", 235 | "lebe", 236 | "miniz_oxide", 237 | "rayon-core", 238 | "smallvec", 239 | "zune-inflate", 240 | ] 241 | 242 | [[package]] 243 | name = "flate2" 244 | version = "1.0.25" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" 247 | dependencies = [ 248 | "crc32fast", 249 | "miniz_oxide", 250 | ] 251 | 252 | [[package]] 253 | name = "flume" 254 | version = "0.10.14" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" 257 | dependencies = [ 258 | "futures-core", 259 | "futures-sink", 260 | "nanorand", 261 | "pin-project", 262 | "spin", 263 | ] 264 | 265 | [[package]] 266 | name = "futures-core" 267 | version = "0.3.28" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" 270 | 271 | [[package]] 272 | name = "futures-sink" 273 | version = "0.3.28" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" 276 | 277 | [[package]] 278 | name = "getrandom" 279 | version = "0.2.8" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 282 | dependencies = [ 283 | "cfg-if", 284 | "js-sys", 285 | "libc", 286 | "wasi", 287 | "wasm-bindgen", 288 | ] 289 | 290 | [[package]] 291 | name = "gif" 292 | version = "0.13.1" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" 295 | dependencies = [ 296 | "color_quant", 297 | "weezl", 298 | ] 299 | 300 | [[package]] 301 | name = "half" 302 | version = "2.2.1" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" 305 | dependencies = [ 306 | "crunchy", 307 | ] 308 | 309 | [[package]] 310 | name = "hashbrown" 311 | version = "0.14.5" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 314 | 315 | [[package]] 316 | name = "heck" 317 | version = "0.5.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 320 | 321 | [[package]] 322 | name = "hermit-abi" 323 | version = "0.2.6" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 326 | dependencies = [ 327 | "libc", 328 | ] 329 | 330 | [[package]] 331 | name = "image" 332 | version = "0.25.2" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" 335 | dependencies = [ 336 | "bytemuck", 337 | "byteorder-lite", 338 | "color_quant", 339 | "exr", 340 | "gif", 341 | "image-webp", 342 | "num-traits", 343 | "png", 344 | "qoi", 345 | "ravif", 346 | "rayon", 347 | "rgb", 348 | "tiff", 349 | "zune-core", 350 | "zune-jpeg", 351 | ] 352 | 353 | [[package]] 354 | name = "image-webp" 355 | version = "0.1.3" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" 358 | dependencies = [ 359 | "byteorder-lite", 360 | "quick-error", 361 | ] 362 | 363 | [[package]] 364 | name = "imgref" 365 | version = "1.10.1" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" 368 | 369 | [[package]] 370 | name = "indexmap" 371 | version = "2.4.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" 374 | dependencies = [ 375 | "equivalent", 376 | "hashbrown", 377 | ] 378 | 379 | [[package]] 380 | name = "interpolate_name" 381 | version = "0.2.4" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" 384 | dependencies = [ 385 | "proc-macro2", 386 | "quote", 387 | "syn 2.0.76", 388 | ] 389 | 390 | [[package]] 391 | name = "itertools" 392 | version = "0.12.1" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 395 | dependencies = [ 396 | "either", 397 | ] 398 | 399 | [[package]] 400 | name = "jobserver" 401 | version = "0.1.32" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 404 | dependencies = [ 405 | "libc", 406 | ] 407 | 408 | [[package]] 409 | name = "jpeg-decoder" 410 | version = "0.3.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" 413 | 414 | [[package]] 415 | name = "js-sys" 416 | version = "0.3.61" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" 419 | dependencies = [ 420 | "wasm-bindgen", 421 | ] 422 | 423 | [[package]] 424 | name = "lebe" 425 | version = "0.5.2" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" 428 | 429 | [[package]] 430 | name = "libc" 431 | version = "0.2.141" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" 434 | 435 | [[package]] 436 | name = "libfuzzer-sys" 437 | version = "0.4.7" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" 440 | dependencies = [ 441 | "arbitrary", 442 | "cc", 443 | "once_cell", 444 | ] 445 | 446 | [[package]] 447 | name = "lock_api" 448 | version = "0.4.9" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 451 | dependencies = [ 452 | "autocfg", 453 | "scopeguard", 454 | ] 455 | 456 | [[package]] 457 | name = "log" 458 | version = "0.4.17" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 461 | dependencies = [ 462 | "cfg-if", 463 | ] 464 | 465 | [[package]] 466 | name = "loop9" 467 | version = "0.1.5" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" 470 | dependencies = [ 471 | "imgref", 472 | ] 473 | 474 | [[package]] 475 | name = "marching_squares" 476 | version = "0.1.0" 477 | dependencies = [ 478 | "image", 479 | "marching_squares", 480 | ] 481 | 482 | [[package]] 483 | name = "maybe-rayon" 484 | version = "0.1.1" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" 487 | dependencies = [ 488 | "cfg-if", 489 | ] 490 | 491 | [[package]] 492 | name = "memchr" 493 | version = "2.7.4" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 496 | 497 | [[package]] 498 | name = "memoffset" 499 | version = "0.8.0" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" 502 | dependencies = [ 503 | "autocfg", 504 | ] 505 | 506 | [[package]] 507 | name = "minimal-lexical" 508 | version = "0.2.1" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 511 | 512 | [[package]] 513 | name = "miniz_oxide" 514 | version = "0.6.2" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" 517 | dependencies = [ 518 | "adler", 519 | ] 520 | 521 | [[package]] 522 | name = "nanorand" 523 | version = "0.7.0" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 526 | dependencies = [ 527 | "getrandom", 528 | ] 529 | 530 | [[package]] 531 | name = "new_debug_unreachable" 532 | version = "1.0.6" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 535 | 536 | [[package]] 537 | name = "nom" 538 | version = "7.1.3" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 541 | dependencies = [ 542 | "memchr", 543 | "minimal-lexical", 544 | ] 545 | 546 | [[package]] 547 | name = "noop_proc_macro" 548 | version = "0.3.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" 551 | 552 | [[package]] 553 | name = "num-bigint" 554 | version = "0.4.4" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" 557 | dependencies = [ 558 | "autocfg", 559 | "num-integer", 560 | "num-traits", 561 | ] 562 | 563 | [[package]] 564 | name = "num-derive" 565 | version = "0.4.2" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" 568 | dependencies = [ 569 | "proc-macro2", 570 | "quote", 571 | "syn 2.0.76", 572 | ] 573 | 574 | [[package]] 575 | name = "num-integer" 576 | version = "0.1.45" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 579 | dependencies = [ 580 | "autocfg", 581 | "num-traits", 582 | ] 583 | 584 | [[package]] 585 | name = "num-rational" 586 | version = "0.4.1" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" 589 | dependencies = [ 590 | "autocfg", 591 | "num-bigint", 592 | "num-integer", 593 | "num-traits", 594 | ] 595 | 596 | [[package]] 597 | name = "num-traits" 598 | version = "0.2.19" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 601 | dependencies = [ 602 | "autocfg", 603 | ] 604 | 605 | [[package]] 606 | name = "num_cpus" 607 | version = "1.15.0" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 610 | dependencies = [ 611 | "hermit-abi", 612 | "libc", 613 | ] 614 | 615 | [[package]] 616 | name = "once_cell" 617 | version = "1.19.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 620 | 621 | [[package]] 622 | name = "paste" 623 | version = "1.0.15" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 626 | 627 | [[package]] 628 | name = "pin-project" 629 | version = "1.0.12" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 632 | dependencies = [ 633 | "pin-project-internal", 634 | ] 635 | 636 | [[package]] 637 | name = "pin-project-internal" 638 | version = "1.0.12" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 641 | dependencies = [ 642 | "proc-macro2", 643 | "quote", 644 | "syn 1.0.109", 645 | ] 646 | 647 | [[package]] 648 | name = "pkg-config" 649 | version = "0.3.30" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 652 | 653 | [[package]] 654 | name = "png" 655 | version = "0.17.7" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" 658 | dependencies = [ 659 | "bitflags", 660 | "crc32fast", 661 | "flate2", 662 | "miniz_oxide", 663 | ] 664 | 665 | [[package]] 666 | name = "ppv-lite86" 667 | version = "0.2.20" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 670 | dependencies = [ 671 | "zerocopy", 672 | ] 673 | 674 | [[package]] 675 | name = "proc-macro2" 676 | version = "1.0.86" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 679 | dependencies = [ 680 | "unicode-ident", 681 | ] 682 | 683 | [[package]] 684 | name = "profiling" 685 | version = "1.0.15" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" 688 | dependencies = [ 689 | "profiling-procmacros", 690 | ] 691 | 692 | [[package]] 693 | name = "profiling-procmacros" 694 | version = "1.0.15" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" 697 | dependencies = [ 698 | "quote", 699 | "syn 2.0.76", 700 | ] 701 | 702 | [[package]] 703 | name = "qoi" 704 | version = "0.4.1" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" 707 | dependencies = [ 708 | "bytemuck", 709 | ] 710 | 711 | [[package]] 712 | name = "quick-error" 713 | version = "2.0.1" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 716 | 717 | [[package]] 718 | name = "quote" 719 | version = "1.0.37" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 722 | dependencies = [ 723 | "proc-macro2", 724 | ] 725 | 726 | [[package]] 727 | name = "rand" 728 | version = "0.8.5" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 731 | dependencies = [ 732 | "libc", 733 | "rand_chacha", 734 | "rand_core", 735 | ] 736 | 737 | [[package]] 738 | name = "rand_chacha" 739 | version = "0.3.1" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 742 | dependencies = [ 743 | "ppv-lite86", 744 | "rand_core", 745 | ] 746 | 747 | [[package]] 748 | name = "rand_core" 749 | version = "0.6.4" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 752 | dependencies = [ 753 | "getrandom", 754 | ] 755 | 756 | [[package]] 757 | name = "rav1e" 758 | version = "0.7.1" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" 761 | dependencies = [ 762 | "arbitrary", 763 | "arg_enum_proc_macro", 764 | "arrayvec", 765 | "av1-grain", 766 | "bitstream-io", 767 | "built", 768 | "cfg-if", 769 | "interpolate_name", 770 | "itertools", 771 | "libc", 772 | "libfuzzer-sys", 773 | "log", 774 | "maybe-rayon", 775 | "new_debug_unreachable", 776 | "noop_proc_macro", 777 | "num-derive", 778 | "num-traits", 779 | "once_cell", 780 | "paste", 781 | "profiling", 782 | "rand", 783 | "rand_chacha", 784 | "simd_helpers", 785 | "system-deps", 786 | "thiserror", 787 | "v_frame", 788 | "wasm-bindgen", 789 | ] 790 | 791 | [[package]] 792 | name = "ravif" 793 | version = "0.11.10" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" 796 | dependencies = [ 797 | "avif-serialize", 798 | "imgref", 799 | "loop9", 800 | "quick-error", 801 | "rav1e", 802 | "rgb", 803 | ] 804 | 805 | [[package]] 806 | name = "rayon" 807 | version = "1.7.0" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" 810 | dependencies = [ 811 | "either", 812 | "rayon-core", 813 | ] 814 | 815 | [[package]] 816 | name = "rayon-core" 817 | version = "1.11.0" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" 820 | dependencies = [ 821 | "crossbeam-channel", 822 | "crossbeam-deque", 823 | "crossbeam-utils", 824 | "num_cpus", 825 | ] 826 | 827 | [[package]] 828 | name = "rgb" 829 | version = "0.8.48" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" 832 | dependencies = [ 833 | "bytemuck", 834 | ] 835 | 836 | [[package]] 837 | name = "scopeguard" 838 | version = "1.1.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 841 | 842 | [[package]] 843 | name = "serde" 844 | version = "1.0.209" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" 847 | dependencies = [ 848 | "serde_derive", 849 | ] 850 | 851 | [[package]] 852 | name = "serde_derive" 853 | version = "1.0.209" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" 856 | dependencies = [ 857 | "proc-macro2", 858 | "quote", 859 | "syn 2.0.76", 860 | ] 861 | 862 | [[package]] 863 | name = "serde_spanned" 864 | version = "0.6.7" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" 867 | dependencies = [ 868 | "serde", 869 | ] 870 | 871 | [[package]] 872 | name = "shlex" 873 | version = "1.3.0" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 876 | 877 | [[package]] 878 | name = "simd-adler32" 879 | version = "0.3.5" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" 882 | 883 | [[package]] 884 | name = "simd_helpers" 885 | version = "0.1.0" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" 888 | dependencies = [ 889 | "quote", 890 | ] 891 | 892 | [[package]] 893 | name = "smallvec" 894 | version = "1.10.0" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 897 | 898 | [[package]] 899 | name = "spin" 900 | version = "0.9.8" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 903 | dependencies = [ 904 | "lock_api", 905 | ] 906 | 907 | [[package]] 908 | name = "syn" 909 | version = "1.0.109" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 912 | dependencies = [ 913 | "proc-macro2", 914 | "quote", 915 | "unicode-ident", 916 | ] 917 | 918 | [[package]] 919 | name = "syn" 920 | version = "2.0.76" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" 923 | dependencies = [ 924 | "proc-macro2", 925 | "quote", 926 | "unicode-ident", 927 | ] 928 | 929 | [[package]] 930 | name = "system-deps" 931 | version = "6.2.2" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" 934 | dependencies = [ 935 | "cfg-expr", 936 | "heck", 937 | "pkg-config", 938 | "toml", 939 | "version-compare", 940 | ] 941 | 942 | [[package]] 943 | name = "target-lexicon" 944 | version = "0.12.16" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" 947 | 948 | [[package]] 949 | name = "thiserror" 950 | version = "1.0.63" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 953 | dependencies = [ 954 | "thiserror-impl", 955 | ] 956 | 957 | [[package]] 958 | name = "thiserror-impl" 959 | version = "1.0.63" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 962 | dependencies = [ 963 | "proc-macro2", 964 | "quote", 965 | "syn 2.0.76", 966 | ] 967 | 968 | [[package]] 969 | name = "tiff" 970 | version = "0.9.1" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" 973 | dependencies = [ 974 | "flate2", 975 | "jpeg-decoder", 976 | "weezl", 977 | ] 978 | 979 | [[package]] 980 | name = "toml" 981 | version = "0.8.19" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 984 | dependencies = [ 985 | "serde", 986 | "serde_spanned", 987 | "toml_datetime", 988 | "toml_edit", 989 | ] 990 | 991 | [[package]] 992 | name = "toml_datetime" 993 | version = "0.6.8" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 996 | dependencies = [ 997 | "serde", 998 | ] 999 | 1000 | [[package]] 1001 | name = "toml_edit" 1002 | version = "0.22.20" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" 1005 | dependencies = [ 1006 | "indexmap", 1007 | "serde", 1008 | "serde_spanned", 1009 | "toml_datetime", 1010 | "winnow", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "unicode-ident" 1015 | version = "1.0.8" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 1018 | 1019 | [[package]] 1020 | name = "v_frame" 1021 | version = "0.3.8" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" 1024 | dependencies = [ 1025 | "aligned-vec", 1026 | "num-traits", 1027 | "wasm-bindgen", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "version-compare" 1032 | version = "0.2.0" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" 1035 | 1036 | [[package]] 1037 | name = "wasi" 1038 | version = "0.11.0+wasi-snapshot-preview1" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1041 | 1042 | [[package]] 1043 | name = "wasm-bindgen" 1044 | version = "0.2.93" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 1047 | dependencies = [ 1048 | "cfg-if", 1049 | "once_cell", 1050 | "wasm-bindgen-macro", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "wasm-bindgen-backend" 1055 | version = "0.2.93" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 1058 | dependencies = [ 1059 | "bumpalo", 1060 | "log", 1061 | "once_cell", 1062 | "proc-macro2", 1063 | "quote", 1064 | "syn 2.0.76", 1065 | "wasm-bindgen-shared", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "wasm-bindgen-macro" 1070 | version = "0.2.93" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 1073 | dependencies = [ 1074 | "quote", 1075 | "wasm-bindgen-macro-support", 1076 | ] 1077 | 1078 | [[package]] 1079 | name = "wasm-bindgen-macro-support" 1080 | version = "0.2.93" 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" 1082 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 1083 | dependencies = [ 1084 | "proc-macro2", 1085 | "quote", 1086 | "syn 2.0.76", 1087 | "wasm-bindgen-backend", 1088 | "wasm-bindgen-shared", 1089 | ] 1090 | 1091 | [[package]] 1092 | name = "wasm-bindgen-shared" 1093 | version = "0.2.93" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 1096 | 1097 | [[package]] 1098 | name = "weezl" 1099 | version = "0.1.8" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" 1102 | 1103 | [[package]] 1104 | name = "winnow" 1105 | version = "0.6.18" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" 1108 | dependencies = [ 1109 | "memchr", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "zerocopy" 1114 | version = "0.7.35" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1117 | dependencies = [ 1118 | "byteorder", 1119 | "zerocopy-derive", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "zerocopy-derive" 1124 | version = "0.7.35" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1127 | dependencies = [ 1128 | "proc-macro2", 1129 | "quote", 1130 | "syn 2.0.76", 1131 | ] 1132 | 1133 | [[package]] 1134 | name = "zune-core" 1135 | version = "0.4.12" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" 1138 | 1139 | [[package]] 1140 | name = "zune-inflate" 1141 | version = "0.2.53" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "440a08fd59c6442e4b846ea9b10386c38307eae728b216e1ab2c305d1c9daaf8" 1144 | dependencies = [ 1145 | "simd-adler32", 1146 | ] 1147 | 1148 | [[package]] 1149 | name = "zune-jpeg" 1150 | version = "0.4.13" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" 1153 | dependencies = [ 1154 | "zune-core", 1155 | ] 1156 | --------------------------------------------------------------------------------