├── .gitignore ├── images └── lenna.png ├── Cargo.toml ├── src ├── error.rs ├── opts.rs └── main.rs ├── rainbow_color_mapper.py ├── LICENSE ├── README.md ├── .github └── workflows │ └── release-binaries.yml └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /images/lenna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DenSASoftware/kantig/HEAD/images/lenna.png -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kantig" 3 | version = "0.1.0" 4 | authors = ["DenSASoftware "] 5 | edition = "2018" 6 | license = "MIT" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | imageproc = "0.21.0" 12 | image = "0.23.8" 13 | rtriangulate = "0.3.1" 14 | structopt = "0.3.17" 15 | thiserror = "1.0.20" 16 | 17 | [dependencies.rand] 18 | version = "0.7.3" 19 | features = ["small_rng"] 20 | 21 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use image::error::ImageError; 2 | use std::io::Error as IOError; 3 | use thiserror::Error; 4 | 5 | /// A simple error wrapping the different errors that can occur during the run 6 | #[derive(Debug, Error)] 7 | pub enum LowPolyError { 8 | #[error("error encoding/decoding image: {0}")] 9 | ImageError(#[from] ImageError), 10 | #[error("io error: {0}")] 11 | IOError(#[from] IOError), 12 | 13 | /// This one is raised if the user passes more than one option regarding the number of points 14 | /// used, thereby giving mixed signals. I could not figure out how to have structopt check for 15 | /// this automatically. 16 | #[error("only one of --points, --points-relative and --points-pixel-relative can be set")] 17 | CLIError, 18 | } 19 | 20 | pub type LowPolyResult = Result; 21 | -------------------------------------------------------------------------------- /rainbow_color_mapper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | def value_to_rgb(value): # 0 <= value < 256 * 6 3 | n, m = divmod(value, 256) 4 | if b % 2 == 0: 5 | m = 255 - m 6 | 7 | if n == 0: return (255, m, 0) 8 | elif n == 1: return (m, 255, 0) 9 | elif n == 2: return (0, 255, m) 10 | elif n == 3: return (0, m, 255) 11 | elif n == 4: return (m, 0, 255) 12 | else: return (255, 0, m) 13 | 14 | def blend(a, b, factor): 15 | return int(a * factor + b * (1 - factor)) 16 | 17 | r, g, b = [int(i) for i in input().split()] 18 | x1, y1, x2, y2, x3, y3 = [int(i) for i in input().split()] 19 | width, height = [int(i) for i in input().split()] 20 | 21 | center = ( 22 | (x1 + x2 + x3) / 3, 23 | (y1 + y2 + y3) / 3 24 | ) 25 | 26 | val = int(center[0] / width * 256 * 6) 27 | (r2, g2, b2) = value_to_rgb(val) 28 | 29 | print( 30 | blend(r, r2, 0.75), 31 | blend(g, g2, 0.75), 32 | blend(b, b2, 0.75) 33 | ) 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 DenSASoftware 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kantig - create low-poly images 2 | 3 | Create low-poly images in your command line. This program has a variety of options and allows the use of stdin/stdout for tons of scripting potential. 4 | 5 | ![Lenna](images/lenna.png) 6 | 7 | ## Installation 8 | 9 | You can either download a prebuilt binary for your platform on the releases page or build the binary yourself. To build this program, [install rust](https://www.rust-lang.org/tools/install) first. Then, clone this project and run `cargo build --release`. The final binary will be in `target/release/kantig` and can be moved into your binary folder. Alternatively you can install the binary directly with: 10 | ```bash 11 | cargo install --git https://github.com/densasoftware/kantig 12 | ``` 13 | 14 | ## Invokation 15 | 16 | Calling kantig is as easy as `kantig -o output.png input.png`. By omitting the input or output file you can instruct kantig to use stdin/stdout. An example would be `curl https://i.redd.it/ub4dywjkj9k51.jpg | kantig | display -`. You can also tweak the quality using the different command line options listed below. 17 | 18 | ## Documentation 19 | 20 | To access the documentation either just open the code or run `cargo doc --open`. 21 | 22 | ## How does it work? 23 | 24 | kantig works by applying an edge detection algorithm to the image. It picks the points which were detected as edges, takes a random sample containing a certain amonut of points that you can specify, removes points too close to each other, uses the delaunay triangulation to produce a mesh of triangles and draws the triangles onto a new image. 25 | 26 | ## Command line options 27 | 28 | When in the command line you can run `kantig --help` to get the same listing. 29 | 30 | Option | Default | Description 31 | ------------ | ------------- | ------------- 32 | `-o`/`--output` | `-` | The output filename. kantig tries to auto-detect the output format from the filename (this can be overwritten with `--output-format`). If not present stdout will be used. 33 | `--output-format` | `png` | The format of the produced image. This has to be the name of the file extension of a format supported by the [image crate](https://docs.rs/image/0.23.9/image/). Examples are `png`, `jpg` and `bmp`. 34 | `--points-min-distance` | `4` | Enforce points used for triangulation to have a distance of X pixels to each other or more 35 | `--rng-seed` | nothing | The seed for the RNG. This can be a number between 0 and 2^16-1 inclusive 36 | `--canny-lower` | `10.0` | The lower bound for the image detection algorithm 37 | `--canny-upper` | `15.0` | The upper bound for the image detection algorithm 38 | `--color-mapper` | nothing | A shell program to change the polygon colors used by kantig. This feature is in its alpha state, for now look it up in the documentation. This will change in the future. See the file rainbow_color_mapper.py for an example. 39 | 40 | ### Specifying the number of points 41 | 42 | There are three ways of specifying how many points kantig uses for the triangulation. Those are mutually exclusive. Note that the removal of close points happens after this, so you might end up with less points than you specified. 43 | 44 | Option | Description 45 | ------------ | ------------- 46 | `--points` | Use an absolute number of points 47 | `--points-relative` | Use a number of edge points relative to the number of edge points found. `0.0` means no points are used and `1.0` means all points are used 48 | `--points-pixel-relative` | Use a number of edge points relative to the number of pixels in the image 49 | 50 | These options exist to allow kantig to work with images of different sizes more seamlessly. By default kantig assumes `--points-relative 0.04`. 51 | 52 | -------------------------------------------------------------------------------- /src/opts.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{LowPolyError, LowPolyResult}; 2 | use image::ImageFormat; 3 | use std::fmt::{Display, Formatter, Result as FmtResult}; 4 | use std::num::ParseFloatError; 5 | use std::path::PathBuf; 6 | use structopt::StructOpt; 7 | use thiserror::Error; 8 | 9 | /// An error for parsing floats from the command line. Aside from the default parsing errors the 10 | /// passed value might not satisfy certain constraints, e.g. being between 0 and 1 or not being 11 | /// NaN. 12 | #[derive(Debug)] 13 | enum FloatParsingError { 14 | Native(ParseFloatError), 15 | NonNormal, 16 | Negative, 17 | BiggerThanOne, 18 | } 19 | 20 | impl Display for FloatParsingError { 21 | fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult { 22 | match self { 23 | FloatParsingError::Native(err) => err.fmt(fmt), 24 | FloatParsingError::NonNormal => write!(fmt, "number is inf or NaN"), 25 | FloatParsingError::Negative => write!(fmt, "number is negative"), 26 | FloatParsingError::BiggerThanOne => write!(fmt, "number is bigger than one"), 27 | } 28 | } 29 | } 30 | 31 | /// Parse a float from `src` and return `Ok(f)` if the value is greater than or equal to 0, 32 | /// excluding NaN and +/- infinite. -0 is allowed. 33 | fn parse_positive_float(src: &str) -> Result { 34 | let num = match src.parse::() { 35 | Ok(num) => num, 36 | Err(err) => return Err(FloatParsingError::Native(err)), 37 | }; 38 | 39 | match num { 40 | _ if num.is_infinite() || num.is_nan() => Err(FloatParsingError::NonNormal), 41 | _ if num < 0. => Err(FloatParsingError::Negative), 42 | _ => Ok(num), 43 | } 44 | } 45 | 46 | /// Same as [parse_positive_float](fn.parse_positive_float.html), but also requires the value to be 47 | /// in the range [0.0, 1.0]. 48 | fn parse_float_between_one_zero(src: &str) -> Result { 49 | let num = parse_positive_float(src)?; 50 | match num { 51 | _ if num > 1. => Err(FloatParsingError::BiggerThanOne), 52 | _ => Ok(num), 53 | } 54 | } 55 | 56 | /// This error will be returned when the user passes an image extension name that cannot be mapped 57 | /// to an image type. 58 | #[derive(Debug, Error)] 59 | enum ImageFormatError { 60 | #[error("unsupported image format {0}")] 61 | Unsupported(String), 62 | } 63 | 64 | /// Search the different image formats supported by the image crate and return the one that lists 65 | /// `src` as one of its file extension names. 66 | fn parse_image_format(src: &str) -> Result { 67 | use ImageFormat::*; 68 | let formats = [ 69 | Png, Jpeg, Gif, WebP, Pnm, Tiff, Tga, Dds, Bmp, Ico, Hdr, Farbfeld, 70 | ]; 71 | 72 | formats 73 | .iter() 74 | .find(|format| format.extensions_str().into_iter().any(|ext| *ext == src)) 75 | .cloned() 76 | .ok_or_else(|| ImageFormatError::Unsupported(src.into())) 77 | } 78 | 79 | #[derive(Debug, StructOpt)] 80 | #[structopt(name = "kantig")] 81 | /// Create low-poly images 82 | /// 83 | /// Transform an image into a low-poly image. This program applies an edge-detection-algorithm, 84 | /// selects a few of the resulting points and uses the delaunay-algorithm to create a mesh of 85 | /// triangles that will be turned into the final image. 86 | pub struct Options { 87 | /// the lower bound for the edge detectioin algorithm 88 | #[structopt(long, parse(try_from_str = parse_positive_float), default_value = "10.0")] 89 | pub canny_lower: f32, 90 | 91 | /// the upper bound for the edge detection algorithm 92 | #[structopt(long, parse(try_from_str = parse_positive_float), default_value = "15.0")] 93 | pub canny_upper: f32, 94 | 95 | /// the number of points to be picked from the edges 96 | #[structopt(short, long)] 97 | pub points: Option, 98 | 99 | /// pick an amount of low-poly points relative to the number of edge-points, 0 means none and 1 100 | /// means all 101 | #[structopt(long, parse(try_from_str = parse_float_between_one_zero))] 102 | pub points_relative: Option, 103 | 104 | /// pick an amount of low-poly points relative to the number of pixels in the image, 0 means 105 | /// none and 1 means all 106 | #[structopt(long, parse(try_from_str = parse_float_between_one_zero))] 107 | pub points_pixel_relative: Option, 108 | 109 | /// enforce a minimum distance between low-poly points 110 | #[structopt(long, parse(try_from_str = parse_positive_float), default_value = "4")] 111 | pub points_min_distance: f32, 112 | 113 | /// a shell command to map color values in the final image 114 | #[structopt(long)] 115 | pub color_mapper: Option, 116 | 117 | /// do not use antialiasing when drawing polygons 118 | #[structopt(long)] 119 | pub no_antialiasing: bool, 120 | 121 | /// the seed for the random number generator, values from 0 to 2^16-1 are acceptable 122 | #[structopt(long)] 123 | pub rng_seed: Option, 124 | 125 | /// the output file name, defaults to stdout 126 | #[structopt(long, short, parse(from_os_str))] 127 | pub output: Option, 128 | 129 | /// the output format, overrides the file format detected in the output file name 130 | #[structopt(long, parse(try_from_str = parse_image_format))] 131 | pub output_format: Option, 132 | 133 | /// the input file name, defaults to stdin 134 | #[structopt(parse(from_os_str))] 135 | pub input: Option, 136 | } 137 | 138 | /// An enum specifying how many points should be used for triangulation 139 | pub enum PixelUnit { 140 | /// Use an absolute number of points 141 | Absolute(usize), 142 | /// Use a fraction of the number of edge points 143 | Relative(f32), 144 | /// Use a fraction of the number of pixels in the image 145 | PixelRelative(f32), 146 | } 147 | 148 | impl Options { 149 | /// Check the cli options and return an object describing how many points should be used for 150 | /// triangulation. Fail if more than one option is set. 151 | /// 152 | /// Use 4% of the edge points by default. 153 | pub fn edge_number(&self) -> LowPolyResult { 154 | match ( 155 | self.points, 156 | self.points_relative, 157 | self.points_pixel_relative, 158 | ) { 159 | (None, None, None) => Ok(PixelUnit::Relative(0.04)), 160 | (Some(abs), None, None) => Ok(PixelUnit::Absolute(abs)), 161 | (None, Some(rel), None) => Ok(PixelUnit::Relative(rel)), 162 | (None, None, Some(rel)) => Ok(PixelUnit::PixelRelative(rel)), 163 | _ => Err(LowPolyError::CLIError), 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /.github/workflows/release-binaries.yml: -------------------------------------------------------------------------------- 1 | name: Release Binary 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+" 7 | 8 | jobs: 9 | create-release: 10 | name: create-release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Create artifacts directory 14 | run: mkdir artifacts 15 | 16 | - name: Get the release version from the tag 17 | if: env.ARTIFACT_VERSION == '' 18 | run: | 19 | # Apparently, this is the right way to get a tag name. Really? 20 | # 21 | # See: https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027 22 | echo "::set-env name=ARTIFACT_VERSION::${GITHUB_REF#refs/tags/}" 23 | echo "version is: ${{ env.ARTIFACT_VERSION }}" 24 | - name: Create GitHub release 25 | id: release 26 | uses: actions/create-release@v1 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | with: 30 | tag_name: ${{ env.ARTIFACT_VERSION }} 31 | release_name: ${{ env.ARTIFACT_VERSION }} 32 | 33 | - name: Save release upload URL to artifact 34 | run: echo "${{ steps.release.outputs.upload_url }}" > artifacts/release-upload-url 35 | 36 | - name: Save version number to artifact 37 | run: echo "${{ env.ARTIFACT_VERSION }}" > artifacts/release-version 38 | 39 | - name: Upload artifacts 40 | uses: actions/upload-artifact@v1 41 | with: 42 | name: artifacts 43 | path: artifacts 44 | 45 | build-release: 46 | name: build-release 47 | needs: ["create-release"] 48 | runs-on: ${{ matrix.os }} 49 | env: 50 | CARGO: cargo 51 | TARGET_FLAGS: 52 | TARGET_DIR: ./target 53 | RUST_BACKTRACE: 1 54 | BIN_NAME: kantig 55 | strategy: 56 | matrix: 57 | build: [linux-musl, linux-gnu, macos, win-msvc, win-gnu, win32-msvc] 58 | include: 59 | - build: linux-musl 60 | os: ubuntu-18.04 61 | rust: stable 62 | target: x86_64-unknown-linux-musl 63 | compiler: cross 64 | - build: linux-gnu 65 | os: ubuntu-18.04 66 | rust: stable 67 | target: x86_64-unknown-linux-gnu 68 | compiler: cargo 69 | - build: macos 70 | os: macos-latest 71 | rust: stable 72 | target: x86_64-apple-darwin 73 | compiler: cross 74 | - build: win-msvc 75 | os: windows-2019 76 | rust: nightly 77 | target: x86_64-pc-windows-msvc 78 | compiler: cross 79 | - build: win-gnu 80 | os: windows-2019 81 | rust: nightly-x86_64-gnu 82 | target: x86_64-pc-windows-gnu 83 | compiler: cross 84 | - build: win32-msvc 85 | os: windows-2019 86 | rust: nightly 87 | target: i686-pc-windows-msvc 88 | compiler: cross 89 | 90 | steps: 91 | - name: Checkout repository 92 | uses: actions/checkout@v1 93 | with: 94 | fetch-depth: 1 95 | 96 | - name: Install packages linux gnu 97 | if: matrix.build == 'linux-gnu' 98 | run: | 99 | sudo apt-get update 100 | sudo apt-get install -y --no-install-recommends build-essential pkg-config 101 | 102 | - name: Install packages linux musl 103 | if: matrix.build == 'linux-musl' 104 | run: | 105 | sudo apt-get update 106 | sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool pkg-config musl-tools 107 | - name: Install packages linux macos 108 | if: matrix.os == 'macos-latest' 109 | run: | 110 | echo "Only for future" 111 | - name: Install Rust 112 | uses: actions-rs/toolchain@v1 113 | with: 114 | toolchain: ${{ matrix.rust }} 115 | profile: minimal 116 | override: true 117 | target: ${{ matrix.target }} 118 | 119 | - name: Setup Env Var 120 | run: | 121 | cargo install --git https://github.com/rust-embedded/cross 122 | echo "::set-env name=CARGO:: ${{ matrix.compiler }}" 123 | echo "::set-env name=TARGET_FLAGS::--target ${{ matrix.target }}" 124 | echo "::set-env name=TARGET_DIR::./target/${{ matrix.target }}" 125 | - name: Show command used for Cargo 126 | run: | 127 | echo "cargo command is: ${{ env.CARGO }}" 128 | echo "target flag is: ${{ env.TARGET_FLAGS }}" 129 | echo "target dir is: ${{ env.TARGET_DIR }}" 130 | - name: Get release download URL 131 | uses: actions/download-artifact@v1 132 | with: 133 | name: artifacts 134 | path: artifacts 135 | 136 | - name: Set release upload URL and release version 137 | shell: bash 138 | run: | 139 | release_upload_url="$(cat artifacts/release-upload-url)" 140 | echo "::set-env name=RELEASE_UPLOAD_URL::$release_upload_url" 141 | echo "release upload url: $RELEASE_UPLOAD_URL" 142 | release_version="$(cat artifacts/release-version)" 143 | echo "::set-env name=RELEASE_VERSION::$release_version" 144 | echo "release version: $RELEASE_VERSION" 145 | 146 | - name: Build release binary 147 | run: ${{ env.CARGO }} build --verbose --release ${{ env.TARGET_FLAGS }} 148 | 149 | - name: Strip release binary (linux and macos) 150 | if: matrix.build == 'linux-musl' || matrix.build == 'linux-gnu' || matrix.build == 'macos' 151 | run: strip "target/${{ matrix.target }}/release/${{ env.BIN_NAME }}" 152 | 153 | - name: Build archive 154 | shell: bash 155 | run: | 156 | staging="${{ env.BIN_NAME }}-${{ env.RELEASE_VERSION }}-${{ matrix.target }}" 157 | mkdir -p "$staging" 158 | cp {README.md,rainbow_color_mapper.py} "$staging/" 159 | if [ "${{ matrix.os }}" = "windows-2019" ]; then 160 | cp "target/${{ matrix.target }}/release/${{ env.BIN_NAME }}.exe" "$staging/" 161 | 7z a "$staging.zip" "$staging" 162 | echo "::set-env name=ASSET::$staging.zip" 163 | else 164 | cp "target/${{ matrix.target }}/release/${{ env.BIN_NAME }}" "$staging/" 165 | tar czf "$staging.tar.gz" "$staging" 166 | echo "::set-env name=ASSET::$staging.tar.gz" 167 | fi 168 | - name: Upload release archive 169 | uses: actions/upload-release-asset@v1.0.1 170 | env: 171 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 172 | with: 173 | upload_url: ${{ env.RELEASE_UPLOAD_URL }} 174 | asset_path: ${{ env.ASSET }} 175 | asset_name: ${{ env.ASSET }} 176 | asset_content_type: application/octet-stream -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use image::io::Reader as ImageReader; 2 | use image::Pixel; 3 | use image::{DynamicImage, ImageFormat, RgbImage}; 4 | use imageproc::drawing::{draw_antialiased_line_segment_mut, draw_convex_polygon_mut, Point}; 5 | use imageproc::edges::canny; 6 | use imageproc::pixelops::interpolate; 7 | use rand::{rngs::SmallRng, seq::SliceRandom, SeedableRng}; 8 | use rtriangulate::{triangulate, Triangle, TriangulationPoint}; 9 | use std::fs::File; 10 | use std::io::{stdin, stdout, BufReader, Cursor, Read, Write}; 11 | use std::process::{Command, Stdio, exit}; 12 | use structopt::StructOpt; 13 | 14 | use std::error::Error; 15 | use error::LowPolyResult; 16 | use opts::{Options, PixelUnit}; 17 | 18 | mod error; 19 | mod opts; 20 | 21 | /// Calculate the distance between the two points. 22 | fn distance(p1: &TriangulationPoint, p2: &TriangulationPoint) -> f32 { 23 | ((p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sqrt() 24 | } 25 | 26 | /// Load the image specified in `opts` and guess the file format. 27 | /// 28 | /// If a filename is given, the file is opened. Otherwise the contents of stdin are loaded into 29 | /// memory because the image decoder needs to be able to perform seek-operations on its input. 30 | fn load_image(opts: &Options) -> LowPolyResult { 31 | match &opts.input { 32 | Some(filename) => Ok(ImageReader::new(BufReader::new(File::open(filename)?)) 33 | .with_guessed_format()? 34 | .decode()?), 35 | None => { 36 | let mut buffer = Vec::new(); 37 | stdin().lock().read_to_end(&mut buffer)?; 38 | 39 | Ok(ImageReader::new(Cursor::new(buffer)) 40 | .with_guessed_format()? 41 | .decode()?) 42 | } 43 | } 44 | } 45 | 46 | /// Calculate edge-points for the image based on the values of `opts`. 47 | /// 48 | /// The four corners of the image will always be added to the result, meaning that the 49 | /// triangulation resulting of the returned points will always cover the whole image area. 50 | fn edge_points(img: &DynamicImage, opts: &Options) -> LowPolyResult>> { 51 | let edges = canny(&img.to_luma(), opts.canny_lower, opts.canny_upper); 52 | 53 | let mut points = Vec::new(); 54 | const WHITE: [u8; 1] = [255u8; 1]; 55 | for (x, y, p) in edges.enumerate_pixels() { 56 | if p.0 == WHITE { 57 | points.push(TriangulationPoint::new(x as f32, y as f32)); 58 | } 59 | } 60 | let mut rng = match opts.rng_seed { 61 | Some(seed) => SmallRng::from_seed(seed.to_le_bytes()), 62 | None => SmallRng::from_entropy(), 63 | }; 64 | points.shuffle(&mut rng); 65 | 66 | let limit = match opts.edge_number()? { 67 | PixelUnit::Absolute(abs) => abs, 68 | PixelUnit::Relative(rel) => (points.len() as f32 * rel) as usize, 69 | PixelUnit::PixelRelative(rel) => ((edges.width() * edges.height()) as f32 * rel) as usize, 70 | }; 71 | points.truncate(limit); 72 | 73 | if opts.points_min_distance > 0. { 74 | remove_close_points(&mut points, opts.points_min_distance); 75 | } 76 | 77 | let width = edges.width() as f32; 78 | let height = edges.height() as f32; 79 | points.push(TriangulationPoint::new(0., 0.)); 80 | points.push(TriangulationPoint::new(width, 0.)); 81 | points.push(TriangulationPoint::new(0., height)); 82 | points.push(TriangulationPoint::new(width, height)); 83 | 84 | Ok(points) 85 | } 86 | 87 | /// Remove points whose distance to each other is below a certain threshold. 88 | /// 89 | /// For every point in the list, all subsequent points in the list whose distance to the point is 90 | /// less then `min_distance` are removed. 91 | fn remove_close_points(points: &mut Vec>, min_distance: f32) { 92 | let mut i = 0; 93 | while i < points.len() { 94 | let mut j = i + 1; 95 | while j < points.len() { 96 | if distance(&points[i], &points[j]) < min_distance { 97 | points.remove(j); 98 | } else { 99 | j += 1; 100 | } 101 | } 102 | 103 | i += 1; 104 | } 105 | } 106 | 107 | /// Create the low-poly image. 108 | /// 109 | /// Use the triangles of `triangulation` referring to the points in `points` and the colors of 110 | /// `original` to create a new rgb-image with the same size as the original. 111 | fn create_low_poly( 112 | original: &RgbImage, 113 | points: &[TriangulationPoint], 114 | triangulation: &[Triangle], 115 | opts: &Options, 116 | ) -> LowPolyResult { 117 | let mut img = RgbImage::new(original.width(), original.height()); 118 | let mut tri_buf = [Point::new(0, 0); 3]; 119 | for tri in triangulation { 120 | let a = points[tri.0]; 121 | let b = points[tri.1]; 122 | let c = points[tri.2]; 123 | 124 | let center = ((a.x + b.x + c.x) as u32 / 3, (a.y + b.y + c.y) as u32 / 3); 125 | tri_buf[0] = Point::new(a.x as i32, a.y as i32); 126 | tri_buf[1] = Point::new(b.x as i32, b.y as i32); 127 | tri_buf[2] = Point::new(c.x as i32, c.y as i32); 128 | 129 | let mut color = original.get_pixel(center.0, center.1).to_rgb(); 130 | if let Some(cmd) = &opts.color_mapper { 131 | color = get_color_from_command(&cmd, color, &[a, b, c], (img.width(), img.height()))?; 132 | } 133 | 134 | draw_convex_polygon_mut(&mut img, &tri_buf, color); 135 | 136 | if !opts.no_antialiasing { 137 | let ps = [a, b, c]; 138 | 139 | for i in 0..3 { 140 | let p1 = ps[i]; 141 | let p2 = ps[(i + 1) % 3]; 142 | 143 | draw_antialiased_line_segment_mut( 144 | &mut img, 145 | (p1.x as i32, p1.y as i32), 146 | (p2.x as i32, p2.y as i32), 147 | color, 148 | interpolate, 149 | ); 150 | } 151 | } 152 | } 153 | 154 | Ok(img) 155 | } 156 | 157 | /// Run a shell command, pass it certain values and read the output. 158 | /// 159 | /// The shell command can be anything that can be executed with your platforms shell. The process 160 | /// gets passed three lines to stdin, of which these are: 161 | /// - the three rgb color values of the color the program would use by default for the triangle 162 | /// - the coordinates of the three points of the triangles 163 | /// - the width and height of the image 164 | /// 165 | /// The program is supposed to print out three space-separated rgb color values (integers between 0 166 | /// and 255 inclusively) on a single line. Everything else will be considered an error. 167 | /// 168 | /// *THIS WILL CHANGE IN FUTURE VERSIONS.* Spawning hundreds or thousand of processes is slow and a 169 | /// future version will most likely spawn a single process that gets passed the same data in a 170 | /// different way. 171 | fn get_color_from_command( 172 | cmd: &str, 173 | default_color: image::Rgb, 174 | triangle_coords: &[TriangulationPoint; 3], 175 | img_size: (u32, u32), 176 | ) -> LowPolyResult> { 177 | let mut proc = if cfg!(target_os = "windows") { 178 | let mut tmp = Command::new("cmd"); 179 | tmp.args(&["/C", cmd]); 180 | 181 | tmp 182 | } else { 183 | let mut tmp = Command::new("sh"); 184 | tmp.args(&["-c", cmd]); 185 | 186 | tmp 187 | }; 188 | 189 | proc.stdin(Stdio::piped()) 190 | .stdout(Stdio::piped()) 191 | .stderr(Stdio::piped()); 192 | let mut proc = proc.spawn().expect("could not spawn child process"); 193 | let stdin = proc.stdin.as_mut().expect("could not open process stdin"); 194 | let text = format!( 195 | "{} {} {}\n{} {} {} {} {} {}\n{} {}", 196 | default_color.0[0], 197 | default_color.0[1], 198 | default_color.0[2], 199 | triangle_coords[0].x, 200 | triangle_coords[0].y, 201 | triangle_coords[1].x, 202 | triangle_coords[1].y, 203 | triangle_coords[2].x, 204 | triangle_coords[2].y, 205 | img_size.0, 206 | img_size.1 207 | ); 208 | stdin.write_all(text.as_bytes())?; 209 | stdin.flush()?; 210 | 211 | let output = proc.wait_with_output()?; 212 | let output = String::from_utf8_lossy(&output.stdout); 213 | let line = output.lines().next().unwrap(); 214 | 215 | let nums = line 216 | .trim() 217 | .split_whitespace() 218 | .map(|s| s.parse::()) 219 | .collect::, _>>() 220 | .unwrap(); 221 | match *nums.as_slice() { 222 | [r, g, b] => Ok([r, g, b].into()), 223 | _ => unimplemented!(), 224 | } 225 | } 226 | 227 | /// Save the image to a file or stdout. 228 | /// 229 | /// If `opts` contains a filename that file will be written to (otherwise stdout will be used). 230 | /// This way the image file format used will be guessed from the filename. This can be 231 | /// overwritten by specifying an output type in the CLI options. If nothing is set or can be 232 | /// determined the PNG format is used. 233 | fn save_image(img: RgbImage, opts: &Options) -> LowPolyResult<()> { 234 | let output_format = opts 235 | .output_format 236 | .or_else(|| { 237 | opts.output 238 | .as_ref() 239 | .and_then(|out| ImageFormat::from_path(out).ok()) 240 | }) 241 | .unwrap_or(ImageFormat::Png); 242 | 243 | match &opts.output { 244 | Some(out) => img.save_with_format(out, output_format), 245 | None => DynamicImage::ImageRgb8(img).write_to(&mut stdout().lock(), output_format), 246 | }?; 247 | 248 | Ok(()) 249 | } 250 | 251 | /// Print the error message and exit in the case of an error 252 | fn simple_unwrap(res: Result, action: &str) -> T { 253 | match res { 254 | Ok(val) => val, 255 | Err(err) => { 256 | eprintln!("during {} an error occured: {}", action, err); 257 | exit(1); 258 | } 259 | } 260 | } 261 | 262 | /// start cubism 263 | fn main() { 264 | let opts = Options::from_args(); 265 | 266 | let image = simple_unwrap(load_image(&opts), "loading the image"); 267 | let points = simple_unwrap(edge_points(&image, &opts), "calculating the edges"); 268 | let image = image.to_rgb(); 269 | 270 | let triangles = triangulate(&points).unwrap(); 271 | 272 | let img = simple_unwrap(create_low_poly(&image, &points, &triangles, &opts), "creating the low-poly image"); 273 | simple_unwrap(save_image(img, &opts), "saving the image"); 274 | } 275 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "ab_glyph_rasterizer" 5 | version = "0.1.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "2b7e4e8cf778db814365e46839949ca74df4efb10e87ba4913e6ec5967ef0285" 8 | 9 | [[package]] 10 | name = "adler32" 11 | version = "1.2.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 14 | 15 | [[package]] 16 | name = "ansi_term" 17 | version = "0.11.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 20 | dependencies = [ 21 | "winapi", 22 | ] 23 | 24 | [[package]] 25 | name = "atty" 26 | version = "0.2.14" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 29 | dependencies = [ 30 | "hermit-abi", 31 | "libc", 32 | "winapi", 33 | ] 34 | 35 | [[package]] 36 | name = "autocfg" 37 | version = "1.0.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 40 | 41 | [[package]] 42 | name = "bitflags" 43 | version = "1.2.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 46 | 47 | [[package]] 48 | name = "bytemuck" 49 | version = "1.4.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "92046dbb6f9332943252123f53623e0a6d513651af14967e2991c371ec20201c" 52 | 53 | [[package]] 54 | name = "byteorder" 55 | version = "1.3.4" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 58 | 59 | [[package]] 60 | name = "cfg-if" 61 | version = "0.1.10" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 64 | 65 | [[package]] 66 | name = "clap" 67 | version = "2.33.3" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 70 | dependencies = [ 71 | "ansi_term", 72 | "atty", 73 | "bitflags", 74 | "strsim", 75 | "textwrap", 76 | "unicode-width", 77 | "vec_map", 78 | ] 79 | 80 | [[package]] 81 | name = "color_quant" 82 | version = "1.0.1" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" 85 | 86 | [[package]] 87 | name = "conv" 88 | version = "0.3.3" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" 91 | dependencies = [ 92 | "custom_derive", 93 | ] 94 | 95 | [[package]] 96 | name = "crc32fast" 97 | version = "1.2.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 100 | dependencies = [ 101 | "cfg-if", 102 | ] 103 | 104 | [[package]] 105 | name = "crossbeam-channel" 106 | version = "0.4.3" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6" 109 | dependencies = [ 110 | "cfg-if", 111 | "crossbeam-utils", 112 | ] 113 | 114 | [[package]] 115 | name = "crossbeam-deque" 116 | version = "0.7.3" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 119 | dependencies = [ 120 | "crossbeam-epoch", 121 | "crossbeam-utils", 122 | "maybe-uninit", 123 | ] 124 | 125 | [[package]] 126 | name = "crossbeam-epoch" 127 | version = "0.8.2" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 130 | dependencies = [ 131 | "autocfg", 132 | "cfg-if", 133 | "crossbeam-utils", 134 | "lazy_static", 135 | "maybe-uninit", 136 | "memoffset", 137 | "scopeguard", 138 | ] 139 | 140 | [[package]] 141 | name = "crossbeam-utils" 142 | version = "0.7.2" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 145 | dependencies = [ 146 | "autocfg", 147 | "cfg-if", 148 | "lazy_static", 149 | ] 150 | 151 | [[package]] 152 | name = "custom_derive" 153 | version = "0.1.7" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" 156 | 157 | [[package]] 158 | name = "deflate" 159 | version = "0.8.6" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" 162 | dependencies = [ 163 | "adler32", 164 | "byteorder", 165 | ] 166 | 167 | [[package]] 168 | name = "either" 169 | version = "1.6.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" 172 | 173 | [[package]] 174 | name = "getrandom" 175 | version = "0.1.14" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 178 | dependencies = [ 179 | "cfg-if", 180 | "libc", 181 | "wasi", 182 | ] 183 | 184 | [[package]] 185 | name = "gif" 186 | version = "0.10.3" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "471d90201b3b223f3451cd4ad53e34295f16a1df17b1edf3736d47761c3981af" 189 | dependencies = [ 190 | "color_quant", 191 | "lzw", 192 | ] 193 | 194 | [[package]] 195 | name = "heck" 196 | version = "0.3.1" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 199 | dependencies = [ 200 | "unicode-segmentation", 201 | ] 202 | 203 | [[package]] 204 | name = "hermit-abi" 205 | version = "0.1.15" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" 208 | dependencies = [ 209 | "libc", 210 | ] 211 | 212 | [[package]] 213 | name = "image" 214 | version = "0.23.8" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "543904170510c1b5fb65140485d84de4a57fddb2ed685481e9020ce3d2c9f64c" 217 | dependencies = [ 218 | "bytemuck", 219 | "byteorder", 220 | "gif", 221 | "jpeg-decoder", 222 | "num-iter", 223 | "num-rational", 224 | "num-traits", 225 | "png", 226 | "scoped_threadpool", 227 | "tiff", 228 | ] 229 | 230 | [[package]] 231 | name = "imageproc" 232 | version = "0.21.0" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "7b0fbd0ced24e3bc65052406fa6466203fe9c8d1990a3327567433e47109ed1a" 235 | dependencies = [ 236 | "conv", 237 | "image", 238 | "itertools", 239 | "num 0.3.0", 240 | "rand", 241 | "rand_distr", 242 | "rayon", 243 | "rulinalg", 244 | "rusttype", 245 | ] 246 | 247 | [[package]] 248 | name = "itertools" 249 | version = "0.9.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 252 | dependencies = [ 253 | "either", 254 | ] 255 | 256 | [[package]] 257 | name = "jpeg-decoder" 258 | version = "0.1.20" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "cc797adac5f083b8ff0ca6f6294a999393d76e197c36488e2ef732c4715f6fa3" 261 | dependencies = [ 262 | "byteorder", 263 | "rayon", 264 | ] 265 | 266 | [[package]] 267 | name = "kantig" 268 | version = "0.1.0" 269 | dependencies = [ 270 | "image", 271 | "imageproc", 272 | "rand", 273 | "rtriangulate", 274 | "structopt", 275 | "thiserror", 276 | ] 277 | 278 | [[package]] 279 | name = "lazy_static" 280 | version = "1.4.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 283 | 284 | [[package]] 285 | name = "libc" 286 | version = "0.2.76" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" 289 | 290 | [[package]] 291 | name = "lzw" 292 | version = "0.10.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" 295 | 296 | [[package]] 297 | name = "matrixmultiply" 298 | version = "0.1.15" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002" 301 | dependencies = [ 302 | "rawpointer", 303 | ] 304 | 305 | [[package]] 306 | name = "maybe-uninit" 307 | version = "2.0.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 310 | 311 | [[package]] 312 | name = "memoffset" 313 | version = "0.5.5" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" 316 | dependencies = [ 317 | "autocfg", 318 | ] 319 | 320 | [[package]] 321 | name = "miniz_oxide" 322 | version = "0.3.7" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 325 | dependencies = [ 326 | "adler32", 327 | ] 328 | 329 | [[package]] 330 | name = "num" 331 | version = "0.1.42" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" 334 | dependencies = [ 335 | "num-integer", 336 | "num-iter", 337 | "num-traits", 338 | ] 339 | 340 | [[package]] 341 | name = "num" 342 | version = "0.3.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "ab3e176191bc4faad357e3122c4747aa098ac880e88b168f106386128736cf4a" 345 | dependencies = [ 346 | "num-bigint", 347 | "num-complex", 348 | "num-integer", 349 | "num-iter", 350 | "num-rational", 351 | "num-traits", 352 | ] 353 | 354 | [[package]] 355 | name = "num-bigint" 356 | version = "0.3.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "b7f3fc75e3697059fb1bc465e3d8cca6cf92f56854f201158b3f9c77d5a3cfa0" 359 | dependencies = [ 360 | "autocfg", 361 | "num-integer", 362 | "num-traits", 363 | ] 364 | 365 | [[package]] 366 | name = "num-complex" 367 | version = "0.3.0" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "b05ad05bd8977050b171b3f6b48175fea6e0565b7981059b486075e1026a9fb5" 370 | dependencies = [ 371 | "num-traits", 372 | ] 373 | 374 | [[package]] 375 | name = "num-integer" 376 | version = "0.1.43" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 379 | dependencies = [ 380 | "autocfg", 381 | "num-traits", 382 | ] 383 | 384 | [[package]] 385 | name = "num-iter" 386 | version = "0.1.41" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" 389 | dependencies = [ 390 | "autocfg", 391 | "num-integer", 392 | "num-traits", 393 | ] 394 | 395 | [[package]] 396 | name = "num-rational" 397 | version = "0.3.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" 400 | dependencies = [ 401 | "autocfg", 402 | "num-bigint", 403 | "num-integer", 404 | "num-traits", 405 | ] 406 | 407 | [[package]] 408 | name = "num-traits" 409 | version = "0.2.12" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 412 | dependencies = [ 413 | "autocfg", 414 | ] 415 | 416 | [[package]] 417 | name = "num_cpus" 418 | version = "1.13.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 421 | dependencies = [ 422 | "hermit-abi", 423 | "libc", 424 | ] 425 | 426 | [[package]] 427 | name = "owned_ttf_parser" 428 | version = "0.6.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" 431 | dependencies = [ 432 | "ttf-parser", 433 | ] 434 | 435 | [[package]] 436 | name = "png" 437 | version = "0.16.7" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "dfe7f9f1c730833200b134370e1d5098964231af8450bce9b78ee3ab5278b970" 440 | dependencies = [ 441 | "bitflags", 442 | "crc32fast", 443 | "deflate", 444 | "miniz_oxide", 445 | ] 446 | 447 | [[package]] 448 | name = "ppv-lite86" 449 | version = "0.2.9" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" 452 | 453 | [[package]] 454 | name = "proc-macro-error" 455 | version = "1.0.4" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 458 | dependencies = [ 459 | "proc-macro-error-attr", 460 | "proc-macro2", 461 | "quote", 462 | "syn", 463 | "version_check", 464 | ] 465 | 466 | [[package]] 467 | name = "proc-macro-error-attr" 468 | version = "1.0.4" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 471 | dependencies = [ 472 | "proc-macro2", 473 | "quote", 474 | "version_check", 475 | ] 476 | 477 | [[package]] 478 | name = "proc-macro2" 479 | version = "1.0.19" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" 482 | dependencies = [ 483 | "unicode-xid", 484 | ] 485 | 486 | [[package]] 487 | name = "quote" 488 | version = "1.0.7" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 491 | dependencies = [ 492 | "proc-macro2", 493 | ] 494 | 495 | [[package]] 496 | name = "rand" 497 | version = "0.7.3" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 500 | dependencies = [ 501 | "getrandom", 502 | "libc", 503 | "rand_chacha", 504 | "rand_core", 505 | "rand_hc", 506 | "rand_pcg", 507 | ] 508 | 509 | [[package]] 510 | name = "rand_chacha" 511 | version = "0.2.2" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 514 | dependencies = [ 515 | "ppv-lite86", 516 | "rand_core", 517 | ] 518 | 519 | [[package]] 520 | name = "rand_core" 521 | version = "0.5.1" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 524 | dependencies = [ 525 | "getrandom", 526 | ] 527 | 528 | [[package]] 529 | name = "rand_distr" 530 | version = "0.2.2" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" 533 | dependencies = [ 534 | "rand", 535 | ] 536 | 537 | [[package]] 538 | name = "rand_hc" 539 | version = "0.2.0" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 542 | dependencies = [ 543 | "rand_core", 544 | ] 545 | 546 | [[package]] 547 | name = "rand_pcg" 548 | version = "0.2.1" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" 551 | dependencies = [ 552 | "rand_core", 553 | ] 554 | 555 | [[package]] 556 | name = "rawpointer" 557 | version = "0.1.0" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019" 560 | 561 | [[package]] 562 | name = "rayon" 563 | version = "1.4.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270" 566 | dependencies = [ 567 | "autocfg", 568 | "crossbeam-deque", 569 | "either", 570 | "rayon-core", 571 | ] 572 | 573 | [[package]] 574 | name = "rayon-core" 575 | version = "1.8.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "91739a34c4355b5434ce54c9086c5895604a9c278586d1f1aa95e04f66b525a0" 578 | dependencies = [ 579 | "crossbeam-channel", 580 | "crossbeam-deque", 581 | "crossbeam-utils", 582 | "lazy_static", 583 | "num_cpus", 584 | ] 585 | 586 | [[package]] 587 | name = "rtriangulate" 588 | version = "0.3.1" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "c737bd8a6b5c105ef1ef2db5af46b7e69d8fb03c6213d64973c551f5ff5c1697" 591 | dependencies = [ 592 | "num-traits", 593 | ] 594 | 595 | [[package]] 596 | name = "rulinalg" 597 | version = "0.4.2" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "04ada202c9685e1d72a7420c578e92b358dbf807d3dfabb676a3dab9cc3bb12f" 600 | dependencies = [ 601 | "matrixmultiply", 602 | "num 0.1.42", 603 | ] 604 | 605 | [[package]] 606 | name = "rusttype" 607 | version = "0.9.2" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" 610 | dependencies = [ 611 | "ab_glyph_rasterizer", 612 | "owned_ttf_parser", 613 | ] 614 | 615 | [[package]] 616 | name = "scoped_threadpool" 617 | version = "0.1.9" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 620 | 621 | [[package]] 622 | name = "scopeguard" 623 | version = "1.1.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 626 | 627 | [[package]] 628 | name = "strsim" 629 | version = "0.8.0" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 632 | 633 | [[package]] 634 | name = "structopt" 635 | version = "0.3.17" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "6cc388d94ffabf39b5ed5fadddc40147cb21e605f53db6f8f36a625d27489ac5" 638 | dependencies = [ 639 | "clap", 640 | "lazy_static", 641 | "structopt-derive", 642 | ] 643 | 644 | [[package]] 645 | name = "structopt-derive" 646 | version = "0.4.10" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "5e2513111825077552a6751dfad9e11ce0fba07d7276a3943a037d7e93e64c5f" 649 | dependencies = [ 650 | "heck", 651 | "proc-macro-error", 652 | "proc-macro2", 653 | "quote", 654 | "syn", 655 | ] 656 | 657 | [[package]] 658 | name = "syn" 659 | version = "1.0.39" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" 662 | dependencies = [ 663 | "proc-macro2", 664 | "quote", 665 | "unicode-xid", 666 | ] 667 | 668 | [[package]] 669 | name = "textwrap" 670 | version = "0.11.0" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 673 | dependencies = [ 674 | "unicode-width", 675 | ] 676 | 677 | [[package]] 678 | name = "thiserror" 679 | version = "1.0.20" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" 682 | dependencies = [ 683 | "thiserror-impl", 684 | ] 685 | 686 | [[package]] 687 | name = "thiserror-impl" 688 | version = "1.0.20" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" 691 | dependencies = [ 692 | "proc-macro2", 693 | "quote", 694 | "syn", 695 | ] 696 | 697 | [[package]] 698 | name = "tiff" 699 | version = "0.5.0" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f" 702 | dependencies = [ 703 | "byteorder", 704 | "lzw", 705 | "miniz_oxide", 706 | ] 707 | 708 | [[package]] 709 | name = "ttf-parser" 710 | version = "0.6.2" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" 713 | 714 | [[package]] 715 | name = "unicode-segmentation" 716 | version = "1.6.0" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 719 | 720 | [[package]] 721 | name = "unicode-width" 722 | version = "0.1.8" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 725 | 726 | [[package]] 727 | name = "unicode-xid" 728 | version = "0.2.1" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 731 | 732 | [[package]] 733 | name = "vec_map" 734 | version = "0.8.2" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 737 | 738 | [[package]] 739 | name = "version_check" 740 | version = "0.9.2" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 743 | 744 | [[package]] 745 | name = "wasi" 746 | version = "0.9.0+wasi-snapshot-preview1" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 749 | 750 | [[package]] 751 | name = "winapi" 752 | version = "0.3.9" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 755 | dependencies = [ 756 | "winapi-i686-pc-windows-gnu", 757 | "winapi-x86_64-pc-windows-gnu", 758 | ] 759 | 760 | [[package]] 761 | name = "winapi-i686-pc-windows-gnu" 762 | version = "0.4.0" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 765 | 766 | [[package]] 767 | name = "winapi-x86_64-pc-windows-gnu" 768 | version = "0.4.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 771 | --------------------------------------------------------------------------------