diff --git a/Cargo.toml b/Cargo.toml index 48d30dc..b8aef1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,9 @@ [package] name = "polygon-art" -version = "1.0.0" +version = "1.0.1" authors = ["Ingolf Wagner "] edition = "2018" - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html description = "Convenience library to use geo to create art." documentation = "https://github.com/mrVanDalo/polygon-art" diff --git a/README.md b/README.md index df8f8cb..1085afc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,15 @@ Convenience library to create art using [geo](https://georust.org/). * load SVGs * scaling -# Asteroids +# Binaries + +I deliver some binaries to give you an impression and ideas +for your own images. + +All binaries created by polygon art have the same +command line interface. + +## Asteroids asteroids is an example binary which renders an image inspired by the @@ -14,12 +22,55 @@ image inspired by the ![image](./images/asteroids.png) -run `asteroids --help` to get information. -All binaries created by polygon art have the same -command line interface. +## Rings -# How to run examples +![image](./images/rings.png) + +# How to run /examples ``` cargo run --example clipping -- --help # run the examples/clipping -``` \ No newline at end of file +``` +# How to Build (with flakes) + +```shell +nix build +``` + +# How to us it (with flakes) + +``` nix +{ + description = "example usage"; + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-21.05"; + inputs.polygon-art.url = "git+https://git.ingolf-wagner.de/palo/polygon-art.git"; + inputs.polygon-art.inputs.nixpkgs.follows = "nixpkgs"; + outputs = { self, nixpkgs, polygon-art, ... }: { + nixosConfigurations.example = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + ({ pkgs, ... }: { + nixpkgs.overlays = [ + (_self: _super: { + polygon-art = polygon-art.packages.${pkgs.system}; + }) + ]; + environment.systemPackages = [ pkgs.polygon-art.polygon-art ]; + }) + ]; + }; + }; +} +``` + +# How to update + +``` shell +cargo update # to update Cargo.lock +``` +and + +``` shell +nix flake update # to update flake.lock +nix build # to verify if everything is ok +``` diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..432eec3 --- /dev/null +++ b/default.nix @@ -0,0 +1,36 @@ +{ rustPlatform, + lib, + cairo, + geos, + clipper, + clang, + pkg-config, + cmake, + openssl, + llvmPackages, + ... }: + +rustPlatform.buildRustPackage { + + pname = "polygon-art"; + version = "1.0.1"; + src = ./.; + cargoSha256 = "0sgk4hw77cxqbqzd258fz67r7fpjblkm7cqh14n5f1c43y8vgxa0"; + verifyCargoDeps = true; + + # Needed so bindgen can find libclang.so + LIBCLANG_PATH = "${llvmPackages.libclang.lib}/lib"; + + buildInputs = [ cairo geos clipper openssl ]; + + nativeBuildInputs = + [ cmake llvmPackages.clang llvmPackages.libclang pkg-config ]; + + meta = with lib; { + description = "Framework with examples to generate plotter friendly SVGs"; + homepage = "https://git.ingolf-wagner.de/palo/polygon-art.git"; + license = licenses.gpl3Plus; + maintainers = [ maintainers.mrVanDalo ]; + }; +} + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e4ba231 --- /dev/null +++ b/flake.lock @@ -0,0 +1,41 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1631561581, + "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1632855891, + "narHash": "sha256-crW76mt9/kbUBiKy/KiSnsQ9JEYgD3StDuYAMVkTbM0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "73086069ebd402e85eaa39c06aef33c2b917f532", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7c51ce4 --- /dev/null +++ b/flake.nix @@ -0,0 +1,15 @@ +{ + description = "Framework with examples to generate plotter friendly SVGs"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + outputs = { self, nixpkgs, flake-utils , ... }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + polygon-art = (pkgs.callPackage ./default.nix {}); + in { + packages.polygon-art = polygon-art; + defaultPackage = polygon-art; + devShell = import ./shell.nix { inherit pkgs; }; + } + ); +} diff --git a/images/rings.png b/images/rings.png new file mode 100644 index 0000000..427b4a7 Binary files /dev/null and b/images/rings.png differ diff --git a/src/bin/rings.rs b/src/bin/rings.rs new file mode 100644 index 0000000..a0b253e --- /dev/null +++ b/src/bin/rings.rs @@ -0,0 +1,55 @@ +use geo::algorithm::rotate::{Rotate, RotatePoint}; +use geo::algorithm::translate::Translate; +use geo::{polygon, Coordinate, MultiPolygon, Point}; +use geo_clipper::Clipper; +use polygon_art::{Context, MergeExt, TranslateExt}; +use rand::prelude::StdRng; +use rand::Rng; + +fn main() { + let mut context = Context::create(); + context.translate_center(); + + let min = f64::min(context.width, context.height); + let number_of_rings = (min / 150.) as i32; + + let mut radius = min / 2. - 5.; + for _ in 0..number_of_rings { + let ring_size = context.gen_rng().gen_range(10..(radius as i32 / 5)) as f64; + radius = radius - ring_size; + if radius < 10. { + break; + } + let polygons = generate_ring(&mut context.gen_rng(), radius, ring_size); + radius = radius - ring_size; + context.draw_multipolygon(&polygons); + } + + context.render(); +} + +fn generate_ring(rng: &mut StdRng, radius: f64, ring_size: f64) -> MultiPolygon { + let main = polygon![ + (x: 0.0, y:0.0), + (x: ring_size , y:0.0), + (x: ring_size , y:ring_size ), + (x: 0.0, y:ring_size ), + ] + .translate_center() + .rotate(30.) + .translate(0., radius); + + let center_point = Point(Coordinate { x: 0., y: 0. }); + let parts = rng.gen_range(20..120); + let rotation = 360. / parts as f64; + let direction = if rng.gen_bool(0.5) { -1. } else { 1. }; + let fragment = main.difference( + &main.rotate_around_point(direction * rotation, center_point), + 100., + ); + let mut polygons: MultiPolygon = MultiPolygon(Vec::new()); + for index in 0..parts { + polygons.merge(&fragment.rotate_around_point(rotation * index as f64, center_point)); + } + polygons +} diff --git a/src/context.rs b/src/context.rs index cdc64eb..4ea9986 100644 --- a/src/context.rs +++ b/src/context.rs @@ -4,12 +4,12 @@ use geo::{polygon, LineString, MultiLineString, MultiPolygon, Polygon}; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; -use crate::context::commandline::Opt; +use crate::context::command_line_options::Opt; use crate::context::palette::Palette; use crate::context::renderer::{PngRenderContainer, RenderContainer, SvgRenderContainer}; use iso8601::Date::*; -pub mod commandline; +pub mod command_line_options; pub mod palette; pub mod renderer; @@ -116,7 +116,8 @@ impl Context { let opt = Opt::get_command_line_arguments(); let line_size = opt.line_size.clone(); - let mm = 3.779527547619048; // calculated with inkscape 1587.40157 (px) / 420 (mm) (from a3) + // todo : this is not correct + let mm = 2.8346456693; // 1mm = 2.8346456693pt let (mut height, mut width) = match opt.format { None => (f64::from(opt.height.clone()), f64::from(opt.width.clone())), Some(format) => match &format[..] { @@ -128,6 +129,9 @@ impl Context { }, }; + print!("height: {}\n", height); + print!("width: {}\n", width); + if opt.orientation == "portrait" { let cache = height; height = width; @@ -150,11 +154,12 @@ impl Context { let mut rng = StdRng::seed_from_u64(random_seed); // color - let y: f32 = rng.gen(); // generates a float between 0 and 1 - let hue: f32 = y * 360.0; - let saturation: f32 = f32::max(rng.gen(), 0.6); - let value: f32 = f32::max(rng.gen(), 0.6); - let palette = Palette::dark_on_bright(Palette::color(hue, saturation, value)); + let main_color = Palette::random_color(StdRng::seed_from_u64(rng.gen())); + let palette = match &opt.color_type[..] { + "bright_on_dark" => Palette::bright_on_dark(main_color), + "dark_on_bright" => Palette::dark_on_bright(main_color), + _ => Palette::bright_on_dark(main_color), + }; let render_container: Box = if opt.output_type == "svg" { let svg_surface = SvgSurface::new( diff --git a/src/context/commandline.rs b/src/context/command_line_options.rs similarity index 88% rename from src/context/commandline.rs rename to src/context/command_line_options.rs index b246a6c..de4cc1d 100644 --- a/src/context/commandline.rs +++ b/src/context/command_line_options.rs @@ -53,6 +53,14 @@ pub struct Opt { /// (must be ISO8601) #[structopt(long = "random-date", conflicts_with = "random-seed", parse(try_from_str = iso8601::date))] pub random_seed_date: Option, + + /// how to use color palette to draw image + #[structopt( + long, + possible_values=&["dark_on_bright", "bright_on_dark"], + default_value="dark_on_bright" + )] + pub color_type: String, } impl Opt { diff --git a/src/context/palette.rs b/src/context/palette.rs index ad486cc..4639f36 100644 --- a/src/context/palette.rs +++ b/src/context/palette.rs @@ -1,5 +1,6 @@ //! Color palate generator. +use crate::RandomExt; use palette::rgb::Rgb; use palette::FromColor; use palette::Hsl; @@ -19,21 +20,18 @@ impl Palette { /// and to color less. #[allow(dead_code)] pub fn random_color(mut rng: StdRng) -> Rgb { - //let mut rng = rand::thread_rng(); - let y: f32 = rng.gen(); // generates a float between 0 and 1 - let hue: f32 = y * 360.0; - let saturation: f32 = f32::max(rng.gen(), 0.6); - let value: f32 = f32::max(rng.gen(), 0.6); - //Rgb::from_linear(Hsv::new(hue, saturation, value).into_rgb()) + let hue: f32 = rng.gen_interval(0., 360.); + let saturation: f32 = rng.gen_interval(0.9, 1.0); + let value: f32 = rng.gen_interval(0.8, 1.0); Palette::color(hue, saturation, value) } + pub fn color(hue: f32, saturation: f32, value: f32) -> Rgb { Rgb::from_linear(Hsv::new(hue, saturation, value).into_rgb()) } /// generate a palette from the tint and shade palette - /// algorithm. choose a dark background and a bright filling color - #[allow(dead_code)] + /// algorithm. choose a dark background, and a bright filling color pub fn bright_on_dark(input: Rgb) -> Palette { let tint_and_shade_palette = TintAndShadePalette::create(input); Palette { @@ -43,7 +41,7 @@ impl Palette { } /// generate a palette from the tint and shade palette - /// algorithm. choose a bright background and a dark filling color + /// algorithm. choose a bright background, and a dark filling color pub fn dark_on_bright(input: Rgb) -> Palette { let tint_and_shade_palette = TintAndShadePalette::create(input); Palette { @@ -54,8 +52,7 @@ impl Palette { } /// The Tint and Shade Palette from https://gitlab.com/cameralibre/tint-and-shade -/// Thank you Sam -#[allow(dead_code)] +/// Thank you, Sam struct TintAndShadePalette { base_color: Rgb, base_tint_30: Rgb, diff --git a/src/extensions.rs b/src/extensions.rs index f2166c7..59b8478 100644 --- a/src/extensions.rs +++ b/src/extensions.rs @@ -1,3 +1,4 @@ pub(crate) mod merge_ext; +pub(crate) mod random_ext; pub(crate) mod scale_ext; pub(crate) mod translate_ext; diff --git a/src/extensions/random_ext.rs b/src/extensions/random_ext.rs new file mode 100644 index 0000000..8fe99c4 --- /dev/null +++ b/src/extensions/random_ext.rs @@ -0,0 +1,49 @@ +use rand::rngs::StdRng; +use rand::Rng; + +pub trait RandomExt { + fn gen_interval(&mut self, min: T, max: T) -> T; +} + +impl RandomExt for StdRng { + fn gen_interval(&mut self, min: f32, max: f32) -> f32 { + if min == max { + return min; + } + let minimum = f32::min(min, max); + let maximum = f32::max(min, max); + let diff = maximum - minimum; + let x: f32 = self.gen(); + x * diff + minimum + } +} + +impl RandomExt for StdRng { + fn gen_interval(&mut self, min: f64, max: f64) -> f64 { + if min == max { + return min; + } + let minimum = f64::min(min, max); + let maximum = f64::max(min, max); + let diff = maximum - minimum; + let x: f64 = self.gen(); + x * diff + minimum + } +} + +#[cfg(test)] +mod test { + use rand::prelude::StdRng; + use rand::{Rng, SeedableRng}; + + #[test] + fn test_gen() { + let random_seed = rand::random(); + let mut rng = StdRng::seed_from_u64(random_seed); + for _ in 0..1000 { + let random_number: f64 = rng.gen(); + assert!(random_number < 1.); + assert!(random_number > 0.); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6923347..8e23a7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub use crate::context::Context; mod extensions; pub use crate::extensions::merge_ext::MergeExt; +pub use crate::extensions::random_ext::RandomExt; pub use crate::extensions::scale_ext::ScaleExt; pub use crate::extensions::translate_ext::TranslateExt;