use crate::bit_writer::BitWriter;
use crate::error::Result;
#[derive(Debug, Clone, Copy)]
pub struct FrameCrop {
pub x0: i32,
pub y0: i32,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, Default)]
pub struct FrameOptions {
pub have_animation: bool,
pub have_timecodes: bool,
pub duration: u32,
pub is_last: bool,
pub crop: Option<FrameCrop>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum FrameType {
#[default]
Regular = 0,
LfFrame = 1,
ReferenceOnly = 2,
SkipProgressive = 3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum Encoding {
#[default]
VarDct = 0,
Modular = 1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum BlendMode {
#[default]
Replace = 0,
Add = 1,
Blend = 2,
AlphaWeightedAdd = 3,
Mul = 4,
}
pub const ENABLE_NOISE: u64 = 0x01;
pub const PATCHES_FLAG: u64 = 0x02;
pub const SPLINES_FLAG: u64 = 0x10;
pub const USE_LF_FRAME: u64 = 0x20;
pub const SKIP_ADAPTIVE_LF_SMOOTHING: u64 = 0x80;
#[derive(Debug, Clone)]
pub struct FrameHeader {
pub frame_type: FrameType,
pub encoding: Encoding,
pub xyb_encoded: bool,
pub flags: u64,
pub do_ycbcr: bool,
pub jpeg_upsampling: [u8; 3],
pub upsampling: u32,
pub ec_upsampling: Vec<u32>,
pub group_size_shift: u32,
pub x_qm_scale: u32,
pub b_qm_scale: u32,
pub num_passes: u32,
pub pass_shifts: Vec<u32>,
pub num_ds: u32,
pub ds_downsample: Vec<u32>,
pub ds_last_pass: Vec<u32>,
pub x0: i32,
pub y0: i32,
pub width: u32,
pub height: u32,
pub blend_mode: BlendMode,
pub ec_blend_modes: Vec<BlendMode>,
pub blend_source: u32,
pub alpha_blend_channel: u32,
pub save_as_reference: u32,
pub save_before_ct: bool,
pub name: String,
pub have_animation: bool,
pub have_timecodes: bool,
pub duration: u32,
pub timecode: u32,
pub is_last: bool,
pub lf_level: u32,
pub gaborish: bool,
pub epf_iters: u32,
}
impl Default for FrameHeader {
fn default() -> Self {
Self {
frame_type: FrameType::Regular,
encoding: Encoding::VarDct,
xyb_encoded: true,
flags: 0,
do_ycbcr: false,
jpeg_upsampling: [0; 3],
upsampling: 1,
ec_upsampling: Vec::new(),
group_size_shift: 1,
x_qm_scale: 2,
b_qm_scale: 2,
num_passes: 1,
pass_shifts: Vec::new(),
num_ds: 0,
ds_downsample: Vec::new(),
ds_last_pass: Vec::new(),
x0: 0,
y0: 0,
width: 0,
height: 0,
blend_mode: BlendMode::Replace,
blend_source: 0,
ec_blend_modes: Vec::new(),
alpha_blend_channel: 0,
save_as_reference: 0,
save_before_ct: false,
name: String::new(),
have_animation: false,
have_timecodes: false,
duration: 0,
timecode: 0,
is_last: true,
lf_level: 0,
gaborish: true,
epf_iters: 2,
}
}
}
impl FrameHeader {
pub fn lossy() -> Self {
Self {
encoding: Encoding::VarDct,
xyb_encoded: true,
flags: 0x80, gaborish: true,
epf_iters: 2,
..Default::default()
}
}
pub fn lossless() -> Self {
Self {
encoding: Encoding::Modular,
xyb_encoded: false,
do_ycbcr: false,
flags: 0,
group_size_shift: 1,
gaborish: false,
epf_iters: 0,
..Default::default()
}
}
pub fn lf_frame(width: u32, height: u32, lf_level: u32) -> Self {
Self {
frame_type: FrameType::LfFrame,
encoding: Encoding::Modular,
xyb_encoded: true,
flags: SKIP_ADAPTIVE_LF_SMOOTHING,
gaborish: false,
epf_iters: 0,
is_last: false,
save_before_ct: false,
width,
height,
lf_level,
group_size_shift: 1, ..Default::default()
}
}
pub fn write(&self, writer: &mut BitWriter) -> Result<()> {
let all_default = self.is_all_default();
writer.write_bit(all_default)?;
if all_default {
return Ok(());
}
writer.write(2, self.frame_type as u64)?;
writer.write(1, self.encoding as u64)?;
writer.write_u64_coder(self.flags)?;
if !self.xyb_encoded {
writer.write_bit(self.do_ycbcr)?;
}
if self.encoding == Encoding::VarDct && self.do_ycbcr && !self.xyb_encoded {
for &up in &self.jpeg_upsampling {
writer.write(2, up as u64)?;
}
}
if self.flags & USE_LF_FRAME == 0 {
writer.write_u32_coder(self.upsampling, 1, 2, 4, 8, 0)?;
for &ecu in &self.ec_upsampling {
writer.write_u32_coder(ecu, 1, 2, 4, 8, 0)?;
}
}
if self.encoding == Encoding::Modular {
writer.write(2, self.group_size_shift as u64)?;
}
if self.encoding == Encoding::VarDct && self.xyb_encoded {
writer.write(3, self.x_qm_scale as u64)?;
writer.write(3, self.b_qm_scale as u64)?;
}
if self.frame_type != FrameType::ReferenceOnly {
writer.write_u32_coder(self.num_passes, 1, 2, 3, 4, 3)?;
if self.num_passes != 1 {
self.write_passes(writer)?;
}
}
if self.frame_type == FrameType::LfFrame {
writer.write_u32_coder(self.lf_level, 1, 2, 3, 4, 0)?;
}
if self.frame_type != FrameType::LfFrame {
let have_crop = self.x0 != 0 || self.y0 != 0 || self.width != 0 || self.height != 0;
writer.write_bit(have_crop)?;
if have_crop {
if self.frame_type != FrameType::ReferenceOnly {
self.write_crop_origin(writer)?;
}
Self::write_crop_u32(writer, self.width)?;
Self::write_crop_u32(writer, self.height)?;
}
}
let normal_frame =
self.frame_type == FrameType::Regular || self.frame_type == FrameType::SkipProgressive;
if normal_frame {
self.write_blending_info(writer)?;
}
for &mode in &self.ec_blend_modes {
self.write_ec_blending_info(mode, writer)?;
}
if normal_frame && self.have_animation {
match self.duration {
0 => writer.write(2, 0)?,
1 => writer.write(2, 1)?,
d if d <= 255 => {
writer.write(2, 2)?;
writer.write(8, d as u64)?;
}
d => {
writer.write(2, 3)?;
writer.write(32, d as u64)?;
}
}
if self.have_timecodes {
writer.write(32, self.timecode as u64)?;
}
}
if normal_frame {
writer.write_bit(self.is_last)?;
}
if !self.is_last && self.frame_type != FrameType::LfFrame {
writer.write(2, self.save_as_reference as u64)?;
if self.frame_type == FrameType::ReferenceOnly {
writer.write_bit(self.save_before_ct)?;
} else {
let full_frame =
self.x0 == 0 && self.y0 == 0 && self.width == 0 && self.height == 0;
let resets_canvas = self.blend_mode == BlendMode::Replace && full_frame;
let can_be_referenced = self.duration == 0 || self.save_as_reference != 0;
if resets_canvas && can_be_referenced && normal_frame {
writer.write_bit(self.save_before_ct)?;
}
}
}
self.write_name(writer)?;
self.write_loop_filter(writer)?;
writer.write_u64_coder(0)?;
Ok(())
}
fn write_crop_origin(&self, writer: &mut BitWriter) -> Result<()> {
let x0u = if self.x0 >= 0 {
(self.x0 as u32) << 1
} else {
(((-self.x0 - 1) as u32) << 1) | 1
};
let y0u = if self.y0 >= 0 {
(self.y0 as u32) << 1
} else {
(((-self.y0 - 1) as u32) << 1) | 1
};
Self::write_crop_u32(writer, x0u)?;
Self::write_crop_u32(writer, y0u)?;
Ok(())
}
fn write_crop_u32(writer: &mut BitWriter, value: u32) -> Result<()> {
if value < 256 {
writer.write(2, 0)?; writer.write(8, value as u64)?;
} else if value < 2304 {
writer.write(2, 1)?; writer.write(11, (value - 256) as u64)?;
} else if value < 18688 {
writer.write(2, 2)?; writer.write(14, (value - 2304) as u64)?;
} else {
writer.write(2, 3)?; writer.write(30, (value - 18688) as u64)?;
}
Ok(())
}
fn write_blending_info(&self, writer: &mut BitWriter) -> Result<()> {
writer.write_u32_coder(self.blend_mode as u32, 0, 1, 2, 3, 2)?;
let full_frame = self.x0 == 0 && self.y0 == 0 && self.width == 0 && self.height == 0;
if !(full_frame && self.blend_mode == BlendMode::Replace) {
writer.write(2, self.blend_source as u64)?;
}
if self.blend_mode == BlendMode::Blend || self.blend_mode == BlendMode::AlphaWeightedAdd {
writer.write_u32_coder(self.alpha_blend_channel, 0, 1, 2, 3, 3)?;
writer.write_bit(false)?; }
Ok(())
}
fn write_ec_blending_info(&self, mode: BlendMode, writer: &mut BitWriter) -> Result<()> {
writer.write_u32_coder(mode as u32, 0, 1, 2, 3, 2)?;
let full_frame = self.x0 == 0 && self.y0 == 0 && self.width == 0 && self.height == 0;
if !(full_frame && mode == BlendMode::Replace) {
writer.write(2, 0)?; }
if mode == BlendMode::Blend || mode == BlendMode::AlphaWeightedAdd {
writer.write_u32_coder(0, 0, 1, 2, 3, 3)?; writer.write_bit(false)?; }
Ok(())
}
fn write_name(&self, writer: &mut BitWriter) -> Result<()> {
let name_len = self.name.len() as u32;
if name_len == 0 {
writer.write(2, 0)?; } else if name_len < 4 {
writer.write(2, 0)?; } else if name_len < 20 {
writer.write(2, 2)?;
writer.write(4, (name_len - 4) as u64)?;
} else {
writer.write(2, 3)?;
writer.write(10, (name_len - 20) as u64)?;
}
for byte in self.name.bytes() {
writer.write(8, byte as u64)?;
}
Ok(())
}
fn write_loop_filter(&self, writer: &mut BitWriter) -> Result<()> {
let lf_all_default = self.gaborish && self.epf_iters == 2;
writer.write_bit(lf_all_default)?;
if lf_all_default {
return Ok(());
}
writer.write_bit(self.gaborish)?;
if self.gaborish {
writer.write_bit(false)?; }
writer.write(2, self.epf_iters as u64)?;
if self.epf_iters > 0 {
writer.write_bit(false)?; writer.write_bit(false)?; writer.write_bit(false)?; }
writer.write_u64_coder(0)?;
Ok(())
}
fn write_passes(&self, writer: &mut BitWriter) -> Result<()> {
writer.write_u32_coder(self.num_ds, 0, 1, 2, 3, 1)?;
for i in 0..self.num_passes.saturating_sub(1) as usize {
let shift = self.pass_shifts.get(i).copied().unwrap_or(0);
writer.write(2, shift as u64)?;
}
for i in 0..self.num_ds as usize {
let ds = self.ds_downsample.get(i).copied().unwrap_or(1);
writer.write_u32_coder(ds, 1, 2, 4, 8, 0)?;
}
for i in 0..self.num_ds as usize {
let lp = self.ds_last_pass.get(i).copied().unwrap_or(0);
writer.write_u32_coder(lp, 0, 1, 2, 0, 3)?;
}
Ok(())
}
fn is_all_default(&self) -> bool {
self.frame_type == FrameType::Regular
&& self.encoding == Encoding::VarDct
&& self.xyb_encoded
&& self.flags == 0
&& self.do_ycbcr
&& self.upsampling == 1
&& self.ec_upsampling.is_empty()
&& self.ec_blend_modes.is_empty()
&& self.group_size_shift == 1
&& self.x_qm_scale == 2
&& self.b_qm_scale == 2
&& self.num_passes == 1
&& self.pass_shifts.is_empty()
&& self.x0 == 0
&& self.y0 == 0
&& self.width == 0
&& self.height == 0
&& self.blend_mode == BlendMode::Replace
&& self.blend_source == 0
&& self.save_as_reference == 0
&& !self.save_before_ct
&& self.name.is_empty()
&& !self.have_animation
&& self.is_last
&& self.gaborish
&& self.epf_iters == 2
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_frame() {
let frame = FrameHeader::lossy();
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
}
#[test]
fn test_lossless_frame() {
let frame = FrameHeader::lossless();
assert_eq!(frame.encoding, Encoding::Modular);
assert!(!frame.do_ycbcr);
assert!(!frame.gaborish);
assert_eq!(frame.epf_iters, 0);
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_frame_type_values() {
assert_eq!(FrameType::Regular as u8, 0);
assert_eq!(FrameType::LfFrame as u8, 1);
assert_eq!(FrameType::ReferenceOnly as u8, 2);
assert_eq!(FrameType::SkipProgressive as u8, 3);
}
#[test]
fn test_encoding_values() {
assert_eq!(Encoding::VarDct as u8, 0);
assert_eq!(Encoding::Modular as u8, 1);
}
#[test]
fn test_blend_mode_values() {
assert_eq!(BlendMode::Replace as u8, 0);
assert_eq!(BlendMode::Add as u8, 1);
assert_eq!(BlendMode::Blend as u8, 2);
assert_eq!(BlendMode::AlphaWeightedAdd as u8, 3);
assert_eq!(BlendMode::Mul as u8, 4);
}
#[test]
fn test_frame_with_crop() {
let mut frame = FrameHeader::lossy();
frame.x0 = 0;
frame.y0 = 0;
frame.width = 20000;
frame.height = 20000;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 10);
}
#[test]
fn test_frame_with_large_crop_offset() {
let mut frame = FrameHeader::lossy();
frame.x0 = 128;
frame.y0 = 128;
frame.width = 20000;
frame.height = 20000;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 10);
}
#[test]
fn test_frame_with_name() {
let mut frame = FrameHeader::lossy();
frame.name = "TestFrame".to_string();
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 80);
}
#[test]
fn test_frame_with_long_name() {
let mut frame = FrameHeader::lossy();
frame.name = "ThisIsAVeryLongFrameName".to_string();
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 200);
}
#[test]
fn test_lf_frame_type() {
let frame = FrameHeader::lf_frame(32, 32, 1);
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_reference_only_frame() {
let mut frame = FrameHeader::lossy();
frame.frame_type = FrameType::ReferenceOnly;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_skip_progressive_frame() {
let mut frame = FrameHeader::lossy();
frame.frame_type = FrameType::SkipProgressive;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_blend_mode_add() {
let mut frame = FrameHeader::lossy();
frame.blend_mode = BlendMode::Add;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_blend_mode_blend_with_alpha() {
let mut frame = FrameHeader::lossy();
frame.blend_mode = BlendMode::Blend;
frame.alpha_blend_channel = 1;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_blend_mode_alpha_weighted_add() {
let mut frame = FrameHeader::lossy();
frame.blend_mode = BlendMode::AlphaWeightedAdd;
frame.alpha_blend_channel = 2;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_blend_mode_mul() {
let mut frame = FrameHeader::lossy();
frame.blend_mode = BlendMode::Mul;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_upsampling_factors() {
for upsampling in [1, 2, 4, 8] {
let mut frame = FrameHeader::lossy();
frame.upsampling = upsampling;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
}
#[test]
fn test_ec_upsampling() {
let mut frame = FrameHeader::lossy();
frame.ec_upsampling = vec![2, 4, 8];
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_group_size_shift() {
for shift in 0..4 {
let mut frame = FrameHeader::lossless();
frame.group_size_shift = shift;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
}
#[test]
fn test_save_as_reference() {
let mut frame = FrameHeader::lossy();
frame.save_as_reference = 2;
frame.is_last = false;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_not_last_frame() {
let mut frame = FrameHeader::lossy();
frame.is_last = false;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_vardct_loop_filter_all_default() {
let frame = FrameHeader::lossy();
assert!(frame.gaborish && frame.epf_iters == 2);
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
}
#[test]
fn test_vardct_no_gaborish() {
let mut frame = FrameHeader::lossy();
frame.gaborish = false;
frame.epf_iters = 1;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_vardct_no_epf() {
let mut frame = FrameHeader::lossy();
frame.gaborish = true;
frame.epf_iters = 0;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_vardct_with_noise() {
let mut frame = FrameHeader::lossy();
frame.flags = 0x80 | 0x01;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_vardct_custom_qm_scale() {
let mut frame = FrameHeader::lossy();
frame.x_qm_scale = 5;
frame.b_qm_scale = 4;
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_vardct_with_extra_channels() {
let mut frame = FrameHeader::lossy();
frame.ec_upsampling = vec![1]; frame.ec_blend_modes = vec![BlendMode::Replace];
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_lossless_with_extra_channels() {
let mut frame = FrameHeader::lossless();
frame.ec_upsampling = vec![1]; frame.ec_blend_modes = vec![BlendMode::Replace];
let mut writer = BitWriter::new();
frame.write(&mut writer).unwrap();
assert!(writer.bits_written() > 0);
}
#[test]
fn test_vardct_bit_exact_vs_old() {
let mut old_writer = BitWriter::new();
old_writer.write(1, 0).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(1, 0).unwrap(); old_writer.write(2, 2).unwrap(); old_writer.write(8, 128 - 17).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(3, 3).unwrap(); old_writer.write(3, 2).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(1, 0).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(1, 1).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(1, 0).unwrap(); old_writer.write(1, 1).unwrap(); old_writer.write(1, 0).unwrap(); old_writer.write(2, 1).unwrap(); old_writer.write(1, 0).unwrap(); old_writer.write(1, 0).unwrap(); old_writer.write(1, 0).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(2, 0).unwrap();
let mut new_writer = BitWriter::new();
let mut frame = FrameHeader::lossy();
frame.x_qm_scale = 3;
frame.b_qm_scale = 2;
frame.epf_iters = 1;
frame.write(&mut new_writer).unwrap();
assert_eq!(
old_writer.bits_written(),
new_writer.bits_written(),
"VarDCT frame header bit count should match"
);
old_writer.zero_pad_to_byte();
new_writer.zero_pad_to_byte();
assert_eq!(
old_writer.finish(),
new_writer.finish(),
"VarDCT frame header should be bit-exact"
);
}
#[test]
fn test_vardct_lf_all_default_bit_exact() {
let mut old_writer = BitWriter::new();
old_writer.write(1, 0).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(1, 0).unwrap(); old_writer.write(2, 2).unwrap(); old_writer.write(8, 128 - 17).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(3, 3).unwrap(); old_writer.write(3, 2).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(1, 0).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(1, 1).unwrap(); old_writer.write(2, 0).unwrap(); old_writer.write(1, 1).unwrap(); old_writer.write(2, 0).unwrap();
let mut new_writer = BitWriter::new();
let mut frame = FrameHeader::lossy();
frame.x_qm_scale = 3;
frame.b_qm_scale = 2;
frame.gaborish = true;
frame.epf_iters = 2;
frame.write(&mut new_writer).unwrap();
assert_eq!(
old_writer.bits_written(),
new_writer.bits_written(),
"VarDCT lf all_default bit count should match"
);
old_writer.zero_pad_to_byte();
new_writer.zero_pad_to_byte();
assert_eq!(
old_writer.finish(),
new_writer.finish(),
"VarDCT with lf all_default should be bit-exact"
);
}
}