Skip to main content

starsheet/
lib.rs

1use rand::distributions::{Distribution, Uniform, WeightedIndex};
2
3/// The main structure exposed by starsheet.
4pub struct Space {
5	width: usize,
6	height: usize,
7	data: Vec<u8>
8}
9
10impl Space {
11	/// Create a new `Space` struct.
12	pub fn new(width: u32, height: u32) -> Self {
13		Self {
14			width: width as usize,
15			height: height as usize,
16			data: vec![0; (width * height) as usize]
17		}
18	}
19
20	/// Get the width of the image.
21	pub fn width(&self) -> u32 {
22		self.width as u32
23	}
24
25	// Get the height of the image.
26	pub fn height(&self) -> u32 {
27		self.height as u32
28	}
29
30	/// Fill space with a random assortmnt of stars. They will have varrying
31	/// brightness and positins.
32	pub fn fill_randomly(&mut self, density: u32) {
33		let mut rng = rand::thread_rng();
34		let rand_x = Uniform::from(0..self.width);
35		let rand_y = Uniform::from(0..self.height);
36
37		let weights = [755, 125, 90, 18, 6, 4, 2, 1];
38		let rand_z = WeightedIndex::new(&weights).expect("Failed to created intensity WeightedIndex");
39	
40		let starcount = ((self.width as f32 / 100f32) * (self.height as f32/ 100f32) * density as f32) as usize;
41
42		for _ in 0..starcount {
43			self.draw_star(
44				rand_x.sample(&mut rng),
45				rand_y.sample(&mut rng),
46				rand_z.sample(&mut rng)
47			);
48		}
49	}
50
51	/// Consumes itself and returns the raw image data. This data is greyscale,
52	/// so each u8 is a pixel.
53	pub fn to_data(self) -> Vec<u8> {
54		self.data
55	}
56
57	fn draw_star(&mut self, x: usize, y: usize, intensity: usize) {
58		if intensity == 0 {
59			let index = y * self.width + x;
60
61			*self.data.get_mut(index).unwrap() = 255;
62		} else {
63			let clamp = |val: isize, low: usize, high: usize| -> usize {
64				if val < low as isize {
65					low
66				} else if val > high as isize {
67					high
68				} else {
69					val as usize
70				}
71			};
72
73			let x1 = x as isize - intensity as isize;
74			let y1 = y as isize - intensity as isize;
75			let x2 = x + intensity;
76			let y2 = y + intensity;
77
78			// Draw the mainlines
79			self.linex(y, clamp(x1, 0, self.width-1), clamp(x2 as isize, 0, self.width-1), 255);
80			self.liney(x,clamp(y1, 0, self.height-1), clamp(y2 as isize, 0, self.height-1), 255);
81
82			// Draw the diagonals
83			if intensity >= 2 {
84				let x1 = x as isize - (intensity/2) as isize;
85				let y1 = y as isize - (intensity/2) as isize;
86				let x2 = x + (intensity/2);
87				let y2 = y + (intensity/2);
88
89				self.line(
90					clamp(x1, 0, self.width-1),
91					clamp(y1, 0, self.height-1),
92					clamp(x2 as isize, 0, self.width-1),
93					clamp(y2 as isize, 0, self.height-1),
94					255
95				);
96
97				self.line(
98					clamp(x2 as isize, 0, self.width-1),
99					clamp(y1, 0, self.height-1),
100					clamp(x1, 0, self.width-1),
101					clamp(y2 as isize, 0, self.height-1),
102					255
103				);
104			}
105
106			// Remove the center
107			*self.data.get_mut(y * self.width + x).unwrap() = 0;
108		}
109	}
110
111	fn linex(&mut self, y: usize, x1: usize, x2: usize, gray: u8) {
112		for x in x1..=x2 {
113			*self.data.get_mut(y * self.width + x).unwrap() = gray;
114		}
115	}
116
117	fn liney(&mut self, x: usize, y1: usize, y2: usize, gray: u8) {
118		for y in y1..=y2 {
119			*self.data.get_mut(y * self.width + x).unwrap() = gray;
120		}
121	}
122
123	fn line(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, gray: u8) {
124		let dx = x2 as isize - x1 as isize;
125		let dy = y2 as isize - y1 as isize;
126		let step = if dx.abs() > dy.abs() {
127			dx.abs()
128		} else {
129			dy.abs()
130		};
131
132		let dx = dx / step;
133		let dy = dy / step;
134		let mut x = x1 as isize;
135		let mut y = y1 as isize;
136		let mut i = 1;
137		loop {
138			if x > 0 && (x as usize) < self.width && y > 0 && (y as usize) < self.height {
139				*self.data.get_mut(y as usize * self.width + x as usize).unwrap() = gray;
140			}
141
142			if i > step {
143				break;
144			}
145
146			x = x + dx;
147			y = y + dy;
148			i += 1;
149		}
150	}
151}