Message Boards Message Boards


Digital Circlism: arrangement of circles over an image

Posted 11 months ago
8 Replies
23 Total Likes

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


Let' s see how to generate pictures like:


Initially, I got the idea from this site:

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

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.


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 = 
   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]

filtered image

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.

   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.

   MapThread[{imgColor[#1], Disk[#1, #2]} &, result]}]]


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.

   MapThread[{colFunc["Aquamarine", #1], Disk[#1, #2]} &, result]}]]


Why only use circles ?

   MapThread[{colFunc["Aquamarine", #1], RegularPolygon[#1, #2, 6]} &,

enter image description here

8 Replies

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.


And it is finally a resource function !

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

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.

I took a similar approach with TessellateGraphics:

though I never thought of using EdgeDetect to apply it images that were not already masks.

enter image description here

Thank you.

enter image description here -- you have earned Featured Contributor Badge enter image description here Your exceptional post has been selected for our editorial column Staff Picks and Your Profile is now distinguished by a Featured Contributor Badge and is displayed on the Featured Contributor Board. Thank you!

Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
or Discard

Group Abstract Group Abstract