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
1 change: 1 addition & 0 deletions lib/image_plug/param_parser/twicpics.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule ImagePlug.ParamParser.Twicpics do
"contain" => {ImagePlug.Transform.Contain, Twicpics.Transform.ContainParser},
"contain-min" => {ImagePlug.Transform.Contain, Twicpics.Transform.ContainMinParser},
"contain-max" => {ImagePlug.Transform.Contain, Twicpics.Transform.ContainMaxParser},
"inside" => {ImagePlug.Transform.Contain, Twicpics.Transform.InsideParser},
"cover" => {ImagePlug.Transform.Cover, Twicpics.Transform.CoverParser},
"cover-min" => {ImagePlug.Transform.Cover, Twicpics.Transform.CoverMinParser},
"cover-max" => {ImagePlug.Transform.Cover, Twicpics.Transform.CoverMaxParser},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ defmodule ImagePlug.ParamParser.Twicpics.Transform.ContainMaxParser do
## Examples

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

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}}
{:ok,
%ContainParams{
type: :dimensions,
width: width,
height: height,
constraint: :max,
letterbox: false
}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ defmodule ImagePlug.ParamParser.Twicpics.Transform.ContainMinParser do
## Examples

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

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}}
{:ok,
%ContainParams{
type: :dimensions,
width: width,
height: height,
constraint: :min,
letterbox: false
}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
Expand Down
11 changes: 9 additions & 2 deletions lib/image_plug/param_parser/twicpics/transform/contain_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,20 @@ defmodule ImagePlug.ParamParser.Twicpics.Transform.ContainParser do
## Examples

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

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: :none}}
{:ok,
%ContainParams{
type: :dimensions,
width: width,
height: height,
constraint: :none,
letterbox: false
}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule ImagePlug.ParamParser.Twicpics.Transform.CoverMaxParser do
## Examples

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

def parse(input, pos_offset \\ 0) do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule ImagePlug.ParamParser.Twicpics.Transform.CoverMinParser do
## Examples

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

def parse(input, pos_offset \\ 0) do
Expand Down
55 changes: 55 additions & 0 deletions lib/image_plug/param_parser/twicpics/transform/inside_parser.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
defmodule ImagePlug.ParamParser.Twicpics.Transform.InsideParser 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:
* `inside=<size>`
* `inside=<ratio>`

## Examples

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

iex> ImagePlug.ParamParser.Twicpics.Transform.InsideParser.parse("1.5:2")
{:ok, %ImagePlug.Transform.Contain.ContainParams{type: :ratio, ratio: {1.5, 2}, letterbox: true}}
"""

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, %ContainParams{type: :ratio, ratio: {width, height}, letterbox: true}}

{: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,
%ContainParams{
type: :dimensions,
width: width,
height: height,
constraint: :none,
letterbox: true
}}

{:error, _reason} = error ->
Utils.update_error_input(error, input)
end
end
end
82 changes: 71 additions & 11 deletions lib/image_plug/transform/contain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,78 @@ defmodule ImagePlug.Transform.Contain do
alias ImagePlug.TransformState

defmodule ContainParams do
defstruct [:width, :height, :constraint]
defstruct [:type, :ratio, :width, :height, :constraint, :letterbox]

@type t ::
%__MODULE__{
width: ImagePlug.imgp_length(),
height: ImagePlug.imgp_length() | :auto,
constraint: :regular | :min | :max
type: :ratio,
ratio: ImagePlug.imgp_ratio(),
letterbox: boolean()
}
| %__MODULE__{
type: :dimensions,
width: ImagePlug.imgp_length(),
height: ImagePlug.imgp_length() | :auto,
constraint: :regular | :min | :max,
letterbox: boolean()
}
| %__MODULE__{
type: :dimensions,
width: ImagePlug.imgp_length() | :auto,
height: ImagePlug.imgp_length(),
constraint: :regular | :min | :max
constraint: :regular | :min | :max,
letterbox: boolean()
}
end

@impl ImagePlug.Transform
def execute(%TransformState{} = state, %ContainParams{
type: :ratio,
ratio: {ratio_width, ratio_height},
# Note: Not letterboxing doesn't make sense with this implementation,
# as the transformation would just return the same image
letterbox: letterbox
}) do
# compute target width and height based on the ratio
image_width = image_width(state)
image_height = image_height(state)

target_ratio = ratio_width / ratio_height
original_ratio = image_width / image_height

{target_width, target_height} =
if original_ratio > target_ratio do
# wider image: scale height to match ratio
{image_width, round(image_width / target_ratio)}
else
# taller image: scale width to match ratio
{round(image_height * target_ratio), image_height}
end

execute(state, %ContainParams{
type: :dimensions,
width: target_width,
height: target_height,
constraint: :none,
letterbox: letterbox
})
end

@impl ImagePlug.Transform
def execute(%TransformState{} = state, %ContainParams{
type: :dimensions,
width: width,
height: height,
constraint: constraint
constraint: constraint,
letterbox: letterbox
}) do
{target_width, target_height} = resolve_auto_size(state, width, height)
{resize_width, resize_height} = fit_inside(state, target_width, target_height)

case maybe_scale(state, resize_width, resize_height, constraint) do
{:ok, scaled_image} -> state |> set_image(scaled_image) |> reset_focus()
with {:ok, state} <- maybe_scale(state, resize_width, resize_height, constraint),
{:ok, state} <- maybe_add_letterbox(state, letterbox, target_width, target_height) do
state |> reset_focus()
else
{:error, error} -> add_error(state, {__MODULE__, error})
end
end
Expand All @@ -52,13 +98,13 @@ defmodule ImagePlug.Transform.Contain do
def maybe_scale(%TransformState{} = state, width, height, :min) do
if width > image_width(state) or height > image_height(state),
do: do_scale(state, width, height),
else: {:ok, state.image}
else: {:ok, state}
end

def maybe_scale(%TransformState{} = state, width, height, :max) do
if width < image_width(state) or height < image_height(state),
do: do_scale(state, width, height),
else: {:ok, state.image}
else: {:ok, state}
end

def maybe_scale(%TransformState{} = state, width, height, _constraint),
Expand All @@ -67,6 +113,20 @@ defmodule ImagePlug.Transform.Contain do
def do_scale(%TransformState{} = state, width, height) do
width_scale = width / image_width(state)
height_scale = height / image_height(state)
Image.resize(state.image, width_scale, vertical_scale: height_scale)

case Image.resize(state.image, width_scale, vertical_scale: height_scale) do
{:ok, resized_image} -> {:ok, set_image(state, resized_image)}
{:error, _reason} = error -> error
end
end

defp maybe_add_letterbox(state, letterbox?, width, height)
defp maybe_add_letterbox(%TransformState{} = state, false, width, height), do: {:ok, state}

defp maybe_add_letterbox(%TransformState{} = state, true, width, height) do
case Image.embed(state.image, width, height, background_color: :white) do
{:ok, letterboxed_image} -> {:ok, set_image(state, letterboxed_image)}
{:error, _reason} = error -> error
end
end
end
4 changes: 3 additions & 1 deletion test/param_parser/twicpics_parser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,11 @@ defmodule ImagePlug.ParamParser.TwicpicsParserTest do

assert {:ok,
%Contain.ContainParams{
type: :dimensions,
width: to_result(width),
height: to_result(height),
constraint: :none
constraint: :none,
letterbox: false
}} ==
parsed
end
Expand Down
Loading