use crate::{
block::lines::LineRefMut,
image::{FlatSamples, Levels, RipMaps},
math::{RoundingMode, Vec2},
meta::{
attribute::{LevelMode, SampleType, TileDescription},
header::Header,
mip_map_indices, mip_map_levels, rip_map_indices, rip_map_levels, BlockDescription,
},
};
pub trait WritableSamples<'slf> {
fn sample_type(&self) -> SampleType;
fn infer_level_modes(&self) -> (LevelMode, RoundingMode);
type Writer: SamplesWriter;
fn create_samples_writer(&'slf self, header: &Header) -> Self::Writer;
}
pub trait WritableLevel<'slf> {
fn sample_type(&self) -> SampleType;
type Writer: SamplesWriter;
fn create_level_writer(&'slf self, size: Vec2<usize>) -> Self::Writer;
}
pub trait SamplesWriter: Sync {
fn extract_line(&self, line: LineRefMut<'_>);
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct FlatSamplesWriter<'samples> {
resolution: Vec2<usize>, samples: &'samples FlatSamples,
}
impl<'samples> WritableSamples<'samples> for FlatSamples {
type Writer = FlatSamplesWriter<'samples>;
fn sample_type(&self) -> SampleType {
match self {
Self::F16(_) => SampleType::F16,
Self::F32(_) => SampleType::F32,
Self::U32(_) => SampleType::U32,
}
}
fn infer_level_modes(&self) -> (LevelMode, RoundingMode) {
(LevelMode::Singular, RoundingMode::Down)
}
fn create_samples_writer(&'samples self, header: &Header) -> Self::Writer {
FlatSamplesWriter {
resolution: header.layer_size,
samples: self,
}
}
}
impl<'samples> WritableLevel<'samples> for FlatSamples {
type Writer = FlatSamplesWriter<'samples>;
fn sample_type(&self) -> SampleType {
match self {
Self::F16(_) => SampleType::F16,
Self::F32(_) => SampleType::F32,
Self::U32(_) => SampleType::U32,
}
}
fn create_level_writer(&'samples self, size: Vec2<usize>) -> Self::Writer {
FlatSamplesWriter {
resolution: size,
samples: self,
}
}
}
impl SamplesWriter for FlatSamplesWriter<'_> {
fn extract_line(&self, line: LineRefMut<'_>) {
let image_width = self.resolution.width(); debug_assert_ne!(image_width, 0, "image width calculation bug");
let start_index = line.location.position.y() * image_width + line.location.position.x();
let end_index = start_index + line.location.sample_count;
debug_assert!(
start_index < end_index && end_index <= self.samples.len(),
"for resolution {:?}, this is an invalid line: {:?}",
self.resolution,
line.location
);
match self.samples {
FlatSamples::F16(samples) => {
line.write_samples_from_slice(&samples[start_index..end_index])
}
FlatSamples::F32(samples) => {
line.write_samples_from_slice(&samples[start_index..end_index])
}
FlatSamples::U32(samples) => {
line.write_samples_from_slice(&samples[start_index..end_index])
}
}
.expect("writing line bytes failed");
}
}
impl<'samples, LevelSamples> WritableSamples<'samples> for Levels<LevelSamples>
where
LevelSamples: WritableLevel<'samples>,
{
type Writer = LevelsWriter<LevelSamples::Writer>;
fn sample_type(&self) -> SampleType {
let sample_type = self.levels_as_slice().first().expect("no levels found").sample_type();
debug_assert!(
self.levels_as_slice().iter().skip(1).all(|ty| ty.sample_type() == sample_type),
"sample types must be the same across all levels"
);
sample_type
}
fn infer_level_modes(&self) -> (LevelMode, RoundingMode) {
match self {
Self::Singular(_) => (LevelMode::Singular, RoundingMode::Down),
Self::Mip {
rounding_mode,
..
} => (LevelMode::MipMap, *rounding_mode),
Self::Rip {
rounding_mode,
..
} => (LevelMode::RipMap, *rounding_mode),
}
}
fn create_samples_writer(&'samples self, header: &Header) -> Self::Writer {
let rounding = match header.blocks {
BlockDescription::Tiles(TileDescription {
rounding_mode,
..
}) => Some(rounding_mode),
BlockDescription::ScanLines => None,
};
LevelsWriter {
levels: match self {
Self::Singular(level) => {
Levels::Singular(level.create_level_writer(header.layer_size))
}
Self::Mip {
level_data,
rounding_mode,
} => {
debug_assert_eq!(
level_data.len(),
mip_map_indices(
rounding.expect("mip maps only with tiles"),
header.layer_size
)
.count(),
"invalid mip map count"
);
Levels::Mip {
rounding_mode: *rounding_mode,
level_data: level_data
.iter()
.zip(mip_map_levels(
rounding.expect("mip maps only with tiles"),
header.layer_size,
))
.map(|(level, (_level_index, level_size))| {
level.create_level_writer(level_size)
})
.collect(),
}
}
Self::Rip {
level_data,
rounding_mode,
} => {
debug_assert_eq!(
level_data.map_data.len(),
level_data.level_count.area(),
"invalid rip level count"
);
debug_assert_eq!(
level_data.map_data.len(),
rip_map_indices(
rounding.expect("rip maps only with tiles"),
header.layer_size
)
.count(),
"invalid rip map count"
);
Levels::Rip {
rounding_mode: *rounding_mode,
level_data: RipMaps {
level_count: level_data.level_count,
map_data: level_data
.map_data
.iter()
.zip(rip_map_levels(
rounding.expect("rip maps only with tiles"),
header.layer_size,
))
.map(|(level, (_level_index, level_size))| {
level.create_level_writer(level_size)
})
.collect(),
},
}
}
},
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct LevelsWriter<SamplesWriter> {
levels: Levels<SamplesWriter>,
}
impl<Samples> SamplesWriter for LevelsWriter<Samples>
where
Samples: SamplesWriter,
{
fn extract_line(&self, line: LineRefMut<'_>) {
self.levels
.get_level(line.location.level)
.expect("invalid level index") .extract_line(line);
}
}