blob: a3d685b6546c921f319c413071ab1559484b587f [file] [log] [blame]
// Copyright (c) 2024, Alliance for Open Media. All rights reserved
//
// This source code is subject to the terms of the BSD 2 Clause License and
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
// was not distributed with this source code in the LICENSE file, you can
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
// Media Patent License 1.0 was not distributed with this source code in the
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
use avifinfo::{get_features, identify, AvifInfoError, Features};
use std::{fs::File, io::Read};
#[cfg(test)]
fn load_file(path: &str) -> Vec<u8> {
let mut bytes = Vec::new();
File::open(path).unwrap().read_to_end(&mut bytes).unwrap();
bytes
}
//------------------------------------------------------------------------------
// Positive tests
#[test]
fn single_pixel() {
let file = load_file("tests/avifinfo_test_1x1.avif");
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(
get_features(file.as_slice()),
Ok(Features {
width: 1,
height: 1,
bit_depth: 8,
num_channels: 3,
has_gainmap: false,
gainmap_item_id: 0,
primary_item_id_location: 96,
primary_item_id_bytes: 2,
})
);
}
#[test]
fn with_alpha() {
let file = load_file("tests/avifinfo_test_2x2_alpha.avif");
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(
get_features(file.as_slice()),
Ok(Features {
width: 2,
height: 2,
bit_depth: 8,
num_channels: 4,
has_gainmap: false,
gainmap_item_id: 0,
primary_item_id_location: 96,
primary_item_id_bytes: 2,
})
);
}
#[test]
fn with_gainmap() {
let file = load_file("tests/avifinfo_test_20x20_gainmap.avif");
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(
get_features(file.as_slice()),
Ok(Features {
width: 20,
height: 20,
bit_depth: 8,
num_channels: 3,
has_gainmap: true,
gainmap_item_id: 2,
primary_item_id_location: 96,
primary_item_id_bytes: 2,
})
);
}
#[test]
fn set_primary_item_id_to_be_gainmap_item_id() {
let mut file = load_file("tests/avifinfo_test_20x20_gainmap.avif");
file[97] = 2;
// TODO(maryla-uc): find a small test file with a gainmap that is smaller
// than the main image.
assert_eq!(
get_features(file.as_slice()),
Ok(Features {
width: 20,
height: 20,
bit_depth: 8,
num_channels: 1, // the gainmap is monochrome
has_gainmap: true,
gainmap_item_id: 2,
primary_item_id_location: 96,
primary_item_id_bytes: 2,
})
);
}
#[test]
fn with_gainmap_tmap() {
for file_path in [
"tests/avifinfo_test_12x34_gainmap_tmap.avif",
"tests/avifinfo_test_12x34_gainmap_tmap_iref_after_iprp.avif",
] {
let file = load_file(file_path);
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(
get_features(file.as_slice()),
Ok(Features {
width: 12,
height: 34,
bit_depth: 10,
num_channels: 4,
has_gainmap: true,
gainmap_item_id: 4,
primary_item_id_location: 96,
primary_item_id_bytes: 2,
})
);
}
}
#[test]
fn no_pixi_10b() {
// Same as above but "meta" box size is stored as 64 bits, "av1C" has
// 'high_bitdepth' set to true, "pixi" was renamed to "pixy" and "mdat" size
// is 0 (extends to the end of the file).
let file = load_file("tests/avifinfo_test_1x1_10b_nopixi_metasize64b_mdatsize0.avif");
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(
get_features(file.as_slice()),
Ok(Features {
width: 1,
height: 1,
bit_depth: 10,
num_channels: 3,
has_gainmap: false,
gainmap_item_id: 0,
primary_item_id_location: 104,
primary_item_id_bytes: 2,
})
);
}
#[test]
fn enough_bytes() {
let mut file = load_file("tests/avifinfo_test_1x1.avif");
// Truncate 'input' just after the required information (discard AV1 box).
let mdat_position = file.windows(4).position(|window| window == b"mdat");
file.resize(mdat_position.unwrap(), 0);
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(
get_features(file.as_slice()),
Ok(Features {
width: 1,
height: 1,
bit_depth: 8,
num_channels: 3,
has_gainmap: false,
gainmap_item_id: 0,
primary_item_id_location: 96,
primary_item_id_bytes: 2,
})
);
}
#[test]
fn metabox_is_big() {
let mut file = load_file("tests/avifinfo_test_1x1.avif");
let meta_position = file.windows(4).position(|window| window == b"meta");
// 32-bit "1" then 4-char "meta" then 64-bit size.
file[meta_position.unwrap() - 4] = 0;
file[meta_position.unwrap() - 3] = 0;
file[meta_position.unwrap() - 2] = 0;
file[meta_position.unwrap() - 1] = 1;
for _ in 0..8 {
file.insert(meta_position.unwrap() + 4, 1);
}
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(
get_features(file.as_slice()),
Ok(Features {
width: 1,
height: 1,
bit_depth: 8,
num_channels: 3,
has_gainmap: false,
gainmap_item_id: 0,
primary_item_id_location: 104,
primary_item_id_bytes: 2,
})
);
}
#[test]
fn metabox_runs_till_end_of_file() {
let mut file = load_file("tests/avifinfo_test_1x1.avif");
let meta_position = file.windows(4).position(|window| window == b"meta");
// 32-bit "0" then 4-char "meta".
file[meta_position.unwrap() - 4] = 0;
file[meta_position.unwrap() - 3] = 0;
file[meta_position.unwrap() - 2] = 0;
file[meta_position.unwrap() - 1] = 0;
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(
get_features(file.as_slice()),
Ok(Features {
width: 1,
height: 1,
bit_depth: 8,
num_channels: 3,
has_gainmap: false,
gainmap_item_id: 0,
primary_item_id_location: 96,
primary_item_id_bytes: 2,
})
);
}
//------------------------------------------------------------------------------
// Negative tests
#[test]
fn empty() {
assert_eq!(identify(&[]), Err(AvifInfoError::NotEnoughData));
assert_eq!(get_features(&[]), Err(AvifInfoError::NotEnoughData));
}
#[test]
fn not_enough_bytes() {
let mut file = load_file("tests/avifinfo_test_1x1.avif");
// Truncate 'input' before having all the required information.
let ipma_position = file.windows(4).position(|window| window == b"ipma");
file.resize(ipma_position.unwrap(), 0);
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(get_features(file.as_slice()), Err(AvifInfoError::NotEnoughData));
}
#[test]
fn broken() {
let mut file = load_file("tests/avifinfo_test_1x1.avif");
// Change "ispe" to "aspe".
let ispe_position = file.windows(4).position(|window| window == b"ispe");
file[ispe_position.unwrap()] = b'a';
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(get_features(file.as_slice()), Err(AvifInfoError::InvalidFile));
}
#[test]
fn metabox_is_too_big() {
let mut file = load_file("tests/avifinfo_test_1x1.avif");
let meta_position = file.windows(4).position(|window| window == b"meta");
// 32-bit "1" then 4-char "meta" then 64-bit size.
file[meta_position.unwrap() - 4] = 0;
file[meta_position.unwrap() - 3] = 0;
file[meta_position.unwrap() - 2] = 0;
file[meta_position.unwrap() - 1] = 1;
for _ in 0..8 {
file.insert(meta_position.unwrap() + 4, 255);
}
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(get_features(file.as_slice()), Err(AvifInfoError::TooComplex));
}
#[test]
fn filetypebox_runs_till_end_of_file() {
let mut file = load_file("tests/avifinfo_test_1x1.avif");
let ftyp_position = file.windows(4).position(|window| window == b"ftyp");
file[ftyp_position.unwrap() - 1] = 0;
assert_eq!(identify(file.as_slice()), Err(AvifInfoError::InvalidFile));
assert_eq!(get_features(file.as_slice()), Err(AvifInfoError::InvalidFile));
}
#[test]
fn imagespatialextentsproperty_runs_till_end_of_file() {
let mut file = load_file("tests/avifinfo_test_1x1.avif");
let ispe_position = file.windows(4).position(|window| window == b"ispe");
file[ispe_position.unwrap() - 1] = 0;
assert_eq!(identify(file.as_slice()), Ok(()));
assert_eq!(get_features(file.as_slice()), Err(AvifInfoError::InvalidFile));
}
#[test]
fn too_many_boxes() {
// Create a valid-ish input with too many boxes to parse.
let mut input: Vec<u8> =
vec![0, 0, 0, 16, b'f', b't', b'y', b'p', b'a', b'v', b'i', b'f', 0, 0, 0, 0];
let num_boxes = 12345;
input.reserve(input.len() + num_boxes * 8);
for _ in 0..num_boxes {
input.extend_from_slice(&[0, 0, 0, 8, b'a', b'b', b'c', b'd']);
}
assert_eq!(identify(input.as_slice()), Ok(()));
assert_eq!(get_features(input.as_slice()), Err(AvifInfoError::TooComplex));
}