Bloom flowers with the Circle Packing.

The original image.

The original image 'Boats and Body of Water" from Pixabay.

Bloom flowers with the Circle Packing.

It's an image manipulation code that translates the original image to the garden of the wildflowers. This code is written in Processing programming language.

The edge detection makes a result like this. I tried to pick the points on the mesh, it worked to get a nice result.

The result of edge detection.

I plotted the points on the mesh. And I omitted some points randomly on purpose then I got the various size of flowers like this.
The various size of flowers.

It's a result of edge detection and picking points on the mesh randomly.
Full of flowers.

I used the pixelated background to imitate the original image.
The pixelated background.

And it's a bonus. 😉
It's just an ornament.

You can use your own photo.

You can put your photo at 'data/your_photo.jpg' and run. Or you can change the original image file name at here.

  PImage img = loadImage("your_photo.jpg");


Reference.

The other image manipulation coding code with the Circle Packing method. 'PacKitty'

The other creative coding code with the Circle Packing method. 'Spin the Wheel'



The example code of Processing.

Please feel free to use this example code. To see other works based on my code is my pleasure. And my honor.

This code does not display any images on the screen but generates image files in frames directory.


/**
 * Wildflower.
 * image manipulation code with circle packing method.
 *
 * @author @deconbatch
 * @version 0.1
 * @license GPL Version 3 http://www.gnu.org/licenses/
 * Processing 3.5.3
 * 2021.02.26
 */

void setup() {
  size(1080, 1080);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();
}

void draw() {

  int caseWidth  = 30;
  int baseCanvas = width - caseWidth * 2;
  int meshDiv    = floor(min(width, height) / 20.0);
  int edgeMax    = floor(width * height / meshDiv);

  PImage img = loadImage("your_photo.jpg");
  float rateSize = baseCanvas * 1.0 / max(img.width, img.height);
  img.resize(floor(img.width * rateSize), floor(img.height * rateSize));
  println(int(img.width));
  println(int(img.height));

  ArrayList<Flower> edges = detectEdge(img, floor(meshDiv * 0.2), edgeMax);
  ArrayList<Flower> denseMeshs  = getMesh(img, meshDiv, 0.8, 40.0);
  ArrayList<Flower> sparseMeshs = getMesh(img, meshDiv, 0.5, 200.0);
  ArrayList<Flower> flowers;
  
  translate((width - img.width) / 2, (height - img.height) / 2);

  // like a original image : edge only
  drawBackground(img, floor(meshDiv * 0.5));
  flowers = circlePacking(edges, 2);
  for (Flower f : flowers) {
    f.draw();
  }
  casing(caseWidth, img.width, img.height);
  saveFrame("frames/wf0001.png");

  // little ornamental : edge + dense mesh
  drawBackground(img, floor(meshDiv * 0.5));
  flowers = circlePacking(edges, 1);
  denseMeshs.addAll(flowers);
  flowers = circlePacking(denseMeshs, 1);
  for (Flower f : flowers) {
    f.draw();
  }
  casing(caseWidth, img.width, img.height);
  saveFrame("frames/wf0002.png");
  
  // just a ornament :sparse mesh only
  drawBackground(img, floor(meshDiv * 0.25));
  flowers = circlePacking(sparseMeshs, 1);
  for (Flower f : flowers) {
    f.draw();
  }    
  casing(caseWidth, img.width, img.height);
  saveFrame("frames/wf0003.png");
  
  exit();
  
}

/**
 * drawBackground : draw mesh background
 * @param _img  : original image to get color.
 * @param _step : mesh size.
 */
private void drawBackground(PImage _img, int _step) {

  int   start = floor(_step * 0.5);
  _img.loadPixels();

  rectMode(CENTER);
  stroke(0.0, 0.0, 90.0, 30.0);
  strokeWeight(2.0);
  background(0.0, 0.0, 90.0, 100.0);
  for (int fX = start; fX < _img.width; fX += _step) {
    for (int fY = start; fY < _img.height; fY += _step) {
      int pixIndex = floor(fY * _img.width + fX);
      fill(
           hue(_img.pixels[pixIndex]),
           map(saturation(_img.pixels[pixIndex]), 0.0, 100.0, 00.0, 40.0),
           map(brightness(_img.pixels[pixIndex]), 0.0, 100.0, 60.0, 90.0),
           30.0
           );
      rect(fX, fY, _step, _step);
    }
  }
}

