# Digital Circlism: arrangement of circles over an image

Posted 11 months ago
3849 Views
|
8 Replies
|
23 Total Likes
|

MODERATOR NOTE: this work is now a resource function: https://resources.wolframcloud.com/FunctionRepository/resources/DigitalCirclism/
full code and functions' definitions can be found in the attached notebook at the end of the post.

## Introduction

Let' s see how to generate pictures like:

Initially, I got the idea from this site:

https://github.com/arihant-001/Circlism

But, I was not able to reproduce the results and my first algorithm was too slow. Then, I discovered this very interesting discussion:

https://mathematica.stackexchange.com/questions/40334/generating-visually-pleasing-circle-packs

But, it was not exactly like the first link.

After some experiments, and by mixing the ideas from the 2 previous links and some ideas of mine, I was able to generate the kind of pictures I wanted.

## Preprocessing

First, we need to extract the edges of the picture. I have found that the following steps are generally giving a good result.

I am resizing the image so that it is not too big. Otherwise the algorithm will be very slow. There are some optimizations I could do.

srcImage =
ColorConvert[
ImageResize[
RemoveAlphaChannel[ExampleData[{"TestImage", "Girl3"}], White],
400], "RGB"]


I am applying a MeanShiftFilter. But you could try with other processing to reduce the noise. There is no rule. It just has to look good at the end.

segmented = MeanShiftFilter[srcImage, 13, 0.1]


Finally, the edges are computed.

edges = ColorNegate@EdgeDetect[segmented]


Now, the first key idea : the Euclidean Distance Transform will give the distance of a point to the closest edge. When you have this distance, then you can draw a circle which will not cross any edge. Since distances will be > 1.0, you need an ImageAdjust if you want to display the result. I do it only as an illustration since this step is part of the main function.

DistanceTransform[edges] // ImageAdjust


## Generation of the circles

The function below will try to place all the circles starting from the biggest to the smallest. The different scales to be considered are given by the list d.

d = {50, 20, 10, 5, 3, 2};


So, for instance, during the first pass, we will consider only the points with with an euclidean distance > 50.0. This euclidean distance is the radius of the circle to place at this point.

We may not be able to place all the circles because of overlap with the circles already in place. So, when a circle cannot be placed, it is resized during the next phase. All circles with radius > 50.0 will be resized to radius 50.0. If they still can't get a place, then during the next phase they will be resized to 20 ...

The overlap detection is the part to be optimized : I am comparing the circle to all of the already placed circles. Some partitioning of the plane may be useful to make this comparison quicker and avoid testing circles which are too far. In this version, it is the biggest limiting factor is you want to process big pictures.

result = circlism[d, edges];


The circlism function is returning a list of {position,radius}.

We use this list to draw the final pictures.

Two options are used by this function. "TimeConstraint" is set by default to 3 minutes. The image generation will stop after 3 minutes so you may not have all the circles.

"Pad" is set to 0.0 and controlling the padding between the circles.

## Picture Generation

Many variations are possible.

In this first picture, we just overlay the disks on top of the picture. The color of the disk is the color of the picture pixel at the center of the disk.

Show[srcImage,
Graphics[{EdgeForm[Thickness[0.001]],
MapThread[{imgColor[#1], Opacity[0.9], Disk[#1, #2]} &, result]}]]


In this variation, we use an uniform background. I am generating this background with an ImageApply because I have observed that the Show is clipping the Graphics as it should when an image is used.

Show[uniformBackground[0.8],
Graphics[{EdgeForm[],


In this other variation, we use the colFunc function which is using a background mask. For circles in the background, a random color from a palette is used.

Show[srcImage,
Graphics[{EdgeForm[Thin],
MapThread[{colFunc["Aquamarine", #1], Disk[#1, #2]} &, result]}]]


Why only use circles ?

Show[srcImage,
Graphics[{EdgeForm[Thin],
MapThread[{colFunc["Aquamarine", #1], RegularPolygon[#1, #2, 6]} &,
result]}]]


Attachments:
8 Replies
Sort By:
Posted 9 months ago
 Congratulations! Your post was highlighted on the Wolfram's official social media channels. Thank you for your contribution. We are looking forward to your future posts.
Posted 10 months ago
 Nice
Posted 10 months ago
 And it is finally a resource function !
Posted 11 months ago
 Its pretty easy to submit items. Use the menu New/RepositoryItem/Function Repostory and just fill in the notebook.If you can see an syntax extension that don't create backward compatibility, feel free to extend the source code of mine by editing https://www.wolframcloud.com/files/4793f5f4-937f-49ff-852b-633e518e2b5c?contentDisposition=attachment
Posted 11 months ago
 Great. I'll play with your function.And perhaps I'll push this work to the function repository when I have learnt how to do it.
Posted 11 months ago
 I took a similar approach with TessellateGraphics:https://resources.wolframcloud.com/FunctionRepository/resources/TessellateGraphicsthough I never thought of using EdgeDetect to apply it images that were not already masks.