Image manipulation code that makes this world full of wildflowers
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 the '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.
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.
It's a result of edge detection and picking points on the mesh randomly.
I used the pixelated background to imitate the original image.
And it's a bonus. 😉
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 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 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/>
*/