use crate::error::Error;
pub const MIN_DIMENSION: usize = 32;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Photo {
pub(crate) img_data: Vec<u8>,
pub(crate) width: usize,
pub(crate) height: usize,
}
impl Default for Photo {
fn default() -> Photo {
Photo {
img_data: Vec::new(),
width: 0,
height: 0,
}
}
}
impl Photo {
pub fn from_rgba(width: usize, height: usize, data: Vec<u8>) -> Result<Photo, Error> {
let expected = width
.checked_mul(height)
.and_then(|pixels| pixels.checked_mul(4))
.ok_or(Error::BufferLength {
expected: usize::MAX,
actual: data.len(),
})?;
if data.len() != expected {
return Err(Error::BufferLength {
expected,
actual: data.len(),
});
}
Ok(Photo {
img_data: data,
width,
height,
})
}
pub fn from_rgb(width: usize, height: usize, data: &[u8]) -> Result<Photo, Error> {
let expected = width
.checked_mul(height)
.and_then(|pixels| pixels.checked_mul(3))
.ok_or(Error::BufferLength {
expected: usize::MAX,
actual: data.len(),
})?;
if data.len() != expected {
return Err(Error::BufferLength {
expected,
actual: data.len(),
});
}
let mut rgba = Vec::with_capacity(width * height * 4);
for pixel in data.chunks_exact(3) {
rgba.extend_from_slice(pixel);
rgba.push(255);
}
Ok(Photo {
img_data: rgba,
width,
height,
})
}
#[inline]
pub fn width(&self) -> usize {
self.width
}
#[inline]
pub fn height(&self) -> usize {
self.height
}
#[inline]
pub fn as_rgba(&self) -> &[u8] {
&self.img_data
}
pub fn into_rgba(self) -> Vec<u8> {
self.img_data
}
pub fn pixel(&self, x: usize, y: usize) -> Option<[u8; 4]> {
if x >= self.width || y >= self.height {
return None;
}
let index = (y * self.width + x) * 4;
Some([
self.img_data[index],
self.img_data[index + 1],
self.img_data[index + 2],
self.img_data[index + 3],
])
}
pub(crate) fn validate(&self) -> Result<(), Error> {
if self.width == 0 || self.height == 0 {
return Err(Error::EmptyPhoto);
}
if self.width < MIN_DIMENSION || self.height < MIN_DIMENSION {
return Err(Error::PhotoTooSmall {
dimensions: (self.width, self.height),
minimum: MIN_DIMENSION,
});
}
Ok(())
}
pub(crate) fn get_rgb(&self, x: usize, y: usize) -> (u8, u8, u8) {
if x >= self.width || y >= self.height {
(0, 0, 255) } else {
let index = (y * self.width + x) * 4;
let r = self.img_data[index];
let g = self.img_data[index + 1];
let b = self.img_data[index + 2];
(r, g, b)
}
}
pub fn scaled_to_width(&self, new_width: usize) -> Photo {
if new_width == 0 {
panic!("The new width must be greater than 0");
}
if self.width == 0 || self.height == 0 {
return Photo::default();
}
let scale_factor = new_width as f32 / self.width as f32;
let new_height = (self.height as f32 * scale_factor).round() as usize;
let mut new_img_data = vec![0u8; new_width * new_height * 4];
for new_y in 0..new_height {
for new_x in 0..new_width {
let orig_x_start = ((new_x as f32) / scale_factor).round() as usize;
let orig_y_start = ((new_y as f32) / scale_factor).round() as usize;
let orig_x_end = (((new_x + 1) as f32) / scale_factor).round() as usize;
let orig_y_end = (((new_y + 1) as f32) / scale_factor).round() as usize;
let orig_x_start = orig_x_start.min(self.width - 1);
let orig_y_start = orig_y_start.min(self.height - 1);
let orig_x_end = orig_x_end.min(self.width - 1).max(orig_x_start);
let orig_y_end = orig_y_end.min(self.height - 1).max(orig_y_start);
let mut r_total: u32 = 0;
let mut g_total: u32 = 0;
let mut b_total: u32 = 0;
let mut a_total: u32 = 0;
let mut pixel_count: u32 = 0;
for orig_y in orig_y_start..=orig_y_end {
for orig_x in orig_x_start..=orig_x_end {
let orig_index = (orig_y * self.width + orig_x) * 4;
r_total += self.img_data[orig_index] as u32;
g_total += self.img_data[orig_index + 1] as u32;
b_total += self.img_data[orig_index + 2] as u32;
a_total += self.img_data[orig_index + 3] as u32;
pixel_count += 1;
}
}
let r_avg = (r_total / pixel_count) as u8;
let g_avg = (g_total / pixel_count) as u8;
let b_avg = (b_total / pixel_count) as u8;
let a_avg = (a_total / pixel_count) as u8;
let new_index = (new_y * new_width + new_x) * 4;
new_img_data[new_index] = r_avg;
new_img_data[new_index + 1] = g_avg;
new_img_data[new_index + 2] = b_avg;
new_img_data[new_index + 3] = a_avg; }
}
Photo {
img_data: new_img_data,
width: new_width,
height: new_height,
}
}
}
#[cfg(feature = "image")]
impl From<image::RgbaImage> for Photo {
fn from(source: image::RgbaImage) -> Photo {
let (width, height) = (source.width() as usize, source.height() as usize);
Photo {
img_data: source.into_raw(),
width,
height,
}
}
}
#[cfg(feature = "image")]
impl From<image::DynamicImage> for Photo {
fn from(source: image::DynamicImage) -> Photo {
Photo::from(source.to_rgba8())
}
}