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]
|
||||
name = "polygon-art"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
authors = ["Ingolf Wagner <contact@ingolf-wagner.de>"]
|
||||
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"
|
||||
|
|
63
README.md
63
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
|
||||
```
|
||||
```
|
||||
# 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::{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<dyn RenderContainer> = if opt.output_type == "svg" {
|
||||
let svg_surface = SvgSurface::new(
|
||||
|
|
|
@ -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<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 {
|
|
@ -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,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub(crate) mod merge_ext;
|
||||
pub(crate) mod random_ext;
|
||||
pub(crate) mod scale_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;
|
||||
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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue