It's an Image Manipulation code with the Circle Packing method.
Example result with the 'Tabby Cat With Long Fur'
http://www.photos-public-domain.com/2012/04/06/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 an Image Manipulation type creative coding made with the '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 with this code like this. Let's try!
example : your photo file = ./data/your_photo.jpg
PImage img = loadImage("your_photo.jpg");
Example code of the 'Processing'.
Please feel free to use this example code under the terms of the GPL.
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/