Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Distinguish between HEIF and HEIC #34

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 86 additions & 20 deletions src/formats/heif.rs → src/container/heif.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
use crate::util::*;
use crate::{ImageError, ImageResult, ImageSize};

use std::convert::TryInto;
use std::io::{BufRead, Seek, SeekFrom};

// REFS: https://github.com/strukturag/libheif/blob/f0c1a863cabbccb2d280515b7ecc73e6717702dc/libheif/heif.h#L600
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Compression {
Av1,
Hevc,
Jpeg,
Unknown,
// unused(reuse in the future?)
// Avc,
// Vvc,
// Evc,
}

pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0))?;
// Read the ftyp header size
Expand Down Expand Up @@ -75,34 +89,86 @@ pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
})
}

pub fn matches(header: &[u8]) -> bool {
pub fn matches<R: BufRead + Seek>(header: &[u8], reader: &mut R) -> Option<Compression> {
if header.len() < 12 || &header[4..8] != b"ftyp" {
return false;
return None;
}

let header_brand = &header[8..12];
let brand: [u8; 4] = header[8..12].try_into().unwrap();

// case 1: { heic, ... }
if let Some(v) = inner_matches(&brand) {
Some(v)

// case 2: { msf1, version, heic, msf1, ... }
// brand brand2 brand3
// case 3: { msf1, version, msf1, heic, ... }
// brand brand2 brand3
} else {
// REFS: https://github.com/nokiatech/heif/blob/be43efdf273ae9cf90e552b99f16ac43983f3d19/srcs/reader/heifreaderimpl.cpp#L738
let m_brands = [b"mif1", b"msf1", b"mif2", b"miaf"];

if m_brands.contains(&&brand) {
let mut buf = [0; 12];

if reader.read_exact(&mut buf).is_err() {
return Some(Compression::Unknown);
}

let brand2: [u8; 4] = buf[4..8].try_into().unwrap();
let brand3: [u8; 4] = buf[8..12].try_into().unwrap();

// case 2
if let Some(v) = inner_matches(&brand2) {
return Some(v);

// case 3
} else if m_brands.contains(&&brand2) {
if let Some(v) = inner_matches(&brand3) {
return Some(v);
}
}
}

Some(Compression::Unknown)
}
}

fn inner_matches(brand: &[u8; 4]) -> Option<Compression> {
// Since other non-heif files may contain ftype in the header
// we try to use brands to distinguish image files specifically.
// List of brands from here: https://mp4ra.org/#/brands
let valid_brands = [
// HEIF specific
b"avci", b"avcs", b"heic", b"heim",
b"heis", b"heix", b"hevc", b"hevm",
b"hevs", b"hevx", b"jpeg", b"jpgs",
b"mif1", b"msf1", b"mif2", b"pred",
// AVIF specific
b"avif", b"avio", b"avis", b"MA1A",
b"MA1B",
let hevc_brands = [
b"heic", b"heix", b"heis", b"hevs", b"heim", b"hevm", b"hevc", b"hevx",
];

for brand in valid_brands {
if brand == header_brand {
return true;
}
}

false
let av1_brands = [
b"avif", b"avio", b"avis",
// AVIF only
// REFS: https://rawcdn.githack.com/AOMediaCodec/av1-avif/67a92add6cd642a8863e386fa4db87954a6735d1/index.html#advanced-profile
b"MA1A", b"MA1B",
];
let jpeg_brands = [b"jpeg", b"jpgs"];

// unused
// REFS: https://github.com/MPEGGroup/FileFormatConformance/blob/6eef4e4c8bc70e2af9aeb1d62e764a6235f9d6a6/data/standard_features/23008-12/brands.json
// let avc_brands = [b"avci", b"avcs"];
// let vvc_brands = [b"vvic", b"vvis"];
// let evc_brands = [b"evbi", b"evbs", b"evmi", b"evms"];

// Maybe unnecessary
// REFS: https://github.com/nokiatech/heif/blob/be43efdf273ae9cf90e552b99f16ac43983f3d19/srcs/reader/heifreaderimpl.cpp#L1415
// REFS: https://github.com/nokiatech/heif/blob/be43efdf273ae9cf90e552b99f16ac43983f3d19/srcs/api-cpp/ImageItem.h#L37
// let feature_brands = [b"pred", b"auxl", b"thmb", b"base", b"dimg"];

Some(if hevc_brands.contains(&brand) {
Compression::Hevc
} else if av1_brands.contains(&brand) {
Compression::Av1
} else if jpeg_brands.contains(&brand) {
Compression::Jpeg
} else {
return None;
})
}

fn skip_to_tag<R: BufRead + Seek>(reader: &mut R, tag: &[u8]) -> ImageResult<u32> {
Expand Down
1 change: 1 addition & 0 deletions src/container/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod heif;
25 changes: 0 additions & 25 deletions src/formats/avif.rs

This file was deleted.

12 changes: 3 additions & 9 deletions src/formats/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
pub mod aesprite;
pub mod avif;
pub mod bmp;
pub mod dds;
pub mod exr;
pub mod farbfeld;
pub mod gif;
pub mod hdr;
pub mod heif;
pub mod ico;
pub mod jpeg;
pub mod jxl;
Expand All @@ -20,7 +18,7 @@ pub mod tiff;
pub mod vtf;
pub mod webp;

use crate::{ImageError, ImageResult, ImageType};
use crate::{container, ImageError, ImageResult, ImageType};
use std::io::{BufRead, Seek};

pub fn image_type<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageType> {
Expand Down Expand Up @@ -56,12 +54,8 @@ pub fn image_type<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageType> {
return Ok(ImageType::Webp);
}

if heif::matches(&header) {
return Ok(ImageType::Heif);
}

if avif::matches(&header) {
return Ok(ImageType::Avif);
if let Some(c) = container::heif::matches(&header, reader) {
return Ok(ImageType::Heif(c));
}

if jxl::matches(&header) {
Expand Down
4 changes: 2 additions & 2 deletions src/formats/pnm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
// If it's a comment, skip until newline
if trimmed_line.starts_with('#') {
read_until_capped(reader, b'\n', 1024)?;
continue
continue;
}

// If it's just empty skip
Expand Down Expand Up @@ -56,7 +56,7 @@ pub fn matches(header: &[u8]) -> bool {
}

// We only support P1 to P6. Currently ignoring P7, PF, PFM
if header[1] < b'1' && header[1] > b'6' {
if header[1] < b'1' || header[1] > b'6' {
return false;
}

Expand Down
19 changes: 11 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ use std::fs::File;
use std::io::{BufRead, BufReader, Cursor, Seek};
use std::path::Path;

mod container;
mod formats;
mod util;

mod formats;
use formats::*;
pub use container::heif::Compression;
use {
container::heif::{self},
formats::*,
};

/// An Error type used in failure cases.
#[derive(Debug)]
Expand Down Expand Up @@ -47,8 +52,6 @@ pub enum ImageType {
/// Animated sprite image format
/// <https://github.com/aseprite/aseprite>
Aseprite,
/// AV1 Image File Format
Avif,
/// Standard Bitmap
Bmp,
/// DirectDraw Surface
Expand All @@ -62,8 +65,8 @@ pub enum ImageType {
Gif,
/// Radiance HDR
Hdr,
/// High Efficiency Image File Format
Heif,
/// Image Container Format
Heif(Compression),
/// Icon file
Ico,
/// Standard JPEG
Expand Down Expand Up @@ -248,14 +251,12 @@ pub fn reader_size<R: BufRead + Seek>(mut reader: R) -> ImageResult<ImageSize> {
fn dispatch_header<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
match formats::image_type(reader)? {
ImageType::Aseprite => aesprite::size(reader),
ImageType::Avif => heif::size(reader), // AVIF uses HEIF size on purpose
ImageType::Bmp => bmp::size(reader),
ImageType::Dds => dds::size(reader),
ImageType::Exr => exr::size(reader),
ImageType::Farbfeld => farbfeld::size(reader),
ImageType::Gif => gif::size(reader),
ImageType::Hdr => hdr::size(reader),
ImageType::Heif => heif::size(reader),
ImageType::Ico => ico::size(reader),
ImageType::Jpeg => jpeg::size(reader),
ImageType::Jxl => jxl::size(reader),
Expand All @@ -268,5 +269,7 @@ fn dispatch_header<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize>
ImageType::Tiff => tiff::size(reader),
ImageType::Vtf => vtf::size(reader),
ImageType::Webp => webp::size(reader),

ImageType::Heif(..) => heif::size(reader),
}
}
42 changes: 39 additions & 3 deletions tests/avif.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@
#[cfg(test)]
use imagesize::{size, ImageSize};
use imagesize::{image_type, size, Compression, ImageSize, ImageType};

#[test]
fn avif_test() {
let dim = size("tests/images/avif/test.avif").unwrap();
assert_eq!(dim, ImageSize { width: 1204, height: 800 });
assert_eq!(
dim,
ImageSize {
width: 1204,
height: 800
}
);
}

#[test]
fn avif_multi_picks_largest() {
let dim = size("tests/images/avif/test.avifs").unwrap();
assert_eq!(dim, ImageSize { width: 159, height: 159 });
assert_eq!(
dim,
ImageSize {
width: 159,
height: 159
}
);
}

#[test]
fn avif_type() {
use std::{fs::File, io::Read};

let mut f = File::open("tests/images/avif/test.avif").unwrap();
let mut buf = vec![];
f.read_to_end(&mut buf).unwrap();

let ty = image_type(&buf).unwrap();
assert_eq!(ty, ImageType::Heif(Compression::Av1));
}

#[test]
fn avif_seq_type() {
use std::{fs::File, io::Read};

let mut f = File::open("tests/images/avif/test.avifs").unwrap();
let mut buf = vec![];
f.read_to_end(&mut buf).unwrap();

let ty = image_type(&buf).unwrap();
assert_eq!(ty, ImageType::Heif(Compression::Av1));
}
50 changes: 43 additions & 7 deletions tests/heic.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,50 @@
#[cfg(test)]
use imagesize::{size, ImageSize};
use imagesize::{image_type, size, Compression, ImageSize, ImageType};

#[test]
fn heif_test() {
let dim = size("tests/images/heic/test.heic").unwrap();
assert_eq!(dim, ImageSize { width: 1280, height: 720 });
fn heic_test() {
let dim = size("tests/images/heic/heic.heic").unwrap();
assert_eq!(
dim,
ImageSize {
width: 2448,
height: 3264
}
);
}

#[test]
fn heif_multi_picks_largest() {
let dim = size("tests/images/heic/IMG_0007.heic").unwrap();
assert_eq!(dim, ImageSize { width: 2448, height: 3264 });
fn heic_multi_picks_largest() {
let dim = size("tests/images/heic/heic.heic").unwrap();
assert_eq!(
dim,
ImageSize {
width: 2448,
height: 3264
}
);
}

#[test]
fn heic_type() {
use std::{fs::File, io::Read};

let mut f = File::open("tests/images/heic/heic.heic").unwrap();
let mut buf = vec![];
f.read_to_end(&mut buf).unwrap();

let ty = image_type(&buf).unwrap();
assert_eq!(ty, ImageType::Heif(Compression::Hevc));
}

#[test]
fn heic_msf1_type() {
use std::{fs::File, io::Read};

let mut f = File::open("tests/images/heic/heic_msf1.heic").unwrap();
let mut buf = vec![];
f.read_to_end(&mut buf).unwrap();

let ty = image_type(&buf).unwrap();
assert_eq!(ty, ImageType::Heif(Compression::Hevc));
}
File renamed without changes.
File renamed without changes.
Loading