View Source Introduction

Mix.install([
  {:kino, "~> 0.7.0"},
  {:vix, "~> 0.5"}
])
# print vips version
IO.puts("Version: " <> Vix.Vips.version())

Vips Image

All image IO operations, such as reading and writing files, are available in the Vix.Vips.Image module. The Image module also contains functions to get image attributes. Most Vips operations take a %Vix.Vips.Image{} struct.

alias Vix.Vips.Image

Reading an image from a file. Note that an image is not actually loaded into memory at this point. img is an %Image{} struct.

{:ok, %Image{} = img} = Image.new_from_file("~/Downloads/kitty.png")

You can also load an image from a binary. This allows us to work with images without touching the file system. It tries to guess the image format from the binary and uses the correct loader.

bin = File.read!("~/Downloads/kitty.png")
{:ok, %Image{} = img} = Image.new_from_buffer(bin)

If you know the image format beforehand, you can use the appropriate function from Vix.Vips.Operation. For example, you would use Vix.Vips.Operation.pngload_buffer/2 to load a PNG.

bin = File.read!("~/Downloads/kitty.png")
{:ok, {img, _flags}} = Vix.Vips.Operation.pngload_buffer(bin)

Writing Image to a file. Image type selected based on the image path extension. See documentation for more options

:ok = Image.write_to_file(img, "kitty.jpg[Q=90]")
# let's print image dimensions
IO.puts("Width: #{Image.width(img)}")
IO.puts("Height: #{Image.height(img)}")

Kino supports showing images inline. We can use this to display image in the livebook. This opens gate for exploratory image processing

defmodule VixExt do
  alias Vix.Vips.Operation

  @max_height 500

  def show(%Image{} = image) do
    height = Image.height(image)

    # scale down if image height is larger than 500px
    image =
      if height > @max_height do
        Operation.resize!(image, @max_height / height)
      else
        image
      end

    # write vips-image as png image to memory
    {:ok, image_bin} = Image.write_to_buffer(image, ".png")
    Kino.render(Kino.Image.new(image_bin, "image/png"))

    :ok
  end
end
import VixExt

# Let's see show in action

show(img)

Vips Operations

All image processing operations are available in Vix.Vips.Operation

alias Vix.Vips.Operation

Crop

Getting a rectangular region from the image

{:ok, extract_img} = Operation.extract_area(img, 100, 50, 200, 200)
show(extract_img)

Thumbnail

This operation is significantly faster than normal resize due to several optimizations such as shrink-on-load. You can read more about it in the libvips docs: https://github.com/libvips/libvips/wiki/HOWTO----Image-shrinking

Check Vix docs for more details about several optional parameters

{:ok, thumb} = Operation.thumbnail("~/Downloads/kitty.png", 100)
show(thumb)

Resize

Resize the image to 400x600. The resize function accepts scaling factor. Skip vscale if you want to preserve the aspect ratio.

hscale = 400 / Image.width(img)
vscale = 600 / Image.height(img)

{:ok, resized_img} = Operation.resize(img, hscale, vscale: vscale)
show(resized_img)

Flip

direction_input =
  Kino.Input.select("Direction: ",
    VIPS_DIRECTION_HORIZONTAL: "Horizontal",
    VIPS_DIRECTION_VERTICAL: "Vertical"
  )
direction = Kino.Input.read(direction_input)

{:ok, flipped_img} = Operation.flip(img, direction)
show(flipped_img)

Text

The text operation takes multiple optional parameters. See libvips documentation for more details.

text_input = Kino.Input.text("Text: ")
str = String.trim(Kino.Input.read(text_input))
{:ok, {text, _}} = Operation.text(str, dpi: 300, rgba: true)

# add text to an image
{:ok, inserted_text_img} = Operation.composite2(img, text, :VIPS_BLEND_MODE_OVER, x: 50, y: 20)

show(inserted_text_img)

Creating a GIF

black = Operation.black!(500, 500, bands: 3)

# create images with different grayscale
frames = Enum.map(1..255//10, fn n ->
  Operation.linear!(black, [1], [n/255, n/255, n/255])
end)

{:ok, joined_img} = Operation.arrayjoin(frames, across: 1)

{:ok, joined_img} =
  Image.mutate(joined_img, fn mut_img ->
    frame_delay = List.duplicate(100, length(frames))
    :ok = Vix.Vips.MutableImage.set(mut_img, "delay", :VipsArrayInt, frame_delay)
  end)

:ok = Operation.gifsave(joined_img, Path.expand("~/Downloads/bw.gif"), "page-height": 500)

A few more operations

# Gaussian blur
{:ok, blurred_img} = Operation.gaussblur(img, 5)
show(blurred_img)

# convert image to a grayscale image
{:ok, bw_img} = Operation.colourspace(img, :VIPS_INTERPRETATION_B_W)
show(bw_img)

# adding gray border
{:ok, extended_img} =
  Operation.embed(img, 10, 10, Image.width(img) + 20, Image.height(img) + 20,
    extend: :VIPS_EXTEND_BACKGROUND,
    background: [128]
  )

show(extended_img)

# rotate image 90 degrees clockwise
{:ok, rotated_img} = Operation.rot(img, :VIPS_ANGLE_D90)
show(rotated_img)

# join two images horizontally
{:ok, main_img} = Image.new_from_file("~/Downloads/kitten.svg")
{:ok, joined_img} = Operation.join(img, main_img, :VIPS_DIRECTION_HORIZONTAL, expand: true)
show(joined_img)