Skip to main content

otf_pixels_codec_avif/av1/
frame.rs

1//! The AV1 frame (uncompressed) header, restricted to intra frames (spec §5.9).
2//!
3//! An AVIF still is a `KEY_FRAME`, which is intra. That collapses the frame
4//! header's branching enormously: no reference-frame selection, no motion
5//! vectors, no global motion, no skip mode, no warped motion — every one of
6//! those reads nothing here. What remains is the size, the quantizer,
7//! segmentation, the loop-filter/CDEF/restoration parameters, the transform
8//! mode, and the tile layout, all of which the reconstruction phases need.
9//!
10//! The parse still walks every field in order, because the syntax has no length
11//! prefixes: the value of `CodedLossless` decides whether the loop filter reads
12//! six bits or zero, so it must be computed here, from the quantizer, before
13//! the filter parameters are reached.
14
15use super::bits::BitReader;
16use super::seq::SequenceHeader;
17use otf_pixels_core::{PixelsError, Result};
18
19const KEY_FRAME: u8 = 0;
20const INTER_FRAME: u8 = 1;
21const INTRA_ONLY_FRAME: u8 = 2;
22const SWITCH_FRAME: u8 = 3;
23
24const PRIMARY_REF_NONE: u8 = 7;
25const NUM_REF_FRAMES: u32 = 8;
26const SELECT: u8 = 2;
27
28const SUPERRES_NUM: u32 = 8;
29const SUPERRES_DENOM_MIN: u32 = 9;
30const SUPERRES_DENOM_BITS: u32 = 3;
31
32const MAX_TILE_WIDTH: u32 = 4096;
33const MAX_TILE_AREA: u32 = 4096 * 2304;
34const MAX_TILE_COLS: u32 = 64;
35const MAX_TILE_ROWS: u32 = 64;
36
37const MAX_SEGMENTS: usize = 8;
38const SEG_LVL_ALT_Q: usize = 0;
39const SEG_LVL_REF_FRAME: usize = 5;
40const SEG_LVL_MAX: usize = 8;
41const MAX_LOOP_FILTER: i32 = 63;
42
43const TOTAL_REFS_PER_FRAME: usize = 8;
44
45const RESTORE_NONE: u8 = 0;
46const RESTORATION_TILESIZE_MAX: u32 = 256;
47
48const SEG_FEATURE_BITS: [u32; SEG_LVL_MAX] = [8, 6, 6, 6, 6, 3, 0, 0];
49const SEG_FEATURE_SIGNED: [bool; SEG_LVL_MAX] = [true, true, true, true, true, false, false, false];
50const SEG_FEATURE_MAX: [i32; SEG_LVL_MAX] = [
51    255,
52    MAX_LOOP_FILTER,
53    MAX_LOOP_FILTER,
54    MAX_LOOP_FILTER,
55    MAX_LOOP_FILTER,
56    7,
57    0,
58    0,
59];
60
61/// `Remap_Lr_Type` (§5.9.20): coded value to restoration type.
62const REMAP_LR_TYPE: [u8; 4] = [
63    RESTORE_NONE,
64    3, /*SWITCHABLE*/
65    1, /*WIENER*/
66    2, /*SGRPROJ*/
67];
68
69/// The tile layout (`tile_info`, §5.9.15).
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub struct TileInfo {
72    /// `log2(TileCols)`.
73    pub cols_log2: u32,
74    /// `log2(TileRows)`.
75    pub rows_log2: u32,
76    /// Number of tile columns.
77    pub cols: u32,
78    /// Number of tile rows.
79    pub rows: u32,
80    /// Superblock column index at which each tile starts, length `cols + 1`.
81    pub col_starts_sb: Vec<u32>,
82    /// Superblock row index at which each tile starts, length `rows + 1`.
83    pub row_starts_sb: Vec<u32>,
84    /// `context_update_tile_id` — which tile carries the CDF update.
85    pub context_update_tile_id: u32,
86    /// Bytes used to code each tile's size in the tile group.
87    pub tile_size_bytes: u32,
88}
89
90impl TileInfo {
91    /// Total number of tiles.
92    #[must_use]
93    pub fn count(&self) -> u32 {
94        self.cols * self.rows
95    }
96}
97
98/// Dequant deltas from `quantization_params` (§5.9.12).
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub struct Quantization {
101    /// Base quantizer index, 0..=255.
102    pub base_q_idx: u8,
103    /// Luma DC delta.
104    pub delta_q_y_dc: i32,
105    /// Chroma-U DC delta.
106    pub delta_q_u_dc: i32,
107    /// Chroma-U AC delta.
108    pub delta_q_u_ac: i32,
109    /// Chroma-V DC delta.
110    pub delta_q_v_dc: i32,
111    /// Chroma-V AC delta.
112    pub delta_q_v_ac: i32,
113    /// Whether quantizer matrices are used.
114    pub using_qmatrix: bool,
115    /// Luma qm index.
116    pub qm_y: u8,
117    /// Chroma-U qm index.
118    pub qm_u: u8,
119    /// Chroma-V qm index.
120    pub qm_v: u8,
121}
122
123/// Segmentation parameters (`segmentation_params`, §5.9.14).
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct Segmentation {
126    /// Whether segmentation is on.
127    pub enabled: bool,
128    /// Per-segment, per-feature enable flags.
129    pub feature_enabled: [[bool; SEG_LVL_MAX]; MAX_SEGMENTS],
130    /// Per-segment, per-feature clipped values.
131    pub feature_data: [[i32; SEG_LVL_MAX]; MAX_SEGMENTS],
132}
133
134impl Segmentation {
135    fn disabled() -> Self {
136        Self {
137            enabled: false,
138            feature_enabled: [[false; SEG_LVL_MAX]; MAX_SEGMENTS],
139            feature_data: [[0; SEG_LVL_MAX]; MAX_SEGMENTS],
140        }
141    }
142
143    /// `seg_feature_active_idx(segment, feature)` (§5.11.14).
144    #[must_use]
145    pub fn feature_active(&self, segment: usize, feature: usize) -> bool {
146        self.enabled
147            && self
148                .feature_enabled
149                .get(segment)
150                .and_then(|f| f.get(feature))
151                .copied()
152                .unwrap_or(false)
153    }
154
155    /// `FeatureData[segment][feature]` when that feature is active, else 0.
156    #[must_use]
157    pub fn feature_value(&self, segment: usize, feature: usize) -> i32 {
158        if self.feature_active(segment, feature) {
159            self.feature_data
160                .get(segment)
161                .and_then(|f| f.get(feature))
162                .copied()
163                .unwrap_or(0)
164        } else {
165            0
166        }
167    }
168
169    /// `LastActiveSegId` (§5.9.14): the highest segment with any feature on.
170    #[must_use]
171    pub fn last_active_segment(&self) -> usize {
172        (0..MAX_SEGMENTS)
173            .rev()
174            .find(|&s| (0..SEG_LVL_MAX).any(|f| self.feature_active(s, f)))
175            .unwrap_or(0)
176    }
177
178    /// `SegIdPreSkip` (§5.9.14): whether any segment enables a feature from
179    /// `SEG_LVL_REF_FRAME` on, which moves `segment_id` ahead of `skip`.
180    #[must_use]
181    pub fn pre_skip(&self) -> bool {
182        (0..MAX_SEGMENTS)
183            .any(|s| (SEG_LVL_REF_FRAME..SEG_LVL_MAX).any(|f| self.feature_active(s, f)))
184    }
185}
186
187/// Loop-filter parameters (`loop_filter_params`, §5.9.11).
188#[derive(Debug, Clone, PartialEq, Eq)]
189pub struct LoopFilter {
190    /// Filter level per component: `[y_vert, y_horz, u, v]`.
191    pub level: [u8; 4],
192    /// Sharpness, 0..=7.
193    pub sharpness: u8,
194    /// Whether ref/mode deltas are enabled.
195    pub delta_enabled: bool,
196    /// Per-reference-frame deltas.
197    pub ref_deltas: [i32; TOTAL_REFS_PER_FRAME],
198    /// Per-mode deltas.
199    pub mode_deltas: [i32; 2],
200}
201
202/// CDEF parameters (`cdef_params`, §5.9.19).
203#[derive(Debug, Clone, PartialEq, Eq)]
204pub struct Cdef {
205    /// `CdefDamping`.
206    pub damping: u32,
207    /// `cdef_bits` — log2 of the number of filter strengths.
208    pub bits: u32,
209    /// Luma primary strengths.
210    pub y_pri_strength: Vec<u32>,
211    /// Luma secondary strengths.
212    pub y_sec_strength: Vec<u32>,
213    /// Chroma primary strengths.
214    pub uv_pri_strength: Vec<u32>,
215    /// Chroma secondary strengths.
216    pub uv_sec_strength: Vec<u32>,
217}
218
219/// Loop-restoration parameters (`lr_params`, §5.9.20).
220#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct LoopRestoration {
222    /// Restoration type per plane.
223    pub frame_restoration_type: [u8; 3],
224    /// Restoration unit size per plane.
225    pub unit_size: [u32; 3],
226    /// Whether any plane uses restoration.
227    pub uses_lr: bool,
228}
229
230/// Film-grain parameters (`film_grain_params`, §5.9.30), stored raw so
231/// synthesis can apply them later without re-parsing.
232#[derive(Debug, Clone, Default, PartialEq, Eq)]
233pub struct FilmGrain {
234    /// Whether grain is applied to this frame.
235    pub apply_grain: bool,
236    /// PRNG seed.
237    pub grain_seed: u16,
238}
239
240/// A fully parsed intra frame header.
241#[derive(Debug, Clone)]
242pub struct FrameHeader {
243    /// `frame_type` — `KEY_FRAME` or `INTRA_ONLY_FRAME` in this decoder.
244    pub frame_type: u8,
245    /// Whether the frame is shown.
246    pub show_frame: bool,
247    /// Whether the frame is later showable.
248    pub showable_frame: bool,
249    /// Whether error-resilient mode is set.
250    pub error_resilient_mode: bool,
251    /// Whether CDF updates are disabled for the frame.
252    pub disable_cdf_update: bool,
253    /// Whether screen-content tools are allowed.
254    pub allow_screen_content_tools: bool,
255    /// Coded frame width, after any super-resolution downscale.
256    pub frame_width: u32,
257    /// Coded frame height.
258    pub frame_height: u32,
259    /// Width before super-resolution upscale (the displayed coded width).
260    pub upscaled_width: u32,
261    /// Render (display) width.
262    pub render_width: u32,
263    /// Render (display) height.
264    pub render_height: u32,
265    /// `SuperresDenom`.
266    pub superres_denom: u32,
267    /// Frame width in 4x4 mode-info units.
268    pub mi_cols: u32,
269    /// Frame height in 4x4 mode-info units.
270    pub mi_rows: u32,
271    /// Whether intra block copy is allowed.
272    pub allow_intrabc: bool,
273    /// Whether the frame-end CDF update is disabled.
274    pub disable_frame_end_update_cdf: bool,
275    /// The tile layout.
276    pub tile_info: TileInfo,
277    /// Quantizer parameters.
278    pub quantization: Quantization,
279    /// Segmentation parameters.
280    pub segmentation: Segmentation,
281    /// Whether per-block delta-Q is present.
282    pub delta_q_present: bool,
283    /// Delta-Q resolution.
284    pub delta_q_res: u32,
285    /// Whether per-block delta-LF is present.
286    pub delta_lf_present: bool,
287    /// Delta-LF resolution.
288    pub delta_lf_res: u32,
289    /// Whether delta-LF is signalled per edge type.
290    pub delta_lf_multi: bool,
291    /// Whether every segment is coded losslessly.
292    pub coded_lossless: bool,
293    /// Whether the frame is lossless and unscaled.
294    pub all_lossless: bool,
295    /// Per-segment lossless flags.
296    pub lossless: [bool; MAX_SEGMENTS],
297    /// Loop-filter parameters.
298    pub loop_filter: LoopFilter,
299    /// CDEF parameters.
300    pub cdef: Cdef,
301    /// Loop-restoration parameters.
302    pub loop_restoration: LoopRestoration,
303    /// `TxMode`.
304    pub tx_mode: TxMode,
305    /// Whether the reduced transform set is used.
306    pub reduced_tx_set: bool,
307    /// Film-grain parameters.
308    pub film_grain: FilmGrain,
309}
310
311/// The transform mode (`read_tx_mode`, §5.9.21).
312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313pub enum TxMode {
314    /// `ONLY_4X4` — lossless frames.
315    Only4x4,
316    /// `TX_MODE_LARGEST`.
317    Largest,
318    /// `TX_MODE_SELECT`.
319    Select,
320}
321
322impl FrameHeader {
323    /// Parse an intra frame header, given the governing sequence header and the
324    /// OBU's temporal/spatial layer ids.
325    pub fn parse(
326        r: &mut BitReader<'_>,
327        seq: &SequenceHeader,
328        temporal_id: u8,
329        spatial_id: u8,
330    ) -> Result<Self> {
331        let id_len = if seq.frame_id_numbers_present {
332            seq.additional_frame_id_length + seq.delta_frame_id_length
333        } else {
334            0
335        };
336        let all_frames = (1_u32 << NUM_REF_FRAMES) - 1;
337
338        let (frame_type, show_frame, showable_frame, error_resilient_mode);
339        if seq.reduced_still_picture_header {
340            frame_type = KEY_FRAME;
341            show_frame = true;
342            showable_frame = false;
343            error_resilient_mode = false;
344        } else {
345            let show_existing_frame = r.flag()?;
346            if show_existing_frame {
347                return Err(PixelsError::unsupported(
348                    "avif: show_existing_frame is an animation/reference feature outside the still-picture subset",
349                ));
350            }
351            frame_type = r.f(2)? as u8;
352            show_frame = r.flag()?;
353            if show_frame && seq.decoder_model_info_present && !seq.equal_picture_interval {
354                // temporal_point_info(): frame_presentation_time f(n).
355                r.f(seq.frame_presentation_time_length)?;
356            }
357            showable_frame = if show_frame {
358                frame_type != KEY_FRAME
359            } else {
360                r.flag()?
361            };
362            error_resilient_mode =
363                if frame_type == SWITCH_FRAME || (frame_type == KEY_FRAME && show_frame) {
364                    true
365                } else {
366                    r.flag()?
367                };
368        }
369
370        let frame_is_intra = frame_type == KEY_FRAME || frame_type == INTRA_ONLY_FRAME;
371        if !frame_is_intra {
372            return Err(PixelsError::unsupported(
373                "avif: inter frames are outside the still-picture subset",
374            ));
375        }
376
377        let disable_cdf_update = r.flag()?;
378        let allow_screen_content_tools = if seq.seq_force_screen_content_tools == SELECT {
379            r.flag()?
380        } else {
381            seq.seq_force_screen_content_tools != 0
382        };
383        // force_integer_mv resolves to 1 for intra regardless; consume its bits.
384        if allow_screen_content_tools && seq.seq_force_integer_mv == SELECT {
385            r.f(1)?;
386        }
387
388        if seq.frame_id_numbers_present {
389            r.f(id_len)?; // current_frame_id
390        }
391
392        let frame_size_override_flag = if frame_type == SWITCH_FRAME {
393            true
394        } else if seq.reduced_still_picture_header {
395            false
396        } else {
397            r.flag()?
398        };
399
400        // order_hint (OrderHintBits wide; 0 in the reduced header).
401        r.f(seq.order_hint_bits)?;
402
403        // primary_ref_frame is PRIMARY_REF_NONE for intra; no bits read.
404        let _primary_ref_frame = PRIMARY_REF_NONE;
405
406        if seq.decoder_model_info_present {
407            let buffer_removal_time_present = r.flag()?;
408            if buffer_removal_time_present {
409                for op in &seq.operating_points {
410                    // decoder_model_present_for_this_op was not retained per-op;
411                    // the reduced/still path never sets it, so this loop only
412                    // runs when a decoder model is genuinely present.
413                    let idc = op.idc;
414                    let in_temporal = (idc >> temporal_id) & 1;
415                    let in_spatial = (idc >> (spatial_id + 8)) & 1;
416                    if idc == 0 || (in_temporal != 0 && in_spatial != 0) {
417                        r.f(seq.buffer_removal_time_length)?;
418                    }
419                }
420            }
421        }
422
423        // refresh_frame_flags: a shown key frame refreshes all slots.
424        let _refresh_frame_flags =
425            if frame_type == SWITCH_FRAME || (frame_type == KEY_FRAME && show_frame) {
426                all_frames
427            } else {
428                r.f(8)?
429            };
430
431        // Intra path: frame_size(), render_size(), then maybe allow_intrabc.
432        let size = parse_frame_size(r, seq, frame_size_override_flag)?;
433        let (render_width, render_height) =
434            parse_render_size(r, size.upscaled_width, size.frame_height)?;
435
436        let allow_intrabc = if allow_screen_content_tools && size.upscaled_width == size.frame_width
437        {
438            r.flag()?
439        } else {
440            false
441        };
442
443        let disable_frame_end_update_cdf = if seq.reduced_still_picture_header || disable_cdf_update
444        {
445            true
446        } else {
447            r.flag()?
448        };
449
450        let tile_info = parse_tile_info(r, seq, size.mi_cols, size.mi_rows)?;
451        let quantization = parse_quantization(r, seq)?;
452        let segmentation = parse_segmentation(r)?;
453
454        // delta_q_params / delta_lf_params.
455        let mut delta_q_present = false;
456        let mut delta_q_res = 0;
457        if quantization.base_q_idx > 0 {
458            delta_q_present = r.flag()?;
459        }
460        if delta_q_present {
461            delta_q_res = r.f(2)?;
462        }
463        let mut delta_lf_present = false;
464        let mut delta_lf_res = 0;
465        let mut delta_lf_multi = false;
466        if delta_q_present {
467            if !allow_intrabc {
468                delta_lf_present = r.flag()?;
469            }
470            if delta_lf_present {
471                delta_lf_res = r.f(2)?;
472                delta_lf_multi = r.flag()?;
473            }
474        }
475
476        // CodedLossless / AllLossless from the quantizer and segmentation.
477        let mut lossless = [false; MAX_SEGMENTS];
478        let mut coded_lossless = true;
479        let seg_count = if segmentation.enabled {
480            MAX_SEGMENTS
481        } else {
482            1
483        };
484        for (segment, slot) in lossless.iter_mut().enumerate().take(seg_count) {
485            let qindex = get_qindex(&segmentation, &quantization, segment);
486            let is_lossless = qindex == 0
487                && quantization.delta_q_y_dc == 0
488                && quantization.delta_q_u_ac == 0
489                && quantization.delta_q_u_dc == 0
490                && quantization.delta_q_v_ac == 0
491                && quantization.delta_q_v_dc == 0;
492            *slot = is_lossless;
493            if !is_lossless {
494                coded_lossless = false;
495            }
496        }
497        // Segments beyond seg_count inherit segment 0's lossless flag when
498        // segmentation is off; only the active range gates CodedLossless.
499        let all_lossless = coded_lossless && size.frame_width == size.upscaled_width;
500
501        let loop_filter = parse_loop_filter(r, seq, coded_lossless, allow_intrabc)?;
502        let cdef = parse_cdef(r, seq, coded_lossless, allow_intrabc)?;
503        let loop_restoration = parse_lr(r, seq, all_lossless, allow_intrabc)?;
504
505        let tx_mode = if coded_lossless {
506            TxMode::Only4x4
507        } else if r.flag()? {
508            TxMode::Select
509        } else {
510            TxMode::Largest
511        };
512
513        // frame_reference_mode: intra reads nothing (reference_select = 0).
514        // skip_mode_params: intra reads nothing.
515        // allow_warped_motion: intra reads nothing.
516        let reduced_tx_set = r.flag()?;
517        // global_motion_params: intra reads nothing.
518        let film_grain = parse_film_grain(r, seq, frame_type, show_frame, showable_frame)?;
519
520        Ok(Self {
521            frame_type,
522            show_frame,
523            showable_frame,
524            error_resilient_mode,
525            disable_cdf_update,
526            allow_screen_content_tools,
527            frame_width: size.frame_width,
528            frame_height: size.frame_height,
529            upscaled_width: size.upscaled_width,
530            render_width,
531            render_height,
532            superres_denom: size.superres_denom,
533            mi_cols: size.mi_cols,
534            mi_rows: size.mi_rows,
535            allow_intrabc,
536            disable_frame_end_update_cdf,
537            tile_info,
538            quantization,
539            segmentation,
540            delta_q_present,
541            delta_q_res,
542            delta_lf_present,
543            delta_lf_res,
544            delta_lf_multi,
545            coded_lossless,
546            all_lossless,
547            lossless,
548            loop_filter,
549            cdef,
550            loop_restoration,
551            tx_mode,
552            reduced_tx_set,
553            film_grain,
554        })
555    }
556}
557
558struct FrameSize {
559    frame_width: u32,
560    frame_height: u32,
561    upscaled_width: u32,
562    superres_denom: u32,
563    mi_cols: u32,
564    mi_rows: u32,
565}
566
567fn parse_frame_size(
568    r: &mut BitReader<'_>,
569    seq: &SequenceHeader,
570    override_flag: bool,
571) -> Result<FrameSize> {
572    let (mut frame_width, frame_height) = if override_flag {
573        let w = r.f(seq.frame_width_bits)? + 1;
574        let h = r.f(seq.frame_height_bits)? + 1;
575        (w, h)
576    } else {
577        (seq.max_frame_width, seq.max_frame_height)
578    };
579
580    // superres_params.
581    let use_superres = if seq.enable_superres {
582        r.flag()?
583    } else {
584        false
585    };
586    let superres_denom = if use_superres {
587        r.f(SUPERRES_DENOM_BITS)? + SUPERRES_DENOM_MIN
588    } else {
589        SUPERRES_NUM
590    };
591    let upscaled_width = frame_width;
592    frame_width = (upscaled_width * SUPERRES_NUM + (superres_denom / 2)) / superres_denom;
593
594    let mi_cols = 2 * ((frame_width + 7) >> 3);
595    let mi_rows = 2 * ((frame_height + 7) >> 3);
596
597    Ok(FrameSize {
598        frame_width,
599        frame_height,
600        upscaled_width,
601        superres_denom,
602        mi_cols,
603        mi_rows,
604    })
605}
606
607fn parse_render_size(
608    r: &mut BitReader<'_>,
609    upscaled_width: u32,
610    frame_height: u32,
611) -> Result<(u32, u32)> {
612    if r.flag()? {
613        let w = r.f(16)? + 1;
614        let h = r.f(16)? + 1;
615        Ok((w, h))
616    } else {
617        Ok((upscaled_width, frame_height))
618    }
619}
620
621/// `tile_log2(blkSize, target)` (§5.9.16): smallest `k` with
622/// `blkSize << k >= target`.
623fn tile_log2(blk_size: u32, target: u32) -> u32 {
624    let mut k = 0;
625    while (blk_size << k) < target {
626        k += 1;
627    }
628    k
629}
630
631fn parse_tile_info(
632    r: &mut BitReader<'_>,
633    seq: &SequenceHeader,
634    mi_cols: u32,
635    mi_rows: u32,
636) -> Result<TileInfo> {
637    let (sb_cols, sb_rows, sb_shift) = if seq.use_128x128_superblock {
638        (((mi_cols + 31) >> 5), ((mi_rows + 31) >> 5), 5)
639    } else {
640        (((mi_cols + 15) >> 4), ((mi_rows + 15) >> 4), 4)
641    };
642    let sb_size = sb_shift + 2;
643    let max_tile_width_sb = MAX_TILE_WIDTH >> sb_size;
644    let max_tile_area_sb = MAX_TILE_AREA >> (2 * sb_size);
645    let min_log2_tile_cols = tile_log2(max_tile_width_sb, sb_cols);
646    let max_log2_tile_cols = tile_log2(1, sb_cols.min(MAX_TILE_COLS));
647    let max_log2_tile_rows = tile_log2(1, sb_rows.min(MAX_TILE_ROWS));
648    let min_log2_tiles = min_log2_tile_cols.max(tile_log2(max_tile_area_sb, sb_rows * sb_cols));
649
650    let uniform_tile_spacing = r.flag()?;
651    let mut col_starts_sb = Vec::new();
652    let mut row_starts_sb = Vec::new();
653    let cols_log2;
654    let rows_log2;
655
656    if uniform_tile_spacing {
657        let mut c = min_log2_tile_cols;
658        while c < max_log2_tile_cols {
659            if r.flag()? {
660                c += 1;
661            } else {
662                break;
663            }
664        }
665        cols_log2 = c;
666        let tile_width_sb = (sb_cols + (1 << cols_log2) - 1) >> cols_log2;
667        let mut start = 0;
668        while start < sb_cols {
669            col_starts_sb.push(start);
670            start += tile_width_sb;
671        }
672        col_starts_sb.push(sb_cols);
673
674        let min_log2_tile_rows = min_log2_tiles.saturating_sub(cols_log2);
675        let mut rl = min_log2_tile_rows;
676        while rl < max_log2_tile_rows {
677            if r.flag()? {
678                rl += 1;
679            } else {
680                break;
681            }
682        }
683        rows_log2 = rl;
684        let tile_height_sb = (sb_rows + (1 << rows_log2) - 1) >> rows_log2;
685        let mut start = 0;
686        while start < sb_rows {
687            row_starts_sb.push(start);
688            start += tile_height_sb;
689        }
690        row_starts_sb.push(sb_rows);
691    } else {
692        let mut widest_tile_sb = 0;
693        let mut start = 0;
694        while start < sb_cols {
695            col_starts_sb.push(start);
696            let max_width = (sb_cols - start).min(max_tile_width_sb);
697            let size_sb = r.ns(max_width)? + 1;
698            widest_tile_sb = widest_tile_sb.max(size_sb);
699            start += size_sb;
700        }
701        col_starts_sb.push(sb_cols);
702        let tile_cols = (col_starts_sb.len() as u32) - 1;
703        cols_log2 = tile_log2(1, tile_cols);
704
705        let max_tile_area_sb = if min_log2_tiles > 0 {
706            (sb_rows * sb_cols) >> (min_log2_tiles + 1)
707        } else {
708            sb_rows * sb_cols
709        };
710        let max_tile_height_sb = (max_tile_area_sb / widest_tile_sb).max(1);
711        let mut start = 0;
712        while start < sb_rows {
713            row_starts_sb.push(start);
714            let max_height = (sb_rows - start).min(max_tile_height_sb);
715            let size_sb = r.ns(max_height)? + 1;
716            start += size_sb;
717        }
718        row_starts_sb.push(sb_rows);
719        let tile_rows = (row_starts_sb.len() as u32) - 1;
720        rows_log2 = tile_log2(1, tile_rows);
721    }
722
723    let cols = (col_starts_sb.len() as u32) - 1;
724    let rows = (row_starts_sb.len() as u32) - 1;
725
726    let (context_update_tile_id, tile_size_bytes) = if cols_log2 > 0 || rows_log2 > 0 {
727        let id = r.f(rows_log2 + cols_log2)?;
728        let bytes = r.f(2)? + 1;
729        (id, bytes)
730    } else {
731        (0, 1)
732    };
733
734    Ok(TileInfo {
735        cols_log2,
736        rows_log2,
737        cols,
738        rows,
739        col_starts_sb,
740        row_starts_sb,
741        context_update_tile_id,
742        tile_size_bytes,
743    })
744}
745
746fn read_delta_q(r: &mut BitReader<'_>) -> Result<i32> {
747    if r.flag()? { r.su(6) } else { Ok(0) }
748}
749
750fn parse_quantization(r: &mut BitReader<'_>, seq: &SequenceHeader) -> Result<Quantization> {
751    let base_q_idx = r.f(8)? as u8;
752    let delta_q_y_dc = read_delta_q(r)?;
753    let (delta_q_u_dc, delta_q_u_ac, delta_q_v_dc, delta_q_v_ac);
754    if seq.color.num_planes > 1 {
755        let diff_uv_delta = if seq.color.separate_uv_delta_q {
756            r.flag()?
757        } else {
758            false
759        };
760        let u_dc = read_delta_q(r)?;
761        let u_ac = read_delta_q(r)?;
762        if diff_uv_delta {
763            delta_q_u_dc = u_dc;
764            delta_q_u_ac = u_ac;
765            delta_q_v_dc = read_delta_q(r)?;
766            delta_q_v_ac = read_delta_q(r)?;
767        } else {
768            delta_q_u_dc = u_dc;
769            delta_q_u_ac = u_ac;
770            delta_q_v_dc = u_dc;
771            delta_q_v_ac = u_ac;
772        }
773    } else {
774        delta_q_u_dc = 0;
775        delta_q_u_ac = 0;
776        delta_q_v_dc = 0;
777        delta_q_v_ac = 0;
778    }
779
780    let using_qmatrix = r.flag()?;
781    let (qm_y, qm_u, qm_v) = if using_qmatrix {
782        let y = r.f(4)? as u8;
783        let u = r.f(4)? as u8;
784        let v = if seq.color.separate_uv_delta_q {
785            r.f(4)? as u8
786        } else {
787            u
788        };
789        (y, u, v)
790    } else {
791        (0, 0, 0)
792    };
793
794    Ok(Quantization {
795        base_q_idx,
796        delta_q_y_dc,
797        delta_q_u_dc,
798        delta_q_u_ac,
799        delta_q_v_dc,
800        delta_q_v_ac,
801        using_qmatrix,
802        qm_y,
803        qm_u,
804        qm_v,
805    })
806}
807
808fn parse_segmentation(r: &mut BitReader<'_>) -> Result<Segmentation> {
809    let enabled = r.flag()?;
810    if !enabled {
811        return Ok(Segmentation::disabled());
812    }
813    // Intra frame: primary_ref_frame is PRIMARY_REF_NONE, so update_map and
814    // update_data are both implied 1 and no flags are read for them.
815    let mut seg = Segmentation::disabled();
816    seg.enabled = true;
817    for segment in 0..MAX_SEGMENTS {
818        for feature in 0..SEG_LVL_MAX {
819            let feature_enabled = r.flag()?;
820            let mut clipped = 0;
821            if feature_enabled {
822                let bits = SEG_FEATURE_BITS.get(feature).copied().unwrap_or(0);
823                let limit = SEG_FEATURE_MAX.get(feature).copied().unwrap_or(0);
824                let signed = SEG_FEATURE_SIGNED.get(feature).copied().unwrap_or(false);
825                if signed {
826                    let value = r.su(bits)?;
827                    clipped = value.clamp(-limit, limit);
828                } else {
829                    let value = r.f(bits)? as i32;
830                    clipped = value.clamp(0, limit);
831                }
832            }
833            if let (Some(en), Some(dat)) = (
834                seg.feature_enabled.get_mut(segment),
835                seg.feature_data.get_mut(segment),
836            ) {
837                if let (Some(e), Some(d)) = (en.get_mut(feature), dat.get_mut(feature)) {
838                    *e = feature_enabled;
839                    *d = clipped;
840                }
841            }
842        }
843    }
844    Ok(seg)
845}
846
847fn get_qindex(seg: &Segmentation, quant: &Quantization, segment: usize) -> i32 {
848    let base = i32::from(quant.base_q_idx);
849    if seg.feature_active(segment, SEG_LVL_ALT_Q) {
850        let data = seg
851            .feature_data
852            .get(segment)
853            .and_then(|f| f.get(SEG_LVL_ALT_Q))
854            .copied()
855            .unwrap_or(0);
856        (base + data).clamp(0, 255)
857    } else {
858        base
859    }
860}
861
862fn parse_loop_filter(
863    r: &mut BitReader<'_>,
864    seq: &SequenceHeader,
865    coded_lossless: bool,
866    allow_intrabc: bool,
867) -> Result<LoopFilter> {
868    // Default ref deltas per §5.9.11: intra +1, others as listed.
869    let mut lf = LoopFilter {
870        level: [0; 4],
871        sharpness: 0,
872        delta_enabled: false,
873        ref_deltas: [1, 0, 0, 0, -1, 0, -1, -1],
874        mode_deltas: [0, 0],
875    };
876    if coded_lossless || allow_intrabc {
877        return Ok(lf);
878    }
879    lf.level[0] = r.f(6)? as u8;
880    lf.level[1] = r.f(6)? as u8;
881    if seq.color.num_planes > 1 && (lf.level[0] != 0 || lf.level[1] != 0) {
882        lf.level[2] = r.f(6)? as u8;
883        lf.level[3] = r.f(6)? as u8;
884    }
885    lf.sharpness = r.f(3)? as u8;
886    lf.delta_enabled = r.flag()?;
887    if lf.delta_enabled {
888        let delta_update = r.flag()?;
889        if delta_update {
890            for slot in lf.ref_deltas.iter_mut() {
891                if r.flag()? {
892                    *slot = r.su(6)?;
893                }
894            }
895            for slot in lf.mode_deltas.iter_mut() {
896                if r.flag()? {
897                    *slot = r.su(6)?;
898                }
899            }
900        }
901    }
902    Ok(lf)
903}
904
905fn parse_cdef(
906    r: &mut BitReader<'_>,
907    seq: &SequenceHeader,
908    coded_lossless: bool,
909    allow_intrabc: bool,
910) -> Result<Cdef> {
911    if coded_lossless || allow_intrabc || !seq.enable_cdef {
912        return Ok(Cdef {
913            damping: 3,
914            bits: 0,
915            y_pri_strength: vec![0],
916            y_sec_strength: vec![0],
917            uv_pri_strength: vec![0],
918            uv_sec_strength: vec![0],
919        });
920    }
921    let damping = r.f(2)? + 3;
922    let bits = r.f(2)?;
923    let count = 1_usize << bits;
924    let mut y_pri = Vec::with_capacity(count);
925    let mut y_sec = Vec::with_capacity(count);
926    let mut uv_pri = Vec::with_capacity(count);
927    let mut uv_sec = Vec::with_capacity(count);
928    for _ in 0..count {
929        y_pri.push(r.f(4)?);
930        let mut ys = r.f(2)?;
931        if ys == 3 {
932            ys += 1;
933        }
934        y_sec.push(ys);
935        if seq.color.num_planes > 1 {
936            uv_pri.push(r.f(4)?);
937            let mut us = r.f(2)?;
938            if us == 3 {
939                us += 1;
940            }
941            uv_sec.push(us);
942        } else {
943            uv_pri.push(0);
944            uv_sec.push(0);
945        }
946    }
947    Ok(Cdef {
948        damping,
949        bits,
950        y_pri_strength: y_pri,
951        y_sec_strength: y_sec,
952        uv_pri_strength: uv_pri,
953        uv_sec_strength: uv_sec,
954    })
955}
956
957fn parse_lr(
958    r: &mut BitReader<'_>,
959    seq: &SequenceHeader,
960    all_lossless: bool,
961    allow_intrabc: bool,
962) -> Result<LoopRestoration> {
963    let mut lr = LoopRestoration {
964        frame_restoration_type: [RESTORE_NONE; 3],
965        unit_size: [RESTORATION_TILESIZE_MAX; 3],
966        uses_lr: false,
967    };
968    if all_lossless || allow_intrabc || !seq.enable_restoration {
969        return Ok(lr);
970    }
971    let mut uses_lr = false;
972    let mut uses_chroma_lr = false;
973    for plane in 0..usize::from(seq.color.num_planes) {
974        let lr_type = r.f(2)? as usize;
975        let mapped = REMAP_LR_TYPE.get(lr_type).copied().unwrap_or(RESTORE_NONE);
976        if let Some(slot) = lr.frame_restoration_type.get_mut(plane) {
977            *slot = mapped;
978        }
979        if mapped != RESTORE_NONE {
980            uses_lr = true;
981            if plane > 0 {
982                uses_chroma_lr = true;
983            }
984        }
985    }
986    lr.uses_lr = uses_lr;
987    if uses_lr {
988        let mut lr_unit_shift;
989        if seq.use_128x128_superblock {
990            lr_unit_shift = r.f(1)? + 1;
991        } else {
992            lr_unit_shift = r.f(1)?;
993            if lr_unit_shift != 0 {
994                lr_unit_shift += r.f(1)?;
995            }
996        }
997        let size0 = RESTORATION_TILESIZE_MAX >> (2 - lr_unit_shift);
998        let lr_uv_shift =
999            if seq.color.subsampling_x == 1 && seq.color.subsampling_y == 1 && uses_chroma_lr {
1000                r.f(1)?
1001            } else {
1002                0
1003            };
1004        lr.unit_size[0] = size0;
1005        if let Some(s) = lr.unit_size.get_mut(1) {
1006            *s = size0 >> lr_uv_shift;
1007        }
1008        if let Some(s) = lr.unit_size.get_mut(2) {
1009            *s = size0 >> lr_uv_shift;
1010        }
1011    }
1012    Ok(lr)
1013}
1014
1015fn parse_film_grain(
1016    r: &mut BitReader<'_>,
1017    seq: &SequenceHeader,
1018    frame_type: u8,
1019    show_frame: bool,
1020    showable_frame: bool,
1021) -> Result<FilmGrain> {
1022    if !seq.film_grain_params_present || (!show_frame && !showable_frame) {
1023        return Ok(FilmGrain::default());
1024    }
1025    let apply_grain = r.flag()?;
1026    if !apply_grain {
1027        return Ok(FilmGrain::default());
1028    }
1029    let grain_seed = r.f(16)? as u16;
1030    let update_grain = if frame_type == INTER_FRAME {
1031        r.flag()?
1032    } else {
1033        true
1034    };
1035    if !update_grain {
1036        // film_grain_params_ref_idx f(3); the rest loads from that reference.
1037        r.f(3)?;
1038        return Ok(FilmGrain {
1039            apply_grain,
1040            grain_seed,
1041        });
1042    }
1043
1044    let num_y_points = r.f(4)?;
1045    for _ in 0..num_y_points {
1046        r.f(8)?; // point_y_value
1047        r.f(8)?; // point_y_scaling
1048    }
1049    let chroma_scaling_from_luma = if seq.color.mono_chrome {
1050        false
1051    } else {
1052        r.flag()?
1053    };
1054    let (num_cb_points, num_cr_points);
1055    if seq.color.mono_chrome
1056        || chroma_scaling_from_luma
1057        || (seq.color.subsampling_x == 1 && seq.color.subsampling_y == 1 && num_y_points == 0)
1058    {
1059        num_cb_points = 0;
1060        num_cr_points = 0;
1061    } else {
1062        let cb = r.f(4)?;
1063        for _ in 0..cb {
1064            r.f(8)?;
1065            r.f(8)?;
1066        }
1067        let cr = r.f(4)?;
1068        for _ in 0..cr {
1069            r.f(8)?;
1070            r.f(8)?;
1071        }
1072        num_cb_points = cb;
1073        num_cr_points = cr;
1074    }
1075    r.f(2)?; // grain_scaling_minus_8
1076    let ar_coeff_lag = r.f(2)?;
1077    let num_pos_luma = 2 * ar_coeff_lag * (ar_coeff_lag + 1);
1078    let num_pos_chroma = if num_y_points > 0 {
1079        for _ in 0..num_pos_luma {
1080            r.f(8)?;
1081        }
1082        num_pos_luma + 1
1083    } else {
1084        num_pos_luma
1085    };
1086    if chroma_scaling_from_luma || num_cb_points > 0 {
1087        for _ in 0..num_pos_chroma {
1088            r.f(8)?;
1089        }
1090    }
1091    if chroma_scaling_from_luma || num_cr_points > 0 {
1092        for _ in 0..num_pos_chroma {
1093            r.f(8)?;
1094        }
1095    }
1096    r.f(2)?; // ar_coeff_shift_minus_6
1097    r.f(2)?; // grain_scale_shift
1098    if num_cb_points > 0 {
1099        r.f(8)?; // cb_mult
1100        r.f(8)?; // cb_luma_mult
1101        r.f(9)?; // cb_offset
1102    }
1103    if num_cr_points > 0 {
1104        r.f(8)?;
1105        r.f(8)?;
1106        r.f(9)?;
1107    }
1108    r.f(1)?; // overlap_flag
1109    r.f(1)?; // clip_to_restricted_range
1110
1111    Ok(FilmGrain {
1112        apply_grain,
1113        grain_seed,
1114    })
1115}
1116
1117#[cfg(test)]
1118#[allow(
1119    clippy::unwrap_used,
1120    clippy::indexing_slicing,
1121    clippy::panic,
1122    reason = "tests operate on known-good values and assert shapes directly"
1123)]
1124mod tests {
1125    use super::*;
1126
1127    #[test]
1128    fn segmentation_derives_pre_skip_and_the_last_active_segment() {
1129        let mut seg = Segmentation::disabled();
1130        seg.feature_enabled[2][SEG_LVL_ALT_Q] = true;
1131        seg.feature_data[2][SEG_LVL_ALT_Q] = -7;
1132        // Disabled, nothing is active whatever the arrays hold.
1133        assert_eq!(seg.feature_value(2, SEG_LVL_ALT_Q), 0);
1134        assert_eq!(seg.last_active_segment(), 0);
1135
1136        seg.enabled = true;
1137        assert_eq!(seg.feature_value(2, SEG_LVL_ALT_Q), -7);
1138        assert_eq!(seg.last_active_segment(), 2);
1139        assert!(!seg.pre_skip(), "a quantizer offset is read after skip");
1140
1141        // SEG_LVL_SKIP (6) is at or past SEG_LVL_REF_FRAME, so segment_id
1142        // moves ahead of skip.
1143        seg.feature_enabled[5][6] = true;
1144        assert!(seg.pre_skip());
1145        assert_eq!(seg.last_active_segment(), 5);
1146    }
1147
1148    /// Bit-level builder mirroring the syntax, shared shape with seq.rs tests.
1149    struct Bldr {
1150        bits: Vec<u8>,
1151    }
1152    impl Bldr {
1153        fn new() -> Self {
1154            Self { bits: Vec::new() }
1155        }
1156        fn put(&mut self, value: u32, n: u32) -> &mut Self {
1157            for i in (0..n).rev() {
1158                self.bits.push(((value >> i) & 1) as u8);
1159            }
1160            self
1161        }
1162        fn flag(&mut self, b: bool) -> &mut Self {
1163            self.put(u32::from(b), 1)
1164        }
1165        fn pack(&self) -> Vec<u8> {
1166            let mut out = vec![0_u8; self.bits.len().div_ceil(8)];
1167            for (i, &bit) in self.bits.iter().enumerate() {
1168                if bit != 0 {
1169                    out[i / 8] |= 1 << (7 - (i % 8));
1170                }
1171            }
1172            out
1173        }
1174    }
1175
1176    /// A reduced-still-picture sequence header for an 8-bit 4:2:0 image, with
1177    /// CDEF and restoration disabled so the frame header stays compact.
1178    fn seq_reduced(width: u32, height: u32) -> SequenceHeader {
1179        let mut b = Bldr::new();
1180        b.put(0, 3).flag(true).flag(true).put(1, 5);
1181        b.put(15, 4)
1182            .put(15, 4)
1183            .put(width - 1, 16)
1184            .put(height - 1, 16);
1185        b.flag(false).flag(false).flag(false); // sb / filter-intra / edge
1186        b.flag(false).flag(false).flag(false); // superres / cdef / restoration
1187        b.flag(false).flag(false).flag(false); // hbd / mono / colordesc
1188        b.flag(false).put(0, 2).flag(false); // range / chroma pos / uv delta q
1189        b.flag(false); // film grain
1190        let bytes = b.pack();
1191        let mut r = BitReader::new(&bytes);
1192        SequenceHeader::parse(&mut r).unwrap()
1193    }
1194
1195    #[test]
1196    fn parses_a_key_frame_header() {
1197        let seq = seq_reduced(64, 64);
1198        // Reduced header: frame_type/show/error implied. First read is
1199        // disable_cdf_update, then (force_screen_content_tools == SELECT) so
1200        // allow_screen_content_tools f(1), then force_integer_mv only if sct.
1201        let mut b = Bldr::new();
1202        b.flag(false); // disable_cdf_update
1203        b.flag(false); // allow_screen_content_tools = 0
1204        // frame_size_override = 0 (reduced), order_hint 0 bits, refresh all.
1205        // frame_size: enable_superres=0 so no bit. render_size:
1206        b.flag(false); // render_and_frame_size_different = 0
1207        // allow_intrabc gated off (sct=0). disable_frame_end_update_cdf implied.
1208        // tile_info: 64x64 -> 16x16 mi -> 1 superblock. uniform_tile_spacing:
1209        b.flag(true); // uniform_tile_spacing
1210        // min==max log2 cols/rows == 0, so no increment bits, no ids.
1211        // quantization: base_q_idx f(8) = 100.
1212        b.put(100, 8);
1213        b.flag(false); // delta_q_y_dc coded = 0
1214        b.flag(false); // diff not read (separate_uv=0) -> u_dc coded
1215        b.flag(false); // u_ac coded = 0
1216        b.flag(false); // using_qmatrix = 0
1217        // segmentation:
1218        b.flag(false); // segmentation_enabled = 0
1219        // delta_q: base>0 so delta_q_present f(1)
1220        b.flag(false); // delta_q_present = 0
1221        // delta_lf gated by delta_q_present=0 -> nothing.
1222        // loop_filter (not lossless, not intrabc): level[0] f(6), level[1] f(6)
1223        b.put(0, 6).put(0, 6); // both 0 -> chroma levels skipped
1224        b.put(0, 3); // sharpness
1225        b.flag(false); // delta_enabled = 0
1226        // cdef disabled in seq -> nothing. lr disabled -> nothing.
1227        // tx_mode: not lossless -> tx_mode_select f(1)
1228        b.flag(false); // tx_mode_select = 0 -> Largest
1229        // reference/skip/warp: nothing. reduced_tx_set f(1):
1230        b.flag(false);
1231        // film grain not present -> nothing.
1232        let bytes = b.pack();
1233        let mut r = BitReader::new(&bytes);
1234        let fh = FrameHeader::parse(&mut r, &seq, 0, 0).unwrap();
1235        assert_eq!(fh.frame_type, KEY_FRAME);
1236        assert!(fh.show_frame);
1237        assert_eq!(fh.frame_width, 64);
1238        assert_eq!(fh.frame_height, 64);
1239        assert_eq!(fh.mi_cols, 16);
1240        assert_eq!(fh.mi_rows, 16);
1241        assert_eq!(fh.quantization.base_q_idx, 100);
1242        assert!(!fh.coded_lossless);
1243        assert_eq!(fh.tile_info.count(), 1);
1244        assert_eq!(fh.tx_mode, TxMode::Largest);
1245    }
1246
1247    #[test]
1248    fn a_zero_quantizer_is_coded_lossless_and_forces_only_4x4() {
1249        let seq = seq_reduced(32, 32);
1250        let mut b = Bldr::new();
1251        b.flag(false); // disable_cdf_update
1252        b.flag(false); // allow_screen_content_tools
1253        b.flag(false); // render size differ
1254        b.flag(true); // uniform tile spacing
1255        b.put(0, 8); // base_q_idx = 0 -> lossless
1256        b.flag(false); // delta_q_y_dc coded
1257        b.flag(false); // u_dc coded
1258        b.flag(false); // u_ac coded
1259        b.flag(false); // using_qmatrix
1260        b.flag(false); // segmentation_enabled
1261        // base_q_idx == 0 -> delta_q_present not read.
1262        // CodedLossless -> loop_filter reads nothing, cdef nothing, lr nothing.
1263        // tx_mode: CodedLossless -> Only4x4, no bit.
1264        b.flag(false); // reduced_tx_set
1265        let bytes = b.pack();
1266        let mut r = BitReader::new(&bytes);
1267        let fh = FrameHeader::parse(&mut r, &seq, 0, 0).unwrap();
1268        assert!(fh.coded_lossless);
1269        assert!(fh.all_lossless);
1270        assert_eq!(fh.tx_mode, TxMode::Only4x4);
1271        assert!(fh.lossless[0]);
1272    }
1273
1274    #[test]
1275    fn tile_log2_is_the_smallest_covering_shift() {
1276        assert_eq!(tile_log2(1, 1), 0);
1277        assert_eq!(tile_log2(1, 2), 1);
1278        assert_eq!(tile_log2(1, 3), 2);
1279        assert_eq!(tile_log2(1, 4), 2);
1280        assert_eq!(tile_log2(4, 16), 2);
1281    }
1282}