use libheif_rs::{
Channel, ChromaDownsamplingAlgorithm, ChromaUpsamplingAlgorithm, ColorSpace, CompressionFormat,
EncoderParameterValue, EncoderQuality, EncodingOptions, HeifContext, Image, ImageOrientation,
LibHeif, Result, RgbChroma,
};
pub fn create_image(width: u32, height: u32) -> Result<Image> {
let mut image = Image::new(width, height, ColorSpace::Rgb(RgbChroma::Rgb))?;
image.create_plane(Channel::Interleaved, width, height, 24)?;
let planes = image.planes_mut();
let plane = planes.interleaved.unwrap();
let stride = plane.stride;
let data = plane.data;
for y in 0..height {
let mut row_start = stride * y as usize;
for x in 0..width {
let color = x * y;
data[row_start] = ((color & 0x00_ff_00_00) >> 16) as u8;
data[row_start + 1] = ((color & 0x00_00_ff_00) >> 8) as u8;
data[row_start + 2] = (color & 0x00_00_00_ff) as u8;
row_start += 3;
}
}
Ok(image)
}
#[test]
fn create_and_encode_image() -> Result<()> {
let width = 640;
let height = 480;
let image = create_image(width, height)?;
let lib_heif = LibHeif::new();
let mut context = HeifContext::new()?;
let mut encoder = lib_heif.encoder_for_format(CompressionFormat::Av1)?;
encoder.set_quality(EncoderQuality::LossLess)?;
let encoding_options: EncodingOptions = Default::default();
context.encode_image(&image, &mut encoder, Some(encoding_options))?;
let buf = context.write_to_bytes()?;
let context = HeifContext::read_from_bytes(&buf)?;
let handle = context.primary_image_handle()?;
assert_eq!(handle.width(), width);
assert_eq!(handle.height(), height);
let image = lib_heif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?;
assert_eq!(image.color_space(), Some(ColorSpace::Rgb(RgbChroma::Rgb)));
let planes = image.planes();
let plan = planes.interleaved.unwrap();
assert_eq!(plan.width, width);
assert_eq!(plan.height, height);
Ok(())
}
#[test]
fn create_and_encode_monochrome_image() -> Result<()> {
let width = 640;
let height = 480;
let mut image = Image::new(width, height, ColorSpace::Monochrome)?;
image.create_plane(Channel::Y, width, height, 8)?;
let planes = image.planes_mut();
let plane_a = planes.y.unwrap();
let stride = plane_a.stride;
let data_a = plane_a.data;
for y in 0..height {
let mut row_start = stride * y as usize;
for x in 0..width {
let color = ((x + y) % 255) as u8;
data_a[row_start] = color;
row_start += 1;
}
}
let lib_heif = LibHeif::new();
let mut context = HeifContext::new()?;
let mut encoder = lib_heif.encoder_for_format(CompressionFormat::Av1)?;
encoder.set_quality(EncoderQuality::LossLess)?;
let encoding_options = EncodingOptions::new()?;
context.encode_image(&image, &mut encoder, Some(encoding_options))?;
let _buf = context.write_to_bytes()?;
Ok(())
}
#[test]
fn set_encoder_param() -> Result<()> {
let width = 640;
let height = 480;
let image = create_image(width, height)?;
let lib_heif = LibHeif::new();
let mut context = HeifContext::new()?;
let mut encoder = lib_heif.encoder_for_format(CompressionFormat::Av1)?;
encoder.set_parameter_value("speed", EncoderParameterValue::Int(5))?;
let encoding_options = EncodingOptions::new()?;
context.encode_image(&image, &mut encoder, Some(encoding_options))?;
let buf = context.write_to_bytes()?;
let context = HeifContext::read_from_bytes(&buf)?;
let handle = context.primary_image_handle()?;
assert_eq!(handle.width(), width);
assert_eq!(handle.height(), height);
let image = lib_heif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?;
assert_eq!(image.color_space(), Some(ColorSpace::Rgb(RgbChroma::Rgb)));
let planes = image.planes();
let plan = planes.interleaved.unwrap();
assert_eq!(plan.width, width);
assert_eq!(plan.height, height);
Ok(())
}
#[test]
fn add_metadata() -> Result<()> {
let width = 640;
let height = 480;
let image = create_image(width, height)?;
let lib_heif = LibHeif::new();
let mut context = HeifContext::new()?;
let mut encoder = lib_heif.encoder_for_format(CompressionFormat::Av1)?;
let handle = context.encode_image(&image, &mut encoder, None)?;
let item_type = b"MyDt";
let item_data = b"custom data";
let exif_data = b"MM\0*FakeExif";
let content_type = Some("text/plain");
context.add_generic_metadata(&handle, item_data, item_type, content_type)?;
context.add_exif_metadata(&handle, exif_data)?;
context.add_xmp_metadata(&handle, item_data)?;
let buf = context.write_to_bytes()?;
let context = HeifContext::read_from_bytes(&buf)?;
let handle = context.primary_image_handle()?;
let mut item_ids = vec![0; 1];
let count = handle.metadata_block_ids(&mut item_ids, item_type);
assert_eq!(count, 1);
let md_data = handle.metadata(item_ids[0])?;
assert_eq!(&md_data, item_data);
let md_content_type = handle.metadata_content_type(item_ids[0]);
assert_eq!(md_content_type, Some(""));
let count = handle.metadata_block_ids(&mut item_ids, b"Exif");
assert_eq!(count, 1);
let md_data = handle.metadata(item_ids[0])?;
assert_eq!(&md_data, b"\0\0\0\0MM\0*FakeExif");
let count = handle.metadata_block_ids(&mut item_ids, b"mime");
assert_eq!(count, 1);
let md_data = handle.metadata(item_ids[0])?;
assert_eq!(&md_data, item_data);
let md_content_type = handle.metadata_content_type(item_ids[0]);
assert_eq!(md_content_type, Some("application/rdf+xml"));
Ok(())
}
#[test]
fn test_encoder_hevc() -> Result<()> {
let lib_heif = LibHeif::new();
let mut encoder = match lib_heif.encoder_for_format(CompressionFormat::Hevc) {
Ok(e) => e,
Err(_) => {
println!(
"WARNING: Hevc encoder is absent. The test that check encoding of heic file has skipped."
);
return Ok(());
}
};
assert!(encoder.name().starts_with("x265 HEVC encoder"));
let mut params = encoder.parameters_names();
params.sort();
assert_eq!(params.len(), 7);
let expect = vec![
"chroma".to_string(),
"complexity".to_string(),
"lossless".to_string(),
"preset".to_string(),
"quality".to_string(),
"tu-intra-depth".to_string(),
"tune".to_string(),
];
assert_eq!(params, expect);
assert_eq!(
encoder.parameter("lossless")?,
Some(EncoderParameterValue::Bool(false))
);
encoder.set_quality(EncoderQuality::LossLess)?;
assert_eq!(
encoder.parameter("lossless")?,
Some(EncoderParameterValue::Bool(true))
);
Ok(())
}
#[test]
fn test_encoder_av1() -> Result<()> {
let lib_heif = LibHeif::new();
let mut encoder = lib_heif.encoder_for_format(CompressionFormat::Av1)?;
assert!(encoder.name().starts_with("AOMedia Project AV1 Encoder"));
let params = encoder.parameters_names();
assert!(params.len() >= 13);
assert_eq!(
encoder.parameter("lossless")?,
Some(EncoderParameterValue::Bool(false))
);
encoder.set_quality(EncoderQuality::LossLess)?;
assert_eq!(
encoder.parameter("lossless")?,
Some(EncoderParameterValue::Bool(true))
);
Ok(())
}
#[test]
fn test_encoding_options() -> Result<()> {
let enc_options = EncodingOptions::new().unwrap();
assert!(enc_options.version() >= 5);
assert!(enc_options.save_alpha_channel());
assert!(!enc_options.mac_os_compatibility_workaround());
assert!(!enc_options.mac_os_compatibility_workaround_no_nclx_profile());
assert!(!enc_options.save_two_colr_boxes_when_icc_and_nclx_available());
assert_eq!(enc_options.image_orientation(), ImageOrientation::Normal);
let color_options = enc_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);
Ok(())
}
#[cfg(feature = "v1_18")]
mod v1_18 {
use super::*;
use std::num::NonZeroU16;
#[test]
fn test_encode_grid() -> Result<()> {
let lib_heif = LibHeif::new();
let collection_ctx = HeifContext::read_from_file("./data/collection.heif")?;
let handles = collection_ctx.top_level_image_handles();
assert_eq!(handles.len(), 4);
let mut tiles = Vec::with_capacity(4);
for handle in handles {
let image = lib_heif.decode(&handle, ColorSpace::Rgb(RgbChroma::Rgb), None)?;
assert_eq!(image.width(), 480);
assert_eq!(image.height(), 360);
assert_eq!(image.color_space(), Some(ColorSpace::Rgb(RgbChroma::Rgb)));
tiles.push(image);
}
let mut encoder = lib_heif.encoder_for_format(CompressionFormat::Av1)?;
encoder.set_quality(EncoderQuality::LossLess)?;
let encoding_options: EncodingOptions = Default::default();
let mut grid_ctx = HeifContext::new()?;
let grid_handle = grid_ctx
.encode_grid(
&tiles,
NonZeroU16::new(2).unwrap(),
&mut encoder,
Some(encoding_options),
)?
.unwrap();
assert_eq!(grid_handle.width(), 0);
assert_eq!(grid_handle.height(), 0);
let buf = grid_ctx.write_to_bytes()?;
let context = HeifContext::read_from_bytes(&buf)?;
let handle = context.primary_image_handle()?;
assert_eq!(handle.width(), 480 * 2);
assert_eq!(handle.height(), 360 * 2);
Ok(())
}
}