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: " Nx.from_binary({:u, 8}) # convert Nx.Tensor to CImg image iex> img1 = Nx.to_binary(tensor) |>CImg.from_bin(2448, 3264, 1, 3, " BGR. ## Examples ```Elixir img = CImg.load("sample.jpg") bin1 = CImg.to_flat(img, [{dtype: " BGR. ## Examples ```Elixir img = CImg.load("sample.jpg") npy1 = img |> CImg.to_npy() npy2 = img |> CImg.to_npy([{dtype: " 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: " 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: " 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 def get_yuv(cimg) do with {:ok, yuv} <- NIF.cimg_get_yuv(cimg), do: %CImg{handle: yuv} 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, :line, :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