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 ### 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 iex> 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 """ 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. - " bin = TflInterp.get_output_tensor(__MODULE__, 0) iex> img = CImg.create_from_bin(bin, 300, 300, 1, 3, " img = CImg.load("sample.jpg") ``` """ def load(fname) do with {:ok, h} <- NIF.cimg_load(fname), do: %CImg{handle: h} end @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 iex> bin = File.read!("sample.jpg") iex> 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 """ 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 iex> 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 ``` iex> 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_dup(cimg), do: %CImg{handle: h} end @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: " BGR. ## Examples ```Elixir iex> img = CImg.load("sample.jpg") iex> bin1 = CImg.to_flat(img, [{dtype: " bin2 = CImg.to_flat(img, dtype: " img = CImg.load("sample.jpg") iex> res = CImg.get_resize(img, {300,300}, :ul) ``` """ def get_resize(cimg, {x, y}, align \\ :none, fill \\ 0) 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 @doc """ """ 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, 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 iex> 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, axis) when axis in [:x, :y] do dup = CImg.dup(cimg) NIF.cimg_mirror(dup, axis) 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 iex> gray = CImg.get_gray(img, 1) # get inverted gray image ``` """ def get_gray(cimg, opt_pn \\ 0) do with {:ok, gray} <- NIF.cimg_get_gray(cimg, opt_pn), do: %CImg{handle: gray} end @doc """ ## 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 """ ## Parameters * cimg - image object %CImg{} to save. """ def map(cimg, lut, boundary \\ 0) do with {:ok, h} <- NIF.cimg_map(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 iex> 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 iex> 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 iex> 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 iex> size = CImg.sizh(imge) ``` """ defdelegate size(cimg), to: NIF, as: :cimg_size @doc """ [mut] Thresholding the image. ## Parameters * img - %CImg{} or %Builder{} object. * val - threshold value * soft - * strict - ## Examples ```Elixir iex> 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, val, soft, strict) do dup = CImg.dup(cimg) NIF.cimg_threshold(dup, val, soft, strict) end @doc """ ## Parameters * cimg - image object %CImg{} to save. """ 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_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 iex> 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 iex> CImg.draw_rect(img, 50, 30, 100, 80, {255, 0, 0}, 0.3, 0xFF00FF00) iex> 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_rect(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_rect(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 iex> 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(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 iex> 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 iex> disp = CImgDisplay.create(img, "Sample") iex> CImg.display(imge, disp) ``` """ defdelegate display(cimg, disp), to: NIF, as: :cimg_display end