After plotting trees and mountains, let us plot something bigger, something we can use as a wallpaper. In this tutorial, we will use MATLAB to draw glowing light dots. Below is a glimpse of the final product (you can click the image to view the full version).
Setting Everything Up
We start by writing down the boilerplate code and defining the necessary parameters we will use later. The description of each parameter is given in the comments. Note that when setting up the figure, we explicitly set the background to black and hide the axes. It is crucial to hide the axes and specify the plot area to be the whole window. Otherwise the axes and paddings will appear in the image to which the Gaussian filtering will be applied, leading to undesired artifacts.
Note:
canvas_width
andcanvas_height
must be smaller than your screen width and height, respectively. MATLAB will attempt to resize the figure window if it cannot fit into your screen, leading to incorrect plots.
function light_dots
%LIGHT_DOTS Draws fancy light dots.
% define canvas size
canvas_width = 1440;
canvas_height = 810;
% define the parameters controlling the curve that the light dots follows
sin_period = 1.2 * canvas_width;
init_phase = unifrnd(0, pi*2);
amp = 0.15 * canvas_height;
% define the parameters controlling the size and randomness of the light
% dots
base_radius = floor(0.09 * canvas_width);
x_randomness = 10;
y_randomness = 50;
% set up the figure
% we want the backgroud to be black
hf = figure('Menubar', 'none', 'Color', 'black', ...
'Position', [0 0 canvas_width canvas_height], ...
'Visible', 'on');
% we want to hide the axes so the actual plot will occupy the whole window
axis('equal');
set(gca, 'Position', [0 0 1 1]);
axis([0 canvas_width 0 canvas_height]);
axis('off');
end
Drawing and Blending
A unblurred light dot can be straightforwardly drawn with the rectangle
function[1]. To make the light dots follow a curve, we just need to
generate their centroid coordinates around a sine curve. The tricky part is
getting the feeling of the depth (the blurry effect). This is done by alternating
between drawing light dots and applying the Gaussian filtering several times.
The whole procedure can be summarized in the following gif:
When translated into the MATLAB code, the whole procedure becomes:
% draw and blend
draw_layer_and_blend(6, 0.3, 0.6, base_radius, 0.3, 1, 50);
draw_layer_and_blend(32, 0.6, 0.9, base_radius, 0.2, 12, 40);
draw_layer_and_blend(16, 0.65, 1.0, base_radius / 3, 0.1, 1, 2);
draw_layer_and_blend(16, 0.70, 1.0, base_radius / 4, 0.1, 1, 1);
draw_layer_and_blend(24, 0.75, 1.0, base_radius / 6, 0.2, 1, 1);
draw_random_dots(48, 0.95, 0.8, base_radius / 12, 0.3, 1);
Implementing draw_random_dots
The implementation of this function is quite straightforward. We first generate
the centroid coordinates around a sine curve, as well as the radiuses, and the colors of
each dot. We then plot then using the rectangle
function. To achieve a smooth
color transition, we use the x coordinates of the dots to control the hue
values. The detailed implementation of draw_random_dots
is given below:
function draw_random_dots(n, alpha, sat, r, r_rnd, ratio)
% Inputs:
% n - Number of dots.
% alpha - Transparency of the dots.
% sat - Saturation of the dots.
% r - Base radius of the dots.
% r_rnd - Randomness in the radius.
% ratio - Width of the dots = radius / ratio;
% generates the centriod coordiates
x_coords = linspace(0, canvas_width, n)' + x_randomness * randn(n, 1);
curve = amp * sin(init_phase + x_coords / sin_period * 2 * pi);
y_coords = curve + canvas_height / 2 + y_randomness * randn(n, 1);
% generate RGB colors according to the x coordinates
% here we use the x coordinate to control the hue value
colors = hsv2rgb([min(max(x_coords / canvas_width, 0), 1) sat*ones(n, 1) ones(n, 1)]);
% generate radiuses
radiuses = r * (1.0 + min(max(r_rnd * randn(n, 1), -1), 1));
% draw the light dots
for ii = 1:n
w = radiuses(ii) / ratio;
xywh = [x_coords(ii) - w y_coords(ii) - radiuses(ii) 2 * w 2 * radiuses(ii)];
rectangle('Position', xywh, 'Curvature', [1 1], ...
'FaceColor', [colors(ii,:) 0.8*alpha], ...
'EdgeColor', [colors(ii,:) alpha], ...
'LineWidth', 1);
end
end
Implementing draw_layer_and_blend
Here is where the magic happens. We first draw a group of light dots by calling
draw_random_dots
. We then retrieve the current plot as an image
, and apply Gaussian filtering[2] to blur it.
Next, we replace the current plot with the blurred image
. If we call this function again, a new group of light
dots will be drawn over the blurred image . The new
plot will be converted to an new image , which will be
blurred and become . Hence, by calling
draw_layer_and_blend
repeatedly, we alternate between drawing light dots and
applying the Gaussian filtering. The detailed implementation of
draw_layer_and_blend
is given below:
function draw_layer_and_blend(n, alpha, sat, r, r_rnd, ratio, sigma)
% Inputs:
% n - Number of dots.
% alpha - Transparency of the dots.
% sat - Saturation of the dots.
% r - Base radius of the dots.
% r_rnd - Randomness in the radius.
% ratio - Width of the dots = radius / ratio;
% sigma - Standard deviation of the 2-D Gaussian smoothing kernel.
% draw the dots
draw_random_dots(n, alpha, sat, r, r_rnd, ratio);
% retrives the current plot as an image and apply the filter
frame = getframe(gca);
blurred_im = imgaussfilt(frame2im(frame), sigma);
im_size = size(blurred_im);
% replace the plot with the blurred image
clf(hf);
image(linspace(0, canvas_width, im_size(1)), ...
linspace(0, canvas_height, im_size(2)), blurred_im);
% make sure the axes are hidden and the plot occupies the whole window
axis('equal');
axis([0 canvas_width 0 canvas_height]);
set(gca, 'Position', [0 0 1 1]);
axis('off');
end
Exporting
To export our final product as a high DPI image, we can simply use the print
function[3]:
% save a high DPI version of our plot so you can use it as a wallpaper!
set(hf, 'PaperPositionMode', 'auto');
print(hf, 'light_dots.png', '-dpng', '-r300');
Putting Everything Together
Now we can put everything together and finalize our function:
function light_dots
%LIGHT_DOTS Draws fancy light dots.
% define canvas size
canvas_width = 1440;
canvas_height = 810;
% define the parameters controlling the curve that the light dots follows
sin_period = 1.2 * canvas_width;
init_phase = unifrnd(0, pi*2);
amp = 0.15 * canvas_height;
% define the parameters controlling the size and randomness of the light
% dots
base_radius = floor(0.09 * canvas_width);
x_randomness = 10;
y_randomness = 50;
% set up the figure
% we want the backgroud to be black
hf = figure('Menubar', 'none', 'Color', 'black', ...
'Position', [0 0 canvas_width canvas_height], ...
'Visible', 'on');
% we want to hide the axes so the actual plot will occupy the whole window
axis('equal');
set(gca, 'Position', [0 0 1 1]);
axis([0 canvas_width 0 canvas_height]);
axis('off');
% draw and blend
draw_layer_and_blend(6, 0.3, 0.6, base_radius, 0.3, 1, 50);
draw_layer_and_blend(32, 0.6, 0.9, base_radius, 0.2, 12, 40);
draw_layer_and_blend(16, 0.65, 1.0, base_radius / 3, 0.1, 1, 2);
draw_layer_and_blend(16, 0.70, 1.0, base_radius / 4, 0.1, 1, 1);
draw_layer_and_blend(24, 0.75, 1.0, base_radius / 6, 0.2, 1, 1);
draw_random_dots(48, 0.95, 0.8, base_radius / 12, 0.3, 1);
% save a high DPI version of our plot so you can use it as a wallpaper!
set(hf, 'PaperPositionMode', 'auto');
print(hf, 'light_dots.png', '-dpng', '-r300');
% nested functions
% ================
function draw_layer_and_blend(n, alpha, sat, r, r_rnd, ratio, sigma)
% Inputs:
% n - Number of dots.
% alpha - Transparency of the dots.
% sat - Saturation of the dots.
% r - Base radius of the dots.
% r_rnd - Randomness in the radius.
% ratio - Width of the dots = radius / ratio;
% sigma - Standard deviation of the 2-D Gaussian smoothing kernel.
% draw the dots
draw_random_dots(n, alpha, sat, r, r_rnd, ratio);
% retrives the current plot as an image and apply the filter
frame = getframe(gca);
blurred_im = imgaussfilt(frame2im(frame), sigma);
im_size = size(blurred_im);
% replace the plot with the blurred image
clf(hf);
image(linspace(0, canvas_width, im_size(1)), ...
linspace(0, canvas_height, im_size(2)), blurred_im);
% make sure the axes are hidden and the plot occupies the whole window
axis('equal');
axis([0 canvas_width 0 canvas_height]);
set(gca, 'Position', [0 0 1 1]);
axis('off');
end
function draw_random_dots(n, alpha, sat, r, r_rnd, ratio)
% Inputs:
% n - Number of dots.
% alpha - Transparency of the dots.
% sat - Saturation of the dots.
% r - Base radius of the dots.
% r_rnd - Randomness in the radius.
% ratio - Width of the dots = radius / ratio;
% generates the centriod coordiates
x_coords = linspace(0, canvas_width, n)' + x_randomness * randn(n, 1);
curve = amp * sin(init_phase + x_coords / sin_period * 2 * pi);
y_coords = curve + canvas_height / 2 + y_randomness * randn(n, 1);
% generate RGB colors according to the x coordinates
% here we use the x coordinate to control the hue value
colors = hsv2rgb([min(max(x_coords / canvas_width, 0), 1) sat*ones(n, 1) ones(n, 1)]);
% generate radiuses
radiuses = r * (1.0 + min(max(r_rnd * randn(n, 1), -1), 1));
% draw the light dots
for ii = 1:n
w = radiuses(ii) / ratio;
xywh = [x_coords(ii) - w y_coords(ii) - radiuses(ii) 2 * w 2 * radiuses(ii)];
rectangle('Position', xywh, 'Curvature', [1 1], ...
'FaceColor', [colors(ii,:) 0.8*alpha], ...
'EdgeColor', [colors(ii,:) alpha], ...
'LineWidth', 1);
end
end
end