Apply a Oil Paint/Sketch effect to a photo using Javascript - javascript

I want to simulate an human drawing effect starting from a photo, using javascript.
I've been searching trough several js libraries that do image manipulation (mostly on canvas).
But seems that no one even attempted what I'm looking for.
I don't think it's impossible to achieve such effects with javascript. So I wonder why I can't find anything already done.
On the native side there are several alternatives to photoshop to achieve such effects, as seen in several apps in the App Store:
Oil Painting Booth Free
Brushstroke
Autopainter
Artist's Touch
Autographo
Here's other examples of possible result (from Artist's Touch App):

Alright so I found a great explanation of the algorithm used here and adapted it to JS and canvas.
Live Demo
CodePen Demo with controls to mess with the effect
How it works is you average all the pixels around your center pixel, then you multiply that average by the intensity level you want, you then divide it by 255. Then you increment the r/g/b's related to the intensity level. Then you check which intensity level was most common among the pixels neighbors, and assign the target pixel that intensity level.
edit worked on it a bit more and rewrote a lot of it, gained some really huge performance gains, works with decent sized images now pretty well.
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
img = new Image();
img.addEventListener('load', function () {
canvas.width = this.width;
canvas.height = this.height;
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
oilPaintEffect(canvas, 4, 55);
});
img.crossOrigin = "Anonymous";
img.src = "https://fbcdn-sphotos-h-a.akamaihd.net/hphotos-ak-xpa1/v/t1.0-9/1379992_10202357787410559_1075078295_n.jpg?oh=5b001e9848796dd942f47a0b2f3df6af&oe=542F3FEF&__gda__=1412145968_4dbb7f75b385770ecc3f4b88105cb0f8";
function oilPaintEffect(canvas, radius, intensity) {
var width = canvas.width,
height = canvas.height,
imgData = ctx.getImageData(0, 0, width, height),
pixData = imgData.data,
destCanvas = document.createElement("canvas"),
dCtx = destCanvas.getContext("2d"),
pixelIntensityCount = [];
destCanvas.width = width;
destCanvas.height = height;
// for demo purposes, remove this to modify the original canvas
document.body.appendChild(destCanvas);
var destImageData = dCtx.createImageData(width, height),
destPixData = destImageData.data,
intensityLUT = [],
rgbLUT = [];
for (var y = 0; y < height; y++) {
intensityLUT[y] = [];
rgbLUT[y] = [];
for (var x = 0; x < width; x++) {
var idx = (y * width + x) * 4,
r = pixData[idx],
g = pixData[idx + 1],
b = pixData[idx + 2],
avg = (r + g + b) / 3;
intensityLUT[y][x] = Math.round((avg * intensity) / 255);
rgbLUT[y][x] = {
r: r,
g: g,
b: b
};
}
}
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
pixelIntensityCount = [];
// Find intensities of nearest pixels within radius.
for (var yy = -radius; yy <= radius; yy++) {
for (var xx = -radius; xx <= radius; xx++) {
if (y + yy > 0 && y + yy < height && x + xx > 0 && x + xx < width) {
var intensityVal = intensityLUT[y + yy][x + xx];
if (!pixelIntensityCount[intensityVal]) {
pixelIntensityCount[intensityVal] = {
val: 1,
r: rgbLUT[y + yy][x + xx].r,
g: rgbLUT[y + yy][x + xx].g,
b: rgbLUT[y + yy][x + xx].b
}
} else {
pixelIntensityCount[intensityVal].val++;
pixelIntensityCount[intensityVal].r += rgbLUT[y + yy][x + xx].r;
pixelIntensityCount[intensityVal].g += rgbLUT[y + yy][x + xx].g;
pixelIntensityCount[intensityVal].b += rgbLUT[y + yy][x + xx].b;
}
}
}
}
pixelIntensityCount.sort(function (a, b) {
return b.val - a.val;
});
var curMax = pixelIntensityCount[0].val,
dIdx = (y * width + x) * 4;
destPixData[dIdx] = ~~ (pixelIntensityCount[0].r / curMax);
destPixData[dIdx + 1] = ~~ (pixelIntensityCount[0].g / curMax);
destPixData[dIdx + 2] = ~~ (pixelIntensityCount[0].b / curMax);
destPixData[dIdx + 3] = 255;
}
}
// change this to ctx to instead put the data on the original canvas
dCtx.putImageData(destImageData, 0, 0);
}

