Current section

Files

Jump to
elxvips lib elxvips.ex
Raw

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 => 0,
:height => 0,
:resize_type => :auto,
},
save: %Elxvips.SaveOptions{},
pdf: false,
page: 0,
n: 1,
]
end
defmodule Elxvips.ImageBytes do
defstruct [
bytes: nil,
resize: %Elxvips.ResizeOptions{
:width => 0,
:height => 0,
:resize_type => :auto,
},
save: %Elxvips.SaveOptions{},
pdf: false,
page: 0,
n: 1,
]
end
defmodule Elxvips do
@moduledoc """
Documentation for `Elxvips`.
"""
alias Elxvips.ImageFile, as: ImageFile
alias Elxvips.ImageBytes, as: ImageBytes
alias Elxvips.SaveOptions, as: SaveOptions
# 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 <- Elxvips.Native.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 <- Elxvips.Native.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 } <- Elxvips.Native.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 } <- Elxvips.Native.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 )
@avif_default_opts Keyword.merge( @save_opts_default, [ quality: 100 ] )
@doc """
Will save the ImageFile in avif(AV1) format to a specified path. Accepts quality and strip options.
By default quality is set to 100
## Examples
iex> import Elxvips
iex>
iex> from_file( "/path/input.jpg" )
iex> |> avif( quality: 72 )
iex |> to_file( "/path/output.avif" )
{ :ok, %ImageFile{} }
"""
def avif( image, opts \\ [] )
def avif( image_file = %ImageFile{}, opts ), do: format( image_file, :avif, Keyword.merge( @avif_default_opts, opts ) )
def avif( image_file = %ImageBytes{}, opts ), do: format( image_file, :avif, Keyword.merge( @avif_default_opts, opts ) )
def avif( { :ok, image }, opts ), do: avif( image, opts )
@doc """
Will save the ImageFile in SVG format to a specified path. Accepts no options.
Highly discouraged to use this function, since it will embed the image in the svg file.
In case an svg is used as input, it will just copy the file.
## Examples
iex> import Elxvips
iex>
iex> from_file( "/path/input.jpg" )
iex> |> svg()
iex |> to_file( "/path/output.svg" )
{ :ok, %ImageFile{} }
"""
def svg( image, opts \\ [] )
def svg( image_file = %ImageFile{}, opts ), do: format( image_file, :svg, opts )
def svg( image_file = %ImageBytes{}, opts ), do: format( image_file, :svg, opts )
def svg( { :ok, image }, opts ), do: svg( 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 %ImageFile{} struct from a pdf path. This struct will be used for further processing.
Accepts the following options:
* `:page` - page number to extract from pdf, default is 0
* `:n` - number of pages to extract from pdf, default is 1
## Examples
iex> import Elxvips
iex>
iex> from_pdf( "/path/input.pdf", page: 0, n: 2 )
%ImageFile{}
"""
def from_pdf( path, opts \\ [ page: 0 ] ) when is_binary( path ) do
page = Keyword.get( opts, :page, 0 )
n = Keyword.get( opts, :n, 1 )
{ :ok, %ImageFile{
:path => path,
:pdf => true,
:page => page,
:n => n,
} }
|> jpg()
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 an %ImageByte{} struct from pdf bitstring or byte list. This struct will be used for further processing.
Accepts the following options:
* `:page` - page number to extract from pdf, default is 0
* `:n` - number of pages to extract from pdf, default is 1
## Examples
iex> import Elxvips
iex>
iex> file = File.open!( "/path/input.pdf" )
iex> bytes = IO.binread( file, :all )
iex> from_pdf_bytes( bytes, page: 0, n: 2 )
%ImageBytes{}
"""
def from_pdf_bytes( bytes ), do: from_pdf_bytes( bytes, [] )
def from_pdf_bytes( bytes, opts ) when is_list( bytes ) do
page = Keyword.get( opts, :page, 0 )
n = Keyword.get( opts, :n, 1 )
{ :ok, %ImageBytes{
:bytes => bytes,
:pdf => true,
:page => page,
:n => n
} }
|> jpg()
end
def from_pdf_bytes( bytes, opts ) when is_bitstring( bytes ) do
page = Keyword.get( opts, :page, 0 )
n = Keyword.get( opts, :n, 1 )
{ :ok, %ImageBytes{
:bytes => :erlang.binary_to_list( bytes ),
:pdf => true,
:page => page,
:n => n
} }
|> jpg()
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
Elxvips.Native.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: Elxvips.Native.vips_get_image_sizes( path )
def get_image_sizes( bytes ) when is_list( bytes ), do: Elxvips.Native.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: Elxvips.Native.vips_get_image_file_format( path )
def get_image_format( bytes ) when is_list( bytes ), do: Elxvips.Native.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