lib/cimg.ex

defmodule CImg do
  @moduledoc """
  A lightweight image processing module in Elixir using CImg, aimed at creating auxiliary routines for Deep Learning.

  ### Session execution

  Each function provided by CImg can be used alone (eager execution) or
  as a set of functions (session execution).

  EAGER execution is good for interactive work because you can see the results
  of your image processing immediately. But it tends to consume more memory
  than necessary. On the other hand, SESSION execution reduces memory consumption
  because after assembling the image processing sequence, it is executed all at once.

  To perform image processing in SESSION mode, you must assemble a image processing
  sequence using Elixir's pipe syntax. At the entrance of the pipe, place
  a function {seed} that generates a %Builder{}, then pipe in the necessary
  image processing {grow}, and finally place a function {crop} that executes
  those image processing.

  In this document, the following symbols indicate which category each function belongs to.
    * {seed} - generate %Builder{}
    * {grow} - image processing piece
    * {crop} - execute session and get result

  #### Examples

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

    CImg.builder(img)               # generate %Builder{} with %CImg{} as seed image
    |> CImg.draw_circle(100, 100, 30, {0,0,255}) # building an image processing session
    |> CImg.draw_circle(150, 200, 40, {255,0,0}) # ...
    |> CImg.display(disp)           # execute above session and display result image on PC screen
    ```

  ### Data exchange with Nx
  It is easy to exchange %CImg{} 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")
  ```
  """
  alias __MODULE__
  alias CImg.NIF

  # 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

  @color16 %{
    :white   => { 255, 255, 255 },
    :silver  => { 192, 192, 192 },
    :gray    => { 128, 128, 128 },
    :black   => {   0,   0,   0 },
    :red     => { 255,   0,   0 },
    :maroon  => { 128,   0,   0 },
    :yellow  => { 255, 255,   0 },
    :olive   => { 128, 128,   0 },
    :lime    => {   0, 255,   0 },
    :green   => {   0, 128,   0 },
    :aqua    => {   0, 255, 255 },
    :teal    => {   0, 128, 128 },
    :blue    => {   0,   0, 255 },
    :navy    => {   0,   0, 128 },
    :fuchsia => { 255,   0, 255 },
    :purple  => { 128,   0, 128 },
  }

  defp color_code(color_name) when is_atom(color_name) do
    @color16[color_name]
  end

  defp color_code({_r,_g,_b}=rgb) do
    rgb
  end

  defmodule Builder do
    @moduledoc """
    Record the image processing sequence for SESSION execution.
    """
	  # builder object

	  #   :seed   - operation to get a initial image.

	  #   :script - image operations

	  defstruct seed: nil, script: []
  end

  defp push_cmd(%Builder{script: script}=builder, cmd) do
    %{builder| script: [cmd|script]}
  end

  @doc """
  {seed} Returns an empty builder for recording image processing scripts.

  ## Parameters

    None.

  ## Examples

    ```elixir
    # build a script
    script = CImg.builder()
      |> CImg.gray()
      |> CImg.resize({256, 256})

    # apply the script to an image.
    seed   = CImg.load("sample.jpg")
    result = CImg.run(script, seed)
    ```
  """
  def builder() do
    %Builder{}
  end

  @doc """
  {seed} Returns a builder with an existing %CImg{} as the seed image.

  ## Parameters

    * img - %CImg{}

  ## Examples

    ```elixir
    cimg = CImg.load("sample.jpg")

    result = CImg.builder(cimg)  # create %Builder{} has cimg
      |> CImg.draw_circle(100, 100, 30, {0, 255, 0})
      |> CImg.run()
    ```
  """
  def builder(%CImg{}=cimg) do
    %Builder{seed: {:copy, cimg}}
  end


  @doc """
  {seed} Returns a builder that uses an image read from the file as the seed image.

  ## Parameters

    * atom - image file format: `:jpeg` or `:png`
    * fname - file name

  ## Examples

    ```elixir
    result = CImg.builder(:jpeg, "sample.jpg")
      |> CImg.draw_circle(100, 100, 30, {0, 255, 0})
      |> CImg.run()
    ```
  """
  def builder(:file, fname) do
    %Builder{seed: {:load, fname}}
  end

  def builder(:image, jpeg_or_png) do
    %Builder{seed: {:load_from_memory, jpeg_or_png}}
  end


  @doc """
  {seed} Returns a builder whose seed image is an image of shape [x,y,z,c] filled with the value `val`.

  ## Parameters

    * x,y,z,c - shape of image
    * val - filling value

  ## Examples

    ```elixir
    res = CImg.builder(640, 480, 1, 3, 64)
      |> CImg.draw_circle(100, 100, 30, {0, 255, 0})
      |> CImg.run()
    ```
  """
  def builder(x, y, z, c, val) when is_integer(val) do
    %Builder{seed: {:create, x, y, z, c, val}}
  end


  @doc """
  {seed} Returns a builder that takes as seed image a binary - shape[], dtype -
  converted to an image

  ## Parameters

    * bin - binary
    * x,y,z,c - shape of the image represented by `bin`
    * opts - convertion options
      - { :dtype, xxx } - convert data type to pixel.
          available: "<f4"/32-bit-float, "<u1"/8bit-unsigned-char
      - { :range, {lo, hi} } - convert range lo..hi to 0..255.
          default range: {0.0, 1.0}
      - { :gauss, {{mu-R,sigma-R},{mu-G,sigma-G},{mu-B,sigma-B}} } - inverse normalization by Gaussian distribution.
      - :nchw - transform axes NCHW to NHWC.
      - :bgt - convert color BGR -> RGB.

  ## Examples

    ```elixir
    result = CImg.builder(bin 640, 480, 1, 3, dtype: "<f4")
      |> CImg.draw_circle(100, 100, 30, {0, 255, 0})
      |> CImg.run()
    ```
  """
  def builder(bin, x, y, z, c, opts \\ []) when is_binary(bin) do
    dtype    = Keyword.get(opts, :dtype, "<f4")
    nchw     = :nchw in opts
    bgr      = :bgr  in opts

    {conv_op, conv_prms} = if prms = Keyword.get(opts, :gauss) do
      {:gauss, prms}
    else
      {:range, Keyword.get(opts, :range, {0.0, 1.0})}
    end

    %Builder{seed: {:create_from_bin, bin, x, y, z, c, dtype, conv_op, conv_prms, nchw, bgr}}
  end


  @doc """
  {crop} Returns an image with the script applied to the seed image.

  ## Parameters

    * builder - %Builder{}

  ## Examples

    ```elixir
    result = CImg.builder(100, 100, 1, 3, 0)
      |> CImg.draw_circle(100, 100, 30, {0, 255, 0})
      |> CImg.run()
    ```
  """
  def run(%Builder{seed: seed, script: script}) when not is_nil(seed) do
    script = [{:get_image} | script]
    case NIF.cimg_run([seed | Enum.reverse(script)]) do
      {:ok, img} -> %CImg{handle: img}
      {:ok, _shape, bin} -> bin
      any -> any
    end
  end


  @doc """
  {crop} Returns an image with the script applied to `cimg`.

  ## Parameters

    * builder - %Builder{}
    * cimg - %CImg{}

  ## Examples

    ```elixir
    cimg = CImg.load("sample.jpg")

    result = CImg.builder()
      |> CImg.draw_circle(100, 100, 30, {0, 255, 0})
      |> CImg.runit(cimg)
    ```
  """
  def run(%Builder{}=builder, %CImg{}=cimg) do
    run(%Builder{builder | seed: {:copy, cimg}})
  end


  @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
    builder(x, y, z, c, val) |> run()
  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
    builder(:file, fname) |> run()
  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
    bin  = File.read!("sample.jpg")
    jpeg = CImg.from_binary(bin)
    ```
  """
  def from_binary(jpeg_or_png) do
    builder(:image, jpeg_or_png) |> run()
  end


  @doc """
  Create image{x,y,z,c} from raw binary.
  `from_binary` 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.
    * opts - convertion options
      - { :dtype, xxx } - convert data type to pixel.
          available: "<f4"/32-bit-float, "<u1"/8bit-unsigned-char
      - { :range, {lo, hi} } - convert range lo..hi to 0..255.
          default range: {0.0, 1.0}
      - { :gauss, {{mu-R,sigma-R},{mu-G,sigma-G},{mu-B,sigma-B}} } - inverse normalization by Gaussian distribution.
      - :nchw - transform axes NCHW to NHWC.
      - :bgt - convert color BGR -> RGB.

  ## Examples

    ```elixir
    bin = TflInterp.get_output_tensor(__MODULE__, 0)
    img = CImg.from_binary(bin, 300, 300, 1, 3, dtype: "<f4")

    img = CImg.from_binary(bin, 300, 300, 1, 3, gauss: {{103.53,57.375},{116.28,57.12},{123.675,58.395}})
    ```
  """
  def from_binary(bin, x, y, z, c, opts \\ []) when is_binary(bin) do
    builder(bin, x, y, z, c, opts) |> run()
  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
    from_binary(bin, w, h, 1, c, dtype)
  end


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

  ## Parameters

    * img - %CImg{} or %Builder{}

  ## Examples

    ```elixir
    shape = CImg.shape(img)
    ```
  """
  def shape(%CImg{}=img) do
    builder(img) |> shape()
  end

  def shape(%Builder{seed: seed, script: script}) when not is_nil(seed) do
    script = [{:get_shape} | script]
    NIF.cimg_run([seed | Enum.reverse(script)])
  end


  @doc """
  {crop} Get byte size of the image

  ## Parameters

    * img - %CImg{} or %Builder{}

  ## Examples

    ```elixir
    size = CImg.size(img)
    ```
  """
  def size(%CImg{}=img) do
    builder(img) |> size()
  end

  def size(%Builder{seed: seed, script: script}) when not is_nil(seed) do
    script = [{:get_size} | script]
    NIF.cimg_run([seed | Enum.reverse(script)])
  end


  @doc """
  {crop} Save the image to the file.

  ## Parameters

    * img - %CImg{} or %Builder{}
    * fname - file path for the image. (only jpeg images - xxx.jpg - are available now)

  ## Examples

    ```elixir
    CImg.save(img, "sample.jpg")
    ```
  """
  def save(%CImg{}=img, fname) do
    builder(img)
    |> save(fname)
  end

  def save(%Builder{seed: seed, script: script}, fname)  when not is_nil(seed) do
    script = [{:save, fname} | script]
    NIF.cimg_run([seed | Enum.reverse(script)])
  end


  @doc """
  {crop} 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

    * img - %CImg{} or %Builder{}
    * 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} } - range transformation when :dtype is "<f4".
           default range: {0.0, 1.0}
      - { :gauss, {{mu-R,sigma-R},{mu-G,sigma-G},{mu-B,sigma-B}} } - normalize by Gaussian distribution.
      - :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.

    bin3 = CImg.to_binary(img, gauss: {{103.53,57.375},{116.28,57.12},{123.675,58.395}})
    # convert pixel value to 32bit-float normalized by Gaussian destribution: mu-R=103.53, sigma-R=57.375,...
    ```
  """
  def to_binary(img, opts \\ [])

  def to_binary(%CImg{}=cimg, opts) do
    builder(cimg)
    |> to_binary(opts)
  end

  def to_binary(%Builder{seed: seed, script: script}=builder, opts) when opts in [:jpeg, :png] do
    if is_nil(seed) do
      push_cmd(builder, {:to_image, opts})
    else
      script = [{:to_image, opts} | script]
      with {:ok, image} <- NIF.cimg_run([seed | Enum.reverse(script)]),
        do: image
    end
  end

  def to_binary(%Builder{seed: seed, script: script}=builder, opts) do
    dtype = Keyword.get(opts, :dtype, "<f4")
    nchw  = :nchw in opts
    bgr   = :bgr  in opts

    {conv_op, conv_prms} = if prms = Keyword.get(opts, :gauss) do
      {:gauss, prms}
    else
      {:range, Keyword.get(opts, :range, {0.0, 1.0})}
    end

    if is_nil(seed) do
      push_cmd(builder, {:to_bin, dtype, conv_op, conv_prms, nchw, bgr})
    else
      script = [{:to_bin, dtype, conv_op, conv_prms, nchw, bgr} | script]
      with {:ok, _shape, bin} <- NIF.cimg_run([seed | Enum.reverse(script)]),
        do: bin
    end
  end


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

  ## Parameters

    * img - %CImg{} or %Builder{}
    * opts - conversion options
      - { :dtype, xx } - convert pixel value to data type.
           available: "<f4"/32bit-float, "<u1"/8bit-unsigned-char
      - { :range, {lo, hi} } - range transformation when :dtype is "<f4".
           default range: {0.0, 1.0}
      - { :gauss, {{mu-R,sigma-R},{mu-G,sigma-G},{mu-B,sigma-B}} } - normalize by Gaussian distribution.
      - :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(img, opts \\ [])

  def to_npy(%CImg{}=cimg, opts) do
    builder(cimg)
    |> to_npy(opts)
  end

  def to_npy(%Builder{seed: seed, script: script}, opts) do
    dtype = Keyword.get(opts, :dtype, "<f4")
    nchw  = :nchw in opts
    bgr   = :bgr  in opts

    {conv_op, conv_prms} = if prms = Keyword.get(opts, :gauss) do
      {:gauss, prms}
    else
      {:range, Keyword.get(opts, :range, {0.0, 1.0})}
    end

    script = [{:to_bin, dtype, conv_op, conv_prms, nchw, bgr} | script]
    with {:ok, shape, bin} <- NIF.cimg_run([seed | Enum.reverse(script)]) do
      %{
        __struct__: Npy,
        descr: dtype,
        fortran_order: false,
        shape: shape,
        data: bin
      }
    end
  end


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

  ## Parameters

    * img - %CImg{} or %Builder{}
    * x,y,z,c - location in the image.

  ## Examples

    ```elixir
    val = CImg.get(img, 120, 40)
    ```
  """
  def get(img, x, y \\ 0, z \\ 0, c \\ 0)

  def get(%CImg{}=cimg, x, y, z, c) do
    builder(cimg) |> get(x, y, z, c)
  end

  def get(%Builder{seed: seed, script: script}, x, y, z, c) do
    script = [{:get, x, y, z, c} | script]
    NIF.cimg_run([seed | Enum.reverse(script)])
  end


  @doc """
  {crop} Extracting a partial image specified in a window from an image.

  ## Parameters

    * img - %CImg{} or %Builder{}
    * x0,y0,z0,c0, x1,y1,z1,c1 - window
    * bundary_condition -

  ## Examples

    ```elixir
    partial = CImg.get_crop(img, 100, 100, 0, 0, 400, 600, 0, 3)
    ```
  """
  def get_crop(img, x0, y0, z0, c0, x1, y1, z1, c1, boundary_conditions \\ 0)

  def get_crop(%CImg{}=cimg, x0, y0, z0, c0, x1, y1, z1, c1, boundary_conditions) do
    builder(cimg)
    |> get_crop(x0, y0, z0, c0, x1, y1, z1, c1, boundary_conditions)
  end

  def get_crop(%Builder{seed: seed, script: script}, x0, y0, z0, c0, x1, y1, z1, c1, boundary_conditions) do
    script = [{:get_crop, x0, y0, z0, c0, x1, y1, z1, c1, boundary_conditions} | script]
    with {:ok, img} <- NIF.cimg_run([seed | Enum.reverse(script)]),
      do: %CImg{handle: img}
  end


  @doc """
  {grow} Booking to clear the image.

  ## Parameters

    * builder - %Builder{}.

  ## Examples

  ```elixir
  result = CImg.builder(:file, "sample.jpg")
    |> CImg.clear()
    |> CImg.run()
  ```
  """
  def clear(%Builder{}=builder) do
    push_cmd(builder, {:clear})
  end


  @doc """
  {grow} Booking to fill the image with `val`.

  ## Parameters

    * builder - %Builder{}
    * val - filling value.

  ## Examples

    ```Elixir
    result = CImg.builder(cimg)
      |> CImg.fill(img, 0x7f)
      |> CImg.run()
    ```
  """
  def fill(%Builder{}=builder, val) do
    push_cmd(builder, {:fill, val})
  end


  @doc """
  {grow} Get the inverted image of the image.

  ## Examples

    ```elixir
    inv = CImg.invert(img)
    # get inverted image
    ```
  """
  def invert(%CImg{}=img) do
    builder(img)
    |> invert()
    |> run()
  end

  def invert(%Builder{}=builder) do
    push_cmd(builder, {:invert})
  end


  @doc """
  {grow} Get the gray image of the image.

  ## Parameters

    * img - %CImg{} or %Builder{}
    * opt_pn - intensity inversion: 0 (default) - no-inversion, 1 - inversion

  ## Examples

    ```elixir
    gray = CImg.gray(img)
    ```
  """
  def gray(img, opt_pn \\ 0)

  def gray(%CImg{}=cimg, opt_pn) do
    builder(cimg)
    |> gray(opt_pn)
    |> run()
  end

  def gray(%Builder{}=builder, opt_pn) do
    push_cmd(builder, {:gray, opt_pn})
  end


  @doc """
  {grow} Thresholding the image.

  ## Parameters

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

  ## Examples

    ```Elixir
    res = CImg.threshold(img, 100)
    ```
  """
  def threshold(img, val, soft \\ false, strict \\ false)

  def threshold(%CImg{}=cimg, val, soft, strict) do
    builder(cimg)
    |> threshold(val, soft, strict)
    |> run()
  end

  def threshold(%Builder{}=builder, val, soft, strict) do
    push_cmd(builder, {:threshold, val, soft, strict})
  end

  @doc """
  {grow} Blending two images.

  ## Parameters

    * img - %CImg{} or %Builder{} object.
    * mask - %CImg{} object.
    * ratio - blending ratio: (1.0-ratio)*img + ratio*mask

  ## Examples

    ```Elixir
    img = CImg.blend(img_org, img_mask, 0.6)
    ```
  """
  def blend(img, mask, ratio \\ 0.5)

  def blend(%CImg{}=cimg, %CImg{}=mask, ratio) do
    builder(cimg)
    |> blend(mask, ratio)
    |> run()
  end

  def blend(%Builder{}=builder, %CImg{}=mask, ratio) do
    push_cmd(builder, {:blend, mask, ratio})
  end

  @doc """
  {grow} Paint mask image.

  ## Parameters

    * img - %CImg{} or %Builder{} object.
    * mask - %CImg{} object, binary image.
    * lut - color map.
    * opacity - opacity: (1.0-opacity)*img + opacity*mask

  ## Examples

    ```Elixir
    img = CImg.paint_mask(img_org, img_mask, [{255,0,0}], 0.6)
    ```
  """
  def paint_mask(img, mask, lut, opacity \\ 0.5)

  def paint_mask(%CImg{}=cimg, %CImg{}=mask, lut, opacity) do
    builder(cimg)
    |> paint_mask(mask, lut, opacity)
    |> run()
  end

  def paint_mask(%Builder{}=builder, %CImg{}=mask, lut, opacity) when is_tuple(lut) do
    paint_mask(builder, mask, [lut], opacity)
  end

  def paint_mask(%Builder{}=builder, %CImg{}=mask, lut, opacity) do
    push_cmd(builder, {:paint_mask, mask, lut, opacity})
  end


  @doc """
  {grow} Create color mapped image by lut.

  ## Parameters

    * img - %CImg{} or %Builder{}
    * 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(img, lut \\ :default, boundary \\ 0)

  def color_mapping(%CImg{}=cimg, lut, boundary) do
    builder(cimg)
    |> color_mapping(lut, boundary)
    |> run()
  end

  def color_mapping(%Builder{}=builder, lut, boundary) do
    cond do
      lut in [:default, :lines, :hot, :cool, :jet] ->
      	  push_cmd(builder, {:color_mapping, lut, boundary})
      is_list(lut) ->
      	  push_cmd(builder, {:color_mapping_by, lut, boundary})
    end
  end

  @doc """
  {grow} Append image.

  ## Parameters

    * img - %CImg{} or %Builder{}
    * img2 - %CImg{}, append image
    * axis - append on axis: {:x, :y, :z, :c}
    * align - 

  ## Examples

    ```elixir
    img = CImg.load("sample.jpg")
    twice = CImg.append(img, img, :x)
    ```
  """
  def append(img, img2, axis, align \\ 0.0)
  
  def append(%CImg{}=cimg, %CImg{}=img2, axis, align) do
    builder(cimg)
    |> append(img2, axis, align)
    |> run()
  end

  def append(%Builder{}=builder, %CImg{}=img2, axis, align) do
    push_cmd(builder, {:append, img2, axis, align})
  end


  @doc """
  {grow} Bluring image.

  ## Parameters

    * img - %CImg{} or %Builder{}
    * 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(%CImg{}=cimg, sigma, boundary_conditions, is_gaussian) do
    builder(cimg)
    |> blur(sigma, boundary_conditions, is_gaussian)
    |> run()
  end

  def blur(%Builder{}=builder, sigma, boundary_conditions, is_gaussian) do
    push_cmd(builder, {:blur, sigma, boundary_conditions, is_gaussian})
  end


  @doc """
  {grow} mirroring the image on `axis`

  ## Parameters

    * img - %CImg{} or %Builder{}
    * axis - flipping axis: :x, :y

  ## Examples

    ```elixir
    mirror = CImg.mirror(img, :y)
    # vertical flipping
    ```
  """
  def mirror(%CImg{}=img, axis) do
    builder(img)
    |> mirror(axis)
    |> run()
  end

  def mirror(%Builder{}=builder, axis) when axis in [:x, :y] do
  	push_cmd(builder, {:mirror, axis})
  end


  @doc """
  {grow} transpose the image

  ## Parameters

    * img - %CImg{} or %Builder{}

  ## Examples

    ```elixir
    transposed = CImg.transpose(img)
    ```
  """
  def transpose(%CImg{}=img) do
    builder(img)
    |> transpose()
    |> run()
  end

  def transpose(%Builder{}=builder) do
    push_cmd(builder, {:transpose})
  end


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

  ## Parameters

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

  ## Examples

    ```elixir
    img = CImg.load("sample.jpg")
    result = CImg.get_resize(img, {300,300}, :ul)
    ```
  """
  def resize(img, size, align \\ :none, fill \\ 0)

  def resize(%CImg{}=cimg, size, align, fill) do
    builder(cimg)
    |> resize(size, align, fill)
    |> run()
  end

  def resize(%Builder{}=builder, %CImg{}=cimg2, align, fill) do
    {w, h, _, _} = CImg.shape(cimg2)
    resize(builder, {w, h}, align, fill)
  end

  def resize(%Builder{}=builder, scale, align, fill) when is_float(scale) do
    size_xy = -round(100*scale)
    resize(builder, {size_xy, size_xy}, align, fill)
  end

  def resize(%Builder{}=builder, {x, y}, align, fill) do
    align = case align do
      :none -> 0
      :ul   -> 1
      :br   -> 2
      :crop -> 3
      _     -> raise(ArgumentError, "unknown align '#{align}'.")
    end

    push_cmd(builder, {:resize, x, y, align, fill})
  end

  @doc """
  {grow} Set the pixel value at (x, y).

  ## Parameters

    * builder - %Builder{}
    * val - value.
    * x,y,z,c - location in the image.

  ## Examples

    ```elixir
    result = CImg.builder(cimg)
      |> CImg.set(0x7f, 120, 40)
      |> CImg.run()
    ```
  """
  def set(%Builder{}=builder, x, y \\ 0, z \\ 0, c \\ 0, val) do
    push_cmd(builder, {:set, x, y, z, c, val})
  end

  @doc """
  [grow] Booking to draw marker.
  
  ## Parameters
    * builder - %Builder{}
    * x,y - location. if all of them are integer, they mean
    actual coodinates. if all of them are float, they mean ratio of the image.
    * color - marker color
    * opts
      * size - size of marker (>= 0)
      * mark - shape of marker (only :circle is avalable now)
  """
  def draw_marker(%Builder{}=builder, x, y, color, opts \\ []) do
    _mark = Keyword.get(opts, :mark, :circle)
    size = Keyword.get(opts, :size, 1)

    color = color_code(color)
    cond do
      Enum.all?([x, y], &is_integer/1) ->
        push_cmd(builder, {:draw_marker, x, y, color, size})
      Enum.all?([x, y], &is_float/1) ->
      	push_cmd(builder, {:draw_marker_ratio, x, y, color, size})
    end
  end

  @doc """
  [grow] Booking to draw line in the image.

  ## Parameters
    * builder - %Builder{}
    * x1,y1,x2,y2 - ends of the line. if all of them are integer, they mean
    actual coodinates. if all of them are float, they mean ratio of the image.
    * color - line color
    * opts
      * thick: val - thick of line
      * opacity: val - opacity: 0.0-1.0
      * pattern: val - boundary line pattern: 32bit pattern

  ## Examples

    ```
    CImg.builder(img)
    |> CImg.draw_line(10, 10, 20, 30, :white, thick: 3)
    ```
  """
  def draw_line(%Builder{}=builder, x1, y1, x2, y2, color, opts \\ []) do
    thick   = Keyword.get(opts, :thick, 1)
    opacity = Keyword.get(opts, :opacity, 1.0)
    pattern = Keyword.get(opts, :pattern, 0xFFFFFFFF)

    color = color_code(color)

    cond do
      Enum.all?([x1, y1, x2, y2], &is_integer/1) ->
        push_cmd(builder, {:draw_line, x1, y1, x2, y2, color, thick, opacity, pattern})
      Enum.all?([x1, y1, x2, y2], &is_float/1) ->
      	push_cmd(builder, {:draw_line_ratio, x1, y1, x2, y2, color, thick, opacity, pattern})
    end
  end

  @doc """
  {grow} Booking to draw filled rectangle in the image.

  ## Parameters
    * builder - %Builder{}
    * 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

  ## Examples

    ```elixir
    CImg.builder(cimg)
    |> CImg.fill_rect(img, 50, 30, 100, 80, {255, 0, 0}, 0.3)
    |> CImg.display()

    CImg.builder(cimg)
    |> CImg.fill_rect(img, 0.2, 0.3, 0.6, 0.8, {0, 255, 0})
    |> CImg.display()
    ```
  """
  def fill_rect(%Builder{}=builder, x0, y0, x1, y1, color, opacity \\ 1.0, pattern \\ 0xFFFFFFFF) do
    cond do
      Enum.all?([x0, y0, x1, y1], &is_integer/1) ->
        push_cmd(builder, {:fill_rectangle, x0, y0, x1, y1, color, opacity, pattern})
      Enum.all?([x0, y0, x1, y1], fn x -> 0.0 <= x and x <= 1.0 end) ->
      	push_cmd(builder, {:fill_rectangle_ratio, x0, y0, x1, y1, color, opacity, pattern})
    end
  end


  @doc """
  {grow} Booking to draw rectangle in the image.

  ## Parameters

    * builder - %Builder{}
    * 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.builder(cimg)
    |> CImg.draw_rect(img, 50, 30, 100, 80, {255, 0, 0}, 0.3, 0xFF00FF00)
    |> CImg.display()

    CImg.builder(cimg)
    |> CImg.draw_rect(img, 0.2, 0.3, 0.6, 0.8, {0, 255, 0})
    |> CImg.display()
    ```
  """
  def draw_rect(%Builder{}=builder, x0, y0, x1, y1, color, opacity \\ 1.0, pattern \\ 0xFFFFFFFF) do
    cond do
      Enum.all?([x0, y0, x1, y1], &is_integer/1) ->
        push_cmd(builder, {:draw_rectangle, x0, y0, x1, y1, color, opacity, pattern})
      Enum.all?([x0, y0, x1, y1], fn x -> 0.0 <= x and x <= 1.0 end) ->
      	push_cmd(builder, {:draw_rectangle_ratio, x0, y0, x1, y1, color, opacity, pattern})
    end
  end


  @doc """
  {grow} Booking to draw filled circle in the image.

  ## Parameters

    * builder - %Builder{}
    * x0,y0 - circle center location
    * radius - circle radius
    * color - filling color
    * opacity - opacity: 0.0-1.0
    * pattern - boundary line pattern

  ## Examples

    ```elixir
    result = CImg.builder(cimg)
      |> CImg.fill_circle(imge, 100, 80, 40, {0, 0, 255}, 0xF0F0F0F0)
      |> CImg.run()
    ```
  """
  def fill_circle(%Builder{}=builder, x0, y0, radius, color, opacity \\ 1.0, pattern \\ 0xFFFFFFFF) do
    cond do
      Enum.all?([x0, y0, radius], &is_integer/1) ->
        push_cmd(builder, {:fill_circle, x0, y0, radius, color, opacity, pattern})
      Enum.all?([x0, y0, radius], fn x -> 0.0 <= x and x <= 1.0 end) ->
        push_cmd(builder, {:fill_circle_ratio, x0, y0, radius, color, opacity, pattern})
    end
  end


  @doc """
  {grow} Booking to draw filled circle in the image.

  ## Parameters

    * builder - %Builder{}
    * x0,y0 - circle center location
    * radius - circle radius
    * color - filling color
    * opacity - opacity: 0.0-1.0

  ## Examples

    ```elixir
    result = CImg.builder(cimg)
      |> CImg.draw_circle(imge, 100, 80, 40, {0, 0, 255})
      |> CImg.run()
    ```
  """
  @deprecated "Use fill_circle/7 instead"
  def draw_circle(%Builder{}=builder, x0, y0, radius, color, opacity \\ 1.0) do
    push_cmd(builder, {:fill_circle, x0, y0, radius, color, opacity, 0})
  end


  @doc """
  {grow} Booking to draw circle in the image.

  ## Parameters

    * builder - %Builder{}
    * x0,y0 - circle center location
    * radius - circle radius
    * color - boundary color
    * opacity - opacity: 0.0-1.0
    * pattern - boundary line pattern

  ## Examples

    ```Elixir
    result = CImg.builder(cimg)
      |> CImg.draw_circle(imge, 100, 80, 40, {0, 0, 255}, 0.3, 0xFFFFFFFF)
      |> CImg.run()
    ```
  """
  def draw_circle(%Builder{}=builder, x0, y0, radius, color, opacity, pattern) do
    push_cmd(builder, {:draw_circle, x0, y0, radius, color, opacity, pattern})
  end


  @doc """
  {grow} Booking to draw graph.

  ## Parameters

    * builder - %Builder{}
    * data - plot data (%CImg{})
    * color - RGB color tuple: {R,G,B} where 0 竕ヲ R,G,B 竕ヲ 255
    * opacity -
    * plot_type -
      * 0 = No plot.
      * 1 = Plot using segments.
      * 2 = Plot using cubic splines.
      * 3 = Plot with bars.
    * vertex_type -
      * 0 = No points.
      * 1 = Point.
      * 2 = Straight cross.
      * 3 = Diagonal cross.
      * 4 = Filled circle.
      * 5 = Outlined circle.
      * 6 = Square.
      * 7 = Diamond.
    * ymin, ymax - lower and upper bound of the y-range
    * pattern - line style

  ## Examples

    ```elixir
      CImg.builder(screen)
        |> CImg.draw_graph(CImg.get_crop(image, 0, y, 0, 0, width-1, y, 0, 0), red,   1.0, 1, 0, 255.0, 0.0)
        |> CImg.draw_graph(CImg.get_crop(image, 0, y, 0, 1, width-1, y, 0, 1), green, 1.0, 1, 0, 255.0, 0.0)
        |> CImg.draw_graph(CImg.get_crop(image, 0, y, 0, 2, width-1, y, 0, 2), blue,  1.0, 1, 0, 255.0, 0.0)
        |> CImg.display(draw_disp)
    ```
  """
  def draw_graph(%Builder{}=builder, data, color, opacity \\ 1.0, plot_type \\ 1, vertex_type \\ 1, ymin \\ 0.0, ymax \\ 0.0, pattern \\ 0xFFFFFFFF) do
    push_cmd(builder, {:draw_graph, data, color, opacity, plot_type, vertex_type, ymin, ymax, pattern})
  end


  @doc """
  {grow} Booking to move pixels on the image according to the mapping table.

  ## Parameters

    * img - %CImg{} or %Builder{}
    * 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]}]

    result = CImg.builder(:jpeg, "sample.jpg")
      |> CImg.draw_morph(map)
      |> CImg.run()
    ```
  """
  def draw_morph(img, mapping, cx \\ 0, cy \\ 0, cz \\ 0)

  def draw_morph(%CImg{}=cimg, mapping, cx, cy, cz) do
    builder(cimg)
    |> draw_morph(mapping, cx, cy, cz)
    |> run()
  end

  def draw_morph(%Builder{}=builder, mapping, cx, cy, cz) do
    push_cmd(builder, {:draw_morph, mapping, cx, cy, cz})
  end


  @doc """
  {grow} Draw text in th image.

  ## Parameters

    * builder - %Builder{}
    * x,y - position on the image where the text will begin to be drawn.
    * text - the text to be drawn.
    * font_height - font height in pixels.
    * fg_color - foreground color. choose one of the following:
            :white,:sliver,:gray,:black,:red,:maroon,:yellow,:olive,
            :lime,:green,:aqua,:teal,:blue,:navy,:fuchsia,:purple,
            :transparent
    * bg_color - background color.
    * opacity - opacity: 0.0..1.0.

  ## Examples

    ```elixir
    result = CImg.draw_text(builder, 10, 20, "Hello world!", 32, :white)
    ```
  """
  def draw_text(%Builder{}=builder, x, y, text, font_height, fg_color, bg_color \\ :transparent, opacity \\ 1.0) do
    push_cmd(builder, {:draw_text, x, y, text, fg_color, bg_color, opacity, font_height})
  end


  @doc """
  {crop} Display the image on the CImgDisplay object.

  ## Parameters

    * img - %CImg{} or %Builder{}
    * display - CImgDisplay object

  ## Examples

    ```elixir
    CImg.display(cimg)

    CImg.builder(cimg)
    |> CImg.draw_circle(100, 100, 50, {255, 0, 0})
    |> CImg.display()
    ```
  """
  def display(%CImg{}=img) do
    builder(img) |> display()
  end

  def display(%Builder{seed: seed, script: script}) do
    script = [{:display} | script]
    NIF.cimg_run([seed | Enum.reverse(script)])
  end


  @doc """
  {crop} Display the image on the CImgDisplay object.

  ## Parameters

    * cimg - %CImg{} or %Builder{}
    * display - CImgDisplay object

  ## Examples

    ```elixir
    disp = CImgDisplay.create(img, "Sample")
    CImg.display(img, disp)
    ```
  """
  def display(%CImg{}=img, disp) do
    builder(img) |> display(disp)
  end

  def display(%Builder{seed: seed, script: script}, disp) when not is_nil(seed) do
    script = [{:display_on, disp} | script]
    NIF.cimg_run([seed | Enum.reverse(script)])
  end

  @doc """
  {crop} Display the image on the Livebook.

  ## Parameters

    * img - %CImg{} or %Builder{}
    * mime_type - image file format: `:jpeg`, `:png`

  ## Examples

    ```elixir
    CImg.builder(:file, "sample.jpg")
    |> CImg.display_kino(:jpeg)
    ```
  """
  def display_kino(%CImg{}=img, mime_type) do
    builder(img) |> display_kino(mime_type)
  end

  def display_kino(%Builder{}=builder, mime_type) when mime_type in [:jpeg, :png] do
    %{
      __struct__: Kino.Image,
      content: to_binary(builder, mime_type),
      mime_type: "image/#{Atom.to_string(mime_type)}"
    }
  end
end