-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
220 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ members = [ | |
"gufo-exif", | ||
"gufo-jpeg", | ||
"gufo-png", | ||
"gufo-webp", | ||
"gufo-xmp", | ||
"tests", | ||
] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
[package] | ||
name = "gufo-webp" | ||
version.workspace = true | ||
license.workspace = true | ||
edition.workspace = true | ||
rust-version.workspace = true | ||
repository.workspace = true | ||
|
||
[dependencies] | ||
gufo-common.workspace = true | ||
|
||
[lints] | ||
workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
fn main() { | ||
let path = std::env::args() | ||
.nth(1) | ||
.expect("First agument must be a path."); | ||
let data = std::fs::read(path).unwrap(); | ||
let webp = gufo_webp::WebP::new(data).unwrap(); | ||
|
||
for chunk in webp.chunks() { | ||
match chunk.four_cc() { | ||
gufo_webp::FourCC::Unknown(unknown) => println!( | ||
"Unknown({})", | ||
String::from_utf8_lossy(&u32::to_le_bytes(unknown)) | ||
), | ||
chunk_type => println!("{chunk_type:?}"), | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
use std::io::{Cursor, Read, Seek}; | ||
use std::ops::Range; | ||
|
||
pub const RIFF_MAGIC_BYTES: &[u8] = b"RIFF"; | ||
pub const WEBP_MAGIC_BYTES: &[u8] = b"WEBP"; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct WebP { | ||
data: Vec<u8>, | ||
chunks: Vec<RawChunk>, | ||
} | ||
|
||
/// Representation of a WEBP image | ||
impl WebP { | ||
/// Returns WEBP image representation | ||
/// | ||
/// * `data`: WEBP image data starting with RIFF magic byte | ||
pub fn new(data: Vec<u8>) -> Result<Self, Error> { | ||
let chunks = Self::find_chunks(&data)?; | ||
|
||
Ok(Self { chunks, data }) | ||
} | ||
|
||
/// Returns all chunks | ||
pub fn chunks(&self) -> Vec<Chunk> { | ||
self.chunks.iter().map(|x| x.chunk(self)).collect() | ||
} | ||
|
||
/// List all chunks in the data | ||
fn find_chunks(data: &[u8]) -> Result<Vec<RawChunk>, Error> { | ||
let mut cur = Cursor::new(data); | ||
|
||
// Riff magic bytes | ||
let riff_magic_bytes = &mut [0; WEBP_MAGIC_BYTES.len()]; | ||
cur.read_exact(riff_magic_bytes) | ||
.map_err(|_| Error::UnexpectedEof)?; | ||
if riff_magic_bytes != RIFF_MAGIC_BYTES { | ||
return Err(Error::RiffMagicBytesMissing(*riff_magic_bytes)); | ||
} | ||
|
||
// File length | ||
let file_length_data = &mut [0; 4]; | ||
cur.read_exact(file_length_data) | ||
.map_err(|_| Error::UnexpectedEof)?; | ||
let file_length = u32::from_le_bytes(*file_length_data); | ||
|
||
// Exif magic bytes | ||
let webp_magic_bytes = &mut [0; WEBP_MAGIC_BYTES.len()]; | ||
cur.read_exact(webp_magic_bytes) | ||
.map_err(|_| Error::UnexpectedEof)?; | ||
if webp_magic_bytes != WEBP_MAGIC_BYTES { | ||
return Err(Error::WebpMagicBytesMissing(*webp_magic_bytes)); | ||
} | ||
|
||
let mut chunks = Vec::new(); | ||
loop { | ||
// Next 4 bytes are chunk FourCC (chunk type) | ||
let four_cc_data = &mut [0; 4]; | ||
cur.read_exact(four_cc_data) | ||
.map_err(|_| Error::UnexpectedEof)?; | ||
let four_cc = FourCC::from(u32::from_le_bytes(*four_cc_data)); | ||
|
||
// First 4 bytes are chunk size | ||
let size_data = &mut [0; 4]; | ||
cur.read_exact(size_data) | ||
.map_err(|_| Error::UnexpectedEof)?; | ||
let size = u32::from_le_bytes(*size_data); | ||
|
||
// Next is the payload | ||
let payload_start: usize = cur | ||
.position() | ||
.try_into() | ||
.map_err(|_| Error::PositionTooLarge)?; | ||
let payload_end = payload_start | ||
.checked_add(size as usize) | ||
.ok_or(Error::PositionTooLarge)?; | ||
let payload = payload_start..payload_end; | ||
|
||
let chunk = RawChunk { four_cc, payload }; | ||
|
||
// Jump to end of payload | ||
cur.set_position(payload_end as u64); | ||
|
||
// If odd, jump over 1 byte padding | ||
if size % 2 != 0 { | ||
cur.seek(std::io::SeekFrom::Current(1)) | ||
.map_err(|_| Error::UnexpectedEof)?; | ||
} | ||
|
||
chunks.push(chunk); | ||
|
||
if cur.position() >= file_length.into() { | ||
break; | ||
} | ||
} | ||
|
||
Ok(chunks) | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct RawChunk { | ||
four_cc: FourCC, | ||
payload: Range<usize>, | ||
} | ||
|
||
impl RawChunk { | ||
fn chunk<'a>(&self, webp: &'a WebP) -> Chunk<'a> { | ||
Chunk { | ||
four_cc: self.four_cc, | ||
payload: self.payload.clone(), | ||
webp, | ||
} | ||
} | ||
|
||
pub fn total_len(&self) -> usize { | ||
self.payload.len().checked_add(8).unwrap() | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct Chunk<'a> { | ||
four_cc: FourCC, | ||
payload: Range<usize>, | ||
webp: &'a WebP, | ||
} | ||
|
||
impl<'a> Chunk<'a> { | ||
pub fn four_cc(&self) -> FourCC { | ||
self.four_cc | ||
} | ||
|
||
pub fn payload(&self) -> &[u8] { | ||
self.webp.data.get(self.payload.clone()).unwrap() | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub enum Error { | ||
RiffMagicBytesMissing([u8; 4]), | ||
WebpMagicBytesMissing([u8; 4]), | ||
UnexpectedEof, | ||
PositionTooLarge, | ||
} | ||
|
||
gufo_common::utils::convertible_enum!( | ||
#[repr(u32)] | ||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] | ||
#[non_exhaustive] | ||
#[allow(non_camel_case_types)] | ||
/// Type of a chunk | ||
/// | ||
/// The value is stored as little endian [`u32`] of the original byte | ||
/// string. | ||
pub enum FourCC { | ||
/// Information about features used in the file | ||
VP8X = b(b"VP8X"), | ||
/// Embedded ICC color profile | ||
ICCP = b(b"ICCP"), | ||
/// Global parameters of the animation. | ||
ANIM = b(b"ANIM"), | ||
|
||
/// Information about a single frame | ||
ANMF = b(b"ANMF"), | ||
/// Alpha data for this frame (only with [`VP8`](Self::VP8)) | ||
ALPH = b(b"ALPH"), | ||
/// Lossy data for this frame | ||
VP8 = b(b"VP8 "), | ||
/// Lossless data for this frame | ||
VP8L = b(b"VP8L"), | ||
|
||
EXIF = b(b"EXIF"), | ||
XMP = b(b"XMP "), | ||
} | ||
); | ||
|
||
impl FourCC { | ||
/// Returns the byte string of the chunk | ||
pub fn bytes(self) -> [u8; 4] { | ||
u32::to_le_bytes(self.into()) | ||
} | ||
} | ||
|
||
/// Convert bytes to u32 | ||
const fn b(d: &[u8; 4]) -> u32 { | ||
u32::from_le_bytes(*d) | ||
} |