avif-serialize-0.8.6/.cargo_vcs_info.json0000644000000001360000000000100137610ustar { "git": { "sha1": "f4f4f96e28d0bd8da9e085ec90220708cc49bbbd" }, "path_in_vcs": "" }avif-serialize-0.8.6/Cargo.lock0000644000000123420000000000100117360ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "ahash" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", "version_check", "zerocopy", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "avif-parse" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f85ce2a7cd14ac0a30dc29a115de22466aeb8a029410f9f1e4f283443c959d1" dependencies = [ "arrayvec", "bitreader", "byteorder", "fallible_collections 0.5.1", "leb128", "log", ] [[package]] name = "avif-serialize" version = "0.8.6" dependencies = [ "arrayvec", "avif-parse", "mp4parse", ] [[package]] name = "bitreader" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "886559b1e163d56c765bc3a985febb4eee8009f625244511d8ee3c432e08c066" dependencies = [ "cfg-if", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "fallible_collections" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a88c69768c0a15262df21899142bc6df9b9b823546d4b4b9a7bc2d6c448ec6fd" dependencies = [ "hashbrown", ] [[package]] name = "fallible_collections" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b3e85d14d419ba3e1db925519461c0d17a49bdd2d67ea6316fa965ca7acdf74" [[package]] name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ "ahash", ] [[package]] name = "leb128" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "mp4parse" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63a35203d3c6ce92d5251c77520acb2e57108c88728695aa883f70023624c570" dependencies = [ "bitreader", "byteorder", "fallible_collections 0.4.9", "log", "num-traits", "static_assertions", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "proc-macro2" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "2.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "zerocopy" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", "syn", ] avif-serialize-0.8.6/Cargo.toml0000644000000027570000000000100117720ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.80" name = "avif-serialize" version = "0.8.6" authors = ["Kornel Lesiński "] build = false include = [ "src/*.rs", "Cargo.toml", "README.md", "LICENSE", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Minimal writer for AVIF header structure (MPEG/HEIF/MIAF/ISO-BMFF)" homepage = "https://lib.rs/avif-serialize" readme = "README.md" keywords = [ "avif", "heif", "bmff", "av1", "mux", ] categories = [ "multimedia::images", "encoding", ] license = "BSD-3-Clause" repository = "https://github.com/kornelski/avif-serialize" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] rustdoc-args = ["--generate-link-to-definition"] [badges.maintenance] status = "passively-maintained" [lib] name = "avif_serialize" path = "src/lib.rs" [dependencies.arrayvec] version = "0.7.6" [dev-dependencies.avif-parse] version = "1.4.0" [dev-dependencies.mp4parse] version = "0.17" avif-serialize-0.8.6/Cargo.toml.orig000064400000000000000000000014441046102023000154430ustar 00000000000000[package] name = "avif-serialize" version = "0.8.6" authors = ["Kornel Lesiński "] edition = "2021" license = "BSD-3-Clause" description = "Minimal writer for AVIF header structure (MPEG/HEIF/MIAF/ISO-BMFF)" readme = "README.md" categories = ["multimedia::images", "encoding"] keywords = ["avif", "heif", "bmff", "av1", "mux"] repository = "https://github.com/kornelski/avif-serialize" homepage = "https://lib.rs/avif-serialize" include = ["src/*.rs", "Cargo.toml", "README.md", "LICENSE"] rust-version = "1.80" [dependencies] arrayvec = "0.7.6" [dev-dependencies] mp4parse = "0.17" avif-parse = "1.4.0" [badges] maintenance = { status = "passively-maintained" } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] rustdoc-args = ["--generate-link-to-definition"] avif-serialize-0.8.6/LICENSE000064400000000000000000000027641046102023000135670ustar 00000000000000BSD 3-Clause License Copyright (c) 2020, Cloudflare, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. avif-serialize-0.8.6/README.md000064400000000000000000000015461046102023000140360ustar 00000000000000# AVIF image serializer (muxer) Minimal writer for AVIF header structure. This is lean, safe-Rust alternative to [libavif](https://lib.rs/libavif). It creates the jungle of MPEG/HEIF/MIAF/ISO-BMFF "boxes" as appropriate for AVIF files. Supports alpha channel embedding. Compatible with decoders in Chrome 85+, libavif v0.8.1, and Firefox 92. It's used in [cavif](https://lib.rs/cavif) and other encoders. Together with [rav1e](https://lib.rs/rav1e), it allows pure-Rust AVIF image encoding. ## Requirements * [Latest stable](https://rustup.rs) version of Rust. ## Usage 1. Compress pixels using an AV1 encoder, such as [rav1e](https://lib.rs/rav1e). [libaom](https://lib.rs/libaom-sys) works too. 2. Call `avif_serialize::serialize_to_vec(av1_data, None, width, height, 8)` See [`ravif` crate sources](https://github.com/kornelski/cavif-rs) for example usage. avif-serialize-0.8.6/src/boxes.rs000064400000000000000000000420231046102023000150270ustar 00000000000000use crate::constants::{ColorPrimaries, MatrixCoefficients, TransferCharacteristics}; use crate::writer::{Writer, WriterBackend, IO}; use arrayvec::ArrayVec; use std::io::Write; use std::num::NonZeroU32; use std::{fmt, io}; pub trait MpegBox { fn len(&self) -> usize; fn write(&self, w: &mut Writer) -> Result<(), B::Error>; } #[derive(Copy, Clone)] pub struct FourCC(pub [u8; 4]); impl fmt::Debug for FourCC { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match std::str::from_utf8(&self.0) { Ok(s) => s.fmt(f), Err(_) => self.0.fmt(f), } } } #[derive(Debug, Clone)] pub struct AvifFile<'data> { pub ftyp: FtypBox, pub meta: MetaBox<'data>, pub mdat: MdatBox, } impl AvifFile<'_> { /// Where the primary data starts inside the `mdat` box, for `iloc`'s offset fn mdat_payload_start_offset(&self) -> u32 { (self.ftyp.len() + self.meta.len() + BASIC_BOX_SIZE) as u32 // mdat head } /// `iloc` is mostly unnecssary, high risk of out-of-buffer accesses in parsers that don't pay attention, /// and also awkward to serialize, because its content depends on its own serialized byte size. fn fix_iloc_positions(&mut self) { let start_offset = self.mdat_payload_start_offset(); self.meta.iloc.absolute_offset_start = NonZeroU32::new(start_offset); } fn write_header(&mut self, out: &mut Vec) -> io::Result<()> { if self.meta.iprp.ipco.ispe().map_or(true, |b| b.width == 0 || b.height == 0) { return Err(io::Error::new(io::ErrorKind::InvalidInput, "missing width/height")); } self.fix_iloc_positions(); out.try_reserve_exact(self.ftyp.len() + self.meta.len())?; let mut w = Writer::new(out); self.ftyp.write(&mut w).map_err(|_| io::ErrorKind::OutOfMemory)?; self.meta.write(&mut w).map_err(|_| io::ErrorKind::OutOfMemory)?; Ok(()) } pub fn file_size(&self) -> usize { self.ftyp.len() + self.meta.len() + self.mdat.len(&self.meta.iloc) } pub fn write_to_vec(&mut self, out: &mut Vec) -> io::Result<()> { let expected_file_size = self.file_size(); out.try_reserve_exact(expected_file_size)?; let initial = out.len(); self.write_header(out)?; let _ = self.mdat.write(&mut Writer::new(out), &self.meta.iloc); let written = out.len() - initial; debug_assert_eq!(expected_file_size, written); Ok(()) } pub fn write(&mut self, mut out: W) -> io::Result<()> { let mut tmp = Vec::new(); self.write_header(&mut tmp)?; out.write_all(&tmp)?; drop(tmp); self.mdat.write(&mut Writer::new(&mut IO(out)), &self.meta.iloc) } } const BASIC_BOX_SIZE: usize = 8; const FULL_BOX_SIZE: usize = BASIC_BOX_SIZE + 4; #[derive(Debug, Clone)] pub struct FtypBox { pub major_brand: FourCC, pub minor_version: u32, pub compatible_brands: ArrayVec, } /// File Type box (chunk) impl MpegBox for FtypBox { #[inline(always)] fn len(&self) -> usize { BASIC_BOX_SIZE + 4 // brand + 4 // ver + 4 * self.compatible_brands.len() } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.basic_box(self.len(), *b"ftyp")?; b.push(&self.major_brand.0)?; b.u32(self.minor_version)?; for cb in &self.compatible_brands { b.push(&cb.0)?; } Ok(()) } } /// Metadata box #[derive(Debug, Clone)] pub struct MetaBox<'data> { pub hdlr: HdlrBox, pub iloc: IlocBox<'data>, pub iinf: IinfBox, pub pitm: PitmBox, pub iprp: IprpBox, pub iref: IrefBox, } impl MpegBox for MetaBox<'_> { #[inline] fn len(&self) -> usize { FULL_BOX_SIZE + self.hdlr.len() + self.pitm.len() + self.iloc.len() + self.iinf.len() + self.iprp.len() + if !self.iref.is_empty() { self.iref.len() } else { 0 } } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"meta", 0)?; self.hdlr.write(&mut b)?; self.pitm.write(&mut b)?; self.iloc.write(&mut b)?; self.iinf.write(&mut b)?; if !self.iref.is_empty() { self.iref.write(&mut b)?; } self.iprp.write(&mut b) } } /// Item Info box #[derive(Debug, Clone)] pub struct IinfBox { pub items: ArrayVec, } impl MpegBox for IinfBox { #[inline] fn len(&self) -> usize { FULL_BOX_SIZE + 2 // num items u16 + self.items.iter().map(|item| item.len()).sum::() } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"iinf", 0)?; b.u16(self.items.len() as _)?; for infe in &self.items { infe.write(&mut b)?; } Ok(()) } } /// Item Info Entry box #[derive(Debug, Copy, Clone)] pub struct InfeBox { pub id: u16, pub typ: FourCC, pub name: &'static str, } impl MpegBox for InfeBox { #[inline(always)] fn len(&self) -> usize { FULL_BOX_SIZE + 2 // id + 2 // item_protection_index + 4 // type + self.name.len() + 1 // nul-terminated } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"infe", 2)?; b.u16(self.id)?; b.u16(0)?; b.push(&self.typ.0)?; b.push(self.name.as_bytes())?; b.u8(0) } } #[derive(Debug, Clone)] pub struct HdlrBox { } impl MpegBox for HdlrBox { #[inline(always)] fn len(&self) -> usize { FULL_BOX_SIZE + 4 + 4 + 13 } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { // because an image format needs to be told it's an image format, // and it does it the way classic MacOS used to, because Quicktime. let mut b = w.full_box(self.len(), *b"hdlr", 0)?; b.u32(0)?; // old MacOS file type handler b.push(b"pict")?; // MacOS Quicktime subtype b.u32(0)?; // Firefox 92 wants all 0 here b.u32(0)?; // Reserved b.u32(0)?; // Reserved b.u8(0)?; // Pascal string for component name Ok(()) } } /// Item properties + associations #[derive(Debug, Clone)] pub struct IprpBox { pub ipco: IpcoBox, pub ipma: IpmaBox, } impl MpegBox for IprpBox { #[inline(always)] fn len(&self) -> usize { BASIC_BOX_SIZE + self.ipco.len() + self.ipma.len() } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.basic_box(self.len(), *b"iprp")?; self.ipco.write(&mut b)?; self.ipma.write(&mut b) } } #[derive(Debug, Clone)] #[non_exhaustive] pub enum IpcoProp { Av1C(Av1CBox), Pixi(PixiBox), Ispe(IspeBox), AuxC(AuxCBox), Colr(ColrBox), } impl IpcoProp { pub fn len(&self) -> usize { match self { Self::Av1C(p) => p.len(), Self::Pixi(p) => p.len(), Self::Ispe(p) => p.len(), Self::AuxC(p) => p.len(), Self::Colr(p) => p.len(), } } pub fn write(&self, w: &mut Writer) -> Result<(), B::Error> { match self { Self::Av1C(p) => p.write(w), Self::Pixi(p) => p.write(w), Self::Ispe(p) => p.write(w), Self::AuxC(p) => p.write(w), Self::Colr(p) => p.write(w), } } } /// Item Property Container box #[derive(Debug, Clone)] pub struct IpcoBox { props: ArrayVec, } impl IpcoBox { pub fn new() -> Self { Self { props: ArrayVec::new() } } #[must_use] pub fn push(&mut self, prop: IpcoProp) -> Option { self.props.try_push(prop).ok()?; Some(self.props.len() as u8) // the spec wants them off by one } pub(crate) fn ispe(&self) -> Option<&IspeBox> { self.props.iter().find_map(|b| match b { IpcoProp::Ispe(i) => Some(i), _ => None, }) } } impl MpegBox for IpcoBox { #[inline] fn len(&self) -> usize { BASIC_BOX_SIZE + self.props.iter().map(|a| a.len()).sum::() } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.basic_box(self.len(), *b"ipco")?; for p in &self.props { p.write(&mut b)?; } Ok(()) } } #[derive(Debug, Copy, Clone)] pub struct AuxCBox { pub urn: &'static str, } impl AuxCBox { pub fn len(&self) -> usize { FULL_BOX_SIZE + self.urn.len() + 1 } pub fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"auxC", 0)?; b.push(self.urn.as_bytes())?; b.u8(0) } } /// Pixies, I guess. #[derive(Debug, Copy, Clone)] pub struct PixiBox { pub depth: u8, pub channels: u8, } impl PixiBox { pub fn len(self) -> usize { FULL_BOX_SIZE + 1 + self.channels as usize } pub fn write(self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"pixi", 0)?; b.u8(self.channels)?; for _ in 0..self.channels { b.u8(self.depth)?; } Ok(()) } } /// This is HEVC-specific and not for AVIF, but Chrome wants it :( #[derive(Debug, Copy, Clone)] pub struct IspeBox { pub width: u32, pub height: u32, } impl MpegBox for IspeBox { #[inline(always)] fn len(&self) -> usize { FULL_BOX_SIZE + 4 + 4 } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"ispe", 0)?; b.u32(self.width)?; b.u32(self.height) } } /// Property→image associations #[derive(Debug, Clone)] pub struct IpmaEntry { pub item_id: u16, pub prop_ids: ArrayVec, } #[derive(Debug, Clone)] pub struct IpmaBox { pub entries: ArrayVec, } impl MpegBox for IpmaBox { #[inline] fn len(&self) -> usize { FULL_BOX_SIZE + 4 + self.entries.iter().map(|e| 2 + 1 + e.prop_ids.len()).sum::() } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"ipma", 0)?; b.u32(self.entries.len() as _)?; // entry count for e in &self.entries { b.u16(e.item_id)?; b.u8(e.prop_ids.len() as u8)?; // assoc count for &p in &e.prop_ids { b.u8(p)?; } } Ok(()) } } /// Item Reference box #[derive(Debug, Copy, Clone)] pub struct IrefEntryBox { pub from_id: u16, pub to_id: u16, pub typ: FourCC, } impl MpegBox for IrefEntryBox { #[inline(always)] fn len(&self) -> usize { BASIC_BOX_SIZE + 2 // from + 2 // refcount + 2 // to } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.basic_box(self.len(), self.typ.0)?; b.u16(self.from_id)?; b.u16(1)?; b.u16(self.to_id) } } #[derive(Debug, Clone)] pub struct IrefBox { pub entries: ArrayVec, } impl IrefBox { pub fn is_empty(&self) -> bool { self.entries.is_empty() } } impl MpegBox for IrefBox { #[inline(always)] fn len(&self) -> usize { FULL_BOX_SIZE + self.entries.iter().map(|e| e.len()).sum::() } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"iref", 0)?; for entry in &self.entries { entry.write(&mut b)?; } Ok(()) } } /// Auxiliary item (alpha or depth map) #[derive(Debug, Copy, Clone)] #[allow(unused)] pub struct AuxlBox {} impl MpegBox for AuxlBox { #[inline(always)] fn len(&self) -> usize { FULL_BOX_SIZE } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { w.full_box(self.len(), *b"auxl", 0)?; Ok(()) } } /// ColourInformationBox #[derive(Debug, Copy, Clone, PartialEq)] pub struct ColrBox { pub color_primaries: ColorPrimaries, pub transfer_characteristics: TransferCharacteristics, pub matrix_coefficients: MatrixCoefficients, pub full_range_flag: bool, // u1 + u7 } impl Default for ColrBox { fn default() -> Self { Self { color_primaries: ColorPrimaries::Bt709, transfer_characteristics: TransferCharacteristics::Srgb, matrix_coefficients: MatrixCoefficients::Bt601, full_range_flag: true, } } } impl MpegBox for ColrBox { #[inline(always)] fn len(&self) -> usize { BASIC_BOX_SIZE + 4 + 2 + 2 + 2 + 1 } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.basic_box(self.len(), *b"colr")?; b.u32(u32::from_be_bytes(*b"nclx"))?; b.u16(self.color_primaries as u16)?; b.u16(self.transfer_characteristics as u16)?; b.u16(self.matrix_coefficients as u16)?; b.u8(if self.full_range_flag { 1 << 7 } else { 0 }) } } #[derive(Debug, Copy, Clone)] pub struct Av1CBox { pub seq_profile: u8, pub seq_level_idx_0: u8, pub seq_tier_0: bool, pub high_bitdepth: bool, pub twelve_bit: bool, pub monochrome: bool, pub chroma_subsampling_x: bool, pub chroma_subsampling_y: bool, pub chroma_sample_position: u8, } impl MpegBox for Av1CBox { #[inline(always)] fn len(&self) -> usize { BASIC_BOX_SIZE + 4 } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.basic_box(self.len(), *b"av1C")?; let flags1 = u8::from(self.seq_tier_0) << 7 | u8::from(self.high_bitdepth) << 6 | u8::from(self.twelve_bit) << 5 | u8::from(self.monochrome) << 4 | u8::from(self.chroma_subsampling_x) << 3 | u8::from(self.chroma_subsampling_y) << 2 | self.chroma_sample_position; b.push(&[ 0x81, // marker and version (self.seq_profile << 5) | self.seq_level_idx_0, // x2d == 45 flags1, 0, ]) } } #[derive(Debug, Copy, Clone)] pub struct PitmBox(pub u16); impl MpegBox for PitmBox { #[inline(always)] fn len(&self) -> usize { FULL_BOX_SIZE + 2 } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"pitm", 0)?; b.u16(self.0) } } #[derive(Debug, Clone)] pub struct IlocBox<'data> { /// update before writing pub absolute_offset_start: Option, pub items: ArrayVec, 3>, } #[derive(Debug, Clone)] pub struct IlocItem<'data> { pub id: u16, pub extents: [IlocExtent<'data>; 1], } #[derive(Debug, Copy, Clone)] pub struct IlocExtent<'data> { /// offset and len will be calculated when writing pub data: &'data [u8], } impl MpegBox for IlocBox<'_> { #[inline(always)] #[allow(unused_parens)] fn len(&self) -> usize { FULL_BOX_SIZE + 1 // offset_size, length_size + 1 // base_offset_size, reserved + 2 // num items + self.items.iter().map(|i| ( // for each item 2 // id + 2 // dat ref idx + 0 // base_offset_size + 2 // extent count + i.extents.len() * ( // for each extent 4 // extent_offset + 4 // extent_len ) )).sum::() } fn write(&self, w: &mut Writer) -> Result<(), B::Error> { let mut b = w.full_box(self.len(), *b"iloc", 0)?; b.push(&[4 << 4 | 4, 0])?; // offset and length are 4 bytes b.u16(self.items.len() as _)?; // num items let mut next_start = if let Some(ok) = self.absolute_offset_start { ok.get() } else { debug_assert!(false); !0 }; for item in &self.items { b.u16(item.id)?; b.u16(0)?; b.u16(item.extents.len() as _)?; // num extents for ex in &item.extents { let len = ex.data.len() as u32; b.u32(next_start)?; next_start += len; b.u32(len)?; } } Ok(()) } } #[derive(Debug, Clone)] pub struct MdatBox; impl MdatBox { #[inline(always)] fn len(&self, chunks: &IlocBox) -> usize { BASIC_BOX_SIZE + chunks.items.iter().flat_map(|c| &c.extents).map(|d| d.data.len()).sum::() } fn write(&self, w: &mut Writer, chunks: &IlocBox) -> Result<(), B::Error> { let mut b = w.basic_box(self.len(chunks), *b"mdat")?; for ch in chunks.items.iter().flat_map(|c| &c.extents) { b.push(ch.data)?; } Ok(()) } } avif-serialize-0.8.6/src/constants.rs000064400000000000000000000057701046102023000157330ustar 00000000000000/// `Bt709` works for sRGB images. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum ColorPrimaries { /// Rec.709 and sRGB Bt709 = 1, Unspecified = 2, /// ITU-R BT601-6 525 Bt601 = 6, /// ITU-R BT2020 Bt2020 = 9, /// SMPTE ST 431-2. NB: "P3" images use DisplayP3 instead. DciP3 = 11, /// SMPTE ST 432-1 DisplayP3 = 12, } /// This controls how color data is interpreted (gamma). /// /// If you don't know what to do with these, pick `Srgb`. /// /// Reasonable options include `Bt709` (HDTV), `Bt2020_10` (Wide Gamut), `Smpte2084`, `Hlg` (HDR). #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum TransferCharacteristics { /// Rec.709. May be appropriate for conversions from video. Bt709 = 1, /// Don't use this for color channels. Unspecified = 2, /// Don't use this. Analog NTSC TV. BT.470 System M (historical) #[deprecated(note = "This is obsolete. Please don't proliferate legacy baggage.")] #[doc(hidden)] Bt470M = 4, /// Don't use this. Analog PAL TV. BT.470 System B, G (historical) #[deprecated(note = "This is obsolete. Please don't proliferate legacy baggage.")] #[doc(hidden)] Bt470BG = 5, /// ITU-R BT601-6 525. Not recommended, unless you're converting from unlabelled low-res video clips. /// See `Bt709` and `Srgb`. Bt601 = 6, /// Don't use this. SMPTE 240 M. It's just a worse Rec.709. Smpte240 = 7, /// "Linear transfer characteristics" Linear = 8, /// "Logarithmic transfer characteristic (100:1 range)" Log = 9, /// "Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range)" LogSqrt = 10, /// IEC 61966-2-4 Iec61966 = 11, /// Don't use this. Obsoleted BT.1361 extended color gamut system (historical) #[deprecated(note = "This is obsolete. Please don't proliferate legacy baggage.")] #[doc(hidden)] Bt1361 = 12, /// sRGB. This is the safe choice for encoding "standard" RGB images, especially 8-bit inputs. Srgb = 13, /// ITU-R BT2020 for 10-bit system. Reasonable for encoding wide gamut. Bt2020_10 = 14, /// ITU-R BT2020 for 12-bit system Bt2020_12 = 15, /// SMPTE ST 2084, ITU BT.2100 PQ Smpte2084 = 16, /// SMPTE ST 428. Not recommended. Overkill for images. Use `Bt2020_10` instead. Smpte428 = 17, /// BT.2100 HLG (Hybrid Log Gamma), ARIB STD-B67 Hlg = 18, } /// This is the format of color channels. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum MatrixCoefficients { /// GBR (sRGB). This isn't actually good for most RGB images. Use `Bt709` for lossy and `Ycgco` for lossless. Rgb = 0, /// ITU-R BT1361 Bt709 = 1, Unspecified = 2, /// ITU-R BT601-6 525. This matches luma in JPEG's YCbCr when used with sRGB transfer characteristics, but is a bit off for chroma. Bt601 = 6, Ycgco = 8, /// ITU-R BT2020 non-constant luminance system Bt2020Ncl = 9, /// ITU-R BT2020 constant luminance system Bt2020Cl = 10, } avif-serialize-0.8.6/src/lib.rs000064400000000000000000000455461046102023000144720ustar 00000000000000//! # AVIF image serializer (muxer) //! //! ## Usage //! //! 1. Compress pixels using an AV1 encoder, such as [rav1e](https://lib.rs/rav1e). [libaom](https://lib.rs/libaom-sys) works too. //! //! 2. Call `avif_serialize::serialize_to_vec(av1_data, None, width, height, 8)` //! //! See [cavif](https://github.com/kornelski/cavif-rs) for a complete implementation. mod boxes; pub mod constants; mod writer; use crate::boxes::*; use arrayvec::ArrayVec; use std::io; /// Config for the serialization (allows setting advanced image properties). /// /// See [`Aviffy::new`]. pub struct Aviffy { premultiplied_alpha: bool, colr: ColrBox, min_seq_profile: u8, chroma_subsampling: (bool, bool), monochrome: bool, width: u32, height: u32, bit_depth: u8, exif: Option>, } /// Makes an AVIF file given encoded AV1 data (create the data with [`rav1e`](https://lib.rs/rav1e)) /// /// `color_av1_data` is already-encoded AV1 image data for the color channels (YUV, RGB, etc.). /// [You can parse this information out of AV1 payload with `avif-parse`](https://docs.rs/avif-parse/latest/avif_parse/struct.AV1Metadata.html). /// /// The color image should have been encoded without chroma subsampling AKA YUV444 (`Cs444` in `rav1e`) /// AV1 handles full-res color so effortlessly, you should never need chroma subsampling ever again. /// /// Optional `alpha_av1_data` is a monochrome image (`rav1e` calls it "YUV400"/`Cs400`) representing transparency. /// Alpha adds a lot of header bloat, so don't specify it unless it's necessary. /// /// `width`/`height` is image size in pixels. It must of course match the size of encoded image data. /// `depth_bits` should be 8, 10 or 12, depending on how the image was encoded. /// /// Color and alpha must have the same dimensions and depth. /// /// Data is written (streamed) to `into_output`. pub fn serialize(into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> io::Result<()> { Aviffy::new() .set_width(width) .set_height(height) .set_bit_depth(depth_bits) .write_slice(into_output, color_av1_data, alpha_av1_data) } impl Aviffy { /// You will have to set image properties to match the AV1 bitstream. /// /// [You can get this information out of the AV1 payload with `avif-parse`](https://docs.rs/avif-parse/latest/avif_parse/struct.AV1Metadata.html). #[inline] #[must_use] pub fn new() -> Self { Self { premultiplied_alpha: false, min_seq_profile: 1, chroma_subsampling: (false, false), monochrome: false, width: 0, height: 0, bit_depth: 0, colr: ColrBox::default(), exif: None, } } /// If set, must match the AV1 color payload, and will result in `colr` box added to AVIF. /// Defaults to BT.601, because that's what Safari assumes when `colr` is missing. /// Other browsers are smart enough to read this from the AV1 payload instead. #[inline] pub fn set_matrix_coefficients(&mut self, matrix_coefficients: constants::MatrixCoefficients) -> &mut Self { self.colr.matrix_coefficients = matrix_coefficients; self } #[doc(hidden)] pub fn matrix_coefficients(&mut self, matrix_coefficients: constants::MatrixCoefficients) -> &mut Self { self.set_matrix_coefficients(matrix_coefficients) } /// If set, must match the AV1 color payload, and will result in `colr` box added to AVIF. /// Defaults to sRGB. #[inline] pub fn set_transfer_characteristics(&mut self, transfer_characteristics: constants::TransferCharacteristics) -> &mut Self { self.colr.transfer_characteristics = transfer_characteristics; self } #[doc(hidden)] pub fn transfer_characteristics(&mut self, transfer_characteristics: constants::TransferCharacteristics) -> &mut Self { self.set_transfer_characteristics(transfer_characteristics) } /// If set, must match the AV1 color payload, and will result in `colr` box added to AVIF. /// Defaults to sRGB/Rec.709. #[inline] pub fn set_color_primaries(&mut self, color_primaries: constants::ColorPrimaries) -> &mut Self { self.colr.color_primaries = color_primaries; self } #[doc(hidden)] pub fn color_primaries(&mut self, color_primaries: constants::ColorPrimaries) -> &mut Self { self.set_color_primaries(color_primaries) } /// If set, must match the AV1 color payload, and will result in `colr` box added to AVIF. /// Defaults to full. #[inline] pub fn set_full_color_range(&mut self, full_range: bool) -> &mut Self { self.colr.full_range_flag = full_range; self } #[doc(hidden)] pub fn full_color_range(&mut self, full_range: bool) -> &mut Self { self.set_full_color_range(full_range) } /// Makes an AVIF file given encoded AV1 data (create the data with [`rav1e`](https://lib.rs/rav1e)) /// /// `color_av1_data` is already-encoded AV1 image data for the color channels (YUV, RGB, etc.). /// The color image should have been encoded without chroma subsampling AKA YUV444 (`Cs444` in `rav1e`) /// AV1 handles full-res color so effortlessly, you should never need chroma subsampling ever again. /// /// Optional `alpha_av1_data` is a monochrome image (`rav1e` calls it "YUV400"/`Cs400`) representing transparency. /// Alpha adds a lot of header bloat, so don't specify it unless it's necessary. /// /// `width`/`height` is image size in pixels. It must of course match the size of encoded image data. /// `depth_bits` should be 8, 10 or 12, depending on how the image has been encoded in AV1. /// /// Color and alpha must have the same dimensions and depth. /// /// Data is written (streamed) to `into_output`. #[inline] pub fn write(&self, into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> io::Result<()> { self.make_boxes(color_av1_data, alpha_av1_data, width, height, depth_bits)?.write(into_output) } /// See [`Self::write`] #[inline] pub fn write_slice(&self, into_output: W, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>) -> io::Result<()> { self.make_boxes(color_av1_data, alpha_av1_data, self.width, self.height, self.bit_depth)?.write(into_output) } fn make_boxes<'data>(&'data self, color_av1_data: &'data [u8], alpha_av1_data: Option<&'data [u8]>, width: u32, height: u32, depth_bits: u8) -> io::Result> { if ![8, 10, 12].contains(&depth_bits) { return Err(io::Error::new(io::ErrorKind::InvalidInput, "depth must be 8/10/12")); } let mut image_items = ArrayVec::new(); let mut iloc_items = ArrayVec::new(); let mut ipma_entries = ArrayVec::new(); let mut irefs = ArrayVec::new(); let mut ipco = IpcoBox::new(); let color_image_id = 1; let alpha_image_id = 2; let exif_id = 3; const ESSENTIAL_BIT: u8 = 0x80; let color_depth_bits = depth_bits; let alpha_depth_bits = depth_bits; // Sadly, the spec requires these to match. image_items.push(InfeBox { id: color_image_id, typ: FourCC(*b"av01"), name: "", }); let ispe_prop = ipco.push(IpcoProp::Ispe(IspeBox { width, height })).ok_or(io::ErrorKind::InvalidInput)?; // This is redundant, but Chrome wants it, and checks that it matches :( let av1c_color_prop = ipco.push(IpcoProp::Av1C(Av1CBox { seq_profile: self.min_seq_profile.max(if color_depth_bits >= 12 { 2 } else { 0 }), seq_level_idx_0: 31, seq_tier_0: false, high_bitdepth: color_depth_bits >= 10, twelve_bit: color_depth_bits >= 12, monochrome: self.monochrome, chroma_subsampling_x: self.chroma_subsampling.0, chroma_subsampling_y: self.chroma_subsampling.1, chroma_sample_position: 0, })).ok_or(io::ErrorKind::InvalidInput)?; // Useless bloat let pixi_3 = ipco.push(IpcoProp::Pixi(PixiBox { channels: 3, depth: color_depth_bits, })).ok_or(io::ErrorKind::InvalidInput)?; let mut ipma = IpmaEntry { item_id: color_image_id, prop_ids: from_array([ispe_prop, av1c_color_prop | ESSENTIAL_BIT, pixi_3]), }; // Redundant info, already in AV1 if self.colr != ColrBox::default() { let colr_color_prop = ipco.push(IpcoProp::Colr(self.colr)).ok_or(io::ErrorKind::InvalidInput)?; ipma.prop_ids.push(colr_color_prop); } ipma_entries.push(ipma); if let Some(exif_data) = self.exif.as_deref() { image_items.push(InfeBox { id: exif_id, typ: FourCC(*b"Exif"), name: "", }); iloc_items.push(IlocItem { id: exif_id, extents: [IlocExtent { data: exif_data }], }); irefs.push(IrefEntryBox { from_id: exif_id, to_id: color_image_id, typ: FourCC(*b"cdsc"), }); } if let Some(alpha_data) = alpha_av1_data { image_items.push(InfeBox { id: alpha_image_id, typ: FourCC(*b"av01"), name: "", }); irefs.push(IrefEntryBox { from_id: alpha_image_id, to_id: color_image_id, typ: FourCC(*b"auxl"), }); if self.premultiplied_alpha { irefs.push(IrefEntryBox { from_id: color_image_id, to_id: alpha_image_id, typ: FourCC(*b"prem"), }); } let av1c_alpha_prop = ipco.push(boxes::IpcoProp::Av1C(Av1CBox { seq_profile: if alpha_depth_bits >= 12 { 2 } else { 0 }, seq_level_idx_0: 31, seq_tier_0: false, high_bitdepth: alpha_depth_bits >= 10, twelve_bit: alpha_depth_bits >= 12, monochrome: true, chroma_subsampling_x: true, chroma_subsampling_y: true, chroma_sample_position: 0, })).ok_or(io::ErrorKind::InvalidInput)?; // So pointless let pixi_1 = ipco.push(IpcoProp::Pixi(PixiBox { channels: 1, depth: alpha_depth_bits, })).ok_or(io::ErrorKind::InvalidInput)?; // that's a silly way to add 1 bit of information, isn't it? let auxc_prop = ipco.push(IpcoProp::AuxC(AuxCBox { urn: "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha", })).ok_or(io::ErrorKind::InvalidInput)?; ipma_entries.push(IpmaEntry { item_id: alpha_image_id, prop_ids: from_array([ispe_prop, av1c_alpha_prop | ESSENTIAL_BIT, auxc_prop, pixi_1]), }); // Use interleaved color and alpha, with alpha first. // Makes it possible to display partial image. iloc_items.push(IlocItem { id: alpha_image_id, extents: [IlocExtent { data: alpha_data }], }); } iloc_items.push(IlocItem { id: color_image_id, extents: [IlocExtent { data: color_av1_data }], }); Ok(AvifFile { ftyp: FtypBox { major_brand: FourCC(*b"avif"), minor_version: 0, compatible_brands: [FourCC(*b"mif1"), FourCC(*b"miaf")].into(), }, meta: MetaBox { hdlr: HdlrBox {}, iinf: IinfBox { items: image_items }, pitm: PitmBox(color_image_id), iloc: IlocBox { absolute_offset_start: None, items: iloc_items, }, iprp: IprpBox { ipco, // It's not enough to define these properties, // they must be assigned to the image ipma: IpmaBox { entries: ipma_entries }, }, iref: IrefBox { entries: irefs }, }, // Here's the actual data. If HEIF wasn't such a kitchen sink, this // would have been the only data this file needs. mdat: MdatBox, }) } /// Panics if the input arguments were invalid. Use [`Self::write`] to handle the errors. #[must_use] #[track_caller] pub fn to_vec(&self, color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec { let mut file = self.make_boxes(color_av1_data, alpha_av1_data, width, height, depth_bits).unwrap(); let mut out = Vec::new(); file.write_to_vec(&mut out).unwrap(); out } /// `(false, false)` is 4:4:4 /// `(true, true)` is 4:2:0 /// /// `chroma_sample_position` is always 0. Don't use chroma subsampling with AVIF. #[inline] pub fn set_chroma_subsampling(&mut self, subsampled_xy: (bool, bool)) -> &mut Self { self.chroma_subsampling = subsampled_xy; self } /// Set whether the image is monochrome (grayscale). /// This is used to set the `monochrome` flag in the AV1 sequence header. #[inline] pub fn set_monochrome(&mut self, monochrome: bool) -> &mut Self { self.monochrome = monochrome; self } /// Set exif metadata to be included in the AVIF file as a separate item. #[inline] pub fn set_exif(&mut self, exif: Vec) -> &mut Self { self.exif = Some(exif); self } /// Sets minimum required /// /// Higher bit depth may increase this #[inline] pub fn set_seq_profile(&mut self, seq_profile: u8) -> &mut Self { self.min_seq_profile = seq_profile; self } #[inline] pub fn set_width(&mut self, width: u32) -> &mut Self { self.width = width; self } #[inline] pub fn set_height(&mut self, height: u32) -> &mut Self { self.height = height; self } /// 8, 10 or 12. #[inline] pub fn set_bit_depth(&mut self, bit_depth: u8) -> &mut Self { self.bit_depth = bit_depth; self } /// Set whether image's colorspace uses premultiplied alpha, i.e. RGB channels were multiplied by their alpha value, /// so that transparent areas are all black. Image decoders will be instructed to undo the premultiplication. /// /// Premultiplied alpha images usually compress better and tolerate heavier compression, but /// may not be supported correctly by less capable AVIF decoders. /// /// This just sets the configuration property. The pixel data must have already been processed before compression. /// If a decoder displays semitransparent colors too dark, it doesn't support premultiplied alpha. /// If a decoder displays semitransparent colors too bright, you didn't premultiply the colors before encoding. /// /// If you're not using premultiplied alpha, consider bleeding RGB colors into transparent areas, /// otherwise there may be unwanted outlines around edges of transparency. #[inline] pub fn set_premultiplied_alpha(&mut self, is_premultiplied: bool) -> &mut Self { self.premultiplied_alpha = is_premultiplied; self } #[doc(hidden)] pub fn premultiplied_alpha(&mut self, is_premultiplied: bool) -> &mut Self { self.set_premultiplied_alpha(is_premultiplied) } } #[inline(always)] fn from_array(array: [T; L1]) -> ArrayVec { assert!(L1 <= L2); let mut tmp = ArrayVec::new_const(); let _ = tmp.try_extend_from_slice(&array); tmp } /// See [`serialize`] for description. This one makes a `Vec` instead of using `io::Write`. #[must_use] #[track_caller] pub fn serialize_to_vec(color_av1_data: &[u8], alpha_av1_data: Option<&[u8]>, width: u32, height: u32, depth_bits: u8) -> Vec { Aviffy::new().to_vec(color_av1_data, alpha_av1_data, width, height, depth_bits) } #[test] fn test_roundtrip_parse_mp4() { let test_img = b"av12356abc"; let avif = serialize_to_vec(test_img, None, 10, 20, 8); let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap(); assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap()); } #[test] fn test_roundtrip_parse_mp4_alpha() { let test_img = b"av12356abc"; let test_a = b"alpha"; let avif = serialize_to_vec(test_img, Some(test_a), 10, 20, 8); let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap(); assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap()); assert_eq!(&test_a[..], ctx.alpha_item_coded_data().unwrap()); } #[test] fn test_roundtrip_parse_exif() { let test_img = b"av12356abc"; let test_a = b"alpha"; let avif = Aviffy::new() .set_exif(b"lol".to_vec()) .to_vec(test_img, Some(test_a), 10, 20, 8); let ctx = mp4parse::read_avif(&mut avif.as_slice(), mp4parse::ParseStrictness::Normal).unwrap(); assert_eq!(&test_img[..], ctx.primary_item_coded_data().unwrap()); assert_eq!(&test_a[..], ctx.alpha_item_coded_data().unwrap()); } #[test] fn test_roundtrip_parse_avif() { let test_img = [1, 2, 3, 4, 5, 6]; let test_alpha = [77, 88, 99]; let avif = serialize_to_vec(&test_img, Some(&test_alpha), 10, 20, 8); let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap(); assert_eq!(&test_img[..], ctx.primary_item.as_slice()); assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap()); } #[test] fn test_roundtrip_parse_avif_colr() { let test_img = [1, 2, 3, 4, 5, 6]; let test_alpha = [77, 88, 99]; let avif = Aviffy::new() .matrix_coefficients(constants::MatrixCoefficients::Bt709) .to_vec(&test_img, Some(&test_alpha), 10, 20, 8); let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap(); assert_eq!(&test_img[..], ctx.primary_item.as_slice()); assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap()); } #[test] fn premultiplied_flag() { let test_img = [1,2,3,4]; let test_alpha = [55,66,77,88,99]; let avif = Aviffy::new().premultiplied_alpha(true).to_vec(&test_img, Some(&test_alpha), 5, 5, 8); let ctx = avif_parse::read_avif(&mut avif.as_slice()).unwrap(); assert!(ctx.premultiplied_alpha); assert_eq!(&test_img[..], ctx.primary_item.as_slice()); assert_eq!(&test_alpha[..], ctx.alpha_item.as_deref().unwrap()); } #[test] fn size_required() { assert!(Aviffy::new().set_bit_depth(10).write_slice(&mut vec![], &[], None).is_err()); } #[test] fn depth_required() { assert!(Aviffy::new().set_width(1).set_height(1).write_slice(&mut vec![], &[], None).is_err()); } avif-serialize-0.8.6/src/writer.rs000064400000000000000000000072251046102023000152300ustar 00000000000000use std::convert::TryFrom; use std::io; pub struct OOM; pub trait WriterBackend { type Error; fn reserve(&mut self, size: usize) -> Result<(), Self::Error>; fn extend_from_slice_in_capacity(&mut self, data: &[u8]) -> Result<(), Self::Error>; } /// `io::Write` generates bloated code (with backtrace for every byte written), /// so small boxes are written infallibly. impl WriterBackend for Vec { type Error = OOM; #[inline] fn reserve(&mut self, size: usize) -> Result<(), Self::Error> { self.try_reserve(size).map_err(|_| OOM) } #[inline(always)] fn extend_from_slice_in_capacity(&mut self, data: &[u8]) -> Result<(), Self::Error> { let has_capacity = self.capacity() - self.len() >= data.len(); debug_assert!(has_capacity); if has_capacity { self.extend_from_slice(data); Ok(()) } else { Err(OOM) } } } pub struct IO(pub W); impl WriterBackend for IO { type Error = io::Error; #[inline] fn reserve(&mut self, _size: usize) -> io::Result<()> { Ok(()) } #[inline(always)] fn extend_from_slice_in_capacity(&mut self, data: &[u8]) -> io::Result<()> { self.0.write_all(data) } } pub struct Writer<'p, 'w, B> { out: &'w mut B, #[cfg(debug_assertions)] parent: Option<&'p mut usize>, #[cfg(not(debug_assertions))] parent: std::marker::PhantomData<&'p mut usize>, #[cfg(debug_assertions)] left: usize, } impl<'w, B> Writer<'static, 'w, B> { #[inline] pub fn new(out: &'w mut B) -> Self { Self { parent: Default::default(), #[cfg(debug_assertions)] left: 0, out, } } } impl Writer<'_, '_, B> { #[inline(always)] pub fn full_box(&mut self, len: usize, typ: [u8; 4], version: u8) -> Result, B::Error> { let mut b = self.basic_box(len, typ)?; b.push(&[version, 0, 0, 0])?; Ok(b) } #[inline] pub fn basic_box(&mut self, len: usize, typ: [u8; 4]) -> Result, B::Error> { let mut b = Writer { out: self.out, parent: Default::default(), #[cfg(debug_assertions)] left: len, }; #[cfg(debug_assertions)] if self.left > 0 { self.left -= len; b.parent = Some(&mut self.left); } else { debug_assert!(self.parent.is_none()); } b.out.reserve(len)?; if let Ok(len) = u32::try_from(len) { b.u32(len)?; } else { debug_assert!(false, "constants for box size don't include this"); b.u32(1)?; b.u64(len as u64)?; } b.push(&typ)?; Ok(b) } #[inline(always)] pub fn push(&mut self, data: &[u8]) -> Result<(), B::Error> { #[cfg(debug_assertions)] { self.left -= data.len(); } self.out.extend_from_slice_in_capacity(data) } #[inline(always)] pub fn u8(&mut self, val: u8) -> Result<(), B::Error> { self.push(std::slice::from_ref(&val)) } #[inline(always)] pub fn u16(&mut self, val: u16) -> Result<(), B::Error> { self.push(&val.to_be_bytes()) } #[inline(always)] pub fn u32(&mut self, val: u32) -> Result<(), B::Error> { self.push(&val.to_be_bytes()) } #[inline(always)] pub fn u64(&mut self, val: u64) -> Result<(), B::Error> { self.push(&val.to_be_bytes()) } } #[cfg(debug_assertions)] impl Drop for Writer<'_, '_, B> { fn drop(&mut self) { assert_eq!(self.left, 0); } }