Paste a demo of #Loktar's answer here for someone looking for this algorithm.
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
img = new Image(),
effectEl = document.getElementById("effect"),
settings = {
radius : 4,
intensity : 25,
ApplyFilter : function(){
doOilPaintEffect();
}
}
img.addEventListener('load', function () {
// reduced the size by half for pen and performance.
canvas.width = (this.width/2);
canvas.height = (this.height/2);
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
doOilPaintEffect();
});
img.crossOrigin = "Anonymous";
img.src = "https://codropspz-tympanus.netdna-ssl.com/codrops/wp-content/uploads/2013/02/ImageTechniques.jpg";
var gui = new dat.GUI();
gui.add(settings, 'intensity');
gui.add(settings, 'radius');
gui.add(settings, 'ApplyFilter');
function doOilPaintEffect(){
oilPaintEffect(canvas, settings.radius, settings.intensity);
}
function oilPaintEffect(canvas, radius, intensity) {
var width = canvas.width,
height = canvas.height,
imgData = ctx.getImageData(0, 0, width, height),
pixData = imgData.data,
// change to createElement getting added element just for the demo
destCanvas = document.getElementById("dest-canvas"),
dCtx = destCanvas.getContext("2d"),
pixelIntensityCount = [];
destCanvas.width = width;
destCanvas.height = height;
var destImageData = dCtx.createImageData(width, height),
destPixData = destImageData.data,
intensityLUT = [],
rgbLUT = [];
for (var y = 0; y < height; y++) {
intensityLUT[y] = [];
rgbLUT[y] = [];
for (var x = 0; x < width; x++) {
var idx = (y * width + x) * 4,
r = pixData[idx],
g = pixData[idx + 1],
b = pixData[idx + 2],
avg = (r + g + b) / 3;
intensityLUT[y][x] = Math.round((avg * intensity) / 255);
rgbLUT[y][x] = {
r: r,
g: g,
b: b
};
}
}
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
pixelIntensityCount = [];
// Find intensities of nearest pixels within radius.
for (var yy = -radius; yy <= radius; yy++) {
for (var xx = -radius; xx <= radius; xx++) {
if (y + yy > 0 && y + yy < height && x + xx > 0 && x + xx < width) {
var intensityVal = intensityLUT[y + yy][x + xx];
if (!pixelIntensityCount[intensityVal]) {
pixelIntensityCount[intensityVal] = {
val: 1,
r: rgbLUT[y + yy][x + xx].r,
g: rgbLUT[y + yy][x + xx].g,
b: rgbLUT[y + yy][x + xx].b
}
} else {
pixelIntensityCount[intensityVal].val++;
pixelIntensityCount[intensityVal].r += rgbLUT[y + yy][x + xx].r;
pixelIntensityCount[intensityVal].g += rgbLUT[y + yy][x + xx].g;
pixelIntensityCount[intensityVal].b += rgbLUT[y + yy][x + xx].b;
}
}
}
}
pixelIntensityCount.sort(function (a, b) {
return b.val - a.val;
});
var curMax = pixelIntensityCount[0].val,
dIdx = (y * width + x) * 4;
destPixData[dIdx] = ~~ (pixelIntensityCount[0].r / curMax);
destPixData[dIdx + 1] = ~~ (pixelIntensityCount[0].g / curMax);
destPixData[dIdx + 2] = ~~ (pixelIntensityCount[0].b / curMax);
destPixData[dIdx + 3] = 255;
}
}
// change this to ctx to instead put the data on the original canvas
dCtx.putImageData(destImageData, 0, 0);
}
body{text-align:center;background:#ececec;font-family:Tahoma, Geneva, sans-serif}
section{display:inline-block}
canvas{border:1px solid #000}
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
<section>
<h2>Original</h2>
<canvas id="canvas"></canvas>
</section>
<section>
<h2>Oil Painting Effect</h2>
<canvas id="dest-canvas"></canvas>
</section>

Related

Maximum call stack size exceeded when filling a square of only 10 by 10 pixels

I know that with the method I want to perform, only small shapes can be filled. But I want to fill a small square but still get a stack overflow error.
Why it happens?
Maybe I have problems with functions color() or setcolor()?
function func() {
var canvas = document.getElementById("image");
var context = canvas.getContext("2d");
canvas.width = 100;
canvas.height = 100;
var width = 10;
var height = 10;
var x = 0;
var y = 0;
var imagedata = context.createImageData(width, height);
var pixelindex = (y * width + x) * 4;
function color(x, y) {
let pixelindex = (y * width + x) * 4;
return imagedata.data[pixelindex] + "," +
+imagedata.data[pixelindex + 1] + "," +
+imagedata.data[pixelindex + 2] + "," +
+imagedata.data[pixelindex + 3];
}
function setcolor(x, y, cn) {
let colors = cn.split(",");
let pixelindex = (y * width + x) * 4;
imagedata.data[pixelindex] = colors[0];
imagedata.data[pixelindex + 1] = colors[1];
imagedata.data[pixelindex + 2] = colors[2];
imagedata.data[pixelindex + 3] = colors[3];
}
function fill4(x, y, cb = "27,94,32,255", cn = "67,160,71,255") {
if (color(x, y) !== cb && color(x, y) !== cn) {
setcolor(x, y, cn);
fill4(x, y - 1, cb, cn);
fill4(x, y + 1, cb, cn);
fill4(x - 1, y, cb, cn);
fill4(x + 1, y, cb, cn);
}
}
fill4(5, 5);
context.putImageData(imagedata, x, y);
}
<body onload="func();">
<canvas id="image"></canvas>
</body>
Based on all the comments under my question, I rewrote the code a bit. Most importantly, I added the conditions that I was told about. Without these conditions, my function ran indefinitely, which is why the error was received. Now, by executing the code, you can see the correct filling of the square with the selected color.
function func() {
var canvas = document.getElementById("image");
var context = canvas.getContext("2d");
canvas.width = 100;
canvas.height = 100;
var width = 100
var x = 0;
var y = 0;
var zX = 50;
var zY = 50;
var cb = [27,94,32,255];
var cn = [67,160,71,255];
context.strokeStyle = "black";
context.lineWidth = 2;
context.strokeRect(x, y, width, width);
if (zX > x && zX < x + width && zY > y && zY < y + width) {
fill4(zY, zY, cb, cn, context);
}
function fill4(zX, zY, cb, cn, content) {
var imagedata = context.createImageData(1, 1);
for (let i = 0; i < 4; i++) {
imagedata.data[i] = cn[i];
}
var color = context.getImageData(zX, zY, 1, 1).data;
if (zX > x && zX < x + width && zY > y && zY < y + width) {
if (color[0] != cn[0] && color[1] != cn[1] && color[2] != cn[2] && color[3] != cn[3]) {
context.putImageData(imagedata, zX, zY);
setTimeout(function() {
fill4(zX, zY-1, cb, cn, context);
fill4(zX, zY+1, cb, cn, context);
fill4(zX-1, zY, cb, cn, context);
fill4(zX+1, zY, cb, cn, context);
}, 20);
}
}
}
}
<body onload="func();">
<canvas id="image"></canvas>
</body>

JS - Find x,y of middle of black square in image

I have a bunch of images like this one:
http://i.imgur.com/kW8UaA4.png
I need to find the x,y of the middle of the dark square.
I currently have the following code:
https://jsfiddle.net/brampower/tw08fdhf/
function rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
var max = Math.max(r, g, b),
min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max == min) {
h = s = 0; // achromatic
} else {
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return ({
h: h,
s: s,
l: l,
});
}
function solve_darkest(url, callback) {
var image = new Image();
image.crossOrigin = "Anonymous";
image.src = url;
image.onload = function(){
var canvas = document.createElement('canvas');
var WIDTH = image.width;
var HEIGHT = image.height;
canvas.width = WIDTH;
canvas.height = HEIGHT;
var context = canvas.getContext("2d");
context.drawImage(image, 0, 0);
document.body.appendChild(canvas);
var imgData = context.getImageData(0, 0, WIDTH, HEIGHT);
var pixel = 0;
var darkest_pixel_lightness = 100;
var darkest_pixel_location = 0;
for (var i = 0; i < imgData.data.length; i += 4, pixel++) {
red = imgData.data[i + 0];
green = imgData.data[i + 1];
blue = imgData.data[i + 2];
alpha = imgData.data[i + 3];
if (alpha < 230) {
continue;
}
console.log(Math.floor(pixel / WIDTH) + " ", i % WIDTH);
var hsl = rgbToHsl(red, green, blue);
var lightness = hsl.l;
if (lightness < darkest_pixel_lightness) {
darkest_pixel_lightness = lightness;
darkest_pixel_location = pixel;
}
}
var y = Math.floor(darkest_pixel_location / WIDTH);
var x = darkest_pixel_location % WIDTH;
callback(x,y);
};
}
image_url = 'http://i.imgur.com/kW8UaA4.png';
solve_darkest(image_url, function(x, y) {
setTimeout(function() {
alert('x: '+x+' y: '+y);
}, 100);
});
But it is not giving me the results I am expecting. It loops through all the pixels and then returns the x and y of the darkest pixel. However, it appears that the darkest pixel doesn't reside in the dark square area.
What can I do to make it return the x,y of the middle of the darker square?

createPattern on canvas issue

As on the attached fiddle, the background image on the canvas is just coming and getting disappearing
I tried both createPattern and drawImage both are having same issue.
I am not an expert in Canvas. Any help would be appreciated
(function(){
var canvas = document.getElementById('c'),
/** #type {CanvasRenderingContext2D} */
ctx = canvas.getContext('2d'),
width = 400,
height = 400,
half_width = width >> 1,
half_height = height >> 1,
size = width * (height + 2) * 2,
delay = 30,
oldind = width,
newind = width * (height + 3),
riprad = 3,
ripplemap = [],
last_map = [],
ripple,
texture,
line_width = 20,
step = line_width * 2,
count = height / line_width;
canvas.width = width;
canvas.height = height;
/*
* Water ripple demo can work with any bitmap image
*/
var imageObj = new Image();
imageObj.onload = function() {
var pattern = ctx.createPattern(imageObj, 'repeat');
ctx.rect(0, 0, width, height);
ctx.fillStyle = pattern;
ctx.fill();
ctx.save();
};
imageObj.src = 'http://www.html5canvastutorials.com/demos/assets/wood-pattern.png';
with (ctx) {
fillStyle = '#ccc';
fillRect(0, 0, width, height);
fillStyle = '#999999';
save();
rotate(-0.785);
for (var i = 0; i < count; i++) {
fillRect(-width, i * step, width * 3, line_width);
}
}
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
for (var i = 0; i < size; i++) {
last_map[i] = ripplemap[i] = 0;
}
/**
* Main loop
*/
function run() {
newframe();
ctx.putImageData(ripple, 0, 0);
}
/**
* Disturb water at specified point
*/
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var j = dy - riprad; j < dy + riprad; j++) {
for (var k = dx - riprad; k < dx + riprad; k++) {
ripplemap[oldind + (j * width) + k] += 128;
}
}
}
/**
* Generates new ripples
*/
function newframe() {
var a, b, data, cur_pixel, new_pixel, old_data;
var t = oldind; oldind = newind; newind = t;
var i = 0;
// create local copies of variables to decrease
// scope lookup time in Firefox
var _width = width,
_height = height,
_ripplemap = ripplemap,
_last_map = last_map,
_rd = ripple.data,
_td = texture.data,
_half_width = half_width,
_half_height = half_height;
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
var _newind = newind + i, _mapind = oldind + i;
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
data -= _ripplemap[_newind];
data -= data >> 5;
_ripplemap[_newind] = data;
//where data=0 then still, where data>0 then wave
data = 1024 - data;
old_data = _last_map[i];
_last_map[i] = data;
if (old_data != data) {
//offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;
//bounds check
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;
new_pixel = (a + (b * _width)) * 4;
cur_pixel = i * 4;
_rd[cur_pixel] = _td[new_pixel];
_rd[cur_pixel + 1] = _td[new_pixel + 1];
_rd[cur_pixel + 2] = _td[new_pixel + 2];
}
++i;
}
}
}
canvas.onmousemove = function(/* Event */ evt) {
disturb(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
};
setInterval(run, delay);
// generate random ripples
var rnd = Math.random;
var timeOut;
var intrvl = setInterval(function() {
clearTimeout(timeOut);
disturb(0, (height/40));
disturb((width/2.67), (height/40));
disturb((width/1.33), (height/40));
disturb(0, (height/2.67));
disturb((width/2.67), (height/2.67));
disturb((width/1.33), (height/2.67));
disturb(0, (height/1.33));
disturb((width/2.67), (height/1.33));
disturb((width/1.33), (height/1.33));
timeOut= setTimeout(function(){
disturb((width/1.8), (height/8));
disturb((width/-1.2), (height/8));
disturb((width/1.14), (height/8));
disturb((width/1.8), (height/2.13));
disturb((width/-1.2), (height/2.13));
disturb((width/1.14), (height/2.13));
disturb((width/1.8), (height/1.03));
disturb((width/-1.2), (height/1.03));
disturb((width/1.14), (height/1.03));;
},300);
}, 700);
setTimeout(function(){
clearInterval(intrvl);
},3000);
})();
this is the link to the fiddle

