defmodule Pdf.Images.JPEG do @moduledoc false import Pdf.Utils alias Pdf.{Array, Dictionary, Image} defstruct bit_depth: nil, height: nil, width: nil, color_type: nil, image_data: <<>> @doc ~S""" Decodes an image and returns the bit depth, height, width and colour color_type Examples: > Pdf.Image.JPEG.decode("path/to/image.jpg") %Pdf.Image.JPEG{bit_depth: 8, height: 75, width: 100, color_type: 3} """ def decode(image_data) do parse_jpeg(image_data) end defp parse_jpeg(<<255, 216, rest::binary>>), do: parse_jpeg(rest) [192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207] |> Enum.each(fn code -> defp parse_jpeg(<<255, unquote(code), _length::unsigned-integer-size(16), rest::binary>>), do: parse_image_data(rest) end) defp parse_jpeg(<<255, _code, length::unsigned-integer-size(16), rest::binary>>) do {:ok, data} = chomp(rest, length - 2) parse_jpeg(data) end def parse_image_data( <> ) do %__MODULE__{bit_depth: bits, height: height, width: width, color_type: color_type} end def parse_image_data(_, _), do: {:error, :parse_error} defp chomp(data, length) do data = :erlang.binary_part(data, {length, byte_size(data) - length}) {:ok, data} end def prepare_image(image_data) do %__MODULE__{bit_depth: bit_depth, height: height, width: width, color_type: color_type} = decode(image_data) build_dictionary(%Image{ bits: bit_depth, height: height, width: width, color_type: color_type, data: image_data, size: byte_size(image_data) }) end def build_dictionary(%Image{} = image) do %{bits: bit_depth, width: width, height: height, color_type: color_type, size: size} = image image_dic = Dictionary.new(%{ "Type" => n("XObject"), "Subtype" => n("Image"), "ColorSpace" => get_colorspace(color_type), "BitsPerComponent" => bit_depth, "Width" => width, "Height" => height, "Length" => size, "Filter" => Array.new([n("DCTDecode")]) }) image_dic = if color_type == 4 do # Invert colours, See :4.8.4 of the spec Dictionary.put(image_dic, "Decode", Array.new([1, 0, 1, 0, 1, 0, 1, 0])) else image_dic end %{image | dictionary: image_dic} end defp get_colorspace(0), do: n("DeviceGray") defp get_colorspace(1), do: n("DeviceGray") defp get_colorspace(2), do: n("DeviceGray") defp get_colorspace(3), do: n("DeviceRGB") defp get_colorspace(4), do: n("DeviceCMYK") defp get_colorspace(_), do: raise("Unsupported number of JPG color_type") end