Packages
image
0.59.1
0.72.0
0.71.0
0.70.0
0.69.0
0.68.0
0.67.0
0.67.0-dev
retired
0.66.0
0.65.0
0.64.0
0.63.0
0.62.1
0.62.0
0.61.1
0.61.0
0.60.0
0.59.3
0.59.2
0.59.1
0.59.0
0.58.0
0.57.0
0.56.1
0.56.0
0.55.2
0.55.1
retired
0.55.0
0.54.4
0.54.3
0.54.2
0.54.1
0.54.0
0.53.0
0.52.3
0.52.2
0.52.1
0.52.0
retired
0.51.0
0.50.0
0.49.0
0.48.1
0.48.0
0.47.0
0.46.0
0.45.0
0.44.0
0.43.2
0.43.1
0.43.0
0.42.0
0.41.0
0.40.0
0.39.3
0.39.2
0.39.1
0.39.0
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.0
0.36.2
0.36.1
0.36.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.1
0.31.0
0.30.0
0.29.0
0.28.2
0.28.1
0.28.0
0.27.0
0.26.0
0.25.1
0.25.0
0.24.1
0.24.0
0.23.2
0.23.1
0.23.0
0.22.1
0.22.0
0.21.0
0.20.0
retired
0.19.0
0.18.1
0.18.0
0.17.0
0.16.0
0.15.0
0.14.4
0.14.2
0.14.1
0.14.0
retired
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.10.0-rc.0
retired
0.9.0
retired
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
An approachable image processing library primarily based upon Vix and libvips that is NIF-based, fast, multi-threaded, pipelined and has a low memory footprint.
Current section
Files
Jump to
Current section
Files
lib/image/options/crop.ex
defmodule Image.Options.Crop do
@moduledoc """
Options and option validation for `Image.crop/5`.
"""
import Image,
only: [
is_size: 1,
is_percent: 1,
is_positive_percent: 1,
is_box: 4
]
@typedoc """
Options applicable to cropping an
image.
Currently there are no options.
"""
@type crop_options :: []
@typedoc """
Indicates how to determine where to crop
an image to fill a target area.
* `:none` means the image will be reduced
to fit the required bounding box and
no cropping will occur.
* `:center` means crop from the center of
the image. The central part of the image
will be returned, cropped to fill the
bounding box.
* `:entropy` uses an entropy measure.
* `:attention` means crop the image by looking
for features likely to draw human attention.
* `:low` means position the crop towards the
low coordinate. This means the bottom part
of the image remains after the crop.
* `:high` means position the crop towards the
high coordinate. This means the top part
of the image remains after the crop.
"""
@type crop_focus :: :none | :center | :entropy | :attention | :low | :high
# The meaning of :low and :high are deliberately
# Although the verb is crop (to remove) most
# would expect this verb to describe what remains
# after cropping. Indeed that is already the behaviour for
# :center and :attention.
# Note too that we use US English spelling as apposed
# to the libvips British English spelling. The
# assumption being that most developers expect
# US English.
@crop_map %{
none: :VIPS_INTERESTING_NONE,
center: :VIPS_INTERESTING_CENTRE,
entropy: :VIPS_INTERESTING_ENTROPY,
attention: :VIPS_INTERESTING_ATTENTION,
low: :VIPS_INTERESTING_HIGH,
high: :VIPS_INTERESTING_LOW
}
@crop Map.keys(@crop_map)
@inverse_crop Map.values(@crop_map)
@doc """
Validates options to `Iamge.crop/5`.
"""
def validate_options(options) do
case Enum.reduce_while(options, options, &validate_option(&1, &2)) do
{:error, value} ->
{:error, value}
options ->
{:ok, options}
end
end
defp validate_option(_other, options) do
{:cont, options}
end
@doc false
def validate_crop(crop, options) when crop in @crop do
crop = Map.fetch!(@crop_map, crop)
{:cont, Keyword.put(options, :crop, crop)}
end
def validate_crop(crop, options) when crop in @inverse_crop do
{:cont, Keyword.put(options, :crop, crop)}
end
def validate_crop(crop, _options) do
{:halt, {:error, invalid_crop(crop)}}
end
@doc false
def normalize_box({w, h}, _left, _top, width, height) when w < width or h < height do
{:error, "Cannot normalize a box that is larger than the image"}
end
def normalize_box({w, _h} = dims, left, top, width, height) when is_percent(left) do
normalize_box(dims, round(left * w), top, width, height)
end
def normalize_box({_w, h} = dims, left, top, width, height) when is_percent(top) do
normalize_box(dims, left, round(top * h), width, height)
end
def normalize_box({w, _h} = dims, left, top, width, height) when is_positive_percent(width) do
normalize_box(dims, left, top, round(width * w), height)
end
def normalize_box({_w, h} = dims, left, top, width, height) when is_positive_percent(height) do
normalize_box(dims, left, top, width, round(height * h))
end
def normalize_box({w, h}, :center, top, width, height) when is_size(width) and is_size(height) do
left = round((w - width) / 2)
normalize_box({w, h}, left, top, width, height)
end
def normalize_box({w, h}, :left, top, width, height) when is_size(width) and is_size(height) do
normalize_box({w, h}, 0, top, width, height)
end
def normalize_box({w, h}, :right, top, width, height) when is_size(width) and is_size(height) do
normalize_box({w, h}, w - width, top, width, height)
end
def normalize_box({w, h}, left, :middle, width, height) when is_size(width) and is_size(height) do
top = round((h - height) / 2)
normalize_box({w, h}, left, top, width, height)
end
def normalize_box({w, h}, left, :top, width, height) when is_size(width) and is_size(height) do
normalize_box({w, h}, left, 0, width, height)
end
def normalize_box({w, h}, left, :bottom, width, height) when is_size(width) and is_size(height) do
normalize_box({w, h}, left, h - height, width, height)
end
def normalize_box({w, _h} = dims, left, top, width, height)
when is_integer(left) and is_integer(top) and left < 0 and top >= 0 do
left = w + left - width
normalize_box(dims, left, top, width, height)
end
def normalize_box({_w, h} = dims, left, top, width, height)
when is_integer(left) and is_integer(top) and left >= 0 and top < 0 do
top = h + top - height
normalize_box(dims, left, top, width, height)
end
def normalize_box({w, h} = dims, left, top, width, height)
when is_integer(left) and is_integer(top) and left < 0 and top < 0 do
left = w + left - width
top = h + top - height
normalize_box(dims, left, top, width, height)
end
def normalize_box(_dims, left, top, width, height) when is_box(left, top, width, height) do
{left, top, width, height}
end
def normalize_box(_dims, _left, _top, width, _height)
when not is_integer(width) and not is_positive_percent(width) do
{:error, size_error("width", width)}
end
def normalize_box(_dims, _left, _top, _width, height)
when not is_integer(height) and not is_positive_percent(height) do
{:error, size_error("height", height)}
end
def normalize_box(_dims, left, _top, _width, _height)
when not is_integer(left) and not is_positive_percent(left) do
{:error, location_error("left", left)}
end
def normalize_box(_dims, _left, top, _width, _height)
when not is_integer(top) and not is_positive_percent(top) do
{:error, location_error("top", top)}
end
def normalize_box(dims, left, top, width, height) do
{:error, "Could not normalize box with #{inspect({dims, left, top, width, height})}"}
end
@doc false
def normalize_dims({w, _h} = dims, width, height) when is_positive_percent(width) do
normalize_dims(dims, round(width * w), height)
end
def normalize_dims({_w, h} = dims, width, height) when is_positive_percent(height) do
normalize_dims(dims, width, round(height * h))
end
def normalize_dims(_dims, width, height) when is_size(width) and is_size(height) do
{width, height}
end
def normalize_dims(_dims, width, _height)
when not is_integer(width) and not is_positive_percent(width) do
{:error, size_error("width", width)}
end
def normalize_dims(_dims, _width, height)
when not is_integer(height) and not is_positive_percent(height) do
{:error, size_error("height", height)}
end
def normalize_dims(_dims, width, height) do
{:error, "Could not normalize dimensions within #{width}, #{height}"}
end
defp size_error(dim, size) when is_number(size) do
"#{dim} must be a percentage expressed as a float greater than 0.0 and " <>
"less than or equal to 1.0. Found #{inspect(size)}"
end
defp location_error(dim, size) when is_number(size) do
"#{dim} must be a percentage expressed as a float beteen -1.0 and 1.0. " <>
"Found #{inspect(size)}"
end
defp invalid_crop(crop) do
"Invalid crop option #{inspect(crop)}. Valid values are #{inspect(@crop)}"
end
end