defmodule Infer.Image do @moduledoc """ Image type matchers based on the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming)) """ @doc """ Takes the binary file contents as arguments. Returns `true` if it's a jpeg. ## Examples iex> binary = File.read!("test/images/sample.jpg") iex> Infer.Image.jpeg?(binary) true iex> binary = File.read!("test/images/sample.jp2") iex> Infer.Image.jpeg?(binary) false """ @spec jpeg?(binary()) :: boolean() def jpeg?(<<0xFF, 0xD8, 0xFF>> <> _), do: true def jpeg?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a jpeg2000. ## Examples iex> binary = File.read!("test/images/sample.jp2") iex> Infer.Image.jpeg2000?(binary) true iex> binary = File.read!("test/images/sample.jpg") iex> Infer.Image.jpeg2000?(binary) false """ @spec jpeg2000?(binary()) :: boolean() def jpeg2000?(<<0x0, 0x0, 0x0, 0xC, 0x6A, 0x50, 0x20, 0x20, 0xD, 0xA, 0x87, 0xA, 0x0, _rest::binary>>), do: true def jpeg2000?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a png. ## Examples iex> binary = File.read!("test/images/sample.png") iex> Infer.Image.png?(binary) true iex> binary = File.read!("test/images/sample.jpg") iex> Infer.Image.png?(binary) false """ @spec png?(binary()) :: boolean() def png?(<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, _rest::binary>>), do: true def png?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a gif. ## Examples iex> binary = File.read!("test/images/sample.gif") iex> Infer.Image.gif?(binary) true iex> binary = File.read!("test/images/sample.jpg") iex> Infer.Image.gif?(binary) false """ @spec gif?(binary()) :: boolean() def gif?(<<0x47, 0x49, 0x46, _rest::binary>>), do: true def gif?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a webp. ## Examples iex> binary = File.read!("test/images/sample.webp") iex> Infer.Image.webp?(binary) true iex> binary = File.read!("test/images/sample.gif") iex> Infer.Image.webp?(binary) false """ @spec webp?(binary()) :: boolean() def webp?(<<_head::binary-size(8), 0x57, 0x45, 0x42, 0x50, _rest::binary>>), do: true def webp?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a cr2. ## Examples iex> binary = File.read!("test/images/sample.cr2") iex> Infer.Image.cr2?(binary) true iex> binary = File.read!("test/images/sample.tiff") iex> Infer.Image.cr2?(binary) false """ @spec cr2?(binary()) :: boolean() def cr2?(<<0x49, 0x49, 0x2A, 0x0, _data::binary-size(4), 0x43, 0x52, 0x02, _rest::binary>>), do: true def cr2?(<<0x4D, 0x4D, 0x0, 0x2A, _data::binary-size(4), 0x43, 0x52, 0x02, _rest::binary>>), do: true def cr2?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a tiff. ## Examples iex> binary = File.read!("test/images/sample.tiff") iex> Infer.Image.tiff?(binary) true iex> binary = File.read!("test/images/sample.cr2") iex> Infer.Image.tiff?(binary) false """ @spec tiff?(binary()) :: boolean() def tiff?(<<0x49, 0x49, 0x2A, 0x0, _data::binary-size(4), i_8, i_9, _rest::binary>> = binary) when i_8 != <<0x43>> and i_9 != <<0x52>>, do: !cr2?(binary) def tiff?(<<0x4D, 0x4D, 0x0, 0x2A, _data::binary-size(4), i_8, i_9, _rest::binary>> = binary) when i_8 != <<0x43>> and i_9 != <<0x52>>, do: !cr2?(binary) def tiff?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a bmp. ## Examples iex> binary = File.read!("test/images/sample.bmp") iex> Infer.Image.bmp?(binary) true iex> binary = File.read!("test/images/sample.png") iex> Infer.Image.bmp?(binary) false """ @spec bmp?(binary()) :: boolean() def bmp?(<<0x42, 0x4D, _rest::binary>>), do: true def bmp?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a jxr. ## Examples iex> binary = File.read!("test/images/sample.jxr") iex> Infer.Image.jxr?(binary) true iex> binary = File.read!("test/images/sample.png") iex> Infer.Image.jxr?(binary) false """ @spec jxr?(binary()) :: boolean() def jxr?(<<0x49, 0x49, 0xBC, _rest::binary>>), do: true def jxr?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a psd. ## Examples iex> binary = File.read!("test/images/sample.psd") iex> Infer.Image.psd?(binary) true iex> binary = File.read!("test/images/sample.png") iex> Infer.Image.psd?(binary) false """ @spec psd?(binary()) :: boolean() def psd?(<<0x38, 0x42, 0x50, 0x53, _rest::binary>>), do: true def psd?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a ico. ## Examples iex> binary = File.read!("test/images/sample.ico") iex> Infer.Image.ico?(binary) true iex> binary = File.read!("test/images/sample.png") iex> Infer.Image.ico?(binary) false """ @spec ico?(binary()) :: boolean() def ico?(<<0x00, 0x00, 0x01, 0x00, _rest::binary>>), do: true def ico?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a heif. ## Examples iex> binary = File.read!("test/images/sample.heif") iex> Infer.Image.heif?(binary) true iex> binary = File.read!("test/images/sample.avif") iex> Infer.Image.heif?(binary) false """ @spec heif?(binary()) :: boolean() def heif?(<> = binary) when bit_size(ftyp_length) >= 16 do case get_ftyp(binary) do {"heic", _minor, _compatbile} -> true {major, _minor, compatible} when major in ["mif1", "msf1"] -> 'heic' in compatible _ -> false end end def heif?(_binary), do: false @doc """ Takes the binary file contents as arguments. Returns `true` if it's a avif. ## Examples iex> binary = File.read!("test/images/sample.avif") iex> Infer.Image.avif?(binary) true iex> binary = File.read!("test/images/sample.heif") iex> Infer.Image.avif?(binary) false """ @spec avif?(binary()) :: boolean() def avif?(<> = binary) when bit_size(ftyp_length) >= 16 do case get_ftyp(binary) do {major, _minor, _compatbile} when major in ["avif", "avis"] -> true {_major, _minor, compatible} -> 'avif' in compatible || 'avis' in compatible _ -> false end end def avif?(_binary), do: false defp get_ftyp(<>) do compatible = rest |> :binary.bin_to_list() |> Stream.chunk_every(4) |> Enum.take(Enum.max([Integer.floor_div(bit_size(ftyp_length), 4) - 4, 0])) {major, minor, compatible} end end