Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
FredericaBernkastel committed Sep 18, 2021
2 parents a443225 + 5779afb commit bc4d5d6
Show file tree
Hide file tree
Showing 15 changed files with 397 additions and 287 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
/.idea
/test
/gallery
/Cargo.lock
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "space-filling"
version = "0.2.0"
version = "0.3.0"
description = "Generalized 2D space filling"
readme = "readme.md"
authors = ["Frederica Bernkastel <bernkastel.frederica@protonmail.com>"]
Expand All @@ -10,7 +10,7 @@ categories = ["science"]
keywords = ["generative-art"]
license = "GPL-3.0-only"
edition = "2018"
exclude = ["doc/*"]
exclude = ["LICENCE", "doc/*", "src/legacy/*"]

[lib]
name = "space_filling"
Expand Down
26 changes: 14 additions & 12 deletions examples/embedded.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use {
space_filling::{
geometry::{Circle, WorldSpace},
geometry::{Shape, Circle, WorldSpace, Scale, Translation},
error::Result,
sdf::{self, SDF},
argmax2d::{Argmax2D, ArgmaxResult},
drawing::{Draw, Shape}
drawing::Draw
},
euclid::Point2D,
euclid::{Point2D, Vector2D as V2},
image::{Luma, Pixel}
};

type AffineT<T> = Scale<Translation<T, f32>, f32>;

pub fn report_progress<'a>(iter: impl Iterator<Item = (ArgmaxResult<f32, WorldSpace>, &'a mut Argmax2D)>, nth: usize)
-> impl Iterator<Item = (ArgmaxResult<f32, WorldSpace>, &'a mut Argmax2D)> {
iter.enumerate()
Expand All @@ -22,7 +25,7 @@ pub fn report_progress<'a>(iter: impl Iterator<Item = (ArgmaxResult<f32, WorldSp

/// A regular distribution embedded in a random one
/// 88.4s, 100'000 circrles, Δ = 2^-14, chunk = 2^6
pub fn embedded(argmax: &mut Argmax2D) -> impl Iterator<Item = Circle<f32, WorldSpace>> + '_ {
pub fn embedded(argmax: &mut Argmax2D) -> impl Iterator<Item = AffineT<Circle>> + '_ {
use rand::prelude::*;
let mut rng = rand_pcg::Pcg64::seed_from_u64(1);

Expand All @@ -45,9 +48,8 @@ pub fn embedded(argmax: &mut Argmax2D) -> impl Iterator<Item = Circle<f32, World
let delta = argmax_ret.distance - r;
let offset = Point2D::from([angle.cos(), angle.sin()]) * delta;

Circle {
xy: (argmax_ret.point - offset).to_point(), r
}
Circle.translate(argmax_ret.point - offset)
.scale(V2::splat(r))
};

