lib/cimg.ex

defmodule CImg do
  @moduledoc """
  Light-weight image processing module in Elixir with CImg. This module aims to
  create auxiliary routines for Deep Learning.

  Note: It still has a few image processing functions currentrly.

  ### Design detail
  The entity of the image handled by CImg is on the NIF side. On Elixir, the
  reference to the image generated by NIF is stored in the CImg structure. You
  cannot read out the pixels of the image and process it directly, instead you
  can use the image processing functions provided in this module.

  The image will be assigned to Erlang Resource by NIF, so the image will
  automatically be subject to garbage collection when it is no longer in use.

  This is most important point. Some of the functions in this module mutably
  rewrite the original image, when they recieve the image as %Builder{}.

    ```Elixir
    img = CImg.load("sample.jpg")   # create %CImg{}
    CImg.builder(img)               # duplicate img and create %Builder{} with it
    |> CImg.fill(0)                              # rewrite
    |> CImg.draw_circle(100, 100, 30, {0,0,255}) # rewrite
    |> CImg.display(disp)
    ```

  ### Platform
  It has been confirmed to work in the following OS environment.

  - Windows MSYS2/MinGW64
  - WSL2/Ubuntu 20.04

  ### Data exchange with Nx
  It is easy to exchange CImg images and Nx tensors.

  ```Elixir
  # convert CImg image to Nx.Tensor
  iex> img0 = CImg.load("sample.jpg")
  %CImg{{2448, 3264, 1, 3}, handle: #Reference<0.2112145695.4205182979.233827>}
  iex> tensor = CImg.to_binary(img0, dtype: "<u8")
         |> Nx.from_binary({:u, 8})

  # convert Nx.Tensor to CImg image
  iex> img1 = Nx.to_binary(tensor)
         |>CImg.from_bin(2448, 3264, 1, 3, "<u8")
  ```

  ### Demo
  There is a simple program in demo directory. You can do it by following the steps below.

  ```shell
  $ cd demo
  $ mix deps.get
  $ mix run -e "CImgDemo.demo1"
  ```

  Close the appaired window, and stop the demo program.
  """
  alias __MODULE__
  alias CImg.NIF
  alias CImg.Builder

  # image object

  #   :handle - Erlang resource object pointing to the CImg image.

  defstruct handle: nil

  defimpl Inspect do
    import Inspect.Algebra

    def inspect(cimg, opts) do
      concat(["%CImg{", to_doc(CImg.shape(cimg), opts), ", handle: ", to_doc(cimg.handle, opts), "}"])
    end
  end


  # Functions to build image processing sequence.

  defdelegate builder(img), to: Builder
  defdelegate builder(x, y, z, c, val), to: Builder
  defdelegate runit(builder), to: Builder


  @doc """
  Create image{x,y,z,c} filled `val`.

  ## Parameters

    * x,y,z,c - image's x-size, y-size, z-size and spectrum.
    * val     - filling value.

  ## Examples

    ```Elixir
    img = CImg.create(200, 100, 1, 3, 127)
    ```
  """
  def create(x, y, z, c, val) when is_integer(val) do
    with {:ok, h} <- NIF.cimg_create(x, y, z, c, val),
      do: %CImg{handle: h}
  end


  @doc deprecated: "Use `from_binary/6` instead"
  @doc """
  Create image{x,y,z,c} from raw binary.
  `create_from_bin` helps you to make the image from the serialiezed output tensor of DNN model.

  ## Parameters

    * bin - raw binary data to have in a image.
    * x,y,z,c - image's x-size, y-size, z-size and spectrum.
    * dtype - data type in the binary. any data types are converted to int8 in the image.
      - "<f4" - 32bit float (available value in range 0.0..1.0)
      - "<u1" - 8bit unsigned integer

  ## Examples

    ```Elixir
    bin = TflInterp.get_output_tensor(__MODULE__, 0)
    img = CImg.create_from_bin(bin, 300, 300, 1, 3, "<f4")
    ```
  """
  def create_from_bin(bin, x, y, z, c, dtype) when is_binary(bin) do
    with {:ok, h} <- NIF.cimg_create_from_bin(bin, x, y, z, c, dtype),
      do: %CImg{handle: h}
  end


  @doc """
  Load a image from file. The file types supported by this function are jpeg, ping and bmp.
  The file extension identifies which file type it is.

  ## Parameters

    * fname - file path of the image.

  ## Examples

    ```Elixir
    img = CImg.load("sample.jpg")
    ```
  """
  def load(fname) do
    with {:ok, h} <- NIF.cimg_load(fname),
      do: %CImg{handle: h}
  end

  
  @doc deprecated: "Use `from_binary/1` instead"
  @doc """
  Load a image from memory.
  You can create an image from loaded binary data of the image file.

  ## Parameters

    * bin - loaded binary of the image file.

  ## Examples

    ```Elixir
    bin = File.read!("sample.jpg")
    img = CImg.load_from_memory(bin)
    ```
  """
  def load_from_memory(bin) do
    with {:ok, h} <- NIF.cimg_load_from_memory(bin),
      do: %CImg{handle: h}
  end


  @doc """
  Create a image from jpeg/png format binary.
  You can create an image from loaded binary of the JPEG/PNG file.

  ## Parameters

    * jpeg_or_png - loaded binary of the image file.

  ## Examples

    ```Elixir
    jpeg = File.read!("sample.jpg")
    img = CImg.from_binary(jpeg)
    ```
  """
  def from_binary(jpeg_or_png) do
    with {:ok, h} <- NIF.cimg_load_from_memory(jpeg_or_png),
      do: %CImg{handle: h}
  end
  
  @doc """
  Create image{x,y,z,c} from raw binary.
  `create_from_bin` helps you to make the image from the serialiezed output tensor of DNN model.

  ## Parameters

    * bin - raw binary data to have in a image.
    * x,y,z,c - image's x-size, y-size, z-size and spectrum.
    * dtype - data type in the binary. any data types are converted to int8 in the image.
      - "<f4" - 32bit float (available value in range 0.0..1.0)
      - "<u1" - 8bit unsigned integer

  ## Examples

    ```Elixir
    bin = TflInterp.get_output_tensor(__MODULE__, 0)
    img = CImg.create_from_bin(bin, 300, 300, 1, 3, "<f4")
    ```
  """
  def from_binary(bin, x, y, z, c, dtype) when is_binary(bin) do
    with {:ok, h} <- NIF.cimg_create_from_bin(bin, x, y, z, c, dtype),
      do: %CImg{handle: h}
  end

  @doc """
  Save the image to the file.

  ## Parameters

    * cimg - image object to save.
    * fname - file path for the image. (only jpeg images - xxx.jpg - are available now)

  ## Examples

    ```Elixir
    CImg.save(img, "sample.jpg")
    ```
  """
  defdelegate save(cimg, fname),
    to: NIF, as: :cimg_save


  @doc """
  Duplicate the image.

  ## Parameters

    * cimg - image object %CImg{} to duplicate.

  ## Examples

    ```
    img = CImg.dup(original)
    # create new image object `img` have same shape and values of original.
    ```
  """
  def dup(cimg) do
    with {:ok, h} <- NIF.cimg_duplicate(cimg),
      do: %CImg{handle: h}
  end


  @doc deprecated: "Use `to_binary/2` instead"
  @doc """
  Get serialized binary of the image from top-left to bottom-right.
  `to_flat/2` helps you to make 32bit-float arrary for the input tensors of DNN model.

  ## Parameters

    * cimg - image object.
    * opts - conversion options
      - { :dtype, xx } - convert pixel value to data type.
           available: "<f4"/32bit-float, "<u1"/8bit-unsigned-char
      - { :range, {lo, hi} } - normarilzed range when :dtype is "<f4".
           default range: {0.0, 1.0}
      - :nchw - transform axes NHWC to NCHW.
      - :bgr - convert color RGB -> BGR.

  ## Examples

    ```Elixir
    img = CImg.load("sample.jpg")
    bin1 = CImg.to_flat(img, [{dtype: "<f4"}, {:range, {-1.0, 1.0}}, :nchw])
    # convert pixel value to 32bit-float in range -1.0..1.0 and transform axis to NCHW.

    bin2 = CImg.to_flat(img, dtype: "<f4")
    # convert pixel value to 32bit-float in range 0.0..1.0.
    ```
  """
  def to_flat(cimg, opts \\ []) do
    dtype    = Keyword.get(opts, :dtype, "<f4")
    {lo, hi} = Keyword.get(opts, :range, {0.0, 1.0})
    nchw     = :nchw in opts
    bgr      = :bgr  in opts

    with {:ok, bin} <- NIF.cimg_to_bin(cimg, dtype, lo, hi, nchw, bgr) do
      %{descr: dtype, fortran_order: false, shape: {size(cimg)}, data: bin}
    end
  end


  @doc """
  Create the image from %Npy{} format data.

  ## Parameters

    * npy - %Npy{} has 3 rank.

  ## Examples

    ```Elixir
    {:ok, npy} = Npy.load("image.npy")
    img = CImg.from_npy(npy)
    ```
  """
  def from_npy(%{descr: dtype, shape: {h, w, c}, data: bin}) do
    create_from_bin(bin, w, h, 1, c, dtype)
  end


  @doc """
  Convert the image to %Npy{} format data.

  ## Parameters

    * cimg - image object.
    * opts - conversion options
      - { :dtype, xx } - convert pixel value to data type.
           available: "<f4"/32bit-float, "<u1"/8bit-unsigned-char
      - { :range, {lo, hi} } - normarilzed range when :dtype is "<f4".
           default range: {0.0, 1.0}
      - :nchw - transform axes NHWC to NCHW.
      - :bgr - convert color RGB -> BGR.

  ## Examples

    ```Elixir
    img = CImg.load("sample.jpg")

    npy1 =
      img
      |> CImg.to_npy()

    npy2 =
      img
      |> CImg.to_npy([{dtype: "<f4"}, {:range, {-1.0, 1.0}}, :nchw])
    # convert pixel value to 32bit-float in range -1.0..1.0 and transform axis to NCHW.
    ```
  """
  def to_npy(cimg, opts \\ []) do
    dtype    = Keyword.get(opts, :dtype, "<f4")
    {lo, hi} = Keyword.get(opts, :range, {0.0, 1.0})
    nchw     = :nchw in opts
    bgr      = :bgr  in opts
    with {:ok, bin} <- NIF.cimg_to_bin(cimg, dtype, lo, hi, nchw, bgr) do
      {w, h, _z, c} = shape(cimg)
      %{
        descr: dtype,
        fortran_order: false,
        shape: unless nchw do {h, w, c} else {c, h, w} end,
        data: bin
      }
    end
  end

  @doc deprecated: "Use `to_binary/2` instead"
  @doc """
  Convert the image to JPEG binary.

  ## Parameters

    * cimg - image object.

  ## Examples

    ```Elixir
    jpeg = CImg.load("sample.jpg")
      |> CImg.resize({512, 512})
      |> CImg.to_jpeg()
    Kino.Image.new(jpeg, "image/jpeg")
    ```
  """
  def to_jpeg(cimg) do
    with {:ok, bin} <- NIF.cimg_convert_to(cimg, :jpeg), do: bin
  end

  @doc deprecated: "Use `to_binary/2` instead"
  @doc """
  Convert the image to PNG binary.

  ## Parameters

    * cimg - image object.

  ## Examples

    ```Elixir
    png = CImg.load("sample.jpg")
      |> CImg.to_png()
    ```
  """
  def to_png(cimg) do
    with {:ok, bin} <- NIF.cimg_convert_to(cimg, :png), do: bin
  end
  
  @doc """
  Get serialized binary of the image from top-left to bottom-right.
  `to_binary/2` helps you to make 32bit-float arrary for the input tensors of DNN model
  or jpeg/png format binary on memory.

  ## Parameters

    * cimg - image object.
    * opts - conversion options
      - :jpeg - convert to JPEG format binary.
      - :png - convert to PNG format binary.

      following options can be applied when converting the image to row binary.
      - { :dtype, xx } - convert pixel value to data type.
           available: "<f4"/32bit-float, "<u1"/8bit-unsigned-char
      - { :range, {lo, hi} } - normarilzed range when :dtype is "<f4".
           default range: {0.0, 1.0}
      - :nchw - transform axes NHWC to NCHW.
      - :bgr - convert color RGB -> BGR.

  ## Examples

    ```Elixir
    img = CImg.load("sample.jpg")
    
    jpeg = CImg.to_binary(img, :jpeg)
    # convert to JPEG format binary on memory.
    
    png = CImg.to_binary(img, :png)
    # convert to PNG format binary on memory.
    
    bin1 = CImg.to_binary(img, [{dtype: "<f4"}, {:range, {-1.0, 1.0}}, :nchw])
    # convert pixel value to 32bit-float in range -1.0..1.0 and transform axis to NCHW.

    bin2 = CImg.to_binary(img, dtype: "<f4")
    # convert pixel value to 32bit-float in range 0.0..1.0.
    ```
  """
  def to_binary(cimg, opts \\ [])

  def to_binary(cimg, :jpeg) do
    with {:ok, bin} <- NIF.cimg_convert_to(cimg, :jpeg) do
      bin
    end
  end

  def to_binary(cimg, :png) do
    with {:ok, bin} <- NIF.cimg_convert_to(cimg, :png) do
      bin
    end
  end

  def to_binary(cimg, opts) do
    with %{data: bin} <- to_npy(cimg, opts) do
      bin
    end
  end


  @doc """
  Get a new image object resized {x, y}.

  ## Parameters

    * cimg - image object.
    * {x, y} - resize width and height or
      scale  - resize scale
    * align - alignment mode
      - :none - fit resizing
      - :ul - fixed aspect resizing, upper-leftt alignment.
      - :br - fixed aspect resizing, bottom-right alignment.
    * fill - filling value for the margins, when fixed aspect resizing.

  ## Examples

    ```Elixir
    img = CImg.load("sample.jpg")
    res = CImg.get_resize(img, {300,300}, :ul)
    ```
  """
  def resize(img, size, align \\ :none, fill \\ 0)
  def resize(%CImg{}=cimg, scale, align, fill) when is_float(scale) do
    {x, y, _, _} = CImg.shape(cimg)
    size = Enum.map([x,y], fn x -> round(scale*x) end) |> List.to_tuple
    resize(cimg, size, align, fill)
  end
  def resize(%CImg{}=cimg, {x, y}=_size, align, fill) do
    align = case align do
      :none -> 0
      :ul   -> 1
      :br   -> 2
      _     -> raise(ArgumentError, "unknown align '#{align}'.")
    end

    with {:ok, packed} <- NIF.cimg_get_resize(cimg, x, y, align, fill),
      do: %CImg{handle: packed}
  end
