├── .gitignore ├── tests ├── test_files │ ├── scripts │ │ ├── color_scale.iq │ │ ├── identity.iq │ │ ├── sobel_edge_detection.iq │ │ └── nested_match_and_alpha.iq │ └── images │ │ ├── dalle_logo.png │ │ ├── dalle_ranch_testifying_in_court.png │ │ └── dalle_philip_seymour_in_cars_movie.png └── test_expressions.rs ├── assets ├── dalle_logo.png └── examples │ ├── dalle_logo.png │ ├── ex1_dalle_red.jpg │ ├── ex3_rotate_ranch.jpg │ ├── ex2_circle_seymour.jpg │ ├── ex2_cropped_seymour.jpg │ ├── ex2_highlight_mcqueen.jpg │ ├── ex4_circle_edge_range.jpg │ ├── dalle_ranch_testifying_in_court.jpg │ └── dalle_philip_seymour_in_cars_movie.jpg ├── Cargo.toml ├── src ├── lib.rs ├── attrs.rs ├── main.rs ├── ast.rs ├── float_ops.rs ├── iqparser.lalrpop ├── ctx_ops.rs ├── context.rs └── eval.rs ├── LICENSE.txt ├── README.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /prototype 3 | -------------------------------------------------------------------------------- /tests/test_files/scripts/color_scale.iq: -------------------------------------------------------------------------------- 1 | _ => color_scale(_, 0.0) 2 | -------------------------------------------------------------------------------- /tests/test_files/scripts/identity.iq: -------------------------------------------------------------------------------- 1 | _ => p(_.y, _.x, _.r, _.g, _.b) -------------------------------------------------------------------------------- /assets/dalle_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/dalle_logo.png -------------------------------------------------------------------------------- /assets/examples/dalle_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/examples/dalle_logo.png -------------------------------------------------------------------------------- /assets/examples/ex1_dalle_red.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/examples/ex1_dalle_red.jpg -------------------------------------------------------------------------------- /assets/examples/ex3_rotate_ranch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/examples/ex3_rotate_ranch.jpg -------------------------------------------------------------------------------- /assets/examples/ex2_circle_seymour.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/examples/ex2_circle_seymour.jpg -------------------------------------------------------------------------------- /assets/examples/ex2_cropped_seymour.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/examples/ex2_cropped_seymour.jpg -------------------------------------------------------------------------------- /tests/test_files/images/dalle_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/tests/test_files/images/dalle_logo.png -------------------------------------------------------------------------------- /assets/examples/ex2_highlight_mcqueen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/examples/ex2_highlight_mcqueen.jpg -------------------------------------------------------------------------------- /assets/examples/ex4_circle_edge_range.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/examples/ex4_circle_edge_range.jpg -------------------------------------------------------------------------------- /assets/examples/dalle_ranch_testifying_in_court.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/examples/dalle_ranch_testifying_in_court.jpg -------------------------------------------------------------------------------- /assets/examples/dalle_philip_seymour_in_cars_movie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/assets/examples/dalle_philip_seymour_in_cars_movie.jpg -------------------------------------------------------------------------------- /tests/test_files/images/dalle_ranch_testifying_in_court.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/tests/test_files/images/dalle_ranch_testifying_in_court.png -------------------------------------------------------------------------------- /tests/test_files/images/dalle_philip_seymour_in_cars_movie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelgiba/iq/HEAD/tests/test_files/images/dalle_philip_seymour_in_cars_movie.png -------------------------------------------------------------------------------- /tests/test_files/scripts/sobel_edge_detection.iq: -------------------------------------------------------------------------------- 1 | _ => color_norm(color_add( 2 | color_scale(neighbors(_, -1, -1), -1.0), 3 | color_scale(neighbors(_, 0, -1), -2.0), 4 | color_scale(neighbors(_, 1, -1), -1.0), 5 | color_scale(neighbors(_, 1, -1), 1.0), 6 | color_scale(neighbors(_, 1, 0), 2.0), 7 | color_scale(neighbors(_, 1, 1), 1.0) 8 | )) -------------------------------------------------------------------------------- /tests/test_files/scripts/nested_match_and_alpha.iq: -------------------------------------------------------------------------------- 1 | 200 >= sqrt(sq(_.x - center().x) + sq(_.y - center().y)) => 2 | ( 3 | 50 >= sqrt(sq(_.x - center().x) + sq(_.y - center().y)) => 4 | p(_.y, _.x, 255, 0, 0) : 5 | p(_.y, _.x, 255, 255, 255) 6 | ) : p(_.y, _.x, 0, 0, 255) | _ => alpha_blend(_, 0.2); 7 | _ => alpha_blend(_, 0.8); 8 | 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "iq" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["michaelgiba@gmail.com"] 6 | 7 | [build-dependencies.lalrpop] 8 | version = "0.19.8" 9 | features = ["lexer"] 10 | 11 | [dependencies] 12 | structopt = "0.3.21" 13 | anyhow = "1.0" 14 | lalrpop-util = { version = "0.19.8", features = ["lexer"] } 15 | regex = "1" 16 | image = "0.24.3" 17 | clap = { version = "3.2.20", features = ["cargo"] } 18 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::IqAstRootNode; 2 | use crate::eval::Evalulate; 3 | 4 | #[macro_use] 5 | extern crate lalrpop_util; 6 | 7 | lalrpop_mod!(#[allow(clippy::all)] pub iqparser); 8 | 9 | #[allow(clippy::large_enum_variant)] 10 | mod ast; 11 | mod attrs; 12 | pub mod context; 13 | mod ctx_ops; 14 | mod eval; 15 | mod float_ops; 16 | 17 | pub fn execute(input_ctx: context::BasicContext, expressions: String) -> context::BasicContext { 18 | let root: IqAstRootNode = iqparser::IqRootParser::new() 19 | .parse(expressions.as_str()) 20 | .unwrap(); 21 | 22 | root.eval(&input_ctx) 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2022 Michael Giba 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/attrs.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | 3 | pub fn access_scalar_annotated_ctx_attr( 4 | ctx: &AnnotatedPixelContext, 5 | attr: &String, 6 | ) -> AnnotatedFloatContext { 7 | if attr.eq_ignore_ascii_case("y") { 8 | return AnnotatedFloatContext::from_iter_with_annotation( 9 | ctx.iter_annotations(), 10 | |(pixel, annot)| (pixel.clone(), annot.y as f64), 11 | ); 12 | } else if attr.eq_ignore_ascii_case("x") { 13 | return AnnotatedFloatContext::from_iter_with_annotation( 14 | ctx.iter_annotations(), 15 | |(pixel, annot)| (pixel.clone(), annot.x as f64), 16 | ); 17 | } 18 | for (i, x) in ["r", "g", "b", "a"].iter().enumerate() { 19 | if attr.eq_ignore_ascii_case(x) { 20 | return AnnotatedFloatContext::from_iter_with_annotation( 21 | ctx.iter_annotations(), 22 | |(pixel, annot)| (pixel.clone(), annot.c[i] as f64), 23 | ); 24 | } 25 | } 26 | panic!("Unknown attribute: {:?}", attr) 27 | } 28 | 29 | pub fn access_scalar_attr(ctx: &Context, attr: &String) -> f64 { 30 | if attr.eq_ignore_ascii_case("h") { 31 | ctx.height() as f64 32 | } else if attr.eq_ignore_ascii_case("w") { 33 | ctx.width() as f64 34 | } else { 35 | panic!("Unknown attribute: {:?}", attr) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/test_expressions.rs: -------------------------------------------------------------------------------- 1 | use iq::context::BasicContext; 2 | use std::fs; 3 | use std::path::{Path, PathBuf}; 4 | 5 | fn test_file_path(rel_path: &str) -> PathBuf { 6 | let root = env!("CARGO_MANIFEST_DIR"); 7 | Path::new(root).join("tests/test_files").join(rel_path) 8 | } 9 | 10 | fn test_file_contents(rel_path: &str) -> String { 11 | fs::read_to_string(test_file_path(rel_path)).unwrap() 12 | } 13 | 14 | #[test] 15 | fn handles_empty_input() { 16 | assert_eq!( 17 | BasicContext::empty(), 18 | iq::execute(BasicContext::empty(), String::from("")) 19 | ); 20 | } 21 | 22 | #[test] 23 | fn handles_identity() { 24 | assert_eq!( 25 | BasicContext::empty(), 26 | iq::execute(BasicContext::empty(), String::from("_ => _")) 27 | ); 28 | assert_eq!( 29 | BasicContext::blank(10, 10), 30 | iq::execute(BasicContext::blank(10, 10), String::from("_ => _")) 31 | ); 32 | assert_eq!( 33 | BasicContext::blank(10, 10), 34 | iq::execute( 35 | BasicContext::blank(10, 10), 36 | String::from("_ => p(_.y, _.x, _.r, _.g, _.b)") 37 | ) 38 | ); 39 | assert_eq!( 40 | BasicContext::blank(10, 10), 41 | iq::execute( 42 | BasicContext::blank(10, 10), 43 | test_file_contents("scripts/identity.iq") 44 | ) 45 | ); 46 | } 47 | 48 | #[test] 49 | fn handles_context_ops() { 50 | assert_eq!( 51 | BasicContext::blank_with_default(10, 10, [0, 0, 0, 255]), 52 | iq::execute( 53 | BasicContext::blank_with_default(10, 10, [255, 255, 255, 255]), 54 | test_file_contents("scripts/color_scale.iq") 55 | ) 56 | ); 57 | 58 | // Just to check sobel doesn't crash. 59 | let output_ctx = iq::execute( 60 | BasicContext::blank_with_default(10, 10, [255, 255, 255, 255]), 61 | test_file_contents("scripts/sobel_edge_detection.iq"), 62 | ); 63 | 64 | assert_eq!(output_ctx.clone(), output_ctx); 65 | } 66 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{AppSettings, Arg}; 2 | use iq::context::BasicContext; 3 | use regex::Regex; 4 | use std::fs; 5 | 6 | fn main() { 7 | let matches = clap::command!("iq") 8 | .setting(AppSettings::AllowMissingPositional) 9 | .arg( 10 | Arg::with_name("blank") 11 | .short('b') 12 | .long("blank") 13 | .takes_value(true) 14 | .value_name("dimensions") 15 | .help("Use a blank canvas of provided size 'HxW' (ex. '100x300')"), 16 | ) 17 | .arg( 18 | Arg::with_name("file") 19 | .short('f') 20 | .long("file") 21 | .takes_value(true) 22 | .help("Pass a file containing expressions to run"), 23 | ) 24 | .arg( 25 | Arg::with_name("expressions") 26 | .short('e') 27 | .long("expr") 28 | .takes_value(true) 29 | .help("The expressions to evaluate"), 30 | ) 31 | .arg(Arg::with_name("input_path").help("The path to the input image")) 32 | .arg(Arg::with_name("output_path").help("Where to write the output image")) 33 | .get_matches(); 34 | 35 | let input_context = match matches.value_of("blank") { 36 | Some(blank_dimensions_string) => { 37 | if matches.value_of("input_path").is_none() { 38 | panic!("Either 'blank' OR an input path should be provided. Not both.") 39 | } 40 | 41 | let re = Regex::new(r"(\d+)x(\d+)").unwrap(); 42 | let captures = re 43 | .captures(blank_dimensions_string) 44 | .expect("blank input should be HxW"); 45 | let height = captures.get(1).unwrap().as_str(); 46 | let width = captures.get(2).unwrap().as_str(); 47 | 48 | BasicContext::blank(height.parse().unwrap(), width.parse().unwrap()) 49 | } 50 | None => BasicContext::from_path( 51 | matches 52 | .value_of("input_path") 53 | .expect("Either 'blank' should be specified or an input path"), 54 | ), 55 | }; 56 | 57 | let script_content = 58 | match matches.value_of("file") { 59 | Some(file_path) => { 60 | fs::read_to_string(file_path).expect("Provided file path cannot be read") 61 | } 62 | None => String::from(matches.value_of("expressions").expect( 63 | "Expressions must be passed as string via --expr or via the --file parameter", 64 | )), 65 | }; 66 | 67 | let context = iq::execute(input_context, script_content); 68 | if let Some(output_path) = matches.value_of("output_path") { 69 | context.write(output_path); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ast.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::option::Option; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct AttrAccessNode { 6 | pub key: String, 7 | } 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct SelectorScalarNode { 11 | pub selector_ctx: SelectorCtxNode, 12 | pub accessed_attr: AttrAccessNode, 13 | } 14 | 15 | #[derive(Debug, Clone)] 16 | pub enum ScalarNode { 17 | Float(f64), 18 | Integer(i64), 19 | SelectorScalar(SelectorScalarNode), 20 | PixelScalar(Box, AttrAccessNode), 21 | } 22 | 23 | #[derive(Debug, Clone)] 24 | pub enum BinaryOpType { 25 | Add(), 26 | Sub(), 27 | Div(), 28 | Mul(), 29 | } 30 | 31 | #[derive(Debug, Clone)] 32 | pub enum MatchOpType { 33 | Lt(), 34 | Lte(), 35 | Gt(), 36 | Gte(), 37 | Eq(), 38 | Neq(), 39 | } 40 | 41 | #[derive(Debug, Clone)] 42 | pub struct PixelNode { 43 | pub y_expr: ScalarExprNode, 44 | pub x_expr: ScalarExprNode, 45 | pub r_expr: ScalarExprNode, 46 | pub g_expr: ScalarExprNode, 47 | pub b_expr: ScalarExprNode, 48 | pub a_expr: ScalarExprNode, 49 | } 50 | 51 | #[derive(Debug, Clone)] 52 | pub enum PixelExprType { 53 | Explicit(PixelNode), 54 | CurrentPixel(), 55 | FnCall(PixelFnCall), 56 | } 57 | 58 | #[derive(Debug, Clone)] 59 | pub enum MatchComparisonValue { 60 | Scalar(ScalarExprNode), 61 | Pixel(PixelExprType), 62 | } 63 | 64 | #[derive(Debug, Clone)] 65 | pub struct MatchComparatorNode { 66 | pub op_type: MatchOpType, 67 | pub cmp_val: MatchComparisonValue, 68 | } 69 | 70 | #[derive(Debug, Clone)] 71 | pub enum MatchReturnValue { 72 | Pixel(PixelExprType), 73 | Operator(OperatorNode), 74 | } 75 | 76 | #[derive(Debug, Clone)] 77 | pub struct MatchExprOpNode { 78 | pub match_value: MatchComparisonValue, 79 | pub match_comparator_node: Option, 80 | pub match_return_value_node: Box, 81 | pub else_return_value_node: Option>, 82 | } 83 | 84 | #[derive(Debug, Clone)] 85 | pub enum OperatorNode { 86 | UnaryNegationOp(), 87 | MatchExprOp(MatchExprOpNode), 88 | } 89 | 90 | #[derive(Debug, Clone)] 91 | pub struct BinaryScalarOpNode { 92 | pub lhs: ScalarExprNode, 93 | pub op: BinaryOpType, 94 | pub rhs: ScalarExprNode, 95 | } 96 | 97 | #[derive(Debug, Clone)] 98 | pub enum ScalarFnOp { 99 | Min(), 100 | Max(), 101 | Square(), 102 | Sqrt(), 103 | } 104 | 105 | #[derive(Debug, Clone)] 106 | pub enum PixelFnOp { 107 | Center(), 108 | Neighbors(i64, i64), 109 | ColorScale(f64), 110 | ColorAdd(), 111 | ColorNorm(), 112 | AlphaBlend(f64), 113 | } 114 | 115 | #[derive(Debug, Clone)] 116 | pub struct PixelFnCall { 117 | pub op: PixelFnOp, 118 | pub args: Vec, 119 | } 120 | 121 | #[derive(Debug, Clone)] 122 | pub struct ScalarFnCall { 123 | pub op: ScalarFnOp, 124 | pub args: Vec, 125 | } 126 | 127 | #[derive(Debug, Clone)] 128 | pub enum ScalarExprNode { 129 | ScalarFn(ScalarFnCall), 130 | SubExpr(Box), 131 | Scalar(ScalarNode), 132 | BinaryOp(Box), 133 | } 134 | 135 | #[derive(Debug, Clone)] 136 | pub struct SliceRangeNode { 137 | pub lower_bound: Option, 138 | pub upper_bound: Option, 139 | } 140 | 141 | #[derive(Debug, Clone)] 142 | pub struct SelectorCtxNode { 143 | pub y_slice_range: Option>, 144 | pub x_slice_range: Option>, 145 | } 146 | 147 | #[derive(Debug, Clone)] 148 | pub struct ExprNode { 149 | pub selector_ctx: Option, 150 | pub op_nodes: Vec, 151 | } 152 | 153 | #[derive(Debug, Clone)] 154 | pub struct IqAstRootNode { 155 | pub exprs: Vec, 156 | } 157 | -------------------------------------------------------------------------------- /src/float_ops.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | 3 | fn are_compatible_contexts(a: &Context, b: &Context) -> bool { 4 | a.x_bounds() == b.x_bounds() && a.y_bounds() == b.y_bounds() && a.count() == b.count() 5 | } 6 | 7 | fn assert_compatible_contexts(a: &Context, b: &Context) { 8 | if !are_compatible_contexts(a, b) { 9 | panic!( 10 | "Incompatible contexts: a= {:} b = {:}", 11 | a.describe(), 12 | b.describe() 13 | ) 14 | } 15 | } 16 | 17 | fn min2(a: &AnnotatedFloatContext, b: &AnnotatedFloatContext) -> AnnotatedFloatContext { 18 | AnnotatedFloatContext::from_iter_with_annotation(a.iter_annotations(), |(pixel, a_annot)| { 19 | let b_annot = b.get_annotation(pixel).unwrap(); 20 | ( 21 | pixel.clone(), 22 | if a_annot < b_annot { 23 | *a_annot 24 | } else { 25 | *b_annot 26 | }, 27 | ) 28 | }) 29 | } 30 | 31 | pub fn min(args: &Vec) -> AnnotatedFloatContext { 32 | if args.is_empty() { 33 | AnnotatedFloatContext::empty() 34 | } else { 35 | args.iter() 36 | .cloned() 37 | .reduce(|accum, item| min2(&accum, &item)) 38 | .unwrap() 39 | } 40 | } 41 | 42 | fn max2(a: &AnnotatedFloatContext, b: &AnnotatedFloatContext) -> AnnotatedFloatContext { 43 | assert_compatible_contexts(a, b); 44 | 45 | AnnotatedFloatContext::from_iter_with_annotation(a.iter_annotations(), |(pixel, a_annot)| { 46 | let b_annot = b.get_annotation(pixel).unwrap(); 47 | ( 48 | pixel.clone(), 49 | if a_annot > b_annot { 50 | *a_annot 51 | } else { 52 | *b_annot 53 | }, 54 | ) 55 | }) 56 | } 57 | 58 | pub fn max(args: &Vec) -> AnnotatedFloatContext { 59 | if args.is_empty() { 60 | AnnotatedFloatContext::empty() 61 | } else { 62 | args.iter() 63 | .cloned() 64 | .reduce(|accum, item| max2(&accum, &item)) 65 | .unwrap() 66 | } 67 | } 68 | 69 | pub fn square(arg: &AnnotatedFloatContext) -> AnnotatedFloatContext { 70 | AnnotatedFloatContext::from_iter_with_annotation(arg.iter_annotations(), |(pixel, annot)| { 71 | (pixel.clone(), annot.powi(2)) 72 | }) 73 | } 74 | 75 | pub fn sqrt(arg: &AnnotatedFloatContext) -> AnnotatedFloatContext { 76 | AnnotatedFloatContext::from_iter_with_annotation(arg.iter_annotations(), |(pixel, annot)| { 77 | (pixel.clone(), annot.sqrt()) 78 | }) 79 | } 80 | 81 | pub fn add(a: &AnnotatedFloatContext, b: &AnnotatedFloatContext) -> AnnotatedFloatContext { 82 | assert_compatible_contexts(a, b); 83 | AnnotatedFloatContext::from_iter_with_annotation(a.iter_annotations(), |(pixel, a_annot)| { 84 | let b_annot = b.get_annotation(pixel).unwrap(); 85 | (pixel.clone(), a_annot + b_annot) 86 | }) 87 | } 88 | 89 | pub fn sub(l: &AnnotatedFloatContext, r: &AnnotatedFloatContext) -> AnnotatedFloatContext { 90 | assert_compatible_contexts(l, r); 91 | AnnotatedFloatContext::from_iter_with_annotation(l.iter_annotations(), |(pixel, l_annot)| { 92 | let r_annot = r.get_annotation(pixel).unwrap(); 93 | (pixel.clone(), l_annot - r_annot) 94 | }) 95 | } 96 | 97 | pub fn div(l: &AnnotatedFloatContext, r: &AnnotatedFloatContext) -> AnnotatedFloatContext { 98 | assert_compatible_contexts(l, r); 99 | AnnotatedFloatContext::from_iter_with_annotation(l.iter_annotations(), |(pixel, l_annot)| { 100 | let r_annot = r.get_annotation(pixel).unwrap(); 101 | (pixel.clone(), l_annot / r_annot) 102 | }) 103 | } 104 | 105 | pub fn mul(a: &AnnotatedFloatContext, b: &AnnotatedFloatContext) -> AnnotatedFloatContext { 106 | assert_compatible_contexts(a, b); 107 | AnnotatedFloatContext::from_iter_with_annotation(a.iter_annotations(), |(pixel, a_annot)| { 108 | let b_annot = b.get_annotation(pixel).unwrap(); 109 | (pixel.clone(), a_annot * b_annot) 110 | }) 111 | } 112 | 113 | pub fn negate(arg: &BasicContext) -> BasicContext { 114 | BasicContext::from_iter(arg.iter(), |pixel| pixel.negate()) 115 | } 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 |
7 | 8 | Logo 9 | 10 | 11 |

