|
|
|
@ -1,12 +1,15 @@
|
|
|
|
|
//! Portable image types.
|
|
|
|
|
|
|
|
|
|
use crate::{ |
|
|
|
|
data::{color::Color, read, vfs}, |
|
|
|
|
data::{color::Rgba8888, read, vfs}, |
|
|
|
|
iter::MaybeRev, |
|
|
|
|
}; |
|
|
|
|
use std::io::{self, Read}; |
|
|
|
|
|
|
|
|
|
/// The error type which is returned from reading an image.
|
|
|
|
|
#[derive(thiserror::Error, Debug)] |
|
|
|
|
#[non_exhaustive] |
|
|
|
|
pub enum ErrImageRead { |
|
|
|
|
pub enum Err { |
|
|
|
|
#[error("Bad color encoding in texture")] |
|
|
|
|
Encoding, |
|
|
|
|
#[error("No first mip level in texture")] |
|
|
|
@ -15,22 +18,25 @@ pub enum ErrImageRead {
|
|
|
|
|
Io(#[from] io::Error), |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// A two-dimensional RGBA8888 (LDR) image.
|
|
|
|
|
pub struct Image { |
|
|
|
|
data: Vec<Color>, |
|
|
|
|
data: Vec<Rgba8888>, |
|
|
|
|
|
|
|
|
|
width: usize, |
|
|
|
|
heigh: usize, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// A set of [`Image`]s with mip-levels.
|
|
|
|
|
pub struct MipImage { |
|
|
|
|
datum: Vec<Image>, |
|
|
|
|
data: Vec<Image>, |
|
|
|
|
|
|
|
|
|
width: usize, |
|
|
|
|
heigh: usize, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Image { |
|
|
|
|
pub fn get(&self, x: usize, y: usize) -> Option<&Color> { |
|
|
|
|
/// Returns the color at the pixel `(x, y)`.
|
|
|
|
|
pub fn get(&self, x: usize, y: usize) -> Option<&Rgba8888> { |
|
|
|
|
if x < self.width && y < self.heigh { |
|
|
|
|
self.data.get(y * self.width + x) |
|
|
|
|
} else { |
|
|
|
@ -38,35 +44,38 @@ impl Image {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn data(&self) -> &[Color] { |
|
|
|
|
/// Returns a slice into the raw image data.
|
|
|
|
|
pub fn data(&self) -> &[Rgba8888] { |
|
|
|
|
&self.data |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the width of the image.
|
|
|
|
|
pub const fn w(&self) -> usize { |
|
|
|
|
self.width |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the height of the image.
|
|
|
|
|
pub const fn h(&self) -> usize { |
|
|
|
|
self.heigh |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Creates an [`Image`] with a basic XOR texture in the specified
|
|
|
|
|
/// dimensions.
|
|
|
|
|
pub fn xor_texture(width: usize, heigh: usize) -> Self { |
|
|
|
|
let mut data = Vec::with_capacity(width * heigh); |
|
|
|
|
|
|
|
|
|
for x in 0..width { |
|
|
|
|
for y in 0..heigh { |
|
|
|
|
let c = x as u8 ^ y as u8; |
|
|
|
|
data.push(Color { r: c, g: c, b: c, a: u8::MAX }); |
|
|
|
|
data.push(Rgba8888 { r: c, g: c, b: c, a: u8::MAX }); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Self { data, width, heigh } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn read<R>(rd: &mut R) -> Result<Self, ErrImageRead> |
|
|
|
|
where |
|
|
|
|
R: Read, |
|
|
|
|
{ |
|
|
|
|
/// Creates an [`Image`] by reading a TARGA format stream.
|
|
|
|
|
pub fn read(rd: &mut impl Read) -> Result<Self, Err> { |
|
|
|
|
let mut head = [0; 18]; |
|
|
|
|
rd.read_exact(&mut head)?; |
|
|
|
|
|
|
|
|
@ -74,7 +83,7 @@ impl Image {
|
|
|
|
|
let alpha = match head[16] { |
|
|
|
|
| 24 => false, |
|
|
|
|
| 32 => true, |
|
|
|
|
| _ => return Err(ErrImageRead::Encoding), |
|
|
|
|
| _ => return Err(Err::Encoding), |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
let descr = head[17]; |
|
|
|
@ -85,7 +94,7 @@ impl Image {
|
|
|
|
|
|| (!alpha && descr & 0xF != 0) |
|
|
|
|
|| descr & 0xC0 != 0 |
|
|
|
|
{ |
|
|
|
|
return Err(ErrImageRead::Encoding); |
|
|
|
|
return Err(Err::Encoding); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// compile size info
|
|
|
|
@ -112,9 +121,9 @@ impl Image {
|
|
|
|
|
for line in MaybeRev::rev_if(cdat.chunks(wscan), from_bot) { |
|
|
|
|
for color in MaybeRev::rev_if(line.chunks(comps), !from_lft) { |
|
|
|
|
data.push(if alpha { |
|
|
|
|
Color { r: color[2], g: color[1], b: color[0], a: color[3] } |
|
|
|
|
Rgba8888 { r: color[2], g: color[1], b: color[0], a: color[3] } |
|
|
|
|
} else { |
|
|
|
|
Color { r: color[2], g: color[1], b: color[0], a: u8::MAX } |
|
|
|
|
Rgba8888 { r: color[2], g: color[1], b: color[0], a: u8::MAX } |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -124,32 +133,39 @@ impl Image {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl MipImage { |
|
|
|
|
/// Returns the specified mip level's [`Image`].
|
|
|
|
|
pub fn get(&self, level: usize) -> Option<&Image> { |
|
|
|
|
self.datum.get(level) |
|
|
|
|
self.data.get(level) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns a slice with all of the mip levels.
|
|
|
|
|
pub fn data(&self) -> &[Image] { |
|
|
|
|
&self.datum |
|
|
|
|
&self.data |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the level 0 image's width.
|
|
|
|
|
pub const fn w(&self) -> usize { |
|
|
|
|
self.width |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the level 0 image's height.
|
|
|
|
|
pub const fn h(&self) -> usize { |
|
|
|
|
self.heigh |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns the number of images.
|
|
|
|
|
pub fn levels(&self) -> usize { |
|
|
|
|
self.datum.len() |
|
|
|
|
self.data.len() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Returns `true` if there are no mip levels in this set.
|
|
|
|
|
pub fn is_empty(&self) -> bool { |
|
|
|
|
self.datum.is_empty() |
|
|
|
|
self.data.is_empty() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn read(vfs: &vfs::Vfs) -> Result<Self, ErrImageRead> { |
|
|
|
|
let mut datum = Vec::new(); |
|
|
|
|
/// Reads a mip-mapped image collection from a [`vfs::Vfs`].
|
|
|
|
|
pub fn read(vfs: &vfs::Vfs) -> Result<Self, Err> { |
|
|
|
|
let mut data = Vec::new(); |
|
|
|
|
let mut width = 0; |
|
|
|
|
let mut heigh = 0; |
|
|
|
|
|
|
|
|
@ -163,21 +179,21 @@ impl MipImage {
|
|
|
|
|
heigh = img.h(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
datum.push(img); |
|
|
|
|
data.push(img); |
|
|
|
|
|
|
|
|
|
i += 1; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !datum.is_empty() { |
|
|
|
|
Ok(Self { datum, width, heigh }) |
|
|
|
|
if !data.is_empty() { |
|
|
|
|
Ok(Self { data, width, heigh }) |
|
|
|
|
} else { |
|
|
|
|
Err(ErrImageRead::InsufficientLevels) |
|
|
|
|
Err(Err::InsufficientLevels) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl std::ops::Index<(usize, usize)> for Image { |
|
|
|
|
type Output = Color; |
|
|
|
|
type Output = Rgba8888; |
|
|
|
|
|
|
|
|
|
fn index(&self, index: (usize, usize)) -> &Self::Output { |
|
|
|
|
self.get(index.0, index.1).unwrap() |
|
|
|
|