Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/image_plug.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule ImagePlug do
@type imgp_pixels() :: {:pixels, imgp_number()}
@type imgp_pct() :: {:percent, imgp_number()}
@type imgp_scale() :: {:scale, imgp_number(), imgp_number()}
@type imgp_ratio() :: {:ratio, imgp_number(), imgp_number()}
@type imgp_ratio() :: {imgp_number(), imgp_number()}
@type imgp_length() :: imgp_pixels() | imgp_pct() | imgp_scale()

@alpha_format_priority ~w(image/avif image/webp image/png)
Expand Down
42 changes: 38 additions & 4 deletions lib/image_plug/param_parser/twicpics.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,36 @@ defmodule ImagePlug.ParamParser.Twicpics do
"resize" => {ImagePlug.Transform.Scale, Twicpics.Transform.ScaleParser},
"focus" => {ImagePlug.Transform.Focus, Twicpics.Transform.FocusParser},
"contain" => {ImagePlug.Transform.Contain, Twicpics.Transform.ContainParser},
"contain-min" => {ImagePlug.Transform.Contain, Twicpics.Transform.ContainMinParser},
"contain-max" => {ImagePlug.Transform.Contain, Twicpics.Transform.ContainMaxParser},
"cover" => {ImagePlug.Transform.Cover, Twicpics.Transform.CoverParser},
"cover-min" => {ImagePlug.Transform.Cover, Twicpics.Transform.CoverMinParser},
"cover-max" => {ImagePlug.Transform.Cover, Twicpics.Transform.CoverMaxParser},
"output" => {ImagePlug.Transform.Output, Twicpics.Transform.OutputParser}
}

@shadowable_transforms ~w(resize cover focus output)

# consecutive transforms that can safely be shadowed
# e.g. two consecutive scale operations will only keep the last one
defp shadow_transforms(transform_kvs) do
Enum.reduce(transform_kvs, [], fn
transform, [] ->
[transform]

{key, _, _} = new, [{prev_key, _, _} | tail] = acc when key == prev_key ->
if Enum.member?(@shadowable_transforms, key) do
[new | tail]
else
[new | acc]
end

elem, acc ->
[elem | acc]
end)
|> Enum.reverse()
end

@transform_keys Map.keys(@transforms)
@query_param "twic"
@query_param_prefix "v1/"
Expand Down Expand Up @@ -53,12 +80,12 @@ defmodule ImagePlug.ParamParser.Twicpics do
{transform_name, params_str, key_start_pos}, {:ok, transforms_acc} ->
{transform_mod, parser_mod} = Map.get(@transforms, transform_name)

# key start pos + key length + 1 (=-sign)
# key start pos + key length + 1 (the = char)
value_pos = key_start_pos + String.length(transform_name) + 1

case parser_mod.parse(params_str, value_pos) do
{:ok, parsed_params} ->
{:cont, {:ok, [{transform_mod, parsed_params} | transforms_acc]}}
{:cont, {:ok, [{transform_name, transform_mod, parsed_params} | transforms_acc]}}

{:error, _reason} = error ->
{:halt, error}
Expand All @@ -69,8 +96,15 @@ defmodule ImagePlug.ParamParser.Twicpics do
error
end
|> case do
{:ok, transforms} -> {:ok, Enum.reverse(transforms)}
other -> other
{:ok, transforms} ->
{:ok,
transforms
|> Enum.reverse()
|> shadow_transforms()
|> Enum.map(fn {_name, mod, params} -> {mod, params} end)}

other ->
other
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule ImagePlug.ParamParser.Twicpics.Transform.ContainMaxParser do
alias ImagePlug.ParamParser.Twicpics.SizeParser
alias ImagePlug.ParamParser.Twicpics.RatioParser
alias ImagePlug.ParamParser.Twicpics.Utils
alias ImagePlug.Transform.Contain.ContainParams

