defmodule Blurhash.Decoder do import Blurhash.Utils alias Blurhash.Base83 use Bitwise defp size_flag(blurhash) do with {:ok, encoded_flag, rest} <- Base83.decode_number(blurhash, 1) do x = rem(encoded_flag, 9) + 1 y = floor(encoded_flag / 9) + 1 {:ok, {x, y}, rest} end end defp max_ac(blurhash) do with {:ok, quantized_max, rest} <- Base83.decode_number(blurhash, 1) do {:ok, (quantized_max + 1) / 166, rest} end end defp average_color_and_dc(blurhash) do with {:ok, raw, rest} <- Base83.decode_number(blurhash, 4) do {r, g, b} = color = {bsr(raw, 16), band(bsr(raw, 8), 255), band(raw, 255)} dc = {srgb_to_linear(r), srgb_to_linear(g), srgb_to_linear(b)} {:ok, {color, dc}, rest} end end def construct_matrix(encoded_ac, max_ac, x, y, dc) do size = x * y - 1 try do # We start with 1 because {0, 0} is the DC {ac_values, rest} = Enum.map_reduce(1..size, encoded_ac, fn index, rest -> case Base83.decode_number(rest, 2) do {:ok, value, rest} -> # add matrix position with the color since we will need it for # inverse dct later matrix_pos = {rem(index, x), floor(index / x)} quantized_r = floor(value / (19 * 19)) quantized_g = floor(rem(floor(value / 19), 19)) quantized_b = rem(value, 19) r = unquantize_color(quantized_r, max_ac) g = unquantize_color(quantized_g, max_ac) b = unquantize_color(quantized_b, max_ac) {{matrix_pos, {r, g, b}}, rest} # Haven't found a more elegant solution to throwing in this case error -> throw(error) end end) if rest != "" do {:error, :unexpected_components} else {r, g, b} = dc matrix = [{{0, 0}, {r, g, b}} | ac_values] {:ok, matrix} end catch error -> error end end def construct_pixel_iodata(width, height, matrix) do Enum.reduce((height - 1)..0, [], fn y, acc -> pixel_row = Enum.reduce((width - 1)..0, [], fn x, acc -> {linear_r, linear_g, linear_b} = Enum.reduce(matrix, {0, 0, 0}, fn {{component_x, component_y}, {current_red, current_green, current_blue}}, {red, green, blue} -> idct_basis = :math.cos(:math.pi() * x * component_x / width) * :math.cos(:math.pi() * y * component_y / height) {red + current_red * idct_basis, green + current_green * idct_basis, blue + current_blue * idct_basis} end) r = linear_to_srgb(linear_r) g = linear_to_srgb(linear_g) b = linear_to_srgb(linear_b) [<> | acc] end) [pixel_row | acc] end) end def decode(blurhash, width, height) do with {:ok, {components_x, components_y}, rest} <- size_flag(blurhash), {:ok, max_ac, rest} <- max_ac(rest), {:ok, {average_color, dc}, rest} <- average_color_and_dc(rest), {:ok, matrix} <- construct_matrix(rest, max_ac, components_x, components_y, dc) do pixels = construct_pixel_iodata(width, height, matrix) {:ok, pixels, average_color} end end end