mod draw;
mod export;
mod gamma;
mod pattern;
pub use crate::export::{EncoderError, ImageFormat, Window, WindowSpans};
use crate::gamma::GammaCurve8;
use crate::pattern::AiryPattern;
pub type Pixel = u16;
pub type Point = (f32, f32);
pub type Vector = (f32, f32);
pub type Matrix = [[f32; 2]; 2];
pub type Matrix23 = [[f32; 3]; 2];
#[derive(Debug, Clone, Copy)]
pub struct SpotShape {
pub xx: f32,
pub xy: f32,
pub yx: f32,
pub yy: f32,
}
#[derive(Debug, Clone, Copy)]
pub struct Transform {
pub xx: f32,
pub xy: f32,
pub yx: f32,
pub yy: f32,
pub tx: f32,
pub ty: f32,
}
pub type SpotId = usize;
#[derive(Debug, Clone, Copy)]
struct SpotRec {
position: Point,
offset: Vector,
intensity: f32,
illumination: f32,
shape: SpotShape,
shape_inv: SpotShape,
}
pub struct Canvas {
width: u32,
height: u32,
background: Pixel,
spots: Vec<SpotRec>,
transform: Transform,
brightness: f32,
pixbuf: Vec<Pixel>,
pattern: AiryPattern,
gamma_curve: GammaCurve8,
}
impl Default for SpotShape {
fn default() -> Self {
SpotShape {
xx: 1.0,
xy: 0.0,
yx: 0.0,
yy: 1.0,
}
}
}
impl From<f32> for SpotShape {
fn from(size: f32) -> Self {
Self::default().scale(size)
}
}
impl From<(f32, f32)> for SpotShape {
fn from(kxy: (f32, f32)) -> Self {
Self::default().stretch(kxy.0, kxy.1)
}
}
impl From<Matrix> for SpotShape {
fn from(mat: Matrix) -> Self {
SpotShape {
xx: mat[0][0],
xy: mat[0][1],
yx: mat[1][0],
yy: mat[1][1],
}
}
}
impl std::fmt::Display for SpotShape {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"[[{}, {}], [{}, {}]]",
self.xx, self.xy, self.yx, self.yy
)
}
}
impl Default for Transform {
fn default() -> Self {
Transform {
xx: 1.0,
xy: 0.0,
yx: 0.0,
yy: 1.0,
tx: 0.0,
ty: 0.0,
}
}
}
impl From<f32> for Transform {
fn from(k: f32) -> Self {
Self::default().scale(k)
}
}
impl From<Vector> for Transform {
fn from(shift: Vector) -> Self {
Self::default().translate(shift)
}
}
impl From<Matrix> for Transform {
fn from(mat: Matrix) -> Self {
Transform {
xx: mat[0][0],
xy: mat[0][1],
yx: mat[1][0],
yy: mat[1][1],
tx: 0.0,
ty: 0.0,
}
}
}
impl From<Matrix23> for Transform {
fn from(mat: Matrix23) -> Self {
Transform {
xx: mat[0][0],
xy: mat[0][1],
yx: mat[1][0],
yy: mat[1][1],
tx: mat[0][2],
ty: mat[1][2],
}
}
}
impl std::fmt::Display for Transform {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"[[{}, {}, {}], [{}, {}, {}]]",
self.xx, self.xy, self.tx, self.yx, self.yy, self.ty
)
}
}
impl SpotShape {
#[must_use]
pub fn scale(&self, k: f32) -> SpotShape {
let xx = k * self.xx;
let xy = k * self.xy;
let yx = k * self.yx;
let yy = k * self.yy;
SpotShape { xx, xy, yx, yy }
}
#[must_use]
pub fn stretch(&self, kx: f32, ky: f32) -> SpotShape {
let xx = kx * self.xx;
let xy = kx * self.xy;
let yx = ky * self.yx;
let yy = ky * self.yy;
SpotShape { xx, xy, yx, yy }
}
#[must_use]
pub fn rotate(&self, phi: f32) -> SpotShape {
let phi_rad = (std::f32::consts::PI / 180.0) * phi;
let (s, c) = phi_rad.sin_cos();
let xx = c * self.xx - s * self.yx;
let yx = c * self.yx + s * self.xx;
let xy = c * self.xy - s * self.yy;
let yy = c * self.yy + s * self.xy;
SpotShape { xx, xy, yx, yy }
}
}
impl Transform {
#[must_use]
pub fn translate(&self, shift: Vector) -> Transform {
let mut transform = *self;
transform.tx += shift.0;
transform.ty += shift.1;
transform
}
#[must_use]
pub fn scale(&self, k: f32) -> Transform {
let xx = k * self.xx;
let xy = k * self.xy;
let yx = k * self.yx;
let yy = k * self.yy;
let tx = k * self.tx;
let ty = k * self.ty;
Transform {
xx,
xy,
yx,
yy,
tx,
ty,
}
}
#[must_use]
pub fn stretch(&self, kx: f32, ky: f32) -> Transform {
let xx = kx * self.xx;
let xy = kx * self.xy;
let yx = ky * self.yx;
let yy = ky * self.yy;
let tx = kx * self.tx;
let ty = ky * self.ty;
Transform {
xx,
xy,
yx,
yy,
tx,
ty,
}
}
#[must_use]
pub fn rotate(&self, phi: f32) -> Transform {
let phi_rad = (std::f32::consts::PI / 180.0) * phi;
let (s, c) = phi_rad.sin_cos();
let xx = c * self.xx - s * self.yx;
let yx = c * self.yx + s * self.xx;
let xy = c * self.xy - s * self.yy;
let yy = c * self.yy + s * self.xy;
let tx = c * self.tx - s * self.ty;
let ty = c * self.ty + s * self.tx;
Transform {
xx,
xy,
yx,
yy,
tx,
ty,
}
}
#[must_use]
pub fn compose(&self, t: Transform) -> Transform {
let xx = self.xx * t.xx + self.yx * t.xy;
let xy = self.xy * t.xx + self.yy * t.xy;
let yx = self.yx * t.yy + self.xx * t.yx;
let yy = self.yy * t.yy + self.xy * t.yx;
let tx = self.tx * t.xx + self.ty * t.xy + t.tx;
let ty = self.ty * t.yy + self.tx * t.yx + t.ty;
Transform {
xx,
xy,
yx,
yy,
tx,
ty,
}
}
#[must_use]
fn apply(&self, p: Point) -> Point {
let x = p.0 * self.xx + p.1 * self.xy + self.tx;
let y = p.1 * self.yy + p.0 * self.yx + self.ty;
(x, y)
}
}
impl Canvas {
#[must_use]
pub fn new(width: u32, height: u32) -> Self {
let background = 0;
let spots = Vec::with_capacity(8);
let transform = Transform::default();
let brightness = 1.0;
let pixbuf = vec![0; (width * height) as usize];
let pattern = AiryPattern::new();
let gamma_curve = GammaCurve8::new();
Canvas {
width,
height,
background,
spots,
transform,
brightness,
pixbuf,
pattern,
gamma_curve,
}
}
pub fn add_spot(&mut self, position: Point, shape: SpotShape, intensity: f32) -> SpotId {
let offset = (0.0, 0.0);
let illumination = 1.0;
let shape_inv = shape.invert();
let spot = SpotRec {
position,
offset,
intensity,
illumination,
shape,
shape_inv,
};
let id = self.spots.len();
self.spots.push(spot);
id
}
#[must_use]
pub fn spot_position(&self, spot: SpotId) -> Option<Point> {
let view_transform = |s: &SpotRec| {
let world_pos = ((s.position.0 + s.offset.0), (s.position.1 + s.offset.1));
self.transform.apply(world_pos)
};
self.spots.get(spot).map(view_transform)
}
#[must_use]
pub fn spot_intensity(&self, spot: SpotId) -> Option<f32> {
self.spots
.get(spot)
.map(|s| s.intensity * s.illumination * self.brightness)
}
pub fn set_spot_offset(&mut self, spot: SpotId, offset: Vector) {
if let Some(s) = self.spots.get_mut(spot) {
s.offset = offset;
}
}
pub fn set_spot_illumination(&mut self, spot: SpotId, illumination: f32) {
if let Some(s) = self.spots.get_mut(spot) {
s.illumination = illumination;
}
}
pub fn clear(&mut self) {
self.pixbuf.fill(self.background);
}
pub fn draw(&mut self) {
self.clear();
if self.brightness <= 0.0 {
return;
}
for spot_id in 0..self.spots.len() {
self.draw_spot(spot_id);
}
}
#[must_use]
pub fn pixels(&self) -> &[Pixel] {
&self.pixbuf
}
#[must_use]
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn set_background(&mut self, level: Pixel) {
self.background = level;
}
pub fn set_view_transform(&mut self, transform: Transform) {
self.transform = transform;
}
pub fn set_brightness(&mut self, brightness: f32) {
self.brightness = brightness;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_canvas() {
let w = 16;
let h = 16;
let c = Canvas::new(w, h);
assert_eq!(c.width, w);
assert_eq!(c.height, h);
let sz = c.pixels().len();
assert_eq!(sz, (w * h) as usize);
let dim = c.dimensions();
assert_eq!(dim, (w, h));
}
#[test]
fn create_shapes() {
let s1 = SpotShape::default();
assert_eq!(s1.to_string(), "[[1, 0], [0, 1]]");
let s2 = s1.scale(2.0);
assert_eq!(s2.to_string(), "[[2, 0], [0, 2]]");
let s3 = s2.stretch(2.0, 3.0);
assert_eq!(s3.to_string(), "[[4, 0], [0, 6]]");
let s4 = s3.rotate(90.0);
assert!(s4.xx.abs() < 1e-4, "xx = {}", s4.xx);
assert!((s4.xy - (-6.0)).abs() < 1e-4, "xy = {}", s4.xy);
assert!((s4.yx - 4.0).abs() < 1e-4, "yx = {}", s4.yx);
assert!(s4.yy.abs() < 1e-4, "yy = {}", s4.yy);
}
#[test]
fn convert_shapes() {
let s1 = SpotShape::from(1.0);
assert_eq!(s1.to_string(), "[[1, 0], [0, 1]]");
let s2: SpotShape = 2.0.into();
assert_eq!(s2.to_string(), "[[2, 0], [0, 2]]");
let s3 = SpotShape::from((2.0, 3.0)).scale(2.0);
assert_eq!(s3.to_string(), "[[4, 0], [0, 6]]");
let s4 = SpotShape::from([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(s4.to_string(), "[[1, 2], [3, 4]]");
}
#[test]
fn add_spots() {
let shape = SpotShape::default();
let mut c = Canvas::new(16, 16);
let spot1 = c.add_spot((1.1, 4.3), shape, 0.5);
let spot2 = c.add_spot((4.6, 7.2), shape, 0.4);
assert_eq!(spot1, 0);
assert_eq!(spot2, 1);
}
#[test]
fn clear_canvas() {
let mut c = Canvas::new(16, 16);
assert_eq!(c.pixels()[0], 0);
c.set_background(100);
c.clear();
assert_eq!(c.pixels()[0], 100);
c.set_background(200);
c.draw();
assert_eq!(c.pixels()[0], 200);
}
#[test]
fn move_spots() {
let shape = SpotShape::default();
let mut c = Canvas::new(16, 16);
let spot1 = c.add_spot((1.1, 4.3), shape, 0.5);
let spot2 = c.add_spot((4.6, 7.2), shape, 0.4);
assert_eq!(c.spot_position(spot1), Some((1.1, 4.3)));
assert_eq!(c.spot_intensity(spot2), Some(0.4));
c.set_spot_offset(spot1, (-3.2, 4.2));
c.set_spot_illumination(spot2, 1.3);
assert_eq!(c.spot_position(spot1), Some((1.1 - 3.2, 4.3 + 4.2)));
assert_eq!(c.spot_intensity(spot2), Some(0.4 * 1.3));
c.set_spot_offset(55, (1.1, 1.2));
c.set_spot_illumination(33, 0.0);
}
#[test]
fn create_transform() {
let t1 = Transform::default().translate((3.5, -4.25));
assert_eq!(t1.to_string(), "[[1, 0, 3.5], [0, 1, -4.25]]");
let t2 = Transform::default().scale(3.5).translate((1.0, 2.0));
assert_eq!(t2.to_string(), "[[3.5, 0, 1], [0, 3.5, 2]]");
let t3 = t2.stretch(3.5, 4.0);
assert_eq!(t3.to_string(), "[[12.25, 0, 3.5], [0, 14, 8]]");
let t4 = t3.compose(t1);
assert_eq!(t4.to_string(), "[[12.25, 0, 7], [0, 14, 3.75]]");
let t5 = t4.compose(t2);
assert_eq!(t5.to_string(), "[[42.875, 0, 25.5], [0, 49, 15.125]]");
let p = t5.apply((1.0, 1.0));
assert_eq!(p, (68.375, 64.125));
}
#[test]
fn convert_transforms() {
let t1 = Transform::from(1.0);
assert_eq!(t1.to_string(), "[[1, 0, 0], [0, 1, 0]]");
let t2: Transform = 2.0.into();
assert_eq!(t2.to_string(), "[[2, 0, 0], [0, 2, 0]]");
let t3 = Transform::from((2.0, 3.0)).scale(2.0);
assert_eq!(t3.to_string(), "[[2, 0, 4], [0, 2, 6]]");
let t4 = Transform::from([[1.0, 2.0], [3.0, 4.0]]);
assert_eq!(t4.to_string(), "[[1, 2, 0], [3, 4, 0]]");
let t5 = Transform::from([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
assert_eq!(t5.to_string(), "[[1, 2, 3], [4, 5, 6]]");
}
#[test]
fn view_transform() {
let shape = SpotShape::default();
let mut c = Canvas::new(16, 16);
let spot1 = c.add_spot((1.1, 4.3), shape, 0.5);
let spot2 = c.add_spot((4.6, 7.2), shape, 0.4);
assert_eq!(c.spot_position(spot1), Some((1.1, 4.3)));
let transform = Transform::default().translate((3.2, -4.8));
c.set_view_transform(transform);
assert_eq!(c.spot_position(spot1), Some((1.1 + 3.2, 4.3 - 4.8)));
assert_eq!(c.spot_position(spot2), Some((4.6 + 3.2, 7.2 - 4.8)));
}
#[test]
fn draw_spots() {
let shape = SpotShape::default().scale(2.5);
let mut c = Canvas::new(32, 32);
let spot1 = c.add_spot((1.1, 4.3), shape, 0.5);
let spot2 = c.add_spot((4.6, 7.2), shape, 0.9);
let spot3 = c.add_spot((17.3, 25.8), shape, 1.2);
let spot4 = c.add_spot((30.6, 10.1), shape, 0.7);
c.set_background(1000);
c.draw();
assert_eq!(c.pixels()[32 * 4 + 1], 31876);
assert_eq!(c.pixels()[32 * 7 + 5], 53389);
assert_eq!(c.pixels()[32 * 26 + 17], 65535);
assert_eq!(c.pixels()[32 * 10 + 30], 37774);
c.set_spot_offset(spot3, (-13.2, 6.2));
for s in [spot1, spot2, spot3, spot4] {
c.set_spot_illumination(s, 1.5);
}
c.draw();
assert_eq!(c.pixels()[32 * 4 + 1], 47314);
assert_eq!(c.pixels()[32 * 7 + 5], 65535);
assert_eq!(c.pixels()[32 * 26 + 17], 1000);
assert_eq!(c.pixels()[32 * 10 + 30], 56161);
assert_eq!(c.pixels()[32 * 31 + 3], 27742);
}
}