imgsyn/src/lib.rs
2026-02-09 18:25:27 +01:00

230 lines
7.6 KiB
Rust

mod canvas;
mod ops;
mod pixel;
mod pixfn;
pub mod polar_math;
pub use canvas::*;
pub use pixel::*;
pub use pixfn::*;
use crate::ops::{
ImgSynArea, ImgSynAxial, ImgSynMap, ImgSynMapArea, ImgSynPolar, ImgSynRect, ImgSynStretched,
ImgSynUnmapRect,
};
pub trait ImgSynPixFn<T>: Clone {
fn run(&self, meta: Canvas, x: f64, y: f64, pixel: Pixel) -> T;
}
pub trait ImgSyn: Clone {
fn getpx(&self, meta: Canvas, x: f64, y: f64) -> Pixel;
fn map<F: ImgSynPixFn<Pixel>>(self, f: F) -> ImgSynMap<Self, F> {
ImgSynMap(self, f)
}
fn area<C: ImgSynPixFn<bool>, ImgB: ImgSyn, F: FnOnce(Self) -> ImgB>(
self,
condition: C,
f: F,
) -> ImgSynArea<Self, ImgB, C> {
ImgSynArea(self.clone(), f(self), condition)
}
fn mapped_rect<ImgB: ImgSyn, F: FnOnce(ImgSynUnmapRect<Self>) -> ImgB>(
self,
begin_x: f64,
begin_y: f64,
size_x: f64,
size_y: f64,
f: F,
) -> ImgSynRect<Self, ImgB> {
let rect = (begin_x, begin_y, size_x, size_y);
ImgSynRect(self.clone(), f(ImgSynUnmapRect(self, rect)), rect)
}
fn add_image<ImgB: ImgSyn>(
self,
begin_x: f64,
begin_y: f64,
size_x: f64,
size_y: f64,
img: ImgB,
) -> ImgSynRect<Self, ImgB> {
let rect = (begin_x, begin_y, size_x, size_y);
ImgSynRect(self, img, rect)
}
fn map_area<C: ImgSynPixFn<bool>, F: ImgSynPixFn<Pixel>>(
self,
condition: C,
f: F,
) -> ImgSynMapArea<Self, C, F> {
ImgSynMapArea(self, condition, f)
}
/// Makes future operations work on polar coordinates when terminated by .axial()
///
/// **MUST** be used before a subsequent .axial()
fn polar(self) -> ImgSynAxial<Self> {
ImgSynAxial(self)
}
/// Makes future operations work on axial coordinates when .polar() was used previously
///
/// **MUST** be used after .polar()
fn axial(self) -> ImgSynPolar<Self> {
ImgSynPolar(self)
}
fn stretched_by(self, x: f64, y: f64) -> ImgSynStretched<Self> {
ImgSynStretched(self, (x, y))
}
fn paint(self, meta: Canvas) -> PaintedCanvas {
PaintedCanvas::new(meta, self)
}
fn rgba255(self, meta: Canvas) -> Vec<u8> {
let painted = self.paint(meta);
PaintedCanvas::rgba255(&painted)
}
#[cfg(feature = "png")]
fn png(self, meta: Canvas) -> Vec<u8> {
let painted = self.paint(meta);
let mut data = Vec::new();
{
let mut encoder = png::Encoder::new(&mut data, meta.width as u32, meta.height as u32);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().unwrap();
writer.write_image_data(&painted.rgba255(meta)).unwrap();
writer.finish().unwrap();
}
data
}
}
#[cfg(test)]
mod test {
use std::{fs, hint::black_box};
use crate::{Canvas, ImgSyn, PixFnFloat, Pixel, polar_math::distance_from_circle};
#[test]
fn test() {
let canvas = Canvas::new(1000., 1000., Pixel::new_black());
fs::write(
"test.png",
canvas
.polar()
.map(|_, y: f64, pix: Pixel| pix.with_r((y - 0.5).abs() * 2.))
.map(|_, y, pix: Pixel| pix.with_b(y))
.axial()
.map(PixFnFloat(|_, _, y, pix| pix.with_g(y)))
.map_area(
PixFnFloat(|_, x, y, _| x < 0.2 && y < 0.2),
PixFnFloat(|_, _, _, pix| pix.with_g(1.0)),
)
.area(PixFnFloat(|_, _, _, px| px.g > 0.8 && px.r < 0.3), |img| {
img.polar()
.map(PixFnFloat(|_, x, _, pix| pix.with_r(x).with_b(0.0)))
.axial()
})
.mapped_rect(0.3, 0.3, 0.4, 0.4, |img| {
img.map(PixFnFloat(|_, x, y, px| {
let thickness = 0.01;
if !(thickness..=1. - thickness).contains(&x)
|| !(thickness..=1. - thickness).contains(&y)
{
return Pixel::new_white();
}
px
}))
})
.paint(canvas)
.png(canvas),
)
.unwrap();
}
#[test]
fn graph() {
let canvas = Canvas::new(1000., 1000., Pixel::new_white());
fs::write(
"graph.png",
canvas
.add_image(
0.07,
0.03,
0.9,
0.9,
Canvas::new(900., 900., Pixel::new_black()).map_area(
|x, y, _| distance_from_circle(x, y, 1.0, 0.0, 0.5, 1.0, 1.0) > 0.0,
|x, y, px: Pixel| px.with_r(x).with_g(1. - y),
),
)
.map_area(
|x, y, _| {
let xc = 0.07;
let yc = 0.93;
let t = 0.003;
(xc - t..=xc + t).contains(&x) && y >= 0.03
|| (yc - t..=yc + t).contains(&y) && x <= 0.97
},
|x, y, px: Pixel| {
if (1. + x - y) % 0.03 <= 0.02 {
px.with_rgb(1.0, 0.0, 0.5)
} else {
px.with_rgb(1.0, 1.0, 0.0)
}
},
)
.mapped_rect(0.02, 0.93, 0.05, 0.05, |img| {
img.map_area(
|x: f64, y: f64, _| {
distance_from_circle(x, y, 0.6, 0.5, 0.2, 1.0, 1.4).abs() < 0.03
},
|_, _, px: Pixel| px.with_rgb_of(0.0),
)
})
.paint(canvas)
.png(canvas),
)
.unwrap();
}
#[test]
fn speed() {
let canvas = Canvas::new(10000., 10000., Pixel::new_white());
black_box(
canvas
.add_image(
0.07,
0.03,
0.9,
0.9,
Canvas::new(9000., 9000., Pixel::new_black()).map_area(
|x, y, _| distance_from_circle(x, y, 1.0, 0.0, 0.5, 1.0, 1.0) > 0.0,
|x, y, px: Pixel| px.with_r(x).with_g(1. - y),
),
)
.map_area(
|x, y, _| {
let xc = 0.07;
let yc = 0.93;
let t = 0.003;
(xc - t..=xc + t).contains(&x) && y >= 0.03
|| (yc - t..=yc + t).contains(&y) && x <= 0.97
},
|x, y, px: Pixel| {
if (1. + x - y) % 0.03 <= 0.02 {
px.with_rgb(1.0, 0.0, 0.5)
} else {
px.with_rgb(1.0, 1.0, 0.0)
}
},
)
.mapped_rect(0.02, 0.93, 0.05, 0.05, |img| {
img.map_area(
|x: f64, y: f64, _| {
distance_from_circle(x, y, 0.6, 0.5, 0.2, 1.0, 1.4).abs() < 0.03
},
|_, _, px: Pixel| px.with_rgb_of(0.0),
)
})
.paint(canvas),
);
}
}