Current section
Files
Jump to
Current section
Files
lib/elxvips.ex
defmodule Elxvips.ResizeOptions do
defstruct [
width: :auto,
height: :auto,
resize_type: :auto, # :type conflicts with rust keyword "type"
]
end
defmodule Elxvips.SaveOptions do
defstruct [
quality: 90,
format: :auto,
strip: true,
path: "",
compression: 6,
background: [],
]
end
defmodule Elxvips.ImageFile do
defstruct [
path: :nil,
resize: %Elxvips.ResizeOptions{
:width => :auto,
:height => :auto,
:resize_type => :auto,
},
save: %Elxvips.SaveOptions{}
]
end
defmodule Elxvips.ImageBytes do
defstruct [
bytes: nil,
resize: %Elxvips.ResizeOptions{
:width => :auto,
:height => :auto,
:resize_type => :auto,
},
save: %Elxvips.SaveOptions{},
]
end
defmodule Elxvips do
@moduledoc """
Documentation for `Elxvips`.
"""
use Rustler, otp_app: :elxvips, crate: "lvips"
alias Elxvips.ImageFile, as: ImageFile
alias Elxvips.ImageBytes, as: ImageBytes
alias Elxvips.SaveOptions, as: SaveOptions
# NIFs
defp vips_set_concurrency(_a), do: :erlang.nif_error(:nif_not_loaded)
defp vips_get_image_sizes(_a), do: :erlang.nif_error(:nif_not_loaded) # returns {:ok, { width, height } }
defp vips_get_image_bytes_sizes(_a), do: :erlang.nif_error(:nif_not_loaded) # same but works with bytes
defp vips_process_file_to_file(_a), do: :erlang.nif_error(:nif_not_loaded) # applies processing from %ImageFile{}
defp vips_process_file_to_bytes(_a), do: :erlang.nif_error(:nif_not_loaded) # applies processing from %ImageBytes{} created from image path
defp vips_process_bytes_to_bytes(_a), do: :erlang.nif_error(:nif_not_loaded) # applies processing from %ImageBytes{} created from image bytes
defp vips_process_bytes_to_file(_a), do: :erlang.nif_error(:nif_not_loaded) # applies processing from %ImageBytes{} created from image bytes
defp vips_get_image_file_format(_a), do: :erlang.nif_error(:nif_not_loaded) # applies processing from %ImageBytes{} created from image bytes
defp vips_get_image_bytes_format(_a), do: :erlang.nif_error(:nif_not_loaded) # applies processing from %ImageBytes{} created from image bytes
# creating new image from an existing image path
defp process_to_file( image_file = %ImageFile{}, path ) when is_binary( path ) do
image_file = %ImageFile{ image_file |
:save => Kernel.struct( image_file.save, [ path: path ] )
}
with :ok <- vips_process_file_to_file( image_file ) do
{ :ok, %ImageFile{
:path => path,
} }
else
err -> err
end
end
defp process_to_file( image_bytes = %ImageBytes{ :bytes => bytes }, path ) when is_binary( path ) and is_list( bytes ) do
image_bytes = %ImageBytes{ image_bytes |
:save => Kernel.struct( image_bytes.save, [ path: path ] )
}
with :ok <- vips_process_bytes_to_file( image_bytes ) do
{ :ok, %ImageFile{
:path => path,
} }
else
err -> err
end
end
# In case the we have a image path as image_bytes
defp process_to_bytes( image_file = %ImageFile{ :path => path } ) when is_binary( path ) do
with { :ok, bytes } <- vips_process_file_to_bytes( image_file ) do
{ :ok, %ImageBytes{
:bytes => bytes,
} }
else
err -> err
end
end
# In case we have raw bytes
defp process_to_bytes( image_bytes = %ImageBytes{ :bytes => bytes } ) when is_list( bytes ) do
with { :ok, bytes } <- vips_process_bytes_to_bytes( image_bytes ) do
{ :ok, %ImageBytes{
:bytes => bytes,
} }
else
err -> err
end
end
defp format_merge( :background, a_val, b_val ) do
case b_val do
[] -> a_val
_ -> b_val
end
end
defp format_merge( _, _, b_val ) do
b_val
end
defp format( image_file = %ImageFile{}, format , opts ) do
with opts when is_list( opts ) <- check_opts( opts ) do
{ :ok, %ImageFile{ image_file |
:save => Map.merge( image_file.save, Map.new( opts ++ [ format: format, path: "" ] ), &format_merge/3 ),
} }
end
end
defp format( image_bytes = %ImageBytes{}, format , opts ) do
with opts when is_list( opts ) <- check_opts( opts ) do
{ :ok, %ImageBytes{ image_bytes |
:save => Map.merge( image_bytes.save, Map.new( opts ++ [ format: format, path: "" ] ), &format_merge/3 ),
} }
end
end
defp format( { :ok, image }, format, opts ), do: format( image, format, opts )
def check_opts( opts \\ [] ) do
with opts when is_list( opts ) <- check_background( opts ) do
opts
end
end
defp check_background( opts ) do
with background_list when is_list( background_list ) <- background_opts( Keyword.get( opts, :background, [] ) ) do
Keyword.put( opts, :background, background_list )
end
end
# vips expects a vector of f64, this should convert integers to float
defp background_opts( [ c1 ] ), do: [ c1 / 1 ]
defp background_opts( [ c1, c2, c3 ] ), do: [ c1 / 1, c2 / 1, c3 / 1 ]
defp background_opts( [] ), do: []
defp background_opts( _ ), do: { :error, "background can be a vector of 1 or 3 numbers" }
@doc """
Sets the background of the image in case there is a transparent background.
Accepts a empty list, or a list of length 2 or 3.
## Examples
iex> import Elxvips
iex>
iex> from_file( "test/input.png" )
iex |> jpg()
iex> |> background( [ 255, 0, 0 ] ) # red background
iex |> to_file( "test/output.jpg" )
{:ok, %ImageBytes{}}
"""
# def background( image = %ImageFile{}, c1 ), do: background( image, c1 )
# def background( image = %ImageFile{}, c1, c2, c3 ), do: background( image, c1 )
# def background( image = %ImageFile{}, c1 ), do: background( image, c1 )
# def background( image = %ImageFile{}, c1, c2, c3 ), do: background( image, c1 )
def background( image, opts \\ [] )
def background( image_file = %ImageFile{}, colors ) do
with background_list when is_list( background_list ) <- background_opts( colors ) do
{ :ok, %ImageFile{ image_file |
:save => %SaveOptions { image_file.save |
:background => background_list,
},
} }
end
end
def background( image_bytes = %ImageBytes{}, colors ) do
with background_list when is_list( background_list ) <- background_opts( colors ) do
{ :ok, %ImageBytes{ image_bytes |
:save => %SaveOptions { image_bytes.save |
:background => background_list,
},
} }
end
end
def background( { :ok, image_file = %ImageFile{} }, colors ), do: background( image_file, colors )
def background( { :ok, image_bytes = %ImageBytes{} }, colors ), do: background( image_bytes, colors )
@doc """
Applies resize options to an %ImageFile{} or %ImageBytes{}, accepts :width, :height and :type (not implemented yet).
If no width or height is specified dimensions are calculated from the input image.
Empty resize( no :width and no :height) will produce an image with the dimensions as the original one.
## Examples
iex> import Elxvips
iex>
iex> from_file( "test/input.png" )
iex> |> resize( width: 300 )
iex |> to_bytes()
{:ok, %ImageBytes{}}
"""
def resize( image_file, opts \\ [] )
def resize( image_file = %ImageFile{}, opts ) do
{ :ok, %ImageFile{ image_file |
:resize => Kernel.struct( image_file.resize, opts )
} }
end
def resize( { :ok, image_file = %ImageFile{} }, opts ), do: resize( image_file, opts )
def resize( image_bytes = %ImageBytes{}, opts ) do
{ :ok, %ImageBytes{ image_bytes |
:resize => Kernel.struct( image_bytes.resize, opts )
} }
end
def resize( { :ok, image_bytes = %ImageBytes{} }, opts ), do: resize( image_bytes, opts )
@save_opts_default [ quality: 100, strip: true, compression: 6, background: [] ]
@jpg_default_opts Keyword.merge( @save_opts_default, [ quality: 90 ] )
@doc """
Will save the ImageFile in jpeg format to a specified path. Accepts quality and strip options.
By default quality is set to 90 and strip to true.
## Examples
iex> import Elxvips
iex>
iex> from_file( "/path/input.png" )
iex> |> jpg( strip: true, quality: 72 )
iex |> to_file( "/path/output.jpg" )
{ :ok, %ImageFile{} }
"""
def jpg( image, opts \\ [] )
def jpg( image_file = %ImageFile{}, opts ), do: format( image_file, :jpg, Keyword.merge( @jpg_default_opts, opts ) )
def jpg( image_file = %ImageBytes{}, opts ), do: format( image_file, :jpg, Keyword.merge( @jpg_default_opts, opts ) )
def jpg( { :ok, image }, opts ), do: jpg( image, opts )
@png_default_opts Keyword.merge( @save_opts_default, [ quality: 100 ] )
@doc """
Will save the ImageFile in png format to a specified path. Accepts quality, compression(0-9) and strip options.
By default quality is set to 100, compression to 6, strip to true. Decreasing compression will speed up image saving.
## Examples
iex> import Elxvips
iex>
iex> from_file( "/path/input.jpg" )
iex> |> png( strip: true, quality: 72 )
iex |> to_file( "/path/output.png" )
{ :ok, %ImageFile{} }
"""
def png( image, opts \\ [] )
def png( image_file = %ImageFile{}, opts ), do: format( image_file, :png, Keyword.merge( @png_default_opts, opts ) )
def png( image_file = %ImageBytes{}, opts ), do: format( image_file, :png, Keyword.merge( @png_default_opts, opts ) )
def png( { :ok, image }, opts ), do: png( image, opts )
@webp_default_opts Keyword.merge( @save_opts_default, [ quality: 100 ] )
@doc """
Will save the ImageFile in webp format to a specified path. Accepts quality and strip options.
By default quality is set to 100, and strip to true.
## Examples
iex> import Elxvips
iex>
iex> from_file( "/path/input.jpg" )
iex> |> webp( strip: true, quality: 72 )
iex |> to_file( "/path/output.webp" )
{ :ok, %ImageFile{} }
"""
def webp( image, opts \\ [] )
def webp( image_file = %ImageFile{}, opts ), do: format( image_file, :webp, Keyword.merge( @webp_default_opts, opts ) )
def webp( image_file = %ImageBytes{}, opts ), do: format( image_file, :webp, Keyword.merge( @webp_default_opts, opts ) )
def webp( { :ok, image }, opts ), do: webp( image, opts )
@doc """
Will create an %ImageFile{} struct from path. This struct will be used for further processing.
## Examples
iex> import Elxvips
iex>
iex> from_file( "/path/input.png" )
%ImageFile{}
"""
def from_file( path ) when is_binary( path ) do
{ :ok, %ImageFile{
:path => path,
} }
end
@doc """
Will create an %ImageByte{} struct from bitstring or byte list. This struct will be used for further processing.
## Examples
iex> import Elxvips
iex>
iex> file = File.open!( "/path/input.png" )
iex> bytes = IO.binread( file, :all )
iex> from_bytes( bytes )
%ImageBytes{}
"""
def from_bytes( bytes ) when is_list( bytes ) do
{ :ok, %ImageBytes{
:bytes => bytes,
} }
end
def from_bytes( bytes ) when is_bitstring( bytes ) do
{ :ok, %ImageBytes{
:bytes => :erlang.binary_to_list( bytes ),
} }
end
@doc """
Will create a new %ImageBytes{} struct containing all the changes.
## Examples
iex> import Elxvips
iex>
iex> from_file( "test/input.png" )
iex> |> png()
iex> |> to_bytes()
{:ok, %ImageBytes{}}
"""
def to_bytes( image = %ImageFile{} ), do: process_to_bytes( image )
def to_bytes( image = %ImageBytes{} ), do: process_to_bytes( image )
def to_bytes( { :ok, image } ), do: to_bytes( image )
@doc """
Will save the image to a path on disk and return a new %ImageFile{} from the new path.
## Examples
iex> import Elxvips
iex>
iex> from_file( "test/input.png" )
iex> |> png()
iex> |> to_file( "test/outping.png" )
{:ok, %ImageBytes{}}
"""
def to_file( image = %ImageFile{}, path ) when is_binary( path ), do: process_to_file( image, path )
def to_file( image = %ImageBytes{}, path ) when is_binary( path ), do: process_to_file( image, path )
def to_file( { :ok, image }, path ), do: to_file( image, path )
def set_concurrency( concurrency ) when is_integer( concurrency ) do
vips_set_concurrency( concurrency )
end
@doc """
Returns dimensions of the specified image, works with a image path or bytes.
## Examples
iex> import Elxvips
iex>
iex> from_file( "test/input.png" )
iex> |> get_image_sizes()
{:ok, [640, 486]}
"""
def get_image_sizes( path ) when is_binary( path ), do: vips_get_image_sizes( path )
def get_image_sizes( bytes ) when is_list( bytes ), do: vips_get_image_bytes_sizes( bytes )
def get_image_sizes( %ImageFile{ :path => path } ), do: get_image_sizes( path )
def get_image_sizes( {:ok, %ImageFile{ :path => path } } ), do: get_image_sizes( path )
def get_image_sizes( %ImageBytes{ :bytes => bytes } ) when is_list( bytes ), do: get_image_sizes( bytes )
def get_image_sizes( { :ok, image_bytes = %ImageBytes{} } ), do: get_image_sizes( image_bytes )
@doc """
Returns format of the specified image, works with a image path or bytes.
## Examples
iex> import Elxvips
iex>
iex> from_file( "test/input.png" )
iex> |> get_image_format()
{:ok, :png}
"""
def get_image_format( path ) when is_binary( path ), do: vips_get_image_file_format( path )
def get_image_format( bytes ) when is_list( bytes ), do: vips_get_image_bytes_format( bytes )
def get_image_format( %ImageFile{ :path => path } ), do: get_image_format( path )
def get_image_format( {:ok, %ImageFile{ :path => path } } ), do: get_image_format( path )
def get_image_format( %ImageBytes{ :bytes => bytes } ) when is_list( bytes ), do: get_image_format( bytes )
def get_image_format( { :ok, image_bytes = %ImageBytes{} } ), do: get_image_format( image_bytes )
end