1use std::io;
4use std::path::Path;
5
6#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
8pub struct Pixel {
9 pub r: u8,
11 pub g: u8,
13 pub b: u8,
15}
16
17impl Pixel {
18 pub const BLACK: Self = Self::rgb(0, 0, 0);
20 pub const WHITE: Self = Self::rgb(255, 255, 255);
22
23 pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
25 Self { r, g, b }
26 }
27}
28
29#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct Image {
32 pub width: usize,
34 pub height: usize,
36 pixels: Vec<Pixel>,
37}
38
39impl Image {
40 pub fn new(width: usize, height: usize) -> Self {
42 Self::from_color(width, height, Pixel::BLACK)
43 }
44
45 pub fn new_black(width: usize, height: usize) -> Self {
47 Self::from_color(width, height, Pixel::BLACK)
48 }
49
50 pub fn new_white(width: usize, height: usize) -> Self {
52 Self::from_color(width, height, Pixel::WHITE)
53 }
54
55 pub fn from_color(width: usize, height: usize, color: Pixel) -> Self {
57 let pixels = vec![color; pixel_count(width, height)];
58 Self {
59 width,
60 height,
61 pixels,
62 }
63 }
64
65 pub fn from_pixels(width: usize, height: usize, pixels: Vec<Pixel>) -> Self {
67 assert_eq!(
68 pixels.len(),
69 pixel_count(width, height),
70 "pixel count must match image dimensions"
71 );
72 Self {
73 width,
74 height,
75 pixels,
76 }
77 }
78
79 pub fn from_pixel_fn(
81 width: usize,
82 height: usize,
83 mut pixel_fn: impl FnMut(usize, usize) -> Pixel,
84 ) -> Self {
85 let mut pixels = Vec::with_capacity(pixel_count(width, height));
86 for y in 0..height {
87 for x in 0..width {
88 pixels.push(pixel_fn(x, y));
89 }
90 }
91 Self::from_pixels(width, height, pixels)
92 }
93
94 pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
96 crate::ppm::read(path)
97 }
98
99 pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
101 crate::ppm::write(self, path)
102 }
103
104 pub fn from_file(filename: &str) -> io::Result<Self> {
106 Self::open(filename)
107 }
108
109 pub fn to_file(&self, filename: &str) -> io::Result<()> {
111 self.save(filename)
112 }
113
114 pub fn from_image(image: &Self) -> Self {
116 image.clone()
117 }
118
119 pub fn pixels(&self) -> &[Pixel] {
121 &self.pixels
122 }
123
124 pub fn get_pixel(&self, x: usize, y: usize) -> Option<&Pixel> {
126 self.pixel_index(x, y).map(|index| &self.pixels[index])
127 }
128
129 pub fn set_pixel(&mut self, x: usize, y: usize, pixel: Pixel) {
131 if let Some(index) = self.pixel_index(x, y) {
132 self.pixels[index] = pixel;
133 }
134 }
135
136 fn pixel_index(&self, x: usize, y: usize) -> Option<usize> {
137 (x < self.width && y < self.height).then_some(y * self.width + x)
138 }
139}
140
141fn pixel_count(width: usize, height: usize) -> usize {
142 width
143 .checked_mul(height)
144 .expect("image dimensions overflow")
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn pixels_are_row_major_and_bounds_checked() {
153 let mut image = Image::new_white(2, 2);
154 image.set_pixel(1, 0, Pixel::rgb(1, 2, 3));
155 image.set_pixel(2, 0, Pixel::BLACK);
156
157 assert_eq!(image.get_pixel(1, 0), Some(&Pixel::rgb(1, 2, 3)));
158 assert_eq!(image.get_pixel(0, 1), Some(&Pixel::WHITE));
159 assert_eq!(image.get_pixel(2, 0), None);
160 assert_eq!(image.pixels().len(), 4);
161 }
162
163 #[test]
164 #[should_panic(expected = "pixel count must match image dimensions")]
165 fn from_pixels_rejects_wrong_pixel_count() {
166 Image::from_pixels(2, 2, vec![Pixel::BLACK]);
167 }
168}