Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added functions for converting image colorspace.
- Loading branch information
Showing
12 changed files
with
760 additions
and
18 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ exclude = ["/data"] | |
|
||
[dependencies] | ||
num-traits = "0.2.15" | ||
once_cell = "1.13.0" | ||
thiserror = "1.0.31" | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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), | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.