I'm trying to recreate in Javascript (specifically with p5.js) an effect others seem to have successfully accomplished using the Mathematica suite, as seen here https://mathematica.stackexchange.com/a/39049.
I'm 100% ignorant about Mathematica, but I see they are using a method called GradientOrientationFilter
to create a pattern of strokes following the direction of the gradients of the image.
My results are still not satisfying.
var img, vectors;
var pixelsToSkip = 2; // for faster rendering we can stroke less lines
var linesLength = 20;
var strokeThickness = 1;
function preload() {
img = loadImage('http://lorempixel.com/300/400/people/1');
img2 = loadImage('http://lorempixel.com/300/400/people/1');
/* you can test in local if the directions are correct using a simple gradient as image
img = loadImage('http://fornace.io/jstests/img/gradient.jpg');
img2 = loadImage('http://fornace.io/jstests/img/gradient.jpg');
*/
}
function setup() {
createCanvas(img.width, img.height);
noLoop();
img.loadPixels();
makeLumas();
makeGradients();
makeVectors();
for ( var xx = 0; xx < img.width; xx = xx + pixelsToSkip) {
for ( var yy = 0; yy < img.height; yy = yy + pixelsToSkip) {
push();
stroke(random(255)); // to color with pixel color change to stroke(img.get(xx, yy));
strokeWeight(strokeThickness);
translate(xx,yy);
rotate( vectors[yy][xx].dir ); // here we use the rotation of the gradient
line(-linesLength/2, 0, linesLength/2, 0);
pop();
}
}
// adding the image in overlay to evaluate if the map is good
// tint(255, 255, 255, 100);
// image(img2,0,0);
}
function draw() {
}
function makeLumas() {
// calculate the luma for each pixel to get a map of dark/light areas ("Rec. 601") https://en.wikipedia.org/wiki/Luma_(video)
lumas = new Array(img.height);
for (var y = 0; y < img.height; y++) {
lumas[y] = new Array(img.width);
for (var x = 0; x < img.height; x++) {
var i = x * 4 + y * 4 * img.width;
var r = img.pixels[i],
g = img.pixels[i + 1],
b = img.pixels[i + 2],
a = img.pixels[i + 3];
var luma = a == 0 ? 1 : (r * 299/1000 + g * 587/1000
+ b * 114/1000) / 255;
lumas[y][x] = luma;
}
}
}
function makeGradients() {
// calculate the gradients (kernel [-1, 0, 1])
var horizontalGradient = verticalGradient = [];
for (var y = 0; y < img.height; y++) {
horizontalGradient[y] = new Array(img.width);
verticalGradient[y] = new Array(img.width);
var row = lumas[y];
for (var x = 0; x < img.width; x++) {
var prevX = x == 0 ? 0 : lumas[y][x - 1];
var nextX = x == img.width - 1 ? 0 : lumas[y][x + 1];
var prevY = y == 0 ? 0 : lumas[y - 1][x];
var nextY = y == img.height - 1 ? 0 : lumas[y + 1][x];
horizontalGradient[y][x] = -prevX + nextX;
verticalGradient[y][x] = -prevY + nextY;
}
}
}
function makeVectors() {
// calculate direction and magnitude
vectors = new Array(img.height);
for (var y = 0; y < img.height; y++) {
vectors[y] = new Array(img.width);
for (var x = 0; x < img.width; x++) {
var prevX = x == 0 ? 0 : lumas[y][x - 1];
var nextX = x == img.width - 1 ? 0 : lumas[y][x + 1];
var prevY = y == 0 ? 0 : lumas[y - 1][x];
var nextY = y == img.height - 1 ? 0 : lumas[y + 1][x];
var gradientX = -prevX + nextX;
var gradientY = -prevY + nextY;
vectors[y][x] = {
mag: Math.sqrt(Math.pow(gradientX, 2) + Math.pow(gradientY, 2)),
dir: Math.atan2(gradientY, gradientX)
}
}
}
}
My field made in javascript is much more noisy than the one made in Mathematica.
http://jsfiddle.net/frapporti/b4zxkcmL/
I'm quite new to p5.js, perhaps I'm reinventing the wheel in some passage. Feel free to correct me in this too.
The solution that worked for me was simply blurring before any pixel analysis to smooth out the results of the gradient orientation filter.
If you are using p5.js, this could be as easy as img.filter("blur",5);
. Otherwise you can go with any other blurring technique of your choice.
You can see the code here: http://jsfiddle.net/frapporti/b4zxkcmL/21/
I will wait before marking this answer accepted in case someone has other ways.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With