Autonomous agents applied to paintings

I’ve recently discovered the work of Leonoardo Solas and I’m very interested in his Mona Rayadas drawings, here is an example:

I’ve already used autonomous agents in the past, but I’m struggling to write an operating system that would preserve the global lines of the drawings. I’ve tried multiples systems, defining the speed of each agents by the color of the pixel it is on, or the color of its neigbhours, but I never get a nice result.

Do you have any idea on how I could do that?

1 Like


I never tested this but maybe it’s possible to create a vector field that is stronger where there’s darker pixels on the image, or darker pixels are more attractive than others.

With this method, you could get a better result with a predrawn painting with clean dark lines…

1 Like


Welcome to forum.

Have you tried momentum or anything similar? You could think painting or colors as 3D landscape and your agents are balls rolling down the slopes. This is identical to finding minimum or gradient descent. With no momentum ball would stop at the first local minimum it finds, i.e. locally smallest values. Momentum would keep ball moving uphill and over the crescent to next downhill.
In this case you wouldn’t be seeking minimum value. You would keep ball rolling until it bumps into line drawn by another ball, it gets out of canvas or momentum is spent and it’s line fades away.

This is such an interesting idea that I might try it myself too

1 Like

Here is my take on this :

I start with the original painting then I draw the main lines in black :

Then the idea is the following :

  • Create a 2D array where we store the brightness value of each pixel of the black and white image (we take the inverse so black lines are more attractive eg 255)

  • Then we randomly create particles where there’s bright pixels on the original image

  • Each frame, we update the particles position. We go through every pixel on the screen, construct the vector from the particle to the pixel and then add that vector multiplied by the brightness and the inverse of the distance at that pixel (stored in the 2D array). The result is that particles are more attracted to black portions of the image that are closer to the particle.
    In addition to that, each particle have a sphere of influence defined by a radius to control how much pixels it can see.

The results are quite nice :slight_smile:

Left : particles are created on lighter portions of the painting
Right : particles are created from darker portions of the painting


And the code :

PImage mona, mona_lines;

// 2D array holding brightness values for each pixel
float[][] field;

// Array of particles
ArrayList<PVector> particles;

// Shere of influence of particles
int treshold = 200;

void setup() {
  size(600, 894);

  // Load both images
  mona = loadImage("mona.jpeg");
  mona_lines = loadImage("mona_lines.jpeg");

  // Initialize field and particles
  field = new float[width][height];
  particles = new ArrayList<PVector>();

  // Compute brightness for each pixel and create particles
  for (int x=0; x<width; x++) {
    for (int y=0; y<height; y++) {
      int loc = x + y*width;
      float brightness = brightness(mona_lines.pixels[loc]);
      // The brightness is inverted and converted to a range of [0, 1]
      field[x][y] = (255 - brightness)/255;

      // Check if the brightness of the pixel on the painting is inferior to a certain amount
      // We put randomly a particle 1/100 of the time
      if (brightness(mona.pixels[loc]) > 100 && random(1) < 0.01) {
        particles.add(new PVector(x, y));
  // If you want to display the black lines
  //image(mona_lines, 0, 0);

// Function to update the particles and draw them
void updateParticles() {
  stroke(0, 20);

  for (PVector particle : particles) {
    point(particle.x, particle.y);
    // Initialize the speed for that particle
    // it's going to be the weighted sum of each distance to dark pixels
    PVector speed = new PVector(0, 0);
    // We go through every pixels 1/5 otherwise it's too slow
    for (int x=0; x<width; x+=5) {
      for (int y=0; y<height; y+=5) {
        // Get the distance vector
        PVector distance = new PVector(x - particle.x, y - particle.y);
        // Get the value of that distance
        float dist = distance.mag();
        // If the pixel is in the influence sphere of the particle
        if (dist < treshold) {
          // We add the distance multiplied by the inverse of the dist
          // so closer pixels produce stronger force
          distance.normalize().mult((1.0/dist) * field[x][y]);
    // We apply the speed to the particle position

void draw() {