# Creating Rainbow 🌈
```elixir
Mix.install([
{:vix, "~> 0.22.0"},
{:kino, "~> 0.10.0"}
])
```
## Introduction
Libvips provides over 300 image processing operations so getting an
intuition for what is possible, and how to combine the primitive
image processing operations, can be challenging.
In this notebook we look into some libvips core operations by
working towards a simple goal — generate a rainbow 🌈.
The rainbow should have a half-circular arch and a smooth blend
between the colors.
## Generating the colors
First, let's look into generating the rainbow colors. We need to
generate the whole spectrum with the smooth blend between them.
libvips provides the `buildlut` function, which generates pointwise
intermediate values between the provided points. `lut` here means
**L**ook **U**p **T**able. `buildlut` takes a 1D matrix where each value
represents different points in the format `[position, bands]`.
`buildlut` will build a single-band, one-pixel-height image with a
smooth pointwise transition between the points. If you pass `[0, 0]`
(at position zero pixel value is zero) `[255, 255]` (at position 255
value is 255), the buildlut will return an image with pixels `0` at
position 0, `1` at position 1, `2` at position 2 etc, ending with
`255` at position 255. We can think of it as a range from 0 to 255, or `0..255//1`.
```elixir
alias Vix.Vips.Image
alias Vix.Vips.Operation
# buildlut needs matrix image.
# we can create matrix-image using `Image.new_matrix_from_array` which takes
# list and return a vips-image which can be passed to `buildlut`
{:ok, mat} = Image.new_matrix_from_array(2, 2, [[0, 0], [255, 255]])
gradient = Operation.buildlut!(mat)
```
Switch to attributes tab to see more details about the image.
We can see the gradient clearly if we increase the image height
```elixir
Operation.resize!(gradient, 3, vscale: 50)
```
`buildlut` accepts multiple bands as well. So we can generate gradients in grayscale or color.
```elixir
defmodule Vix.KinoUtils do
# Utility to read color and parse hex string into list of
# 8-bit integers
def read_colors do
start_color = Kino.Input.color("Start", default: "#DDDD55")
end_color = Kino.Input.color("End", default: "#FF2200")
[start_color, end_color]
|> Kino.Layout.grid(columns: 6)
|> Kino.render()
start_color = read(start_color)
end_color = read(end_color)
{start_color, end_color}
end
defp read(input) do
"#" <> color = Kino.Input.read(input)
for <<hex::binary-size(2) <- color>> do
String.to_integer(hex, 16)
end
end
end
# read user input
{start_color, end_color} = Vix.KinoUtils.read_colors()
{:ok, mat} =
Image.new_matrix_from_array(4, 2, [
[0 | start_color],
[255 | end_color]
])
mat
|> Operation.buildlut!()
|> Operation.cast!(:VIPS_FORMAT_UCHAR)
|> Operation.resize!(3, vscale: 50)
# change the `Start` and `End` colors and evaluate
```
To generate the rainbow colors, the pixel values in RGB colour space
will be `[255, 0, 0] (red), [255, 165, 0] (orange) ... [143, 0, 255]
(violet)`. We could generate these values but it is inconvenient to
juggle all 3 bands.
Generating the colors in HSV color space we need `[0, 255, 255] (red),
[24, 255, 255] (orange) ... [212, 255, 255] (violet)`. Which is much
nicer to work with, since we only need to change one value.
```elixir
{:ok, mat} =
Image.new_matrix_from_array(4, 2, [
# position 0 - [0, 255, 255]
[0, 0, 255, 255],
# position 255 - [255, 255, 255]
[255, 255, 255, 255]
])
gradient = Operation.buildlut!(mat)
# by default the colour space won't be HSV.
# We make a shallow copy and set the colorspace to HSV.
# While also setting band format to `unsigned char`
rainbow_colors =
Operation.copy!(gradient,
band_format: :VIPS_FORMAT_UCHAR,
interpretation: :VIPS_INTERPRETATION_HSV
)
# resize to make it visible
Operation.resize!(rainbow_colors, 3, vscale: 50)
# notice that the output spectrum starts from RED and ends with RED.
# this is slightly incorrect, since rainbow ends with violet. We'll fix this later
```
## Generating the Arch
How do we generate the half circular arch? There are several way to
approach this but we are going to use complex band format and polar
coordinates.
### Complex Numbers
In a traditional coordinate system, a pixel’s position is represented
by two values: its x- and y-coordinates. The x-coordinate indicates the
horizontal position along the width of the plane, while the y-coordinate
indicates the vertical position along the height. The two values taken
together describe a point's location on a two-dimensional (2D) plane.
Now consider an operator, like the Fourier transformation. It takes a
coordinate pair and may output a complex number. How would we handle that?
It turns out that even though a complex number is a single value, it still
consists of two components: a **Real** number and an **Imaginary** number.
Like the x-coordinate above, the **Real** number component of a complex number
indicates the horizontal position along the width of the plane. An **Imaginary**
number technically represents how many units, i, a point is located off a 2D plane,
but for our purposes, we treat it the way we would a y-coordinate.
<!-- livebook:{"break_markdown":true} -->
There are two different ways to locate a point on 2D plane
* Cartesian coordinate system
* Polar coordinate system
**Cartesian coordinate System**
Here real part represent x coordinate and imaginary part represents y coordinate.

`a` and `b` is used to locate the point `z`
<!-- livebook:{"break_markdown":true} -->
**Polar System**
Here real part represents distance from the origin and imaginary part represents angle

