pub mod crop;
pub mod pixel_vec;
pub mod read;
pub mod recursive;
pub mod write;
use half::f16;
use smallvec::SmallVec;
use crate::{
compression::Compression,
error::Error,
math::{RoundingMode, Vec2},
meta::{
attribute::{LineOrder, Text},
header::{ImageAttributes, LayerAttributes},
},
};
pub(crate) const fn ignore_progress(_progress: f64) {}
pub type AnyImage = Image<Layers<AnyChannels<Levels<FlatSamples>>>>;
pub type FlatImage = Image<Layers<AnyChannels<FlatSamples>>>;
pub type PixelLayersImage<Storage, Channels> = Image<Layers<SpecificChannels<Storage, Channels>>>;
pub type PixelImage<Storage, Channels> = Image<Layer<SpecificChannels<Storage, Channels>>>;
pub type RgbaLayersImage<Storage> = PixelLayersImage<Storage, RgbaChannels>;
pub type RgbaImage<Storage> = PixelImage<Storage, RgbaChannels>;
pub type RgbaChannels =
(ChannelDescription, ChannelDescription, ChannelDescription, Option<ChannelDescription>);
pub type RgbChannels = (ChannelDescription, ChannelDescription, ChannelDescription);
#[derive(Debug, Clone, PartialEq)]
pub struct Image<Layers> {
pub attributes: ImageAttributes,
pub layer_data: Layers,
}
pub type Layers<Channels> = SmallVec<[Layer<Channels>; 2]>;
#[derive(Debug, Clone, PartialEq)]
pub struct Layer<Channels> {
pub channel_data: Channels,
pub attributes: LayerAttributes,
pub size: Vec2<usize>,
pub encoding: Encoding,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Encoding {
pub compression: Compression,
pub blocks: Blocks,
pub line_order: LineOrder,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Blocks {
ScanLines,
Tiles(Vec2<usize>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpecificChannels<Pixels, ChannelsDescription> {
pub channels: ChannelsDescription,
pub pixels: Pixels, }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnyChannels<Samples> {
pub list: SmallVec<[AnyChannel<Samples>; 4]>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnyChannel<Samples> {
pub name: Text,
pub sample_data: Samples,
pub quantize_linearly: bool,
pub sampling: Vec2<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Levels<Samples> {
Singular(Samples),
Mip {
rounding_mode: RoundingMode,
level_data: LevelMaps<Samples>,
},
Rip {
rounding_mode: RoundingMode,
level_data: RipMaps<Samples>,
},
}
pub type LevelMaps<Samples> = Vec<Samples>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RipMaps<Samples> {
pub map_data: LevelMaps<Samples>,
pub level_count: Vec2<usize>,
}
#[derive(Clone, PartialEq)] pub enum FlatSamples {
F16(Vec<f16>),
F32(Vec<f32>),
U32(Vec<u32>),
}
use std::{marker::PhantomData, ops::Not};
use crate::{
block::samples::{Sample, *},
error::Result,
image::{
recursive::{IntoRecursive, NoneMore, Recursive},
validate_results::ValidationOptions,
write::{channels::*, layers::WritableLayers, samples::WritableSamples},
},
io::Data,
meta::{attribute::*, mip_map_levels, rip_map_levels},
};
impl<Channels> Layer<Channels> {
pub fn absolute_bounds(&self) -> IntegerBounds {
IntegerBounds::new(self.attributes.layer_position, self.size)
}
}
impl<SampleStorage, Channels> SpecificChannels<SampleStorage, Channels> {
pub const fn new(channels: Channels, source_samples: SampleStorage) -> Self
where
SampleStorage: GetPixel,
SampleStorage::Pixel: IntoRecursive,
Channels: Sync + Clone + IntoRecursive,
<Channels as IntoRecursive>::Recursive:
WritableChannelsDescription<<SampleStorage::Pixel as IntoRecursive>::Recursive>,
{
Self {
channels,
pixels: source_samples,
}
}
}
pub trait IntoSample: IntoNativeSample {
const PREFERRED_SAMPLE_TYPE: SampleType;
}
impl IntoSample for f16 {
const PREFERRED_SAMPLE_TYPE: SampleType = SampleType::F16;
}
impl IntoSample for f32 {
const PREFERRED_SAMPLE_TYPE: SampleType = SampleType::F32;
}
impl IntoSample for u32 {
const PREFERRED_SAMPLE_TYPE: SampleType = SampleType::U32;
}
#[derive(Debug)]
pub struct SpecificChannelsBuilder<RecursiveChannels, RecursivePixel> {
channels: RecursiveChannels,
px: PhantomData<RecursivePixel>,
}
pub trait CheckDuplicates {
fn already_contains(&self, name: &Text) -> bool;
}
impl CheckDuplicates for NoneMore {
fn already_contains(&self, _: &Text) -> bool {
false
}
}
impl<Inner: CheckDuplicates> CheckDuplicates for Recursive<Inner, ChannelDescription> {
fn already_contains(&self, name: &Text) -> bool {
&self.value.name == name || self.inner.already_contains(name)
}
}
impl SpecificChannels<(), ()> {
pub fn build() -> SpecificChannelsBuilder<NoneMore, NoneMore> {
SpecificChannelsBuilder {
channels: NoneMore,
px: Default::default(),
}
}
}
impl<RecursiveChannels: CheckDuplicates, RecursivePixel>
SpecificChannelsBuilder<RecursiveChannels, RecursivePixel>
{
pub fn with_channel<Sample: IntoSample>(
self,
name: impl Into<Text>,
) -> SpecificChannelsBuilder<
Recursive<RecursiveChannels, ChannelDescription>,
Recursive<RecursivePixel, Sample>,
> {
self.with_channel_details::<Sample>(ChannelDescription::named(
name,
Sample::PREFERRED_SAMPLE_TYPE,
))
}
pub fn with_channel_details<Sample: Into<Sample>>(
self,
channel: ChannelDescription,
) -> SpecificChannelsBuilder<
Recursive<RecursiveChannels, ChannelDescription>,
Recursive<RecursivePixel, Sample>,
> {
assert!(
self.channels.already_contains(&channel.name).not(),
"channel name `{}` is duplicate",
channel.name
);
SpecificChannelsBuilder {
channels: Recursive::new(self.channels, channel),
px: PhantomData,
}
}
pub fn with_pixels<Pixels>(
self,
get_pixel: Pixels,
) -> SpecificChannels<Pixels, RecursiveChannels>
where
Pixels: GetPixel,
<Pixels as GetPixel>::Pixel: IntoRecursive<Recursive = RecursivePixel>,
{
SpecificChannels {
channels: self.channels,
pixels: get_pixel,
}
}
pub fn with_pixel_fn<Pixel, Pixels>(
self,
get_pixel: Pixels,
) -> SpecificChannels<Pixels, RecursiveChannels>
where
Pixels: Sync + Fn(Vec2<usize>) -> Pixel,
Pixel: IntoRecursive<Recursive = RecursivePixel>,
{
SpecificChannels {
channels: self.channels,
pixels: get_pixel,
}
}
}
impl<SampleStorage>
SpecificChannels<
SampleStorage,
(ChannelDescription, ChannelDescription, ChannelDescription, ChannelDescription),
>
{
pub fn rgba<R, G, B, A>(source_samples: SampleStorage) -> Self
where
R: IntoSample,
G: IntoSample,
B: IntoSample,
A: IntoSample,
SampleStorage: GetPixel<Pixel = (R, G, B, A)>,
{
Self {
channels: (
ChannelDescription::named("R", R::PREFERRED_SAMPLE_TYPE),
ChannelDescription::named("G", G::PREFERRED_SAMPLE_TYPE),
ChannelDescription::named("B", B::PREFERRED_SAMPLE_TYPE),
ChannelDescription::named("A", A::PREFERRED_SAMPLE_TYPE),
),
pixels: source_samples,
}
}
}
impl<SampleStorage>
SpecificChannels<SampleStorage, (ChannelDescription, ChannelDescription, ChannelDescription)>
{
pub fn rgb<R, G, B>(source_samples: SampleStorage) -> Self
where
R: IntoSample,
G: IntoSample,
B: IntoSample,
SampleStorage: GetPixel<Pixel = (R, G, B)>,
{
Self {
channels: (
ChannelDescription::named("R", R::PREFERRED_SAMPLE_TYPE),
ChannelDescription::named("G", G::PREFERRED_SAMPLE_TYPE),
ChannelDescription::named("B", B::PREFERRED_SAMPLE_TYPE),
),
pixels: source_samples,
}
}
}
pub type FlatSamplesPixel = SmallVec<[Sample; 8]>;
impl Layer<AnyChannels<FlatSamples>> {
pub fn sample_vec_at(&self, position: Vec2<usize>) -> FlatSamplesPixel {
self.samples_at(position).collect()
}
pub const fn samples_at(&self, position: Vec2<usize>) -> FlatSampleIterator<'_> {
FlatSampleIterator {
layer: self,
channel_index: 0,
position,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct FlatSampleIterator<'s> {
layer: &'s Layer<AnyChannels<FlatSamples>>,
channel_index: usize,
position: Vec2<usize>,
}
impl Iterator for FlatSampleIterator<'_> {
type Item = Sample;
fn next(&mut self) -> Option<Self::Item> {
if self.channel_index < self.layer.channel_data.list.len() {
let channel = &self.layer.channel_data.list[self.channel_index];
let sample = channel
.sample_data
.value_by_flat_index(self.position.flat_index_for_size(self.layer.size));
self.channel_index += 1;
Some(sample)
} else {
None
}
}
fn nth(&mut self, pos: usize) -> Option<Self::Item> {
self.channel_index += pos;
self.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.layer.channel_data.list.len().saturating_sub(self.channel_index);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for FlatSampleIterator<'_> {}
impl<SampleData> AnyChannels<SampleData> {
pub fn sort(mut list: SmallVec<[AnyChannel<SampleData>; 4]>) -> Self {
list.sort_unstable_by_key(|channel| channel.name.clone()); Self {
list,
}
}
}
impl<LevelSamples> Levels<LevelSamples> {
pub fn get_level(&self, level: Vec2<usize>) -> Result<&LevelSamples> {
match self {
Self::Singular(block) => {
debug_assert_eq!(
level,
Vec2(0, 0),
"singular image cannot write leveled blocks bug"
);
Ok(block)
}
Self::Mip {
level_data,
..
} => {
debug_assert_eq!(
level.x(),
level.y(),
"mip map levels must be equal on x and y bug"
);
level_data.get(level.x()).ok_or_else(|| {
Error::invalid(format!(
"mip level index {} out of range (max: {})",
level.x(),
level_data.len().saturating_sub(1)
))
})
}
Self::Rip {
level_data,
..
} => level_data
.get_by_level(level)
.ok_or_else(|| Error::invalid(format!("rip level index {level:?} not found"))),
}
}
pub fn get_level_mut(&mut self, level: Vec2<usize>) -> Result<&mut LevelSamples> {
match self {
Self::Singular(ref mut block) => {
debug_assert_eq!(
level,
Vec2(0, 0),
"singular image cannot write leveled blocks bug"
);
Ok(block)
}
Self::Mip {
level_data,
..
} => {
debug_assert_eq!(
level.x(),
level.y(),
"mip map levels must be equal on x and y bug"
);
let max_level = level_data.len().saturating_sub(1);
let level_index = level.x();
level_data.get_mut(level_index).ok_or_else(|| {
Error::invalid(format!(
"mip level index {level_index} out of range (max: {max_level})"
))
})
}
Self::Rip {
level_data,
..
} => level_data
.get_by_level_mut(level)
.ok_or_else(|| Error::invalid(format!("rip level index {level:?} not found"))),
}
}
pub fn levels_as_slice(&self) -> &[LevelSamples] {
match self {
Self::Singular(data) => std::slice::from_ref(data),
Self::Mip {
level_data,
..
} => level_data,
Self::Rip {
level_data,
..
} => &level_data.map_data,
}
}
pub fn levels_as_slice_mut(&mut self) -> &mut [LevelSamples] {
match self {
Self::Singular(data) => std::slice::from_mut(data),
Self::Mip {
level_data,
..
} => level_data,
Self::Rip {
level_data,
..
} => &mut level_data.map_data,
}
}
pub const fn level_mode(&self) -> LevelMode {
match self {
Self::Singular(_) => LevelMode::Singular,
Self::Mip {
..
} => LevelMode::MipMap,
Self::Rip {
..
} => LevelMode::RipMap,
}
}
}
impl<Samples> RipMaps<Samples> {
pub fn get_level_index(&self, level: Vec2<usize>) -> usize {
level.flat_index_for_size(self.level_count)
}
pub fn get_by_level(&self, level: Vec2<usize>) -> Option<&Samples> {
self.map_data.get(self.get_level_index(level))
}
pub fn get_by_level_mut(&mut self, level: Vec2<usize>) -> Option<&mut Samples> {
let index = self.get_level_index(level);
self.map_data.get_mut(index)
}
}
impl FlatSamples {
pub fn len(&self) -> usize {
match self {
Self::F16(vec) => vec.len(),
Self::F32(vec) => vec.len(),
Self::U32(vec) => vec.len(),
}
}
pub fn values_as_f32(&self) -> impl '_ + Iterator<Item = f32> {
self.values().map(super::block::samples::Sample::to_f32)
}
pub fn values(&self) -> impl '_ + Iterator<Item = Sample> {
(0..self.len()).map(move |index| self.value_by_flat_index(index))
}
pub fn value_by_flat_index(&self, index: usize) -> Sample {
match self {
Self::F16(vec) => Sample::F16(vec[index]),
Self::F32(vec) => Sample::F32(vec[index]),
Self::U32(vec) => Sample::U32(vec[index]),
}
}
}
impl<'s, ChannelData: 's> Layer<ChannelData> {
pub fn new(
dimensions: impl Into<Vec2<usize>>,
attributes: LayerAttributes,
encoding: Encoding,
channels: ChannelData,
) -> Self
where
ChannelData: WritableChannels<'s>,
{
Self {
channel_data: channels,
attributes,
size: dimensions.into(),
encoding,
}
}
pub fn levels_with_resolution<'l, L>(
&self,
levels: &'l Levels<L>,
) -> Box<dyn 'l + Iterator<Item = (&'l L, Vec2<usize>)>> {
match levels {
Levels::Singular(level) => Box::new(std::iter::once((level, self.size))),
Levels::Mip {
rounding_mode,
level_data,
} => Box::new(
level_data
.iter()
.zip(mip_map_levels(*rounding_mode, self.size).map(|(_index, size)| size)),
),
Levels::Rip {
rounding_mode,
level_data,
} => Box::new(
level_data
.map_data
.iter()
.zip(rip_map_levels(*rounding_mode, self.size).map(|(_index, size)| size)),
),
}
}
}
impl Encoding {
pub const FAST_LOSSLESS: Self = Self {
compression: Compression::RLE,
blocks: Blocks::Tiles(Vec2(64, 64)), line_order: LineOrder::Unspecified,
};
pub const SMALL_FAST_LOSSLESS: Self = Self {
compression: Compression::PIZ,
blocks: Blocks::Tiles(Vec2(256, 256)),
line_order: LineOrder::Unspecified,
};
pub const SMALL_LOSSLESS: Self = Self {
compression: Compression::ZIP16,
blocks: Blocks::ScanLines,
line_order: LineOrder::Increasing,
};
pub const UNCOMPRESSED: Self = Self {
compression: Compression::Uncompressed,
blocks: Blocks::ScanLines, line_order: LineOrder::Increasing, };
}
impl Default for Encoding {
fn default() -> Self {
Self::FAST_LOSSLESS
}
}
impl<'s, LayerData: 's> Image<LayerData>
where
LayerData: WritableLayers<'s>,
{
pub const fn new(image_attributes: ImageAttributes, layer_data: LayerData) -> Self {
Self {
attributes: image_attributes,
layer_data,
}
}
}
impl<'s, Channels: 's> Image<Layers<Channels>>
where
Channels: WritableChannels<'s>,
{
pub fn from_layers(
image_attributes: ImageAttributes,
layer_data: impl Into<Layers<Channels>>,
) -> Self {
Self::new(image_attributes, layer_data.into())
}
}
impl<'s, ChannelData: 's> Image<Layer<ChannelData>>
where
ChannelData: WritableChannels<'s>,
{
pub fn from_layer(layer: Layer<ChannelData>) -> Self {
let bounds = IntegerBounds::new(layer.attributes.layer_position, layer.size);
Self::new(ImageAttributes::new(bounds), layer)
}
pub fn from_encoded_channels(
size: impl Into<Vec2<usize>>,
encoding: Encoding,
channels: ChannelData,
) -> Self {
Self::from_layer(Layer::new(size, LayerAttributes::default(), encoding, channels))
}
pub fn from_channels(size: impl Into<Vec2<usize>>, channels: ChannelData) -> Self {
Self::from_encoded_channels(size, Encoding::default(), channels)
}
}
impl Image<NoneMore> {
#[must_use]
pub const fn empty(attributes: ImageAttributes) -> Self {
Self {
attributes,
layer_data: NoneMore,
}
}
}
impl<'s, InnerLayers: 's> Image<InnerLayers>
where
InnerLayers: WritableLayers<'s>,
{
pub fn with_layer<NewChannels>(
self,
layer: Layer<NewChannels>,
) -> Image<Recursive<InnerLayers, Layer<NewChannels>>>
where
NewChannels: 's + WritableChannels<'s>,
{
Image {
attributes: self.attributes,
layer_data: Recursive::new(self.layer_data, layer),
}
}
}
impl<'s, SampleData: 's> AnyChannel<SampleData> {
pub fn new(name: impl Into<Text>, sample_data: SampleData) -> Self
where
SampleData: WritableSamples<'s>,
{
let name: Text = name.into();
Self {
quantize_linearly: ChannelDescription::guess_quantization_linearity(&name),
name,
sample_data,
sampling: Vec2(1, 1),
}
}
}
impl std::fmt::Debug for FlatSamples {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.len() <= 6 {
match self {
Self::F16(vec) => vec.fmt(formatter),
Self::F32(vec) => vec.fmt(formatter),
Self::U32(vec) => vec.fmt(formatter),
}
} else {
match self {
Self::F16(vec) => write!(formatter, "[f16; {}]", vec.len()),
Self::F32(vec) => write!(formatter, "[f32; {}]", vec.len()),
Self::U32(vec) => write!(formatter, "[u32; {}]", vec.len()),
}
}
}
}
pub mod validate_results {
use std::ops::Not;
use smallvec::Array;
use crate::{
block::samples::IntoNativeSample,
image::write::samples::WritableSamples,
prelude::{recursive::*, *},
};
pub trait ValidateResult {
fn assert_equals_result(&self, result: &Self) {
self.validate_result(result, ValidationOptions::default(), String::new).unwrap();
}
fn assert_approx_equals_result(&self, result: &Self) {
self.validate_result(
result,
ValidationOptions {
allow_lossy: true,
nan_converted_to_zero: false,
},
String::new,
)
.unwrap();
}
fn validate_result(
&self,
lossy_result: &Self,
options: ValidationOptions,
context: impl Fn() -> String,
) -> ValidationResult;
}
#[derive(Default, Debug, Eq, PartialEq, Hash, Copy, Clone)]
pub struct ValidationOptions {
allow_lossy: bool,
nan_converted_to_zero: bool,
}
pub type ValidationResult = std::result::Result<(), String>;
impl<C> ValidateResult for Image<C>
where
C: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
if self.attributes == other.attributes {
self.layer_data.validate_result(&other.layer_data, options, || {
location() + "| image > layer data"
})
} else {
Err(location() + "| image > attributes")
}
}
}
impl<S> ValidateResult for Layer<AnyChannels<S>>
where
AnyChannel<S>: ValidateResult,
S: for<'a> WritableSamples<'a>,
{
fn validate_result(
&self,
other: &Self,
_overridden: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
let location = || format!("{} (layer `{:?}`)", location(), self.attributes.layer_name);
if self.attributes != other.attributes {
Err(location() + " > attributes")
} else if self.encoding != other.encoding {
Err(location() + " > encoding")
} else if self.size != other.size {
Err(location() + " > size")
} else if self.channel_data.list.len() != other.channel_data.list.len() {
Err(location() + " > channel count")
} else {
for (own_chan, other_chan) in
self.channel_data.list.iter().zip(other.channel_data.list.iter())
{
own_chan.validate_result(
other_chan,
ValidationOptions {
allow_lossy: other
.encoding
.compression
.is_lossless_for(other_chan.sample_data.sample_type())
.not(),
nan_converted_to_zero: other.encoding.compression.supports_nan().not(),
},
|| format!("{} > channel `{}`", location(), own_chan.name),
)?;
}
Ok(())
}
}
}
impl<Px, Desc> ValidateResult for Layer<SpecificChannels<Px, Desc>>
where
SpecificChannels<Px, Desc>: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
_overridden: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
let location = || format!("{} (layer `{:?}`)", location(), self.attributes.layer_name);
if self.attributes != other.attributes {
Err(location() + " > attributes")
} else if self.encoding != other.encoding {
Err(location() + " > encoding")
} else if self.size != other.size {
Err(location() + " > size")
} else {
let options = ValidationOptions {
allow_lossy: other.encoding.compression.may_loose_data(),
nan_converted_to_zero: other.encoding.compression.supports_nan().not(),
};
self.channel_data.validate_result(&other.channel_data, options, || {
location() + " > channel_data"
})?;
Ok(())
}
}
}
impl<S> ValidateResult for AnyChannels<S>
where
S: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
self.list.validate_result(&other.list, options, location)
}
}
impl<S> ValidateResult for AnyChannel<S>
where
S: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
if self.name != other.name {
Err(location() + " > name")
} else if self.quantize_linearly != other.quantize_linearly {
Err(location() + " > quantize_linearly")
} else if self.sampling != other.sampling {
Err(location() + " > sampling")
} else {
self.sample_data
.validate_result(&other.sample_data, options, || location() + " > sample_data")
}
}
}
impl<Pxs, Chans> ValidateResult for SpecificChannels<Pxs, Chans>
where
Pxs: ValidateResult,
Chans: Eq,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
if self.channels == other.channels {
self.pixels
.validate_result(&other.pixels, options, || location() + " > specific pixels")
} else {
Err(location() + " > specific channels")
}
}
}
impl<S> ValidateResult for Levels<S>
where
S: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
self.levels_as_slice()
.validate_result(&other.levels_as_slice(), options, || location() + " > levels")
}
}
impl ValidateResult for FlatSamples {
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
use FlatSamples::*;
match (self, other) {
(F16(values), F16(other_values)) => {
values.as_slice().validate_result(&other_values.as_slice(), options, || {
location() + " > f16 samples"
})
}
(F32(values), F32(other_values)) => {
values.as_slice().validate_result(&other_values.as_slice(), options, || {
location() + " > f32 samples"
})
}
(U32(values), U32(other_values)) => {
values.as_slice().validate_result(&other_values.as_slice(), options, || {
location() + " > u32 samples"
})
}
(own, other) => Err(format!(
"{}: samples type mismatch. expected {:?}, found {:?}",
location(),
own.sample_type(),
other.sample_type()
)),
}
}
}
impl<T> ValidateResult for &[T]
where
T: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
if self.len() == other.len() {
for (index, (slf, other)) in self.iter().zip(other.iter()).enumerate() {
slf.validate_result(other, options, || {
format!("{} element [{}] of {}", location(), index, self.len())
})?;
}
Ok(())
} else {
Err(location() + " count")
}
}
}
impl<A: Array> ValidateResult for SmallVec<A>
where
A::Item: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
self.as_slice().validate_result(&other.as_slice(), options, location)
}
}
impl<A> ValidateResult for Vec<A>
where
A: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
self.as_slice().validate_result(&other.as_slice(), options, location)
}
}
impl<A, B, C, D> ValidateResult for (A, B, C, D)
where
A: Clone + ValidateResult,
B: Clone + ValidateResult,
C: Clone + ValidateResult,
D: Clone + ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
self.clone().into_recursive().validate_result(
&other.clone().into_recursive(),
options,
location,
)
}
}
impl<A, B, C> ValidateResult for (A, B, C)
where
A: Clone + ValidateResult,
B: Clone + ValidateResult,
C: Clone + ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
self.clone().into_recursive().validate_result(
&other.clone().into_recursive(),
options,
location,
)
}
}
impl ValidateResult for NoneMore {
fn validate_result(
&self,
_: &Self,
_: ValidationOptions,
_: impl Fn() -> String,
) -> ValidationResult {
Ok(())
}
}
impl<Inner, T> ValidateResult for Recursive<Inner, T>
where
Inner: ValidateResult,
T: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
self.value
.validate_result(&other.value, options, &location)
.and_then(|()| self.inner.validate_result(&other.inner, options, &location))
}
}
impl<S> ValidateResult for Option<S>
where
S: ValidateResult,
{
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
match (self, other) {
(None, None) => Ok(()),
(Some(value), Some(other)) => value.validate_result(other, options, location),
_ => Err(location() + ": option mismatch"),
}
}
}
impl ValidateResult for f32 {
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
if self == other
|| (self.is_nan() && other.is_nan())
|| (options.nan_converted_to_zero && !self.is_normal() && *other == 0.0)
{
return Ok(());
}
if options.allow_lossy {
let epsilon = 0.06;
let max_difference = 0.1;
let adaptive_threshold = epsilon * (self.abs() + other.abs());
let tolerance = adaptive_threshold.max(max_difference);
let difference = (self - other).abs();
return if difference <= tolerance {
Ok(())
} else {
Err(format!(
"{}: expected ~{}, found {} (adaptive tolerance {})",
location(),
self,
other,
tolerance
))
};
}
Err(format!("{}: expected exactly {}, found {}", location(), self, other))
}
}
impl ValidateResult for f16 {
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
if self.to_bits() == other.to_bits() {
Ok(())
} else {
self.to_f32().validate_result(&other.to_f32(), options, location)
}
}
}
impl ValidateResult for u32 {
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
if self == other {
Ok(())
} else {
self.to_f32().validate_result(&other.to_f32(), options, location)
}
}
}
impl ValidateResult for Sample {
fn validate_result(
&self,
other: &Self,
options: ValidationOptions,
location: impl Fn() -> String,
) -> ValidationResult {
use Sample::*;
match (self, other) {
(F16(a), F16(b)) => a.validate_result(b, options, || location() + " (f16)"),
(F32(a), F32(b)) => a.validate_result(b, options, || location() + " (f32)"),
(U32(a), U32(b)) => a.validate_result(b, options, || location() + " (u32)"),
(_, _) => Err(location() + ": sample type mismatch"),
}
}
}
#[cfg(test)]
mod test_value_result {
use std::{f32::consts::*, io::Cursor};
use crate::{
image::{
pixel_vec::PixelVec,
validate_results::{ValidateResult, ValidationOptions},
FlatSamples,
},
meta::attribute::LineOrder::Increasing,
};
fn expect_valid<T>(original: &T, result: &T, allow_lossy: bool, nan_converted_to_zero: bool)
where
T: ValidateResult,
{
original
.validate_result(
result,
ValidationOptions {
allow_lossy,
nan_converted_to_zero,
},
String::new,
)
.unwrap();
}
fn expect_invalid<T>(
original: &T,
result: &T,
allow_lossy: bool,
nan_converted_to_zero: bool,
) where
T: ValidateResult,
{
assert!(original
.validate_result(
result,
ValidationOptions {
allow_lossy,
nan_converted_to_zero
},
String::new
)
.is_err());
}
#[test]
fn test_f32() {
let original: &[f32] = &[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, -20.4, f32::NAN];
let lossy: &[f32] = &[0.0, 0.2, 0.2, 0.3, 0.4, 0.5, -20.5, f32::NAN];
expect_valid(&original, &original, true, true);
expect_valid(&original, &original, true, false);
expect_valid(&original, &original, false, true);
expect_valid(&original, &original, false, false);
expect_invalid(&original, &lossy, false, false);
expect_valid(&original, &lossy, true, false);
expect_invalid(&original, &&original[..original.len() - 2], true, true);
expect_valid(&1_000_f32, &1_001_f32, true, false);
expect_invalid(&1_000_f32, &1_200_f32, true, false);
expect_valid(&10_000_f32, &10_100_f32, true, false);
expect_invalid(&10_000_f32, &12_000_f32, true, false);
expect_valid(&33_120_f32, &30_120_f32, true, false);
expect_invalid(&33_120_f32, &20_120_f32, true, false);
}
#[test]
fn test_nan() {
let original: &[f32] = &[0.0, f32::NAN, f32::NAN];
let lossy: &[f32] = &[0.0, f32::NAN, 0.0];
expect_valid(&original, &lossy, true, true);
expect_invalid(&lossy, &original, true, true);
expect_valid(&lossy, &lossy, true, true);
expect_valid(&lossy, &lossy, false, true);
}
#[test]
fn test_error() {
fn print_error<T: ValidateResult>(original: &T, lossy: &T, allow_lossy: bool) {
let message = original
.validate_result(
lossy,
ValidationOptions {
allow_lossy,
..Default::default()
},
String::new, )
.unwrap_err();
println!("message: {message}");
}
let original: &[f32] = &[0.0, f32::NAN, f32::NAN];
let lossy: &[f32] = &[0.0, f32::NAN, 0.0];
print_error(&original, &lossy, false);
print_error(&2.0, &1.0, true);
print_error(&2.0, &1.0, false);
print_error(
&FlatSamples::F32(vec![0.1, 0.1]),
&FlatSamples::F32(vec![0.1, 0.2]),
false,
);
print_error(&FlatSamples::U32(vec![0, 0]), &FlatSamples::F32(vec![0.1, 0.2]), false);
{
let image = crate::prelude::read_all_data_from_file(
"tests/images/valid/openexr/MultiResolution/Kapaa.exr",
)
.unwrap();
let mut mutated = image.clone();
let samples = mutated
.layer_data
.first_mut()
.unwrap()
.channel_data
.list
.first_mut()
.unwrap()
.sample_data
.levels_as_slice_mut()
.first_mut()
.unwrap();
match samples {
FlatSamples::F16(vals) => vals[100] = vals[1],
FlatSamples::F32(vals) => vals[100] = vals[1],
FlatSamples::U32(vals) => vals[100] = vals[1],
}
print_error(&image, &mutated, false);
}
}
#[test]
fn test_uncompressed() {
use crate::prelude::*;
let original_pixels: [(f32, f32, f32); 4] = [
(0.0, -1.1, PI),
(0.0, -1.1, TAU),
(0.0, -1.1, f32::EPSILON),
(f32::NAN, 10000.1, -1024.009),
];
let mut file_bytes = Vec::new();
let original_image = Image::from_encoded_channels(
(2, 2),
Encoding {
compression: Compression::Uncompressed,
line_order: Increasing,
..Encoding::default()
},
SpecificChannels::rgb(PixelVec::new(Vec2(2, 2), original_pixels.to_vec())),
);
original_image.write().to_buffered(Cursor::new(&mut file_bytes)).unwrap();
let lossy_image = read()
.no_deep_data()
.largest_resolution_level()
.rgb_channels(PixelVec::<(f32, f32, f32)>::constructor, PixelVec::set_pixel)
.first_valid_layer()
.all_attributes()
.from_buffered(Cursor::new(&file_bytes))
.unwrap();
original_image.assert_equals_result(&original_image);
lossy_image.assert_equals_result(&lossy_image);
original_image.assert_equals_result(&lossy_image);
lossy_image.assert_equals_result(&original_image);
}
#[test]
fn test_compiles() {
use crate::prelude::*;
fn accepts_validatable_value(_: &impl ValidateResult) {}
let object: Levels<FlatSamples> = Levels::Singular(FlatSamples::F32(Vec::default()));
accepts_validatable_value(&object);
let object: AnyChannels<Levels<FlatSamples>> = AnyChannels::sort(SmallVec::default());
accepts_validatable_value(&object);
let layer: Layer<AnyChannels<Levels<FlatSamples>>> =
Layer::new((0, 0), Default::default(), Default::default(), object);
accepts_validatable_value(&layer);
let layers: Layers<AnyChannels<Levels<FlatSamples>>> = Default::default();
accepts_validatable_value(&layers);
let object: Image<Layer<AnyChannels<Levels<FlatSamples>>>> = Image::from_layer(layer);
object.assert_equals_result(&object);
}
}
#[test]
fn test_nan_compression_attribute() {
use std::io::Cursor;
use crate::{
image::pixel_vec::PixelVec,
prelude::{Compression::*, LineOrder::Increasing, *},
};
let all_compression_methods = [Uncompressed, RLE, ZIP1, ZIP16, PXR24, PIZ, B44, B44A];
let original_pixels: [(f32, f32, f16); 4] = [
(f32::NAN, f32::from_bits(0x7fc01234), f16::from_bits(0x7E01)),
(f32::NAN, f32::from_bits(0xffcabcde), f16::from_bits(0x7FFF)),
(f32::NAN, f32::from_bits(0x7f800001), f16::from_bits(0xFE01)),
(f32::NAN, f32::NAN, f16::NAN),
];
assert!(
original_pixels.iter().all(|&(a, b, c)| a.is_nan() && b.is_nan() && c.is_nan()),
"test case has a bug"
);
for compression in all_compression_methods {
let mut file_bytes = Vec::new();
let original_image = Image::from_encoded_channels(
(2, 2),
Encoding {
compression,
line_order: Increasing,
..Encoding::default()
},
SpecificChannels::rgb(PixelVec::new((2, 2), original_pixels.to_vec())),
);
let result = original_image.write().to_buffered(Cursor::new(&mut file_bytes));
if let Err(Error::NotSupported(_)) = result {
continue;
}
let reconstructed_image = read()
.no_deep_data()
.largest_resolution_level()
.rgb_channels(PixelVec::<(f32, f32, f16)>::constructor, PixelVec::set_pixel)
.first_valid_layer()
.all_attributes()
.from_buffered(Cursor::new(&file_bytes))
.unwrap();
assert_eq!(
original_image.layer_data.channel_data.pixels.pixels.len(),
reconstructed_image.layer_data.channel_data.pixels.pixels.len()
);
let was_nanness_preserved = reconstructed_image
.layer_data
.channel_data
.pixels
.pixels
.iter()
.all(|(r, g, b)| r.is_nan() && g.is_nan() && b.is_nan());
assert_eq!(
was_nanness_preserved,
compression.supports_nan(),
"{compression} nanness claims do not match real output"
);
let was_nan_pattern_preserved = reconstructed_image
.layer_data
.channel_data
.pixels
.pixels
.iter()
.zip(original_pixels.iter())
.all(|((r2, g2, b2), (r1, g1, b1))| {
r2.to_bits() == r1.to_bits()
&& g2.to_bits() == g1.to_bits()
&& b2.to_bits() == b1.to_bits()
});
assert_eq!(
was_nan_pattern_preserved,
compression.preserves_nan_bits(),
"{compression} nan bit claims do not match real output"
);
}
}
}