use std::path::Path;
use std::fs::File;
use std::io::{Read, Seek, BufReader, Write, BufWriter};
use crate::math::{Vec2, RoundingMode};
use crate::error::{Result, Error, UnitResult};
use crate::meta::attributes::{PixelType, Channel, Text, LineOrder, TileDescription, LevelMode};
use std::convert::TryInto;
use crate::meta::{Header, ImageAttributes, LayerAttributes, MetaData, Blocks};
use half::f16;
use crate::image::{ReadOptions, OnReadProgress, WriteOptions, OnWriteProgress};
use crate::compression::Compression;
#[derive(Debug, Clone, PartialEq)]
pub struct Image {
pub data: Pixels,
pub resolution: Vec2<usize>,
pub has_alpha_channel: bool,
pub is_linear: bool,
pub image_attributes: ImageAttributes,
pub layer_attributes: LayerAttributes,
pub encoding: Encoding,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Encoding {
pub compression: Compression,
pub tiles: Option<Vec2<usize>>,
pub line_order: LineOrder,
}
#[derive(Clone, PartialEq)]
pub enum Pixels {
F16(Vec<f16>),
F32(Vec<f32>),
U32(Vec<u32>),
}
impl Image {
#[inline]
pub fn channel_count(&self) -> usize {
if self.has_alpha_channel { 4 } else { 3 }
}
#[inline]
pub fn calculate_vector_index_of_first_pixel_component(resolution: Vec2<usize>, channel_count: usize, pixel: Vec2<usize>) -> usize {
debug_assert!(pixel.0 < resolution.0 && pixel.1 < resolution.1, "coordinate out of range");
(pixel.1 * resolution.0 + pixel.0) * channel_count
}
#[inline]
pub fn vector_index_of_first_pixel_component(&self, pixel: Vec2<usize>) -> usize {
Self::calculate_vector_index_of_first_pixel_component(self.resolution, self.channel_count(), pixel)
}
#[inline]
#[must_use]
pub fn read_from_file(path: impl AsRef<Path>, options: ReadOptions<impl OnReadProgress>) -> Result<Self> {
Self::read_from_unbuffered(File::open(path)?, options)
}
#[inline]
#[must_use]
pub fn read_from_unbuffered(read: impl Read + Seek + Send, options: ReadOptions<impl OnReadProgress>) -> Result<Self> {
Self::read_from_buffered(BufReader::new(read), options)
}
#[inline]
#[must_use]
pub fn read_from_buffered(read: impl Read + Seek + Send, options: ReadOptions<impl OnReadProgress>) -> Result<Self> {
crate::image::read_filtered_lines_from_buffered(
read,
Self::extract,
|image, header, tile| {
tile.location.is_largest_resolution_level() && header.own_attributes.name == image.layer_attributes.name },
|image, meta, line| {
debug_assert_eq!(meta[line.location.layer].own_attributes.name, image.layer_attributes.name, "irrelevant header should be filtered out");
let channel_index = 3 - line.location.channel; let line_position = line.location.position;
let Vec2(width, height) = image.resolution;
let channel_count = image.channel_count();
let get_index_of_sample = move |sample_index| {
let location = line_position + Vec2(sample_index, 0);
debug_assert!(location.0 < width && location.1 < height, "coordinate out of range: {:?}", location);
let flat = location.1 * width + location.0;
let r_index = flat * channel_count;
r_index + channel_index
};
match &mut image.data {
Pixels::F16(vec) => for (sample_index, sample) in line.read_samples().enumerate() { vec[get_index_of_sample(sample_index)] = sample?;
},
Pixels::F32(vec) => for (sample_index, sample) in line.read_samples().enumerate() { vec[get_index_of_sample(sample_index)] = sample?;
},
Pixels::U32(vec) => for (sample_index, sample) in line.read_samples().enumerate() { vec[get_index_of_sample(sample_index)] = sample?;
},
};
Ok(())
},
options
)
}
fn allocate(header: &Header, linear: bool, alpha: bool, pixel_type: PixelType) -> Self {
let components = if alpha { 4 } else { 3 };
let samples = components * header.data_size.area();
Self {
resolution: header.data_size,
has_alpha_channel: alpha,
data: match pixel_type {
PixelType::F16 => Pixels::F16(vec![f16::from_f32(0.0); samples]),
PixelType::F32 => Pixels::F32(vec![0.0; samples]),
PixelType::U32 => Pixels::U32(vec![0; samples]),
},
is_linear: linear,
layer_attributes: header.own_attributes.clone(),
image_attributes: header.shared_attributes.clone(),
encoding: Encoding {
compression: header.compression,
line_order: header.line_order,
tiles: match header.blocks {
Blocks::Tiles(tiles) => Some(tiles.tile_size),
Blocks::ScanLines => None,
},
}
}
}
fn extract(headers: &[Header]) -> Result<Self> {
let first_header_name = headers.first()
.and_then(|header| header.own_attributes.name.as_ref());
for (header_index, header) in headers.iter().enumerate() {
if header_index != 0 && header.own_attributes.name.as_ref() == first_header_name {
return Err(Error::invalid("duplicate header name"))
}
let channels = &header.channels.list;
let is_rgba = channels.len() == 4
&& channels[0].name == "A".try_into().unwrap()
&& channels[1].name == "B".try_into().unwrap()
&& channels[2].name == "G".try_into().unwrap()
&& channels[3].name == "R".try_into().unwrap();
let is_rgb = channels.len() == 3
&& channels[0].name == "B".try_into().unwrap()
&& channels[1].name == "G".try_into().unwrap()
&& channels[2].name == "R".try_into().unwrap();
if !is_rgba && !is_rgb { continue; }
let first_channel: &Channel = &channels[0];
let pixel_type_mismatch = channels[1..].iter()
.any(|channel|
channel.pixel_type != first_channel.pixel_type
&& channel.is_linear == first_channel.is_linear
);
if pixel_type_mismatch { continue; }
return Ok(Self::allocate(header, first_channel.is_linear, is_rgba, first_channel.pixel_type))
}
Err(Error::invalid("no valid RGB or RGBA image part"))
}
#[must_use]
pub fn write_to_file(&self, path: impl AsRef<Path>, options: WriteOptions<impl OnWriteProgress>) -> UnitResult {
crate::io::attempt_delete_file_on_write_error(path, |write|
self.write_to_unbuffered(write, options)
)
}
#[must_use]
pub fn write_to_unbuffered(&self, write: impl Write + Seek, options: WriteOptions<impl OnWriteProgress>) -> UnitResult {
self.write_to_buffered(BufWriter::new(write), options)
}
#[must_use]
pub fn write_to_buffered(&self, write: impl Write + Seek, options: WriteOptions<impl OnWriteProgress>) -> UnitResult {
let pixel_type = match self.data {
Pixels::F16(_) => PixelType::F16,
Pixels::F32(_) => PixelType::F32,
Pixels::U32(_) => PixelType::U32,
};
let header = Header::new(
self.layer_attributes.name.clone().unwrap_or(Text::from("RGBA").unwrap()),
self.resolution,
if self.has_alpha_channel { smallvec![
Channel::new("A".try_into().unwrap(), pixel_type, self.is_linear), Channel::new("B".try_into().unwrap(), pixel_type, self.is_linear),
Channel::new("G".try_into().unwrap(), pixel_type, self.is_linear),
Channel::new("R".try_into().unwrap(), pixel_type, self.is_linear),
] }
else { smallvec![
Channel::new("B".try_into().unwrap(), pixel_type, self.is_linear),
Channel::new("G".try_into().unwrap(), pixel_type, self.is_linear),
Channel::new("R".try_into().unwrap(), pixel_type, self.is_linear),
] }
);
let header = header
.with_shared_attributes(self.image_attributes.clone())
.with_attributes(self.layer_attributes.clone())
.with_encoding(
self.encoding.compression,
match self.encoding.tiles {
None => Blocks::ScanLines,
Some(size) => Blocks::Tiles(TileDescription {
tile_size: size,
level_mode: LevelMode::Singular,
rounding_mode: RoundingMode::Down
})
},
self.encoding.line_order,
);
crate::image::write_all_lines_to_buffered(
write,
MetaData::new(smallvec![ header ]),
|_meta, line| {
let channel_index = 3 - line.location.channel; let line_position = line.location.position;
let Vec2(width, height) = self.resolution;
let channel_count = self.channel_count();
let get_index_of_sample = move |sample_index| {
let location = line_position + Vec2(sample_index, 0);
debug_assert!(location.0 < width && location.1 < height, "coordinate out of range: {:?}", location);
let flat = location.1 * width + location.0;
let r_index = flat * channel_count;
r_index + channel_index
};
match &self.data {
Pixels::F16(vec) => line.write_samples(|sample_index|{
vec[get_index_of_sample(sample_index)]
})?,
Pixels::F32(vec) => line.write_samples(|sample_index|{
vec[get_index_of_sample(sample_index)]
})?,
Pixels::U32(vec) => line.write_samples(|sample_index|{
vec[get_index_of_sample(sample_index)]
})?,
};
Ok(())
},
options
)
}
}
impl std::fmt::Debug for Pixels {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Pixels::F16(ref vec) => write!(formatter, "[F16; {}]", vec.len()),
Pixels::F32(ref vec) => write!(formatter, "[F32; {}]", vec.len()),
Pixels::U32(ref vec) => write!(formatter, "[U32; {}]", vec.len()),
}
}
}