Added functions for converting image colorspace.

This commit is contained in:
Kirill Kuzminykh
2022-08-31 22:38:27 +03:00
parent 9b8bba2149
commit c454c3b990
12 changed files with 760 additions and 18 deletions
Generated
+1
View File
@@ -485,6 +485,7 @@ dependencies = [
"image",
"nix",
"num-traits",
"once_cell",
"png",
"resize",
"rgb",
+1
View File
@@ -21,6 +21,7 @@ exclude = ["/data"]
[dependencies]
num-traits = "0.2.15"
once_cell = "1.13.0"
thiserror = "1.0.31"
+32 -9
View File
@@ -1,7 +1,6 @@
use std::ffi::OsStr;
use std::fmt::Debug;
use std::num::NonZeroU32;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use anyhow::{anyhow, Context, Result};
use clap::Parser;
@@ -37,6 +36,10 @@ struct Cli {
#[clap(short, long, action)]
overwrite: bool,
/// Colorspace of image
#[clap(short, long, value_enum, default_value_t = structs::ColorSpace::NonLinear)]
colorspace: structs::ColorSpace,
/// Algorithm used to resize image
#[clap(short, long, value_enum, default_value_t = structs::Algorithm::Convolution)]
algorithm: structs::Algorithm,
@@ -58,7 +61,7 @@ fn main() -> Result<()> {
}
fn resize(cli: &Cli) -> Result<()> {
let (mut src_image, color_type) = open_source_image(&cli.source_path)?;
let (mut src_image, color_type) = open_source_image(cli)?;
let mut dst_image = create_destination_image(cli, &src_image);
let mul_div = fr::MulDiv::default();
@@ -88,13 +91,11 @@ fn resize(cli: &Cli) -> Result<()> {
.with_context(|| "Failed to divide color channels by alpha")?;
}
save_result(cli, &dst_image, color_type)
save_result(cli, dst_image, color_type)
}
fn open_source_image<P>(source_path: P) -> Result<(fr::Image<'static>, ColorType)>
where
P: AsRef<Path> + Debug,
{
fn open_source_image(cli: &Cli) -> Result<(fr::Image<'static>, ColorType)> {
let source_path = &cli.source_path;
debug!("Opening the source image {:?}", source_path);
let image = ImageReader::open(&source_path)
.with_context(|| format!("Failed to read source file from {:?}", source_path))?
@@ -195,7 +196,7 @@ fn get_resizing_algorithm(cli: &Cli) -> fr::ResizeAlg {
}
}
fn save_result(cli: &Cli, image: &fr::Image, color_type: ColorType) -> Result<()> {
fn save_result(cli: &Cli, mut image: fr::Image, color_type: ColorType) -> Result<()> {
let result_path = if let Some(path) = cli.destination_path.clone() {
path
} else {
@@ -214,6 +215,28 @@ fn save_result(cli: &Cli, image: &fr::Image, color_type: ColorType) -> Result<()
result_path
));
};
image = match cli.colorspace {
structs::ColorSpace::NonLinear => {
debug!("Convert the result image from linear colorspace into non-linear");
let mut non_linear_dst_image =
fr::Image::new(image.width(), image.height(), image.pixel_type());
if color_type.has_color() {
fr::color::srgb::rgb_into_srgb(
&image.view(),
&mut non_linear_dst_image.view_mut(),
)?;
} else {
fr::color::gamma::linear_into_gamma22(
&image.view(),
&mut non_linear_dst_image.view_mut(),
)?;
}
non_linear_dst_image
}
_ => image,
};
debug!("Save the result image into the file {:?}", result_path);
image::save_buffer(
result_path,
+7
View File
@@ -48,3 +48,10 @@ impl From<FilterType> for fr::FilterType {
}
}
}
#[derive(Copy, Clone, Debug, clap::ValueEnum)]
pub enum ColorSpace {
Linear,
/// sRGB for color images or gamma 2.2 for grayscale images
NonLinear,
}
+198
View File
@@ -0,0 +1,198 @@
//! Functions for changing image gamma.
use once_cell::sync::Lazy;
use crate::pixels::{U16x2, U16x3, U16x4, U8x2, U8x3, U8x4, U16, U8};
use crate::typed_image_view::{TypedImageView, TypedImageViewMut};
use crate::{ImageView, ImageViewMut, MappingError, PixelType};
use super::MappingTable;
macro_rules! gamma_table {
($src_type:tt, $dst_type:tt, $gamma:expr) => {{
const TABLE_SIZE: usize = $src_type::MAX as usize + 1;
let mut table: [$dst_type; TABLE_SIZE] = [0; TABLE_SIZE];
table.iter_mut().enumerate().for_each(|(i, v)| {
let signal = i as f32 / $src_type::MAX as f32;
let power = signal.powf($gamma);
*v = ($dst_type::MAX as f32 * power).round() as $dst_type;
});
table
}};
}
static GAMMA22_U8_INTO_LINEAR_U8: Lazy<MappingTable<u8, 256>> =
Lazy::new(|| MappingTable(gamma_table!(u8, u8, 2.2)));
static LINEAR_U8_INTO_GAMMA22_U8: Lazy<MappingTable<u8, 256>> =
Lazy::new(|| MappingTable(gamma_table!(u8, u8, 1.0 / 2.2)));
static GAMMA22_U8_INTO_LINEAR_U16: Lazy<MappingTable<u16, 256>> =
Lazy::new(|| MappingTable(gamma_table!(u8, u16, 2.2)));
static LINEAR_U8_INTO_GAMMA22_U16: Lazy<MappingTable<u16, 256>> =
Lazy::new(|| MappingTable(gamma_table!(u8, u16, 1.0 / 2.2)));
static GAMMA22_U16_INTO_LINEAR_U8: Lazy<MappingTable<u8, 65536>> =
Lazy::new(|| MappingTable(gamma_table!(u16, u8, 2.2)));
static LINEAR_U16_INTO_GAMMA22_U8: Lazy<MappingTable<u8, 65536>> =
Lazy::new(|| MappingTable(gamma_table!(u16, u8, 1.0 / 2.2)));
static GAMMA22_U16_INTO_LINEAR_U16: Lazy<MappingTable<u16, 65536>> =
Lazy::new(|| MappingTable(gamma_table!(u16, u16, 2.2)));
static LINEAR_U16_INTO_GAMMA22_U16: Lazy<MappingTable<u16, 65536>> =
Lazy::new(|| MappingTable(gamma_table!(u16, u16, 1.0 / 2.2)));
/// Convert image from gamma 2.2 into linear colorspace.
pub fn gamma22_into_linear(
src_image: &ImageView,
dst_image: &mut ImageViewMut,
) -> Result<(), MappingError> {
if src_image.width() != dst_image.width() || src_image.height() != dst_image.height() {
return Err(MappingError::DifferentDimensions);
}
macro_rules! map {
($src_pixel:ty, $dst_pixel:ty, $mapping_table:ident) => {
if let Some(src) = TypedImageView::<$src_pixel>::from_image_view(src_image) {
if let Some(dst) = TypedImageViewMut::<$dst_pixel>::from_image_view(dst_image) {
$mapping_table.map_typed_image(src, dst);
}
}
};
}
let src_pixel_type = src_image.pixel_type();
let dst_pixel_type = dst_image.pixel_type();
match (src_pixel_type, dst_pixel_type) {
// U8 -> U8
(PixelType::U8, PixelType::U8) => {
map!(U8, U8, GAMMA22_U8_INTO_LINEAR_U8);
}
(PixelType::U8x2, PixelType::U8x2) => {
map!(U8x2, U8x2, GAMMA22_U8_INTO_LINEAR_U8);
}
(PixelType::U8x3, PixelType::U8x3) => {
map!(U8x3, U8x3, GAMMA22_U8_INTO_LINEAR_U8);
}
(PixelType::U8x4, PixelType::U8x4) => {
map!(U8x4, U8x4, GAMMA22_U8_INTO_LINEAR_U8);
}
// U8 -> U16
(PixelType::U8, PixelType::U16) => {
map!(U8, U16, GAMMA22_U8_INTO_LINEAR_U16);
}
(PixelType::U8x2, PixelType::U16x2) => {
map!(U8x2, U16x2, GAMMA22_U8_INTO_LINEAR_U16);
}
(PixelType::U8x3, PixelType::U16x3) => {
map!(U8x3, U16x3, GAMMA22_U8_INTO_LINEAR_U16);
}
(PixelType::U8x4, PixelType::U16x4) => {
map!(U8x4, U16x4, GAMMA22_U8_INTO_LINEAR_U16);
}
// U16 -> U8
(PixelType::U16, PixelType::U8) => {
map!(U16, U8, GAMMA22_U16_INTO_LINEAR_U8);
}
(PixelType::U16x2, PixelType::U8x2) => {
map!(U16x2, U8x2, GAMMA22_U16_INTO_LINEAR_U8);
}
(PixelType::U16x3, PixelType::U8x3) => {
map!(U16x3, U8x3, GAMMA22_U16_INTO_LINEAR_U8);
}
(PixelType::U16x4, PixelType::U8x4) => {
map!(U16x4, U8x4, GAMMA22_U16_INTO_LINEAR_U8);
}
// U16 -> U16
(PixelType::U16, PixelType::U16) => {
map!(U16, U16, GAMMA22_U16_INTO_LINEAR_U16);
}
(PixelType::U16x2, PixelType::U16x2) => {
map!(U16x2, U16x2, GAMMA22_U16_INTO_LINEAR_U16);
}
(PixelType::U16x3, PixelType::U16x3) => {
map!(U16x3, U16x3, GAMMA22_U16_INTO_LINEAR_U16);
}
(PixelType::U16x4, PixelType::U16x4) => {
map!(U16x4, U16x4, GAMMA22_U16_INTO_LINEAR_U16);
}
_ => return Err(MappingError::UnsupportedCombinationOfImageTypes),
}
Ok(())
}
/// Convert image from linear colorspace into gamma 2.2.
pub fn linear_into_gamma22(
src_image: &ImageView,
dst_image: &mut ImageViewMut,
) -> Result<(), MappingError> {
if src_image.width() != dst_image.width() || src_image.height() != dst_image.height() {
return Err(MappingError::DifferentDimensions);
}
macro_rules! map {
($src_pixel:ty, $dst_pixel:ty, $mapping_table:ident) => {
if let Some(src) = TypedImageView::<$src_pixel>::from_image_view(src_image) {
if let Some(dst) = TypedImageViewMut::<$dst_pixel>::from_image_view(dst_image) {
$mapping_table.map_typed_image(src, dst);
}
}
};
}
let src_pixel_type = src_image.pixel_type();
let dst_pixel_type = dst_image.pixel_type();
match (src_pixel_type, dst_pixel_type) {
// U8 -> U8
(PixelType::U8, PixelType::U8) => {
map!(U8, U8, LINEAR_U8_INTO_GAMMA22_U8);
}
(PixelType::U8x2, PixelType::U8x2) => {
map!(U8x2, U8x2, LINEAR_U8_INTO_GAMMA22_U8);
}
(PixelType::U8x3, PixelType::U8x3) => {
map!(U8x3, U8x3, LINEAR_U8_INTO_GAMMA22_U8);
}
(PixelType::U8x4, PixelType::U8x4) => {
map!(U8x4, U8x4, LINEAR_U8_INTO_GAMMA22_U8);
}
// U8 -> U16
(PixelType::U8, PixelType::U16) => {
map!(U8, U16, LINEAR_U8_INTO_GAMMA22_U16);
}
(PixelType::U8x2, PixelType::U16x2) => {
map!(U8x2, U16x2, LINEAR_U8_INTO_GAMMA22_U16);
}
(PixelType::U8x3, PixelType::U16x3) => {
map!(U8x3, U16x3, LINEAR_U8_INTO_GAMMA22_U16);
}
(PixelType::U8x4, PixelType::U16x4) => {
map!(U8x4, U16x4, LINEAR_U8_INTO_GAMMA22_U16);
}
// U16 -> U8
(PixelType::U16, PixelType::U8) => {
map!(U16, U8, LINEAR_U16_INTO_GAMMA22_U8);
}
(PixelType::U16x2, PixelType::U8x2) => {
map!(U16x2, U8x2, LINEAR_U16_INTO_GAMMA22_U8);
}
(PixelType::U16x3, PixelType::U8x3) => {
map!(U16x3, U8x3, LINEAR_U16_INTO_GAMMA22_U8);
}
(PixelType::U16x4, PixelType::U8x4) => {
map!(U16x4, U8x4, LINEAR_U16_INTO_GAMMA22_U8);
}
// U16 -> U16
(PixelType::U16, PixelType::U16) => {
map!(U16, U16, LINEAR_U16_INTO_GAMMA22_U16);
}
(PixelType::U16x2, PixelType::U16x2) => {
map!(U16x2, U16x2, LINEAR_U16_INTO_GAMMA22_U16);
}
(PixelType::U16x3, PixelType::U16x3) => {
map!(U16x3, U16x3, LINEAR_U16_INTO_GAMMA22_U16);
}
(PixelType::U16x4, PixelType::U16x4) => {
map!(U16x4, U16x4, LINEAR_U16_INTO_GAMMA22_U16);
}
_ => return Err(MappingError::UnsupportedCombinationOfImageTypes),
}
Ok(())
}
+71
View File
@@ -0,0 +1,71 @@
//! Functions for working with colorspace and gamma.
//!
//! Supported all pixel types exclude `I32` and `F32`.
//!
//! Source and destination images may have different bit depth of one pixel component.
//! But count of components must be equal.
//! For example, you may convert `U8x3` image with sRGB colorspace into
//! `U16x3` image with linear colorspace.
use crate::pixels::{GetCount, Pixel, PixelComponent, PixelComponentInto, Values};
use crate::typed_image_view::{TypedImageView, TypedImageViewMut};
pub mod gamma;
pub mod srgb;
struct MappingTable<Out: PixelComponent, const N: usize>([Out; N]);
impl<Out, const N: usize> MappingTable<Out, N>
where
Out: PixelComponent,
{
fn map<In>(&self, src_buffer: &[In], dst_buffer: &mut [Out])
where
In: PixelComponent + Into<usize>,
{
for (&src, dst) in src_buffer.iter().zip(dst_buffer) {
*dst = self.0[src.into()];
}
}
fn map_with_gaps<In>(&self, src_buffer: &[In], dst_buffer: &mut [Out], gap_step: usize)
where
In: PixelComponentInto<Out> + Into<usize>,
{
for (i, (&src, dst)) in src_buffer.iter().zip(dst_buffer).enumerate() {
if (i + 1) % gap_step != 0 {
*dst = self.0[src.into()];
} else {
*dst = src.into_component();
}
}
}
pub fn map_typed_image<S, D, CC, In>(
&self,
src_image: TypedImageView<S>,
mut dst_image: TypedImageViewMut<D>,
) where
In: PixelComponentInto<Out> + Into<usize>,
CC: GetCount,
S: Pixel<
Component = In,
ComponentsCount = CC, // Count of source pixel's components
ComponentCountOfValues = Values<N>, // Total count of values of one source pixel's component
>,
S::Component: Into<usize>,
D: Pixel<
Component = Out,
ComponentsCount = CC, // Count of destination pixel's components
>,
{
for (s_row, d_row) in src_image.iter_rows(0).zip(dst_image.iter_rows_mut()) {
let s_comp = S::components(s_row);
let d_comp = D::components_mut(d_row);
match CC::count() {
2 => self.map_with_gaps(s_comp, d_comp, 2), // Don't map alpha channel
4 => self.map_with_gaps(s_comp, d_comp, 4), // Don't map alpha channel
_ => self.map(s_comp, d_comp),
}
}
}
}
+169
View File
@@ -0,0 +1,169 @@
//! Functions for converting image between sRGB and linear colorspace.
use once_cell::sync::Lazy;
use crate::pixels::{U16x3, U16x4, U8x3, U8x4};
use crate::typed_image_view::{TypedImageView, TypedImageViewMut};
use crate::{ImageView, ImageViewMut, MappingError, PixelType};
use super::MappingTable;
/// https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
/// http://www.ericbrasseur.org/gamma.html?i=2#formulas
macro_rules! srgb_into_rgb_table {
($src_type:tt, $dst_type:tt) => {{
const TABLE_SIZE: usize = $src_type::MAX as usize + 1;
let mut table: [$dst_type; TABLE_SIZE] = [0; TABLE_SIZE];
table.iter_mut().enumerate().for_each(|(i, v)| {
let signal = i as f32 / $src_type::MAX as f32;
let power = if signal < 0.04045 {
signal / 12.92
} else {
const A: f32 = 0.055;
((signal + A) / (1. + A)).powf(2.4)
};
*v = ($dst_type::MAX as f32 * power).round() as $dst_type;
});
table
}};
}
/// https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB
/// http://www.ericbrasseur.org/gamma.html?i=2#formulas
macro_rules! rgb_into_srgb_table {
($src_type:tt, $dst_type:tt) => {{
const TABLE_SIZE: usize = $src_type::MAX as usize + 1;
let mut table: [$dst_type; TABLE_SIZE] = [0; TABLE_SIZE];
table.iter_mut().enumerate().for_each(|(i, v)| {
let signal = i as f32 / $src_type::MAX as f32;
let power = if signal < 0.0031308 {
12.92 * signal
} else {
const A: f32 = 0.055;
(1. + A) * signal.powf(1. / 2.4) - A
};
*v = ($dst_type::MAX as f32 * power).round() as $dst_type;
});
table
}};
}
static SRGB8_INTO_RGB8: Lazy<MappingTable<u8, 256>> =
Lazy::new(|| MappingTable(srgb_into_rgb_table!(u8, u8)));
static SRGB8_INTO_RGB16: Lazy<MappingTable<u16, 256>> =
Lazy::new(|| MappingTable(srgb_into_rgb_table!(u8, u16)));
static SRGB16_INTO_RGB8: Lazy<MappingTable<u8, 65536>> =
Lazy::new(|| MappingTable(srgb_into_rgb_table!(u16, u8)));
static SRGB16_INTO_RGB16: Lazy<MappingTable<u16, 65536>> =
Lazy::new(|| MappingTable(srgb_into_rgb_table!(u16, u16)));
static RGB8_INTO_SRGB8: Lazy<MappingTable<u8, 256>> =
Lazy::new(|| MappingTable(rgb_into_srgb_table!(u8, u8)));
static RGB8_INTO_SRGB16: Lazy<MappingTable<u16, 256>> =
Lazy::new(|| MappingTable(rgb_into_srgb_table!(u8, u16)));
static RGB16_INTO_SRGB8: Lazy<MappingTable<u8, 65536>> =
Lazy::new(|| MappingTable(rgb_into_srgb_table!(u16, u8)));
static RGB16_INTO_SRGB16: Lazy<MappingTable<u16, 65536>> =
Lazy::new(|| MappingTable(rgb_into_srgb_table!(u16, u16)));
/// Convert image from sRGB into linear RGB colorspace.
pub fn srgb_into_rgb(
src_image: &ImageView,
dst_image: &mut ImageViewMut,
) -> Result<(), MappingError> {
if src_image.width() != dst_image.width() || src_image.height() != dst_image.height() {
return Err(MappingError::DifferentDimensions);
}
macro_rules! map {
($src_pixel:ty, $dst_pixel:ty, $mapping_table:ident) => {
if let Some(src) = TypedImageView::<$src_pixel>::from_image_view(src_image) {
if let Some(dst) = TypedImageViewMut::<$dst_pixel>::from_image_view(dst_image) {
$mapping_table.map_typed_image(src, dst);
}
}
};
}
let src_pixel_type = src_image.pixel_type();
let dst_pixel_type = dst_image.pixel_type();
match (src_pixel_type, dst_pixel_type) {
(PixelType::U8x3, PixelType::U8x3) => {
map!(U8x3, U8x3, SRGB8_INTO_RGB8);
}
(PixelType::U8x3, PixelType::U16x3) => {
map!(U8x3, U16x3, SRGB8_INTO_RGB16);
}
(PixelType::U16x3, PixelType::U8x3) => {
map!(U16x3, U8x3, SRGB16_INTO_RGB8);
}
(PixelType::U16x3, PixelType::U16x3) => {
map!(U16x3, U16x3, SRGB16_INTO_RGB16);
}
(PixelType::U8x4, PixelType::U8x4) => {
map!(U8x4, U8x4, SRGB8_INTO_RGB8);
}
(PixelType::U8x4, PixelType::U16x4) => {
map!(U8x4, U16x4, SRGB8_INTO_RGB16);
}
(PixelType::U16x4, PixelType::U8x4) => {
map!(U16x4, U8x4, SRGB16_INTO_RGB8);
}
(PixelType::U16x4, PixelType::U16x4) => {
map!(U16x4, U16x4, SRGB16_INTO_RGB16);
}
_ => return Err(MappingError::UnsupportedCombinationOfImageTypes),
}
Ok(())
}
/// Convert image from linear RGB into sRGB colorspace.
pub fn rgb_into_srgb(
src_image: &ImageView,
dst_image: &mut ImageViewMut,
) -> Result<(), MappingError> {
if src_image.width() != dst_image.width() || src_image.height() != dst_image.height() {
return Err(MappingError::DifferentDimensions);
}
macro_rules! map {
($src_pixel:ty, $dst_pixel:ty, $mapping_table:ident) => {
if let Some(src) = TypedImageView::<$src_pixel>::from_image_view(src_image) {
if let Some(dst) = TypedImageViewMut::<$dst_pixel>::from_image_view(dst_image) {
$mapping_table.map_typed_image(src, dst);
}
}
};
}
let src_pixel_type = src_image.pixel_type();
let dst_pixel_type = dst_image.pixel_type();
match (src_pixel_type, dst_pixel_type) {
(PixelType::U8x3, PixelType::U8x3) => {
map!(U8x3, U8x3, RGB8_INTO_SRGB8);
}
(PixelType::U8x3, PixelType::U16x3) => {
map!(U8x3, U16x3, RGB8_INTO_SRGB16);
}
(PixelType::U16x3, PixelType::U8x3) => {
map!(U16x3, U8x3, RGB16_INTO_SRGB8);
}
(PixelType::U16x3, PixelType::U16x3) => {
map!(U16x3, U16x3, RGB16_INTO_SRGB16);
}
(PixelType::U8x4, PixelType::U8x4) => {
map!(U8x4, U8x4, RGB8_INTO_SRGB8);
}
(PixelType::U8x4, PixelType::U16x4) => {
map!(U8x4, U16x4, RGB8_INTO_SRGB16);
}
(PixelType::U16x4, PixelType::U8x4) => {
map!(U16x4, U8x4, RGB16_INTO_SRGB8);
}
(PixelType::U16x4, PixelType::U16x4) => {
map!(U16x4, U16x4, RGB16_INTO_SRGB16);
}
_ => return Err(MappingError::UnsupportedCombinationOfImageTypes),
}
Ok(())
}
+8
View File
@@ -33,3 +33,11 @@ pub struct DifferentTypesOfPixelsError;
"The dimensions of the source image are not equal to the dimensions of the destination image"
)]
pub(crate) struct DifferentDimensionsError;
#[derive(Error, Debug, Clone, Copy)]
pub enum MappingError {
#[error("The dimensions of the source image are not equal to the dimensions of the destination image")]
DifferentDimensions,
#[error("Unsupported combination of pixels of source and/or destination images")]
UnsupportedCombinationOfImageTypes,
}
+1 -1
View File
@@ -20,7 +20,7 @@ impl<'a> PixelsContainer<'a> {
}
}
/// Simple image container.
/// Simple container of image data.
#[derive(Debug)]
pub struct Image<'a> {
width: NonZeroU32,
+1
View File
@@ -13,6 +13,7 @@ pub use resizer::{CpuExtensions, ResizeAlg, Resizer};
pub use crate::image::Image;
mod alpha;
pub mod color;
mod convolution;
mod errors;
mod image;
+97 -8
View File
@@ -49,18 +49,67 @@ impl PixelType {
}
}
pub trait GetCount {
fn count() -> usize;
}
/// Generic type to represent the number of component in single pixel.
pub struct Count<const N: usize>;
impl<const N: usize> GetCount for Count<N> {
fn count() -> usize {
N
}
}
pub trait CountOfValues {
fn count_of_values() -> usize;
}
/// Generic type to represent the number of available values for a single pixel component.
pub struct Values<const N: usize>;
impl<const N: usize> CountOfValues for Values<N> {
fn count_of_values() -> usize {
N
}
}
pub trait PixelComponent
where
Self: Sized + Copy + 'static,
{
}
impl PixelComponent for u8 {}
impl PixelComponent for u16 {}
impl PixelComponent for i32 {}
impl PixelComponent for f32 {}
/// Additional information about pixel type.
pub trait Pixel
where
Self: Copy + Sized + Debug,
{
/// Type of pixel components
type Component;
type Component: PixelComponent;
/// Type that provides information about a count of pixel's components
type ComponentsCount: GetCount;
/// Type that provides information about a count of available values of one
/// pixel's component as usize
type ComponentCountOfValues: CountOfValues;
fn pixel_type() -> PixelType;
/// Count of pixel's components
fn components_count() -> usize;
fn components_count() -> usize {
Self::ComponentsCount::count()
}
/// Count of available values of one pixel's component as usize
fn component_count_of_values() -> usize {
Self::ComponentCountOfValues::count_of_values()
}
/// Size of pixel in bytes
///
@@ -91,7 +140,7 @@ where
}
macro_rules! pixel_struct {
($name:ident, $type:tt, $comp_type:tt, $comp_count:expr, $pixel_type:expr, $doc:expr) => {
($name:ident, $type:tt, $comp_type:tt, $comp_count:literal, $comp_values:literal, $pixel_type:expr, $doc:expr) => {
#[doc = $doc]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
@@ -99,24 +148,31 @@ macro_rules! pixel_struct {
impl Pixel for $name {
type Component = $comp_type;
type ComponentsCount = Count<$comp_count>;
type ComponentCountOfValues = Values<$comp_values>;
fn pixel_type() -> PixelType {
$pixel_type
}
fn components_count() -> usize {
$comp_count
}
}
};
}
pixel_struct!(U8, u8, u8, 1, PixelType::U8, "One byte per pixel (e.g. L8)");
pixel_struct!(
U8,
u8,
u8,
1,
256,
PixelType::U8,
"One byte per pixel (e.g. L8)"
);
pixel_struct!(
U8x2,
u16,
u8,
2,
256,
PixelType::U8x2,
"Two bytes per pixel (e.g. LA8)"
);
@@ -125,6 +181,7 @@ pixel_struct!(
[u8; 3],
u8,
3,
256,
PixelType::U8x3,
"Three bytes per pixel (e.g. RGB8)"
);
@@ -133,6 +190,7 @@ pixel_struct!(
u32,
u8,
4,
256,
PixelType::U8x4,
"Four bytes per pixel (RGBA8, RGBx8, CMYK8 and other)"
);
@@ -141,6 +199,7 @@ pixel_struct!(
u16,
u16,
1,
65536,
PixelType::U16,
"One `u16` component per pixel (e.g. L16)"
);
@@ -149,6 +208,7 @@ pixel_struct!(
[u16; 2],
u16,
2,
65536,
PixelType::U16x2,
"Two `u16` components per pixel (e.g. LA16)"
);
@@ -157,6 +217,7 @@ pixel_struct!(
[u16; 3],
u16,
3,
65536,
PixelType::U16x3,
"Three `u16` components per pixel (e.g. RGB16)"
);
@@ -165,6 +226,7 @@ pixel_struct!(
[u16; 4],
u16,
4,
65536,
PixelType::U16x4,
"Four `u16` components per pixel (e.g. RGBA16)"
);
@@ -173,6 +235,7 @@ pixel_struct!(
i32,
i32,
1,
0,
PixelType::I32,
"One `i32` component per pixel"
);
@@ -181,6 +244,32 @@ pixel_struct!(
f32,
f32,
1,
0,
PixelType::F32,
"One `f32` component per pixel"
);
pub(crate) trait PixelComponentInto<Out: PixelComponent>
where
Self: PixelComponent,
{
fn into_component(self) -> Out;
}
impl<C: PixelComponent> PixelComponentInto<C> for C {
fn into_component(self) -> C {
self
}
}
impl PixelComponentInto<u8> for u16 {
fn into_component(self) -> u8 {
self.to_le_bytes()[1]
}
}
impl PixelComponentInto<u16> for u8 {
fn into_component(self) -> u16 {
u16::from_le_bytes([self, self])
}
}
+174
View File
@@ -0,0 +1,174 @@
use fast_image_resize as fr;
use std::num::NonZeroU32;
fn nonzero(v: u32) -> NonZeroU32 {
NonZeroU32::new(v).unwrap()
}
mod gamma_tests {
use super::*;
#[test]
fn gamma22_into_linear_test() {
let buffer: Vec<u8> = (0u8..=255).collect();
let src_image =
fr::Image::from_vec_u8(nonzero(16), nonzero(16), buffer, fr::PixelType::U8).unwrap();
let src_checksum = testing::image_checksum::<1>(src_image.buffer());
assert_eq!(src_checksum, [32640]);
// into U8
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U8);
fr::color::gamma::gamma22_into_linear(&src_image.view(), &mut dst_image.view_mut())
.unwrap();
let dst_checksum = testing::image_checksum::<1>(dst_image.buffer());
assert_eq!(dst_checksum, [20443]);
// into U16
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U16);
fr::color::gamma::gamma22_into_linear(&src_image.view(), &mut dst_image.view_mut())
.unwrap();
let dst_checksum = testing::image_u16_checksum::<1>(dst_image.buffer());
assert_eq!(dst_checksum, [5255141]);
}
#[test]
fn gamma22_into_linear_errors_test() {
let buffer: Vec<u8> = (0u8..=255).collect();
let src_image =
fr::Image::from_vec_u8(nonzero(16), nonzero(16), buffer, fr::PixelType::U8).unwrap();
let mut dst_image = fr::Image::new(nonzero(16), nonzero(1), fr::PixelType::U8);
let result =
fr::color::gamma::gamma22_into_linear(&src_image.view(), &mut dst_image.view_mut());
assert!(matches!(result, Err(fr::MappingError::DifferentDimensions)));
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U8x2);
let result =
fr::color::gamma::gamma22_into_linear(&src_image.view(), &mut dst_image.view_mut());
assert!(matches!(
result,
Err(fr::MappingError::UnsupportedCombinationOfImageTypes)
));
}
#[test]
fn linear_into_gamma22_test() {
let buffer: Vec<u8> = (0u8..=255).collect();
let src_image =
fr::Image::from_vec_u8(nonzero(16), nonzero(16), buffer, fr::PixelType::U8).unwrap();
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U8);
fr::color::gamma::linear_into_gamma22(&src_image.view(), &mut dst_image.view_mut())
.unwrap();
let src_checksum = testing::image_checksum::<1>(src_image.buffer());
assert_eq!(src_checksum, [32640]);
let dst_checksum = testing::image_checksum::<1>(dst_image.buffer());
assert_eq!(dst_checksum, [44824]);
}
#[test]
fn linear_into_gamma22_errors_test() {
let buffer: Vec<u8> = (0u8..=255).collect();
let src_image =
fr::Image::from_vec_u8(nonzero(16), nonzero(16), buffer, fr::PixelType::U8).unwrap();
let mut dst_image = fr::Image::new(nonzero(16), nonzero(1), fr::PixelType::U8);
let result =
fr::color::gamma::linear_into_gamma22(&src_image.view(), &mut dst_image.view_mut());
assert!(matches!(result, Err(fr::MappingError::DifferentDimensions)));
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U8x2);
let result =
fr::color::gamma::linear_into_gamma22(&src_image.view(), &mut dst_image.view_mut());
assert!(matches!(
result,
Err(fr::MappingError::UnsupportedCombinationOfImageTypes)
));
}
}
mod srgb_tests {
use super::*;
#[test]
fn srgb_into_rgb_test() {
let buffer: Vec<u8> = (0u8..=255).flat_map(|v| [v, v, v]).collect();
let src_image =
fr::Image::from_vec_u8(nonzero(16), nonzero(16), buffer, fr::PixelType::U8x3).unwrap();
let src_checksum = testing::image_checksum::<3>(src_image.buffer());
assert_eq!(src_checksum, [32640, 32640, 32640]);
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U8x3);
fr::color::srgb::srgb_into_rgb(&src_image.view(), &mut dst_image.view_mut()).unwrap();
let dst_checksum = testing::image_checksum::<3>(dst_image.buffer());
assert_eq!(dst_checksum, [20304, 20304, 20304]);
}
#[test]
fn srgba_into_rgba_test() {
let buffer: Vec<u8> = (0u8..=255).flat_map(|v| [v, v, v, 255]).collect();
let src_image =
fr::Image::from_vec_u8(nonzero(16), nonzero(16), buffer, fr::PixelType::U8x4).unwrap();
let src_checksum = testing::image_checksum::<4>(src_image.buffer());
assert_eq!(src_checksum, [32640, 32640, 32640, 65280]);
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U8x4);
fr::color::srgb::srgb_into_rgb(&src_image.view(), &mut dst_image.view_mut()).unwrap();
let dst_checksum = testing::image_checksum::<4>(dst_image.buffer());
assert_eq!(dst_checksum, [20304, 20304, 20304, 65280]);
}
#[test]
fn srgb_into_rgb_errors_test() {
let buffer: Vec<u8> = (0u8..=255).flat_map(|v| [v, v, v]).collect();
let src_image =
fr::Image::from_vec_u8(nonzero(16), nonzero(16), buffer, fr::PixelType::U8x3).unwrap();
let mut dst_image = fr::Image::new(nonzero(16), nonzero(1), fr::PixelType::U8x3);
let result = fr::color::srgb::srgb_into_rgb(&src_image.view(), &mut dst_image.view_mut());
assert!(matches!(result, Err(fr::MappingError::DifferentDimensions)));
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U8x2);
let result = fr::color::srgb::srgb_into_rgb(&src_image.view(), &mut dst_image.view_mut());
assert!(matches!(
result,
Err(fr::MappingError::UnsupportedCombinationOfImageTypes)
));
}
#[test]
fn rgb_into_srgb_test() {
let buffer: Vec<u8> = (0u8..=255).flat_map(|v| [v, v, v]).collect();
let src_image =
fr::Image::from_vec_u8(nonzero(16), nonzero(16), buffer, fr::PixelType::U8x3).unwrap();
let src_checksum = testing::image_checksum::<3>(src_image.buffer());
assert_eq!(src_checksum, [32640, 32640, 32640]);
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U8x3);
fr::color::srgb::rgb_into_srgb(&src_image.view(), &mut dst_image.view_mut()).unwrap();
let dst_checksum = testing::image_checksum::<3>(dst_image.buffer());
assert_eq!(dst_checksum, [44981, 44981, 44981]);
}
#[test]
fn rgb_into_srgb_errors_test() {
let buffer: Vec<u8> = (0u8..=255).flat_map(|v| [v, v, v]).collect();
let src_image =
fr::Image::from_vec_u8(nonzero(16), nonzero(16), buffer, fr::PixelType::U8x3).unwrap();
let mut dst_image = fr::Image::new(nonzero(16), nonzero(1), fr::PixelType::U8x3);
let result = fr::color::srgb::rgb_into_srgb(&src_image.view(), &mut dst_image.view_mut());
assert!(matches!(result, Err(fr::MappingError::DifferentDimensions)));
let mut dst_image = fr::Image::new(nonzero(16), nonzero(16), fr::PixelType::U8x2);
let result = fr::color::srgb::rgb_into_srgb(&src_image.view(), &mut dst_image.view_mut());
assert!(matches!(
result,
Err(fr::MappingError::UnsupportedCombinationOfImageTypes)
));
}
}