use std::fs::File;
use std::io::{BufReader, Read, Seek, SeekFrom};
use exif::parse_exif;
use libheif_rs::{
check_file_type, color_profile_types, AuxiliaryImagesFilter, Chroma,
ChromaDownsamplingAlgorithm, ChromaUpsamplingAlgorithm, ColorPrimaries, ColorProfile,
ColorSpace, CompressionFormat, DecodingOptions, FileTypeResult, HeifContext, ImageHandle,
ItemId, LibHeif, MatrixCoefficients, Result, RgbChroma, StreamReader, TransferCharacteristics,
};
fn version(lib_heif: &LibHeif) -> u16 {
let version = lib_heif.version();
version[0] as u16 * 100 + version[1] as u16
}
#[test]
fn read_from_file() -> Result<()> {
let ctx = HeifContext::read_from_file("./data/test.heif")?;
let handle = ctx.primary_image_handle()?;
assert_eq!(handle.width(), 1652);
assert_eq!(handle.height(), 1791);
Ok(())
}
#[test]
fn read_from_memory() -> Result<()> {
let mut buffer = Vec::new();
File::open("./data/test.heif")
.unwrap()
.read_to_end(&mut buffer)
.unwrap();
let ctx = HeifContext::read_from_bytes(&buffer)?;
let handle = ctx.primary_image_handle()?;
assert_eq!(handle.width(), 1652);
assert_eq!(handle.height(), 1791);
Ok(())
}
#[test]
fn read_from_reader() -> Result<()> {
let lib_heif = LibHeif::new();
let lib_version = version(&lib_heif);
let mut file = BufReader::new(File::open("./data/test.heif").unwrap());
let total_size = file.seek(SeekFrom::End(0)).unwrap();
file.rewind().unwrap();
let stream_reader = StreamReader::new(file, total_size);
let mut ctx = HeifContext::read_from_reader(Box::new(stream_reader))?;
let handle = ctx.primary_image_handle()?;
assert_eq!(handle.width(), 1652);
assert_eq!(handle.height(), 1791);
ctx.set_max_decoding_threads(2);
let preferred_colorspace = handle.preferred_decoding_colorspace()?;
if lib_version > 118 {
assert_eq!(preferred_colorspace, ColorSpace::YCbCr(Chroma::C420));
} else {
assert_eq!(preferred_colorspace, ColorSpace::YCbCr(Chroma::C444));
}
let src_img = lib_heif.decode(&handle, ColorSpace::Undefined, None)?;
if lib_version > 118 {
assert_eq!(src_img.color_space(), Some(ColorSpace::YCbCr(Chroma::C420)));
} else {
assert_eq!(
src_img.color_space(),
Some(ColorSpace::Rgb(RgbChroma::C444))
);
}
Ok(())
}
#[test]
fn get_image_handler() -> Result<()> {
let ctx = HeifContext::read_from_file("./data/test.heif")?;
let handle = ctx.primary_image_handle()?;
assert_eq!(handle.width(), 1652);
assert_eq!(handle.height(), 1791);
assert_eq!(handle.ispe_width(), 1652);
assert_eq!(handle.ispe_height(), 1791);
assert!(!handle.has_alpha_channel());
assert!(!handle.is_premultiplied_alpha());
assert!(handle.is_primary());
assert_eq!(handle.luma_bits_per_pixel(), 8);
assert_eq!(handle.chroma_bits_per_pixel(), 8);
assert!(!handle.has_depth_image());
assert_eq!(handle.number_of_depth_images(), 0);
let mut image_ids: Vec<ItemId> = vec![0; 1];
let count = handle.depth_image_ids(&mut image_ids);
assert_eq!(count, 0);
Ok(())
}
#[test]
fn get_thumbnail() -> Result<()> {
let ctx = HeifContext::read_from_file("./data/test.heif")?;
let thumb_handle: ImageHandle;
{
let handle = ctx.primary_image_handle()?;
assert_eq!(handle.number_of_thumbnails(), 1);
let mut thumb_ids: Vec<ItemId> = vec![0; 2];
let count = handle.thumbnail_ids(&mut thumb_ids);
assert_eq!(count, 1);
thumb_handle = handle.thumbnail(thumb_ids[0])?;
}
assert_eq!(thumb_handle.width(), 236);
assert_eq!(thumb_handle.height(), 256);
Ok(())
}
#[test]
fn get_exif() -> Result<()> {
let ctx = HeifContext::read_from_file("./data/test.heif")?;
let handle = ctx.primary_image_handle()?;
assert_eq!(handle.number_of_metadata_blocks(0), 1);
let mut meta_ids: Vec<ItemId> = vec![0; 2];
let count = handle.metadata_block_ids(&mut meta_ids, 0);
assert_eq!(count, 1);
let meta_type = handle.metadata_type(meta_ids[0]);
assert_eq!(meta_type, Some("Exif"));
let meta_content_type = handle.metadata_content_type(meta_ids[0]);
assert_eq!(meta_content_type, Some(""));
assert_eq!(handle.metadata_size(meta_ids[0]), 2330);
assert_eq!(handle.number_of_metadata_blocks(b"Unkn"), 0);
let count = handle.metadata_block_ids(&mut meta_ids, b"Unkn");
assert_eq!(count, 0);
assert_eq!(handle.number_of_metadata_blocks(b"Exif"), 1);
let count = handle.metadata_block_ids(&mut meta_ids, b"Exif");
assert_eq!(count, 1);
let exif = handle.metadata(meta_ids[0])?;
assert_eq!(exif.len(), 2330);
assert_eq!(exif[0..8], [0, 0, 0, 0, b'M', b'M', 0, b'*']);
let tiff_exif = &exif[4..]; let (exif_fields, is_le) = parse_exif(tiff_exif).unwrap();
assert!(!is_le);
assert_eq!(exif_fields.len(), 45);
Ok(())
}
#[test]
fn get_all_metadata() -> Result<()> {
let ctx = HeifContext::read_from_file("./data/test.heif")?;
let handle = ctx.primary_image_handle()?;
let metadata_items = handle.all_metadata();
assert_eq!(metadata_items.len(), 1);
let exif = &metadata_items[0];
assert_eq!(exif.item_type, b"Exif".into());
assert_eq!(exif.content_type, "");
assert_eq!(exif.raw_data.len(), 2330);
assert_eq!(exif.raw_data[0..8], [0, 0, 0, 0, b'M', b'M', 0, b'*']);
Ok(())
}
#[test]
fn decode_and_scale_image() -> Result<()> {
let lib_heif = LibHeif::new();
let ctx = HeifContext::read_from_file("./data/test.heif")?;
let handle = ctx.primary_image_handle()?;
let src_img = lib_heif.decode(&handle, ColorSpace::YCbCr(Chroma::C420), None)?;
assert_eq!(src_img.color_space(), Some(ColorSpace::YCbCr(Chroma::C420)));
let planes = src_img.planes();
let y_plane = planes.y.unwrap();
assert_eq!(y_plane.width, 1652);
assert_eq!(y_plane.height, 1791);
let cb_plane = planes.cb.unwrap();
assert_eq!(cb_plane.width, 1652 / 2);
assert_eq!(cb_plane.height, 1791 / 2 + 1);
let cr_plane = planes.cr.unwrap();
assert_eq!(cr_plane.width, 1652 / 2);
assert_eq!(cr_plane.height, 1791 / 2 + 1);
let img = src_img.scale(1024, 800, None)?;
let planes = img.planes();
let y_plane = planes.y.unwrap();
assert_eq!(y_plane.width, 1024);
assert_eq!(y_plane.height, 800);
assert!(!y_plane.data.is_empty());
assert!(y_plane.stride > 0);
let cb_plane = planes.cb.unwrap();
assert_eq!(cb_plane.width, 1024 / 2);
assert_eq!(cb_plane.height, 800 / 2);
let cr_plane = planes.cr.unwrap();
assert_eq!(cr_plane.width, 1024 / 2);
assert_eq!(cr_plane.height, 800 / 2);
Ok(())
}
#[test]
fn top_decode_heic() -> Result<()> {
let lib_heif = LibHeif::new();
if lib_heif
.decoder_descriptors(1, Some(CompressionFormat::Hevc))
.is_empty()
{
println!(
"WARNING: Hevc decoder is absent. The test checking decoding of heic file has been skipped."
);
return Ok(());
}
let ctx = HeifContext::read_from_file("./data/test.heic")?;
let handle = ctx.primary_image_handle()?;
let src_img = lib_heif.decode(&handle, ColorSpace::Undefined, None)?;
if version(&lib_heif) > 118 {
assert_eq!(src_img.color_space(), Some(ColorSpace::YCbCr(Chroma::C420)));
let planes = src_img.planes();
let y_plane = planes.y.unwrap();
assert_eq!(y_plane.width, 1652);
assert_eq!(y_plane.height, 1791);
} else {
assert_eq!(
src_img.color_space(),
Some(ColorSpace::Rgb(RgbChroma::C444))
);
let planes = src_img.planes();
let r_plane = planes.r.unwrap();
assert_eq!(r_plane.width, 1652);
assert_eq!(r_plane.height, 1791);
}
Ok(())
}
#[test]
fn image_collection() -> Result<()> {
let ctx = HeifContext::read_from_file("./data/collection.heif")?;
assert_eq!(ctx.number_of_top_level_images(), 4);
let mut image_ids: Vec<ItemId> = vec![0; 5];
let count = ctx.top_level_image_ids(&mut image_ids);
assert_eq!(count, 4);
for &image_id in image_ids[0..4].iter() {
let handle = ctx.image_handle(image_id)?;
assert_eq!(handle.width(), 480);
assert_eq!(handle.height(), 360);
}
Ok(())
}
#[test]
fn test_check_file_type() {
let mut data = vec![0u8; 16];
assert_eq!(check_file_type(&data), FileTypeResult::No);
let mut f = File::open("./data/test.heif").unwrap();
let len = f.read(&mut data).unwrap();
assert_eq!(len, 16);
assert_eq!(check_file_type(&data[..7]), FileTypeResult::MayBe);
assert_eq!(check_file_type(&data[..8]), FileTypeResult::MayBe);
assert_eq!(check_file_type(&data[..12]), FileTypeResult::Supported);
}
#[test]
fn test_raw_color_profile_of_image_handle() -> Result<()> {
let ctx = HeifContext::read_from_file("./data/test.heif")?;
let handle = ctx.primary_image_handle()?;
let raw_profile = handle.color_profile_raw().unwrap();
assert_eq!(raw_profile.profile_type(), color_profile_types::PROF);
assert_eq!(raw_profile.data.len(), 536);
Ok(())
}
#[test]
fn test_raw_color_profile_of_image() -> Result<()> {
let lib_heif = LibHeif::new();
let ctx = HeifContext::read_from_file("./data/test.heif")?;
let handle = ctx.primary_image_handle()?;
let image = lib_heif.decode(&handle, ColorSpace::Undefined, None)?;
let raw_profile = image.color_profile_raw().unwrap();
assert_eq!(raw_profile.profile_type(), color_profile_types::PROF);
assert_eq!(raw_profile.data.len(), 536);
Ok(())
}
#[cfg(feature = "v1_21")]
#[test]
fn test_nclx_color_profile_of_handle_and_image() -> Result<()> {
let lib_heif = LibHeif::new();
let ctx = HeifContext::read_from_file("./data/test_nclx.heif")?;
let handle = ctx.primary_image_handle()?;
assert!(handle.color_profile_raw().is_none());
let handle_nclx_profile = handle.color_profile_nclx().unwrap();
let image = lib_heif.decode(&handle, ColorSpace::Undefined, None)?;
assert!(image.color_profile_raw().is_none());
let image_nclx_profile = image.color_profile_nclx().unwrap();
assert_eq!(
handle_nclx_profile.profile_type(),
color_profile_types::NCLX
);
assert_eq!(image_nclx_profile.profile_type(), color_profile_types::NCLX);
assert_eq!(
handle_nclx_profile.color_primaries(),
ColorPrimaries::ITU_R_BT_2020_2_and_2100_0
);
assert_eq!(
image_nclx_profile.color_primaries(),
ColorPrimaries::ITU_R_BT_709_5
);
assert_eq!(
handle_nclx_profile.transfer_characteristics(),
TransferCharacteristics::ITU_R_BT_2100_0_PQ
);
assert_eq!(
image_nclx_profile.transfer_characteristics(),
TransferCharacteristics::IEC_61966_2_1
);
assert_eq!(
handle_nclx_profile.matrix_coefficients(),
MatrixCoefficients::RGB_GBR
);
assert_eq!(
image_nclx_profile.matrix_coefficients(),
MatrixCoefficients::ITU_R_BT_601_6
);
assert_eq!(handle_nclx_profile.full_range_flag(), 1);
assert_eq!(image_nclx_profile.full_range_flag(), 1);
assert_eq!(handle_nclx_profile.color_primary_red_x(), 0.708);
assert_eq!(image_nclx_profile.color_primary_red_x(), 0.64);
assert_eq!(handle_nclx_profile.color_primary_red_y(), 0.292);
assert_eq!(image_nclx_profile.color_primary_red_y(), 0.33);
assert_eq!(handle_nclx_profile.color_primary_green_x(), 0.17);
assert_eq!(image_nclx_profile.color_primary_green_x(), 0.3);
assert_eq!(handle_nclx_profile.color_primary_green_y(), 0.797);
assert_eq!(image_nclx_profile.color_primary_green_y(), 0.6);
assert_eq!(handle_nclx_profile.color_primary_blue_x(), 0.131);
assert_eq!(image_nclx_profile.color_primary_blue_x(), 0.15);
assert_eq!(handle_nclx_profile.color_primary_blue_y(), 0.046);
assert_eq!(image_nclx_profile.color_primary_blue_y(), 0.06);
assert_eq!(handle_nclx_profile.color_primary_white_x(), 0.3127);
assert_eq!(image_nclx_profile.color_primary_white_x(), 0.3127);
assert_eq!(handle_nclx_profile.color_primary_white_y(), 0.329);
assert_eq!(image_nclx_profile.color_primary_white_y(), 0.329);
Ok(())
}
#[test]
fn test_read_avif_image() -> Result<()> {
let lib_heif = LibHeif::new();
let ctx = HeifContext::read_from_file("./data/test_nclx.avif")?;
let handle = ctx.primary_image_handle()?;
assert_eq!(handle.width(), 2048);
assert_eq!(handle.height(), 1440);
assert_eq!(handle.luma_bits_per_pixel(), 12);
assert_eq!(handle.chroma_bits_per_pixel(), 12);
let nclx_profile = handle.color_profile_nclx();
assert!(nclx_profile.is_some());
let image = lib_heif.decode(&handle, ColorSpace::Undefined, None)?;
if version(&lib_heif) > 118 {
assert_eq!(image.color_space(), Some(ColorSpace::YCbCr(Chroma::C444)));
let planes = image.planes();
let y_plane = planes.y.unwrap();
assert_eq!(y_plane.width, 2048);
assert_eq!(y_plane.height, 1440);
} else {
assert_eq!(image.color_space(), Some(ColorSpace::Rgb(RgbChroma::C444)));
let planes = image.planes();
let r_plane = planes.r.unwrap();
assert_eq!(r_plane.width, 2048);
assert_eq!(r_plane.height, 1440);
}
Ok(())
}
#[test]
fn test_decoding_options() -> Result<()> {
let mut dec_options = DecodingOptions::new().unwrap();
assert!(dec_options.version() >= 5);
assert!(!dec_options.ignore_transformations());
assert!(!dec_options.convert_hdr_to_8bit());
assert!(!dec_options.strict_decoding());
assert!(dec_options.decoder_id().is_none());
let color_options = dec_options.color_conversion_options();
assert_eq!(
color_options.preferred_chroma_downsampling_algorithm,
ChromaDownsamplingAlgorithm::Average
);
assert_eq!(
color_options.preferred_chroma_upsampling_algorithm,
ChromaUpsamplingAlgorithm::Bilinear
);
assert!(!color_options.only_use_preferred_chroma_algorithm);
dec_options.set_decoder_id(Some("heif"))?;
assert_eq!(dec_options.decoder_id(), Some("heif"));
#[cfg(feature = "v1_20")]
{
use libheif_rs::AlphaCompositionMode;
let alpha_mode = dec_options.alpha_composition_mode();
assert_eq!(alpha_mode, AlphaCompositionMode::None);
let expected_mode = AlphaCompositionMode::SolidColor {
background_rgb: [0, 1, 2],
};
dec_options.set_alpha_composition_mode(expected_mode);
let alpha_mode = dec_options.alpha_composition_mode();
assert_eq!(alpha_mode, expected_mode);
let expected_mode = AlphaCompositionMode::Checkerboard {
background_rgb: [3, 4, 5],
secondary_background_rgb: [6, 7, 8],
square_size: 24,
};
dec_options.set_alpha_composition_mode(expected_mode);
let alpha_mode = dec_options.alpha_composition_mode();
assert_eq!(alpha_mode, expected_mode);
}
#[cfg(feature = "v1_21")]
{
use libheif_rs::ColorProfileNCLX;
assert!(!dec_options.ignore_sequence_edit_list());
dec_options.set_ignore_sequence_edit_list(true);
assert!(dec_options.ignore_sequence_edit_list());
assert!(dec_options.output_image_nclx_profile().is_none());
dec_options.set_output_image_nclx_profile(Some(ColorProfileNCLX::new().unwrap()));
assert!(dec_options.output_image_nclx_profile().is_some());
dec_options.set_output_image_nclx_profile(None);
assert!(dec_options.output_image_nclx_profile().is_none());
assert_eq!(dec_options.num_library_threads(), 0);
dec_options.set_num_library_threads(2);
assert_eq!(dec_options.num_library_threads(), 2);
assert_eq!(dec_options.num_codec_threads(), 0);
dec_options.set_num_codec_threads(2);
assert_eq!(dec_options.num_codec_threads(), 2);
}
Ok(())
}
#[test]
fn get_auxiliary_images() -> Result<()> {
let lib_heif = LibHeif::new();
let ctx = HeifContext::read_from_file("./data/alpha.heif")?;
let handle = ctx.primary_image_handle()?;
assert_eq!(handle.auxiliary_type()?, "");
let mut auxiliary_images = handle.auxiliary_images(None);
assert_eq!(auxiliary_images.len(), 1);
let alpha_handle = auxiliary_images.pop().unwrap();
assert_eq!(alpha_handle.auxiliary_type()?, "urn:mpeg:hevc:2015:auxid:1");
assert_eq!(alpha_handle.width(), 256);
assert_eq!(alpha_handle.height(), 256);
assert!(!alpha_handle.has_alpha_channel());
assert_eq!(alpha_handle.chroma_bits_per_pixel(), 8);
let alpha_img = lib_heif.decode(&alpha_handle, ColorSpace::Undefined, None)?;
assert!(matches!(
alpha_img.color_space(),
Some(ColorSpace::Monochrome)
));
assert!(handle
.auxiliary_images(AuxiliaryImagesFilter::OMIT_ALPHA)
.is_empty());
Ok(())
}