iq - a little tool for image manipulation

12 | 13 |

14 | Inspired by `jq`, iq is an experimental tool for manipulating images through expressions which transform pixels. 15 |

16 |
17 | 18 | ## Usage 19 | 20 | ``` 21 | iq 0.1.0 22 | michaelgiba@gmail.com 23 | 24 | USAGE: 25 | iq [OPTIONS] [ARGS] 26 | 27 | ARGS: 28 | The path to the input image 29 | Where to write the output image 30 | 31 | OPTIONS: 32 | -b, --blank Use a blank canvas of provided size 'HxW' (ex. '100x300') 33 | -e, --expr The expressions to evaluate 34 | -f, --file Pass a file containing expressions to run 35 | -h, --help Print help information 36 | -V, --version Print version information 37 | ``` 38 | 39 | 40 | ## Examples 41 | 42 | *Disclaimer:* The tool is a work in progress and in its very early stages. There are certainly bugs and plenty of things to improve. 43 | 44 | `iq` is passed an image and a set of expressions and produces an output image. For example: 45 | 46 | ``` 47 | iq -e "_ => p(_.y, _.x, _.r, 0, 0)" assets/dalle_logo.png out.jpg 48 | ``` 49 | 50 | Would produce an output image `out.jpg` with the green and blue color channels removed. 51 | 52 | 53 | Logo 54 | 55 | 56 | ### Slice Ranges 57 | 58 | Expressions can do much more complex actions than simply uniformly changing colors. For example given this DALLE generated image of Philip Seymour Hoffman in the Disney film "Cars": 59 | 60 | 61 | Logo 62 | 63 | 64 | We can crop patches of the image using "slice ranges": 65 | 66 | ``` 67 | # Crop out a circle and replace the outside with a gradient 68 | iq -e "[0:100, 0:100]" \ 69 | assets/examples/dalle_philip_seymour_in_cars_movie.jpg \ 70 | cropped_seymour.jpg 71 | ``` 72 | 73 | 74 | Logo 75 | 76 | 77 | ### Match Expressions 78 | 79 | We can use match expressions to apply other expressions conditionally 80 | 81 | ``` 82 | # Change any pixel with a green channel value greater than 128 to white 83 | iq -e "_.g > 128 => p(_.y, _.x, 255, 255, 255) : _" \ 84 | assets/examples/dalle_philip_seymour_in_cars_movie.jpg \ 85 | highlight_mcqueen.jpg 86 | ``` 87 | 88 | Logo 89 | 90 | 91 | ``` 92 | # Crop out a circle and replace the outside with a gradient 93 | iq -e " 94 | ([].w/2) >= sqrt(sq(_.x - center().x) + sq(_.y - center().y)) => 95 | p(_.y, _.x, _.r, 255 - _.r, _.r) : 96 | p(_.y, _.x, (_.r * _.y) / [].h, (_.g * _.x) / [].w, 0) 97 | " \ 98 | assets/examples/dalle_philip_seymour_in_cars_movie.jpg \ 99 | circle_seymour.jpg 100 | ``` 101 | 102 | Logo 103 | 104 | 105 | 106 | ### Layering 107 | 108 | If we provide more than one expression separated by semicolons they will be alpha composited together. For example consider this DALLE 109 | generated bottle of ranch testifying in court; 110 | 111 | 112 | Logo 113 | 114 | 115 | 116 | ``` 117 | # Superimpose a rotated version on the original; 118 | iq -e " 119 | _ => p(_.y, _.x, _.r, _.g, _.b, _.a * 0.5); 120 | _ => p(_.x, _.y, _.r, _.g, _.b, _.a * 0.5); 121 | " \ 122 | assets/examples/dalle_ranch_testifying_in_court.jpg \ 123 | rotate.jpg 124 | ``` 125 | 126 | Logo 127 | 128 | 129 | 130 | ### Pixel Functions 131 | 132 | There are a few builtin pixel functions like: 133 | - `color_norm` 134 | - `color_add` 135 | - `color_scale` 136 | - `neighbors` 137 | 138 | That when combined with other standard features can even do some convolutions like this sobel edge 139 | detection: 140 | 141 | ``` 142 | # Crop into a circle and do sobel edge detection 143 | iq -e " 144 | ([].w/2) >= sqrt(sq(_.x - center().x) + sq(_.y - center().y)) => color_norm(color_add( 145 | color_scale(neighbors(_, -1, -1), -1.0), 146 | color_scale(neighbors(_, 0, -1), -2.0), 147 | color_scale(neighbors(_, 1, -1), -1.0), 148 | color_scale(neighbors(_, 1, -1), 1.0), 149 | color_scale(neighbors(_, 1, 0), 2.0), 150 | color_scale(neighbors(_, 1, 1), 1.0) 151 | )); 152 | " \ 153 | assets/examples/dalle_ranch_testifying_in_court.jpg \ 154 | circle_edge_ranch.jpg 155 | ``` 156 | 157 | Logo 158 | 159 | 160 | 161 | ## Language Reference 162 | 163 | Coming soon... 164 | 165 | ## How it works 166 | 167 | `iq` is written in `rust` and uses LALRPOP for parser/lexer generation. 168 | 169 | 170 | ## Installation 171 | 172 | Right now the tool is only distributed as source. Clone the repo and do a standard `cargo build` 173 | 174 | ## Contributing 175 | 176 | All contributions are welcome! 177 | 178 | 179 | ## License 180 | 181 | Distributed under the MIT License. See `LICENSE.txt` for more information. 182 | 183 |