#  defdelegate resize(builder, size, align, fill),

#    to: Builder



  @doc """
  Bluring image.

  ## Parameters

    * img - %CImg{} or %Builder{} object
    * sigma -
    * boundary_conditions -
    * is_gaussian -

  ## Examples

    ```Elixir
    img = CImg.load("sample.jpg")
    blured = CImg.blur(img, 0.3)
    ```
  """
  def blur(img, sigma, boundary_conditions \\ true, is_gaussian \\ true)
  def blur(%Builder{}=builder, sigma, boundary_conditions, is_gaussian) do
    # mutable operation.

    NIF.cimg_blur(builder, sigma, boundary_conditions, is_gaussian)
  end
  def blur(%CImg{}=cimg, sigma, boundary_conditions, is_gaussian) do
    dup = CImg.dup(cimg)
    NIF.cimg_blur(dup, sigma, boundary_conditions, is_gaussian)
  end


  @doc """
  mirroring the image on `axis`

  ## Parameters

    * cimg - %CImg{} or %Builder{} object.
    * axis - flipping axis: :x, :y

  ## Examples

    ```Elixir
    mirror = CImg.mirror(img, :y)
    # vertical flipping
    ```
  """
  def mirror(%Builder{}=builder, axis) when axis in [:x, :y] do
    # mutable operation

    NIF.cimg_mirror(builder, axis)
  end
  def mirror(%CImg{}=cimg, axis) when axis in [:x, :y] do
    dup = CImg.dup(cimg)
    NIF.cimg_mirror(dup, axis)
  end


  def transpose(cimg) do
    NIF.cimg_transpose(cimg)
  end

  @doc """
  Get the gray image of the image.

  ## Parameters

    * cimg - image object %CImg{} to save.
    * opt_pn - intensity inversion: 0 (default) - no-inversion, 1 - inversion

  ## Examples

    ```Elixir
    gray = CImg.gray(img, 1)
    # get inverted gray image
    ```
  """
  def gray(cimg, opt_pn \\ 0) do
    with {:ok, gray} <- NIF.cimg_get_gray(cimg, opt_pn),
      do: %CImg{handle: gray}
  end

  @doc """
  Get the inverted image of the image.

  ## Examples

    ```Elixir
    inv = CImg.invert(img)
    # get inverted image
    ```
  """
  def invert(cimg) do
    with {:ok, inv} <- NIF.cimg_get_invert(cimg),
      do: %CImg{handle: inv}
  end


  @doc """
  Get crop.

  ## Parameters

    * cimg - image object %CImg{} to save.
  """
  def get_crop(cimg, x0, y0, z0, c0, x1, y1, z1, c1, boundary_conditions \\ 0) do
    with {:ok, crop} <- NIF.cimg_get_crop(cimg, x0, y0, z0, c0, x1, y1, z1, c1, boundary_conditions),
      do: %CImg{handle: crop}
  end


  @doc """
  Create color mapped image by lut.

  ## Parameters

    * cimg - image object %CImg{} to save.
    * lut - color map. build-in or user defined.
      - build-in map: {:default, :lines, :hot, :cool, :jet}
      - user defined: list of color tupple, [{0,0,0},{10,8,9},{22,15,24}...].
    * boundary - handling the pixel value outside the color map range.
      - 0 - set to zero value.
      - 1 - 
      - 2 - repeat from the beginning of the color map.
      - 3 - repeat while wrapping the color map.
  
  ## Examples
  
    ```Elixir
    gray = CImg.load("sample.jpg") |> CImg.gray()
    
    jet = CImg.color_mapping(gray, :jet)
    # heat-map coloring.

    custom = CImg.color_mapping(gray, [{0,0,0},{10,8,9},{22,15,24}], 2)
    # custom coloring.
    ```
  """
  def color_mapping(cimg, lut \\ :default, boundary \\ 0)

  def color_mapping(cimg, lut, boundary) when lut in [:default, :lines, :hot, :cool,:jet]  do
    with {:ok, h} <- NIF.cimg_color_mapping(cimg, lut, boundary),
      do: %CImg{handle: h}
  end

  def color_mapping(cimg, lut, boundary) when is_list(lut) do
    with {:ok, h} <- NIF.cimg_color_mapping_by(cimg, lut, boundary),
      do: %CImg{handle: h}
  end


  @doc """
  [mut] Set the pixel value at (x, y).

  ## Parameters

    * cimg - image object.
    * val - value.
    * x,y,z,c - location in the image.

  ## Examples

    ```Elixir
    res = CImg.set(0x7f, 120, 40)
    ```
  """
  def set(val, cimg, x, y \\ 0, z \\ 0, c \\ 0) do
    dup = CImg.dup(cimg)
    NIF.cimg_set(val, dup, x, y, z, c)
  end


  @doc """
  Get the pixel value at (x, y).

  ## Parameters

    * cimg - image object.
    * x,y,z,c - location in the image.

  ## Examples

    ```Elixir
    x = CImg.get(120, 40)
    ```
  """
  defdelegate get(cimg, x, y \\ 0, z \\ 0, c \\ 0),
    to: NIF, as: :cimg_get


  @doc """
  Get shape {x,y,z,c} of the image

  ## Parameters

    * cimg - image object.

  ## Examples

    ```Elixir
    shape = CImg.shape(imge)
    ```
  """
  defdelegate shape(cimg),
    to: NIF, as: :cimg_shape


  @doc """
  Get byte size of the image

  ## Parameters

    * builder - builder object.

  ## Examples

    ```Elixir
    size = CImg.sizh(imge)
    ```
  """
  defdelegate size(cimg),
    to: NIF, as: :cimg_size


  @doc """
  Thresholding the image.

  ## Parameters

    * img - %CImg{} or %Builder{} object.
    * val - threshold value
    * soft -
    * strict -

  ## Examples

    ```Elixir
    res = CImg.threshold(imge, 100)
    ```
  """
  def threshold(img, val, soft \\ false, strict \\ false)
  def threshold(%Builder{}=builder, val, soft, strict) do
    # mutable operation.

    NIF.cimg_threshold(builder, val, soft, strict)
  end
  def threshold(%CImg{}=cimg, val, soft, strict) do
    dup = CImg.dup(cimg)
    NIF.cimg_threshold(dup, val, soft, strict)
  end


  @doc """
  Pick the pixels on the source image and write on the distination image
  according to the mapping table.

  ## Parameters

    * cimg - distination image object.
    * cimg_src - source image.
    * mapping - mapping table. ex) [{[10,10],[10,20]}], move pixel at [10,10] to [10,20]
    * cx, cy, cz - location of upper-left mapping table on both images.

  ## Examples

    ```Elixir
    map = [{[20,20],[25,25]}, {[20,21],[25,26]}]
    src = CImg.load("sample.jpg")
    dst = CImg.builder(src)
      |> CImg.transfer(src, map)
      |> CImg.runit()
    ```
  """
  def transfer(img, cimg_src, mapping, cx \\ 0, cy \\ 0, cz \\ 0)
  def transfer(%Builder{}=builder, cimg_src, mapping, cx, cy, cz) do
    # mutable operation.

    NIF.cimg_transfer(builder, cimg_src, mapping, cx, cy, cz)
  end
  def transfer(%CImg{}=cimg, cimg_src, mapping, cx, cy, cz) do
    dup = CImg.dup(cimg)
    NIF.cimg_transfer(dup, cimg_src, mapping, cx, cy, cz)
  end


  @doc """
  [mut] Filling the image with `val`.

  ## Parameters

    * builder - builder object.
    * val - filling value.

  ## Examples

    ```Elixir
    res = CImg.fill(img, 0x7f)
    ```
  """
  def fill(%Builder{}=builder, val) do
    NIF.cimg_fill(builder, val)
  end

  @doc """
  [mut] Draw graph.

  ## Parameters

    * cimg - image object %CImg{} to save.
  """
  def draw_graph(%Builder{}=cimg, data, color, opacity \\ 1.0, plot_type \\ 1, vertex_type \\ 1, ymin \\ 0.0, ymax \\ 0.0, pattern \\ 0xFFFFFFFF) do
    # mutable operation.

    NIF.cimg_draw_graph(cimg, data, color, opacity, plot_type, vertex_type, ymin, ymax, pattern)
  end


  @doc """
  [mut] Draw rectangle in the image.

  ## Parameters

    * builder - builder object.
    * x0,y0,x1,y1 - diagonal coordinates. if all of them are integer, they mean
    actual coodinates. if all of them are float within 0.0-1.0, they mean ratio
    of the image.
    * color - boundary color
    * opacity - opacity: 0.0-1.0
    * pattern - boundary line pattern: 32bit pattern

  ## Examples

    ```Elixir
    CImg.draw_rect(img, 50, 30, 100, 80, {255, 0, 0}, 0.3, 0xFF00FF00)

    CImg.draw_rect(img, 0.2, 0.3, 0.6, 0.8, {0, 255, 0})
    ```
  """
  def draw_rect(%Builder{}=builder, x0, y0, x1, y1, color, opacity \\ 1.0, pattern \\ 0xFFFFFFFF) do
    # mutable operation.

    cond do
      Enum.all?([x0, y0, x1, y1], &is_integer/1) ->
        NIF.cimg_draw_rectangle(builder, x0, y0, x1, y1, color, opacity, pattern)
      Enum.all?([x0, y0, x1, y1], fn x -> 0.0 <= x and x <= 1.0 end) ->
        NIF.cimg_draw_ratio_rectangle(builder, x0, y0, x1, y1, color, opacity, pattern)
    end
  end


  @doc """
  [mut] Draw filled circle in the image.

  ## Parameters

    * builder - builder object.
    * x0,y0 - circle center location
    * radius - circle radius
    * color - filling color
    * opacity - opacity: 0.0-1.0

  ## Examples

    ```Elixir
    res = CImg.draw_circle(imge, 100, 80, 40, {0, 0, 255})
    ```
  """
  def draw_circle(%Builder{}=builder, x0, y0, radius, color, opacity \\ 1.0) do
    # mutable operation.

    NIF.cimg_draw_circle_filled(builder, x0, y0, radius, color, opacity)
  end


  @doc """
  [mut] Draw circle in the image.

  ## Parameters

    * builder - builder object.
    * x0,y0 - circle center location
    * radius - circle radius
    * color - boundary color
    * opacity - opacity: 0.0-1.0
    * pattern - boundary line pattern

  ## Examples

    ```Elixir
    res = CImg.draw_circle(imge, 100, 80, 40, {0, 0, 255}, 0.3, 0xFFFFFFFF)
    ```
  """
  def draw_circle(%Builder{}=builder, x0, y0, radius, color, opacity, pattern) do
    # mutable operation.

  	NIF.cimg_draw_circle(builder, x0, y0, radius, color, opacity, pattern)
  end


  @doc """
  Display the image on the CImgDisplay object.

  ## Parameters

    * cimg - image object.
    * display - CImgDisplay object

  ## Examples

    ```Elixir
    disp = CImgDisplay.create(img, "Sample")
    CImg.display(imge, disp)
    ```
  """
  defdelegate display(cimg, disp),
    to: NIF, as: :cimg_display
end