Javascript/HTML Canvas Issues in Chrome/Firefox (Not Safari)

When I run this script on Safari, everything works fine. However when I open it up in Chrome or Firefox it does not execute correctly. In the Chrome console it says that there are Uncaught type errors for:
ctx.drawImage(img, x + 1, y + 1, iwh, iwh);
and
function renderStarField() {
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, WIDTH, HEIGHT);
for (var i = 0; i < stars.length; i++) {
stars[i].plot();
}
}
Here is my entire script, thanks for the help!
<script type="text/javascript">
var starField = (function () {
var browserWIDTH = $(document).width(),
browserHEIGHT = $(document).height(),
WIDTH = browserWIDTH + 500,
HEIGHT = 400,
FIELD_DEPTH = 15,
DISTANCE = 500,
STAR_DIAMETER = 45,
STAR_SPEED = 0.003,
canvas,
ctx,
numStars = 2000,
stars = [];
function Star() {
this.calcPosition();
var RANDSTAR = Math.floor(Math.random() * 3) + 1;
}
Star.prototype.calcPosition = function (reset) {
this.x = this.randomise(-25, 50);
this.y = this.randomise(-25, 50);
this.z = reset ? FIELD_DEPTH : this.randomise(1, FIELD_DEPTH);
};
Star.prototype.randomise = function (min, max) {
return Math.floor((Math.random() * max) + min);
};
Star.prototype.plot = function () {
//calculate 3d to 2d using perspective projection with the screen as the origin
var x = this.x * (DISTANCE / this.z) + WIDTH / 2,
y = this.y * (DISTANCE / this.z) + HEIGHT / 2;
if ((x >= 0 && x <= WIDTH) && (y >= 0 && y <= HEIGHT)) {
ctx.beginPath();
var img = document.createElement('image');
img.src ='Star1.png';
var iwh = this.calcSize(this.z);
ctx.moveTo(x, y);
ctx.drawImage(img, x + 1, y + 1, iwh, iwh);
}
this.z -= STAR_SPEED;
if (this.z <= 0) {
this.calcPosition(true);
}
};
Star.prototype.calcColor = function (z) {
var rgb = Math.abs((z * 5) - 255).toFixed(0),
a = (1 - ((z / (FIELD_DEPTH / 100)) / 100)).toFixed(1);
return 'rgba(' + rgb + ', ' + rgb + ', ' + rgb + ', ' + a + ')';
};
Star.prototype.calcSize = function (z) {
return Math.abs(((z / (FIELD_DEPTH / 100)) * (STAR_DIAMETER / 100)) - STAR_DIAMETER);
};
function setUpCanvas() {
canvas = document.querySelector('#stage');
canvas.width = WIDTH;
canvas.height = HEIGHT;
ctx = canvas.getContext('2d');
}
function buildStars() {
for (var i = 0; i < numStars; i++) {
stars.push(new Star());
}
}
function renderStarField() {
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, WIDTH, HEIGHT);
for (var i = 0; i < stars.length; i++) {
stars[i].plot();
}
}
function initialise() {
setUpCanvas();
buildStars();
setInterval(renderStarField, 20);
}
return {
init: initialise
}
})();
document.addEventListener('DOMContentLoaded', function () {
starField.init();
});
</script>
if ((x >= 0 && x <= WIDTH) && (y >= 0 && y <= HEIGHT)) {
ctx.beginPath();
var img = document.createElement('image');
img.src ='Star1.png';
var iwh = this.calcSize(this.z);
ctx.moveTo(x, y);
ctx.drawImage(img, x + 1, y + 1, iwh, iwh);
}
This code has some error
img.src = 'Star1.png' is not working, try img.setAttribute('src','Star1.png');
and Create <img> tag code is not document.createElement('image')
try document.createElement('img');
Change To
if ((x >= 0 && x <= WIDTH) && (y >= 0 && y <= HEIGHT)) {
ctx.beginPath();
var img = document.createElement('img');
img.setAttribute('src','Star1.png');
var iwh = this.calcSize(this.z);
ctx.moveTo(x, y);
ctx.drawImage(img, x + 1, y + 1, iwh, iwh);
}

