mirror of
https://github.com/Cykooz/fast_image_resize.git
synced 2026-10-08 01:11:09 +00:00
Added functions for converting image colorspace.
This commit is contained in:
Generated
+1
@@ -485,6 +485,7 @@ dependencies = [
|
||||
"image",
|
||||
"nix",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"png",
|
||||
"resize",
|
||||
"rgb",
|
||||
|
||||
@@ -21,6 +21,7 @@ exclude = ["/data"]
|
||||
|
||||
[dependencies]
|
||||
num-traits = "0.2.15"
|
||||
once_cell = "1.13.0"
|
||||
thiserror = "1.0.31"
|
||||
|
||||
|
||||
|
||||
+32
-9
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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
@@ -20,7 +20,7 @@ impl<'a> PixelsContainer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple image container.
|
||||
/// Simple container of image data.
|
||||
#[derive(Debug)]
|
||||
pub struct Image<'a> {
|
||||
width: NonZeroU32,
|
||||
|
||||
@@ -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
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user