use std::time::Duration;
use crate::{MemoryFormat, SandboxPosture};
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
pub enum Orientation {
#[default]
Normal,
FlipHorizontal,
Rotate180,
FlipVertical,
Transpose,
Rotate90,
Transverse,
Rotate270,
}
impl Orientation {
pub fn from_exif(value: u16) -> Self {
match value {
2 => Self::FlipHorizontal,
3 => Self::Rotate180,
4 => Self::FlipVertical,
5 => Self::Transpose,
6 => Self::Rotate90,
7 => Self::Transverse,
8 => Self::Rotate270,
_ => Self::Normal,
}
}
pub fn exif_value(self) -> u16 {
match self {
Self::Normal => 1,
Self::FlipHorizontal => 2,
Self::Rotate180 => 3,
Self::FlipVertical => 4,
Self::Transpose => 5,
Self::Rotate90 => 6,
Self::Transverse => 7,
Self::Rotate270 => 8,
}
}
pub fn swaps_axes(self) -> bool {
matches!(
self,
Self::Transpose | Self::Rotate90 | Self::Transverse | Self::Rotate270
)
}
}
#[derive(Debug, Clone)]
pub struct Texture {
width: u32,
height: u32,
stride: u32,
format: MemoryFormat,
data: Box<[u8]>,
}
impl Texture {
pub fn from_parts(
width: u32,
height: u32,
stride: u32,
format: MemoryFormat,
data: Box<[u8]>,
) -> Option<Self> {
let expected = (stride as usize).checked_mul(height as usize)?;
if data.len() != expected {
return None;
}
if (stride as u64) < (width as u64) * (format.bytes_per_pixel() as u64) {
return None;
}
Some(Self {
width,
height,
stride,
format,
data,
})
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn stride(&self) -> u32 {
self.stride
}
pub fn format(&self) -> MemoryFormat {
self.format
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn into_data(self) -> Box<[u8]> {
self.data
}
}
#[derive(Debug, Clone)]
pub struct Frame {
texture: Texture,
delay: Option<Duration>,
}
impl Frame {
pub fn new(texture: Texture, delay: Option<Duration>) -> Self {
Self { texture, delay }
}
pub fn width(&self) -> u32 {
self.texture.width()
}
pub fn height(&self) -> u32 {
self.texture.height()
}
pub fn texture(&self) -> &Texture {
&self.texture
}
pub fn into_texture(self) -> Texture {
self.texture
}
pub fn delay(&self) -> Option<Duration> {
self.delay
}
}
#[derive(Debug, Clone)]
pub struct Image {
width: u32,
height: u32,
format_name: &'static str,
orientation: Orientation,
icc_profile: Option<Vec<u8>>,
exif: Option<Vec<u8>>,
frames: Vec<Frame>,
sandbox_posture: SandboxPosture,
}
impl Image {
pub fn from_parts(
format_name: &'static str,
width: u32,
height: u32,
frames: Vec<Frame>,
) -> Self {
Self {
width,
height,
format_name,
orientation: Orientation::Normal,
icc_profile: None,
exif: None,
frames,
sandbox_posture: SandboxPosture::none(),
}
}
pub fn set_orientation(&mut self, orientation: Orientation) {
self.orientation = orientation;
}
pub fn set_icc_profile(&mut self, profile: Vec<u8>) {
self.icc_profile = Some(profile);
}
pub fn set_exif(&mut self, exif: Vec<u8>) {
self.exif = Some(exif);
}
pub(crate) fn set_sandbox_posture(&mut self, posture: SandboxPosture) {
self.sandbox_posture = posture;
}
pub fn replace_frames(&mut self, frames: Vec<Frame>, width: u32, height: u32) {
self.frames = frames;
self.width = width;
self.height = height;
}
}
impl Image {
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn format_name(&self) -> &'static str {
self.format_name
}
pub fn orientation(&self) -> Orientation {
self.orientation
}
pub fn icc_profile(&self) -> Option<&[u8]> {
self.icc_profile.as_deref()
}
pub fn exif(&self) -> Option<&[u8]> {
self.exif.as_deref()
}
pub fn frames(&self) -> &[Frame] {
&self.frames
}
pub fn first_frame(&self) -> Option<&Frame> {
self.frames.first()
}
pub fn is_animated(&self) -> bool {
self.frames.len() > 1
}
pub fn sandbox_posture(&self) -> SandboxPosture {
self.sandbox_posture
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn orientation_round_trips_through_exif() {
for v in 1..=8 {
let o = Orientation::from_exif(v);
assert_eq!(o.exif_value(), v);
}
}
#[test]
fn unknown_exif_orientation_is_normal() {
assert_eq!(Orientation::from_exif(0), Orientation::Normal);
assert_eq!(Orientation::from_exif(9), Orientation::Normal);
assert_eq!(Orientation::from_exif(255), Orientation::Normal);
}
#[test]
fn orientation_swaps_axes_correctly() {
assert!(!Orientation::Normal.swaps_axes());
assert!(!Orientation::FlipHorizontal.swaps_axes());
assert!(!Orientation::Rotate180.swaps_axes());
assert!(!Orientation::FlipVertical.swaps_axes());
assert!(Orientation::Transpose.swaps_axes());
assert!(Orientation::Rotate90.swaps_axes());
assert!(Orientation::Transverse.swaps_axes());
assert!(Orientation::Rotate270.swaps_axes());
}
#[test]
fn texture_from_parts_validates_length() {
let data = vec![0u8; 16].into_boxed_slice();
let t = Texture::from_parts(2, 2, 8, MemoryFormat::R8g8b8a8, data).unwrap();
assert_eq!(t.width(), 2);
assert_eq!(t.height(), 2);
assert_eq!(t.stride(), 8);
assert_eq!(t.format(), MemoryFormat::R8g8b8a8);
assert_eq!(t.data().len(), 16);
}
#[test]
fn texture_from_parts_rejects_wrong_length() {
let data = vec![0u8; 15].into_boxed_slice();
assert!(Texture::from_parts(2, 2, 8, MemoryFormat::R8g8b8a8, data).is_none());
}
#[test]
fn texture_from_parts_rejects_stride_below_row() {
let data = vec![0u8; 12].into_boxed_slice();
assert!(Texture::from_parts(2, 2, 6, MemoryFormat::R8g8b8a8, data).is_none());
}
#[test]
fn image_accessors() {
let texture = Texture::from_parts(
1,
1,
4,
MemoryFormat::R8g8b8a8,
vec![1, 2, 3, 4].into_boxed_slice(),
)
.unwrap();
let frame = Frame::new(texture, None);
let mut img = Image::from_parts("png", 1, 1, vec![frame]);
assert_eq!(img.width(), 1);
assert_eq!(img.format_name(), "png");
assert_eq!(img.orientation(), Orientation::Normal);
assert!(!img.is_animated());
img.set_orientation(Orientation::Rotate90);
assert_eq!(img.orientation(), Orientation::Rotate90);
img.set_icc_profile(b"icc".to_vec());
assert_eq!(img.icc_profile(), Some(&b"icc"[..]));
img.set_exif(b"exif".to_vec());
assert_eq!(img.exif(), Some(&b"exif"[..]));
}
}