Angle `φ` and distance `r` is used to locate point `z`
<!-- livebook:{"break_markdown":true} -->
**But why do we need complex number?**
Because it makes certain operations simple. Operations which operate
on plane rather than axis. For example, to draw a circle on a polar
plane we only need to vary angle keeping the distance constant.
Let's do an example to see how libvips operations work on the complex
number plane. First, let's create an image that has an origin in the center of
the image. It makes it easy to understand what is happening.
```elixir
use Vix.Operator
width = height = 255
# create 200 x 200 matrix where each pixel represent its own position.
# pixel at left-top will be `{0, 0}` (black)
# pixel right-bottom will be `{255, 255}` (white)
xy = Operation.xyz!(width, height)
# Display how axis values are chaining
Kino.Layout.grid([Kino.Text.new("X-Axis"), Kino.Text.new("Y-Axis"), xy[0], xy[1]], columns: 2)
|> Kino.render()
# move origin to center of the image
xy = (xy - [width / 2, height / 2]) * 2
# Display how axis values are changing after moving the origin.
# Notice that origin black pixel starts at top-left before
# and at center after moving the origin
Kino.Layout.grid(
[Kino.Text.new("Centered X-Axis"), Kino.Text.new("Centered Y-Axis"), xy[0], xy[1]],
columns: 2
)
```
Now that we have an image, we can see how it looks in polar plane
```elixir
# convert band format to complex number format.
# we specify that read 2 bands to form a single complx band.
# x axis becomes the real part of the complex number
# y axis becomes the Imaginary part of the complex number
complex_xy = Operation.copy!(xy, format: :VIPS_FORMAT_COMPLEX, bands: 1)
# change the complex number plane to the polar plane.
# vips reads a complex number and converts it to a value on the polar plane for all pixels.
# real part will be distance from the origin
# imaginary part will be the angle in degree
polar_xy = Operation.complex!(complex_xy, :VIPS_OPERATION_COMPLEX_POLAR)
# convert the complex number back to 2-band float image.
# x axis is the real part of the complex number
# y asix is the imaginary part of the complex number
xy = Operation.copy!(polar_xy, format: :VIPS_FORMAT_FLOAT, bands: 2)
# angle will be in degree (from 0 to 360), scale it back to 255
xy = xy * [1, height / 360]
Kino.Layout.grid([Kino.Text.new("X-Axis"), Kino.Text.new("Y-Axis"), xy[0], xy[1]], columns: 2)
```
As you can see the x-axis is the real part of the complex number, which is
distance from the origin, and the y-axis is the imaginary part, which is the angle.
This is much easier to understand if we draw a few lines on the input
image. and see how it changes in the polar plane
```elixir
defmodule ComplexOps do
def to_polar(img, background \\ [0, 0, 0]) do
%{width: width, height: height} = Image.headers(img)
xy = Operation.xyz!(width, height)
xy = xy - [width / 2, height / 2]
scale = min(height, width) / width
xy = xy * 2 / scale
xy =
xy
|> Operation.copy!(format: :VIPS_FORMAT_COMPLEX, bands: 1)
|> Operation.complex!(:VIPS_OPERATION_COMPLEX_POLAR)
|> Operation.copy!(format: :VIPS_FORMAT_FLOAT, bands: 2)
xy = xy * [1, height / 360]
# mapim takes an input and a `map` and generates an output image
# where input image pixels are moved based on map.
#
# [new_x, new_y] = map[x, y]
# out[x, y] = img[new_x, new_y]
#
# mapim is to rotate, displace, distort, any type of spatial operations.
# where the pixel value (color) remain same but the position is changed.
Operation.mapim!(img, xy, background: background)
end
end
x_line = Operation.black!(10, height) + 255
y_line = Operation.black!(width, 10) + 125
# create a black image and draw 2 lines
# an x axis at 50
# a y axis at 50
img =
Operation.black!(width, height)
|> Operation.insert!(x_line, 50, 0)
|> Operation.insert!(y_line, 0, 50)
# convert img to polar
out = ComplexOps.to_polar(img)
Kino.Layout.grid([Kino.Text.new("Input"), Kino.Text.new("Output"), img, out], columns: 2)
```
A line on the x axis becomes a circle on the polar plane and a line on
the y axis becomes a line from the origin.
So to draw a rainbow circle, we just draw a rainbow line on the
x-axis and convert that to the polar plane!
```elixir
rainbow_colors
|> Operation.resize!(100 / 255, vscale: 400)
|> Operation.embed!(150, 0, 600, 400)
# wrap moves the image to origin
|> Operation.wrap!()
|> ComplexOps.to_polar()
|> dbg()
# select the stage on the output to see how image transforms
:ok
```
All that is left now is to make a few final adjustments to make it pretty
```elixir
# create colors from violet to red instead of red to red
{:ok, mat} = Image.new_matrix_from_array(4, 2, [[0, 220, 255, 255], [255, 0, 255, 255]])
rainbow_colors =
Operation.copy!(Operation.buildlut!(mat),
band_format: :VIPS_FORMAT_UCHAR,
interpretation: :VIPS_INTERPRETATION_HSV
)
sky_color = [135, 100, 255]
rainbow_colors
|> Operation.resize!(100 / 255, vscale: 500)
|> Operation.embed!(50, 0, 500, 500, background: sky_color)
|> Operation.wrap!()
|> ComplexOps.to_polar(sky_color)
# take only top half of the image
|> Operation.copy!(height: 250, width: 500)
```