craft-video: VP8 key-frame encoder (vp8-encode feature) (#969)

Moves effectcraft's effectcraft-vp8enc here: the lossy picture of a WebP image
or animation frame, written from RFC 6386. Rate-distortion choice between the
16x16 and B_PRED intra modes, per-frame token probability updates, loop filter
level from the quantizer.

`vp8` now turns on `vp8-decode` and `vp8-encode`, as `vp9` does; the decoder's
tests and examples require `vp8-decode`. The tests decode the encoder's frames
with this crate's decoder: with the loop filter off it reproduces the encoder's
reconstruction exactly.
This commit is contained in:
Justin Wilson
2026-11-11 20:09:32 -05:00
committed by GitHub
parent 75471d19bd
commit 8cdcdce326
12 changed files with 2355 additions and 9 deletions
+11 -5
View File
@@ -21,8 +21,10 @@ dnx = []
apv = ["apv-decode", "apv-encode"]
apv-decode = ["craft-media/bitstream"]
apv-encode = ["craft-media/bitstream"]
# VP8 (RFC 6386): decoder.
vp8 = []
# VP8 (RFC 6386): decoder and encoder; `vp8-decode` or `vp8-encode` for one of them.
vp8 = ["vp8-decode", "vp8-encode"]
vp8-decode = []
vp8-encode = []
# MPEG-2 video (ITU-T H.262) and MPEG-1 video (ISO/IEC 11172-2): decoder.
mpeg2 = []
# Apple ProRes (SMPTE RDD 36): decoder and encoder.
@@ -64,15 +66,15 @@ required-features = ["apv"]
[[test]]
name = "vp8"
required-features = ["vp8"]
required-features = ["vp8-decode"]
[[example]]
name = "vp8_fuzz"
required-features = ["vp8"]
required-features = ["vp8-decode"]
[[example]]
name = "vp8_vp8dec"
required-features = ["vp8"]
required-features = ["vp8-decode"]
[[test]]
name = "av1_decode"
@@ -98,6 +100,10 @@ required-features = ["hevc-decode"]
name = "hevc_hevcdec"
required-features = ["hevc-decode"]
[[test]]
name = "vp8_encode"
required-features = ["vp8"]
[[test]]
name = "vp9_decode"
required-features = ["vp9-decode"]
+2 -2
View File
@@ -7,7 +7,7 @@
//! |---|---|---|
//! | `dnx` | [`dnx`] | Avid DNxHD / DNxHR (SMPTE ST 2019-1): decoder and encoder |
//! | `apv` | [`apv`] | APV (RFC 9924): decoder (`apv-decode`) and encoder (`apv-encode`) |
//! | `vp8` | [`vp8`] | VP8 (RFC 6386): decoder |
//! | `vp8` | [`vp8`] | VP8 (RFC 6386): decoder (`vp8-decode`) and key-frame encoder (`vp8-encode`) |
//! | `mpeg2` | [`mpeg2`] | MPEG-2 video (ITU-T H.262) and MPEG-1 video: decoder |
//! | `prores` | [`prores`] | Apple ProRes (SMPTE RDD 36): decoder and encoder |
//! | `h264` | [`h264`] | ITU-T H.264 / AVC: decoder (`h264-decode`) and encoder (`h264-encode`) |
@@ -31,7 +31,7 @@ pub mod apv;
#[cfg(feature = "dnx")]
pub mod dnx;
#[cfg(feature = "vp8")]
#[cfg(any(feature = "vp8-decode", feature = "vp8-encode"))]
pub mod vp8;
#[cfg(feature = "mpeg2")]
+34
View File
@@ -0,0 +1,34 @@
# VP8 encoder (`vp8-encode`)
A VP8 key-frame encoder: the lossy picture inside a WebP image or animation frame (a `VP8 `
chunk). WebP's lossy frames are VP8 key frames, so that is all this encodes; there are no inter
frames.
* **Prediction**: every macroblock is coded with the best of the four 16×16 luma modes (DC, V, H,
TM, with the second-order Walsh–Hadamard "Y2" block of the subblocks' DC coefficients) or
B_PRED (each 4×4 subblock with the best of its ten modes, predicted from the subblocks before
it), and the best of the four 8×8 chroma modes.
* **Choice**: by rate–distortion cost, squared error plus λ times the bits, the bits counted from
the token and mode probabilities in the contexts a decoder uses.
* **Quantization**: one quality setting (0–100) picks the frame's quantizer index; luma DC, Y2 and
chroma factors follow from it as the format derives them. Macroblocks left with nothing to code
are skipped (`mb_skip_coeff`).
* **Probabilities**: the coefficient token probabilities are updated per frame where the frame's
own token counts save more than the update costs.
* **Loop filter**: the level is chosen from the quantizer (normal filter, sharpness 0). Intra
prediction reads the unfiltered reconstruction, so the filter shapes only the decoded picture.
* **Colour**: RGBA → BT.601 studio-range Y′CbCr 4:2:0, chroma averaged per 2×2 weighted by alpha
so colour under full transparency does not tint visible edges.
The encoder reconstructs exactly what a decoder reconstructs before loop filtering (prediction
with the frame-edge values of §12, the 16-bit inverse transforms of §14) and returns it with the
frame; the tests require a decoder (ffmpeg, when installed) to reproduce it exactly, and the
`image` crate's WebP decoder to read the frame back.
Written from **RFC 6386, *VP8 Data Format and Decoding Guide* (November 2011)**, sections 1–19 (the
format description and Annex A's bitstream syntax). The constant tables (§11.5, §13.4, §13.5,
§14.1) are generated from the RFC's text. Its Attachment One, the reference decoder source, was
not used; neither was any other encoder or decoder's source. Where the RFC's text leaves a detail
to that source — the coefficient scan order and the Y2 and chroma dequantization factors — the
encoder uses the format's known values, checked by decoding its output with independent decoders.
The forward transforms are derived from §14's inverse transforms (see `src/transform.rs`).
+233
View File
@@ -0,0 +1,233 @@
//! The boolean entropy encoder (RFC 6386 §7) and the cost of coding a bool.
use std::sync::OnceLock;
/// Writes bools at 8-bit probabilities, as §7.3 describes: `bottom` and `range` are the coding
/// interval, `bit_count` the shifts left before a byte of `bottom` is ready.
pub(crate) struct BoolWriter {
out: Vec<u8>,
range: u32,
bottom: u32,
bit_count: i32,
}
impl BoolWriter {
/// An empty writer, its range full (§7.3).
pub(crate) fn new() -> BoolWriter {
BoolWriter { out: Vec::new(), range: 255, bottom: 0, bit_count: 24 }
}
/// A bool whose probability of being false is `prob / 256`.
pub(crate) fn put(&mut self, prob: u8, bit: bool) {
let split = 1 + (((self.range - 1) * u32::from(prob)) >> 8);
if bit {
self.bottom = self.bottom.wrapping_add(split);
self.range -= split;
} else {
self.range = split;
}
while self.range < 128 {
self.range <<= 1;
if self.bottom & (1 << 31) != 0 {
self.carry();
}
self.bottom <<= 1;
self.bit_count -= 1;
if self.bit_count == 0 {
self.out.push((self.bottom >> 24) as u8);
self.bottom &= (1 << 24) - 1;
self.bit_count = 8;
}
}
}
/// An unsigned `bits`-wide literal, high bit first, each bit at probability 1/2.
pub(crate) fn literal(&mut self, bits: u32, value: u32) {
for b in (0..bits).rev() {
self.put(128, (value >> b) & 1 == 1);
}
}
/// The bytes written, the interval's remainder appended (§7.3, flush).
pub(crate) fn finish(mut self) -> Vec<u8> {
let mut c = self.bit_count;
let mut v = self.bottom;
if c < 32 && v & (1 << (32 - c)) != 0 {
self.carry();
}
v <<= c & 7;
c >>= 3;
while c > 0 {
v <<= 8;
c -= 1;
}
for _ in 0..4 {
self.out.push((v >> 24) as u8);
v <<= 8;
}
self.out
}
/// Adds one to the bytes already written. The coded value is always less than one, so the
/// carry never runs off the front.
fn carry(&mut self) {
for b in self.out.iter_mut().rev() {
if *b == 255 {
*b = 0;
} else {
*b += 1;
return;
}
}
}
}
/// Bits a bool costs at `prob`: −log2 of its probability.
pub(crate) fn cost(prob: u8, bit: bool) -> f32 {
static TABLE: OnceLock<[f32; 256]> = OnceLock::new();
let t = TABLE.get_or_init(|| {
let mut t = [0f32; 256];
for (p, c) in t.iter_mut().enumerate() {
*c = -((p.max(1) as f32) / 256.0).log2();
}
t
});
let p = if bit { 256 - usize::from(prob) } else { usize::from(prob) };
t[p.min(255)]
}
/// Writes `leaf` of a tree in the RFC's array form (§8.1): even entries are pairs of branches, a
/// positive entry the index of a deeper pair, a non-positive one the leaf `-entry`. `probs` are
/// indexed by half the pair's index. Returns false when the tree has no such leaf.
pub(crate) fn put_tree(w: &mut BoolWriter, tree: &[i8], probs: &[u8], leaf: u8) -> bool {
let mut path = Vec::with_capacity(12);
if !find(tree, 0, leaf, &mut path, 0) {
return false;
}
for (node, bit) in path {
w.put(probs.get(node / 2).copied().unwrap_or(128), bit);
}
true
}
/// Bits [`put_tree`] would spend.
pub(crate) fn tree_cost(tree: &[i8], probs: &[u8], leaf: u8) -> f32 {
let mut path = Vec::with_capacity(12);
if !find(tree, 0, leaf, &mut path, 0) {
return 0.0;
}
path.iter().map(|&(node, bit)| cost(probs.get(node / 2).copied().unwrap_or(128), bit)).sum()
}
/// Find the path from `node` to `leaf` in a token tree, pushing each (node, bit) on the way; false if it isn't there.
fn find(tree: &[i8], node: usize, leaf: u8, path: &mut Vec<(usize, bool)>, depth: usize) -> bool {
if depth > 16 {
return false;
}
for bit in [false, true] {
let Some(&entry) = tree.get(node + usize::from(bit)) else { return false };
path.push((node, bit));
let hit = if entry > 0 { find(tree, entry as usize, leaf, path, depth + 1) } else { (-i16::from(entry)) as u8 == leaf };
if hit {
return true;
}
path.pop();
}
false
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
/// The decoder of §7.3, to read back what the writer wrote.
pub(crate) struct BoolReader<'a> {
data: &'a [u8],
pos: usize,
value: u32,
range: u32,
bit_count: u32,
}
impl<'a> BoolReader<'a> {
/// A reader over `data`, primed with its first two bytes.
pub(crate) fn new(data: &'a [u8]) -> Self {
let byte = |i: usize| u32::from(data.get(i).copied().unwrap_or(0));
BoolReader { data, pos: 2, value: (byte(0) << 8) | byte(1), range: 255, bit_count: 0 }
}
/// Read one bool coded with probability `prob` (of a 0).
pub(crate) fn get(&mut self, prob: u8) -> bool {
let split = 1 + (((self.range - 1) * u32::from(prob)) >> 8);
let big = split << 8;
let bit = if self.value >= big {
self.range -= split;
self.value -= big;
true
} else {
self.range = split;
false
};
while self.range < 128 {
self.value <<= 1;
self.range <<= 1;
self.bit_count += 1;
if self.bit_count == 8 {
self.bit_count = 0;
self.value |= u32::from(self.data.get(self.pos).copied().unwrap_or(0));
self.pos += 1;
}
}
bit
}
}
/// Bools written at assorted probabilities read back unchanged.
#[test]
fn bools_read_back() {
let mut w = BoolWriter::new();
let mut want = Vec::new();
let mut x = 12345u32;
for i in 0..20000 {
x = x.wrapping_mul(1103515245).wrapping_add(12345);
let prob = ((x >> 16) % 255 + 1) as u8;
let bit = (x >> 8) % 256 >= u32::from(prob) || i % 97 == 0;
w.put(prob, bit);
want.push((prob, bit));
}
let data = w.finish();
let mut r = BoolReader::new(&data);
for (i, (prob, bit)) in want.into_iter().enumerate() {
assert_eq!(r.get(prob), bit, "bool {i}");
}
}
/// Each leaf of a tree reads back as the leaf that was written.
#[test]
fn trees_write_their_leaves() {
// kf_ymode_tree (§11.2): B_PRED = 0, DC = 100, V = 101, H = 110, TM = 111.
const TREE: [i8; 8] = [-4, 2, 4, 6, 0, -1, -2, -3];
for leaf in 0..5u8 {
let mut w = BoolWriter::new();
assert!(put_tree(&mut w, &TREE, &[145, 156, 163, 128], leaf));
let data = w.finish();
let mut r = BoolReader::new(&data);
let read = if !r.get(145) {
4
} else if !r.get(156) {
u8::from(r.get(163))
} else {
2 + u8::from(r.get(128))
};
assert_eq!(read, leaf);
}
assert!(!put_tree(&mut BoolWriter::new(), &TREE, &[128; 4], 9));
}
/// A bool's cost is its information in bits: −log₂ of its probability.
#[test]
fn costs_are_information() {
assert!((cost(128, false) - 1.0).abs() < 1e-6);
assert!((cost(192, false) - 0.415).abs() < 1e-3);
assert!((cost(192, true) - 2.0).abs() < 1e-6);
}
}
+787
View File
@@ -0,0 +1,787 @@
//! Mode decision, quantization, reconstruction and the bitstream of a key frame (RFC 6386 §9–§14
//! and Annex A).
//!
//! Three passes over the macroblocks: the first chooses each one's modes and quantized
//! coefficients and reconstructs it as a decoder will (later macroblocks are predicted from that
//! reconstruction); the second counts the coefficient tokens, from which the probability updates
//! are chosen; the third writes the first partition (header and modes) and the token partition.
use super::bool::{BoolWriter, cost, put_tree, tree_cost};
use super::predict::{B_DC_PRED, B_HE_PRED, B_PRED, B_TM_PRED, B_VE_PRED, DC_PRED, Edges, H_PRED, TM_PRED, V_PRED, predict_block, predict_sub};
use super::tables::{
AC_QLOOKUP, CAT_BASE, COEFF_BANDS, COEFF_UPDATE_PROBS, DC_QLOOKUP, DEFAULT_COEFF_PROBS, KF_BMODE_PROB, KF_UV_MODE_PROB, KF_YMODE_PROB, PCAT,
};
use super::transform::{fdct, fwht, idct, iwht};
use super::{Vp8Error, Vp8Frame, Vp8Params, q_index};
/// One 8-bit plane.
#[derive(Clone, Debug, PartialEq)]
pub struct Plane {
pub width: usize,
pub height: usize,
pub data: Vec<u8>,
}
impl Plane {
/// A zeroed `width`×`height` plane.
pub(crate) fn new(width: usize, height: usize) -> Plane {
Plane { width, height, data: vec![0; width * height] }
}
/// The sample at (x, y); 0 outside the plane.
pub fn get(&self, x: usize, y: usize) -> u8 {
if x < self.width { self.data.get(y * self.width + x).copied().unwrap_or(0) } else { 0 }
}
/// Set the sample at (x, y); writes outside the plane are ignored.
pub(crate) fn set(&mut self, x: usize, y: usize, v: u8) {
if x < self.width
&& let Some(p) = self.data.get_mut(y * self.width + x)
{
*p = v;
}
}
}
/// The scan order of coefficients: position in the scan → raster index in the 4×4 block.
const ZIGZAG: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
/// The mode trees of §11.2 and §11.4 in the RFC's array form.
const KF_YMODE_TREE: [i8; 8] = [-4, 2, 4, 6, 0, -1, -2, -3];
const UV_MODE_TREE: [i8; 6] = [0, 2, -1, 4, -2, -3];
const BMODE_TREE: [i8; 18] = [0, 2, -1, 4, -2, 6, 8, 12, -3, 10, -5, -6, -4, 14, -7, 16, -8, -9];
/// Coefficient block types, the outer index of the token probabilities (§13.3).
const TYPE_Y_AFTER_Y2: usize = 0;
const TYPE_Y2: usize = 1;
const TYPE_UV: usize = 2;
const TYPE_Y_WITH_DC: usize = 3;
type Probs = [[[[u8; 11]; 3]; 8]; 4];
/// Lagrange multiplier per squared AC step: the weight of a bit against squared error.
const LAMBDA_PER_STEP2: f64 = 0.02;
/// Dequantization factors, `[dc, ac]` for luma, Y2 and chroma, from one quantizer index.
#[derive(Clone, Copy)]
struct Quant {
y1: [i32; 2],
y2: [i32; 2],
uv: [i32; 2],
}
impl Quant {
/// The dequantization factors for quantizer index `q` (0–127, §14.1).
fn new(q: u8) -> Quant {
let q = usize::from(q.min(127));
let (dc, ac) = (DC_QLOOKUP[q], AC_QLOOKUP[q]);
Quant { y1: [dc, ac], y2: [dc * 2, (ac * 155 / 100).max(8)], uv: [dc.min(132), ac] }
}
}
/// A coefficient quantized to a level a decoder can hold: the token alphabet stops at 2048 and the
/// dequantized value is a 16-bit integer.
fn quantize(c: f64, dq: i32, dc: bool) -> i16 {
let bias = if dc { 0.45 } else { 0.38 };
let max = (32767 / dq.max(1)).min(2048);
let level = ((c.abs() / f64::from(dq.max(1))) + bias).floor().min(f64::from(max)) as i16;
if c < 0.0 { -level } else { level }
}
/// A level times its dequantization factor, as a decoder computes it.
fn dequant(level: i16, dq: i32) -> i16 {
(i32::from(level) * dq) as i16
}
/// Whether neighbouring blocks had non-zero coefficients, per plane: the first token's context.
#[derive(Clone, Copy, Default)]
struct Nz {
y: [bool; 4],
u: [bool; 2],
v: [bool; 2],
y2: bool,
}
/// What is decided for a macroblock. Levels are in raster order within each 4×4 block.
#[derive(Clone)]
struct Mb {
ymode: u8,
bmodes: [u8; 16],
uvmode: u8,
skip: bool,
y2: [i16; 16],
y: [[i16; 16]; 16],
u: [[i16; 16]; 4],
v: [[i16; 16]; 4],
}
/// The subblock mode a whole-block luma mode stands for in later blocks' contexts (§11.3).
fn implied_bmode(ymode: u8) -> u8 {
match ymode {
V_PRED => B_VE_PRED,
H_PRED => B_HE_PRED,
TM_PRED => B_TM_PRED,
_ => B_DC_PRED,
}
}
/// Receives the bools of coefficient tokens: to cost them, count them or write them.
trait TokenSink {
/// `node` is the token tree node (for counting), `None` for extra bits and signs.
fn bit(&mut self, ty: usize, band: usize, ctx: usize, node: Option<usize>, prob: u8, bit: bool);
}
struct CostSink(f32);
impl TokenSink for CostSink {
/// Adds the bit's cost.
fn bit(&mut self, _: usize, _: usize, _: usize, _: Option<usize>, prob: u8, bit: bool) {
self.0 += cost(prob, bit);
}
}
/// A subblock mode candidate: cost, mode, levels, reconstruction, squared error, bits.
type BestSub = (f64, u8, [i16; 16], [u8; 16], u64, f32);
/// How often each token tree node saw a 0 and a 1: by block type, band, context and node.
type TokenCounts = Box<[[[[[u32; 2]; 11]; 3]; 8]; 4]>;
struct CountSink(TokenCounts);
impl TokenSink for CountSink {
/// Counts the bit against its tree node, for choosing probabilities.
fn bit(&mut self, ty: usize, band: usize, ctx: usize, node: Option<usize>, _: u8, bit: bool) {
if let Some(n) = node {
self.0[ty][band][ctx][n][usize::from(bit)] += 1;
}
}
}
impl TokenSink for BoolWriter {
/// Writes the bit.
fn bit(&mut self, _: usize, _: usize, _: usize, _: Option<usize>, prob: u8, bit: bool) {
self.put(prob, bit);
}
}
/// Codes one token (§13.2): its tree path, extra bits and sign.
fn token<S: TokenSink>(s: &mut S, probs: &Probs, ty: usize, band: usize, ctx: usize, v: i16, skip_eob: bool) {
let p = &probs[ty][band][ctx];
let node = |s: &mut S, n: usize, bit: bool| s.bit(ty, band, ctx, Some(n), p[n], bit);
if !skip_eob {
node(s, 0, true);
}
let a = u32::from(v.unsigned_abs());
if a == 0 {
node(s, 1, false);
return;
}
node(s, 1, true);
if a == 1 {
node(s, 2, false);
} else {
node(s, 2, true);
if a <= 4 {
node(s, 3, false);
if a == 2 {
node(s, 4, false);
} else {
node(s, 4, true);
node(s, 5, a == 4);
}
} else {
node(s, 3, true);
let cat = match a {
5..=6 => 0,
7..=10 => 1,
11..=18 => 2,
19..=34 => 3,
35..=66 => 4,
_ => 5,
};
if cat <= 1 {
node(s, 6, false);
node(s, 7, cat == 1);
} else {
node(s, 6, true);
if cat <= 3 {
node(s, 8, false);
node(s, 9, cat == 3);
} else {
node(s, 8, true);
node(s, 10, cat == 5);
}
}
let extra = a - CAT_BASE[cat];
let bits = PCAT[cat];
for (k, &pp) in bits.iter().enumerate() {
s.bit(ty, band, ctx, None, pp, (extra >> (bits.len() - 1 - k)) & 1 == 1);
}
}
}
s.bit(ty, band, ctx, None, 128, v < 0);
}
/// Codes a block's coefficients from scan position `first` with neighbour context `ctx`;
/// returns whether any of them is non-zero.
fn code_block<S: TokenSink>(s: &mut S, probs: &Probs, levels: &[i16; 16], ty: usize, first: usize, ctx: usize) -> bool {
let Some(last) = (first..16).rev().find(|&i| levels[ZIGZAG[i]] != 0) else {
let band = COEFF_BANDS[first];
s.bit(ty, band, ctx, Some(0), probs[ty][band][ctx][0], false);
return false;
};
let mut c = ctx;
let mut skip_eob = false;
for i in first..=last {
let v = levels[ZIGZAG[i]];
token(s, probs, ty, COEFF_BANDS[i], c, v, skip_eob);
let a = v.unsigned_abs();
c = match a {
0 => 0,
1 => 1,
_ => 2,
};
skip_eob = a == 0;
}
if last < 15 {
let band = COEFF_BANDS[last + 1];
s.bit(ty, band, c, Some(0), probs[ty][band][c][0], false);
}
true
}
/// The bits a block of levels costs to code, from its first coefficient.
fn block_cost(probs: &Probs, levels: &[i16; 16], ty: usize, first: usize, ctx: usize) -> f32 {
let mut s = CostSink(0.0);
code_block(&mut s, probs, levels, ty, first, ctx);
s.0
}
/// Whether any coefficient from `first` on is non-zero.
fn any_coded(levels: &[i16; 16], first: usize) -> bool {
(first..16).any(|i| levels[ZIGZAG[i]] != 0)
}
/// Runs every macroblock's coefficients through `sink` with the contexts a decoder keeps.
fn tokens<S: TokenSink>(s: &mut S, probs: &Probs, mbs: &[Mb], mbw: usize) {
let mut above = vec![Nz::default(); mbw];
let mut left = Nz::default();
for (i, mb) in mbs.iter().enumerate() {
let x = i % mbw;
if x == 0 {
left = Nz::default();
}
let Some(up) = above.get_mut(x) else { return };
let has_y2 = mb.ymode != B_PRED;
if mb.skip {
// Nothing coded: every block is empty, and so is Y2 for a macroblock that has one.
up.y = [false; 4];
up.u = [false; 2];
up.v = [false; 2];
left.y = [false; 4];
left.u = [false; 2];
left.v = [false; 2];
if has_y2 {
up.y2 = false;
left.y2 = false;
}
continue;
}
let (ty, first) = if has_y2 {
let nz = code_block(s, probs, &mb.y2, TYPE_Y2, 0, usize::from(left.y2) + usize::from(up.y2));
left.y2 = nz;
up.y2 = nz;
(TYPE_Y_AFTER_Y2, 1)
} else {
(TYPE_Y_WITH_DC, 0)
};
for (b, levels) in mb.y.iter().enumerate() {
let (r, c) = (b / 4, b % 4);
let nz = code_block(s, probs, levels, ty, first, usize::from(left.y[r]) + usize::from(up.y[c]));
left.y[r] = nz;
up.y[c] = nz;
}
for (b, levels) in mb.u.iter().enumerate() {
let (r, c) = (b / 2, b % 2);
let nz = code_block(s, probs, levels, TYPE_UV, 0, usize::from(left.u[r]) + usize::from(up.u[c]));
left.u[r] = nz;
up.u[c] = nz;
}
for (b, levels) in mb.v.iter().enumerate() {
let (r, c) = (b / 2, b % 2);
let nz = code_block(s, probs, levels, TYPE_UV, 0, usize::from(left.v[r]) + usize::from(up.v[c]));
left.v[r] = nz;
up.v[c] = nz;
}
}
}
/// The probabilities to code the frame's tokens with: each default replaced where the frame's
/// own counts save more than the update costs (§13.4).
fn updated_probs(counts: &CountSink) -> Probs {
let mut probs = DEFAULT_COEFF_PROBS;
for t in 0..4 {
for b in 0..8 {
for c in 0..3 {
for n in 0..11 {
let [n0, n1] = counts.0[t][b][c][n];
let total = n0 + n1;
if total == 0 {
continue;
}
let old = DEFAULT_COEFF_PROBS[t][b][c][n];
let new = ((u64::from(n0) * 256 + u64::from(total) / 2) / u64::from(total)).clamp(1, 255) as u8;
let bits = |p: u8| n0 as f32 * cost(p, false) + n1 as f32 * cost(p, true);
let upd = COEFF_UPDATE_PROBS[t][b][c][n];
let keep = bits(old) + cost(upd, false);
let change = bits(new) + cost(upd, true) + 8.0;
if new != old && change < keep {
probs[t][b][c][n] = new;
}
}
}
}
}
probs
}
struct Encoder<'a> {
src_y: &'a Plane,
src_u: &'a Plane,
src_v: &'a Plane,
rec_y: Plane,
rec_u: Plane,
rec_v: Plane,
mbw: usize,
q: Quant,
lambda: f64,
probs: Probs,
above_nz: Vec<Nz>,
left_nz: Nz,
above_bmodes: Vec<[u8; 4]>,
left_bmodes: [u8; 4],
}
/// A candidate luma coding of a macroblock.
struct Luma {
mode: u8,
bmodes: [u8; 16],
y2: [i16; 16],
y: [[i16; 16]; 16],
recon: [u8; 256],
sse: u64,
bits: f32,
}
/// A candidate chroma coding.
struct Chroma {
mode: u8,
u: [[i16; 16]; 4],
v: [[i16; 16]; 4],
recon_u: [u8; 64],
recon_v: [u8; 64],
sse: u64,
bits: f32,
}
/// `v` clamped to a sample.
fn clamp255(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
impl Encoder<'_> {
/// The rate-distortion cost: the squared error plus λ times the bits.
fn cost_of(&self, sse: u64, bits: f32) -> f64 {
sse as f64 + self.lambda * f64::from(bits)
}
/// The 16×16 luma edges, and the four pixels above and to the right (§12.3).
fn luma_edges(&self, mbx: usize, mby: usize) -> (Edges<16>, [u8; 4]) {
let (x0, y0) = (mbx * 16, mby * 16);
let rec = &self.rec_y;
let above: [u8; 16] = std::array::from_fn(|i| if mby == 0 { 127 } else { rec.get(x0 + i, y0 - 1) });
let left: [u8; 16] = std::array::from_fn(|i| if mbx == 0 { 129 } else { rec.get(x0 - 1, y0 + i) });
let corner = if mby == 0 {
127
} else if mbx == 0 {
129
} else {
rec.get(x0 - 1, y0 - 1)
};
let right: [u8; 4] = std::array::from_fn(|i| {
if mby == 0 {
127
} else if mbx + 1 == self.mbw {
rec.get(x0 + 15, y0 - 1)
} else {
rec.get(x0 + 16 + i, y0 - 1)
}
});
(Edges { above, left, corner, have_above: mby > 0, have_left: mbx > 0 }, right)
}
/// The reconstructed edges an 8×8 chroma block predicts from, with the spec's 127 / 129 outside the frame.
fn chroma_edges(rec: &Plane, mbx: usize, mby: usize) -> Edges<8> {
let (x0, y0) = (mbx * 8, mby * 8);
let above: [u8; 8] = std::array::from_fn(|i| if mby == 0 { 127 } else { rec.get(x0 + i, y0 - 1) });
let left: [u8; 8] = std::array::from_fn(|i| if mbx == 0 { 129 } else { rec.get(x0 - 1, y0 + i) });
let corner = if mby == 0 {
127
} else if mbx == 0 {
129
} else {
rec.get(x0 - 1, y0 - 1)
};
Edges { above, left, corner, have_above: mby > 0, have_left: mbx > 0 }
}
/// A 4×4 block of a plane minus a prediction.
fn residual(src: &Plane, x0: usize, y0: usize, pred: &[u8], stride: usize) -> [i32; 16] {
std::array::from_fn(|i| {
let (r, c) = (i / 4, i % 4);
i32::from(src.get(x0 + c, y0 + r)) - i32::from(pred[r * stride + c])
})
}
/// Code the macroblock's luma with one 16×16 prediction `mode`: its residual through Y2 and its cost.
fn whole_luma(&self, mbx: usize, mby: usize, mode: u8, e: &Edges<16>) -> Luma {
let (x0, y0) = (mbx * 16, mby * 16);
let mut pred = [0u8; 256];
predict_block(mode, e, &mut pred);
let coefs: [[f64; 16]; 16] = std::array::from_fn(|b| {
let (r, c) = (b / 4, b % 4);
fdct(&Self::residual(self.src_y, x0 + c * 4, y0 + r * 4, &pred[r * 64 + c * 4..], 16))
});
let dc: [f64; 16] = std::array::from_fn(|b| coefs[b][0]);
let y2c = fwht(&dc);
let y2: [i16; 16] = std::array::from_fn(|i| quantize(y2c[i], self.q.y2[usize::from(i > 0)], i == 0));
let y: [[i16; 16]; 16] =
std::array::from_fn(|b| std::array::from_fn(|i| if i == 0 { 0 } else { quantize(coefs[b][i], self.q.y1[1], false) }));
let y2dq: [i16; 16] = std::array::from_fn(|i| dequant(y2[i], self.q.y2[usize::from(i > 0)]));
let dcs = iwht(&y2dq);
let mut recon = [0u8; 256];
let mut sse = 0u64;
let (mut ly, mut ay) = (self.left_nz.y, self.above_nz.get(mbx).copied().unwrap_or_default().y);
let up = self.above_nz.get(mbx).copied().unwrap_or_default();
let mut bits = tree_cost(&KF_YMODE_TREE, &KF_YMODE_PROB, mode)
+ block_cost(&self.probs, &y2, TYPE_Y2, 0, usize::from(self.left_nz.y2) + usize::from(up.y2));
for b in 0..16 {
let (r, c) = (b / 4, b % 4);
let mut d: [i16; 16] = std::array::from_fn(|i| dequant(y[b][i], self.q.y1[1]));
d[0] = dcs[b];
let res = idct(&d);
for i in 0..16 {
let (rr, cc) = (r * 4 + i / 4, c * 4 + i % 4);
let v = clamp255(i32::from(pred[rr * 16 + cc]) + i32::from(res[i]));
recon[rr * 16 + cc] = v;
let diff = i64::from(v) - i64::from(self.src_y.get(x0 + cc, y0 + rr));
sse += (diff * diff) as u64;
}
bits += block_cost(&self.probs, &y[b], TYPE_Y_AFTER_Y2, 1, usize::from(ly[r]) + usize::from(ay[c]));
let nz = any_coded(&y[b], 1);
ly[r] = nz;
ay[c] = nz;
}
Luma { mode, bmodes: [implied_bmode(mode); 16], y2, y, recon, sse, bits }
}
/// B_PRED: each subblock with its best mode, in order, each predicted from the ones before.
/// Gives up (None) once it costs more than `limit`.
fn sub_luma(&self, mbx: usize, mby: usize, e: &Edges<16>, right: &[u8; 4], limit: f64) -> Option<Luma> {
let (x0, y0) = (mbx * 16, mby * 16);
let mut wr = [0u8; 256];
let mut bm = [0u8; 16];
let mut y = [[0i16; 16]; 16];
let (mut ly, mut ay) = (self.left_nz.y, self.above_nz.get(mbx).copied().unwrap_or_default().y);
let above_modes = self.above_bmodes.get(mbx).copied().unwrap_or([B_DC_PRED; 4]);
let mut bits = tree_cost(&KF_YMODE_TREE, &KF_YMODE_PROB, B_PRED);
let mut sse = 0u64;
for s in 0..16 {
let (r, c) = (s / 4, s % 4);
let a: [u8; 8] = std::array::from_fn(|i| {
if i < 4 {
if r == 0 { e.above[c * 4 + i] } else { wr[(r * 4 - 1) * 16 + c * 4 + i] }
} else if c == 3 {
right[i - 4]
} else if r == 0 {
e.above[c * 4 + i]
} else {
wr[(r * 4 - 1) * 16 + c * 4 + i]
}
});
let l: [u8; 4] = std::array::from_fn(|i| if c == 0 { e.left[r * 4 + i] } else { wr[(r * 4 + i) * 16 + c * 4 - 1] });
let p = match (r, c) {
(0, 0) => e.corner,
(0, _) => e.above[c * 4 - 1],
(_, 0) => e.left[r * 4 - 1],
_ => wr[(r * 4 - 1) * 16 + c * 4 - 1],
};
let am = if r == 0 { above_modes[c] } else { bm[s - 4] };
let lm = if c == 0 { self.left_bmodes[r] } else { bm[s - 1] };
let mode_probs = &KF_BMODE_PROB[usize::from(am)][usize::from(lm)];
let ctx = usize::from(ly[r]) + usize::from(ay[c]);
let mut best: Option<BestSub> = None;
for m in 0..10u8 {
let pred = predict_sub(m, &a, &l, p);
let coefs = fdct(&Self::residual(self.src_y, x0 + c * 4, y0 + r * 4, &pred, 4));
let lv: [i16; 16] = std::array::from_fn(|i| quantize(coefs[i], self.q.y1[usize::from(i > 0)], i == 0));
let d: [i16; 16] = std::array::from_fn(|i| dequant(lv[i], self.q.y1[usize::from(i > 0)]));
let res = idct(&d);
let mut out = [0u8; 16];
let mut e2 = 0u64;
for i in 0..16 {
out[i] = clamp255(i32::from(pred[i]) + i32::from(res[i]));
let diff = i64::from(out[i]) - i64::from(self.src_y.get(x0 + c * 4 + i % 4, y0 + r * 4 + i / 4));
e2 += (diff * diff) as u64;
}
let b = tree_cost(&BMODE_TREE, mode_probs, m) + block_cost(&self.probs, &lv, TYPE_Y_WITH_DC, 0, ctx);
let j = self.cost_of(e2, b);
if best.as_ref().is_none_or(|bst| j < bst.0) {
best = Some((j, m, lv, out, e2, b));
}
}
let (_, m, lv, out, e2, b) = best?;
for i in 0..16 {
wr[(r * 4 + i / 4) * 16 + c * 4 + i % 4] = out[i];
}
bm[s] = m;
y[s] = lv;
sse += e2;
bits += b;
let nz = any_coded(&lv, 0);
ly[r] = nz;
ay[c] = nz;
if self.cost_of(sse, bits) > limit {
return None;
}
}
Some(Luma { mode: B_PRED, bmodes: bm, y2: [0; 16], y, recon: wr, sse, bits })
}
/// Code the macroblock's U and V with prediction `mode`: their levels, reconstruction and cost.
fn chroma(&self, mbx: usize, mby: usize, mode: u8) -> Chroma {
let (x0, y0) = (mbx * 8, mby * 8);
let up = self.above_nz.get(mbx).copied().unwrap_or_default();
let mut bits = tree_cost(&UV_MODE_TREE, &KF_UV_MODE_PROB, mode);
let mut sse = 0u64;
let mut plane = |src: &Plane, rec: &Plane, lnz: [bool; 2], anz: [bool; 2]| -> ([[i16; 16]; 4], [u8; 64]) {
let e = Self::chroma_edges(rec, mbx, mby);
let mut pred = [0u8; 64];
predict_block(mode, &e, &mut pred);
let mut levels = [[0i16; 16]; 4];
let mut recon = [0u8; 64];
let (mut l, mut a) = (lnz, anz);
for b in 0..4 {
let (r, c) = (b / 2, b % 2);
let coefs = fdct(&Self::residual(src, x0 + c * 4, y0 + r * 4, &pred[r * 32 + c * 4..], 8));
let lv: [i16; 16] = std::array::from_fn(|i| quantize(coefs[i], self.q.uv[usize::from(i > 0)], i == 0));
let d: [i16; 16] = std::array::from_fn(|i| dequant(lv[i], self.q.uv[usize::from(i > 0)]));
let res = idct(&d);
for i in 0..16 {
let (rr, cc) = (r * 4 + i / 4, c * 4 + i % 4);
let v = clamp255(i32::from(pred[rr * 8 + cc]) + i32::from(res[i]));
recon[rr * 8 + cc] = v;
let diff = i64::from(v) - i64::from(src.get(x0 + cc, y0 + rr));
sse += (diff * diff) as u64;
}
bits += block_cost(&self.probs, &lv, TYPE_UV, 0, usize::from(l[r]) + usize::from(a[c]));
let nz = any_coded(&lv, 0);
l[r] = nz;
a[c] = nz;
levels[b] = lv;
}
(levels, recon)
};
let (u, recon_u) = plane(self.src_u, &self.rec_u, self.left_nz.u, up.u);
let (v, recon_v) = plane(self.src_v, &self.rec_v, self.left_nz.v, up.v);
Chroma { mode, u, v, recon_u, recon_v, sse, bits }
}
/// Chooses, codes and reconstructs macroblock (mbx, mby).
fn macroblock(&mut self, mbx: usize, mby: usize) -> Mb {
let (e, right) = self.luma_edges(mbx, mby);
let mut luma: Option<Luma> = None;
for mode in [DC_PRED, V_PRED, H_PRED, TM_PRED] {
let cand = self.whole_luma(mbx, mby, mode, &e);
if luma.as_ref().is_none_or(|l| self.cost_of(cand.sse, cand.bits) < self.cost_of(l.sse, l.bits)) {
luma = Some(cand);
}
}
let limit = luma.as_ref().map_or(f64::INFINITY, |l| self.cost_of(l.sse, l.bits));
if let Some(sub) = self.sub_luma(mbx, mby, &e, &right, limit) {
luma = Some(sub);
}
let mut chroma: Option<Chroma> = None;
for mode in [DC_PRED, V_PRED, H_PRED, TM_PRED] {
let cand = self.chroma(mbx, mby, mode);
if chroma.as_ref().is_none_or(|c| self.cost_of(cand.sse, cand.bits) < self.cost_of(c.sse, c.bits)) {
chroma = Some(cand);
}
}
let luma = luma.unwrap_or_else(|| self.whole_luma(mbx, mby, DC_PRED, &e));
let chroma = chroma.unwrap_or_else(|| self.chroma(mbx, mby, DC_PRED));
// The reconstruction later macroblocks are predicted from.
for i in 0..256 {
self.rec_y.set(mbx * 16 + i % 16, mby * 16 + i / 16, luma.recon[i]);
}
for i in 0..64 {
self.rec_u.set(mbx * 8 + i % 8, mby * 8 + i / 8, chroma.recon_u[i]);
self.rec_v.set(mbx * 8 + i % 8, mby * 8 + i / 8, chroma.recon_v[i]);
}
// Contexts, as the token pass will keep them.
let has_y2 = luma.mode != B_PRED;
let first = usize::from(has_y2);
let up = self.above_nz.get(mbx).copied().unwrap_or_default();
let mut nz = Nz { y2: if has_y2 { any_coded(&luma.y2, 0) } else { up.y2 }, ..Nz::default() };
let mut left = self.left_nz;
if has_y2 {
left.y2 = nz.y2;
}
for b in 0..16 {
let coded = any_coded(&luma.y[b], first);
left.y[b / 4] = coded;
nz.y[b % 4] = coded;
}
for b in 0..4 {
let cu = any_coded(&chroma.u[b], 0);
let cv = any_coded(&chroma.v[b], 0);
left.u[b / 2] = cu;
nz.u[b % 2] = cu;
left.v[b / 2] = cv;
nz.v[b % 2] = cv;
}
if !has_y2 {
left.y2 = self.left_nz.y2;
}
self.left_nz = left;
if let Some(a) = self.above_nz.get_mut(mbx) {
*a = nz;
}
if let Some(a) = self.above_bmodes.get_mut(mbx) {
*a = [luma.bmodes[12], luma.bmodes[13], luma.bmodes[14], luma.bmodes[15]];
}
self.left_bmodes = [luma.bmodes[3], luma.bmodes[7], luma.bmodes[11], luma.bmodes[15]];
let skip = (!has_y2 || !any_coded(&luma.y2, 0))
&& luma.y.iter().all(|b| !any_coded(b, first))
&& chroma.u.iter().chain(chroma.v.iter()).all(|b| !any_coded(b, 0));
Mb { ymode: luma.mode, bmodes: luma.bmodes, uvmode: chroma.mode, skip, y2: luma.y2, y: luma.y, u: chroma.u, v: chroma.v }
}
}
/// Encodes planes already at the coded size (whole macroblocks).
pub(crate) fn encode(y: &Plane, u: &Plane, v: &Plane, width: u32, height: u32, params: &Vp8Params) -> Result<Vp8Frame, Vp8Error> {
let (mbw, mbh) = (y.width / 16, y.height / 16);
let q = q_index(params.quality);
let quant = Quant::new(q);
let lf = params.loop_filter.unwrap_or_else(|| ((f64::from(q) * 0.4).round() as u8).min(63)).min(63);
let mut enc = Encoder {
src_y: y,
src_u: u,
src_v: v,
rec_y: Plane::new(y.width, y.height),
rec_u: Plane::new(u.width, u.height),
rec_v: Plane::new(v.width, v.height),
mbw,
q: quant,
lambda: LAMBDA_PER_STEP2 * f64::from(quant.y1[1]).powi(2),
probs: DEFAULT_COEFF_PROBS,
above_nz: vec![Nz::default(); mbw],
left_nz: Nz::default(),
above_bmodes: vec![[B_DC_PRED; 4]; mbw],
left_bmodes: [B_DC_PRED; 4],
};
let mut mbs = Vec::with_capacity(mbw * mbh);
for mby in 0..mbh {
enc.left_nz = Nz::default();
enc.left_bmodes = [B_DC_PRED; 4];
for mbx in 0..mbw {
mbs.push(enc.macroblock(mbx, mby));
}
}
// Token counts → probability updates.
let mut counts = CountSink(Box::new([[[[[0; 2]; 11]; 3]; 8]; 4]));
tokens(&mut counts, &DEFAULT_COEFF_PROBS, &mbs, mbw);
let probs = updated_probs(&counts);
// First partition: the frame header (Annex A §19.2) and the macroblock headers (§19.3).
let skipped = mbs.iter().filter(|m| m.skip).count();
let use_skip = skipped > 0;
let prob_skip = (((mbs.len() - skipped) * 256 + mbs.len() / 2) / mbs.len().max(1)).clamp(1, 255) as u8;
let mut w = BoolWriter::new();
w.literal(1, 0); // color_space: BT.601
w.literal(1, 0); // clamping_type: the decoder clamps
w.literal(1, 0); // segmentation_enabled
w.literal(1, 0); // filter_type: normal
w.literal(6, u32::from(lf));
w.literal(3, 0); // sharpness_level
w.literal(1, 0); // loop_filter_adj_enable
w.literal(2, 0); // log2_nbr_of_dct_partitions: one
w.literal(7, u32::from(q));
for _ in 0..5 {
w.literal(1, 0); // no quantizer index deltas
}
w.literal(1, 0); // refresh_entropy_probs
for t in 0..4 {
for b in 0..8 {
for c in 0..3 {
for n in 0..11 {
let new = probs[t][b][c][n];
let changed = new != DEFAULT_COEFF_PROBS[t][b][c][n];
w.put(COEFF_UPDATE_PROBS[t][b][c][n], changed);
if changed {
w.literal(8, u32::from(new));
}
}
}
}
}
w.literal(1, u32::from(use_skip));
if use_skip {
w.literal(8, u32::from(prob_skip));
}
let mut above_modes = vec![[B_DC_PRED; 4]; mbw];
let mut left_modes = [B_DC_PRED; 4];
for (i, mb) in mbs.iter().enumerate() {
let x = i % mbw;
if x == 0 {
left_modes = [B_DC_PRED; 4];
}
if use_skip {
w.put(prob_skip, mb.skip);
}
put_tree(&mut w, &KF_YMODE_TREE, &KF_YMODE_PROB, mb.ymode);
let up = above_modes.get(x).copied().unwrap_or([B_DC_PRED; 4]);
if mb.ymode == B_PRED {
for s in 0..16 {
let (r, c) = (s / 4, s % 4);
let am = if r == 0 { up[c] } else { mb.bmodes[s - 4] };
let lm = if c == 0 { left_modes[r] } else { mb.bmodes[s - 1] };
put_tree(&mut w, &BMODE_TREE, &KF_BMODE_PROB[usize::from(am)][usize::from(lm)], mb.bmodes[s]);
}
}
put_tree(&mut w, &UV_MODE_TREE, &KF_UV_MODE_PROB, mb.uvmode);
if let Some(a) = above_modes.get_mut(x) {
*a = [mb.bmodes[12], mb.bmodes[13], mb.bmodes[14], mb.bmodes[15]];
}
left_modes = [mb.bmodes[3], mb.bmodes[7], mb.bmodes[11], mb.bmodes[15]];
}
let first = w.finish();
if first.len() >= 1 << 19 {
return Err(Vp8Error::PartitionTooLarge(first.len()));
}
let mut t = BoolWriter::new();
tokens(&mut t, &probs, &mbs, mbw);
let second = t.finish();
// Frame tag (key frame, version 0, shown) and the key frame start code and size (§9.1).
let tag = (1u32 << 4) | ((first.len() as u32) << 5);
let mut data = Vec::with_capacity(10 + first.len() + second.len());
data.extend_from_slice(&tag.to_le_bytes()[..3]);
data.extend_from_slice(&[0x9d, 0x01, 0x2a]);
data.extend_from_slice(&((width & 0x3fff) as u16).to_le_bytes());
data.extend_from_slice(&((height & 0x3fff) as u16).to_le_bytes());
data.extend_from_slice(&first);
data.extend_from_slice(&second);
Ok(Vp8Frame { data, y: enc.rec_y, u: enc.rec_u, v: enc.rec_v, q_index: q, loop_filter: lf })
}
+106
View File
@@ -0,0 +1,106 @@
//! A VP8 key-frame encoder: the lossy picture of a WebP image or animation frame, written from
//! RFC 6386 (*VP8 Data Format and Decoding Guide*, November 2011) by mirroring its decoding
//! process (see README.md).
//!
//! * Every macroblock is predicted with the best of the four 16×16 luma modes (with the Y2
//! Walsh–Hadamard block of DCs) or B_PRED (each 4×4 subblock with the best of its ten modes),
//! and the best of the four 8×8 chroma modes, chosen by rate–distortion cost: squared error
//! plus λ times the bits, the bits counted from the token and mode probabilities.
//! * Coefficients are quantized from one quality setting (0–100 → the frame's quantizer index);
//! macroblocks left with nothing to code are skipped.
//! * The coefficient probabilities are updated per frame where the frame's own token counts pay
//! for the update (§13.4).
//! * The loop filter level is chosen from the quantizer (it shapes only the decoded picture: intra
//! prediction reads the unfiltered reconstruction, §12).
//!
//! The encoder reconstructs exactly what a decoder reconstructs before loop filtering — the
//! prediction, the 16-bit inverse transforms of §14 — and returns it with the frame.
mod bool;
mod frame;
mod predict;
mod tables;
mod transform;
mod yuv;
pub use frame::Plane;
/// Largest width or height: the key frame header stores 14 bits.
pub const MAX_SIDE: u32 = (1 << 14) - 1;
/// What to encode with.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Vp8Params {
/// 0 (smallest) to 100 (best).
pub quality: u8,
/// The loop filter level (0–63, 0 = off); `None` chooses one from the quantizer.
pub loop_filter: Option<u8>,
}
impl Default for Vp8Params {
/// Quality 90, the loop filter chosen from the quantizer.
fn default() -> Self {
Vp8Params { quality: 90, loop_filter: None }
}
}
/// An encoded key frame.
#[derive(Clone, Debug)]
pub struct Vp8Frame {
/// The VP8 frame: frame tag, key frame header, first partition, token partition — what a
/// WebP `VP8 ` chunk holds.
pub data: Vec<u8>,
/// What a decoder reconstructs before loop filtering, at the coded (macroblock-padded) size.
pub y: Plane,
pub u: Plane,
pub v: Plane,
/// The quantizer index used (0–127).
pub q_index: u8,
/// The loop filter level signalled.
pub loop_filter: u8,
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum Vp8Error {
#[error("VP8 frames are 1 to {MAX_SIDE} pixels a side (this one is {0}×{1})")]
Size(u32, u32),
#[error("{0} bytes of RGBA for a {1}×{2} frame")]
Buffer(usize, u32, u32),
#[error("the first partition is {0} bytes, over the 19-bit limit")]
PartitionTooLarge(usize),
}
/// Encodes straight (unpremultiplied) 8-bit RGBA as one VP8 key frame. Alpha is not part of a
/// VP8 frame (a WebP file carries it beside the frame); it weights the colour of the chroma
/// samples, so fully transparent pixels do not bleed into the edges of what is visible.
pub fn encode_rgba(rgba: &[u8], width: u32, height: u32, params: &Vp8Params) -> Result<Vp8Frame, Vp8Error> {
if width == 0 || height == 0 || width > MAX_SIDE || height > MAX_SIDE {
return Err(Vp8Error::Size(width, height));
}
let n = (width as usize).checked_mul(height as usize).and_then(|n| n.checked_mul(4));
if n != Some(rgba.len()) {
return Err(Vp8Error::Buffer(rgba.len(), width, height));
}
let (y, u, v) = yuv::rgba_to_yuv420(rgba, width, height);
frame::encode(&y, &u, &v, width, height, params)
}
/// Quality → quantizer index, interpolated between these points. Fitted on rendered motion
/// graphics frames so that a quality gives about the PSNR (within half a decibel) and size the same
/// quality gives in the widely used libwebp encoder; steep at the top, where each step of quality
/// costs the most bytes.
const QUALITY_TO_Q: [(u8, u8); 13] =
[(0, 105), (10, 76), (20, 66), (30, 58), (40, 47), (50, 42), (60, 36), (70, 31), (80, 22), (85, 15), (90, 7), (95, 2), (100, 0)];
/// The quantizer index for a quality: 100 is index 0 (the finest steps).
pub fn q_index(quality: u8) -> u8 {
let q = quality.min(100);
for pair in QUALITY_TO_Q.windows(2) {
let [(q0, i0), (q1, i1)] = [pair[0], pair[1]];
if q <= q1 {
let t = f64::from(q - q0) / f64::from(q1 - q0);
return (f64::from(i0) + t * (f64::from(i1) - f64::from(i0))).round() as u8;
}
}
0
}
+278
View File
@@ -0,0 +1,278 @@
//! Intra prediction, RFC 6386 §12: the whole-block modes for 16×16 luma and 8×8 chroma, and the
//! ten 4×4 subblock modes.
/// Whole-block modes, numbered as `intra_mbmode` (§11.2).
pub(crate) const DC_PRED: u8 = 0;
pub(crate) const V_PRED: u8 = 1;
pub(crate) const H_PRED: u8 = 2;
pub(crate) const TM_PRED: u8 = 3;
pub(crate) const B_PRED: u8 = 4;
/// Subblock modes, numbered as `intra_bmode` (§11.2).
pub(crate) const B_DC_PRED: u8 = 0;
pub(crate) const B_TM_PRED: u8 = 1;
pub(crate) const B_VE_PRED: u8 = 2;
pub(crate) const B_HE_PRED: u8 = 3;
pub(crate) const B_LD_PRED: u8 = 4;
pub(crate) const B_RD_PRED: u8 = 5;
pub(crate) const B_VR_PRED: u8 = 6;
pub(crate) const B_VL_PRED: u8 = 7;
pub(crate) const B_HD_PRED: u8 = 8;
pub(crate) const B_HU_PRED: u8 = 9;
/// The pixels a block is predicted from, with the frame-edge values of §12 already in place:
/// `above` (and, for 16×16 luma, the four above-right pixels after it), `left`, the corner above
/// and to the left, and whether the real neighbours exist (for DC prediction's averages).
pub(crate) struct Edges<const N: usize> {
pub above: [u8; N],
pub left: [u8; N],
pub corner: u8,
pub have_above: bool,
pub have_left: bool,
}
/// Predicts an N×N whole block (N = 16 luma, 8 chroma) in `mode` (DC, V, H or TM).
pub(crate) fn predict_block<const N: usize>(mode: u8, e: &Edges<N>, out: &mut [u8]) {
match mode {
V_PRED => {
for r in 0..N {
out[r * N..r * N + N].copy_from_slice(&e.above);
}
}
H_PRED => {
for r in 0..N {
out[r * N..r * N + N].fill(e.left[r]);
}
}
TM_PRED => {
for r in 0..N {
for c in 0..N {
out[r * N + c] = clamp(i32::from(e.left[r]) + i32::from(e.above[c]) - i32::from(e.corner));
}
}
}
_ => {
let shift = N.trailing_zeros();
let sum_a: u32 = e.above.iter().map(|&v| u32::from(v)).sum();
let sum_l: u32 = e.left.iter().map(|&v| u32::from(v)).sum();
let dc = match (e.have_above, e.have_left) {
(true, true) => (sum_a + sum_l + (1 << shift)) >> (shift + 1),
(true, false) => (sum_a + (1 << (shift - 1))) >> shift,
(false, true) => (sum_l + (1 << (shift - 1))) >> shift,
(false, false) => 128,
};
out[..N * N].fill(dc as u8);
}
}
}
/// `v` clamped to a sample.
fn clamp(v: i32) -> u8 {
v.clamp(0, 255) as u8
}
/// The weighted average of three neighbours, `(x + 2y + z + 2) / 4`.
fn avg3(x: u8, y: u8, z: u8) -> u8 {
((u32::from(x) + 2 * u32::from(y) + u32::from(z) + 2) >> 2) as u8
}
/// The average of two neighbours, rounded up.
fn avg2(x: u8, y: u8) -> u8 {
((u32::from(x) + u32::from(y) + 1) >> 1) as u8
}
/// Predicts a 4×4 subblock in `mode` from `a` (the 8 pixels above: 4 over the block, then 4 above
/// and to its right), `l` (the 4 to its left) and `p` (above-left), as §12.3's
/// `subblock_intra_predict`.
pub(crate) fn predict_sub(mode: u8, a: &[u8; 8], l: &[u8; 4], p: u8) -> [u8; 16] {
let mut b = [0u8; 16];
let mut set = |r: usize, c: usize, v: u8| b[r * 4 + c] = v;
// E: left column bottom to top, the corner, then the row above.
let e = [l[3], l[2], l[1], l[0], p, a[0], a[1], a[2], a[3]];
// avg3p(A + j) for j = -1..=6 (A[-1] is the corner).
let at = |j: i32| if j < 0 { p } else { a[j as usize] };
let a3 = |j: i32| avg3(at(j - 1), at(j), at(j + 1));
let e3 = |j: usize| avg3(e[j - 1], e[j], e[j + 1]);
let e2 = |j: usize| avg2(e[j], e[j + 1]);
match mode {
B_TM_PRED => {
for r in 0..4 {
for c in 0..4 {
set(r, c, clamp(i32::from(l[r]) + i32::from(a[c]) - i32::from(p)));
}
}
}
B_VE_PRED => {
for c in 0..4 {
let v = a3(c as i32);
for r in 0..4 {
set(r, c, v);
}
}
}
B_HE_PRED => {
let rows = [avg3(p, l[0], l[1]), avg3(l[0], l[1], l[2]), avg3(l[1], l[2], l[3]), avg3(l[2], l[3], l[3])];
for (r, v) in rows.into_iter().enumerate() {
for c in 0..4 {
set(r, c, v);
}
}
}
B_LD_PRED => {
for r in 0..4 {
for c in 0..4 {
let d = r + c;
set(r, c, if d == 6 { avg3(a[6], a[7], a[7]) } else { a3(d as i32 + 1) });
}
}
}
B_RD_PRED => {
for r in 0..4 {
for c in 0..4 {
// (3,0) is E+1 ... (0,3) is E+7.
set(r, c, e3(4 + c - r));
}
}
}
B_VR_PRED => {
set(3, 0, e3(2));
set(2, 0, e3(3));
let v = e3(4);
set(3, 1, v);
set(1, 0, v);
let v = e2(4);
set(2, 1, v);
set(0, 0, v);
let v = e3(5);
set(3, 2, v);
set(1, 1, v);
let v = e2(5);
set(2, 2, v);
set(0, 1, v);
let v = e3(6);
set(3, 3, v);
set(1, 2, v);
let v = e2(6);
set(2, 3, v);
set(0, 2, v);
set(1, 3, e3(7));
set(0, 3, e2(7));
}
B_VL_PRED => {
let a2 = |j: usize| avg2(a[j], a[j + 1]);
set(0, 0, a2(0));
set(1, 0, a3(1));
let v = a2(1);
set(2, 0, v);
set(0, 1, v);
let v = a3(2);
set(1, 1, v);
set(3, 0, v);
let v = a2(2);
set(2, 1, v);
set(0, 2, v);
let v = a3(3);
set(3, 1, v);
set(1, 2, v);
let v = a2(3);
set(2, 2, v);
set(0, 3, v);
let v = a3(4);
set(3, 2, v);
set(1, 3, v);
set(2, 3, a3(5));
set(3, 3, a3(6));
}
B_HD_PRED => {
set(3, 0, e2(0));
set(3, 1, e3(1));
let v = e2(1);
set(2, 0, v);
set(3, 2, v);
let v = e3(2);
set(2, 1, v);
set(3, 3, v);
let v = e2(2);
set(2, 2, v);
set(1, 0, v);
let v = e3(3);
set(2, 3, v);
set(1, 1, v);
let v = e2(3);
set(1, 2, v);
set(0, 0, v);
let v = e3(4);
set(1, 3, v);
set(0, 1, v);
set(0, 2, e3(5));
set(0, 3, e3(6));
}
B_HU_PRED => {
set(0, 0, avg2(l[0], l[1]));
set(0, 1, avg3(l[0], l[1], l[2]));
let v = avg2(l[1], l[2]);
set(0, 2, v);
set(1, 0, v);
let v = avg3(l[1], l[2], l[3]);
set(0, 3, v);
set(1, 1, v);
let v = avg2(l[2], l[3]);
set(1, 2, v);
set(2, 0, v);
let v = avg3(l[2], l[3], l[3]);
set(1, 3, v);
set(2, 1, v);
for (r, c) in [(2, 2), (2, 3), (3, 0), (3, 1), (3, 2), (3, 3)] {
set(r, c, l[3]);
}
}
_ => {
// B_DC_PRED
let sum: u32 = a[..4].iter().chain(l.iter()).map(|&v| u32::from(v)).sum();
b.fill(((sum + 4) >> 3) as u8);
}
}
b
}
#[cfg(test)]
mod tests {
use super::*;
/// V, TM and DC predictions of a whole block, and DC with an edge missing.
#[test]
fn whole_block_modes() {
let e = Edges::<8> { above: [10, 20, 30, 40, 50, 60, 70, 80], left: [5; 8], corner: 10, have_above: true, have_left: true };
let mut out = [0u8; 64];
predict_block(V_PRED, &e, &mut out);
assert_eq!(&out[56..64], &e.above);
predict_block(TM_PRED, &e, &mut out);
assert_eq!(out[0], 5, "5 + 10 - 10");
assert_eq!(out[7], 75);
predict_block(DC_PRED, &e, &mut out);
assert_eq!(out[0], ((360 + 40 + 8) >> 4) as u8);
let top = Edges::<8> { have_above: false, ..e };
predict_block(DC_PRED, &top, &mut out);
assert_eq!(out[0], 5, "left only");
}
/// Each subblock mode follows the direction of its lines.
#[test]
fn subblock_modes_follow_their_lines() {
let a = [10, 20, 30, 40, 50, 60, 70, 80];
let l = [15, 25, 35, 45];
let p = 5;
let ld = predict_sub(B_LD_PRED, &a, &l, p);
// Each antidiagonal is one value.
assert_eq!(ld[1], ld[4]);
assert_eq!(ld[3], ld[12]);
assert_eq!(ld[15], avg3(70, 80, 80));
let rd = predict_sub(B_RD_PRED, &a, &l, p);
assert_eq!(rd[0], rd[5]);
assert_eq!(rd[0], avg3(15, 5, 10));
let hu = predict_sub(B_HU_PRED, &a, &l, p);
assert_eq!(hu[15], 45);
let dc = predict_sub(B_DC_PRED, &a, &l, p);
assert_eq!(dc[0], ((100 + 120 + 4) >> 3) as u8);
}
}
+510
View File
@@ -0,0 +1,510 @@
//! Constant tables of the VP8 key-frame syntax, as RFC 6386 (November 2011) gives them.
//!
//! The multi-dimensional tables are generated from the RFC's own text (sections 11.5, 13.4,
//! 13.5 and 14.1) with its comments taken out, not typed.
/// Probabilities of the key-frame luma mode tree (§11.2).
pub(crate) const KF_YMODE_PROB: [u8; 4] = [145, 156, 163, 128];
/// Probabilities of the key-frame chroma mode tree (§11.4).
pub(crate) const KF_UV_MODE_PROB: [u8; 3] = [142, 114, 183];
/// Subblock mode probabilities by the modes above and to the left (§11.5).
pub(crate) const KF_BMODE_PROB: [[[u8; 9]; 10]; 10] = [
[
[231, 120, 48, 89, 115, 113, 120, 152, 112],
[152, 179, 64, 126, 170, 118, 46, 70, 95],
[175, 69, 143, 80, 85, 82, 72, 155, 103],
[56, 58, 10, 171, 218, 189, 17, 13, 152],
[144, 71, 10, 38, 171, 213, 144, 34, 26],
[114, 26, 17, 163, 44, 195, 21, 10, 173],
[121, 24, 80, 195, 26, 62, 44, 64, 85],
[170, 46, 55, 19, 136, 160, 33, 206, 71],
[63, 20, 8, 114, 114, 208, 12, 9, 226],
[81, 40, 11, 96, 182, 84, 29, 16, 36],
],
[
[134, 183, 89, 137, 98, 101, 106, 165, 148],
[72, 187, 100, 130, 157, 111, 32, 75, 80],
[66, 102, 167, 99, 74, 62, 40, 234, 128],
[41, 53, 9, 178, 241, 141, 26, 8, 107],
[104, 79, 12, 27, 217, 255, 87, 17, 7],
[74, 43, 26, 146, 73, 166, 49, 23, 157],
[65, 38, 105, 160, 51, 52, 31, 115, 128],
[87, 68, 71, 44, 114, 51, 15, 186, 23],
[47, 41, 14, 110, 182, 183, 21, 17, 194],
[66, 45, 25, 102, 197, 189, 23, 18, 22],
],
[
[88, 88, 147, 150, 42, 46, 45, 196, 205],
[43, 97, 183, 117, 85, 38, 35, 179, 61],
[39, 53, 200, 87, 26, 21, 43, 232, 171],
[56, 34, 51, 104, 114, 102, 29, 93, 77],
[107, 54, 32, 26, 51, 1, 81, 43, 31],
[39, 28, 85, 171, 58, 165, 90, 98, 64],
[34, 22, 116, 206, 23, 34, 43, 166, 73],
[68, 25, 106, 22, 64, 171, 36, 225, 114],
[34, 19, 21, 102, 132, 188, 16, 76, 124],
[62, 18, 78, 95, 85, 57, 50, 48, 51],
],
[
[193, 101, 35, 159, 215, 111, 89, 46, 111],
[60, 148, 31, 172, 219, 228, 21, 18, 111],
[112, 113, 77, 85, 179, 255, 38, 120, 114],
[40, 42, 1, 196, 245, 209, 10, 25, 109],
[100, 80, 8, 43, 154, 1, 51, 26, 71],
[88, 43, 29, 140, 166, 213, 37, 43, 154],
[61, 63, 30, 155, 67, 45, 68, 1, 209],
[142, 78, 78, 16, 255, 128, 34, 197, 171],
[41, 40, 5, 102, 211, 183, 4, 1, 221],
[51, 50, 17, 168, 209, 192, 23, 25, 82],
],
[
[125, 98, 42, 88, 104, 85, 117, 175, 82],
[95, 84, 53, 89, 128, 100, 113, 101, 45],
[75, 79, 123, 47, 51, 128, 81, 171, 1],
[57, 17, 5, 71, 102, 57, 53, 41, 49],
[115, 21, 2, 10, 102, 255, 166, 23, 6],
[38, 33, 13, 121, 57, 73, 26, 1, 85],
[41, 10, 67, 138, 77, 110, 90, 47, 114],
[101, 29, 16, 10, 85, 128, 101, 196, 26],
[57, 18, 10, 102, 102, 213, 34, 20, 43],
[117, 20, 15, 36, 163, 128, 68, 1, 26],
],
[
[138, 31, 36, 171, 27, 166, 38, 44, 229],
[67, 87, 58, 169, 82, 115, 26, 59, 179],
[63, 59, 90, 180, 59, 166, 93, 73, 154],
[40, 40, 21, 116, 143, 209, 34, 39, 175],
[57, 46, 22, 24, 128, 1, 54, 17, 37],
[47, 15, 16, 183, 34, 223, 49, 45, 183],
[46, 17, 33, 183, 6, 98, 15, 32, 183],
[65, 32, 73, 115, 28, 128, 23, 128, 205],
[40, 3, 9, 115, 51, 192, 18, 6, 223],
[87, 37, 9, 115, 59, 77, 64, 21, 47],
],
[
[104, 55, 44, 218, 9, 54, 53, 130, 226],
[64, 90, 70, 205, 40, 41, 23, 26, 57],
[54, 57, 112, 184, 5, 41, 38, 166, 213],
[30, 34, 26, 133, 152, 116, 10, 32, 134],
[75, 32, 12, 51, 192, 255, 160, 43, 51],
[39, 19, 53, 221, 26, 114, 32, 73, 255],
[31, 9, 65, 234, 2, 15, 1, 118, 73],
[88, 31, 35, 67, 102, 85, 55, 186, 85],
[56, 21, 23, 111, 59, 205, 45, 37, 192],
[55, 38, 70, 124, 73, 102, 1, 34, 98],
],
[
[102, 61, 71, 37, 34, 53, 31, 243, 192],
[69, 60, 71, 38, 73, 119, 28, 222, 37],
[68, 45, 128, 34, 1, 47, 11, 245, 171],
[62, 17, 19, 70, 146, 85, 55, 62, 70],
[75, 15, 9, 9, 64, 255, 184, 119, 16],
[37, 43, 37, 154, 100, 163, 85, 160, 1],
[63, 9, 92, 136, 28, 64, 32, 201, 85],
[86, 6, 28, 5, 64, 255, 25, 248, 1],
[56, 8, 17, 132, 137, 255, 55, 116, 128],
[58, 15, 20, 82, 135, 57, 26, 121, 40],
],
[
[164, 50, 31, 137, 154, 133, 25, 35, 218],
[51, 103, 44, 131, 131, 123, 31, 6, 158],
[86, 40, 64, 135, 148, 224, 45, 183, 128],
[22, 26, 17, 131, 240, 154, 14, 1, 209],
[83, 12, 13, 54, 192, 255, 68, 47, 28],
[45, 16, 21, 91, 64, 222, 7, 1, 197],
[56, 21, 39, 155, 60, 138, 23, 102, 213],
[85, 26, 85, 85, 128, 128, 32, 146, 171],
[18, 11, 7, 63, 144, 171, 4, 4, 246],
[35, 27, 10, 146, 174, 171, 12, 26, 128],
],
[
[190, 80, 35, 99, 180, 80, 126, 54, 45],
[85, 126, 47, 87, 176, 51, 41, 20, 32],
[101, 75, 128, 139, 118, 146, 116, 128, 85],
[56, 41, 15, 176, 236, 85, 37, 9, 62],
[146, 36, 19, 30, 171, 255, 97, 27, 20],
[71, 30, 17, 119, 118, 255, 17, 18, 138],
[101, 38, 60, 138, 55, 70, 43, 26, 142],
[138, 45, 61, 62, 219, 1, 81, 188, 64],
[32, 41, 20, 117, 151, 142, 20, 21, 163],
[112, 19, 12, 61, 195, 128, 48, 4, 24],
],
];
/// Probabilities of the flags that say a coefficient probability is updated (§13.4).
pub(crate) const COEFF_UPDATE_PROBS: [[[[u8; 11]; 3]; 8]; 4] = [
[
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255],
[249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255],
[234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255],
[250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
],
[
[
[217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255],
[234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255],
],
[
[255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
],
[
[
[186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255],
[234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255],
[251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255],
],
[
[255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255],
],
[
[255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
],
[
[
[248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255],
[248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255],
],
[
[255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255],
[248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255],
[250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
[
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
],
],
];
/// Coefficient token probabilities at the start of every key frame (§13.5).
pub(crate) const DEFAULT_COEFF_PROBS: [[[[u8; 11]; 3]; 8]; 4] = [
[
[
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
],
[
[253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128],
[189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128],
[106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128],
],
[
[1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128],
[181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128],
[78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128],
],
[
[1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128],
[184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128],
[77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128],
],
[
[1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128],
[170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128],
[37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128],
],
[
[1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128],
[207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128],
[102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128],
],
[
[1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128],
[177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128],
[80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128],
],
[
[1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
],
],
[
[
[198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62],
[131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1],
[68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128],
],
[
[1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128],
[184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128],
[81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128],
],
[
[1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128],
[99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128],
[23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128],
],
[
[1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128],
[109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128],
[44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128],
],
[
[1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128],
[94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128],
[22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128],
],
[
[1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128],
[124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128],
[35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128],
],
[
[1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128],
[121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128],
[45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128],
],
[
[1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128],
[203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128],
[137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128],
],
],
[
[
[253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128],
[175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128],
[73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128],
],
[
[1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128],
[239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128],
[155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128],
],
[
[1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128],
[201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128],
[69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128],
],
[
[1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128],
[223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128],
[141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128],
],
[
[1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128],
[190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128],
[149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
],
[
[1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128],
],
[
[1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128],
[213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128],
[55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128],
],
[
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
],
],
[
[
[202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255],
[126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128],
[61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128],
],
[
[1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128],
[166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128],
[39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128],
],
[
[1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128],
[124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128],
[24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128],
],
[
[1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128],
[149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128],
[28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128],
],
[
[1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128],
[123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128],
[20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128],
],
[
[1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128],
[168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128],
[47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128],
],
[
[1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128],
[141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128],
[42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128],
],
[
[1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
],
],
];
/// Coefficient position to probability band (§13.3).
pub(crate) const COEFF_BANDS: [usize; 16] = [0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7];
/// Extra-bit probabilities of the six value categories, high bit first (§13.2).
pub(crate) const PCAT: [&[u8]; 6] = [
&[159],
&[165, 145],
&[173, 148, 140],
&[176, 155, 140, 135],
&[180, 157, 141, 134, 130],
&[254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129],
];
/// Smallest value of each category (§13.2).
pub(crate) const CAT_BASE: [u32; 6] = [5, 7, 11, 19, 35, 67];
/// DC dequantization factor by quantizer index (§14.1).
pub(crate) const DC_QLOOKUP: [i32; 128] = [
4, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
69, 70, 71, 72, 73, 74, 75, 76, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 91, 93, 95, 96, 98, 100, 101, 102, 104, 106, 108, 110,
112, 114, 116, 118, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 143, 145, 148, 151, 154, 157,
];
/// AC dequantization factor by quantizer index (§14.1).
pub(crate) const AC_QLOOKUP: [i32; 128] = [
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96,
98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 119, 122, 125, 128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 161, 164, 167, 170, 173,
177, 181, 185, 189, 193, 197, 201, 205, 209, 213, 217, 221, 225, 229, 234, 239, 245, 249, 254, 259, 264, 269, 274, 279, 284,
];
+157
View File
@@ -0,0 +1,157 @@
//! The 4×4 transforms: the decoder's inverse DCT and WHT exactly as RFC 6386 §14.3–14.4 gives
//! them, and forward transforms derived from them.
//!
//! The inverse DCT is, apart from its integer rounding, `R = T·X·Tᵀ / 8` with the 1-D basis
//! `T = [[1, C, 1, S], [1, S, −1, −C], [1, −S, −1, C], [1, −C, 1, −S]]` (C = √2·cos π/8,
//! S = √2·sin π/8). T's columns are orthogonal and of norm 2, so `TᵀT = 4I` and the exact inverse
//! is `X = Tᵀ·R·T / 2`. The inverse WHT is likewise `R = H·Y·Hᵀ / 8` with the 4×4 Hadamard
//! matrix H in the order of §14.3, so `Y = Hᵀ·R·H / 2`.
const COSPI8SQRT2MINUS1: i32 = 20091;
const SINPI8SQRT2: i32 = 35468;
/// Inverse DCT of dequantized coefficients (raster order) to residue, §14.4. Intermediate and
/// final values are 16-bit, as the RFC stores them.
pub(crate) fn idct(input: &[i16; 16]) -> [i16; 16] {
let mut tmp = [0i16; 16];
for i in 0..4 {
let x = |r: usize| i32::from(input[r * 4 + i]);
let a1 = x(0) + x(2);
let b1 = x(0) - x(2);
let c1 = ((x(1) * SINPI8SQRT2) >> 16) - (x(3) + ((x(3) * COSPI8SQRT2MINUS1) >> 16));
let d1 = (x(1) + ((x(1) * COSPI8SQRT2MINUS1) >> 16)) + ((x(3) * SINPI8SQRT2) >> 16);
tmp[i] = (a1 + d1) as i16;
tmp[12 + i] = (a1 - d1) as i16;
tmp[4 + i] = (b1 + c1) as i16;
tmp[8 + i] = (b1 - c1) as i16;
}
let mut out = [0i16; 16];
for r in 0..4 {
let x = |c: usize| i32::from(tmp[r * 4 + c]);
let a1 = x(0) + x(2);
let b1 = x(0) - x(2);
let c1 = ((x(1) * SINPI8SQRT2) >> 16) - (x(3) + ((x(3) * COSPI8SQRT2MINUS1) >> 16));
let d1 = (x(1) + ((x(1) * COSPI8SQRT2MINUS1) >> 16)) + ((x(3) * SINPI8SQRT2) >> 16);
out[r * 4] = ((a1 + d1 + 4) >> 3) as i16;
out[r * 4 + 3] = ((a1 - d1 + 4) >> 3) as i16;
out[r * 4 + 1] = ((b1 + c1 + 4) >> 3) as i16;
out[r * 4 + 2] = ((b1 - c1 + 4) >> 3) as i16;
}
out
}
/// Inverse WHT of the dequantized Y2 block to the 16 luma DC coefficients, §14.3.
pub(crate) fn iwht(input: &[i16; 16]) -> [i16; 16] {
let mut tmp = [0i16; 16];
for i in 0..4 {
let x = |r: usize| i32::from(input[r * 4 + i]);
let a1 = x(0) + x(3);
let b1 = x(1) + x(2);
let c1 = x(1) - x(2);
let d1 = x(0) - x(3);
tmp[i] = (a1 + b1) as i16;
tmp[4 + i] = (c1 + d1) as i16;
tmp[8 + i] = (a1 - b1) as i16;
tmp[12 + i] = (d1 - c1) as i16;
}
let mut out = [0i16; 16];
for r in 0..4 {
let x = |c: usize| i32::from(tmp[r * 4 + c]);
let a1 = x(0) + x(3);
let b1 = x(1) + x(2);
let c1 = x(1) - x(2);
let d1 = x(0) - x(3);
out[r * 4] = ((a1 + b1 + 3) >> 3) as i16;
out[r * 4 + 1] = ((c1 + d1 + 3) >> 3) as i16;
out[r * 4 + 2] = ((a1 - b1 + 3) >> 3) as i16;
out[r * 4 + 3] = ((d1 - c1 + 3) >> 3) as i16;
}
out
}
/// The forward DCT's basis, scaled to match the spec's inverse.
fn basis_dct() -> [[f64; 4]; 4] {
let c = std::f64::consts::SQRT_2 * (std::f64::consts::PI / 8.0).cos();
let s = std::f64::consts::SQRT_2 * (std::f64::consts::PI / 8.0).sin();
[[1.0, c, 1.0, s], [1.0, s, -1.0, -c], [1.0, -s, -1.0, c], [1.0, -c, 1.0, -s]]
}
const HADAMARD: [[f64; 4]; 4] = [[1.0, 1.0, 1.0, 1.0], [1.0, 1.0, -1.0, -1.0], [1.0, -1.0, -1.0, 1.0], [1.0, -1.0, 1.0, -1.0]];
/// `Mᵀ·R·M / 2` for a 4×4 `R` in raster order.
fn forward(m: &[[f64; 4]; 4], r: &[f64; 16]) -> [f64; 16] {
let mut tmp = [0f64; 16]; // Mᵀ·R
for i in 0..4 {
for j in 0..4 {
tmp[i * 4 + j] = (0..4).map(|k| m[k][i] * r[k * 4 + j]).sum();
}
}
let mut out = [0f64; 16];
for i in 0..4 {
for j in 0..4 {
out[i * 4 + j] = (0..4).map(|k| tmp[i * 4 + k] * m[k][j]).sum::<f64>() / 2.0;
}
}
out
}
/// Forward DCT of a 4×4 residue (raster order), in the scale [`idct`] inverts.
pub(crate) fn fdct(residue: &[i32; 16]) -> [f64; 16] {
let r: [f64; 16] = std::array::from_fn(|i| f64::from(residue[i]));
forward(&basis_dct(), &r)
}
/// Forward WHT of the 16 luma DC coefficients (raster order of their subblocks), in the scale
/// [`iwht`] inverts.
pub(crate) fn fwht(dc: &[f64; 16]) -> [f64; 16] {
forward(&HADAMARD, dc)
}
#[cfg(test)]
mod tests {
use super::*;
/// The DCT and its inverse round-trip residuals within a level.
#[test]
fn dct_round_trips() {
let mut x = 7u32;
for _ in 0..500 {
let r: [i32; 16] = std::array::from_fn(|_| {
x = x.wrapping_mul(1664525).wrapping_add(1013904223);
(x >> 16) as i32 % 511 - 255
});
let c = fdct(&r);
let q: [i16; 16] = std::array::from_fn(|i| c[i].round() as i16);
let back = idct(&q);
for i in 0..16 {
assert!((i32::from(back[i]) - r[i]).abs() <= 1, "{r:?} -> {back:?}");
}
}
}
/// The Walsh–Hadamard transform and its inverse round-trip within a level.
#[test]
fn wht_round_trips() {
let mut x = 99u32;
for _ in 0..500 {
let d: [f64; 16] = std::array::from_fn(|_| {
x = x.wrapping_mul(1664525).wrapping_add(1013904223);
f64::from((x >> 16) as i32 % 4001 - 2000)
});
let y = fwht(&d);
let q: [i16; 16] = std::array::from_fn(|i| y[i].round() as i16);
let back = iwht(&q);
for i in 0..16 {
assert!((f64::from(back[i]) - d[i]).abs() <= 1.0, "{d:?} -> {back:?}");
}
}
}
/// A lone DC coefficient inverts to a flat block.
#[test]
fn a_lone_dc_is_flat() {
let mut c = [0i16; 16];
c[0] = 80;
assert!(idct(&c).iter().all(|&v| v == (80 + 4) >> 3));
}
}
+85
View File
@@ -0,0 +1,85 @@
//! RGBA to Y′CbCr 4:2:0 (Recommendation BT.601, studio range), padded to whole macroblocks.
//!
//! RFC 9649 §2.5 asks for BT.601 between RGB and the VP8 planes. Chroma is the average of each
//! 2×2 group weighted by alpha, so colour hidden under full transparency does not tint the edge of
//! what shows; a group with no alpha at all averages evenly.
use super::frame::Plane;
/// BT.601 studio-range luma (16–235) from RGB.
fn luma(r: i32, g: i32, b: i32) -> u8 {
(((66 * r + 129 * g + 25 * b + 128) >> 8) + 16).clamp(0, 255) as u8
}
/// BT.601 studio-range Cb and Cr (16–240) from RGB.
fn chroma(r: i32, g: i32, b: i32) -> (u8, u8) {
let u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
let v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;
(u.clamp(0, 255) as u8, v.clamp(0, 255) as u8)
}
/// The planes at the coded size: whole macroblocks, the picture's last row and column repeated
/// into the padding. The caller has checked `rgba` holds `width × height` pixels.
pub(crate) fn rgba_to_yuv420(rgba: &[u8], width: u32, height: u32) -> (Plane, Plane, Plane) {
let (w, h) = (width as usize, height as usize);
let (mbw, mbh) = (w.div_ceil(16), h.div_ceil(16));
let mut y = Plane::new(mbw * 16, mbh * 16);
let mut u = Plane::new(mbw * 8, mbh * 8);
let mut v = Plane::new(mbw * 8, mbh * 8);
let px = |x: usize, yy: usize| -> [i32; 4] {
let i = (yy.min(h - 1) * w + x.min(w - 1)) * 4;
match rgba.get(i..i + 4) {
Some(p) => [i32::from(p[0]), i32::from(p[1]), i32::from(p[2]), i32::from(p[3])],
None => [0; 4],
}
};
for yy in 0..y.height {
for x in 0..y.width {
let [r, g, b, _] = px(x, yy);
y.set(x, yy, luma(r, g, b));
}
}
for cy in 0..u.height {
for cx in 0..u.width {
let group = [px(2 * cx, 2 * cy), px(2 * cx + 1, 2 * cy), px(2 * cx, 2 * cy + 1), px(2 * cx + 1, 2 * cy + 1)];
let weight: i32 = group.iter().map(|p| p[3]).sum();
let (mut r, mut g, mut b) = (0, 0, 0);
for p in &group {
let k = if weight > 0 { p[3] } else { 1 };
r += p[0] * k;
g += p[1] * k;
b += p[2] * k;
}
let total = if weight > 0 { weight } else { 4 };
let (cu, cv) = chroma((r + total / 2) / total, (g + total / 2) / total, (b + total / 2) / total);
u.set(cx, cy, cu);
v.set(cx, cy, cv);
}
}
(y, u, v)
}
#[cfg(test)]
mod tests {
use super::*;
/// Black and white map to 16 and 235, and grey has neutral chroma.
#[test]
fn studio_range() {
assert_eq!(luma(0, 0, 0), 16);
assert_eq!(luma(255, 255, 255), 235);
assert_eq!(chroma(128, 128, 128), (128, 128));
}
/// Padding repeats the edge pixel, and chroma ignores fully transparent pixels.
#[test]
fn padding_repeats_the_edge_and_alpha_weights_chroma() {
// 3×1: red opaque, then two fully transparent black pixels.
let rgba = [255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0];
let (y, u, v) = rgba_to_yuv420(&rgba, 3, 1);
assert_eq!((y.width, y.height, u.width), (16, 16, 8));
assert_eq!(y.get(15, 15), y.get(2, 0), "the last pixel repeats");
// The first chroma sample sees only the red pixel's colour.
assert_eq!((u.get(0, 0), v.get(0, 0)), chroma(255, 0, 0));
}
}
+28 -2
View File
@@ -1,5 +1,11 @@
//! Clean-room pure-Rust VP8 decoder, implemented from RFC 6386 ("VP8 Data Format and Decoding
//! Guide"), sections 1-19.
//! Clean-room pure-Rust VP8, implemented from RFC 6386 ("VP8 Data Format and Decoding Guide").
//!
//! | Feature | Module | What |
//! |---|---|---|
//! | `vp8-decode` | this module | Decoder: sections 1-19, key and inter frames ([`Decoder`]) |
//! | `vp8-encode` | this module | Encoder: key frames, as the lossy picture of a WebP image ([`encode_rgba`]) |
//!
//! Feature `vp8` turns on both. The decoder:
//!
//! Supported: the whole format — key frames and inter frames, all intra modes (16x16, chroma,
//! the ten 4x4 subblock modes), inter prediction from the last, golden and altref frames with
@@ -18,20 +24,32 @@
#![allow(clippy::needless_range_loop)]
#[cfg(feature = "vp8-decode")]
mod boolcoder;
#[cfg(feature = "vp8-decode")]
mod decoder;
#[cfg(feature = "vp8-decode")]
mod error;
#[cfg(feature = "vp8-decode")]
mod header;
#[cfg(feature = "vp8-decode")]
mod loopfilter;
#[cfg(feature = "vp8-decode")]
mod predict;
#[cfg(feature = "vp8-decode")]
#[rustfmt::skip]
mod spec_tables;
#[cfg(feature = "vp8-decode")]
mod tables;
#[cfg(feature = "vp8-decode")]
mod transform;
#[cfg(feature = "vp8-decode")]
pub use decoder::{DecodeStats, Decoder};
#[cfg(feature = "vp8-decode")]
pub use error::{Error, Result};
#[cfg(feature = "vp8-decode")]
/// A decoded picture: 8-bit 4:2:0, planes tight (stride = width).
#[derive(Clone, Debug)]
pub struct Picture {
@@ -49,6 +67,7 @@ pub struct Picture {
pub color_space: u8,
}
#[cfg(feature = "vp8-decode")]
/// Stream parameters read from a key frame's uncompressed chunk (see [`keyframe_info`]).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct KeyframeInfo {
@@ -60,6 +79,7 @@ pub struct KeyframeInfo {
pub version: u8,
}
#[cfg(feature = "vp8-decode")]
/// If `frame` is a key frame (a random access point), its parameters.
pub fn keyframe_info(frame: &[u8]) -> Option<KeyframeInfo> {
let tag = header::FrameTag::parse(frame).ok()?;
@@ -72,7 +92,13 @@ pub fn keyframe_info(frame: &[u8]) -> Option<KeyframeInfo> {
})
}
#[cfg(feature = "vp8-decode")]
/// Whether `frame` is a key frame (decoding can start there).
pub fn is_keyframe(frame: &[u8]) -> bool {
keyframe_info(frame).is_some()
}
#[cfg(feature = "vp8-encode")]
mod encode;
#[cfg(feature = "vp8-encode")]
pub use encode::*;
+124
View File
@@ -0,0 +1,124 @@
//! The VP8 encoder's frames read back by this crate's own decoder: the decode must reproduce the
//! encoder's reconstruction exactly (loop filter off) and read as a picture close to the source.
use craft_video::vp8::{Decoder, Picture, Vp8Frame, Vp8Params, encode_rgba};
/// A test picture with something for every mode: flat areas, gradients, hard edges, diagonal
/// stripes, fine noise, colour.
fn picture(w: u32, h: u32, seed: u32) -> Vec<u8> {
let mut x = seed.wrapping_mul(2654435761) | 1;
let mut out = Vec::with_capacity((w * h * 4) as usize);
for py in 0..h {
for px in 0..w {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
let noise = (x % 64) as i32 - 32;
let (fx, fy) = (px as f32 / w as f32, py as f32 / h as f32);
let (r, g, b) = if fx < 0.25 {
(200, 40, 40) // flat
} else if fx < 0.5 {
((fx * 400.0) as i32, (fy * 255.0) as i32, 128) // gradient
} else if fx < 0.75 {
let stripe = ((px + py) / 3) % 2 == 0; // diagonal stripes
if stripe { (240, 240, 240) } else { (20, 30, 90) }
} else {
(128 + noise * 2, 100 + noise, 160 - noise) // noise
};
let c = |v: i32| v.clamp(0, 255) as u8;
out.extend_from_slice(&[c(r), c(g), c(b), 255]);
}
}
out
}
/// The decoder's reading of the frame.
fn decode(frame: &Vp8Frame) -> Picture {
Decoder::new().decode(&frame.data, 0).expect("decode").expect("a key frame is a picture")
}
/// The encoder's reconstruction cropped to the picture, as the decoder's planes: Y, then U, then V.
fn reconstruction(frame: &Vp8Frame, w: u32, h: u32) -> Vec<u8> {
let (w, h) = (w as usize, h as usize);
let (cw, ch) = (w.div_ceil(2), h.div_ceil(2));
let mut out = Vec::with_capacity(w * h + 2 * cw * ch);
for y in 0..h {
for x in 0..w {
out.push(frame.y.get(x, y));
}
}
for p in [&frame.u, &frame.v] {
for y in 0..ch {
for x in 0..cw {
out.push(p.get(x, y));
}
}
}
out
}
/// With the loop filter off, the decoder reconstructs exactly what the encoder did.
#[test]
fn the_decoder_reconstructs_exactly_what_the_encoder_did() {
for (w, h, quality) in [(64, 48, 90), (33, 17, 50), (100, 75, 10), (16, 16, 100), (130, 66, 75), (1, 1, 60)] {
let rgba = picture(w, h, w * 31 + h);
let frame = encode_rgba(&rgba, w, h, &Vp8Params { quality, loop_filter: Some(0) }).expect("encode");
let name = format!("{w}x{h} q{quality}");
let pic = decode(&frame);
assert_eq!((pic.width, pic.height), (w, h), "{name}: picture size");
let got: Vec<u8> = [&pic.y, &pic.u, &pic.v].into_iter().flatten().copied().collect();
let want = reconstruction(&frame, w, h);
assert_eq!(got.len(), want.len(), "{name}: plane sizes");
if let Some(i) = got.iter().zip(&want).position(|(a, b)| a != b) {
panic!("{name}: the decoder differs from the encoder's reconstruction at byte {i} of {} ({} vs {})", got.len(), got[i], want[i]);
}
}
}
/// The peak signal-to-noise ratio of `b` against `a`, in dB.
fn psnr(a: &[u8], b: &[u8]) -> f64 {
let se: f64 = a.iter().zip(b).map(|(&x, &y)| (f64::from(x) - f64::from(y)).powi(2)).sum();
let mse = se / a.len() as f64;
if mse == 0.0 { f64::INFINITY } else { 10.0 * (255.0f64 * 255.0 / mse).log10() }
}
/// A higher quality decodes closer to the quality-100 picture.
#[test]
fn the_decoder_reads_it_back_close_to_the_source() {
// Against the decode at quality 100, so the comparison is of the coding alone and not of
// the colour 4:2:0 cannot hold (this picture's fine coloured stripes and noise).
let (w, h) = (96, 64);
let rgba = picture(w, h, 7);
let planes = |quality| {
let frame = encode_rgba(&rgba, w, h, &Vp8Params { quality, loop_filter: None }).expect("encode");
let pic = decode(&frame);
assert_eq!((pic.width, pic.height), (w, h));
[&pic.y, &pic.u, &pic.v].into_iter().flatten().copied().collect::<Vec<u8>>()
};
let best = planes(100);
let mut last = 0.0;
for quality in [20u8, 60, 95] {
let q = psnr(&planes(quality), &best);
assert!(q >= last, "quality {quality} is no closer than a lower quality: {q:.1} dB after {last:.1}");
last = q;
}
assert!(last > 38.0, "quality 95: {last:.1} dB");
}
/// A higher quality gives a larger frame.
#[test]
fn higher_quality_spends_more_bytes() {
let (w, h) = (96, 64);
let rgba = picture(w, h, 11);
let size = |quality| encode_rgba(&rgba, w, h, &Vp8Params { quality, loop_filter: None }).expect("encode").data.len();
let (low, mid, high) = (size(10), size(60), size(98));
assert!(low < mid && mid < high, "{low} {mid} {high}");
}
/// Empty, short and oversized pictures are errors.
#[test]
fn bad_sizes_are_errors() {
assert!(encode_rgba(&[], 0, 0, &Vp8Params::default()).is_err());
assert!(encode_rgba(&[0; 12], 2, 2, &Vp8Params::default()).is_err());
assert!(encode_rgba(&[0; 4], 16384, 1, &Vp8Params::default()).is_err());
}