use std::io::{Read, Write};
use image::error::{
DecodingError, EncodingError, ImageFormatHint, UnsupportedError, UnsupportedErrorKind,
};
use image::metadata::Orientation;
use image::{ColorType, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageResult};
use crate::{EncodeOptions, Image, Metadata, Options};
const EXTENSIONS: [&str; 3] = ["heic", "heif", "hif"];
const BRANDS: [&[u8; 4]; 4] = [b"heic", b"heix", b"heim", b"heis"];
pub fn register_image_decoder_hooks() -> bool {
let mut all = true;
for ext in EXTENSIONS {
all &= image::hooks::register_decoding_hook(
ext.into(),
Box::new(|reader| Ok(Box::new(HeifDecoder::new(reader)?))),
);
}
const MASK: &[u8] = b"\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff";
static SIGNATURES: [[u8; 12]; 4] = {
let mut s = [[0u8; 12]; 4];
let mut i = 0;
while i < 4 {
let mut j = 0;
while j < 4 {
s[i][4 + j] = b"ftyp"[j];
s[i][8 + j] = BRANDS[i][j];
j += 1;
}
i += 1;
}
s
};
for signature in &SIGNATURES {
image::hooks::register_format_detection_hook("heic".into(), signature, Some(MASK));
}
all
}
pub struct HeifDecoder {
image: Image,
metadata: Metadata,
}
impl HeifDecoder {
pub fn new(mut reader: impl Read) -> ImageResult<Self> {
let mut bytes = Vec::new();
reader
.read_to_end(&mut bytes)
.map_err(ImageError::IoError)?;
Self::from_bytes(&bytes, &Options::default())
}
pub fn from_bytes(bytes: &[u8], options: &Options) -> ImageResult<Self> {
let image = crate::decode_with_options(bytes, options).map_err(decoding_error)?;
let metadata = crate::read_metadata(bytes).map_err(decoding_error)?;
Ok(Self { image, metadata })
}
}
fn decoding_error(e: crate::Error) -> ImageError {
ImageError::Decoding(DecodingError::new(ImageFormatHint::Name("HEIF".into()), e))
}
impl ImageDecoder for HeifDecoder {
fn dimensions(&self) -> (u32, u32) {
(self.image.width, self.image.height)
}
fn color_type(&self) -> ColorType {
match (self.image.bit_depth > 8, self.image.has_alpha) {
(false, false) => ColorType::Rgb8,
(false, true) => ColorType::Rgba8,
(true, false) => ColorType::Rgb16,
(true, true) => ColorType::Rgba16,
}
}
fn icc_profile(&mut self) -> ImageResult<Option<Vec<u8>>> {
Ok(self.metadata.icc_profile.clone())
}
fn exif_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
Ok(self.metadata.exif.clone())
}
fn xmp_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
Ok(self.metadata.xmp.clone())
}
fn orientation(&mut self) -> ImageResult<Orientation> {
Ok(Orientation::NoTransforms)
}
fn set_limits(&mut self, limits: image::Limits) -> ImageResult<()> {
limits.check_support(&image::LimitSupport::default())?;
limits.check_dimensions(self.image.width, self.image.height)?;
Ok(())
}
fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
let channels = if self.image.has_alpha { 4 } else { 3 };
let max = u32::from(self.image.max_value());
let pixels = self.image.data.chunks_exact(4).map(|p| &p[..channels]);
if self.image.bit_depth > 8 {
for (out, v) in buf.chunks_exact_mut(2).zip(pixels.flatten()) {
let v16 = ((u32::from(*v) * 65535 + max / 2) / max) as u16;
out.copy_from_slice(&v16.to_ne_bytes());
}
} else {
for (out, v) in buf.iter_mut().zip(pixels.flatten()) {
*out = *v as u8;
}
}
Ok(())
}
fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
(*self).read_image(buf)
}
}
#[derive(Debug)]
pub struct HeifEncoder<W: Write> {
writer: W,
options: EncodeOptions,
metadata: Metadata,
}
impl<W: Write> HeifEncoder<W> {
pub fn new(writer: W) -> Self {
Self::with_options(writer, EncodeOptions::default())
}
pub fn with_options(writer: W, options: EncodeOptions) -> Self {
Self {
writer,
options,
metadata: Metadata::default(),
}
}
}
fn hint() -> ImageFormatHint {
ImageFormatHint::Name("HEIF".into())
}
impl<W: Write> ImageEncoder for HeifEncoder<W> {
fn write_image(
mut self,
buf: &[u8],
width: u32,
height: u32,
color_type: ExtendedColorType,
) -> ImageResult<()> {
use ExtendedColorType as C;
let (channels, wide) = match color_type {
C::L8 => (1, false),
C::La8 => (2, false),
C::Rgb8 => (3, false),
C::Rgba8 => (4, false),
C::L16 => (1, true),
C::La16 => (2, true),
C::Rgb16 => (3, true),
C::Rgba16 => (4, true),
_ => {
return Err(ImageError::Unsupported(
UnsupportedError::from_format_and_kind(
hint(),
UnsupportedErrorKind::Color(color_type),
),
));
}
};
let bytes = if wide { 2 } else { 1 };
let expected = u64::from(width) * u64::from(height) * channels * bytes;
if buf.len() as u64 != expected {
return Err(ImageError::Parameter(
image::error::ParameterError::from_kind(
image::error::ParameterErrorKind::DimensionMismatch,
),
));
}
let channels = channels as usize;
let sample = |i: usize| -> u8 {
if wide {
let v = u16::from_ne_bytes([buf[2 * i], buf[2 * i + 1]]);
((u32::from(v) * 255 + 32767) / 65535) as u8
} else {
buf[i]
}
};
let pixel = |i: usize| -> [u8; 4] {
let p = i * channels;
match channels {
1 => {
let v = sample(p);
[v, v, v, 255]
}
2 => {
let v = sample(p);
[v, v, v, sample(p + 1)]
}
3 => [sample(p), sample(p + 1), sample(p + 2), 255],
_ => [sample(p), sample(p + 1), sample(p + 2), sample(p + 3)],
}
};
let n = width as usize * height as usize;
let has_alpha = channels % 2 == 0 && (0..n).any(|i| pixel(i)[3] != 255);
let heic = crate::encode::encode_pixels(
width,
height,
has_alpha,
Some(&self.metadata),
&self.options,
pixel,
)
.map_err(|e| ImageError::Encoding(EncodingError::new(hint(), e)))?;
self.writer.write_all(&heic).map_err(ImageError::IoError)
}
fn set_icc_profile(&mut self, icc_profile: Vec<u8>) -> Result<(), UnsupportedError> {
self.metadata.icc_profile = Some(icc_profile);
Ok(())
}
fn set_exif_metadata(&mut self, exif: Vec<u8>) -> Result<(), UnsupportedError> {
let tiff = exif.strip_prefix(b"Exif\0\0").unwrap_or(&exif);
self.metadata.exif = Some(tiff.to_vec());
Ok(())
}
}