Packages

phoenix_kit

2.60.2
2.60.2 2.60.1 2.60.0 2.59.0 2.58.0 2.57.1 2.57.0 2.56.1 2.56.0 2.55.1 2.55.0 2.54.2 2.54.1 2.54.0 2.53.0 2.52.2 2.52.1 2.52.0 2.51.0 2.50.0 2.49.1 2.49.0 2.48.0 2.47.0 2.46.0 2.45.0 2.44.0 2.43.1 2.43.0 2.42.1 2.42.0 2.41.6 2.41.4 2.41.3 2.41.2 2.41.1 2.41.0 2.40.1 2.40.0 2.39.0 2.38.1 2.38.0 2.37.5 2.37.4 2.37.3 2.37.2 2.37.1 2.37.0 2.36.1 2.36.0 2.35.0 2.34.0 2.33.0 2.32.1 2.32.0 2.31.1 2.31.0 2.30.0 2.29.1 2.29.0 2.28.2 2.28.1 2.28.0 2.27.2 2.27.1 2.27.0 2.26.1 2.26.0 2.25.0 2.24.0 2.23.3 2.23.2 2.23.1 2.23.0 2.22.24 2.22.23 2.22.22 2.22.21 2.22.20 2.22.19 2.22.18 2.22.17 2.22.16 2.22.15 2.22.14 2.22.13 2.22.12 2.22.11 2.22.10 2.22.9 2.22.8 2.22.7 2.22.6 2.22.5 2.22.4 2.22.3 2.22.2 2.22.1 2.22.0 2.21.5 2.21.4 2.21.3 2.21.2 2.21.1 2.21.0 2.20.0 2.19.0 2.18.1 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.2 2.14.1 2.14.0 2.13.19 2.13.18 2.13.17 2.13.16 2.13.15 2.13.13 2.13.12 2.13.11 2.13.10 2.13.9 2.13.8 2.13.7 2.13.6 2.13.5 2.13.4 2.13.3 2.13.2 2.13.1 2.13.0 2.12.1 2.12.0 2.11.0 2.10.0 2.9.0 2.8.1 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.0 2.0.1 2.0.0 1.7.236 1.7.235 1.7.234 1.7.233 1.7.232 1.7.231 1.7.230 1.7.229 1.7.228 1.7.227 1.7.226 1.7.225 1.7.224 1.7.223 1.7.222 1.7.221 1.7.220 1.7.219 1.7.218 1.7.217 1.7.216 1.7.215 1.7.214 1.7.213 1.7.212 1.7.211 1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules storage shape.ex
Raw

lib/modules/storage/shape.ex

defmodule PhoenixKit.Modules.Storage.Shape do
  @moduledoc """
  The shape of a picture: ordinary, **wide** (a horizontal panorama) or **tall**
  (a vertical one, a Pinterest pin).

  Shape is read from a file's `aspect_ratio` (width ÷ height, kept by Postgres,
  V212), never from EXIF: no camera flag is involved, and an edit that crops or
  rotates the photo changes its shape with no job to keep a flag right. Where wide
  stops and panorama starts is a taste, so the two thresholds live here and are
  the one place to change (a per-user setting would replace these constants):

    * **wide**: `aspect_ratio >= 2.0` (2:1 or wider);
    * **tall**: `aspect_ratio <= 0.5` (1:2 or taller; a phone screenshot, at
      about 1:2.16, counts as tall).

  ## Filtering

      Shape.filter(File, :wide)           # or :tall; nil / "all" leave it alone
      Storage.list_files_in_scope(nil, shape: :wide)

  A file with no usable size (`aspect_ratio` nil) is neither.
  """

  import Ecto.Query, only: [where: 3]

  @wide_min 2.0
  @tall_max 0.5

  @type t :: :wide | :tall | :normal

  @doc "The smallest `aspect_ratio` of a wide picture."
  @spec wide_min() :: float()
  def wide_min, do: @wide_min

  @doc "The largest `aspect_ratio` of a tall picture."
  @spec tall_max() :: float()
  def tall_max, do: @tall_max

  @doc """
  The shape of a ratio, a `{width, height}` pair or anything carrying them (a
  file); `nil` when there is no usable size.
  """
  @spec classify(number() | {integer(), integer()} | map() | nil) :: t() | nil
  def classify(ratio) when is_number(ratio) do
    cond do
      ratio >= @wide_min -> :wide
      ratio <= @tall_max -> :tall
      true -> :normal
    end
  end

  def classify({width, height}) when is_integer(width) and is_integer(height) do
    if width > 0 and height > 0, do: classify(width / height)
  end

  def classify(%{aspect_ratio: ratio}) when is_number(ratio), do: classify(ratio)
  def classify(%{width: width, height: height}), do: classify({width, height})
  def classify(_), do: nil

  @doc "A size in megapixels to one decimal: 4032 × 3024 is `12.2`. `0.0` without a usable size."
  @spec megapixels(integer() | nil, integer() | nil) :: float()
  def megapixels(width, height) when is_integer(width) and is_integer(height),
    do: Float.round(width * height / 1_000_000, 1)

  def megapixels(_width, _height), do: 0.0

  @doc """
  Narrows a query over files (or anything with an `aspect_ratio`) to a shape.
  `nil`, `"all"` and `""` leave it unfiltered; `:wide`/`"wide"` and
  `:tall`/`"tall"` filter; anything else raises, so a typo is not silently
  "no filter".
  """
  @spec filter(Ecto.Queryable.t(), t() | String.t() | nil) :: Ecto.Query.t()
  def filter(query, shape) when shape in [nil, "", "all"], do: query

  def filter(query, shape) when shape in [:wide, "wide"],
    do: where(query, [f], f.aspect_ratio >= ^@wide_min)

  def filter(query, shape) when shape in [:tall, "tall"],
    do: where(query, [f], f.aspect_ratio <= ^@tall_max)
end