Applying Edge Filter on Video using Canvas in HTML5 but getting lag in video. Is there any better way that i don't get lag?

I am using HTML5 Canvas and applying Edge Filter by pixel manipulation. But I am getting lag in the video. Is there any better way to achieve video with edge filter without any lag in video?
Following is the JavaScript I am using with canvas and HTML 5 to apply the effect:-
function sobel (px) {
px = greyScale(px);
var vertical = convoluteFloat32(px,
[-1, -2, -1,
0, 0, 0,
1, 2, 1], false);
var horizontal = convoluteFloat32(px,
[-1, 0, 1,
-2, 0, 2,
-1, 0, 1], false);
var id = createImageData(vertical.width, vertical.height);
for (var i = 0; i < id.data.length; i += 4) {
var v = Math.abs(vertical.data[i]);
id.data[i] = v;
var h = Math.abs(horizontal.data[i]);
id.data[i + 1] = h
id.data[i + 2] = (v + h) / 4;
id.data[i + 3] = 255;
}
return id;
}
function convoluteFloat32 (pixels, weights, opaque) {
var side = Math.round(Math.sqrt(weights.length));
var halfSide = Math.floor(side / 2);
var src = pixels.data;
var sw = pixels.width;
var sh = pixels.height;
var w = sw;
var h = sh;
var output = {
width: w, height: h, data: new Float32Array(w * h * 4)
};
var dst = output.data;
var alphaFac = opaque ? 1 : 0;
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var sy = y;
var sx = x;
var dstOff = (y * w + x) * 4;
var r = 0, g = 0, b = 0, a = 0;
for (var cy = 0; cy < side; cy++) {
for (var cx = 0; cx < side; cx++) {
var scy = Math.min(sh - 1, Math.max(0, sy + cy - halfSide));
var scx = Math.min(sw - 1, Math.max(0, sx + cx - halfSide));
var srcOff = (scy * sw + scx) * 4;
var wt = weights[cy * side + cx];
r += src[srcOff] * wt;
g += src[srcOff + 1] * wt;
b += src[srcOff + 2] * wt;
a += src[srcOff + 3] * wt;
}
}
dst[dstOff] = r;
dst[dstOff + 1] = g;
dst[dstOff + 2] = b;
dst[dstOff + 3] = a + alphaFac * (255 - a);
}
}
return output;
}
function draw() {
// First, draw it into the backing canvas
backcontext.drawImage(video, 0, 0, video.width, video.height);
// Grab the pixel data from the backing canvas
var idata = backcontext.getImageData(0, 0, video.width, video.height);
idata = sobel(idata);
context.putImageData(idata, 0, 0);
// Start over!
setTimeout(draw, 50);
}
Instead of convolution with a 2D kernel, you can try 1D convolution with the separable convolution approach. This should increase the speed noticeably.
An explanation on how to use separable convolution using Sobel kernels can be found here.
You should change your code to something like this:
function convoluteFloat32 (pixels, weights1DHorizontal, weights1DVertical, opaque){
convolveFloat321DHorizontal(pixels, weights1DHorizontal, opaque);
convolveFloat321DVertical(pixels, weights1DVertical, opaque);
}
And convolveFloat321DHorizontal/convolveFloat321DVertical methods are just like the original convoluteFloat32 method, but they have 3 nested for loops instead of 4.

Categories

Resources