use crate::sections::PsdCursor;
use failure::{Error, Fail};
pub const EXPECTED_PSD_SIGNATURE: [u8; 4] = [56, 66, 80, 83];
const EXPECTED_VERSION: [u8; 2] = [0, 1];
const EXPECTED_RESERVED: [u8; 6] = [0; 6];
#[derive(Debug)]
pub struct FileHeaderSection {
pub(in crate) version: PsdVersion,
pub(in crate) channel_count: ChannelCount,
pub(in crate) width: PsdWidth,
pub(in crate) height: PsdHeight,
pub(in crate) depth: PsdDepth,
pub(in crate) color_mode: ColorMode,
}
#[derive(Debug, PartialEq, Fail)]
pub enum FileHeaderSectionError {
#[fail(
display = "A file section header is comprised of 26 bytes, you provided {} bytes.",
length
)]
IncorrectLength { length: usize },
#[fail(
display = r#"The first four bytes (indices 0-3) of a PSD must always equal [56, 66, 80, 83],
which in string form is '8BPS'."#
)]
InvalidSignature {},
#[fail(
display = r#"Bytes 5 and 6 (indices 4-5) must always be [0, 1], Representing a PSD version of 1."#
)]
InvalidVersion {},
#[fail(display = r#"Bytes 7-12 (indices 6-11) must be zeroes"#)]
InvalidReserved {},
}
impl FileHeaderSection {
pub fn from_bytes(bytes: &[u8]) -> Result<FileHeaderSection, Error> {
let mut cursor = PsdCursor::new(bytes);
if bytes.len() != 26 {
return Err(FileHeaderSectionError::IncorrectLength {
length: bytes.len(),
}
.into());
}
let signature = cursor.read_4()?;
if signature != EXPECTED_PSD_SIGNATURE {
return Err(FileHeaderSectionError::InvalidSignature {}.into());
}
let version = cursor.read_2()?;
if version != EXPECTED_VERSION {
return Err(FileHeaderSectionError::InvalidVersion {}.into());
}
let reserved = cursor.read_6()?;
if reserved != EXPECTED_RESERVED {
return Err(FileHeaderSectionError::InvalidReserved {}.into());
}
let channel = cursor.read_u16()?;
let channel_count = ChannelCount::new(channel as u8)?;
let height = cursor.read_u32()?;
let height = PsdHeight::new(height)?;
let width = cursor.read_u32()?;
let width = PsdWidth::new(width)?;
let depth = cursor.read_2()?;
let depth = PsdDepth::new(depth[1])?;
let color_mode = cursor.read_2()?;
let color_mode = ColorMode::new(color_mode[1])?;
let file_header_section = FileHeaderSection {
version: PsdVersion::One,
channel_count,
width,
height,
depth,
color_mode,
};
Ok(file_header_section)
}
}
#[derive(Debug)]
pub enum PsdVersion {
One,
}
#[derive(Debug)]
pub struct ChannelCount(u8);
#[derive(Debug, Fail)]
pub enum ChannelCountError {
#[fail(
display = "Invalid channel count: {}. Must be 1 <= channel count <= 56",
channel_count
)]
OutOfRange { channel_count: u8 },
}
impl ChannelCount {
pub fn new(channel_count: u8) -> Result<ChannelCount, Error> {
if channel_count < 1 || channel_count > 56 {
return Err(ChannelCountError::OutOfRange { channel_count }.into());
}
Ok(ChannelCount(channel_count))
}
pub fn count(&self) -> u8 {
self.0
}
}
#[derive(Debug, Fail)]
pub enum PsdSizeError {
#[fail(display = "Invalid width: {}. Must be 1 <= width <= 30,000", width)]
WidthOutOfRange { width: u32 },
#[fail(display = "Invalid height: {}. Must be 1 <= height <= 30,000", height)]
HeightOutOfRange { height: u32 },
}
#[derive(Debug)]
pub struct PsdHeight(pub(in crate) u32);
impl PsdHeight {
pub fn new(height: u32) -> Result<PsdHeight, Error> {
if height < 1 || height > 30000 {
return Err(PsdSizeError::HeightOutOfRange { height }.into());
}
Ok(PsdHeight(height))
}
}
#[derive(Debug, Clone, Copy)]
pub struct PsdWidth(pub(in crate) u32);
impl PsdWidth {
pub fn new(width: u32) -> Result<PsdWidth, Error> {
if width < 1 || width > 30000 {
return Err(PsdSizeError::WidthOutOfRange { width }.into());
}
Ok(PsdWidth(width))
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum PsdDepth {
One = 1,
Eight = 8,
Sixteen = 16,
ThirtyTwo = 32,
}
#[derive(Debug, Fail)]
pub enum PsdDepthError {
#[fail(display = "Depth {} is invalid. Must be 1, 8, 16 or 32", depth)]
InvalidDepth { depth: u8 },
#[fail(display = r#"Only 8 and 16 bit depths are supported at the moment.
If you'd like to see 1 and 32 bit depths supported - please open an issue."#)]
UnsupportedDepth,
}
impl PsdDepth {
pub fn new(depth: u8) -> Result<PsdDepth, Error> {
match depth {
1 => Ok(PsdDepth::One),
8 => Ok(PsdDepth::Eight),
16 => Ok(PsdDepth::Sixteen),
32 => Ok(PsdDepth::ThirtyTwo),
_ => Err(PsdDepthError::InvalidDepth { depth }.into()),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum ColorMode {
Bitmap = 0,
Grayscale = 1,
Indexed = 2,
Rgb = 3,
Cmyk = 4,
Multichannel = 7,
Duotone = 8,
Lab = 9,
}
#[derive(Debug, Fail)]
pub enum ColorModeError {
#[fail(
display = "Invalid color mode {}. Must be 0, 1, 2, 3, 4, 7, 8 or 9",
color_mode
)]
InvalidColorMode { color_mode: u8 },
}
impl ColorMode {
pub fn new(color_mode: u8) -> Result<ColorMode, Error> {
match color_mode {
0 => Ok(ColorMode::Bitmap),
1 => Ok(ColorMode::Grayscale),
2 => Ok(ColorMode::Indexed),
3 => Ok(ColorMode::Rgb),
4 => Ok(ColorMode::Cmyk),
7 => Ok(ColorMode::Multichannel),
8 => Ok(ColorMode::Duotone),
9 => Ok(ColorMode::Lab),
_ => Err(ColorModeError::InvalidColorMode { color_mode }.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_channel_count() {
for channel_count in 1..=56 {
assert!(ChannelCount::new(channel_count).is_ok());
}
}
#[test]
fn invalid_channel_count() {
assert!(ChannelCount::new(0).is_err());
assert!(ChannelCount::new(57).is_err());
}
#[test]
fn incorrect_file_header_section_length() {
let too_short = [0; 25];
match error_from_bytes(&too_short) {
FileHeaderSectionError::IncorrectLength { length } => assert_eq!(length, 25),
_ => panic!("Should have returned incorrect length error"),
};
}
#[test]
fn first_four_bytes_incorrect() {
let bytes = make_bytes();
let error = error_from_bytes(&bytes);
match error {
FileHeaderSectionError::InvalidSignature {} => {}
_ => panic!("Should have returned invalid signature error"),
};
}
#[test]
fn version_incorrect() {
let mut bytes = make_bytes();
bytes[0..4].copy_from_slice(&EXPECTED_PSD_SIGNATURE);
match error_from_bytes(&bytes) {
FileHeaderSectionError::InvalidVersion {} => {}
_ => panic!("Should have returned invalid version error"),
};
}
#[test]
fn invalid_reserved_section() {
let mut bytes = make_bytes();
bytes[0..4].copy_from_slice(&EXPECTED_PSD_SIGNATURE);
bytes[4..6].copy_from_slice(&EXPECTED_VERSION);
match error_from_bytes(&bytes) {
FileHeaderSectionError::InvalidReserved {} => {}
_ => panic!("Should have returned reserved section error"),
};
}
fn error_from_bytes(bytes: &[u8]) -> FileHeaderSectionError {
let error = FileHeaderSection::from_bytes(&bytes);
downcast_file_section_header_error(error)
}
fn downcast_file_section_header_error(
error: Result<FileHeaderSection, Error>,
) -> FileHeaderSectionError {
error
.err()
.unwrap()
.downcast::<FileHeaderSectionError>()
.unwrap()
}
fn make_bytes() -> [u8; 26] {
let mut bytes = [0; 26];
for i in 0..26 {
bytes[i] = i as u8;
}
bytes
}
}