polygon-art/src/context.rs

196 lines
6.3 KiB
Rust

use cairo::Format;
use cairo::{ImageSurface, SvgSurface};
use geo::{polygon, LineString, MultiLineString, MultiPolygon, Polygon};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use crate::context::command_line_options::Opt;
use crate::context::palette::Palette;
use crate::context::renderer::{PngRenderContainer, RenderContainer, SvgRenderContainer};
use iso8601::Date::*;
pub mod command_line_options;
pub mod palette;
pub mod renderer;
pub struct Context {
render_container: Box<dyn RenderContainer>,
pub height: f64,
pub width: f64,
pub line_size: f64,
pub pseudo_random_number_generator: Box<StdRng>,
pub seed: u64,
}
impl Context {
/// generate a frame for the image
pub fn frame(&mut self, left: f64, bottom: f64, right: f64, top: f64) -> Polygon<f64> {
polygon![
(x: left, y: top),
(x: self.width - right, y: top),
(x: self.width - right, y: self.height - bottom),
(x: left , y: self.height - bottom),
]
}
/// a fall back operation, if you need a function not provided by this struct.
pub fn cairo_context(&self) -> &cairo::Context {
self.render_container.context()
}
pub fn draw_multipolygon(&self, multi_polygon: &MultiPolygon<f64>) {
for polygon in multi_polygon.iter() {
self.draw_polygon(polygon);
}
}
pub fn draw_polygon(&self, polygon: &Polygon<f64>) {
self.draw_line_string(polygon.exterior());
for interior in polygon.interiors() {
self.draw_line_string(interior);
}
}
pub fn draw_line_string(&self, line_string: &LineString<f64>) {
let context = self.render_container.context();
let mut init = true;
for point in line_string.clone().into_points() {
if init {
context.move_to(point.x(), point.y());
init = false;
} else {
context.line_to(point.x(), point.y());
}
}
context.stroke();
}
pub fn draw_line_strings(&self, line_strings: &Vec<LineString<f64>>) {
for lines in line_strings {
self.draw_line_string(&lines);
}
}
pub fn draw_multiline_string(&self, multilinestring: &MultiLineString<f64>) {
for line in multilinestring.iter() {
self.draw_line_string(line);
}
}
/// restore state which was saved before by the context
pub fn restore(&self) {
self.render_container.context().restore();
}
/// save state to restore later again
pub fn save(&self) {
self.render_container.context().save();
}
/// scale, but line with stays
pub fn scale(&self, factor: f64) {
let context = self.render_container.context();
context.scale(factor, factor);
context.set_line_width(1. / factor);
}
/// translate to the center of the image
pub fn translate_center(&self) {
self.translate(self.width / 2., self.height / 2.);
}
/// translate to a position
pub fn translate(&self, tx: f64, ty: f64) {
let context = self.render_container.context();
context.translate(tx, ty);
}
pub fn render(self) {
self.render_container.render();
}
pub fn gen_rng(&mut self) -> StdRng {
StdRng::seed_from_u64(self.pseudo_random_number_generator.gen())
}
pub fn create() -> Context {
let opt = Opt::get_command_line_arguments();
let line_size = opt.line_size.clone();
// 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[..] {
"a3" => (297. * mm, 420. * mm),
"a4" => (210. * mm, 297. * mm),
"a5" => (148. * mm, 210. * mm),
"x220" => (768., 1366.),
_ => (f64::from(opt.height.clone()), f64::from(opt.width.clone())),
},
};
print!("height: {}\n", height);
print!("width: {}\n", width);
if opt.orientation == "portrait" {
let cache = height;
height = width;
width = cache;
}
let random_seed = match opt.random_seed {
Some(random_seed) => random_seed,
None => match opt.random_seed_date {
Some(YMD { year, month, day }) => {
((10000 + year) as u32 * 365 + month * 12 + day) as u64
}
Some(Week { year, ww, d }) => ((10000 + year) as u32 * 365 + ww * 7 + d) as u64,
Some(Ordinal { year, ddd }) => ((10000 + year) as u32 * 365 + ddd) as u64,
None => rand::random(),
},
};
println!("random seed {}", random_seed);
let mut rng = StdRng::seed_from_u64(random_seed);
// color
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(
f64::from(width),
f64::from(height),
Some(opt.output.as_path()),
)
.expect("Can't svg surface");
Box::new(SvgRenderContainer::new(svg_surface, palette)) as Box<dyn RenderContainer>
} else if opt.output_type == "png" {
let png_surface = ImageSurface::create(Format::Rgb24, width as i32, height as i32)
.expect("Can't create png surface");
Box::new(PngRenderContainer::new(
opt.output.to_string_lossy().to_string(),
png_surface,
palette,
)) as Box<dyn RenderContainer>
} else {
panic!("output type unknown");
};
render_container.init(width as f64, height as f64, line_size);
Context {
height,
width,
line_size,
pseudo_random_number_generator: Box::new(rng),
render_container,
seed: random_seed,
}
}
}