use piet::{Color, RenderContext, kurbo::Rect};
use piet_common::Device;
use piet_common::kurbo::{Point, Size};
use rand::{prelude::*, random};
use rand_distr::Normal;
const WIDTH: usize = 1920;
const HEIGHT: usize = 1080;
const DPI: f64 = 96.;
fn main() {
let mut device = Device::new().unwrap();
let mut bitmap = device.bitmap_target(WIDTH, HEIGHT, 1.0).unwrap();
let mut rc = bitmap.render_context();
Mondrian {
split_count: ((WIDTH + HEIGHT) as f64 * 0.5 * 0.02) as usize,
border: 0.5,
min_gap: 0.3,
skip_prob: 0.6,
color_proportion: 0.2,
stroke_width: 0.1,
colors: vec![
Color::BLACK,
Color::rgb8(19, 86, 162),
Color::rgb8(247, 216, 66),
Color::rgb8(212, 9, 32),
],
white: Color::rgb8(242, 245, 241),
}
.generate(Size::new(WIDTH as f64, HEIGHT as f64), &mut rc);
rc.finish().unwrap();
std::mem::drop(rc);
bitmap
.save_to_file("temp-image.png")
.expect("file save error");
}
struct Mondrian {
split_count: usize,
border: f64,
min_gap: f64,
skip_prob: f64,
color_proportion: f64,
stroke_width: f64,
colors: Vec<Color>,
white: Color,
}
impl Mondrian {
fn generate(&self, size: Size, ctx: &mut impl RenderContext) {
let mut rects = vec![ColorRect {
color: self.white,
rect: Rect::new(0., 0., size.width, size.height).inset(-self.border * DPI),
}];
let mut rng = thread_rng();
for _ in 0..self.split_count {
let coord = Point {
x: rng.r#gen::<f64>() * size.width,
y: rng.r#gen::<f64>() * size.height,
};
rects = rects
.into_iter()
.flat_map(|rect| rect.intersect(coord, self.min_gap * DPI, self.skip_prob))
.collect();
}
let proportion_each_color = self.color_proportion / self.colors.len() as f64;
rects.shuffle(&mut rng);
let mut i = 0;
let n = Normal::new(
(proportion_each_color * rects.len() as f64).ln(),
0.2f64,
)
.unwrap();
'a: for color in &self.colors {
for _ in 0..(n.sample(&mut rng).exp().round() as usize) {
rects[i].color = *color;
i += 1;
if i >= rects.len() {
break 'a;
}
}
}
for rect in &rects {
ctx.fill(rect.rect, &rect.color);
}
for rect in rects {
ctx.stroke(rect.rect, &Color::BLACK, self.stroke_width * DPI);
}
}
}
struct ColorRect {
color: Color,
rect: Rect,
}
impl ColorRect {
fn intersect(self, p: Point, min_gap: f64, skip_prob: f64) -> impl Iterator<Item = ColorRect> {
self.intersect_x(p.x, min_gap, skip_prob)
.flat_map(move |rect| rect.intersect_y(p.y, min_gap, skip_prob))
}
fn intersect_x(self, x: f64, min_gap: f64, skip_prob: f64) -> impl Iterator<Item = ColorRect> {
if self.rect.x0 + min_gap < x
&& x < self.rect.x1 - min_gap
&& random::<f64>() <= (1. - skip_prob)
{
Either::Left(IntoIterator::into_iter([
ColorRect {
color: self.color,
rect: Rect {
x0: self.rect.x0,
y0: self.rect.y0,
x1: x,
y1: self.rect.y1,
},
},
ColorRect {
color: self.color,
rect: Rect {
x0: x,
y0: self.rect.y0,
x1: self.rect.x1,
y1: self.rect.y1,
},
},
]))
} else {
Either::Right(IntoIterator::into_iter([self]))
}
}
fn intersect_y(self, y: f64, min_gap: f64, skip_prob: f64) -> impl Iterator<Item = ColorRect> {
if self.rect.y0 + min_gap < y
&& y < self.rect.y1 - min_gap
&& random::<f64>() <= (1. - skip_prob)
{
Either::Left(IntoIterator::into_iter([
ColorRect {
color: self.color,
rect: Rect {
x0: self.rect.x0,
y0: self.rect.y0,
x1: self.rect.x1,
y1: y,
},
},
ColorRect {
color: self.color,
rect: Rect {
x0: self.rect.x0,
y0: y,
x1: self.rect.x1,
y1: self.rect.y1,
},
},
]))
} else {
Either::Right(IntoIterator::into_iter([self]))
}
}
}
enum Either<T, U> {
Left(T),
Right(U),
}
impl<Item, T, U> Iterator for Either<T, U>
where
T: Iterator<Item = Item>,
U: Iterator<Item = Item>,
{
type Item = Item;
fn next(&mut self) -> Option<Self::Item> {
match self {
Either::Left(t) => t.next(),
Either::Right(u) => u.next(),
}
}
}