use crate::*;
#[cfg(feature = "parallel")]
use rayon::{iter::ParallelIterator, prelude::*};
pub struct Image<T: Type, C: Color> {
pub meta: Meta<T, C>,
pub data: Box<dyn ImageData<T>>,
}
impl<T: Type, C: Color> PartialEq for Image<T, C> {
fn eq(&self, other: &Self) -> bool {
self.meta == other.meta && self.data.as_ref().as_ref() == other.data.as_ref().as_ref()
}
}
impl<T: Type, C: Color> Clone for Image<T, C> {
fn clone(&self) -> Self {
Image {
meta: self.meta.clone(),
data: Box::new(self.data.data().to_vec().into_boxed_slice()),
}
}
}
impl<X: Into<Point>, T: Type, C: Color> std::ops::Index<X> for Image<T, C> {
type Output = [T];
fn index(&self, pt: X) -> &Self::Output {
let index = self.meta.index(pt);
&self.data[index..index + self.channels()]
}
}
impl<X: Into<Point>, T: Type, C: Color> std::ops::IndexMut<X> for Image<T, C> {
fn index_mut(&mut self, pt: X) -> &mut Self::Output {
let index = self.meta.index(pt);
let channels = self.channels();
&mut self.data[index..index + channels]
}
}
impl<T: Type, C: Color> Image<T, C> {
pub fn new_with_data(
size: impl Into<Size>,
data: impl 'static + ImageData<T>,
) -> Result<Image<T, C>, Error> {
let meta = Meta::new(size);
if data.as_ref().len() < meta.num_values() {
return Err(Error::InvalidDimensions(
meta.width(),
meta.height(),
C::CHANNELS,
));
}
Ok(Image {
meta,
data: Box::new(data),
})
}
pub fn new(size: impl Into<Size>) -> Image<T, C> {
let size = size.into();
let data = vec![T::default(); size.width * size.height * C::CHANNELS];
Image {
meta: Meta::new(size),
data: Box::new(data.into_boxed_slice()),
}
}
pub fn new_like(&self) -> Image<T, C> {
Image::new(self.size())
}
pub fn new_like_with_type<U: Type>(&self) -> Image<U, C> {
Image::new(self.size())
}
pub fn new_like_with_color<D: Color>(&self) -> Image<T, D> {
Image::new(self.size())
}
pub fn new_like_with_type_and_color<U: Type, D: Color>(&self) -> Image<U, D> {
Image::new(self.size())
}
#[cfg(feature = "mmap")]
pub fn new_mmap(
filename: impl AsRef<std::path::Path>,
meta: Option<Meta<T, C>>,
) -> Result<Image<T, C>, Error> {
match meta {
Some(meta) => Mmap::create_image(filename, &meta),
None => Mmap::load_image(filename),
}
}
#[cfg(feature = "mmap")]
pub fn mmap(mut self, filename: impl AsRef<std::path::Path>) -> Result<Image<T, C>, Error> {
let mut data = Mmap::create(filename, &self.meta)?;
data.data_mut().copy_from_slice(self.data.data());
self.data = Box::new(data);
Ok(self)
}
#[inline]
pub fn channels(&self) -> Channel {
C::CHANNELS
}
#[inline]
pub fn meta(&self) -> Meta<T, C> {
self.meta.clone()
}
#[inline]
pub fn width(&self) -> usize {
self.meta.size.width
}
#[inline]
pub fn height(&self) -> usize {
self.meta.size.height
}
#[inline]
pub fn shape(&self) -> (usize, usize, Channel) {
(self.meta.size.width, self.meta.size.height, self.channels())
}
#[inline]
pub fn size(&self) -> Size {
self.meta.size()
}
pub fn with_color<D: Color>(self) -> Image<T, D> {
assert!(C::CHANNELS == D::CHANNELS);
Image {
meta: Meta::new(self.meta.size),
data: self.data,
}
}
pub fn buffer(&self) -> &[u8] {
self.data.buffer()
}
pub fn buffer_mut(&mut self) -> &mut [u8] {
self.data.buffer_mut()
}
#[inline]
pub fn get(&self, pt: impl Into<Point>) -> Data<T, C> {
let index = self.meta.index(pt);
Data::new(&self.data[index..index + self.channels()])
}
#[inline]
pub fn get_mut(&mut self, pt: impl Into<Point>) -> DataMut<T, C> {
let index = self.meta.index(pt);
let channels = self.channels();
DataMut::new(&mut self.data[index..index + channels])
}
#[inline]
pub fn set(&mut self, pt: impl Into<Point>, data: impl AsRef<[T]>) {
let mut image = self.get_mut(pt);
image.as_mut().clone_from_slice(data.as_ref())
}
#[inline]
pub fn in_bounds(&self, pt: impl Into<Point>) -> bool {
let pt = pt.into();
pt.x < self.width() && pt.y < self.height()
}
#[inline]
pub fn at(&self, pt: impl Into<Point>, mut px: impl AsMut<[T]>) -> bool {
let pt = pt.into();
let px = px.as_mut();
if !self.in_bounds(pt) || px.len() < C::CHANNELS {
return false;
}
px.copy_from_slice(self.get(pt).as_ref());
true
}
#[inline]
pub fn pixel_at(&self, pt: impl Into<Point>, px: &mut Pixel<C>) -> bool {
let pt = pt.into();
if !self.in_bounds(pt) {
return false;
}
let data = self.get(pt);
px.copy_from_slice(data.as_ref());
true
}
#[inline]
pub fn new_pixel(&self) -> Pixel<C> {
Pixel::new()
}
#[inline]
pub fn get_pixel(&self, pt: impl Into<Point>) -> Pixel<C> {
let mut px = Pixel::new();
self.pixel_at(pt, &mut px);
px
}
#[inline]
pub fn set_pixel(&mut self, pt: impl Into<Point>, px: &Pixel<C>) {
let data = self.get_mut(pt);
px.copy_to_slice(data);
}
pub fn get_f(&self, pt: impl Into<Point>, c: Channel) -> f64 {
let pt = pt.into();
if !self.in_bounds(pt) || c >= C::CHANNELS {
return 0.0;
}
let data = self.get(pt);
data[c].to_norm()
}
pub fn set_f(&mut self, pt: impl Into<Point>, c: Channel, f: f64) {
let pt = pt.into();
if !self.in_bounds(pt) || c >= C::CHANNELS {
return;
}
let mut data = self.get_mut(pt);
data[c] = T::from_norm(f);
}
#[inline]
pub fn row(&self, y: usize) -> Data<T, C> {
let index = self.meta.index((0, y));
Data::new(&self.data[index..index + self.channels() * self.width()])
}
#[inline]
pub fn row_mut(&mut self, y: usize) -> DataMut<T, C> {
let index = self.meta.index((0, y));
let len = self.channels() * self.width();
DataMut::new(&mut self.data[index..index + len])
}
#[cfg(not(feature = "parallel"))]
pub fn rows(&self) -> impl Iterator<Item = (usize, &[T])> {
self.data.data().chunks(self.meta.width_step()).enumerate()
}
#[cfg(not(feature = "parallel"))]
pub fn rows_mut(&mut self) -> impl Iterator<Item = (usize, &mut [T])> {
self.data
.data_mut()
.chunks_mut(self.meta.width_step())
.enumerate()
}
#[cfg(feature = "parallel")]
pub fn rows(&self) -> impl ParallelIterator<Item = (usize, &[T])> {
self.data
.data()
.par_chunks(self.meta.width_step())
.enumerate()
}
#[cfg(feature = "parallel")]
pub fn rows_mut(&mut self) -> impl ParallelIterator<Item = (usize, &mut [T])> {
self.data
.data_mut()
.par_chunks_mut(self.meta.width_step())
.enumerate()
}
#[cfg(not(feature = "parallel"))]
pub fn row_range(&self, y: usize, height: usize) -> impl Iterator<Item = (usize, &[T])> {
self.data
.data()
.chunks(self.meta.width_step())
.skip(y)
.take(height)
.enumerate()
.map(move |(i, d)| (i + y, d))
}
#[cfg(not(feature = "parallel"))]
pub fn row_range_mut(
&mut self,
y: usize,
height: usize,
) -> impl Iterator<Item = (usize, &mut [T])> {
self.data
.data_mut()
.chunks_mut(self.meta.width_step())
.skip(y)
.take(height)
.enumerate()
.map(move |(i, d)| (i + y, d))
}
#[cfg(feature = "parallel")]
pub fn row_range(
&self,
y: usize,
height: usize,
) -> impl ParallelIterator<Item = (usize, &[T])> {
self.data
.data()
.par_chunks(self.meta.width_step())
.skip(y)
.take(height)
.enumerate()
.map(move |(i, d)| (i + y, d))
}
#[cfg(feature = "parallel")]
pub fn row_range_mut(
&mut self,
y: usize,
height: usize,
) -> impl ParallelIterator<Item = (usize, &mut [T])> {
self.data
.data_mut()
.par_chunks_mut(self.meta.width_step())
.skip(y)
.take(height)
.enumerate()
.map(move |(i, d)| (i + y, d))
}
pub fn open(path: impl AsRef<std::path::Path>) -> Result<Image<T, C>, Error> {
io::read(path)
}
pub fn save(&self, path: impl AsRef<std::path::Path>) -> Result<(), Error> {
io::write(path, self)
}
#[cfg(feature = "parallel")]
pub fn iter_region_mut(
&mut self,
roi: Region,
) -> impl rayon::iter::ParallelIterator<Item = (Point, DataMut<T, C>)> {
self.row_range_mut(roi.origin.y, roi.height())
.flat_map(move |(y, row)| {
row.par_chunks_mut(C::CHANNELS)
.skip(roi.origin.x)
.take(roi.width())
.map(DataMut::new)
.enumerate()
.map(move |(x, d)| (Point::new(x, y), d))
})
}
#[cfg(not(feature = "parallel"))]
pub fn iter_region_mut(
&mut self,
roi: Region,
) -> impl std::iter::Iterator<Item = (Point, DataMut<T, C>)> {
self.row_range_mut(roi.origin.y, roi.height())
.flat_map(move |(y, row)| {
row.chunks_mut(C::CHANNELS)
.skip(roi.origin.x)
.take(roi.width())
.map(DataMut::new)
.enumerate()
.map(move |(x, d)| (Point::new(x, y), d))
})
}
#[cfg(feature = "parallel")]
pub fn iter_region(
&self,
roi: Region,
) -> impl rayon::iter::ParallelIterator<Item = (Point, Data<T, C>)> {
self.row_range(roi.origin.y, roi.height())
.flat_map(move |(y, row)| {
row.par_chunks(C::CHANNELS)
.skip(roi.origin.x)
.take(roi.width())
.map(Data::new)
.enumerate()
.map(move |(x, d)| (Point::new(x, y), d))
})
}
#[cfg(not(feature = "parallel"))]
pub fn iter_region(&self, roi: Region) -> impl std::iter::Iterator<Item = (Point, Data<T, C>)> {
self.row_range(roi.origin.y, roi.height())
.flat_map(move |(y, row)| {
row.chunks(C::CHANNELS)
.skip(roi.origin.x)
.take(roi.width())
.map(Data::new)
.enumerate()
.map(move |(x, d)| (Point::new(x, y), d))
})
}
#[cfg(feature = "parallel")]
pub fn iter(&self) -> impl rayon::iter::ParallelIterator<Item = (Point, Data<T, C>)> {
self.rows().flat_map(move |(y, row)| {
row.par_chunks(C::CHANNELS)
.map(Data::new)
.enumerate()
.map(move |(x, d)| (Point::new(x, y), d))
})
}
#[cfg(not(feature = "parallel"))]
pub fn iter(&self) -> impl std::iter::Iterator<Item = (Point, Data<T, C>)> {
self.rows().flat_map(move |(y, row)| {
row.chunks(C::CHANNELS)
.map(Data::new)
.enumerate()
.map(move |(x, d)| (Point::new(x, y), d))
})
}
#[cfg(feature = "parallel")]
pub fn iter_mut(
&mut self,
) -> impl rayon::iter::ParallelIterator<Item = (Point, DataMut<T, C>)> {
self.rows_mut().flat_map(move |(y, row)| {
row.par_chunks_mut(C::CHANNELS)
.map(DataMut::new)
.enumerate()
.map(move |(x, d)| (Point::new(x, y), d))
})
}
#[cfg(not(feature = "parallel"))]
pub fn iter_mut(&mut self) -> impl std::iter::Iterator<Item = (Point, DataMut<T, C>)> {
self.rows_mut().flat_map(move |(y, row)| {
row.chunks_mut(C::CHANNELS)
.map(DataMut::new)
.enumerate()
.map(move |(x, d)| (Point::new(x, y), d))
})
}
pub fn for_each<F: Sync + Send + Fn(Point, DataMut<T, C>)>(&mut self, f: F) {
self.rows_mut().for_each(|(y, row)| {
row.chunks_mut(C::CHANNELS)
.map(DataMut::new)
.enumerate()
.for_each(|(x, px)| f(Point::new(x, y), px))
})
}
pub fn for_each_region<F: Sync + Send + Fn(Point, DataMut<T, C>)>(
&mut self,
roi: Region,
f: F,
) {
self.iter_region_mut(roi).for_each(|(pt, px)| f(pt, px))
}
#[cfg(feature = "parallel")]
pub fn for_each2<F: Sync + Send + Fn(Point, DataMut<T, C>, Data<T, C>)>(
&mut self,
other: &Image<T, C>,
f: F,
) {
let meta = self.meta();
let b = other.data.data().par_chunks(C::CHANNELS);
self.data
.data_mut()
.par_chunks_mut(C::CHANNELS)
.zip(b)
.enumerate()
.for_each(|(n, (pixel, pixel1))| {
let pt = meta.convert_index_to_point(n * C::CHANNELS);
f(pt, DataMut::new(pixel), Data::new(pixel1))
});
}
#[cfg(not(feature = "parallel"))]
pub fn for_each2<F: Sync + Send + Fn(Point, DataMut<T, C>, Data<T, C>)>(
&mut self,
other: &Image<T, C>,
f: F,
) {
let meta = self.meta();
let b = other.data.data().chunks(C::CHANNELS);
self.data
.data_mut()
.chunks_mut(C::CHANNELS)
.zip(b)
.enumerate()
.for_each(|(n, (pixel, pixel1))| {
let pt = meta.convert_index_to_point(n * C::CHANNELS);
f(pt, DataMut::new(pixel), Data::new(pixel1))
});
}
pub fn each_pixel<F: Sync + Send + FnMut(Point, &Pixel<C>)>(&self, mut f: F) {
let meta = self.meta();
let mut pixel = Pixel::new();
self.data
.data()
.chunks_exact(C::CHANNELS)
.enumerate()
.for_each(|(n, px)| {
let pt = meta.convert_index_to_point(n * C::CHANNELS);
pixel.copy_from_slice(px);
f(pt, &pixel)
})
}
pub fn each_pixel_region<F: Sync + Send + FnMut(Point, &Pixel<C>)>(
&self,
region: Region,
mut f: F,
) {
let meta = self.meta();
let mut pixel = Pixel::new();
self.data
.data()
.chunks_exact(C::CHANNELS)
.enumerate()
.map(|(n, px)| {
let pt = meta.convert_index_to_point(n * C::CHANNELS);
(pt, px)
})
.filter(|(pt, _px)| region.contains(*pt))
.for_each(|(pt, px)| {
pixel.copy_from_slice(px);
f(pt, &pixel);
})
}
pub fn each_pixel_mut<F: Sync + Send + FnMut(Point, &mut Pixel<C>)>(&mut self, mut f: F) {
let meta = self.meta();
let mut pixel = Pixel::new();
self.data
.data_mut()
.chunks_exact_mut(C::CHANNELS)
.enumerate()
.for_each(|(n, px)| {
let pt = meta.convert_index_to_point(n * C::CHANNELS);
pixel.copy_from_slice(&px);
f(pt, &mut pixel);
pixel.copy_to_slice(px);
});
}
pub fn each_pixel_region_mut<F: Sync + Send + FnMut(Point, &mut Pixel<C>)>(
&mut self,
region: Region,
mut f: F,
) {
let meta = self.meta();
let mut pixel = Pixel::new();
self.data
.data_mut()
.chunks_exact_mut(C::CHANNELS)
.enumerate()
.map(|(n, px)| {
let pt = meta.convert_index_to_point(n * C::CHANNELS);
(pt, px)
})
.filter(|(pt, _px)| region.contains(*pt))
.for_each(|(pt, px)| {
pixel.copy_from_slice(&px);
f(pt, &mut pixel);
pixel.copy_to_slice(px);
})
}
pub fn crop(&self, roi: Region) -> Image<T, C> {
let mut dest = Image::new(roi.size);
dest.apply(filter::crop(roi), &[self]);
dest
}
pub fn copy_from_region(&mut self, offs: impl Into<Point>, other: &Image<T, C>, roi: Region) {
let offs = offs.into();
self.for_each_region(roi, |pt, mut px| {
px.copy_from_slice(
other.get((pt.x - roi.origin.x + offs.x, pt.y - roi.origin.y + offs.y)),
);
});
}
pub fn apply<U: Type, D: Color>(
&mut self,
filter: impl Filter<U, D, T, C>,
input: &[&Image<U, D>],
) -> &mut Self {
filter.eval(input, self);
self
}
pub async fn apply_async<'a, U: Type, D: Color>(
&mut self,
mode: AsyncMode,
filter: impl Filter<U, D, T, C> + Unpin,
input: &[&Image<U, D>],
) -> &mut Self {
filters::eval_async(&filter, mode, Input::new(input), self).await;
self
}
pub fn run_in_place(&mut self, filter: impl Filter<T, C>) -> &mut Self {
filter.eval_in_place(self);
self
}
pub fn run<U: Type, D: Color>(
&self,
filter: impl Filter<T, C, U, D>,
output: Option<Meta<U, D>>,
) -> Image<U, D> {
let size = if let Some(o) = output {
o.size
} else {
self.size()
};
let mut dest = Image::new(size);
dest.apply(filter, &[self]);
dest
}
pub async fn run_async<'a, U: 'a + Type, D: 'a + Color>(
&self,
mode: AsyncMode,
filter: impl Filter<T, C, U, D> + Unpin,
output: Option<Meta<U, D>>,
) -> Image<U, D> {
let size = if let Some(o) = output {
o.size
} else {
self.size()
};
let mut dest = Image::new(size);
dest.apply_async(mode, filter, &[self]).await;
dest
}
pub fn convert<U: Type, D: Color>(&self) -> Image<U, D> {
self.run(filter::convert(), None)
}
pub fn convert_to<U: Type, D: Color>(&self, dest: &mut Image<U, D>) {
dest.apply(filter::convert(), &[self]);
}
#[cfg(feature = "oiio")]
pub(crate) fn image_buf(&mut self) -> io::oiio::internal::ImageBuf {
io::oiio::internal::ImageBuf::new_with_data(
self.width(),
self.height(),
self.channels(),
self.data.data_mut(),
)
}
#[cfg(feature = "oiio")]
pub(crate) fn const_image_buf(&self) -> io::oiio::internal::ImageBuf {
io::oiio::internal::ImageBuf::const_new_with_data(
self.width(),
self.height(),
self.channels(),
self.data.data(),
)
}
#[cfg(feature = "oiio")]
pub fn convert_colorspace_to(
&self,
dest: &mut Image<T, C>,
a: impl AsRef<str>,
b: impl AsRef<str>,
) -> Result<(), Error> {
let buf = self.const_image_buf();
let ok = buf.convert_color(&mut dest.image_buf(), a.as_ref(), b.as_ref());
if ok {
Ok(())
} else {
Err(Error::FailedColorConversion(
a.as_ref().into(),
b.as_ref().into(),
))
}
}
#[cfg(feature = "oiio")]
pub fn convert_colorspace(
&self,
a: impl AsRef<str>,
b: impl AsRef<str>,
) -> Result<Image<T, C>, Error> {
let mut dest = self.new_like_with_color();
self.convert_colorspace_to(&mut dest, a, b)?;
Ok(dest)
}
pub fn histogram(&self, bins: usize) -> Vec<Histogram> {
let mut hist = vec![Histogram::new(bins); C::CHANNELS];
self.each_pixel(|_, px| {
for i in 0..C::CHANNELS {
hist[i].add_value(px[i]);
}
});
hist
}
pub fn gamma(&mut self, value: f64) {
self.for_each(|_, px| {
for x in px {
*x = T::from_f64(T::to_f64(x).powf(value))
}
})
}
pub fn set_gamma_log(&mut self) {
self.gamma(1. / 2.2)
}
pub fn set_gamma_lin(&mut self) {
self.gamma(2.2)
}
pub fn resize(&self, size: impl Into<Size>) -> Image<T, C> {
let size = size.into();
self.run(filter::resize(self.size(), size), Some(Meta::new(size)))
}
pub fn scale(&self, width: f64, height: f64) -> Image<T, C> {
self.run(
filter::scale(width, height),
Some(Meta::new((
(self.width() as f64 * width) as usize,
(self.height() as f64 * height) as usize,
))),
)
}
pub fn data(&self) -> &[T] {
self.data.data()
}
pub fn data_mut(&mut self) -> &mut [T] {
self.data.data_mut()
}
}