Skip to content

Commit 22bf034

Browse files
committed
add support for the AVIF, HEIC and HEIF formats
These 3 formats have in common that they use the HEIF container format specified in ISO/IEC 23008-12. The MIME types and variant types (file extension) for HEIC and HEIF are based on sections C.2 and D.2 of the specification. A HEIF container can contain multiple images, so the logic is kept as in `:ico` (take the size of the largest image). The size of an image is found in the `ispe` box.
1 parent 8588595 commit 22bf034

File tree

5 files changed

+221
-5
lines changed

5 files changed

+221
-5
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ Supported formats (image type to be parsed as):
5757
- `:psd`
5858
- `:tiff`
5959
- `:webp` (VP8X animated in `v0.2.4`)
60+
- `:avif`
61+
- `:heic`
62+
- `:heif`
6063

6164
## Mime-types and Variants
6265

@@ -68,9 +71,14 @@ Each mime-type can be linked to at least one variant type:
6871

6972
| mime-type | variant type | description |
7073
| ------------------------- | ------------ | ------------------ |
74+
| `image/avif` | `AVIF` | |
7175
| `image/bmp` | `BMP` | |
7276
| `image/gif` | `GIF87a` | 87a gif spec |
7377
| `image/gif` | `GIF89a` | 89a gif spec |
78+
| `image/heic` | `HEIC` | |
79+
| `image/heic-sequence` | `HEICS` | |
80+
| `image/heif` | `HEIF` | |
81+
| `image/heif-sequence` | `HEIFS` | |
7482
| `image/x-icon` | `ICO` | |
7583
| `image/jpeg` | `baseJPEG` | baseline JPEG |
7684
| `image/jpeg` | `progJPEG` | progressive JPEG |
@@ -89,10 +97,10 @@ Each mime-type can be linked to at least one variant type:
8997
The variant type is created just to provide a bit more of information
9098
for every image format (if applicable).
9199

92-
*Note*: `:ico` returns the dimensions of the largest image contained (not the first found).
100+
*Note*: `:avif`, `:heic`, `:heif` and `:ico` return the dimensions of the largest image contained (not the first found).
93101

