Coverage Report

Created: 2025-07-12 07:18

/rust/registry/src/index.crates.io-6f17d22bba15001f/av1-grain-0.2.4/src/diff.rs
Line
Count
Source (jump to first uncovered line)
1
use anyhow::{ensure, Result};
2
use num_rational::Rational64;
3
use v_frame::{frame::Frame, pixel::Pixel};
4
5
use self::solver::{FlatBlockFinder, NoiseModel};
6
use crate::{util::frame_into_u8, GrainTableSegment};
7
8
mod solver;
9
10
const BLOCK_SIZE: usize = 32;
11
const BLOCK_SIZE_SQUARED: usize = BLOCK_SIZE * BLOCK_SIZE;
12
13
pub struct DiffGenerator {
14
    fps: Rational64,
15
    source_bit_depth: usize,
16
    denoised_bit_depth: usize,
17
    frame_count: usize,
18
    prev_timestamp: u64,
19
    flat_block_finder: FlatBlockFinder,
20
    noise_model: NoiseModel,
21
    grain_table: Vec<GrainTableSegment>,
22
}
23
24
impl DiffGenerator {
25
    #[must_use]
26
0
    pub fn new(fps: Rational64, source_bit_depth: usize, denoised_bit_depth: usize) -> Self {
27
0
        Self {
28
0
            frame_count: 0,
29
0
            fps,
30
0
            flat_block_finder: FlatBlockFinder::new(),
31
0
            noise_model: NoiseModel::new(),
32
0
            grain_table: Vec::new(),
33
0
            prev_timestamp: 0,
34
0
            source_bit_depth,
35
0
            denoised_bit_depth,
36
0
        }
37
0
    }
38
39
    /// Processes the next frame and adds the results to the state of this
40
    /// `DiffGenerator`.
41
    ///
42
    /// # Errors
43
    /// - If the frames do not have the same resolution
44
    /// - If the frames do not have the same chroma subsampling
45
0
    pub fn diff_frame<T: Pixel, U: Pixel>(
46
0
        &mut self,
47
0
        source: &Frame<T>,
48
0
        denoised: &Frame<U>,
49
0
    ) -> Result<()> {
50
0
        self.diff_frame_internal(
51
0
            &frame_into_u8(source, self.source_bit_depth),
52
0
            &frame_into_u8(denoised, self.denoised_bit_depth),
53
0
        )
54
0
    }
55
56
    /// Finalize the state of this `DiffGenerator` and return the resulting
57
    /// grain table segments.
58
    #[must_use]
59
0
    pub fn finish(mut self) -> Vec<GrainTableSegment> {
60
0
        log::debug!("Updating final parameters");
61
0
        self.grain_table.push(
62
0
            self.noise_model
63
0
                .get_grain_parameters(self.prev_timestamp, i64::MAX as u64),
64
0
        );
65
0
66
0
        self.grain_table
67
0
    }
68
69
0
    fn diff_frame_internal(&mut self, source: &Frame<u8>, denoised: &Frame<u8>) -> Result<()> {
70
0
        verify_dimensions_match(source, denoised)?;
71
72
0
        let (flat_blocks, num_flat_blocks) = self.flat_block_finder.run(&source.planes[0]);
73
0
        log::debug!("Num flat blocks: {num_flat_blocks}");
74
75
0
        log::debug!("Updating noise model");
76
0
        let status = self.noise_model.update(source, denoised, &flat_blocks);
77
0
78
0
        if status == NoiseStatus::DifferentType {
79
0
            let cur_timestamp = self.frame_count as u64 * 10_000_000u64 * *self.fps.denom() as u64
80
0
                / *self.fps.numer() as u64;
81
0
            log::debug!(
82
0
                "Updating parameters for times {} to {}",
83
                self.prev_timestamp,
84
                cur_timestamp
85
            );
86
0
            self.grain_table.push(
87
0
                self.noise_model
88
0
                    .get_grain_parameters(self.prev_timestamp, cur_timestamp),
89
0
            );
90
0
            self.noise_model.save_latest();
91
0
            self.prev_timestamp = cur_timestamp;
92
0
        }
93
0
        log::debug!("Noise model updated for frame {}", self.frame_count);
94
0
        self.frame_count += 1;
95
0
96
0
        Ok(())
97
0
    }
98
}
99
100
#[derive(Debug)]
101
enum NoiseStatus {
102
    Ok,
103
    DifferentType,
104
    #[allow(dead_code)]
105
    Error(anyhow::Error),
106
}
107
108
impl PartialEq for NoiseStatus {
109
0
    fn eq(&self, other: &Self) -> bool {
110
0
        match (self, other) {
111
0
            (&Self::Error(_), &Self::Error(_)) => true,
112
0
            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
113
        }
114
0
    }
115
}
116
117
0
fn verify_dimensions_match(source: &Frame<u8>, denoised: &Frame<u8>) -> Result<()> {
118
0
    let res_1 = (source.planes[0].cfg.width, source.planes[0].cfg.height);
119
0
    let res_2 = (denoised.planes[0].cfg.width, denoised.planes[0].cfg.height);
120
0
    ensure!(
121
0
        res_1 == res_2,
122
0
        "Luma resolutions were not equal, {}x{} != {}x{}",
123
        res_1.0,
124
        res_1.1,
125
        res_2.0,
126
        res_2.1
127
    );
128
129
0
    let res_1 = (source.planes[1].cfg.width, source.planes[1].cfg.height);
130
0
    let res_2 = (denoised.planes[1].cfg.width, denoised.planes[1].cfg.height);
131
0
    ensure!(
132
0
        res_1 == res_2,
133
0
        "Chroma resolutions were not equal, {}x{} != {}x{}",
134
        res_1.0,
135
        res_1.1,
136
        res_2.0,
137
        res_2.1
138
    );
139
140
0
    Ok(())
141
0
}