(back to top)

184 | 185 | -------------------------------------------------------------------------------- /src/iqparser.lalrpop: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use crate::ast::*; 3 | use std::boxed::Box; 4 | 5 | grammar; 6 | 7 | 8 | pub IqRoot: IqAstRootNode = { 9 | "" => IqAstRootNode { exprs: vec!() }, 10 | => IqAstRootNode{ exprs: vec!(e) }, 11 | ";")*> => IqAstRootNode{ exprs: e }, 12 | }; 13 | 14 | 15 | Expr: ExprNode = { 16 | => ExprNode { 17 | selector_ctx: Some(<>), 18 | op_nodes: vec!(), 19 | }, 20 | => ExprNode { 21 | selector_ctx: None, 22 | op_nodes: vec!(<>), 23 | }, 24 | )+> => ExprNode { 25 | selector_ctx: Some(s), 26 | op_nodes: ops, 27 | }, 28 | )+> => ExprNode { 29 | selector_ctx: None, 30 | op_nodes: vec!(o).into_iter().chain(ops.into_iter()).collect(), 31 | }, 32 | }; 33 | 34 | SelectorCtx: SelectorCtxNode = { 35 | "[" "]" => SelectorCtxNode { y_slice_range: None, x_slice_range: None }, 36 | "[" "]" => SelectorCtxNode { 37 | y_slice_range: Some(l), x_slice_range: None 38 | }, 39 | "[" "," "]" => SelectorCtxNode { 40 | y_slice_range: Some(l), x_slice_range: Some(r) 41 | }, 42 | }; 43 | 44 | SelectorSliceExpr: Box = { 45 | ":" => Box::new(SliceRangeNode { 46 | lower_bound: None, 47 | upper_bound: None, 48 | }), 49 | ":" => Box::new(SliceRangeNode { 50 | lower_bound: Some(<>), 51 | upper_bound: None, 52 | }), 53 | ":" => Box::new(SliceRangeNode { 54 | lower_bound: None, 55 | upper_bound: Some(<>), 56 | }), 57 | ":" => Box::new(SliceRangeNode { 58 | lower_bound: Some(l), 59 | upper_bound: Some(r), 60 | }), 61 | } 62 | 63 | 64 | Operator: OperatorNode = { 65 | "~" => OperatorNode::UnaryNegationOp(), 66 | MatchOperator, 67 | }; 68 | 69 | MatchOperator: OperatorNode = { 70 | )?> "=>" )?> => OperatorNode::MatchExprOp ( 71 | MatchExprOpNode { 72 | match_value: v, 73 | match_comparator_node: c, 74 | match_return_value_node: rval, 75 | else_return_value_node: other, 76 | } 77 | ), 78 | } 79 | 80 | MatchReturnValue: Box = { 81 | => Box::new(MatchReturnValue::Pixel(<>)), 82 | "(" ")" => Box::new(MatchReturnValue::Operator(<>)), 83 | } 84 | 85 | MatchComparator: MatchComparatorNode = { 86 | => MatchComparatorNode { 87 | op_type: o, 88 | cmp_val: v, 89 | }, 90 | } 91 | 92 | MatchComparisonValue: MatchComparisonValue = { 93 | => MatchComparisonValue::Pixel(<>), 94 | => MatchComparisonValue::Scalar(<>), 95 | } 96 | 97 | MatchExprOp: MatchOpType = { 98 | "<=" => MatchOpType::Lte(), 99 | ">=" => MatchOpType::Gte(), 100 | ">" => MatchOpType::Gt(), 101 | "<" => MatchOpType::Lt(), 102 | "==" => MatchOpType::Eq(), 103 | "!=" => MatchOpType::Neq(), 104 | }; 105 | 106 | 107 | ExplicitPixel: PixelNode = { 108 | "p(" 109 | "," 110 | "," 111 | "," 112 | "," 113 | 114 | )?> 115 | ")" => PixelNode{ 116 | y_expr: y, 117 | x_expr: x, 118 | r_expr: r, 119 | g_expr: g, 120 | b_expr: b, 121 | a_expr: a.unwrap_or( 122 | ScalarExprNode::Scalar( 123 | ScalarNode::Integer(255) 124 | ) 125 | ), 126 | }, 127 | } 128 | 129 | PixelExpr: PixelExprType = { 130 | "_" => PixelExprType::CurrentPixel(), 131 | => PixelExprType::FnCall(<>), 132 | => PixelExprType::Explicit(<>), 133 | } 134 | 135 | PixelFnCall: PixelFnCall = { 136 | "center()" => PixelFnCall { 137 | op: PixelFnOp::Center(), 138 | args: vec!(), 139 | }, 140 | "neighbors(" "," "," ")" => PixelFnCall { 141 | op: PixelFnOp::Neighbors(dy, dx), 142 | args: vec!(p), 143 | }, 144 | "color_scale(" "," ")" => PixelFnCall { 145 | op: PixelFnOp::ColorScale(f), 146 | args: vec!(p), 147 | }, 148 | "color_add(" )+> ")" => PixelFnCall { 149 | op: PixelFnOp::ColorAdd(), 150 | args: vec!(expr).into_iter().chain(exprs.into_iter()).collect(), 151 | }, 152 | "color_norm(" ")" => PixelFnCall { 153 | op: PixelFnOp::ColorNorm(), 154 | args: vec!(expr), 155 | }, 156 | "alpha_blend(" "," ")" => PixelFnCall { 157 | op: PixelFnOp::AlphaBlend(f), 158 | args: vec!(expr), 159 | }, 160 | } 161 | 162 | ScalarExpr: ScalarExprNode = { 163 | "+" => ScalarExprNode::BinaryOp( 164 | Box::new( 165 | BinaryScalarOpNode{ 166 | lhs: l, 167 | op: BinaryOpType::Add(), 168 | rhs: r, 169 | } 170 | ) 171 | ), 172 | "-" => ScalarExprNode::BinaryOp( 173 | Box::new( 174 | BinaryScalarOpNode{ 175 | lhs: l, 176 | op: BinaryOpType::Sub(), 177 | rhs: r, 178 | } 179 | ) 180 | ), 181 | ScalarExprFactor, 182 | } 183 | 184 | ScalarExprFactor: ScalarExprNode = { 185 | "/" => ScalarExprNode::BinaryOp( 186 | Box::new( 187 | BinaryScalarOpNode{ 188 | lhs: l, 189 | op: BinaryOpType::Div(), 190 | rhs: r, 191 | } 192 | ) 193 | ), 194 | "*" => ScalarExprNode::BinaryOp( 195 | Box::new( 196 | BinaryScalarOpNode{ 197 | lhs: l, 198 | op: BinaryOpType::Mul(), 199 | rhs: r, 200 | } 201 | ) 202 | ), 203 | ScalarExprTerm, 204 | } 205 | 206 | 207 | ScalarExprTerm: ScalarExprNode = { 208 | => ScalarExprNode::Scalar(<>), 209 | => ScalarExprNode::Scalar(<>), 210 | "." => ScalarExprNode::Scalar( 211 | ScalarNode::PixelScalar(Box::new(p), s) 212 | ), 213 | => ScalarExprNode::ScalarFn(<>), 214 | "(" ")", 215 | } 216 | 217 | ScalarFnCall: ScalarFnCall = { 218 | "min(" "," ")" => ScalarFnCall { 219 | op: ScalarFnOp::Min(), 220 | args: vec!(l, r), 221 | }, 222 | "max(" "," ")" => ScalarFnCall { 223 | op: ScalarFnOp::Max(), 224 | args: vec!(l, r), 225 | }, 226 | "sq(" ")" => ScalarFnCall { 227 | op: ScalarFnOp::Square(), 228 | args: vec!(<>), 229 | }, 230 | "sqrt(" ")" => ScalarFnCall { 231 | op: ScalarFnOp::Sqrt(), 232 | args: vec!(<>), 233 | }, 234 | } 235 | 236 | 237 | SelectorScalar: ScalarNode = { 238 | "." => ScalarNode::SelectorScalar( 239 | SelectorScalarNode { 240 | selector_ctx: c, 241 | accessed_attr: a, 242 | } 243 | ), 244 | } 245 | 246 | AttrAccess: AttrAccessNode = { 247 | r"[a-z]+" => AttrAccessNode { key : String::from(<>) }, 248 | } 249 | 250 | ScalarNode: ScalarNode = { 251 | => ScalarNode::Float(<>), 252 | => ScalarNode::Integer(<>), 253 | } 254 | 255 | Float: f64 = { 256 | r"(-)?[0-9]+\.[0-9]+" => f64::from_str(<>).unwrap(), 257 | } 258 | 259 | Integer: i64 = { 260 | r"(-)?[0-9]+" => i64::from_str(<>).unwrap(), 261 | }; 262 | 263 | -------------------------------------------------------------------------------- /src/ctx_ops.rs: -------------------------------------------------------------------------------- 1 | use crate::context::*; 2 | use std::vec::Vec; 3 | 4 | pub fn center(ctx: &BasicContext) -> AnnotatedPixelContext { 5 | AnnotatedPixelContext::like(ctx, &ctx.center()) 6 | } 7 | 8 | pub fn neighbors(arg: &AnnotatedPixelContext, dy: i64, dx: i64) -> AnnotatedPixelContext { 9 | AnnotatedPixelContext::from_iter_with_annotation(arg.iter_annotations(), |(pixel, annot)| { 10 | let ny = (annot.y as i64 + dy) as u32; 11 | let nx = (annot.x as i64 + dx) as u32; 12 | let default = IqPixel { 13 | y: ny, 14 | x: nx, 15 | c: [0, 0, 0, 0], 16 | }; 17 | ( 18 | pixel.clone(), 19 | IqPixel { 20 | y: ny, 21 | x: nx, 22 | c: arg.get_annotation_at_loc((ny, nx)).unwrap_or(&default).c, 23 | }, 24 | ) 25 | }) 26 | } 27 | 28 | pub fn color_scale(arg: &AnnotatedPixelContext, scale_factor: f64) -> AnnotatedPixelContext { 29 | AnnotatedPixelContext::from_iter_with_annotation(arg.iter_annotations(), |(pixel, annot)| { 30 | ( 31 | pixel.clone(), 32 | IqPixel { 33 | y: pixel.y, 34 | x: pixel.x, 35 | c: [ 36 | (annot.c[0] as f64 * scale_factor) as i64, 37 | (annot.c[1] as f64 * scale_factor) as i64, 38 | (annot.c[2] as f64 * scale_factor) as i64, 39 | annot.c[3], 40 | ], 41 | }, 42 | ) 43 | }) 44 | } 45 | 46 | pub fn color_add(args: &Vec) -> AnnotatedPixelContext { 47 | if args.is_empty() { 48 | return AnnotatedPixelContext::empty(); 49 | } 50 | 51 | let red_channels: Vec = args 52 | .iter() 53 | .map(|pixel_context| { 54 | AnnotatedFloatContext::from_iter_with_annotation( 55 | pixel_context.iter_annotations(), 56 | |(pixel, annot)| (pixel.clone(), annot.c[0] as f64), 57 | ) 58 | }) 59 | .collect(); 60 | let green_channels: Vec = args 61 | .iter() 62 | .map(|pixel_context| { 63 | AnnotatedFloatContext::from_iter_with_annotation( 64 | pixel_context.iter_annotations(), 65 | |(pixel, annot)| (pixel.clone(), annot.c[0] as f64), 66 | ) 67 | }) 68 | .collect(); 69 | let blue_channels: Vec = args 70 | .iter() 71 | .map(|pixel_context| { 72 | AnnotatedFloatContext::from_iter_with_annotation( 73 | pixel_context.iter_annotations(), 74 | |(pixel, annot)| (pixel.clone(), annot.c[0] as f64), 75 | ) 76 | }) 77 | .collect(); 78 | 79 | let mut merged_red_channel: AnnotatedFloatContext = AnnotatedFloatContext::empty(); 80 | let mut merged_green_channel: AnnotatedFloatContext = AnnotatedFloatContext::empty(); 81 | let mut merged_blue_channel: AnnotatedFloatContext = AnnotatedFloatContext::empty(); 82 | 83 | red_channels.iter().for_each(|r_channel_ctx| { 84 | for (pixel, &value) in r_channel_ctx.iter_annotations() { 85 | let loc = (pixel.y, pixel.x); 86 | if let Some(annot) = merged_red_channel.get_annotation_at_loc(loc) { 87 | merged_red_channel.update_annot_at_loc(loc, annot + value); 88 | } else { 89 | merged_red_channel.insert_with_annotation(pixel.clone(), value); 90 | } 91 | } 92 | }); 93 | green_channels.iter().for_each(|g_channel_ctx| { 94 | for (pixel, &value) in g_channel_ctx.iter_annotations() { 95 | let loc = (pixel.y, pixel.x); 96 | if let Some(annot) = merged_green_channel.get_annotation_at_loc(loc) { 97 | merged_green_channel.update_annot_at_loc(loc, annot + value); 98 | } else { 99 | merged_green_channel.insert_with_annotation(pixel.clone(), value); 100 | } 101 | } 102 | }); 103 | blue_channels.iter().for_each(|b_channel_ctx| { 104 | for (pixel, &value) in b_channel_ctx.iter_annotations() { 105 | let loc = (pixel.y, pixel.x); 106 | if let Some(annot) = merged_blue_channel.get_annotation_at_loc(loc) { 107 | merged_blue_channel.update_annot_at_loc(loc, annot + value); 108 | } else { 109 | merged_blue_channel.insert_with_annotation(pixel.clone(), value); 110 | } 111 | } 112 | }); 113 | 114 | AnnotatedPixelContext::from_iter_with_annotation(args.first().unwrap().iter(), |pixel| { 115 | let r = *merged_red_channel 116 | .get_annotation_at_loc((pixel.y, pixel.x)) 117 | .unwrap() as i64; 118 | let g = *merged_green_channel 119 | .get_annotation_at_loc((pixel.y, pixel.x)) 120 | .unwrap() as i64; 121 | let b = *merged_blue_channel 122 | .get_annotation_at_loc((pixel.y, pixel.x)) 123 | .unwrap() as i64; 124 | 125 | ( 126 | pixel.clone(), 127 | IqPixel { 128 | y: pixel.y, 129 | x: pixel.x, 130 | c: [r, g, b, pixel.c[3]], 131 | }, 132 | ) 133 | }) 134 | } 135 | 136 | pub fn color_norm(arg: &AnnotatedPixelContext) -> AnnotatedPixelContext { 137 | if arg.count() == 0 { 138 | return AnnotatedPixelContext::empty(); 139 | } 140 | 141 | let r_bounds = arg 142 | .iter_annotations() 143 | .map(|(_, annot)| (annot.c[0], annot.c[0])) 144 | .reduce(|accum, rval| { 145 | ( 146 | std::cmp::min(accum.0, rval.0), 147 | std::cmp::max(accum.1, rval.1), 148 | ) 149 | }) 150 | .unwrap(); 151 | let g_bounds = arg 152 | .iter_annotations() 153 | .map(|(_, annot)| (annot.c[1], annot.c[1])) 154 | .reduce(|accum, rval| { 155 | ( 156 | std::cmp::min(accum.0, rval.0), 157 | std::cmp::max(accum.1, rval.1), 158 | ) 159 | }) 160 | .unwrap(); 161 | let b_bounds = arg 162 | .iter_annotations() 163 | .map(|(_, annot)| (annot.c[2], annot.c[2])) 164 | .reduce(|accum, rval| { 165 | ( 166 | std::cmp::min(accum.0, rval.0), 167 | std::cmp::max(accum.1, rval.1), 168 | ) 169 | }) 170 | .unwrap(); 171 | 172 | let r_range = if (r_bounds.1 - r_bounds.0) == 0 { 173 | 1.0 174 | } else { 175 | (r_bounds.1 - r_bounds.0) as f64 176 | }; 177 | 178 | let g_range = if (g_bounds.1 - g_bounds.0) == 0 { 179 | 1.0 180 | } else { 181 | (g_bounds.1 - g_bounds.0) as f64 182 | }; 183 | 184 | let b_range = if (b_bounds.1 - b_bounds.0) == 0 { 185 | 1.0 186 | } else { 187 | (b_bounds.1 - b_bounds.0) as f64 188 | }; 189 | 190 | let r_inv = 1.0 / r_range; 191 | let g_inv = 1.0 / g_range; 192 | let b_inv = 1.0 / b_range; 193 | 194 | AnnotatedPixelContext::from_iter_with_annotation(arg.iter_annotations(), |(pixel, annot)| { 195 | ( 196 | pixel.clone(), 197 | IqPixel { 198 | y: pixel.y, 199 | x: pixel.x, 200 | c: [ 201 | (((annot.c[0] - r_bounds.0) as f64 * r_inv) * 255.0) as i64, 202 | (((annot.c[1] - g_bounds.0) as f64 * g_inv) * 255.0) as i64, 203 | (((annot.c[2] - b_bounds.0) as f64 * b_inv) * 255.0) as i64, 204 | annot.c[3], 205 | ], 206 | }, 207 | ) 208 | }) 209 | } 210 | 211 | pub fn alpha_blend(arg: &AnnotatedPixelContext, blend: f64) -> AnnotatedPixelContext { 212 | AnnotatedPixelContext::from_iter_with_annotation(arg.iter_annotations(), |(pixel, annot)| { 213 | ( 214 | pixel.clone(), 215 | IqPixel { 216 | y: pixel.y, 217 | x: pixel.x, 218 | c: [ 219 | annot.c[0], 220 | annot.c[1], 221 | annot.c[2], 222 | (annot.c[3] as f64 * blend) as i64, 223 | ], 224 | }, 225 | ) 226 | }) 227 | } 228 | -------------------------------------------------------------------------------- /src/context.rs: -------------------------------------------------------------------------------- 1 | use image::RgbaImage; 2 | use std::cmp::{max, min, PartialOrd}; 3 | use std::collections::HashMap; 4 | use std::path::Path; 5 | 6 | #[derive(Debug, Clone, Hash, Eq, PartialEq, PartialOrd)] 7 | pub struct IqPixel { 8 | pub x: u32, 9 | pub y: u32, 10 | pub c: [i64; 4], 11 | } 12 | 13 | impl IqPixel { 14 | pub fn negate(&self) -> Self { 15 | Self { 16 | x: self.x, 17 | y: self.y, 18 | c: [!self.c[0], !self.c[1], !self.c[2], !self.c[3]], 19 | } 20 | } 21 | 22 | pub fn alpha_composite(&self, other: &Self) -> Self { 23 | let ca = [ 24 | self.c[0] as f64, 25 | self.c[1] as f64, 26 | self.c[2] as f64, 27 | self.c[3] as f64, 28 | ]; 29 | let cb = [ 30 | other.c[0] as f64, 31 | other.c[1] as f64, 32 | other.c[2] as f64, 33 | other.c[3] as f64, 34 | ]; 35 | let alpha_a = ca[3] / 255.0; 36 | let alpha_b = cb[3] / 255.0; 37 | let cout = [ 38 | (ca[0] * alpha_a + cb[0] * alpha_b * (1.0 - alpha_a)) as i64, 39 | (ca[1] * alpha_a + cb[1] * alpha_b * (1.0 - alpha_a)) as i64, 40 | (ca[2] * alpha_a + cb[2] * alpha_b * (1.0 - alpha_a)) as i64, 41 | 255, 42 | ]; 43 | IqPixel { 44 | y: self.y, 45 | x: self.x, 46 | c: cout, 47 | } 48 | } 49 | } 50 | 51 | #[derive(Debug, Clone, PartialEq, Eq)] 52 | pub struct Context { 53 | min_y: u32, 54 | max_y: u32, 55 | min_x: u32, 56 | max_x: u32, 57 | pixels: HashMap<(u32, u32), IqPixel>, 58 | annotations: HashMap, 59 | } 60 | 61 | pub type BasicContext = Context<()>; 62 | pub type AnnotatedFloatContext = Context; 63 | pub type AnnotatedPixelContext = Context; 64 | 65 | impl Context { 66 | pub fn empty() -> Self { 67 | Self { 68 | min_y: 0, 69 | max_y: 0, 70 | min_x: 0, 71 | max_x: 0, 72 | pixels: HashMap::new(), 73 | annotations: HashMap::new(), 74 | } 75 | } 76 | 77 | pub fn blank_with_default(h: u32, w: u32, c: [i64; 4]) -> Self { 78 | let mut ctx = Self::empty(); 79 | for y in 0..h { 80 | for x in 0..w { 81 | ctx.insert(IqPixel { y, x, c }); 82 | } 83 | } 84 | ctx 85 | } 86 | 87 | pub fn blank(h: u32, w: u32) -> Self { 88 | Self::blank_with_default(h, w, [255, 255, 255, 255]) 89 | } 90 | 91 | pub fn from_contexts(contexts: Vec) -> Self { 92 | if contexts.is_empty() { 93 | Self::empty() 94 | } else { 95 | let mut out = Self::empty(); 96 | for ctx in contexts { 97 | for pixel in ctx.iter() { 98 | out.insert(pixel.clone()) 99 | } 100 | } 101 | out 102 | } 103 | } 104 | 105 | pub fn alpha_composite(contexts: Vec) -> Self { 106 | if contexts.is_empty() { 107 | Self::empty() 108 | } else { 109 | let mut out = Self::empty(); 110 | for ctx in contexts { 111 | for pixel in ctx.iter() { 112 | if let Some(under_pixel) = out.pixels.get(&(pixel.y, pixel.x)) { 113 | out.insert(under_pixel.alpha_composite(pixel)) 114 | } else { 115 | out.insert(pixel.clone()) 116 | } 117 | } 118 | } 119 | out 120 | } 121 | } 122 | 123 | pub fn write(&self, path: &str) { 124 | let mut img = RgbaImage::new(self.max_y + 1, self.max_x + 1); 125 | 126 | for pixel in self.iter() { 127 | img.put_pixel( 128 | pixel.x - self.min_x, 129 | pixel.y - self.min_y, 130 | image::Rgba([ 131 | pixel.c[0] as u8, 132 | pixel.c[1] as u8, 133 | pixel.c[2] as u8, 134 | pixel.c[3] as u8, 135 | ]), 136 | ) 137 | } 138 | 139 | img.save(path).unwrap(); 140 | } 141 | 142 | pub fn from_path(path: &str) -> Self { 143 | let img = image::open(&Path::new(path)).unwrap().to_rgba8(); 144 | let mut out = Self::empty(); 145 | 146 | for (x, y, c) in img.enumerate_pixels() { 147 | out.insert(IqPixel { 148 | y, 149 | x, 150 | c: [c[0] as i64, c[1] as i64, c[2] as i64, c[3] as i64], 151 | }) 152 | } 153 | 154 | out 155 | } 156 | 157 | pub fn subcontext( 158 | &self, 159 | y_bounds: (Option, Option), 160 | x_bounds: (Option, Option), 161 | ) -> Self { 162 | let lby = y_bounds.0.unwrap_or(self.min_y); 163 | let uby = y_bounds.1.unwrap_or(self.max_y); 164 | let lbx = x_bounds.0.unwrap_or(self.min_x); 165 | let ubx = x_bounds.1.unwrap_or(self.max_x); 166 | 167 | let mut subctx = Self::empty(); 168 | for pixel in self.iter() { 169 | if lby <= pixel.y && pixel.y <= uby && lbx <= pixel.x && pixel.x <= ubx { 170 | subctx.insert(pixel.clone()) 171 | } 172 | } 173 | subctx 174 | } 175 | 176 | pub fn width(&self) -> u32 { 177 | self.max_x - self.min_x 178 | } 179 | 180 | pub fn height(&self) -> u32 { 181 | self.max_y - self.min_y 182 | } 183 | 184 | pub fn insert(&mut self, pixel: IqPixel) { 185 | self.min_x = min(pixel.x, self.min_x); 186 | self.max_x = max(pixel.x, self.max_x); 187 | self.min_y = min(pixel.y, self.min_y); 188 | self.max_y = max(pixel.y, self.max_y); 189 | self.pixels.insert((pixel.y, pixel.x), pixel); 190 | } 191 | 192 | pub fn count(&self) -> usize { 193 | self.pixels.len() 194 | } 195 | 196 | pub fn iter(&self) -> std::collections::hash_map::Values<(u32, u32), IqPixel> { 197 | self.pixels.values() 198 | } 199 | 200 | pub fn select(&self, selection_ctx: Context) -> Context { 201 | let mut selected_context = Context::empty(); 202 | for pixel in self.iter() { 203 | if selection_ctx.pixels.contains_key(&(pixel.y, pixel.x)) { 204 | selected_context.insert(pixel.clone()) 205 | } 206 | } 207 | selected_context 208 | } 209 | 210 | pub fn center(&self) -> IqPixel { 211 | let y = self.min_y + (self.max_y - self.min_y) / 2; 212 | let x = self.min_x + (self.max_x - self.min_x) / 2; 213 | if let Some(p) = self.pixels.get(&(y, x)) { 214 | p.clone() 215 | } else { 216 | IqPixel { 217 | y, 218 | x, 219 | c: [255, 255, 255, 255], 220 | } 221 | } 222 | } 223 | 224 | pub fn x_bounds(&self) -> (u32, u32) { 225 | (self.min_x, self.max_x) 226 | } 227 | 228 | pub fn y_bounds(&self) -> (u32, u32) { 229 | (self.min_y, self.max_y) 230 | } 231 | 232 | pub fn describe(&self) -> String { 233 | format!( 234 | "", 235 | self.x_bounds(), 236 | self.y_bounds() 237 | ) 238 | } 239 | 240 | pub fn from_iter(iter: P, f: F) -> Self 241 | where 242 | F: Fn(I) -> IqPixel, 243 | P: IntoIterator, 244 | { 245 | let mut out = Self::empty(); 246 | for item in iter { 247 | out.insert(f(item)) 248 | } 249 | out 250 | } 251 | 252 | pub fn from_iter_with_annotation(iter: P, f: F) -> Self 253 | where 254 | F: Fn(I) -> (IqPixel, T), 255 | P: IntoIterator, 256 | { 257 | let mut out = Self::empty(); 258 | for item in iter { 259 | let result = f(item); 260 | out.insert_with_annotation(result.0, result.1) 261 | } 262 | out 263 | } 264 | 265 | pub fn insert_with_annotation(&mut self, pixel: IqPixel, annotation: T) { 266 | self.insert(pixel.clone()); 267 | self.annotations.insert(pixel, annotation); 268 | } 269 | 270 | pub fn get_annotation(&self, pixel: &IqPixel) -> Option<&T> { 271 | self.annotations.get(pixel) 272 | } 273 | 274 | pub fn get_annotation_at_loc(&self, loc: (u32, u32)) -> Option<&T> { 275 | if let Some(p) = self.pixels.get(&loc) { 276 | self.get_annotation(p) 277 | } else { 278 | None 279 | } 280 | } 281 | 282 | pub fn update_annot_at_loc(&mut self, loc: (u32, u32), annot: T) { 283 | if let Some(p) = self.pixels.get(&loc) { 284 | self.annotations.insert(p.clone(), annot); 285 | } 286 | } 287 | 288 | pub fn iter_annotations(&self) -> std::collections::hash_map::Iter { 289 | self.annotations.iter() 290 | } 291 | 292 | pub fn first(&self) -> &T { 293 | self.iter_annotations().next().unwrap().1 294 | } 295 | 296 | pub fn like(ctx: &Context, default: &T) -> Self 297 | where 298 | T: Clone, 299 | { 300 | let mut annotated_ctx = Self::empty(); 301 | for pixel in ctx.iter() { 302 | annotated_ctx.insert_with_annotation(pixel.clone(), default.clone()) 303 | } 304 | annotated_ctx 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/eval.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::*; 2 | use crate::attrs; 3 | use crate::context::{AnnotatedFloatContext, AnnotatedPixelContext, BasicContext, IqPixel}; 4 | use crate::ctx_ops; 5 | use crate::float_ops; 6 | 7 | pub trait Evalulate { 8 | fn eval(&self, image_ctx: &BasicContext) -> T; 9 | } 10 | 11 | impl Evalulate for IqAstRootNode { 12 | fn eval(&self, image_ctx: &BasicContext) -> BasicContext { 13 | BasicContext::alpha_composite( 14 | (*self.exprs) 15 | .iter() 16 | .map(|expr| expr.eval(image_ctx)) 17 | .collect(), 18 | ) 19 | } 20 | } 21 | 22 | impl Evalulate for ExprNode { 23 | fn eval(&self, image_ctx: &BasicContext) -> BasicContext { 24 | let mut selected_ctx = match &self.selector_ctx { 25 | None => image_ctx.clone(), 26 | Some(selector_ctx) => selector_ctx.eval(image_ctx), 27 | }; 28 | 29 | for op in &self.op_nodes { 30 | selected_ctx = op.eval(&selected_ctx); 31 | } 32 | 33 | selected_ctx 34 | } 35 | } 36 | 37 | impl SliceRangeNode { 38 | fn default_x(image_ctx: &BasicContext) -> Self { 39 | Self::with_bounds(0, image_ctx.width().into()) 40 | } 41 | 42 | fn default_y(image_ctx: &BasicContext) -> Self { 43 | Self::with_bounds(0, image_ctx.height().into()) 44 | } 45 | 46 | fn with_bounds(lower: i64, upper: i64) -> Self { 47 | SliceRangeNode { 48 | lower_bound: Some(ScalarExprNode::Scalar(ScalarNode::Integer(lower))), 49 | upper_bound: Some(ScalarExprNode::Scalar(ScalarNode::Integer(upper))), 50 | } 51 | } 52 | } 53 | 54 | impl Evalulate for SelectorCtxNode { 55 | fn eval(&self, image_ctx: &BasicContext) -> BasicContext { 56 | let y_slice_range = match &self.y_slice_range { 57 | None => Box::new(SliceRangeNode::default_y(image_ctx)), 58 | Some(y_slice_range) => y_slice_range.clone(), 59 | }; 60 | 61 | let x_slice_range = match &self.x_slice_range { 62 | None => Box::new(SliceRangeNode::default_x(image_ctx)), 63 | Some(x_slice_range) => x_slice_range.clone(), 64 | }; 65 | 66 | let y_bounds = y_slice_range.eval(image_ctx); 67 | let x_bounds = x_slice_range.eval(image_ctx); 68 | 69 | image_ctx.subcontext(y_bounds, x_bounds) 70 | } 71 | } 72 | 73 | impl Evalulate<(Option, Option)> for SliceRangeNode { 74 | fn eval(&self, image_ctx: &BasicContext) -> (Option, Option) { 75 | let lower_bound = match &self.lower_bound { 76 | None => None, 77 | Some(lower_bound) => { 78 | let floating_lower_bound: f64 = *lower_bound.eval(image_ctx).first(); 79 | Some(floating_lower_bound.round() as u32) 80 | } 81 | }; 82 | let upper_bound = match &self.upper_bound { 83 | None => None, 84 | Some(upper_bound) => { 85 | let floating_upper_bound: f64 = *upper_bound.eval(image_ctx).first(); 86 | Some(floating_upper_bound.round() as u32) 87 | } 88 | }; 89 | 90 | (lower_bound, upper_bound) 91 | } 92 | } 93 | 94 | impl Evalulate for ScalarExprNode { 95 | fn eval(&self, image_ctx: &BasicContext) -> AnnotatedFloatContext { 96 | match &self { 97 | Self::ScalarFn(fncall_node) => fncall_node.eval(image_ctx), 98 | Self::SubExpr(subexpr_node) => subexpr_node.eval(image_ctx), 99 | Self::Scalar(scalar_node) => scalar_node.eval(image_ctx), 100 | Self::BinaryOp(binary_op_node) => binary_op_node.eval(image_ctx), 101 | } 102 | } 103 | } 104 | 105 | impl Evalulate for ScalarFnCall { 106 | fn eval(&self, image_ctx: &BasicContext) -> AnnotatedFloatContext { 107 | let mut evaluated_args = (*self.args).iter().map(|arg| arg.eval(image_ctx)); 108 | 109 | match &self.op { 110 | ScalarFnOp::Min() => float_ops::min(&evaluated_args.collect()), 111 | ScalarFnOp::Max() => float_ops::max(&evaluated_args.collect()), 112 | ScalarFnOp::Square() => float_ops::square(&evaluated_args.next().unwrap()), 113 | ScalarFnOp::Sqrt() => float_ops::sqrt(&evaluated_args.next().unwrap()), 114 | } 115 | } 116 | } 117 | 118 | impl Evalulate for ScalarNode { 119 | fn eval(&self, image_ctx: &BasicContext) -> AnnotatedFloatContext { 120 | match &self { 121 | ScalarNode::Float(n) => AnnotatedFloatContext::like(image_ctx, n), 122 | ScalarNode::Integer(n) => AnnotatedFloatContext::like(image_ctx, &(*n as f64)), 123 | ScalarNode::SelectorScalar(selector_scalar_node) => { 124 | AnnotatedFloatContext::like(image_ctx, &selector_scalar_node.eval(image_ctx)) 125 | } 126 | ScalarNode::PixelScalar(pixel_expr, attr_access) => { 127 | attrs::access_scalar_annotated_ctx_attr( 128 | &pixel_expr.eval(image_ctx), 129 | &attr_access.key, 130 | ) 131 | } 132 | } 133 | } 134 | } 135 | 136 | impl Evalulate for SelectorScalarNode { 137 | fn eval(&self, image_ctx: &BasicContext) -> f64 { 138 | attrs::access_scalar_attr(&self.selector_ctx.eval(image_ctx), &self.accessed_attr.key) 139 | } 140 | } 141 | 142 | impl Evalulate for BinaryScalarOpNode { 143 | fn eval(&self, image_ctx: &BasicContext) -> AnnotatedFloatContext { 144 | let lhs = self.lhs.eval(image_ctx); 145 | let rhs = self.rhs.eval(image_ctx); 146 | match &self.op { 147 | BinaryOpType::Add() => float_ops::add(&lhs, &rhs), 148 | BinaryOpType::Sub() => float_ops::sub(&lhs, &rhs), 149 | BinaryOpType::Div() => float_ops::div(&lhs, &rhs), 150 | BinaryOpType::Mul() => float_ops::mul(&lhs, &rhs), 151 | } 152 | } 153 | } 154 | 155 | impl Evalulate for OperatorNode { 156 | fn eval(&self, image_ctx: &BasicContext) -> BasicContext { 157 | match &self { 158 | Self::UnaryNegationOp() => float_ops::negate(image_ctx), 159 | Self::MatchExprOp(op) => op.eval(image_ctx), 160 | } 161 | } 162 | } 163 | 164 | fn match_compare(op_type: &MatchOpType, lhs: T, rhs: T) -> bool { 165 | match op_type { 166 | MatchOpType::Lt() => lhs < rhs, 167 | MatchOpType::Lte() => lhs <= rhs, 168 | MatchOpType::Gt() => lhs > rhs, 169 | MatchOpType::Gte() => lhs >= rhs, 170 | MatchOpType::Eq() => lhs == rhs, 171 | MatchOpType::Neq() => lhs != rhs, 172 | } 173 | } 174 | 175 | impl Evalulate for MatchExprOpNode { 176 | fn eval(&self, image_ctx: &BasicContext) -> BasicContext { 177 | let match_comp_lhs = &self.match_value; 178 | let (matched_ctx, else_context) = match &self.match_comparator_node { 179 | None => (image_ctx.clone(), BasicContext::empty()), 180 | Some(match_comparator) => match (match_comp_lhs, &match_comparator.cmp_val) { 181 | ( 182 | MatchComparisonValue::Scalar(lhs_scalar_expr), 183 | MatchComparisonValue::Scalar(rhs_scalar_expr), 184 | ) => { 185 | let lhs_terms: AnnotatedFloatContext = lhs_scalar_expr.eval(image_ctx); 186 | let rhs_terms: AnnotatedFloatContext = rhs_scalar_expr.eval(image_ctx); 187 | let mut matched_ctx = BasicContext::empty(); 188 | let mut else_context = BasicContext::empty(); 189 | 190 | for (point, annotation) in lhs_terms.iter_annotations() { 191 | if match_compare( 192 | &match_comparator.op_type, 193 | annotation, 194 | rhs_terms.get_annotation(point).unwrap(), 195 | ) { 196 | matched_ctx.insert(point.clone()); 197 | } else { 198 | else_context.insert(point.clone()); 199 | } 200 | } 201 | 202 | (matched_ctx, else_context) 203 | } 204 | ( 205 | MatchComparisonValue::Pixel(lhs_pixel_expr), 206 | MatchComparisonValue::Pixel(rhs_pixel_expr), 207 | ) => { 208 | let lhs_terms: AnnotatedPixelContext = lhs_pixel_expr.eval(image_ctx); 209 | let rhs_terms: AnnotatedPixelContext = rhs_pixel_expr.eval(image_ctx); 210 | let mut matched_ctx = BasicContext::empty(); 211 | let mut else_context = BasicContext::empty(); 212 | 213 | for (point, annotation) in lhs_terms.iter_annotations() { 214 | if match_compare( 215 | &match_comparator.op_type, 216 | annotation, 217 | rhs_terms.get_annotation(point).unwrap(), 218 | ) { 219 | matched_ctx.insert(annotation.clone()); 220 | } else { 221 | else_context.insert(annotation.clone()); 222 | } 223 | } 224 | 225 | (matched_ctx, else_context) 226 | } 227 | _ => panic!("match terms have incompatible types"), 228 | }, 229 | }; 230 | let matched_outputs = self.match_return_value_node.eval(&matched_ctx); 231 | 232 | if let Some(else_block) = &self.else_return_value_node { 233 | BasicContext::from_contexts(vec![matched_outputs, else_block.eval(&else_context)]) 234 | } else { 235 | matched_outputs 236 | } 237 | } 238 | } 239 | 240 | impl Evalulate for MatchReturnValue { 241 | fn eval(&self, image_ctx: &BasicContext) -> BasicContext { 242 | match self { 243 | MatchReturnValue::Pixel(pixel_expr) => BasicContext::from_iter( 244 | pixel_expr 245 | .eval(image_ctx) 246 | .iter_annotations() 247 | .map(|(_, annotation)| annotation) 248 | .cloned(), 249 | |annotation| annotation, 250 | ), 251 | MatchReturnValue::Operator(operator) => operator.eval(image_ctx), 252 | } 253 | } 254 | } 255 | 256 | impl Evalulate for PixelExprType { 257 | fn eval(&self, image_ctx: &BasicContext) -> AnnotatedPixelContext { 258 | match self { 259 | PixelExprType::Explicit(pixelexpr) => pixelexpr.eval(image_ctx), 260 | PixelExprType::CurrentPixel() => { 261 | AnnotatedPixelContext::from_iter_with_annotation(image_ctx.iter(), |point| { 262 | (point.clone(), point.clone()) 263 | }) 264 | } 265 | PixelExprType::FnCall(pixel_fn_call) => pixel_fn_call.eval(image_ctx), 266 | } 267 | } 268 | } 269 | 270 | impl Evalulate for PixelFnCall { 271 | fn eval(&self, image_ctx: &BasicContext) -> AnnotatedPixelContext { 272 | let mut evaluated_args = (*self.args).iter().map(|arg| arg.eval(image_ctx)); 273 | match self.op { 274 | PixelFnOp::Center() => ctx_ops::center(image_ctx), 275 | PixelFnOp::Neighbors(dy, dx) => { 276 | ctx_ops::neighbors(&evaluated_args.next().unwrap(), dy, dx) 277 | } 278 | PixelFnOp::ColorScale(scale_factor) => { 279 | ctx_ops::color_scale(&evaluated_args.next().unwrap(), scale_factor) 280 | } 281 | PixelFnOp::ColorAdd() => ctx_ops::color_add(&evaluated_args.collect()), 282 | PixelFnOp::ColorNorm() => ctx_ops::color_norm(&evaluated_args.next().unwrap()), 283 | PixelFnOp::AlphaBlend(blend) => { 284 | ctx_ops::alpha_blend(&evaluated_args.next().unwrap(), blend) 285 | } 286 | } 287 | } 288 | } 289 | 290 | impl Evalulate for PixelNode { 291 | fn eval(&self, image_ctx: &BasicContext) -> AnnotatedPixelContext { 292 | let x_values = self.x_expr.eval(image_ctx); 293 | let y_values = self.y_expr.eval(image_ctx); 294 | let r_values = self.r_expr.eval(image_ctx); 295 | let g_values = self.g_expr.eval(image_ctx); 296 | let b_values = self.b_expr.eval(image_ctx); 297 | let a_values = self.a_expr.eval(image_ctx); 298 | 299 | let mut annotated_ctx = AnnotatedPixelContext::empty(); 300 | for pixel in image_ctx.iter() { 301 | annotated_ctx.insert_with_annotation( 302 | pixel.clone(), 303 | IqPixel { 304 | x: x_values.get_annotation(pixel).unwrap().round() as u32, 305 | y: y_values.get_annotation(pixel).unwrap().round() as u32, 306 | c: [ 307 | r_values.get_annotation(pixel).unwrap().round() as i64, 308 | g_values.get_annotation(pixel).unwrap().round() as i64, 309 | b_values.get_annotation(pixel).unwrap().round() as i64, 310 | a_values.get_annotation(pixel).unwrap().round() as i64, 311 | ], 312 | }, 313 | ); 314 | } 315 | 316 | annotated_ctx 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /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 = "adler32" 13 | version = "1.2.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "0.7.18" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 22 | dependencies = [ 23 | "memchr", 24 | ] 25 | 26 | [[package]] 27 | name = "ansi_term" 28 | version = "0.12.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 31 | dependencies = [ 32 | "winapi", 33 | ] 34 | 35 | [[package]] 36 | name = "anyhow" 37 | version = "1.0.61" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8" 40 | 41 | [[package]] 42 | name = "ascii-canvas" 43 | version = "3.0.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" 46 | dependencies = [ 47 | "term", 48 | ] 49 | 50 | [[package]] 51 | name = "atty" 52 | version = "0.2.14" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 55 | dependencies = [ 56 | "hermit-abi", 57 | "libc", 58 | "winapi", 59 | ] 60 | 61 | [[package]] 62 | name = "autocfg" 63 | version = "1.1.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 66 | 67 | [[package]] 68 | name = "bit-set" 69 | version = "0.5.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" 72 | dependencies = [ 73 | "bit-vec", 74 | ] 75 | 76 | [[package]] 77 | name = "bit-vec" 78 | version = "0.6.3" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 81 | 82 | [[package]] 83 | name = "bit_field" 84 | version = "0.10.1" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" 87 | 88 | [[package]] 89 | name = "bitflags" 90 | version = "1.3.2" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 93 | 94 | [[package]] 95 | name = "bumpalo" 96 | version = "3.11.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" 99 | 100 | [[package]] 101 | name = "bytemuck" 102 | version = "1.12.1" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" 105 | 106 | [[package]] 107 | name = "byteorder" 108 | version = "1.4.3" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 111 | 112 | [[package]] 113 | name = "cfg-if" 114 | version = "1.0.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 117 | 118 | [[package]] 119 | name = "clap" 120 | version = "2.34.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 123 | dependencies = [ 124 | "ansi_term", 125 | "atty", 126 | "bitflags", 127 | "strsim 0.8.0", 128 | "textwrap 0.11.0", 129 | "unicode-width", 130 | "vec_map", 131 | ] 132 | 133 | [[package]] 134 | name = "clap" 135 | version = "3.2.20" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd" 138 | dependencies = [ 139 | "atty", 140 | "bitflags", 141 | "clap_lex", 142 | "indexmap", 143 | "once_cell", 144 | "strsim 0.10.0", 145 | "termcolor", 146 | "textwrap 0.15.0", 147 | ] 148 | 149 | [[package]] 150 | name = "clap_lex" 151 | version = "0.2.4" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 154 | dependencies = [ 155 | "os_str_bytes", 156 | ] 157 | 158 | [[package]] 159 | name = "color_quant" 160 | version = "1.1.0" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 163 | 164 | [[package]] 165 | name = "crc32fast" 166 | version = "1.3.2" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 169 | dependencies = [ 170 | "cfg-if", 171 | ] 172 | 173 | [[package]] 174 | name = "crossbeam-channel" 175 | version = "0.5.6" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" 178 | dependencies = [ 179 | "cfg-if", 180 | "crossbeam-utils", 181 | ] 182 | 183 | [[package]] 184 | name = "crossbeam-deque" 185 | version = "0.8.2" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" 188 | dependencies = [ 189 | "cfg-if", 190 | "crossbeam-epoch", 191 | "crossbeam-utils", 192 | ] 193 | 194 | [[package]] 195 | name = "crossbeam-epoch" 196 | version = "0.9.10" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" 199 | dependencies = [ 200 | "autocfg", 201 | "cfg-if", 202 | "crossbeam-utils", 203 | "memoffset", 204 | "once_cell", 205 | "scopeguard", 206 | ] 207 | 208 | [[package]] 209 | name = "crossbeam-utils" 210 | version = "0.8.11" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" 213 | dependencies = [ 214 | "cfg-if", 215 | "once_cell", 216 | ] 217 | 218 | [[package]] 219 | name = "crunchy" 220 | version = "0.2.2" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 223 | 224 | [[package]] 225 | name = "deflate" 226 | version = "1.0.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" 229 | dependencies = [ 230 | "adler32", 231 | ] 232 | 233 | [[package]] 234 | name = "diff" 235 | version = "0.1.13" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 238 | 239 | [[package]] 240 | name = "dirs-next" 241 | version = "2.0.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 244 | dependencies = [ 245 | "cfg-if", 246 | "dirs-sys-next", 247 | ] 248 | 249 | [[package]] 250 | name = "dirs-sys-next" 251 | version = "0.1.2" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 254 | dependencies = [ 255 | "libc", 256 | "redox_users", 257 | "winapi", 258 | ] 259 | 260 | [[package]] 261 | name = "either" 262 | version = "1.8.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 265 | 266 | [[package]] 267 | name = "ena" 268 | version = "0.14.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" 271 | dependencies = [ 272 | "log", 273 | ] 274 | 275 | [[package]] 276 | name = "exr" 277 | version = "1.5.0" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "78c26a90d9dd411a3d119d6f55752fb4c134ca243250c32fb9cab7b2561638d2" 280 | dependencies = [ 281 | "bit_field", 282 | "flume", 283 | "half", 284 | "lebe", 285 | "miniz_oxide", 286 | "smallvec", 287 | "threadpool", 288 | ] 289 | 290 | [[package]] 291 | name = "fixedbitset" 292 | version = "0.4.2" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 295 | 296 | [[package]] 297 | name = "flate2" 298 | version = "1.0.24" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" 301 | dependencies = [ 302 | "crc32fast", 303 | "miniz_oxide", 304 | ] 305 | 306 | [[package]] 307 | name = "flume" 308 | version = "0.10.14" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" 311 | dependencies = [ 312 | "futures-core", 313 | "futures-sink", 314 | "nanorand", 315 | "pin-project", 316 | "spin", 317 | ] 318 | 319 | [[package]] 320 | name = "futures-core" 321 | version = "0.3.23" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" 324 | 325 | [[package]] 326 | name = "futures-sink" 327 | version = "0.3.23" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" 330 | 331 | [[package]] 332 | name = "getrandom" 333 | version = "0.2.7" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 336 | dependencies = [ 337 | "cfg-if", 338 | "js-sys", 339 | "libc", 340 | "wasi", 341 | "wasm-bindgen", 342 | ] 343 | 344 | [[package]] 345 | name = "gif" 346 | version = "0.11.4" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" 349 | dependencies = [ 350 | "color_quant", 351 | "weezl", 352 | ] 353 | 354 | [[package]] 355 | name = "half" 356 | version = "1.8.2" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 359 | 360 | [[package]] 361 | name = "hashbrown" 362 | version = "0.12.3" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 365 | 366 | [[package]] 367 | name = "heck" 368 | version = "0.3.3" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 371 | dependencies = [ 372 | "unicode-segmentation", 373 | ] 374 | 375 | [[package]] 376 | name = "hermit-abi" 377 | version = "0.1.19" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 380 | dependencies = [ 381 | "libc", 382 | ] 383 | 384 | [[package]] 385 | name = "image" 386 | version = "0.24.3" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" 389 | dependencies = [ 390 | "bytemuck", 391 | "byteorder", 392 | "color_quant", 393 | "exr", 394 | "gif", 395 | "jpeg-decoder", 396 | "num-rational", 397 | "num-traits", 398 | "png", 399 | "scoped_threadpool", 400 | "tiff", 401 | ] 402 | 403 | [[package]] 404 | name = "indexmap" 405 | version = "1.9.1" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 408 | dependencies = [ 409 | "autocfg", 410 | "hashbrown", 411 | ] 412 | 413 | [[package]] 414 | name = "iq" 415 | version = "0.1.0" 416 | dependencies = [ 417 | "anyhow", 418 | "clap 3.2.20", 419 | "image", 420 | "lalrpop", 421 | "lalrpop-util", 422 | "regex", 423 | "structopt", 424 | ] 425 | 426 | [[package]] 427 | name = "itertools" 428 | version = "0.10.3" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" 431 | dependencies = [ 432 | "either", 433 | ] 434 | 435 | [[package]] 436 | name = "jpeg-decoder" 437 | version = "0.2.6" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" 440 | dependencies = [ 441 | "rayon", 442 | ] 443 | 444 | [[package]] 445 | name = "js-sys" 446 | version = "0.3.59" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" 449 | dependencies = [ 450 | "wasm-bindgen", 451 | ] 452 | 453 | [[package]] 454 | name = "lalrpop" 455 | version = "0.19.8" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "b30455341b0e18f276fa64540aff54deafb54c589de6aca68659c63dd2d5d823" 458 | dependencies = [ 459 | "ascii-canvas", 460 | "atty", 461 | "bit-set", 462 | "diff", 463 | "ena", 464 | "itertools", 465 | "lalrpop-util", 466 | "petgraph", 467 | "pico-args", 468 | "regex", 469 | "regex-syntax", 470 | "string_cache", 471 | "term", 472 | "tiny-keccak", 473 | "unicode-xid", 474 | ] 475 | 476 | [[package]] 477 | name = "lalrpop-util" 478 | version = "0.19.8" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "bcf796c978e9b4d983414f4caedc9273aa33ee214c5b887bd55fde84c85d2dc4" 481 | dependencies = [ 482 | "regex", 483 | ] 484 | 485 | [[package]] 486 | name = "lazy_static" 487 | version = "1.4.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 490 | 491 | [[package]] 492 | name = "lebe" 493 | version = "0.5.2" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" 496 | 497 | [[package]] 498 | name = "libc" 499 | version = "0.2.131" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40" 502 | 503 | [[package]] 504 | name = "lock_api" 505 | version = "0.4.7" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 508 | dependencies = [ 509 | "autocfg", 510 | "scopeguard", 511 | ] 512 | 513 | [[package]] 514 | name = "log" 515 | version = "0.4.17" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 518 | dependencies = [ 519 | "cfg-if", 520 | ] 521 | 522 | [[package]] 523 | name = "memchr" 524 | version = "2.5.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 527 | 528 | [[package]] 529 | name = "memoffset" 530 | version = "0.6.5" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 533 | dependencies = [ 534 | "autocfg", 535 | ] 536 | 537 | [[package]] 538 | name = "miniz_oxide" 539 | version = "0.5.3" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" 542 | dependencies = [ 543 | "adler", 544 | ] 545 | 546 | [[package]] 547 | name = "nanorand" 548 | version = "0.7.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" 551 | dependencies = [ 552 | "getrandom", 553 | ] 554 | 555 | [[package]] 556 | name = "new_debug_unreachable" 557 | version = "1.0.4" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 560 | 561 | [[package]] 562 | name = "num-integer" 563 | version = "0.1.45" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 566 | dependencies = [ 567 | "autocfg", 568 | "num-traits", 569 | ] 570 | 571 | [[package]] 572 | name = "num-rational" 573 | version = "0.4.1" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" 576 | dependencies = [ 577 | "autocfg", 578 | "num-integer", 579 | "num-traits", 580 | ] 581 | 582 | [[package]] 583 | name = "num-traits" 584 | version = "0.2.15" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 587 | dependencies = [ 588 | "autocfg", 589 | ] 590 | 591 | [[package]] 592 | name = "num_cpus" 593 | version = "1.13.1" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 596 | dependencies = [ 597 | "hermit-abi", 598 | "libc", 599 | ] 600 | 601 | [[package]] 602 | name = "once_cell" 603 | version = "1.13.1" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" 606 | 607 | [[package]] 608 | name = "os_str_bytes" 609 | version = "6.3.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" 612 | 613 | [[package]] 614 | name = "parking_lot" 615 | version = "0.12.1" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 618 | dependencies = [ 619 | "lock_api", 620 | "parking_lot_core", 621 | ] 622 | 623 | [[package]] 624 | name = "parking_lot_core" 625 | version = "0.9.3" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 628 | dependencies = [ 629 | "cfg-if", 630 | "libc", 631 | "redox_syscall", 632 | "smallvec", 633 | "windows-sys", 634 | ] 635 | 636 | [[package]] 637 | name = "petgraph" 638 | version = "0.6.2" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" 641 | dependencies = [ 642 | "fixedbitset", 643 | "indexmap", 644 | ] 645 | 646 | [[package]] 647 | name = "phf_shared" 648 | version = "0.10.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 651 | dependencies = [ 652 | "siphasher", 653 | ] 654 | 655 | [[package]] 656 | name = "pico-args" 657 | version = "0.4.2" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468" 660 | 661 | [[package]] 662 | name = "pin-project" 663 | version = "1.0.12" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 666 | dependencies = [ 667 | "pin-project-internal", 668 | ] 669 | 670 | [[package]] 671 | name = "pin-project-internal" 672 | version = "1.0.12" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 675 | dependencies = [ 676 | "proc-macro2", 677 | "quote", 678 | "syn", 679 | ] 680 | 681 | [[package]] 682 | name = "png" 683 | version = "0.17.5" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" 686 | dependencies = [ 687 | "bitflags", 688 | "crc32fast", 689 | "deflate", 690 | "miniz_oxide", 691 | ] 692 | 693 | [[package]] 694 | name = "precomputed-hash" 695 | version = "0.1.1" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 698 | 699 | [[package]] 700 | name = "proc-macro-error" 701 | version = "1.0.4" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 704 | dependencies = [ 705 | "proc-macro-error-attr", 706 | "proc-macro2", 707 | "quote", 708 | "syn", 709 | "version_check", 710 | ] 711 | 712 | [[package]] 713 | name = "proc-macro-error-attr" 714 | version = "1.0.4" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 717 | dependencies = [ 718 | "proc-macro2", 719 | "quote", 720 | "version_check", 721 | ] 722 | 723 | [[package]] 724 | name = "proc-macro2" 725 | version = "1.0.43" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" 728 | dependencies = [ 729 | "unicode-ident", 730 | ] 731 | 732 | [[package]] 733 | name = "quote" 734 | version = "1.0.21" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 737 | dependencies = [ 738 | "proc-macro2", 739 | ] 740 | 741 | [[package]] 742 | name = "rayon" 743 | version = "1.5.3" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" 746 | dependencies = [ 747 | "autocfg", 748 | "crossbeam-deque", 749 | "either", 750 | "rayon-core", 751 | ] 752 | 753 | [[package]] 754 | name = "rayon-core" 755 | version = "1.9.3" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" 758 | dependencies = [ 759 | "crossbeam-channel", 760 | "crossbeam-deque", 761 | "crossbeam-utils", 762 | "num_cpus", 763 | ] 764 | 765 | [[package]] 766 | name = "redox_syscall" 767 | version = "0.2.16" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 770 | dependencies = [ 771 | "bitflags", 772 | ] 773 | 774 | [[package]] 775 | name = "redox_users" 776 | version = "0.4.3" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 779 | dependencies = [ 780 | "getrandom", 781 | "redox_syscall", 782 | "thiserror", 783 | ] 784 | 785 | [[package]] 786 | name = "regex" 787 | version = "1.6.0" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 790 | dependencies = [ 791 | "aho-corasick", 792 | "memchr", 793 | "regex-syntax", 794 | ] 795 | 796 | [[package]] 797 | name = "regex-syntax" 798 | version = "0.6.27" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 801 | 802 | [[package]] 803 | name = "rustversion" 804 | version = "1.0.9" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" 807 | 808 | [[package]] 809 | name = "scoped_threadpool" 810 | version = "0.1.9" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 813 | 814 | [[package]] 815 | name = "scopeguard" 816 | version = "1.1.0" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 819 | 820 | [[package]] 821 | name = "siphasher" 822 | version = "0.3.10" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 825 | 826 | [[package]] 827 | name = "smallvec" 828 | version = "1.9.0" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 831 | 832 | [[package]] 833 | name = "spin" 834 | version = "0.9.4" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" 837 | dependencies = [ 838 | "lock_api", 839 | ] 840 | 841 | [[package]] 842 | name = "string_cache" 843 | version = "0.8.4" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" 846 | dependencies = [ 847 | "new_debug_unreachable", 848 | "once_cell", 849 | "parking_lot", 850 | "phf_shared", 851 | "precomputed-hash", 852 | ] 853 | 854 | [[package]] 855 | name = "strsim" 856 | version = "0.8.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 859 | 860 | [[package]] 861 | name = "strsim" 862 | version = "0.10.0" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 865 | 866 | [[package]] 867 | name = "structopt" 868 | version = "0.3.26" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 871 | dependencies = [ 872 | "clap 2.34.0", 873 | "lazy_static", 874 | "structopt-derive", 875 | ] 876 | 877 | [[package]] 878 | name = "structopt-derive" 879 | version = "0.4.18" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 882 | dependencies = [ 883 | "heck", 884 | "proc-macro-error", 885 | "proc-macro2", 886 | "quote", 887 | "syn", 888 | ] 889 | 890 | [[package]] 891 | name = "syn" 892 | version = "1.0.99" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" 895 | dependencies = [ 896 | "proc-macro2", 897 | "quote", 898 | "unicode-ident", 899 | ] 900 | 901 | [[package]] 902 | name = "term" 903 | version = "0.7.0" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 906 | dependencies = [ 907 | "dirs-next", 908 | "rustversion", 909 | "winapi", 910 | ] 911 | 912 | [[package]] 913 | name = "termcolor" 914 | version = "1.1.3" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 917 | dependencies = [ 918 | "winapi-util", 919 | ] 920 | 921 | [[package]] 922 | name = "textwrap" 923 | version = "0.11.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 926 | dependencies = [ 927 | "unicode-width", 928 | ] 929 | 930 | [[package]] 931 | name = "textwrap" 932 | version = "0.15.0" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 935 | 936 | [[package]] 937 | name = "thiserror" 938 | version = "1.0.32" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" 941 | dependencies = [ 942 | "thiserror-impl", 943 | ] 944 | 945 | [[package]] 946 | name = "thiserror-impl" 947 | version = "1.0.32" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" 950 | dependencies = [ 951 | "proc-macro2", 952 | "quote", 953 | "syn", 954 | ] 955 | 956 | [[package]] 957 | name = "threadpool" 958 | version = "1.8.1" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" 961 | dependencies = [ 962 | "num_cpus", 963 | ] 964 | 965 | [[package]] 966 | name = "tiff" 967 | version = "0.7.3" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "7259662e32d1e219321eb309d5f9d898b779769d81b76e762c07c8e5d38fcb65" 970 | dependencies = [ 971 | "flate2", 972 | "jpeg-decoder", 973 | "weezl", 974 | ] 975 | 976 | [[package]] 977 | name = "tiny-keccak" 978 | version = "2.0.2" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 981 | dependencies = [ 982 | "crunchy", 983 | ] 984 | 985 | [[package]] 986 | name = "unicode-ident" 987 | version = "1.0.3" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" 990 | 991 | [[package]] 992 | name = "unicode-segmentation" 993 | version = "1.9.0" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 996 | 997 | [[package]] 998 | name = "unicode-width" 999 | version = "0.1.9" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1002 | 1003 | [[package]] 1004 | name = "unicode-xid" 1005 | version = "0.2.3" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" 1008 | 1009 | [[package]] 1010 | name = "vec_map" 1011 | version = "0.8.2" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1014 | 1015 | [[package]] 1016 | name = "version_check" 1017 | version = "0.9.4" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1020 | 1021 | [[package]] 1022 | name = "wasi" 1023 | version = "0.11.0+wasi-snapshot-preview1" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1026 | 1027 | [[package]] 1028 | name = "wasm-bindgen" 1029 | version = "0.2.82" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" 1032 | dependencies = [ 1033 | "cfg-if", 1034 | "wasm-bindgen-macro", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "wasm-bindgen-backend" 1039 | version = "0.2.82" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" 1042 | dependencies = [ 1043 | "bumpalo", 1044 | "log", 1045 | "once_cell", 1046 | "proc-macro2", 1047 | "quote", 1048 | "syn", 1049 | "wasm-bindgen-shared", 1050 | ] 1051 | 1052 | [[package]] 1053 | name = "wasm-bindgen-macro" 1054 | version = "0.2.82" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" 1057 | dependencies = [ 1058 | "quote", 1059 | "wasm-bindgen-macro-support", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "wasm-bindgen-macro-support" 1064 | version = "0.2.82" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" 1067 | dependencies = [ 1068 | "proc-macro2", 1069 | "quote", 1070 | "syn", 1071 | "wasm-bindgen-backend", 1072 | "wasm-bindgen-shared", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "wasm-bindgen-shared" 1077 | version = "0.2.82" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" 1080 | 1081 | [[package]] 1082 | name = "weezl" 1083 | version = "0.1.7" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" 1086 | 1087 | [[package]] 1088 | name = "winapi" 1089 | version = "0.3.9" 1090 | source = "registry+https://github.com/rust-lang/crates.io-index" 1091 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1092 | dependencies = [ 1093 | "winapi-i686-pc-windows-gnu", 1094 | "winapi-x86_64-pc-windows-gnu", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "winapi-i686-pc-windows-gnu" 1099 | version = "0.4.0" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1102 | 1103 | [[package]] 1104 | name = "winapi-util" 1105 | version = "0.1.5" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1108 | dependencies = [ 1109 | "winapi", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "winapi-x86_64-pc-windows-gnu" 1114 | version = "0.4.0" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1117 | 1118 | [[package]] 1119 | name = "windows-sys" 1120 | version = "0.36.1" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1123 | dependencies = [ 1124 | "windows_aarch64_msvc", 1125 | "windows_i686_gnu", 1126 | "windows_i686_msvc", 1127 | "windows_x86_64_gnu", 1128 | "windows_x86_64_msvc", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "windows_aarch64_msvc" 1133 | version = "0.36.1" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1136 | 1137 | [[package]] 1138 | name = "windows_i686_gnu" 1139 | version = "0.36.1" 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" 1141 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1142 | 1143 | [[package]] 1144 | name = "windows_i686_msvc" 1145 | version = "0.36.1" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1148 | 1149 | [[package]] 1150 | name = "windows_x86_64_gnu" 1151 | version = "0.36.1" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1154 | 1155 | [[package]] 1156 | name = "windows_x86_64_msvc" 1157 | version = "0.36.1" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1160 | --------------------------------------------------------------------------------