94102
The guessing functions try to detect the format of the binary by testing every available type based on its global usage (popularity, [usage of image file formats](https://w3techs.com/technologies/overview/image_format/all), but still keeping the `:png` as the first one):
95-
- `:png`, `:jpeg`, `:gif`, `:bmp`, `:ico`, `:tiff`, `:webp`, `:psd`, `:jp2`, `:pnm`
103+
- `:png`, `:jpeg`, `:gif`, `:bmp`, `:ico`, `:tiff`, `:webp`, `:psd`, `:jp2`, `:pnm`, `:avif`, `:heic`, `:heif`
96104

97105
**Warnings:**
98106

lib/ex_image_info.ex

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
defmodule ExImageInfo do
2-
alias ExImageInfo.Types.{BMP, GIF, ICO, JP2, JPEG, PNG, PNM, PSD, TIFF, WEBP}
2+
alias ExImageInfo.Types.{
3+
AVIF,
4+
BMP,
5+
GIF,
6+
HEIC,
7+
HEIF,
8+
ICO,
9+
JP2,
10+
JPEG,
11+
PNG,
12+
PNM,
13+
PSD,
14+
TIFF,
15+
WEBP
16+
}
317

418
@moduledoc """
519
ExImageInfo is an Elixir library to parse images (binaries) and get the dimensions (size), detected mime-type and overall validity for a set of image formats. Main module to parse a binary and get if it seems to be an image (validity), the mime-type (and variant detected) and the dimensions of the image, based on a specific image format.
@@ -43,9 +57,14 @@ defmodule ExImageInfo do
4357
4458
| mime-type | variant type | description |
4559
| ------------------------- | ------------ | ------------------ |
60+
| `image/avif` | `AVIF` | |
4661
| `image/bmp` | `BMP` | |
4762
| `image/gif` | `GIF87a` | 87a gif spec |
4863
| `image/gif` | `GIF89a` | 89a gif spec |
64+
| `image/heic` | `HEIC` | |
65+
| `image/heic-sequence` | `HEICS` | |
66+
| `image/heif` | `HEIF` | |
67+
| `image/heif-sequence` | `HEIFS` | |
4968
| `image/x-icon` | `ICO` | |
5069
| `image/jpeg` | `baseJPEG` | baseline JPEG |
5170
| `image/jpeg` | `progJPEG` | progressive JPEG |
@@ -67,13 +86,27 @@ defmodule ExImageInfo do
6786
*Note*: `:ico` returns the dimensions of the largest image contained (not the first found).
6887
6988
The guessing functions try to detect the format of the binary by testing every available type based on its global usage (popularity, [usage of image file formats](https://w3techs.com/technologies/overview/image_format/all), but still keeping the `:png` as the first one):
70-
- `:png`, `:jpeg`, `:gif`, `:bmp`, `:ico`, `:tiff`, `:webp`, `:psd`, `:jp2`, `:pnm`
89+
- `:png`, `:jpeg`, `:gif`, `:bmp`, `:ico`, `:tiff`, `:webp`, `:psd`, `:jp2`, `:pnm`, `:avif`, `:heic`, `:heif`
7190
"""
7291

7392
# Guessing function ordered by global usage
7493
# https://w3techs.com/technologies/overview/image_format/all
7594
# but still keeping :png as the first
76-
@types [:png, :jpeg, :gif, :bmp, :ico, :tiff, :webp, :psd, :jp2, :pnm]
95+
@types [
96+
:png,
97+
:jpeg,
98+
:gif,
99+
:bmp,
100+
:ico,
101+
:tiff,
102+
:webp,
103+
:psd,
104+
:jp2,
105+
:pnm,
106+
:avif,
107+
:heic,
108+
:heif
109+
]
77110

78111
@typedoc "The supported image formats"
79112
@type image_format ::
@@ -87,6 +120,9 @@ defmodule ExImageInfo do
87120
| :psd
88121
| :jp2
89122
| :pnm
123+
| :avif
124+
| :heic
125+
| :heif
90126

91127
## Public API
92128

@@ -130,6 +166,9 @@ defmodule ExImageInfo do
130166
def seems?(binary, :jp2), do: JP2.seems?(binary)
131167
def seems?(binary, :pnm), do: PNM.seems?(binary)
132168
def seems?(binary, :ico), do: ICO.seems?(binary)
169+
def seems?(binary, :avif), do: AVIF.seems?(binary)
170+
def seems?(binary, :heic), do: HEIC.seems?(binary)
171+
def seems?(binary, :heif), do: HEIF.seems?(binary)
133172
def seems?(_, _), do: nil
134173

135174
@doc """
@@ -201,6 +240,9 @@ defmodule ExImageInfo do
201240
def type(binary, :jp2), do: JP2.type(binary)
202241
def type(binary, :pnm), do: PNM.type(binary)
203242
def type(binary, :ico), do: ICO.type(binary)
243+
def type(binary, :avif), do: AVIF.type(binary)
244+
def type(binary, :heic), do: HEIC.type(binary)
245+
def type(binary, :heif), do: HEIF.type(binary)
204246
def type(_, _), do: nil
205247

206248
@doc """
@@ -272,6 +314,9 @@ defmodule ExImageInfo do
272314
def info(binary, :jp2), do: JP2.info(binary)
273315
def info(binary, :pnm), do: PNM.info(binary)
274316
def info(binary, :ico), do: ICO.info(binary)
317+
def info(binary, :avif), do: AVIF.info(binary)
318+
def info(binary, :heic), do: HEIC.info(binary)
319+
def info(binary, :heif), do: HEIF.info(binary)
275320
def info(_, _), do: nil
276321

277322
@doc """

lib/ex_image_info/types/avif.ex

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
defmodule ExImageInfo.Types.AVIF do
2+
@moduledoc false
3+
4+
alias ExImageInfo.Types.HEIF
5+
6+
@behaviour ExImageInfo.Detector
7+
8+
@mime "image/avif"
9+
@signature <<"ftypavif">>
10+
@ftype "AVIF"
11+
12+
## Public API
13+
14+
def seems?(<<_::size(32), @signature, _rest::binary>>), do: true
15+
def seems?(_), do: false
16+
17+
def info(<<_::size(32), @signature, rest::binary>>),
18+
do: HEIF.info(rest, @mime, @ftype)
19+
20+
def info(_), do: nil
21+
22+
def type(<<_::size(32), @signature, _rest::binary>>), do: {@mime, @ftype}
23+
def type(_), do: nil
24+
end

lib/ex_image_info/types/heic.ex

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
defmodule ExImageInfo.Types.HEIC do
2+
@moduledoc false
3+
4+
alias ExImageInfo.Types.HEIF
5+
6+
@behaviour ExImageInfo.Detector
7+
8+
@mime_heic "image/heic"
9+
@mime_heic_sequence "image/heic-sequence"
10+
@signature_heic <<"ftypheic">>
11+
@signature_heim <<"ftypheim">>
12+
@signature_heis <<"ftypheis">>
13+
@signature_heix <<"ftypheix">>
14+
@signature_hevc <<"ftyphevc">>
15+
@signature_hevm <<"ftyphevm">>
16+
@signature_hevs <<"ftyphevs">>
17+
@signature_hevx <<"ftyphevx">>
18+
@ftype_heic "HEIC"
19+
@ftype_heic_sequence "HEICS"
20+
21+
## Public API
22+
23+
def seems?(<<_::size(32), @signature_heic, _rest::binary>>), do: true
24+
def seems?(<<_::size(32), @signature_heim, _rest::binary>>), do: true
25+
def seems?(<<_::size(32), @signature_heis, _rest::binary>>), do: true
26+
def seems?(<<_::size(32), @signature_heix, _rest::binary>>), do: true
27+
def seems?(_), do: false
28+
29+
def info(<<_::size(32), @signature_heic, rest::binary>>),
30+
do: HEIF.info(rest, @mime_heic, @ftype_heic)
31+
32+
def info(<<_::size(32), @signature_heim, rest::binary>>),
33+
do: HEIF.info(rest, @mime_heic, @ftype_heic)
34+
35+
def info(<<_::size(32), @signature_heis, rest::binary>>),
36+
do: HEIF.info(rest, @mime_heic, @ftype_heic)
37+
38+
def info(<<_::size(32), @signature_heix, rest::binary>>),
39+
do: HEIF.info(rest, @mime_heic, @ftype_heic)
40+
41+
def info(<<_::size(32), @signature_hevc, rest::binary>>),
42+
do: HEIF.info(rest, @mime_heic_sequence, @ftype_heic_sequence)
43+
44+
def info(<<_::size(32), @signature_hevm, rest::binary>>),
45+
do: HEIF.info(rest, @mime_heic_sequence, @ftype_heic_sequence)
46+
47+
def info(<<_::size(32), @signature_hevs, rest::binary>>),
48+
do: HEIF.info(rest, @mime_heic_sequence, @ftype_heic_sequence)
49+
50+
def info(<<_::size(32), @signature_hevx, rest::binary>>),
51+
do: HEIF.info(rest, @mime_heic_sequence, @ftype_heic_sequence)
52+
53+
def info(_), do: nil
54+
55+
def type(<<_::size(32), @signature_heic, _rest::binary>>),
56+
do: {@mime_heic, @ftype_heic}
57+
58+
def type(<<_::size(32), @signature_heim, _rest::binary>>),
59+
do: {@mime_heic, @ftype_heic}
60+
61+
def type(<<_::size(32), @signature_heis, _rest::binary>>),
62+
do: {@mime_heic, @ftype_heic}
63+
64+
def type(<<_::size(32), @signature_heix, _rest::binary>>),
65+
do: {@mime_heic, @ftype_heic}
66+
67+
def type(<<_::size(32), @signature_hevc, _rest::binary>>),
68+
do: {@mime_heic_sequence, @ftype_heic_sequence}
69+
70+
def type(<<_::size(32), @signature_hevm, _rest::binary>>),
71+
do: {@mime_heic_sequence, @ftype_heic_sequence}
72+
73+
def type(<<_::size(32), @signature_hevs, _rest::binary>>),
74+
do: {@mime_heic_sequence, @ftype_heic_sequence}
75+
76+
def type(<<_::size(32), @signature_hevx, _rest::binary>>),
77+
do: {@mime_heic_sequence, @ftype_heic_sequence}
78+
79+
def type(_), do: nil
80+
end

lib/ex_image_info/types/heif.ex

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
defmodule ExImageInfo.Types.HEIF do
2+
@moduledoc false
3+
4+
@behaviour ExImageInfo.Detector
5+
6+
@mime_heif "image/heif"
7+
@mime_heif_sequence "image/heif-sequence"
8+
@signature_mif1 <<"ftypmif1">>
9+
@signature_msf1 <<"ftypmsf1">>
10+
@ftype_heif "HEIF"
11+
@ftype_heif_sequence "HEIFS"
12+
13+
## Public API
14+
15+
def seems?(<<_::size(32), @signature_mif1, _rest::binary>>), do: true
16+
def seems?(<<_::size(32), @signature_msf1, _rest::binary>>), do: true
17+
def seems?(_), do: false
18+
19+
def info(<<_::size(32), @signature_mif1, rest::binary>>) do
20+
info(rest, @mime_heif, @ftype_heif)
21+
end
22+
23+
def info(<<_::size(32), @signature_msf1, rest::binary>>),
24+
do: info(rest, @mime_heif_sequence, @ftype_heif_sequence)
25+
26+
def info(_), do: nil
27+
28+
def info(binary, mime, ftype) do
29+
case size(binary) do
30+
{w, h} -> {mime, w, h, ftype}
31+
_ -> nil
32+
end
33+
end
34+
35+
def type(<<_::size(32), @signature_mif1, _rest::binary>>),
36+
do: {@mime_heif, @ftype_heif}
37+
38+
def type(<<_::size(32), @signature_msf1, _rest::binary>>),
39+
do: {@mime_heif_sequence, @ftype_heif_sequence}
40+
41+
def type(_), do: nil
42+
43+
defp size(binary) do
44+
[_hd | ispe_boxes] = String.split(binary, "ispe")
45+
46+
if length(ispe_boxes) > 0 do
47+
{width, height} =
48+
ispe_boxes
49+
|> Enum.map(fn <<_::size(32), width::size(32), height::size(32), _rest::binary>> ->
50+
{width, height}
51+
end)
52+
|> Enum.max_by(&elem(&1, 0))
53+
54+
{width, height}
55+
else
56+
nil
57+
end
58+
end
59+
end

0 commit comments

Comments
 (0)