blob: 186ded4b24f8a3a488327db1acc1ccab6dcbc2f7 [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crabby_avif::decoder::track::RepetitionCount;
use crabby_avif::decoder::CompressionFormat;
use crabby_avif::decoder::ImageContentType;
use crabby_avif::image::*;
use crabby_avif::reformat::rgb;
use crabby_avif::*;
#[path = "./mod.rs"]
mod tests;
use std::cell::RefCell;
use std::rc::Rc;
use tests::*;
// From avifalphanoispetest.cc
#[test]
fn alpha_no_ispe() {
// See https://github.com/AOMediaCodec/libavif/pull/745.
let mut decoder = get_decoder("alpha_noispe.avif");
// By default, non-strict files are refused.
assert!(matches!(
decoder.settings.strictness,
decoder::Strictness::All
));
let res = decoder.parse();
assert!(matches!(res, Err(AvifError::BmffParseFailed(_))));
// Allow this kind of file specifically.
decoder.settings.strictness =
decoder::Strictness::SpecificExclude(vec![decoder::StrictnessFlag::AlphaIspeRequired]);
let res = decoder.parse();
assert!(res.is_ok());
let image = decoder.image().expect("image was none");
assert!(image.alpha_present);
assert!(!image.image_sequence_track_present);
if !HAS_DECODER {
return;
}
let res = decoder.next_image();
assert!(res.is_ok());
let image = decoder.image().expect("image was none");
let alpha_plane = image.plane_data(Plane::A);
assert!(alpha_plane.is_some());
assert!(alpha_plane.unwrap().row_bytes > 0);
}
#[test]
fn alpha_premultiplied() {
let mut decoder = get_decoder("alpha_premultiplied.avif");
let res = decoder.parse();
assert!(res.is_ok());
let image = decoder.image().expect("image was none");
assert!(image.alpha_present);
assert!(image.alpha_premultiplied);
if !HAS_DECODER {
return;
}
let res = decoder.next_image();
assert!(res.is_ok());
let image = decoder.image().expect("image was none");
assert!(image.alpha_present);
assert!(image.alpha_premultiplied);
let alpha_plane = image.plane_data(Plane::A);
assert!(alpha_plane.is_some());
assert!(alpha_plane.unwrap().row_bytes > 0);
}
// From avifanimationtest.cc
#[test_case::test_case("colors-animated-8bpc.avif")]
#[test_case::test_case("colors-animated-8bpc-audio.avif")]
fn animated_image(filename: &str) {
let mut decoder = get_decoder(filename);
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert!(!image.alpha_present);
assert!(image.image_sequence_track_present);
assert_eq!(decoder.image_count(), 5);
assert_eq!(decoder.repetition_count(), RepetitionCount::Finite(0));
for i in 0..5 {
assert_eq!(decoder.nearest_keyframe(i), 0);
}
if !HAS_DECODER {
return;
}
for _ in 0..5 {
assert!(decoder.next_image().is_ok());
}
}
// From avifanimationtest.cc
#[test_case::test_case("colors-animated-8bpc.avif")]
#[test_case::test_case("colors-animated-8bpc-audio.avif")]
fn animated_image_with_source_set_to_primary_item(filename: &str) {
let mut decoder = get_decoder(filename);
decoder.settings.source = decoder::Source::PrimaryItem;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert!(!image.alpha_present);
// This will be reported as true irrespective of the preferred source.
assert!(image.image_sequence_track_present);
// imageCount is expected to be 1 because we are using primary item as the
// preferred source.
assert_eq!(decoder.image_count(), 1);
assert_eq!(decoder.repetition_count(), RepetitionCount::Finite(0));
if !HAS_DECODER {
return;
}
// Get the first (and only) image.
assert!(decoder.next_image().is_ok());
// Subsequent calls should not return anything since there is only one
// image in the preferred source.
assert!(decoder.next_image().is_err());
}
// From avifanimationtest.cc
#[test]
fn animated_image_with_alpha_and_metadata() {
let mut decoder = get_decoder("colors-animated-8bpc-alpha-exif-xmp.avif");
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert!(image.alpha_present);
assert!(image.image_sequence_track_present);
assert_eq!(decoder.image_count(), 5);
assert_eq!(decoder.repetition_count(), RepetitionCount::Infinite);
assert_eq!(image.exif.len(), 1126);
assert_eq!(image.xmp.len(), 3898);
if !HAS_DECODER {
return;
}
for _ in 0..5 {
assert!(decoder.next_image().is_ok());
}
}
#[test]
fn animated_image_with_depth_and_metadata() {
// Depth map data is not supported and should be ignored.
let mut decoder = get_decoder("colors-animated-8bpc-depth-exif-xmp.avif");
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert!(!image.alpha_present);
assert!(image.image_sequence_track_present);
assert_eq!(decoder.image_count(), 5);
assert_eq!(decoder.repetition_count(), RepetitionCount::Infinite);
assert_eq!(image.exif.len(), 1126);
assert_eq!(image.xmp.len(), 3898);
if !HAS_DECODER {
return;
}
for _ in 0..5 {
assert!(decoder.next_image().is_ok());
}
}
#[test]
fn animated_image_with_depth_and_metadata_source_set_to_primary_item() {
// Depth map data is not supported and should be ignored.
let mut decoder = get_decoder("colors-animated-8bpc-depth-exif-xmp.avif");
decoder.settings.source = decoder::Source::PrimaryItem;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert!(!image.alpha_present);
// This will be reported as true irrespective of the preferred source.
assert!(image.image_sequence_track_present);
// imageCount is expected to be 1 because we are using primary item as the
// preferred source.
assert_eq!(decoder.image_count(), 1);
assert_eq!(decoder.repetition_count(), RepetitionCount::Finite(0));
if !HAS_DECODER {
return;
}
// Get the first (and only) image.
assert!(decoder.next_image().is_ok());
// Subsequent calls should not return anything since there is only one
// image in the preferred source.
assert!(decoder.next_image().is_err());
}
// From avifkeyframetest.cc
#[test]
fn keyframes() {
let mut decoder = get_decoder("colors-animated-12bpc-keyframes-0-2-3.avif");
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert!(image.image_sequence_track_present);
assert_eq!(decoder.image_count(), 5);
// First frame is always a keyframe.
assert!(decoder.is_keyframe(0));
assert_eq!(decoder.nearest_keyframe(0), 0);
assert!(!decoder.is_keyframe(1));
assert_eq!(decoder.nearest_keyframe(1), 0);
assert!(decoder.is_keyframe(2));
assert_eq!(decoder.nearest_keyframe(2), 2);
assert!(decoder.is_keyframe(3));
assert_eq!(decoder.nearest_keyframe(3), 3);
assert!(!decoder.is_keyframe(4));
assert_eq!(decoder.nearest_keyframe(4), 3);
// Not an existing frame.
assert!(!decoder.is_keyframe(15));
assert_eq!(decoder.nearest_keyframe(15), 3);
}
// From avifdecodetest.cc
#[test]
fn color_grid_alpha_no_grid() {
// Test case from https://github.com/AOMediaCodec/libavif/issues/1203.
let mut decoder = get_decoder("color_grid_alpha_nogrid.avif");
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert!(image.alpha_present);
assert!(!image.image_sequence_track_present);
if !HAS_DECODER {
return;
}
let res = decoder.next_image();
assert!(res.is_ok());
let image = decoder.image().expect("image was none");
let alpha_plane = image.plane_data(Plane::A);
assert!(alpha_plane.is_some());
assert!(alpha_plane.unwrap().row_bytes > 0);
}
// From avifprogressivetest.cc
#[test_case::test_case("progressive_dimension_change.avif", 2, 256, 256; "progressive_dimension_change")]
#[test_case::test_case("progressive_layered_grid.avif", 2, 512, 256; "progressive_layered_grid")]
#[test_case::test_case("progressive_quality_change.avif", 2, 256, 256; "progressive_quality_change")]
#[test_case::test_case("progressive_same_layers.avif", 4, 256, 256; "progressive_same_layers")]
#[test_case::test_case("tiger_3layer_1res.avif", 3, 1216, 832; "tiger_3layer_1res")]
#[test_case::test_case("tiger_3layer_3res.avif", 3, 1216, 832; "tiger_3layer_3res")]
fn progressive(filename: &str, layer_count: u32, width: u32, height: u32) {
let mut filename_with_prefix = String::from("progressive/");
filename_with_prefix.push_str(filename);
let mut decoder = get_decoder(&filename_with_prefix);
decoder.settings.allow_progressive = false;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert!(matches!(
image.progressive_state,
decoder::ProgressiveState::Available
));
decoder.settings.allow_progressive = true;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert!(matches!(
image.progressive_state,
decoder::ProgressiveState::Active
));
assert_eq!(image.width, width);
assert_eq!(image.height, height);
assert_eq!(decoder.image_count(), layer_count);
if !HAS_DECODER {
return;
}
for _i in 0..decoder.image_count() {
let res = decoder.next_image();
assert!(res.is_ok());
let image = decoder.image().expect("image was none");
assert_eq!(image.width, width);
assert_eq!(image.height, height);
}
}
// From avifmetadatatest.cc
#[test]
fn decoder_parse_icc_exif_xmp() {
// Test case from https://github.com/AOMediaCodec/libavif/issues/1086.
let mut decoder = get_decoder("paris_icc_exif_xmp.avif");
decoder.settings.ignore_xmp = true;
decoder.settings.ignore_exif = true;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert_eq!(image.icc.len(), 596);
assert_eq!(image.icc[0], 0);
assert_eq!(image.icc[1], 0);
assert_eq!(image.icc[2], 2);
assert_eq!(image.icc[3], 84);
assert!(image.exif.is_empty());
assert!(image.xmp.is_empty());
decoder.settings.ignore_xmp = false;
decoder.settings.ignore_exif = false;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert_eq!(image.exif.len(), 1126);
assert_eq!(image.exif[0], 73);
assert_eq!(image.exif[1], 73);
assert_eq!(image.exif[2], 42);
assert_eq!(image.exif[3], 0);
assert_eq!(image.xmp.len(), 3898);
assert_eq!(image.xmp[0], 60);
assert_eq!(image.xmp[1], 63);
assert_eq!(image.xmp[2], 120);
assert_eq!(image.xmp[3], 112);
}
// From avifgainmaptest.cc
#[test]
fn color_grid_gainmap_different_grid() {
let mut decoder = get_decoder("color_grid_gainmap_different_grid.avif");
decoder.settings.image_content_to_decode = ImageContentType::All;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
// Color+alpha: 4x3 grid of 128x200 tiles.
assert_eq!(image.width, 128 * 4);
assert_eq!(image.height, 200 * 3);
assert_eq!(image.depth, 10);
// Gain map: 2x2 grid of 64x80 tiles.
assert!(decoder.gainmap_present());
assert_eq!(decoder.gainmap().image.width, 64 * 2);
assert_eq!(decoder.gainmap().image.height, 80 * 2);
assert_eq!(decoder.gainmap().image.depth, 8);
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.0, 6);
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.1, 2);
if !HAS_DECODER {
return;
}
let res = decoder.next_image();
assert!(res.is_ok());
assert!(decoder.gainmap().image.row_bytes[0] > 0);
}
// From avifgainmaptest.cc
#[test]
fn color_grid_alpha_grid_gainmap_nogrid() {
let mut decoder = get_decoder("color_grid_alpha_grid_gainmap_nogrid.avif");
decoder.settings.image_content_to_decode = ImageContentType::All;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
// Color+alpha: 4x3 grid of 128x200 tiles.
assert_eq!(image.width, 128 * 4);
assert_eq!(image.height, 200 * 3);
assert_eq!(image.depth, 10);
// Gain map: single image of size 64x80.
assert!(decoder.gainmap_present());
assert_eq!(decoder.gainmap().image.width, 64);
assert_eq!(decoder.gainmap().image.height, 80);
assert_eq!(decoder.gainmap().image.depth, 8);
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.0, 6);
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.1, 2);
if !HAS_DECODER {
return;
}
let res = decoder.next_image();
assert!(res.is_ok());
assert!(decoder.gainmap().image.row_bytes[0] > 0);
}
// From avifgainmaptest.cc
#[test]
fn color_nogrid_alpha_nogrid_gainmap_grid() {
let mut decoder = get_decoder("color_nogrid_alpha_nogrid_gainmap_grid.avif");
decoder.settings.image_content_to_decode = ImageContentType::All;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
// Color+alpha: single image of size 128x200.
assert_eq!(image.width, 128);
assert_eq!(image.height, 200);
assert_eq!(image.depth, 10);
// Gain map: 2x2 grid of 64x80 tiles.
assert!(decoder.gainmap_present());
assert_eq!(decoder.gainmap().image.width, 64 * 2);
assert_eq!(decoder.gainmap().image.height, 80 * 2);
assert_eq!(decoder.gainmap().image.depth, 8);
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.0, 6);
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.1, 2);
if !HAS_DECODER {
return;
}
let res = decoder.next_image();
assert!(res.is_ok());
assert!(decoder.gainmap().image.row_bytes[0] > 0);
}
// From avifgainmaptest.cc
#[test]
fn gainmap_oriented() {
let mut decoder = get_decoder("gainmap_oriented.avif");
decoder.settings.image_content_to_decode = ImageContentType::All;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert_eq!(image.irot_angle, Some(1));
assert_eq!(image.imir_axis, Some(0));
assert!(decoder.gainmap_present());
assert_eq!(decoder.gainmap().image.irot_angle, None);
assert_eq!(decoder.gainmap().image.imir_axis, None);
}
// The two test files should produce the same results:
// One has an unsupported 'version' field, the other an unsupported
// 'minimum_version' field, but the behavior of these two files is the same.
// From avifgainmaptest.cc
#[test_case::test_case("unsupported_gainmap_version.avif")]
#[test_case::test_case("unsupported_gainmap_minimum_version.avif")]
fn decode_unsupported_version(filename: &str) {
// Parse with various settings.
let mut decoder = get_decoder(filename);
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
// Gain map marked as not present because the metadata is not supported.
assert!(!decoder.gainmap_present());
assert_eq!(decoder.gainmap().image.width, 0);
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.0, 0);
assert_eq!(decoder.gainmap().metadata.alternate_hdr_headroom.0, 0);
decoder = get_decoder(filename);
decoder.settings.image_content_to_decode = ImageContentType::All;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
// Gainmap not found: its metadata is not supported.
assert!(!decoder.gainmap_present());
assert_eq!(decoder.gainmap().image.width, 0);
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.0, 0);
assert_eq!(decoder.gainmap().metadata.alternate_hdr_headroom.0, 0);
}
// From avifgainmaptest.cc
#[test]
fn decode_unsupported_writer_version_with_extra_bytes() {
let mut decoder = get_decoder("unsupported_gainmap_writer_version_with_extra_bytes.avif");
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
// Decodes successfully: there are extra bytes at the end of the gain map
// metadata but that's expected as the writer_version field is higher
// that supported.
assert!(decoder.gainmap_present());
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.0, 6);
assert_eq!(decoder.gainmap().metadata.base_hdr_headroom.1, 2);
}
// From avifgainmaptest.cc
#[test]
fn decode_supported_writer_version_with_extra_bytes() {
let mut decoder = get_decoder("supported_gainmap_writer_version_with_extra_bytes.avif");
let res = decoder.parse();
// Fails to decode: there are extra bytes at the end of the gain map metadata
// that shouldn't be there.
assert!(matches!(res, Err(AvifError::InvalidToneMappedImage(_))));
}
// From avifgainmaptest.cc
#[test]
fn decode_ignore_gain_map_but_read_metadata() {
let mut decoder = get_decoder("seine_sdr_gainmap_srgb.avif");
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
decoder.image().expect("image was none");
// Gain map not decoded.
assert!(decoder.gainmap_present());
// ... but not decoded because enableDecodingGainMap is false by default.
assert_eq!(decoder.gainmap().image.width, 0);
assert_eq!(decoder.gainmap().image.row_bytes[0], 0);
// Check that the gain map metadata WAS populated.
assert_eq!(decoder.gainmap().metadata.alternate_hdr_headroom.0, 13);
assert_eq!(decoder.gainmap().metadata.alternate_hdr_headroom.1, 10);
}
// From avifgainmaptest.cc
#[test]
fn decode_ignore_color_and_alpha() {
let mut decoder = get_decoder("seine_sdr_gainmap_srgb.avif");
decoder.settings.image_content_to_decode = ImageContentType::GainMap;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
// Main image metadata is available.
assert_eq!(image.width, 400);
// The gain map metadata is available.
assert!(decoder.gainmap_present());
assert_eq!(decoder.gainmap().image.width, 400);
assert_eq!(decoder.gainmap().metadata.alternate_hdr_headroom.0, 13);
if !HAS_DECODER {
return;
}
let res = decoder.next_image();
let image = decoder.image().expect("image was none");
assert!(res.is_ok());
// Main image pixels are not available.
assert_eq!(image.row_bytes[0], 0);
// Gain map pixels are available.
assert!(decoder.gainmap().image.row_bytes[0] > 0);
}
// From avifgainmaptest.cc
#[test_case::test_case("paris_icc_exif_xmp.avif")]
#[test_case::test_case("sofa_grid1x5_420.avif")]
#[test_case::test_case("color_grid_alpha_nogrid.avif")]
#[test_case::test_case("seine_sdr_gainmap_srgb.avif")]
fn decode_ignore_all(filename: &str) {
let mut decoder = get_decoder(filename);
// Ignore both the main image and the gain map.
decoder.settings.image_content_to_decode = ImageContentType::None;
// But do read the gain map metadata
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
// Main image metadata is available.
assert!(image.width > 0);
// But trying to access the next image should give an error because both
// ignoreColorAndAlpha and enableDecodingGainMap are set.
let res = decoder.next_image();
assert!(res.is_err());
}
// From avifcllitest.cc
#[test_case::test_case("clli_0_0.avif", 0, 0; "clli_0_0")]
#[test_case::test_case("clli_0_1.avif", 0, 1; "clli_0_1")]
#[test_case::test_case("clli_0_65535.avif", 0, 65535; "clli_0_65535")]
#[test_case::test_case("clli_1_0.avif", 1, 0; "clli_1_0")]
#[test_case::test_case("clli_1_1.avif", 1, 1; "clli_1_1")]
#[test_case::test_case("clli_1_65535.avif", 1, 65535; "clli_1_65535")]
#[test_case::test_case("clli_65535_0.avif", 65535, 0; "clli_65535_0")]
#[test_case::test_case("clli_65535_1.avif", 65535, 1; "clli_65535_1")]
#[test_case::test_case("clli_65535_65535.avif", 65535, 65535; "clli_65535_65535")]
fn clli(filename: &str, max_cll: u16, max_pall: u16) {
let mut filename_with_prefix = String::from("clli/");
filename_with_prefix.push_str(filename);
let mut decoder = get_decoder(&filename_with_prefix);
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
if max_cll == 0 && max_pall == 0 {
assert!(image.clli.is_none());
} else {
assert!(image.clli.is_some());
let clli = image.clli.as_ref().unwrap();
assert_eq!(clli.max_cll, max_cll);
assert_eq!(clli.max_pall, max_pall);
}
}
#[test]
fn raw_io() {
let data =
std::fs::read(get_test_file("colors-animated-8bpc.avif")).expect("Unable to read file");
let mut decoder = decoder::Decoder::default();
let _ = unsafe {
decoder
.set_io_raw(data.as_ptr(), data.len())
.expect("Failed to set IO")
};
assert!(decoder.parse().is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
assert_eq!(decoder.image_count(), 5);
if !HAS_DECODER {
return;
}
for _ in 0..5 {
assert!(decoder.next_image().is_ok());
}
}
struct CustomIO {
data: Vec<u8>,
available_size_rc: Rc<RefCell<usize>>,
}
impl decoder::IO for CustomIO {
fn read(&mut self, offset: u64, max_read_size: usize) -> AvifResult<&[u8]> {
let available_size = self.available_size_rc.borrow();
let start = usize::try_from(offset).unwrap();
let end = start + max_read_size;
if start > self.data.len() || end > self.data.len() {
return Err(AvifError::IoError);
}
let mut ssize = max_read_size;
if ssize > self.data.len() - start {
ssize = self.data.len() - start;
}
let end = start + ssize;
if *available_size < end {
return Err(AvifError::WaitingOnIo);
}
Ok(&self.data[start..end])
}
fn size_hint(&self) -> u64 {
self.data.len() as u64
}
fn persistent(&self) -> bool {
false
}
}
#[test]
fn custom_io() {
let data =
std::fs::read(get_test_file("colors-animated-8bpc.avif")).expect("Unable to read file");
let mut decoder = decoder::Decoder::default();
let available_size_rc = Rc::new(RefCell::new(data.len()));
let io = Box::new(CustomIO {
available_size_rc: available_size_rc.clone(),
data,
});
decoder.set_io(io);
assert!(decoder.parse().is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
assert_eq!(decoder.image_count(), 5);
if !HAS_DECODER {
return;
}
for _ in 0..5 {
assert!(decoder.next_image().is_ok());
}
}
fn expected_min_decoded_row_count(
height: u32,
cell_height: u32,
cell_columns: u32,
available_size: usize,
size: usize,
grid_cell_offsets: &Vec<usize>,
) -> u32 {
if available_size >= size {
return height;
}
let mut cell_index: Option<usize> = None;
for (index, offset) in grid_cell_offsets.iter().enumerate().rev() {
if available_size >= *offset {
cell_index = Some(index);
break;
}
}
if cell_index.is_none() {
return 0;
}
let cell_index = cell_index.unwrap() as u32;
let cell_row = cell_index / cell_columns;
let cell_column = cell_index % cell_columns;
let cell_rows_decoded = if cell_column == cell_columns - 1 { cell_row + 1 } else { cell_row };
cell_rows_decoded * cell_height
}
#[test]
fn expected_min_decoded_row_count_computation() {
let grid_cell_offsets: Vec<usize> = vec![3258, 10643, 17846, 22151, 25409, 30000];
let cell_height = 154;
assert_eq!(
0,
expected_min_decoded_row_count(770, cell_height, 1, 1000, 30000, &grid_cell_offsets)
);
assert_eq!(
1 * cell_height,
expected_min_decoded_row_count(770, cell_height, 1, 4000, 30000, &grid_cell_offsets)
);
assert_eq!(
2 * cell_height,
expected_min_decoded_row_count(770, cell_height, 1, 12000, 30000, &grid_cell_offsets)
);
assert_eq!(
3 * cell_height,
expected_min_decoded_row_count(770, cell_height, 1, 17846, 30000, &grid_cell_offsets)
);
assert_eq!(
1 * cell_height,
expected_min_decoded_row_count(462, cell_height, 2, 17846, 30000, &grid_cell_offsets)
);
assert_eq!(
2 * cell_height,
expected_min_decoded_row_count(462, cell_height, 2, 23000, 30000, &grid_cell_offsets)
);
assert_eq!(
1 * cell_height,
expected_min_decoded_row_count(308, cell_height, 3, 23000, 30000, &grid_cell_offsets)
);
assert_eq!(
2 * cell_height,
expected_min_decoded_row_count(308, cell_height, 3, 30000, 30000, &grid_cell_offsets)
);
}
#[test]
fn incremental_decode() {
// Grid item offsets for sofa_grid1x5_420.avif:
// Each line is "$extent_offset + $extent_length".
let grid_cell_offsets: Vec<usize> = vec![
578 + 2680,
3258 + 7385,
10643 + 7203,
17846 + 4305,
22151 + 3258,
];
let data = std::fs::read(get_test_file("sofa_grid1x5_420.avif")).expect("Unable to read file");
let len = data.len();
let available_size_rc = Rc::new(RefCell::new(0usize));
let mut decoder = decoder::Decoder::default();
decoder.settings.allow_incremental = true;
let io = Box::new(CustomIO {
available_size_rc: available_size_rc.clone(),
data,
});
decoder.set_io(io);
let step: usize = std::cmp::max(1, len / 10000) as usize;
// Parsing is not incremental.
let mut parse_result = decoder.parse();
while parse_result.is_err()
&& matches!(parse_result.as_ref().err().unwrap(), AvifError::WaitingOnIo)
{
{
let mut available_size = available_size_rc.borrow_mut();
if *available_size >= len {
println!("parse returned waiting on io after full file.");
assert!(false);
}
*available_size = std::cmp::min(*available_size + step, len);
}
parse_result = decoder.parse();
}
assert!(parse_result.is_ok());
if !HAS_DECODER {
return;
}
// Decoding is incremental.
let mut previous_decoded_row_count = 0;
let mut decode_result = decoder.next_image();
while decode_result.is_err()
&& matches!(
decode_result.as_ref().err().unwrap(),
AvifError::WaitingOnIo
)
{
{
let mut available_size = available_size_rc.borrow_mut();
if *available_size >= len {
println!("next_image returned waiting on io after full file.");
assert!(false);
}
let decoded_row_count = decoder.decoded_row_count();
assert!(decoded_row_count >= previous_decoded_row_count);
let expected_min_decoded_row_count = expected_min_decoded_row_count(
decoder.image().unwrap().height,
154,
1,
*available_size,
len,
&grid_cell_offsets,
);
assert!(decoded_row_count >= expected_min_decoded_row_count);
previous_decoded_row_count = decoded_row_count;
*available_size = std::cmp::min(*available_size + step, len);
}
decode_result = decoder.next_image();
}
assert!(decode_result.is_ok());
assert_eq!(decoder.decoded_row_count(), decoder.image().unwrap().height);
// TODO: check if incremental and non incremental produces same output.
}
#[test]
fn nth_image() {
let mut decoder = get_decoder("colors-animated-8bpc.avif");
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
assert_eq!(decoder.image_count(), 5);
if !HAS_DECODER {
return;
}
assert!(decoder.nth_image(3).is_ok());
assert!(decoder.next_image().is_ok());
assert!(decoder.next_image().is_err());
assert!(decoder.nth_image(1).is_ok());
assert!(decoder.nth_image(4).is_ok());
assert!(decoder.nth_image(50).is_err());
}
#[test]
fn color_and_alpha_dimensions_do_not_match() {
let mut decoder = get_decoder("invalid_color10x10_alpha5x5.avif");
// Parsing should succeed.
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert_eq!(image.width, 10);
assert_eq!(image.height, 10);
if !HAS_DECODER {
return;
}
// Decoding should fail.
let res = decoder.next_image();
assert!(res.is_err());
}
#[test]
fn rgb_conversion_alpha_premultiply() -> AvifResult<()> {
let mut decoder = get_decoder("alpha.avif");
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
if !HAS_DECODER {
return Ok(());
}
let res = decoder.next_image();
assert!(res.is_ok());
let image = decoder.image().expect("image was none");
let mut rgb = rgb::Image::create_from_yuv(image);
rgb.premultiply_alpha = true;
rgb.allocate()?;
assert!(rgb.convert_from_yuv(image).is_ok());
Ok(())
}
#[test]
fn white_1x1() -> AvifResult<()> {
let mut decoder = get_decoder("white_1x1.avif");
assert_eq!(decoder.parse(), Ok(()));
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
if !HAS_DECODER {
return Ok(());
}
assert_eq!(decoder.next_image(), Ok(()));
let image = decoder.image().expect("image was none");
let mut rgb = rgb::Image::create_from_yuv(image);
rgb.allocate()?;
assert!(rgb.convert_from_yuv(image).is_ok());
assert_eq!(rgb.width * rgb.height, 1);
let format = rgb.format;
for i in [format.r_offset(), format.g_offset(), format.b_offset()] {
assert_eq!(rgb.row(0)?[i], 253); // Compressed with loss, not pure white.
}
if rgb.has_alpha() {
assert_eq!(rgb.row(0)?[rgb.format.alpha_offset()], 255);
}
Ok(())
}
#[test]
fn white_1x1_mdat_size0() -> AvifResult<()> {
// Edit the file to simulate an 'mdat' box with size 0 (meaning it ends at EOF).
let mut file_bytes = std::fs::read(get_test_file("white_1x1.avif")).unwrap();
let mdat = [b'm', b'd', b'a', b't'];
let mdat_size_pos = file_bytes.windows(4).position(|w| w == mdat).unwrap() - 4;
file_bytes[mdat_size_pos + 3] = b'\0';
let mut decoder = decoder::Decoder::default();
decoder.set_io_vec(file_bytes);
assert_eq!(decoder.parse(), Ok(()));
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
Ok(())
}
#[test]
fn white_1x1_meta_size0() -> AvifResult<()> {
// Edit the file to simulate a 'meta' box with size 0 (invalid).
let mut file_bytes = std::fs::read(get_test_file("white_1x1.avif")).unwrap();
let meta = [b'm', b'e', b't', b'a'];
let meta_size_pos = file_bytes.windows(4).position(|w| w == meta).unwrap() - 4;
file_bytes[meta_size_pos + 3] = b'\0';
let mut decoder = decoder::Decoder::default();
decoder.set_io_vec(file_bytes);
// This should fail because the meta box contains the mdat box.
// However, the section 8.11.3.1 of ISO/IEC 14496-12 does not explicitly require the coded image
// item extents to be read from the MediaDataBox if the construction_method is 0.
// Maybe another section or specification enforces that.
assert_eq!(decoder.parse(), Ok(()));
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
if !HAS_DECODER {
return Ok(());
}
assert_eq!(decoder.next_image(), Ok(()));
Ok(())
}
#[test]
fn white_1x1_ftyp_size0() -> AvifResult<()> {
// Edit the file to simulate a 'ftyp' box with size 0 (invalid).
let mut file_bytes = std::fs::read(get_test_file("white_1x1.avif")).unwrap();
file_bytes[3] = b'\0';
let mut decoder = decoder::Decoder::default();
decoder.set_io_vec(file_bytes);
assert!(matches!(
decoder.parse(),
Err(AvifError::BmffParseFailed(_))
));
Ok(())
}
#[test]
fn dimg_repetition() {
let mut decoder = get_decoder("sofa_grid1x5_420_dimg_repeat.avif");
assert_eq!(
decoder.parse(),
Err(AvifError::BmffParseFailed(
"multiple dimg references for item ID 1".into()
))
);
}
#[test]
fn dimg_shared() {
let mut decoder = get_decoder("color_grid_alpha_grid_tile_shared_in_dimg.avif");
assert_eq!(decoder.parse(), Err(AvifError::NotImplemented));
}
#[test]
fn dimg_ordering() {
if !HAS_DECODER {
return;
}
let mut decoder1 = get_decoder("sofa_grid1x5_420.avif");
let res = decoder1.parse();
assert!(res.is_ok());
let res = decoder1.next_image();
assert!(res.is_ok());
let mut decoder2 = get_decoder("sofa_grid1x5_420_random_dimg_order.avif");
let res = decoder2.parse();
assert!(res.is_ok());
let res = decoder2.next_image();
assert!(res.is_ok());
let image1 = decoder1.image().expect("image1 was none");
let image2 = decoder2.image().expect("image2 was none");
// Ensure that the pixels in image1 and image2 are not the same.
let row1 = image1.row(Plane::Y, 0).expect("row1 was none");
let row2 = image2.row(Plane::Y, 0).expect("row2 was none");
assert_ne!(row1, row2);
}
#[test]
fn heic_peek() {
let file_data = std::fs::read(get_test_file("blue.heic")).expect("could not read file");
assert_eq!(
decoder::Decoder::peek_compatible_file_type(&file_data),
cfg!(feature = "heic")
);
}
#[test]
fn heic_parsing() {
let mut decoder = get_decoder("blue.heic");
let res = decoder.parse();
if cfg!(feature = "heic") {
assert!(res.is_ok());
let image = decoder.image().expect("image was none");
assert_eq!(image.width, 320);
assert_eq!(image.height, 240);
assert_eq!(decoder.compression_format(), CompressionFormat::Heic);
if cfg!(feature = "android_mediacodec") {
// Decoding is available only via android_mediacodec.
assert!(!matches!(
decoder.next_image(),
Err(AvifError::NoCodecAvailable)
));
}
} else {
assert!(res.is_err());
}
}
#[test]
fn clap_irot_imir_non_essential() {
let mut decoder = get_decoder("clap_irot_imir_non_essential.avif");
let res = decoder.parse();
assert!(res.is_err());
}
#[derive(Clone)]
struct ExpectedOverlayImageInfo<'a> {
filename: &'a str,
width: u32,
height: u32,
expected_pixels: &'a [(usize, u32, [u8; 4])], // (x, y, [rgba]).
}
const RED: [u8; 4] = [255, 0, 0, 255];
const GREEN: [u8; 4] = [0, 255, 0, 255];
const BLUE: [u8; 4] = [0, 0, 255, 255];
const BLACK: [u8; 4] = [0, 0, 0, 255];
const YELLOW: [u8; 4] = [255, 255, 0, 255];
const EXPECTED_OVERLAY_IMAGE_INFOS: [ExpectedOverlayImageInfo; 4] = [
ExpectedOverlayImageInfo {
// Three 80x60 sub-images with the following offsets:
// horizontal_offsets: [0, 40, 80]
// vertical_offsets: [0, 40, 80]
filename: "overlay_exact_bounds.avif",
width: 160,
height: 140,
expected_pixels: &[
// Top left should be red.
(0, 0, RED),
(10, 10, RED),
(20, 20, RED),
// Green should be overlaid on top of the red block starting at (40, 40).
(40, 40, GREEN),
(50, 50, GREEN),
(60, 60, GREEN),
// Blue should be overlaid on top of the green block starting at (80, 80).
(80, 80, BLUE),
(90, 90, BLUE),
(100, 100, BLUE),
// Top right should be background color.
(159, 0, BLACK),
// Bottom left should be background color.
(0, 139, BLACK),
],
},
ExpectedOverlayImageInfo {
// Three 80x60 sub-images with the following offsets:
// horizontal_offsets: [20, 60, 100]
// vertical_offsets: [20, 60, 100]
filename: "overlay_with_border.avif",
width: 200,
height: 180,
expected_pixels: &[
// Top left should be background color.
(0, 0, BLACK),
// Red should be overlaid starting at (20, 20).
(20, 20, RED),
(30, 30, RED),
(40, 40, RED),
// Green should be overlaid on top of the red block starting at (60, 60).
(60, 60, GREEN),
(70, 70, GREEN),
(80, 80, GREEN),
// Blue should be overlaid on top of the green block starting at (100, 100).
(100, 100, BLUE),
(110, 110, BLUE),
(120, 120, BLUE),
// Top right should be background color.
(199, 0, BLACK),
// Bottom left should be background color.
(0, 179, BLACK),
// Bottom right should be background color.
(199, 179, BLACK),
],
},
ExpectedOverlayImageInfo {
// Two 80x60 sub-images with the following offsets:
// horizontal_offsets: [-40, 120]
// vertical_offsets: [-40, 100]
filename: "overlay_outside_bounds.avif",
width: 160,
height: 140,
expected_pixels: &[
// Red overlay is 40x20 in the top left.
(0, 0, RED),
(15, 15, RED),
(39, 19, RED),
(40, 20, BLACK),
// Blue overlay is 40x40 in the bottom right.
(119, 99, BLACK),
(120, 100, BLUE),
(140, 120, BLUE),
(159, 139, BLUE),
// Center of the image should be background color.
(80, 70, BLACK),
// Top right should be background color.
(159, 0, BLACK),
// Bottom left should be background color.
(0, 139, BLACK),
],
},
ExpectedOverlayImageInfo {
// Three 80x60 sub-images with the following offsets:
// horizontal_offsets: [0, 40, 80]
// vertical_offsets: [0, 40, 80]
// canvas background color: yellow.
filename: "overlay_yellow_bg.avif",
width: 160,
height: 140,
expected_pixels: &[
// Top left should be red.
(0, 0, RED),
(10, 10, RED),
(20, 20, RED),
// Green should be overlaid on top of the red block starting at (40, 40).
(40, 40, GREEN),
(50, 50, GREEN),
(60, 60, GREEN),
// Blue should be overlaid on top of the green block starting at (80, 80).
(80, 80, BLUE),
(90, 90, BLUE),
(100, 100, BLUE),
// Top right should be background color.
(159, 0, YELLOW),
// Bottom left should be background color.
(0, 139, YELLOW),
],
},
];
macro_rules! pixel_eq {
($a:expr, $b:expr) => {
assert!((i32::from($a) - i32::from($b)).abs() <= 3);
};
}
#[test_case::test_matrix(0usize..4)]
fn overlay(index: usize) {
let info = &EXPECTED_OVERLAY_IMAGE_INFOS[index];
let mut decoder = get_decoder(info.filename);
decoder.settings.strictness = decoder::Strictness::None;
let res = decoder.parse();
assert!(res.is_ok());
assert_eq!(decoder.compression_format(), CompressionFormat::Avif);
let image = decoder.image().expect("image was none");
assert_eq!(image.width, info.width);
assert_eq!(image.height, info.height);
if !HAS_DECODER {
return;
}
let res = decoder.next_image();
assert!(res.is_ok());
let image = decoder.image().expect("image was none");
assert_eq!(image.width, info.width);
assert_eq!(image.height, info.height);
let mut rgb = rgb::Image::create_from_yuv(image);
rgb.format = rgb::Format::Rgba;
assert!(rgb.allocate().is_ok());
assert!(rgb.convert_from_yuv(image).is_ok());
for expected_pixel in info.expected_pixels {
let column = expected_pixel.0;
let row = expected_pixel.1;
let pixels = rgb.row(row).expect("row was none");
let r = pixels[column * 4];
let g = pixels[(column * 4) + 1];
let b = pixels[(column * 4) + 2];
let a = pixels[(column * 4) + 3];
pixel_eq!(r, expected_pixel.2[0]);
pixel_eq!(g, expected_pixel.2[1]);
pixel_eq!(b, expected_pixel.2[2]);
pixel_eq!(a, expected_pixel.2[3]);
}
}