PacKitty.

Example result with the 'Tabby Cat With Long Fur'
http://www.photos-public-domain.com/2012/04/06/tabby-cat-with-long-fur/

Description of this Image Manipulation code.

It's a Image Manipulation type creative coding made with Processing. It draws the image from some photo with the Circle Packing method.

I use my edge detection code to draw detailes with small circles.
ArrayList<PVector> edges = detectEdge(img);
circlePacking(drops, edges, img, 0.1, 1.0);
And I fill the empty space with random circle locations.
circlePacking(drops, randomAdds(img.width, img.height), img, 1.0, 0.5);
You can use your photo.
example : your photo file = ./data/your_photo.jpg
          PImage img = loadImage("your_photo.jpg");

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 file in frames directory.



/**
 * PacKitty.
 * image manipulation with the Circle Packing method.
 *
 * Processing 3.5.3
 * @author @deconbatch
 * @version 0.1
 * created 0.1 2020.05.06
 */

import java.util.Collections;

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;

  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));

  // edge detection
  ArrayList<PVector> edges = detectEdge(img);
  Collections.shuffle(edges);

  // circle packing
  ArrayList<Drop> drops = new ArrayList<Drop>();
  circlePacking(drops, edges, img, 0.1, 1.0);
  circlePacking(drops, randomAdds(img.width, img.height), img, 1.0, 0.5);

  // draw the result
  background(0.0, 0.0, 96.0, 100.0);
  translate((width - img.width) / 2, (height - img.height) / 2);
  for (Drop f : drops) {
    f.draw();
  }    

  casing(caseWidth, img.width, img.height);
  saveFrame("frames/0001.png");

  exit();
  
}

/**
 * randomAdds : make random PVectors
 * @param  _width, _height : x, y scope of random location.
 * @return ArrayList<PVector> : random PVectors.
 */
public ArrayList<PVector> randomAdds(float _width, float _height) {

  ArrayList<PVector> rnds = new ArrayList<PVector>();
  int   rndsMax  = 10000; // a trying count to add and grow drops.
  for (int rndsCnt = 0; rndsCnt < rndsMax; rndsCnt++) {
    rnds.add(new PVector(random(_width), random(_height)));
  }
  return rnds;
  
}

/**
 * circlePacking : locate Drops with the Circle Packing method.
 * @param ArrayList<Drop>    _drops : holds Circle Packing results.
 * @param ArrayList<PVector> _adds  : Circle Packing start point candidates.
 * @param PImage _img  : original image.
 * @param float _incre : increment value of the Circle growing.
 * @param float _mult  : multiple value of saturation of original image.
 */
public void circlePacking(ArrayList<Drop> _drops, ArrayList<PVector> _adds, PImage _img, float _incre, float _mult) {

  float gapAdding    = 10.0;
  float gapCollision = 1.0;
  _img.loadPixels();

  for (int i = 0; i < _adds.size(); i++) {

    int fX = floor(_adds.get(i).x);
    int fY = floor(_adds.get(i).y);
    
    // add new drop
    boolean inner = false;
    for (Drop f : _drops) {
      if (dist(fX, fY, f.x, f.y) < f.r + gapAdding) {
        inner = true;
        break;
      }
    }
    if (!inner) {
      int pixIndex = floor(fY * _img.width + fX);
      _drops.add(new Drop(
                          fX,
                          fY,
                          hue(_img.pixels[pixIndex]),
                          saturation(_img.pixels[pixIndex]) * _mult,
                          brightness(_img.pixels[pixIndex]) * map(_mult, 0.0, 1.0, 2.0, 1.0)
                          ));
    }

    if (i % 10 == 0) {
      // grow drops
      for (Drop fThis : _drops) {
        boolean collision = false;
        for (Drop fThat : _drops) {
          if (fThis != fThat) {
            if (dist(fThis.x, fThis.y, fThat.x, fThat.y) < (fThis.r + fThat.r) * 0.5 + gapCollision) {
              collision = true;
              break;
            }
          }
        }
        if (!collision) {
          fThis.r += _incre;
        }
      }
    }

  }

}

/**
 * Drop : draw and hold location, size and color.
 */
public class Drop {

  public  int   x, y;   // coordinate of drop
  public  float r;      // radius
  private float hueVal; // hue value of drop
  private float satVal; // saturation value of drop
  private float briVal; // brightness value of drop

  Drop(int _x, int _y, float _c, float _s, float _b) {
    x = _x;
    y = _y;
    r = 1.0;  // initial radius of the drop
    hueVal = _c;
    satVal = _s;
    briVal = _b;
  }

  public void draw() {

    noStroke();
    fill(hueVal % 360.0, satVal, briVal, 100.0);
    ellipse(x, y, r, r);

    fill(hueVal % 360.0, satVal * 1.5, briVal, 100.0);
    stroke(0.0, 0.0, 96.0, 100.0);
    strokeWeight(r * 0.1);
    ellipse(x, y, r * 0.5, r * 0.5);

  }

}

/**
 * detectEdge : detect edges of photo image.
 * @param  _img : detect edges of this image.
 * @return ArrayList<PVector> : edges locations.
 */
public ArrayList<PVector> detectEdge(PImage _img) {

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

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

      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
                         , 2);

      // 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
                         , 2);

      // 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
                         , 2);

      // bright and saturation difference
      if (
          brightness(_img.pixels[pixIndex]) > 30.0
          && lapSat > 20.0
          ) edges.add(new PVector(idxW, idxH));

      // bright and some saturation and hue difference
      if (
          brightness(_img.pixels[pixIndex]) > 30.0
          && saturation(_img.pixels[pixIndex]) > 10.0
          && lapHue > 100.0
          ) edges.add(new PVector(idxW, idxH));

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

    }
  }

  return edges;
}

/**
 * casing : draw fancy casing
 */
public void casing(int _casing, float _w, float _h) {
  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);
  noStroke();
  noFill();
}


/*
Copyright (C) 2020- 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/>
*/




An example image with 'Two Tabby Cats'.

http://www.photos-public-domain.com/2016/11/15/two-tabby-cats/










No comments :

Post a Comment