argmax.insert_sdf_domain(
Expand All @@ -61,13 +63,12 @@ pub fn embedded(argmax: &mut Argmax2D) -> impl Iterator<Item = Circle<f32, World
report_progress(
argmax.iter()
.min_dist_px(1.0 * std::f32::consts::SQRT_2)
.build()
.take(100000),
.build(),
1000
).map(|(argmax_ret, argmax)| {
let circle = Circle {
xy: argmax_ret.point, r: argmax_ret.distance / 3.0
};
let circle = Circle
.translate(argmax_ret.point.to_vector())
.scale(V2::splat(argmax_ret.distance / 3.0));

argmax.insert_sdf_domain(
Argmax2D::domain_empirical(argmax_ret.point, argmax_ret.distance),
Expand All @@ -83,6 +84,7 @@ fn main() -> Result<()> {
let mut argmax = Argmax2D::new(16384, 64)?;
let mut image = image::RgbaImage::new(16384, 16384);
embedded(&mut argmax)
.take(100000)
.for_each(|c| c.texture(Luma([255]).to_rgba()).draw(&mut image));
image.save(path)?;
open::that(path)?;
Expand Down
20 changes: 11 additions & 9 deletions examples/fractal_distribution.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
use {
space_filling::{
geometry::{Circle, WorldSpace},
geometry::{Shape, Circle, Translation, Scale},
error::Result,
sdf::{self, SDF},
argmax2d::Argmax2D,
drawing::{Draw, Shape}
drawing::Draw
},
image::{Luma, Pixel}
image::{Luma, Pixel},
euclid::{Vector2D as V2}
};

type AffineT<T> = Scale<Translation<T, f32>, f32>;

// 158ms, 1000 circles, Δ = 2^-10, chunk = 2^4
fn fractal_distribution(argmax: &mut Argmax2D) -> impl Iterator<Item = Circle<f32, WorldSpace>> + '_ {
fn fractal_distribution(argmax: &mut Argmax2D) -> impl Iterator<Item = AffineT<Circle>> + '_ {
argmax.insert_sdf(sdf::boundary_rect);

argmax.iter().build()
.map(|(argmax_ret, argmax)| {
let circle = Circle {
xy: argmax_ret.point,
r: argmax_ret.distance / 4.0
};
let circle = Circle
.translate(argmax_ret.point.to_vector())
.scale(V2::splat(argmax_ret.distance / 4.0));
argmax.insert_sdf_domain(
Argmax2D::domain_empirical(argmax_ret.point, argmax_ret.distance),
|pixel| circle.sdf(pixel)
Expand All @@ -40,4 +42,4 @@ fn main() -> Result<()> {
image.save(path)?;
open::that(path)?;
Ok(())
}
}
4 changes: 2 additions & 2 deletions examples/image_dataset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use {
space_filling::{
error::Result,
argmax2d::Argmax2D,
drawing::{self, DrawSync, Shape},
geometry::BoundingBox
drawing::{self, DrawSync},
geometry::{Shape, BoundingBox}
},
embedded::embedded,
image::RgbaImage
Expand Down
25 changes: 13 additions & 12 deletions examples/polymorphic.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use {
std::sync::Arc,
space_filling::{
geometry::Circle,
geometry::{Shape, Circle, Square},
error::Result,
sdf,
argmax2d::Argmax2D,
drawing::{self, Shape, DrawSync}
drawing::{self, DrawSync}
},
image::{RgbaImage, Rgba, DynamicImage},
euclid::{Rect, Vector2D, Size2D, Point2D, Angle}
euclid::{Vector2D as V2, Point2D, Angle}
};

// 174ms, 1000 circles, Δ = 2^-10, chunk = 2^4
Expand Down Expand Up @@ -36,16 +36,17 @@ fn polymorphic(argmax: &mut Argmax2D, texture: Arc<DynamicImage>) -> impl Iterat
let delta = argmax_ret.distance - r;
let offset = Point2D::from([angle.cos(), angle.sin()]) * delta;

Circle {
xy: (argmax_ret.point - offset).to_point(), r
}
}.texture(texture.clone())),
Circle
.translate(argmax_ret.point - offset)
.scale(V2::splat(r))
.texture(texture.clone())
}),

1 | _ => Box::new(Rect {
origin: argmax_ret.point - Vector2D::splat(argmax_ret.distance / 2.0),
size: Size2D::splat(argmax_ret.distance / 1.0)
}.rotate(Angle::degrees(rng.gen_range::<f32, _>(0.0..45.0)))
.texture(Rgba([(argmax_ret.distance.sqrt() * 255.0) as u8, 32, 128, 255])))
1 | _ => Box::new(Square
.translate(argmax_ret.point.to_vector())
.scale(V2::splat(argmax_ret.distance / 2.0))
.rotate(Angle::degrees(rng.gen_range::<f32, _>(0.0..45.0)))
.texture(Rgba([(argmax_ret.distance.sqrt() * 255.0) as u8, 32, 128, 255])))

};
argmax.insert_sdf_domain(
Expand Down
15 changes: 8 additions & 7 deletions examples/random_distribution.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use {
space_filling::{
geometry::{Circle, WorldSpace},
geometry::{Shape, Circle, Translation, Scale},
error::Result,
sdf::{self, SDF},
argmax2d::Argmax2D,
drawing::{Draw, Shape}
drawing::Draw
},
image::{Luma, Pixel},
euclid::Point2D
euclid::{Point2D, Vector2D as V2}
};

type AffineT<T> = Scale<Translation<T, f32>, f32>;

// 104ms, 1000 circrles, Δ = 2^-10, chunk = 2^4
fn random_distribution(argmax: &mut Argmax2D) -> impl Iterator<Item = Circle<f32, WorldSpace>> + '_ {
fn random_distribution(argmax: &mut Argmax2D) -> impl Iterator<Item = AffineT<Circle>> + '_ {
use rand::prelude::*;
let mut rng = rand_pcg::Pcg64::seed_from_u64(0);

Expand All @@ -32,9 +34,8 @@ fn random_distribution(argmax: &mut Argmax2D) -> impl Iterator<Item = Circle<f32
// polar to cartesian
let offset = Point2D::from([angle.cos(), angle.sin()]) * delta;

Circle {
xy: (argmax_ret.point - offset).to_point(), r
}
Circle.translate(argmax_ret.point - offset)
.scale(V2::splat(r))
};
argmax.insert_sdf_domain(
Argmax2D::domain_empirical(argmax_ret.point, argmax_ret.distance),
Expand Down
47 changes: 32 additions & 15 deletions src/drawing/impl_draw_rgbaimage.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(non_snake_case)]
use {
std::{thread, sync::Arc},
std::{thread, sync::Arc, ops::Fn},
euclid::{Point2D, Rect, Size2D},
image::{
DynamicImage, GenericImageView, Pixel, Rgba, RgbaImage,
Expand All @@ -9,58 +9,76 @@ use {
crate::{
drawing::{Draw, DrawSync, Shape, Texture, rescale_bounding_box},
error::Result,
geometry::{BoundingBox, PixelSpace},
geometry::{BoundingBox, PixelSpace, WorldSpace},
sdf::SDF
}
};

impl <Cutie> Draw<RgbaImage> for Texture<Cutie, Rgba<u8>>
where Cutie: Shape + Clone
{
fn draw(&self, image: &mut RgbaImage) {
self.shape.clone()
.texture(|_| self.texture)
.draw(image);
}
}

impl <'a, Cutie> Draw<RgbaImage> for Texture<Cutie, &'a DynamicImage>
where Cutie: Shape
{
fn draw(&self, image: &mut RgbaImage) {
let resolution: Size2D<_, PixelSpace> = image.dimensions().into();
let (bounding_box, offset, min_side) = rescale_bounding_box(self.bounding_box(), resolution);
let (bounding_box, offset, min_side) = rescale_bounding_box(self.shape.bounding_box(), resolution);
let bounding_box = match bounding_box {
Some(x) => x,
None => return // bounding box has no intersection with screen at all
None => return
};
let Δp = 1.0 / min_side;
let tex = rescale_texture(self.texture, bounding_box.size());

itertools::iproduct!(bounding_box.y_range(), bounding_box.x_range())
.map(|(y, x)| Point2D::<_, PixelSpace>::from([x, y]))
.for_each(|pixel| {
let pixel_world = ((pixel.to_f32() - offset).to_vector() * Δp)
let pixel_world = ((pixel.to_f32() - offset).to_vector() / min_side)
.cast_unit().to_point();
let tex_px = pixel - bounding_box.min.to_vector();
let tex_px = tex.get_pixel(tex_px.x, tex_px.y);

let sdf = self.sdf(pixel_world);
let pixel = image.get_pixel_mut(pixel.x, pixel.y);
*pixel = sdf_overlay_aa(sdf, Δp, *pixel, self.texture);
*pixel = sdf_overlay_aa(sdf, Δp, *pixel, tex_px);
});
}
}

impl <'a, Cutie> Draw<RgbaImage> for Texture<Cutie, &'a DynamicImage>
where Cutie: Shape
/// F: Fn(pixel: Point2D) -> Rgba<u8>
/// where pixel is in normalized texture coordinates.
impl <Cutie, F> Draw<RgbaImage> for Texture<Cutie, F>
where Cutie: Shape,
F: Fn(Point2D<f32, WorldSpace>) -> Rgba<u8>
{
fn draw(&self, image: &mut RgbaImage) {
let resolution: Size2D<_, PixelSpace> = image.dimensions().into();
let (bounding_box, offset, min_side) = rescale_bounding_box(self.shape.bounding_box(), resolution);
let (bounding_box, offset, min_side) = rescale_bounding_box(self.bounding_box(), resolution);
let bounding_box = match bounding_box {
Some(x) => x,
None => return
None => return // bounding box has no intersection with screen at all
};
let Δp = 1.0 / min_side;
let tex = rescale_texture(self.texture, bounding_box.size());
let tex_scale = bounding_box.size().width.min(bounding_box.size().height) as f32;

itertools::iproduct!(bounding_box.y_range(), bounding_box.x_range())
.map(|(y, x)| Point2D::<_, PixelSpace>::from([x, y]))
.for_each(|pixel| {
let pixel_world = ((pixel.to_f32() - offset).to_vector() / min_side)
.cast_unit().to_point();
let tex_px = pixel - bounding_box.min.to_vector();
let tex_px = tex.get_pixel(tex_px.x, tex_px.y);

let sdf = self.sdf(pixel_world);

let tex_px = ((pixel - bounding_box.min.to_vector())
.to_f32() / tex_scale).cast_unit();
let tex_px = (self.texture)(tex_px);

let pixel = image.get_pixel_mut(pixel.x, pixel.y);
*pixel = sdf_overlay_aa(sdf, Δp, *pixel, tex_px);
});
Expand Down Expand Up @@ -115,7 +133,6 @@ fn sdf_overlay_aa(sdf: f32, Δp: f32, col1: Rgba<u8>, mut col2: Rgba<u8>) -> Rgb
col2.0[3] = ((col2.0[3] as f32) * alpha) as u8;
col2.blend(&col1);
col2
//col1.map2(&col2, |p1, p2| p1.max( p2))
}

/// Draw shapes, parallel.
Expand Down

0 comments on commit bc4d5d6

Please sign in to comment.