@doc """
Parses a string into a `ImagePlug.Transform.Contain.ContainParams` struct.

Syntax:
* `contain-max=<size>`

## Examples

iex> ImagePlug.ParamParser.Twicpics.Transform.ContainMaxParser.parse("250x25.5")
{:ok, %ImagePlug.Transform.Contain.ContainParams{width: {:pixels, 250}, height: {:pixels, 25.5}, constraint: :max}}
"""

def parse(input, pos_offset \\ 0) do
case SizeParser.parse(input, pos_offset) do
{:ok, %{width: width, height: height}} ->
{:ok, %ContainParams{width: width, height: height, constraint: :max}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule ImagePlug.ParamParser.Twicpics.Transform.ContainMinParser do
alias ImagePlug.ParamParser.Twicpics.SizeParser
alias ImagePlug.ParamParser.Twicpics.RatioParser
alias ImagePlug.ParamParser.Twicpics.Utils
alias ImagePlug.Transform.Contain.ContainParams

@doc """
Parses a string into a `ImagePlug.Transform.Contain.ContainParams` struct.

Syntax:
* `contain-min=<size>`

## Examples

iex> ImagePlug.ParamParser.Twicpics.Transform.ContainMinParser.parse("250x25.5")
{:ok, %ImagePlug.Transform.Contain.ContainParams{width: {:pixels, 250}, height: {:pixels, 25.5}, constraint: :min}}
"""

def parse(input, pos_offset \\ 0) do
case SizeParser.parse(input, pos_offset) do
{:ok, %{width: width, height: height}} ->
{:ok, %ContainParams{width: width, height: height, constraint: :min}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ defmodule ImagePlug.ParamParser.Twicpics.Transform.ContainParser do
* `contain=<size>`

## Examples
iex> ImagePlug.ParamParser.Twicpics.Transform.ContainParser.parse("250x25.5")
{:ok, %ImagePlug.Transform.Contain.ContainParams{width: {:pixels, 250}, height: {:pixels, 25.5}}}

iex> ImagePlug.ParamParser.Twicpics.Transform.ContainParser.parse("250x25.5p")
{:ok, %ImagePlug.Transform.Contain.ContainParams{width: {:pixels, 250}, height: {:percent, 25.5}, constraint: :none}}
"""

def parse(input, pos_offset \\ 0) do
case SizeParser.parse(input, pos_offset) do
{:ok, %{width: width, height: height}} ->
{:ok, %ContainParams{width: width, height: height}}
{:ok, %ContainParams{width: width, height: height, constraint: :none}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
Expand Down
28 changes: 28 additions & 0 deletions lib/image_plug/param_parser/twicpics/transform/cover_max_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule ImagePlug.ParamParser.Twicpics.Transform.CoverMaxParser do
alias ImagePlug.ParamParser.Twicpics.SizeParser
alias ImagePlug.ParamParser.Twicpics.RatioParser
alias ImagePlug.ParamParser.Twicpics.Utils
alias ImagePlug.Transform.Cover.CoverParams

@doc """
Parses a string into a `ImagePlug.Transform.Cover.CoverParams` struct.

Syntax:
* `cover-max=<size>`

## Examples

iex> ImagePlug.ParamParser.Twicpics.Transform.CoverMaxParser.parse("250x25.5")
{:ok, %ImagePlug.Transform.Cover.CoverParams{width: {:pixels, 250}, height: {:pixels, 25.5}, constraint: :max}}
"""

def parse(input, pos_offset \\ 0) do
case SizeParser.parse(input, pos_offset) do
{:ok, %{width: width, height: height}} ->
{:ok, %CoverParams{width: width, height: height, constraint: :max}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
end
end
end
28 changes: 28 additions & 0 deletions lib/image_plug/param_parser/twicpics/transform/cover_min_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule ImagePlug.ParamParser.Twicpics.Transform.CoverMinParser do
alias ImagePlug.ParamParser.Twicpics.SizeParser
alias ImagePlug.ParamParser.Twicpics.RatioParser
alias ImagePlug.ParamParser.Twicpics.Utils
alias ImagePlug.Transform.Cover.CoverParams

@doc """
Parses a string into a `ImagePlug.Transform.Cover.CoverParams` struct.

Syntax:
* `cover-min=<size>`

## Examples

iex> ImagePlug.ParamParser.Twicpics.Transform.CoverMinParser.parse("250x25.5")
{:ok, %ImagePlug.Transform.Cover.CoverParams{width: {:pixels, 250}, height: {:pixels, 25.5}, constraint: :min}}
"""

def parse(input, pos_offset \\ 0) do
case SizeParser.parse(input, pos_offset) do
{:ok, %{width: width, height: height}} ->
{:ok, %CoverParams{width: width, height: height, constraint: :min}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
end
end
end
48 changes: 48 additions & 0 deletions lib/image_plug/param_parser/twicpics/transform/cover_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
defmodule ImagePlug.ParamParser.Twicpics.Transform.CoverParser do
alias ImagePlug.ParamParser.Twicpics.SizeParser
alias ImagePlug.ParamParser.Twicpics.RatioParser
alias ImagePlug.ParamParser.Twicpics.Utils
alias ImagePlug.Transform.Cover.CoverParams

@doc """
Parses a string into a `ImagePlug.Transform.Cover.CoverParams` struct.

Syntax
* `cover=<size>`
* `cover=<ratio>`

## Examples

iex> ImagePlug.ParamParser.Twicpics.Transform.CoverParser.parse("250x25.5")
{:ok, %ImagePlug.Transform.Cover.CoverParams{type: :dimensions, width: {:pixels, 250}, height: {:pixels, 25.5}, constraint: :none}}

iex> ImagePlug.ParamParser.Twicpics.Transform.CoverParser.parse("16:9")
{:ok, %ImagePlug.Transform.Cover.CoverParams{type: :ratio, ratio: {16, 9}}}
"""

def parse(input, pos_offset \\ 0) do
if String.contains?(input, ":"),
do: parse_ratio(input, pos_offset),
else: parse_size(input, pos_offset)
end

defp parse_ratio(input, pos_offset) do
case RatioParser.parse(input, pos_offset) do
{:ok, %{width: width, height: height}} ->
{:ok, %CoverParams{type: :ratio, ratio: {width, height}}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
end
end

defp parse_size(input, pos_offset) do
case SizeParser.parse(input, pos_offset) do
{:ok, %{width: width, height: height}} ->
{:ok, %CoverParams{type: :dimensions, width: width, height: height, constraint: :none}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
end
end
end
18 changes: 9 additions & 9 deletions lib/image_plug/param_parser/twicpics/transform/scale_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,25 @@ defmodule ImagePlug.ParamParser.Twicpics.Transform.ScaleParser do

## Examples
iex> ImagePlug.ParamParser.Twicpics.Transform.ScaleParser.parse("250x25p")
{:ok, %ImagePlug.Transform.Scale.ScaleParams{method: %ImagePlug.Transform.Scale.ScaleParams.Dimensions{width: {:pixels, 250}, height: {:percent, 25}}}}
{:ok, %ImagePlug.Transform.Scale.ScaleParams{type: :dimensions, width: {:pixels, 250}, height: {:percent, 25}}}

iex> ImagePlug.ParamParser.Twicpics.Transform.ScaleParser.parse("-x25p")
{:ok, %ImagePlug.Transform.Scale.ScaleParams{method: %ImagePlug.Transform.Scale.ScaleParams.Dimensions{width: :auto, height: {:percent, 25}}}}
{:ok, %ImagePlug.Transform.Scale.ScaleParams{type: :dimensions, width: :auto, height: {:percent, 25}}}

iex> ImagePlug.ParamParser.Twicpics.Transform.ScaleParser.parse("50.5px-")
{:ok, %ImagePlug.Transform.Scale.ScaleParams{method: %ImagePlug.Transform.Scale.ScaleParams.Dimensions{width: {:percent, 50.5}, height: :auto}}}
{:ok, %ImagePlug.Transform.Scale.ScaleParams{type: :dimensions, width: {:percent, 50.5}, height: :auto}}

iex> ImagePlug.ParamParser.Twicpics.Transform.ScaleParser.parse("50.5")
{:ok, %ImagePlug.Transform.Scale.ScaleParams{method: %ImagePlug.Transform.Scale.ScaleParams.Dimensions{width: {:pixels, 50.5}, height: :auto}}}
{:ok, %ImagePlug.Transform.Scale.ScaleParams{type: :dimensions, width: {:pixels, 50.5}, height: :auto}}

iex> ImagePlug.ParamParser.Twicpics.Transform.ScaleParser.parse("50p")
{:ok, %ImagePlug.Transform.Scale.ScaleParams{method: %ImagePlug.Transform.Scale.ScaleParams.Dimensions{width: {:percent, 50}, height: :auto}}}
{:ok, %ImagePlug.Transform.Scale.ScaleParams{type: :dimensions, width: {:percent, 50}, height: :auto}}

iex> ImagePlug.ParamParser.Twicpics.Transform.ScaleParser.parse("(25*10)x(1/2)s")
{:ok, %ImagePlug.Transform.Scale.ScaleParams{method: %ImagePlug.Transform.Scale.ScaleParams.Dimensions{width: {:pixels, 250}, height: {:scale, 0.5}}}}
{:ok, %ImagePlug.Transform.Scale.ScaleParams{type: :dimensions, width: {:pixels, 250}, height: {:scale, 0.5}}}

iex> ImagePlug.ParamParser.Twicpics.Transform.ScaleParser.parse("16:9")
{:ok, %ImagePlug.Transform.Scale.ScaleParams{method: %ImagePlug.Transform.Scale.ScaleParams.AspectRatio{aspect_ratio: {:ratio, 16, 9}}}}
{:ok, %ImagePlug.Transform.Scale.ScaleParams{type: :ratio, ratio: {16, 9}}}
"""
def parse(input, pos_offset \\ 0) do
if String.contains?(input, ":"),
Expand All @@ -44,7 +44,7 @@ defmodule ImagePlug.ParamParser.Twicpics.Transform.ScaleParser do
defp parse_ratio(input, pos_offset) do
case RatioParser.parse(input, pos_offset) do
{:ok, %{width: width, height: height}} ->
{:ok, %ScaleParams{method: %AspectRatio{aspect_ratio: {:ratio, width, height}}}}
{:ok, %ScaleParams{type: :ratio, ratio: {width, height}}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
Expand All @@ -54,7 +54,7 @@ defmodule ImagePlug.ParamParser.Twicpics.Transform.ScaleParser do
defp parse_size(input, pos_offset) do
case SizeParser.parse(input, pos_offset) do
{:ok, %{width: width, height: height}} ->
{:ok, %ScaleParams{method: %Dimensions{width: width, height: height}}}
{:ok, %ScaleParams{type: :dimensions, width: width, height: height}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
Expand Down
19 changes: 0 additions & 19 deletions lib/image_plug/transform.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,4 @@ defmodule ImagePlug.Transform do
alias ImagePlug.ArithmeticParser

@callback execute(TransformState.t(), String.t()) :: TransformState.t()

def image_dim(%TransformState{image: image}, :width), do: Image.width(image)
def image_dim(%TransformState{image: image}, :height), do: Image.height(image)

@spec to_pixels(TransformState.t(), :width | :height, ImagePlug.imgp_length()) ::
{:ok, integer()} | {:error, atom()}
def to_pixels(state, dimension, length)

def to_pixels(_state, _dimension, {:pixels, num}) do
{:ok, round(num)}
end

def to_pixels(state, dimension, {:scale, numerator, denominator}) do
{:ok, round(image_dim(state, dimension) * numerator / denominator)}
end

def to_pixels(state, dimension, {:percent, num}) do
{:ok, round(num / 100 * image_dim(state, dimension))}
end
end
Loading
Loading