pub use crate::decoder::*;
pub use crate::error::*;
pub use crate::marker::*;
pub use crate::parser::CodingProcess;
use std::fmt::Write;
mod byteorder;
mod decoder;
mod error;
mod marker;
mod parser;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Scans {
pub metadata_end: Option<usize>,
pub dc_end: Option<usize>,
pub good_scan_end: Option<usize>,
}
pub fn scans(input_file: &[u8]) -> Result<Scans> {
let mut decoder = Decoder::new(input_file);
let markers = decoder.decode()?;
let mut found = Scans::default();
let mut seen_sof = false;
let mut seen_dc = false;
let mut number_of_components = 3;
let mut is_progressive = false;
let mut dc_components_seen = [false; 3];
let mut luma_coeff_bits_missing = [255u8; 16];
for m in markers {
if seen_sof {
if found.metadata_end.is_none() {
found.metadata_end = Some(m.position);
if !is_progressive {
break;
}
} else if seen_dc {
if found.dc_end.is_none() {
found.dc_end = Some(m.position);
}
else if found.good_scan_end.is_none() && (m.position >= input_file.len()/2 || coeff_fill_factor(&luma_coeff_bits_missing) >= 91) {
found.good_scan_end = Some(m.position);
break;
}
}
}
match m.marker {
MarkerData::Frame(frame) => {
number_of_components = frame.components.len();
is_progressive = frame.coding_process == CodingProcess::DctProgressive;
seen_sof = true;
},
MarkerData::Scan(scan) => {
if *scan.spectral_selection.start() == 0 {
for comp in scan.component_indices.iter().copied() {
if let Some(seen) = dc_components_seen.get_mut(comp as usize) {
*seen = true;
}
}
}
if dc_components_seen.iter().take(number_of_components).all(|&v| v) {
seen_dc = true;
}
if scan.component_indices.contains(&0) {
for bit in scan.spectral_selection {
if (bit as usize) < luma_coeff_bits_missing.len() {
luma_coeff_bits_missing[bit as usize] = luma_coeff_bits_missing[bit as usize].min(scan.successive_approximation_low);
}
}
}
},
MarkerData::Other(_) => {},
}
}
Ok(found)
}
fn coeff_fill_factor(bits_missing: &[u8; 16]) -> u16 {
let mut coeff_fill_factor = 0;
for missing in bits_missing {
coeff_fill_factor += match missing {
0 => 12,
1 => 8,
2 => 5,
3 => 1,
_ => 0,
}
}
coeff_fill_factor
}
pub fn cf_priority_change_header(image: &[u8]) -> Result<(String, String)> {
cf_priority_change_header_from_scans(scans(image)?, image.len())
}
#[cfg(target_arch = "wasm32")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn cf_priority_change_header_wasm(image: &[u8]) -> Option<String> {
cf_priority_change_header(image).ok()
}
fn cf_priority_change_header_from_scans(s: Scans, image_len: usize) -> Result<(String, String)> {
let mut pri_header = "30/1";
let mut change_header = String::with_capacity(127);
if let Some(metadata_end) = s.metadata_end {
let fat_metadata_limit = ((image_len / 8) + 100).min(2000);
if metadata_end < fat_metadata_limit {
pri_header = "50/0";
}
let next_priority = match s.dc_end {
Some(n) if n < 25000 => "30/0",
Some(_) => "30/1",
None if image_len < 2000 => "30/n",
None => "20/n",
};
let _ = write!(change_header, "{}:{}", metadata_end, next_priority);
}
if let Some(dc_end) = s.dc_end {
if !change_header.is_empty() {change_header.push(',');}
let is_next_scan_small = s.good_scan_end.map_or(false, |s| s < 100_000);
let scan_concurrency = if is_next_scan_small { '1' } else { 'n' };
let _ = write!(change_header, "{}:20/{}", dc_end, scan_concurrency);
}
if let Some(good_scan_end) = s.good_scan_end {
if !change_header.is_empty() {change_header.push(',');}
let file_is_small = image_len < 100_000;
let scan_concurrency = if file_is_small { '1' } else { 'n' };
let _ = write!(change_header, "{}:10/{}", good_scan_end, scan_concurrency);
}
if !change_header.is_empty() {
Ok((String::from(pri_header), change_header))
} else {
Err(Error::Format("Can't find useful scans"))
}
}
#[cfg(test)]
fn s(a: &str, b: &str) -> (String, String) { (a.into(), b.into()) }
#[test]
fn test_baseline() {
assert!(cf_priority_change_header_from_scans(Scans {
metadata_end: None,
dc_end: None,
good_scan_end: None,
}, 100_000).is_err());
assert_eq!(s("50/0", "101:20/n"), cf_priority_change_header_from_scans(Scans {
metadata_end: Some(101),
dc_end: None,
good_scan_end: None,
}, 100_000).unwrap());
assert_eq!(s("50/0", "101:30/n"), cf_priority_change_header_from_scans(Scans {
metadata_end: Some(101),
dc_end: None,
good_scan_end: None,
}, 1000).unwrap());
assert_eq!(s("30/1", "9999:20/n"), cf_priority_change_header_from_scans(Scans {
metadata_end: Some(9999),
dc_end: None,
good_scan_end: None,
}, 100_000).unwrap());
assert_eq!(s("30/1", "1000:20/n"), cf_priority_change_header_from_scans(Scans {
metadata_end: Some(1000),
dc_end: None,
good_scan_end: None,
}, 3000).unwrap());
}
#[test]
fn test_progressive() {
assert_eq!(s("50/0", "1000:30/0,10000:20/n,100000:10/n"), cf_priority_change_header_from_scans(Scans {
metadata_end: Some(1_000),
dc_end: Some(10_000),
good_scan_end: Some(100_000),
}, 200_000).unwrap());
assert_eq!(s("30/1", "4000:30/0,10000:20/n,100000:10/n"), cf_priority_change_header_from_scans(Scans {
metadata_end: Some(4_000),
dc_end: Some(10_000),
good_scan_end: Some(100_000),
}, 200_000).unwrap());
assert_eq!(s("50/0", "1000:30/1,50000:20/n,100000:10/n"), cf_priority_change_header_from_scans(Scans {
metadata_end: Some(1_000),
dc_end: Some(50_000),
good_scan_end: Some(100_000),
}, 200_000).unwrap());
assert_eq!(s("50/0", "1000:30/0,10000:20/1,11000:10/n"), cf_priority_change_header_from_scans(Scans {
metadata_end: Some(1_000),
dc_end: Some(10_000),
good_scan_end: Some(11_000),
}, 200_000).unwrap());
}