/**
 * getMesh : locate Flowers on mesh with some randomness.
 * @param _img     : original image to get color.
 * @param _step    : mesh size.
 * @param _addRate : get rate, 0.0 : no get, 1.0 get whole.
 * @param _satMax  : reduce the maximum value of the saturation of the original image to this value.
 * @return ArrayList<Flower> : holds Flowers.
 */
private ArrayList<Flower> getMesh(PImage _img, int _step, float _addRate, float _satMax) {

  ArrayList<Flower> fs = new ArrayList<Flower>();
  int start = floor(_step * 0.5);
  _img.loadPixels();

  for (int fX = start; fX < _img.width; fX += _step) {
    for (int fY = start; fY < _img.height; fY += _step) {
      if (random(1.0) < _addRate) {
        // add flower bud
        int pixIndex = floor(fY * _img.width + fX);
        fs.add(new Flower(
                          fX,
                          fY,
                          hue(_img.pixels[pixIndex]),
                          map(saturation(_img.pixels[pixIndex]), 0.0, 100.0, 0.0, _satMax),
                          brightness(_img.pixels[pixIndex])
                          ));
      }
    }
  }
  return fs;
}

/**
 * detectEdge : detect edges on the mesh of photo image.
 * @param _img     : detect edges of this image.
 * @param _step    : mesh size.
 * @param _edgeNum : return edge points number.
 * @return ArrayList<Flower> : holds Flowers.
 */
private ArrayList<Flower> detectEdge(PImage _img, int _step, int _edgeNum) {

  ArrayList<Flower> fs = new ArrayList<Flower>();
  ArrayList<PVector> edges = new ArrayList<PVector>();

  _img.loadPixels();
  for (int idxW = 1; idxW < _img.width - 1; idxW += _step) {  
    for (int idxH = 1; idxH < _img.height - 1; idxH += _step) {

      int pixIndex = idxH * _img.width + idxW;

      // saturation difference
      float satCenter = saturation(_img.pixels[pixIndex]);
      float satNorth  = saturation(_img.pixels[pixIndex - _img.width]);
      float satWest   = saturation(_img.pixels[pixIndex - 1]);
      float satEast   = saturation(_img.pixels[pixIndex + 1]);
      float satSouth  = saturation(_img.pixels[pixIndex + _img.width]);
      float lapSat = pow(
                         - satCenter * 4.0
                         + satNorth
                         + satWest
                         + satSouth
                         + satEast
                         , 4);

      // brightness difference
      float briCenter = brightness(_img.pixels[pixIndex]);
      float briNorth  = brightness(_img.pixels[pixIndex - _img.width]);
      float briWest   = brightness(_img.pixels[pixIndex - 1]);
      float briEast   = brightness(_img.pixels[pixIndex + 1]);
      float briSouth  = brightness(_img.pixels[pixIndex + _img.width]);
      float lapBri = pow(
                         - briCenter * 4.0
                         + briNorth
                         + briWest
                         + briSouth
                         + briEast
                         , 4);

      // hue difference
      float hueCenter = hue(_img.pixels[pixIndex]);
      float hueNorth  = hue(_img.pixels[pixIndex - _img.width]);
      float hueWest   = hue(_img.pixels[pixIndex - 1]);
      float hueEast   = hue(_img.pixels[pixIndex + 1]);
      float hueSouth  = hue(_img.pixels[pixIndex + _img.width]);
      float lapHue = pow(
                         - hueCenter * 4.0
                         + hueNorth
                         + hueWest
                         + hueSouth
                         + hueEast
                         , 4);

      // bright and saturation difference
      if (
          briCenter > 40.0
          && lapSat > 100.0
          ) edges.add(new PVector(idxW, idxH));

      // bright and some saturation and hue difference
      if (
          briCenter > 40.0
          && satCenter > 40.0
          && lapHue > 120.0
          ) edges.add(new PVector(idxW, idxH));

      // just brightness difference
      if (
          lapBri > 400.0
          ) edges.add(new PVector(idxW, idxH));

    }
  }

  // if it's over the _edgeNum then reduce randomly
  int removeCnt = edges.size() - _edgeNum;
  for (int i = 0; i < removeCnt; i++) {
    edges.remove(floor(random(edges.size())));
  }

  // add flower bud with over saturation
  for (PVector p : edges) {
    int pixIndex = floor(p.y * _img.width + p.x);
    fs.add(new Flower(
                      p.x,
                      p.y,
                      hue(_img.pixels[pixIndex]),
                      map(saturation(_img.pixels[pixIndex]), 0.0, 100.0, 0.0, 150.0),
                      brightness(_img.pixels[pixIndex])
                      ));
  }

  return fs;

}

