Merge branch 'release/1.0.1'
This commit is contained in:
commit
a83f3c782e
13 changed files with 285 additions and 27 deletions
|
@ -1,10 +1,9 @@
|
||||||
[package]
|
[package]
|
||||||
name = "polygon-art"
|
name = "polygon-art"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
authors = ["Ingolf Wagner <contact@ingolf-wagner.de>"]
|
authors = ["Ingolf Wagner <contact@ingolf-wagner.de>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# 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."
|
description = "Convenience library to use geo to create art."
|
||||||
documentation = "https://github.com/mrVanDalo/polygon-art"
|
documentation = "https://github.com/mrVanDalo/polygon-art"
|
||||||
|
|
63
README.md
63
README.md
|
@ -6,7 +6,15 @@ Convenience library to create art using [geo](https://georust.org/).
|
||||||
* load SVGs
|
* load SVGs
|
||||||
* scaling
|
* 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
|
asteroids is an example binary which renders an
|
||||||
image inspired by the
|
image inspired by the
|
||||||
|
@ -14,12 +22,55 @@ image inspired by the
|
||||||
|
|
||||||
![image](./images/asteroids.png)
|
![image](./images/asteroids.png)
|
||||||
|
|
||||||
run `asteroids --help` to get information.
|
## Rings
|
||||||
All binaries created by polygon art have the same
|
|
||||||
command line interface.
|
|
||||||
|
|
||||||
# How to run examples
|
![image](./images/rings.png)
|
||||||
|
|
||||||
|
# How to run /examples
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo run --example clipping -- --help # run the examples/clipping
|
cargo run --example clipping -- --help # run the examples/clipping
|
||||||
```
|
```
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
36
default.nix
Normal file
36
default.nix
Normal file
|
@ -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 ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
41
flake.lock
Normal file
41
flake.lock
Normal file
|
@ -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
|
||||||
|
}
|
15
flake.nix
Normal file
15
flake.nix
Normal file
|
@ -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; };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
BIN
images/rings.png
Normal file
BIN
images/rings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 173 KiB |
55
src/bin/rings.rs
Normal file
55
src/bin/rings.rs
Normal file
|
@ -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<f64> {
|
||||||
|
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<f64> = MultiPolygon(Vec::new());
|
||||||
|
for index in 0..parts {
|
||||||
|
polygons.merge(&fragment.rotate_around_point(rotation * index as f64, center_point));
|
||||||
|
}
|
||||||
|
polygons
|
||||||
|
}
|
|
@ -4,12 +4,12 @@ use geo::{polygon, LineString, MultiLineString, MultiPolygon, Polygon};
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
|
|
||||||
use crate::context::commandline::Opt;
|
use crate::context::command_line_options::Opt;
|
||||||
use crate::context::palette::Palette;
|
use crate::context::palette::Palette;
|
||||||
use crate::context::renderer::{PngRenderContainer, RenderContainer, SvgRenderContainer};
|
use crate::context::renderer::{PngRenderContainer, RenderContainer, SvgRenderContainer};
|
||||||
use iso8601::Date::*;
|
use iso8601::Date::*;
|
||||||
|
|
||||||
pub mod commandline;
|
pub mod command_line_options;
|
||||||
pub mod palette;
|
pub mod palette;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
|
|
||||||
|
@ -116,7 +116,8 @@ impl Context {
|
||||||
let opt = Opt::get_command_line_arguments();
|
let opt = Opt::get_command_line_arguments();
|
||||||
let line_size = opt.line_size.clone();
|
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 {
|
let (mut height, mut width) = match opt.format {
|
||||||
None => (f64::from(opt.height.clone()), f64::from(opt.width.clone())),
|
None => (f64::from(opt.height.clone()), f64::from(opt.width.clone())),
|
||||||
Some(format) => match &format[..] {
|
Some(format) => match &format[..] {
|
||||||
|
@ -128,6 +129,9 @@ impl Context {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
print!("height: {}\n", height);
|
||||||
|
print!("width: {}\n", width);
|
||||||
|
|
||||||
if opt.orientation == "portrait" {
|
if opt.orientation == "portrait" {
|
||||||
let cache = height;
|
let cache = height;
|
||||||
height = width;
|
height = width;
|
||||||
|
@ -150,11 +154,12 @@ impl Context {
|
||||||
let mut rng = StdRng::seed_from_u64(random_seed);
|
let mut rng = StdRng::seed_from_u64(random_seed);
|
||||||
|
|
||||||
// color
|
// color
|
||||||
let y: f32 = rng.gen(); // generates a float between 0 and 1
|
let main_color = Palette::random_color(StdRng::seed_from_u64(rng.gen()));
|
||||||
let hue: f32 = y * 360.0;
|
let palette = match &opt.color_type[..] {
|
||||||
let saturation: f32 = f32::max(rng.gen(), 0.6);
|
"bright_on_dark" => Palette::bright_on_dark(main_color),
|
||||||
let value: f32 = f32::max(rng.gen(), 0.6);
|
"dark_on_bright" => Palette::dark_on_bright(main_color),
|
||||||
let palette = Palette::dark_on_bright(Palette::color(hue, saturation, value));
|
_ => Palette::bright_on_dark(main_color),
|
||||||
|
};
|
||||||
|
|
||||||
let render_container: Box<dyn RenderContainer> = if opt.output_type == "svg" {
|
let render_container: Box<dyn RenderContainer> = if opt.output_type == "svg" {
|
||||||
let svg_surface = SvgSurface::new(
|
let svg_surface = SvgSurface::new(
|
||||||
|
|
|
@ -53,6 +53,14 @@ pub struct Opt {
|
||||||
/// (must be ISO8601)
|
/// (must be ISO8601)
|
||||||
#[structopt(long = "random-date", conflicts_with = "random-seed", parse(try_from_str = iso8601::date))]
|
#[structopt(long = "random-date", conflicts_with = "random-seed", parse(try_from_str = iso8601::date))]
|
||||||
pub random_seed_date: Option<iso8601::Date>,
|
pub random_seed_date: Option<iso8601::Date>,
|
||||||
|
|
||||||
|
/// 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 {
|
impl Opt {
|
|
@ -1,5 +1,6 @@
|
||||||
//! Color palate generator.
|
//! Color palate generator.
|
||||||
|
|
||||||
|
use crate::RandomExt;
|
||||||
use palette::rgb::Rgb;
|
use palette::rgb::Rgb;
|
||||||
use palette::FromColor;
|
use palette::FromColor;
|
||||||
use palette::Hsl;
|
use palette::Hsl;
|
||||||
|
@ -19,21 +20,18 @@ impl Palette {
|
||||||
/// and to color less.
|
/// and to color less.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn random_color(mut rng: StdRng) -> Rgb {
|
pub fn random_color(mut rng: StdRng) -> Rgb {
|
||||||
//let mut rng = rand::thread_rng();
|
let hue: f32 = rng.gen_interval(0., 360.);
|
||||||
let y: f32 = rng.gen(); // generates a float between 0 and 1
|
let saturation: f32 = rng.gen_interval(0.9, 1.0);
|
||||||
let hue: f32 = y * 360.0;
|
let value: f32 = rng.gen_interval(0.8, 1.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())
|
|
||||||
Palette::color(hue, saturation, value)
|
Palette::color(hue, saturation, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn color(hue: f32, saturation: f32, value: f32) -> Rgb {
|
pub fn color(hue: f32, saturation: f32, value: f32) -> Rgb {
|
||||||
Rgb::from_linear(Hsv::new(hue, saturation, value).into_rgb())
|
Rgb::from_linear(Hsv::new(hue, saturation, value).into_rgb())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generate a palette from the tint and shade palette
|
/// generate a palette from the tint and shade palette
|
||||||
/// algorithm. choose a dark background and a bright filling color
|
/// algorithm. choose a dark background, and a bright filling color
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn bright_on_dark(input: Rgb) -> Palette {
|
pub fn bright_on_dark(input: Rgb) -> Palette {
|
||||||
let tint_and_shade_palette = TintAndShadePalette::create(input);
|
let tint_and_shade_palette = TintAndShadePalette::create(input);
|
||||||
Palette {
|
Palette {
|
||||||
|
@ -43,7 +41,7 @@ impl Palette {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generate a palette from the tint and shade 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 {
|
pub fn dark_on_bright(input: Rgb) -> Palette {
|
||||||
let tint_and_shade_palette = TintAndShadePalette::create(input);
|
let tint_and_shade_palette = TintAndShadePalette::create(input);
|
||||||
Palette {
|
Palette {
|
||||||
|
@ -54,8 +52,7 @@ impl Palette {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Tint and Shade Palette from https://gitlab.com/cameralibre/tint-and-shade
|
/// The Tint and Shade Palette from https://gitlab.com/cameralibre/tint-and-shade
|
||||||
/// Thank you Sam
|
/// Thank you, Sam
|
||||||
#[allow(dead_code)]
|
|
||||||
struct TintAndShadePalette {
|
struct TintAndShadePalette {
|
||||||
base_color: Rgb,
|
base_color: Rgb,
|
||||||
base_tint_30: Rgb,
|
base_tint_30: Rgb,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
pub(crate) mod merge_ext;
|
pub(crate) mod merge_ext;
|
||||||
|
pub(crate) mod random_ext;
|
||||||
pub(crate) mod scale_ext;
|
pub(crate) mod scale_ext;
|
||||||
pub(crate) mod translate_ext;
|
pub(crate) mod translate_ext;
|
||||||
|
|
49
src/extensions/random_ext.rs
Normal file
49
src/extensions/random_ext.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
use rand::rngs::StdRng;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
pub trait RandomExt<T> {
|
||||||
|
fn gen_interval(&mut self, min: T, max: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RandomExt<f32> 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<f64> 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.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ pub use crate::context::Context;
|
||||||
|
|
||||||
mod extensions;
|
mod extensions;
|
||||||
pub use crate::extensions::merge_ext::MergeExt;
|
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::scale_ext::ScaleExt;
|
||||||
pub use crate::extensions::translate_ext::TranslateExt;
|
pub use crate::extensions::translate_ext::TranslateExt;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue