use super::config::ScanSearchConfig;
#[derive(Debug, Clone, Copy)]
pub(crate) struct TrialScan {
pub component: u8,
pub ss: u8,
pub se: u8,
pub ah: u8,
pub al: u8,
pub comps_in_scan: u8,
}
impl TrialScan {
fn dc_all(num_components: u8) -> Self {
Self {
component: 0,
ss: 0,
se: 0,
ah: 0,
al: 0,
comps_in_scan: num_components,
}
}
fn dc_single(component: u8) -> Self {
Self {
component,
ss: 0,
se: 0,
ah: 0,
al: 0,
comps_in_scan: 1,
}
}
fn dc_pair(c1: u8, _c2: u8) -> Self {
Self {
component: c1,
ss: 0,
se: 0,
ah: 0,
al: 0,
comps_in_scan: 2,
}
}
fn ac(component: u8, ss: u8, se: u8, ah: u8, al: u8) -> Self {
Self {
component,
ss,
se,
ah,
al,
comps_in_scan: 1,
}
}
pub fn is_dc(&self) -> bool {
self.ss == 0 && self.se == 0
}
}
pub(crate) fn generate_search_scans(
num_components: u8,
config: &ScanSearchConfig,
) -> Vec<TrialScan> {
if num_components == 1 {
return generate_grayscale_search_scans(config);
}
let mut scans = Vec::with_capacity(64);
if config.dc_scan_opt_mode == 0 {
scans.push(TrialScan::dc_all(num_components));
} else {
scans.push(TrialScan::dc_single(0));
}
scans.push(TrialScan::ac(0, 1, 8, 0, 0));
scans.push(TrialScan::ac(0, 9, 63, 0, 0));
for al in 0..config.al_max_luma {
scans.push(TrialScan::ac(0, 1, 63, al + 1, al)); scans.push(TrialScan::ac(0, 1, 8, 0, al + 1)); scans.push(TrialScan::ac(0, 9, 63, 0, al + 1)); }
scans.push(TrialScan::ac(0, 1, 63, 0, 0));
for &split in &config.frequency_splits {
scans.push(TrialScan::ac(0, 1, split, 0, 0));
scans.push(TrialScan::ac(0, split + 1, 63, 0, 0));
}
if num_components >= 3 {
scans.push(TrialScan::dc_pair(1, 2)); scans.push(TrialScan::dc_single(1)); scans.push(TrialScan::dc_single(2));
scans.push(TrialScan::ac(1, 1, 8, 0, 0));
scans.push(TrialScan::ac(1, 9, 63, 0, 0));
scans.push(TrialScan::ac(2, 1, 8, 0, 0));
scans.push(TrialScan::ac(2, 9, 63, 0, 0));
for al in 0..config.al_max_chroma {
scans.push(TrialScan::ac(1, 1, 63, al + 1, al)); scans.push(TrialScan::ac(2, 1, 63, al + 1, al)); scans.push(TrialScan::ac(1, 1, 8, 0, al + 1)); scans.push(TrialScan::ac(1, 9, 63, 0, al + 1)); scans.push(TrialScan::ac(2, 1, 8, 0, al + 1)); scans.push(TrialScan::ac(2, 9, 63, 0, al + 1)); }
scans.push(TrialScan::ac(1, 1, 63, 0, 0));
scans.push(TrialScan::ac(2, 1, 63, 0, 0));
for &split in &config.frequency_splits {
scans.push(TrialScan::ac(1, 1, split, 0, 0));
scans.push(TrialScan::ac(1, split + 1, 63, 0, 0));
scans.push(TrialScan::ac(2, 1, split, 0, 0));
scans.push(TrialScan::ac(2, split + 1, 63, 0, 0));
}
}
scans
}
fn generate_grayscale_search_scans(config: &ScanSearchConfig) -> Vec<TrialScan> {
let mut scans = Vec::with_capacity(23);
scans.push(TrialScan::dc_single(0));
scans.push(TrialScan::ac(0, 1, 8, 0, 0));
scans.push(TrialScan::ac(0, 9, 63, 0, 0));
for al in 0..config.al_max_luma {
scans.push(TrialScan::ac(0, 1, 63, al + 1, al));
scans.push(TrialScan::ac(0, 1, 8, 0, al + 1));
scans.push(TrialScan::ac(0, 9, 63, 0, al + 1));
}
scans.push(TrialScan::ac(0, 1, 63, 0, 0));
for &split in &config.frequency_splits {
scans.push(TrialScan::ac(0, 1, split, 0, 0));
scans.push(TrialScan::ac(0, split + 1, 63, 0, 0));
}
scans
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_ycbcr_64_scans() {
let config = ScanSearchConfig::default();
let scans = generate_search_scans(3, &config);
assert_eq!(scans.len(), 64, "YCbCr should generate 64 scans");
}
#[test]
fn test_generate_grayscale_23_scans() {
let config = ScanSearchConfig::default();
let scans = generate_search_scans(1, &config);
assert_eq!(scans.len(), 23, "Grayscale should generate 23 scans");
}
#[test]
fn test_luma_scan_layout() {
let config = ScanSearchConfig::default();
let scans = generate_search_scans(3, &config);
assert!(scans[0].is_dc());
assert_eq!(scans[0].comps_in_scan, 1);
assert_eq!((scans[1].ss, scans[1].se), (1, 8));
assert_eq!((scans[1].ah, scans[1].al), (0, 0));
assert_eq!((scans[2].ss, scans[2].se), (9, 63));
assert_eq!((scans[2].ah, scans[2].al), (0, 0));
assert_eq!((scans[3].ss, scans[3].se), (1, 63));
assert_eq!((scans[3].ah, scans[3].al), (1, 0));
assert_eq!((scans[4].ss, scans[4].se), (1, 8));
assert_eq!((scans[4].ah, scans[4].al), (0, 1));
assert_eq!((scans[12].ss, scans[12].se), (1, 63));
assert_eq!((scans[12].ah, scans[12].al), (0, 0));
}
#[test]
fn test_chroma_scan_layout() {
let config = ScanSearchConfig::default();
let scans = generate_search_scans(3, &config);
assert!(scans[23].is_dc());
assert_eq!(scans[23].comps_in_scan, 2);
assert!(scans[24].is_dc());
assert_eq!(scans[24].component, 1);
assert_eq!(scans[24].comps_in_scan, 1);
assert!(scans[25].is_dc());
assert_eq!(scans[25].component, 2);
assert_eq!(scans[25].comps_in_scan, 1);
assert_eq!(scans[26].component, 1);
assert_eq!((scans[26].ss, scans[26].se), (1, 8));
assert_eq!(scans[27].component, 1);
assert_eq!((scans[27].ss, scans[27].se), (9, 63));
assert_eq!(scans[28].component, 2);
assert_eq!(scans[29].component, 2);
}
#[test]
fn test_all_luma_scans_are_component_0() {
let config = ScanSearchConfig::default();
let scans = generate_search_scans(3, &config);
for (i, scan) in scans[1..23].iter().enumerate() {
assert_eq!(
scan.component,
0,
"Luma scan {} (index {}) should be component 0",
i + 1,
i + 1
);
}
}
}