/**
 * casing : draw fancy casing
 */
private void casing(int _casing, float _w, float _h) {
  rectMode(CORNER);
  fill(0.0, 0.0, 0.0, 0.0);
  strokeWeight(_casing + 4.0);
  stroke(0.0, 0.0, 30.0, 100.0);
  rect(-_casing * 0.5, -_casing * 0.5, _w + _casing, _h + _casing);
  strokeWeight(_casing);
  stroke(0.0, 0.0, 100.0, 100.0);
  rect(-_casing * 0.5, -_casing * 0.5, _w + _casing, _h + _casing);
}

/**
 * circlePacking : bloom flowers with the Circle Packing method.
 * @param _fs    : flowers to bloom.
 * @param _allow : number to allow the collisions.
 */
private ArrayList<Flower> circlePacking(ArrayList<Flower> _fs, int _allow) {

  int   tryMax  = 1000; // a trying count to add and grow flowers.
  float gap = 1.0;

  // deep copy
  ArrayList<Flower> flowers = new ArrayList<Flower>();
  for (Flower f : _fs) {
    flowers.add(f.clone());
  }

  for (int tryCnt = 0; tryCnt < tryMax; tryCnt++) {
    // grow flowers
    for (Flower fThis : flowers) {
      int collision = 0;
      for (Flower fThat : flowers) {
        if (fThis != fThat) {
          if (dist(fThis.x, fThis.y, fThat.x, fThat.y) < (fThis.r + fThat.r) * 0.5 + gap) {
            collision++;
          }
        }
      }
      if (collision < _allow) {
        fThis.grow();
      }
    }

  }
  return flowers;
}

/**
 * Flower : draw and hold location, size and color.
 */
public class Flower implements Cloneable {

  private float x, y;     // coordinate
  private float r;        // radius
  private float elpVal;   // ellipticity of the petal
  private float hueVal;   // hue value
  private float satVal;   // saturation value
  private float briVal;   // brightness value
  private float tooSmall; // detemination criteria of small flower

  Flower(float _x, float _y, float _hue, float _sat, float _bri) {
    x = _x;
    y = _y;
    r = 0.0;
    elpVal   = random(0.1, 0.5);
    hueVal   = _hue;
    satVal   = constrain(_sat, 0.0, 100.0);
    briVal   = constrain(_bri, 0.0, 90.0);
    tooSmall = min(width, height) * 0.02;
  }

  public void grow() {
    r++;
  }

  public void draw() {

    float shortR = r * elpVal;
    float petalDiv = PI / (ceil(random(1.0, 5.0)) * 2.0);

    pushMatrix();
    translate(x, y);
    rotate(random(PI));

    noStroke();
    fill(hueVal % 360.0, satVal, briVal, 100.0);
    for (float t = 0.0; t < PI; t += petalDiv) {
      rotate(t);
      ellipse(0.0, 0.0, r, shortR);
    }

    if (r < tooSmall) {
      strokeWeight(0.1);
    } else {
      strokeWeight(1.0);
    }
    stroke(0.0, 0.0, 90.0, 30.0);
    noFill();
    for (float t = 0.0; t < PI; t += petalDiv) {
      rotate(t);
      ellipse(0.0, 0.0, r, shortR);
    }

    strokeWeight(shortR * 0.2);
    stroke((hueVal + 330.0) % 360.0, satVal, briVal, 100.0);
    fill((hueVal + 30.0) % 360.0, satVal, briVal, 100.0);
    ellipse(0.0, 0.0, shortR, shortR);
    
    popMatrix();

  }
  
  @Override
  public Flower clone() {
    Flower f = null;
    try {
      f = (Flower)super.clone();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return f;
  }
}


/*
Copyright (C) 2021- deconbatch

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/