The 'Processing' code processes the image to the Vector Field.
The original image is the photo of the Gargano National Park.
This creative coding process the image hard!
This is the creative coding of image processing. It was written in the 'Processing'.
It's a near relation work of 'Daydream'. It uses the Vector Field method to process the image. It starts the Vector Field from the points of edges in the image.
You can use your photo to process.
PImage img = loadImage("your_photo.png");
The keys of this code.
One of the keys of this code is using nested 3D noise to calculate the Vector Field.
noise(xPrev * _noiseDiv, yPrev * _noiseDiv, noise(yPrev * _noiseDiv * 3.0) * _uneven)
Ordinary, I use 2D noise like 'noise(x, y)'.
And I found the interesting effect when I used nested 3D noise like 'noise(x, y, noise(x, y))'.
And the other key of this code is limiting the direction of the path of the Vector Field.
cos(TWO_PI * round(nX * _curlMult * _angles) / _angles)
An example image : no limitting.
An example image : limitting the direction.
An example code that was written in the 'Processing'.
This code does not display any images on the screen but generates image files in frames directory.
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.
/**
* Picture This.
* The Vector field that starts from the edges of some photo.
*
* @author @deconbatch
* @version 0.1
* @license GPL Version 3 http://www.gnu.org/licenses/
* Processing 3.5.3
* 2020.11.19
*/
void setup() {
size(980, 980);
colorMode(HSB, 360, 100, 100, 100);
smooth();
noLoop();
}
void draw() {
int imgMax = 3;
int initMax = 5000;
int caseWidth = 30;
int baseCanvas = width - caseWidth * 2;
// original photo load
PImage img = loadImage("your_photo.png");
float rateSize = baseCanvas * 1.0 / max(img.width, img.height);
img.resize(floor(img.width * rateSize), floor(img.height * rateSize));
img.loadPixels();
// detect edges
ArrayList<PVector> edges = detectEdge(img, initMax);
float initHue = typicalHue(img, edges) + 120.0;
for (int imgCnt = 0; imgCnt < imgMax; imgCnt++) {
float curlMult = random(4.0,8.0);
float noiseDiv = 0.006 / curlMult;
float uneven = random(5.0, 10.0);
float baseSiz = 1.5;
initHue += 30.0;
// calculate the vector field
ArrayList<ArrayList<PVector>> paths = getPaths(edges, curlMult, noiseDiv, uneven, imgCnt * 2 + 5);
// draw background
pushMatrix();
translate((width - img.width) / 2, (height - img.height) / 2);
background(initHue % 360.0, 80.0, 60.0, 100);
noStroke();
// draw shadow
blendMode(DARKEST);
for (ArrayList<PVector> path : paths) {
float xInit = path.get(0).x;
float yInit = path.get(0).y;
float xPoint = xInit;
float yPoint = yInit;
int pIdx = floor(yPoint * img.width + xPoint);
float baseHue = hue(img.pixels[pIdx]);
float baseBri = brightness(img.pixels[pIdx]);
for (int i = 0; i < path.size(); i++) {
PVector p = path.get(i);
float pRatio = i * 1.0 / path.size();
float eHue = baseHue + floor(((xInit * yInit) * 1.0) % 4.0) * 20.0;
float eBri = baseBri * sin(PI * pRatio) * (8.0 + floor(((xInit * yInit) * 20000.0) % 5.0)) / 30.0;
float eSiz = baseSiz * sin(PI * pRatio);
fill(eHue % 360.0, 100.0, eBri, 100);
translate(eSiz * 2.0, eSiz * 2.0);
ellipse(p.x, p.y, eSiz, eSiz);
translate(-eSiz * 2.0, -eSiz * 2.0);
}
}
// draw foreground
blendMode(LIGHTEST);
for (ArrayList<PVector> path : paths) {
float xInit = path.get(0).x;
float yInit = path.get(0).y;
float xPoint = xInit;
float yPoint = yInit;
int pIdx = floor(yPoint * img.width + xPoint);
float baseHue = hue(img.pixels[pIdx]);
float baseBri = brightness(img.pixels[pIdx]);
for (int i = 0; i < path.size(); i++) {
PVector p = path.get(i);
float pRatio = i * 1.0 / path.size();
float eHue = baseHue + floor(((xInit * yInit) * 1.0) % 4.0) * 20.0;
float eSat = map(sin(PI * pRatio), 0.0, 1.0, 70.0, 90.0) * (6.0 + floor(((xInit * yInit) * 30000.0) % 4.0)) / 9.0;
float eBri = baseBri * sin(PI * pRatio) * (8.0 + floor(((xInit * yInit) * 20000.0) % 5.0)) / 10.0;
float eSiz = baseSiz * sin(PI * pRatio);
fill(eHue % 360.0, eSat, eBri, 100.0);
ellipse(p.x, p.y, eSiz, eSiz);
}
}
popMatrix();
casing();
saveFrame("frames/" + String.format("%04d", imgCnt + 1) + ".png");
}
exit();
}
/**
* casing : draw fancy casing
*/
private void casing() {
blendMode(BLEND);
fill(0.0, 0.0, 0.0, 0.0);
strokeWeight(60.0);
stroke(0.0, 0.0, 0.0, 100.0);
rect(0.0, 0.0, width, height);
strokeWeight(50.0);
stroke(0.0, 0.0, 100.0, 100.0);
rect(0.0, 0.0, width, height);
noStroke();
noFill();
}
/**
* getPaths : calculate the Vector Field paths.
* @param _pvs : start points coordinate of the Vector Field.
* @param _curlMult : curl ratio of the path.
* @param _noiseDiv : noise parameter step ratio.
* @param _uneven : make uneven the Vector Field with 3rd parameter of the noise.
* @param _angles : slant of the path.
* @return array of the Vector Field path, path is the array of the PVector coordinates.
*/
private ArrayList<ArrayList<PVector>> getPaths(ArrayList<PVector> _pvs, float _curlMult, float _noiseDiv, float _uneven, int _angles) {
int plotMax = 2000;
float plotDiv = 0.2;
ArrayList<ArrayList<PVector>> paths = new ArrayList<ArrayList<PVector>>();
for (PVector p : _pvs) {
ArrayList<PVector> path = new ArrayList<PVector>();
float xInit = p.x;
float yInit = p.y;
float xPoint = xInit;
float yPoint = yInit;
for (int plotCnt = 0; plotCnt < plotMax; ++plotCnt) {
float xPrev = xPoint;
float yPrev = yPoint;
float nX = noise(xPrev * _noiseDiv, yPrev * _noiseDiv, noise(yPrev * _noiseDiv * 3.0) * _uneven);
float nY = noise(yPrev * _noiseDiv, xPrev * _noiseDiv, noise(xPrev * _noiseDiv * 3.0) * _uneven);
xPoint += plotDiv * cos(TWO_PI * round(nX * _curlMult * _angles) / _angles);
yPoint += plotDiv * sin(TWO_PI * round(nY * _curlMult * _angles) / _angles);
path.add(new PVector(xPoint, yPoint));
}
if (dist(path.get(0).x, path.get(0).y, path.get(path.size() - 1).x, path.get(path.size() - 1).y) > 20.0) {
paths.add(path);
}
}
return paths;
}
/**
* detectEdge : detect edges of photo image.
* @param _img : detect edges of this image.
* @param _edgeNum : return edge points number.
* @return array of the edges locations.
*/
private ArrayList<PVector> detectEdge(PImage _img, int _edgeNum) {
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]) > 40.0
&& lapSat > 30.0
) edges.add(new PVector(idxW, idxH));
// bright and some saturation and hue difference
if (
brightness(_img.pixels[pixIndex]) > 40.0
&& saturation(_img.pixels[pixIndex]) > 20.0
&& lapHue > 100.0
) edges.add(new PVector(idxW, idxH));
// just brightness difference
if (
lapBri > 100.0
) edges.add(new PVector(idxW, idxH));
}
}
int removeCnt = edges.size() - _edgeNum;
for (int i = 0; i < removeCnt; i++) {
edges.remove(floor(random(edges.size())));
}
return edges;
}
/**
* typicalHue : detect typical hue value.
* @param _img : pick up hue value from this image.
* @param _pvs : coordinates in _img.
* @return hue value.
*/
private float typicalHue(PImage _img, ArrayList<PVector> _pvs) {
int[] cnt = new int[12];
for (int i : cnt) {
i = 0;
}
for (PVector p : _pvs) {
int pIdx = floor(p.y * _img.width + p.x);
cnt[floor(hue(_img.pixels[pIdx]) / 30.0)]++;
}
int hueValue = 0;
int maxNum = 0;
for (int i = 0; i < 12; i++) {
if (cnt[i] > maxNum) {
hueValue = i;
maxNum = cnt[i];
}
}
return hueValue * 30.0;
}
/*
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/>
*/
Can this type of code be used without a picture? I am just learning all of this. And can the 3d be applied to p5.js? I tried to figure out how to apply similar effects to an existing P5.js code that I made. I know it is different from processing.
Maybe yes.
The similar work without a picture is here. Enjoy! 😀
https://www.deconbatch.com/2020/12/welcome-to-my-nightmare-creative-coding.html