summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJules Laplace <jules@okfoc.us>2015-04-22 05:21:11 -0400
committerJules Laplace <jules@okfoc.us>2015-04-22 05:21:11 -0400
commitbef106de9fa827d983fa319cecdf688c2822efb9 (patch)
treed336ec726e97e5c19cb0dfee56d2bf4912d0db29
smartblur convolution experiment
-rw-r--r--convolve.html67
-rw-r--r--convolve.js79
-rw-r--r--fetch.js30
-rw-r--r--img/orange-01.jpgbin0 -> 20218 bytes
-rw-r--r--img/oranges.jpgbin0 -> 16277 bytes
-rw-r--r--smartblur.java162
-rw-r--r--smartblur.js138
-rw-r--r--test.html21
-rw-r--r--webcam.html55
9 files changed, 552 insertions, 0 deletions
diff --git a/convolve.html b/convolve.html
new file mode 100644
index 0000000..24e3590
--- /dev/null
+++ b/convolve.html
@@ -0,0 +1,67 @@
+<style>
+canvas { display: inline-block; }
+</style>
+<body></body>
+<script src="convolve.js"></script>
+<script src="smartblur.js"></script>
+<script src="fetch.js"></script>
+<script>
+
+var url = 'img/oranges.jpg'
+var smart_blur = new SmartBlurFilter ()
+
+var identityArray = [
+ 1,
+]
+var brightArray = [
+ 2,
+]
+var dimArray = [
+ 0.5,
+]
+var oddArray = [
+ -1, -1, -1,
+ -1, 8.5, -1,
+ -1, -1, -1,
+]
+var blurArray = [
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1
+]
+
+var edgeArray = [
+ 0,-1,0,
+ -1,6,-1,
+ 0,-1,0,
+]
+
+var kernels = [
+// new Kernel(1, 1, identityArray),
+ new Kernel(1, 1, brightArray),
+ new Kernel(1, 1, dimArray),
+// new Kernel(3, 3, oddArray),
+// new Kernel(3, 3, edgeArray).normalize(),
+ new Kernel(9, 9, blurArray).normalize(),
+]
+var convolvers = kernels.map(function(kernel){
+ return new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null)
+}).concat(kernels.map(function(kernel){
+ return new ConvolveOp(kernel, ConvolveOp.EDGE_ZERO_FILL, null)
+}))
+
+fromImage(url, function(canvas){
+ document.body.appendChild(canvas)
+ convolvers.forEach(function(convolver){
+ var convolved = convolver.convolveCanvas(canvas)
+ document.body.appendChild(convolved)
+ })
+})
+
+</script>
diff --git a/convolve.js b/convolve.js
new file mode 100644
index 0000000..2e59c6c
--- /dev/null
+++ b/convolve.js
@@ -0,0 +1,79 @@
+Kernel = function (w, h, a) {
+ this.w = w
+ this.h = h
+ this.a = a
+}
+Kernel.prototype.normalize = function(){
+ var n = 0;
+ for (var i = 0; i < this.a.length; i++)
+ n += this.a[i];
+ for (var i = 0; i < this.a.length; i++)
+ this.a[i] /= n;
+ return this
+}
+ConvolveOp = function (kernel, edge_zero_fill, idk) {
+ var k = kernel.a
+ var klen = k.length
+ var kw = kernel.w
+ var kh = kernel.h
+ var kx = -((kw / 2)|0)
+ var ky = -((kh / 2)|0)
+ this.filter = function(srcImageData, destImageData){
+ // assume these are both identically sized canvas objects..
+ var src = srcImageData.data
+ var dest = destImageData.data
+ var w = srcImageData.width
+ var h = srcImageData.height
+ var ix, iy, it, jx, jy, jt
+ var r, g, b, a
+
+ for (var i = 0, len = w*h; i < len; i++) {
+ ix = i%w
+ iy = (i/w)|0
+ it = i * 4
+ r = g = b = a = 0
+ for (var j = 0; j < klen; j++) {
+ jx = ix + j%kw + kx
+ jy = iy + ((j/kh)|0) + ky
+ jt = 4 * (jx + jy*w)
+ if (0 > jx || jx > w || 0 > jy || jy > h) {
+ if (edge_zero_fill) {
+ continue
+ }
+ jt = it
+ }
+ r += src[jt] * k[j]
+ g += src[jt+1] * k[j]
+ b += src[jt+2] * k[j]
+ a += src[jt+3] * k[j]
+ }
+ dest[it] = r
+ dest[it+1] = g
+ dest[it+2] = b
+ dest[it+3] = a
+ }
+ return destImageData
+ }
+ this.convolveCanvas = function(image){
+ var target = document.createElement("canvas")
+ var w = target.width = image.width
+ var h = target.height = image.height
+ var imagectx = image.getContext('2d')
+ var targetctx = target.getContext('2d')
+
+ targetctx.drawImage(image, 0, 0);
+
+ // get source pixels
+ var src = imagectx.getImageData(0,0,w,h)
+ var dest = targetctx.getImageData(0,0,w,h)
+
+ // convolve the cloned image
+ dest = this.filter(src, dest);
+
+ targetctx.putImageData(dest, 0,0)
+
+ return target
+ }
+}
+ConvolveOp.prototype.EDGE_NO_OP = 0
+ConvolveOp.prototype.EDGE_ZERO_FILL = 1
diff --git a/fetch.js b/fetch.js
new file mode 100644
index 0000000..c6e0cdb
--- /dev/null
+++ b/fetch.js
@@ -0,0 +1,30 @@
+function getNaturalDimensions (img) {
+ if (img.naturalWidth) {
+ return { naturalWidth: img.naturalWidth, naturalHeight: img.naturalHeight }
+ }
+ if (img.videoWidth) {
+ return { naturalWidth: img.videoWidth, naturalHeight: img.videoHeight }
+ }
+ return { naturalWidth: img.width, naturalHeight: img.height }
+}
+function fromImage (url, cb) {
+ var loaded = false
+ var img = new Image ()
+ img.onload = function(){
+ if (loaded) return
+ loaded = true
+ fromCanvas(img, cb)
+ }
+ if (img.src == url) { return img.onload() }
+ img.src = url
+ if (img.complete) { return img.onload() }
+}
+function fromCanvas (img, cb) {
+ var canvas = document.createElement("canvas")
+ var ctx = canvas.getContext('2d')
+ var dims = getNaturalDimensions(img)
+ canvas.width = dims.naturalWidth
+ canvas.height = dims.naturalHeight
+ ctx.drawImage(img,0,0,dims.naturalWidth,dims.naturalHeight,0,0,canvas.width,canvas.height)
+ cb(canvas)
+}
diff --git a/img/orange-01.jpg b/img/orange-01.jpg
new file mode 100644
index 0000000..735c0d8
--- /dev/null
+++ b/img/orange-01.jpg
Binary files differ
diff --git a/img/oranges.jpg b/img/oranges.jpg
new file mode 100644
index 0000000..443f157
--- /dev/null
+++ b/img/oranges.jpg
Binary files differ
diff --git a/smartblur.java b/smartblur.java
new file mode 100644
index 0000000..f62f2fa
--- /dev/null
+++ b/smartblur.java
@@ -0,0 +1,162 @@
+import java.awt.image.Kernel;
+import java.awt.image.BufferedImage;
+import java.awt.image.ConvolveOp;
+import java.awt.Graphics;
+
+// http://asserttrue.blogspot.ca/2010/08/implementing-smart-blur-in-java.html
+public class SmartBlurFilter
+{
+ double SENSITIVITY = 10;
+ int REGION_SIZE = 5;
+
+ float[] kernelArray =
+ {
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1
+ };
+
+ Kernel kernel = new Kernel(9, 9, normalizeKernel(kernelArray));
+
+ float[] normalizeKernel(float[] ar)
+ {
+ int n = 0;
+ for (int i = 0; i < ar.length; i++)
+ n += ar[i];
+ for (int i = 0; i < ar.length; i++)
+ ar[i] /= n;
+
+ return ar;
+ }
+
+ public double lerp(double a,double b, double amt)
+ {
+ return a + amt * (b - a);
+ }
+
+ public double getLerpAmount(double a, double cutoff)
+ {
+ if (a > cutoff)
+ return 1.0;
+
+ return a / cutoff;
+ }
+
+ public double rmsError(int[] pixels)
+ {
+ double ave = 0;
+
+ for (int i = 0; i < pixels.length; i++)
+ ave += (pixels[ i ] >> 8) & 255;
+
+ ave /= pixels.length;
+
+ double diff = 0;
+ double accumulator = 0;
+
+ for (int i = 0; i < pixels.length; i++)
+ {
+ diff = ((pixels[ i ] >> 8) & 255) - ave;
+ diff *= diff;
+ accumulator += diff;
+ }
+
+ double rms = accumulator / pixels.length;
+
+ rms = Math.sqrt(rms);
+
+ return rms;
+ }
+
+ int[] getSample(BufferedImage image, int x, int y, int size)
+ {
+ int[] pixels = {};
+
+ try
+ {
+ BufferedImage subimage = image.getSubimage(x,y, size, size);
+ pixels = subimage.getRGB(0,0,size,size,null,0,size);
+ }
+ catch(Exception e)
+ {
+ // will arrive here if we requested
+ // pixels outside the image bounds
+ }
+ return pixels;
+ }
+
+ int lerpPixel(int oldpixel, int newpixel, double amt)
+ {
+ int oldRed = (oldpixel >> 16) & 255;
+ int newRed = (newpixel >> 16) & 255;
+ int red = (int) lerp((double)oldRed, (double)newRed, amt) & 255;
+
+ int oldGreen = (oldpixel >> 8) & 255;
+ int newGreen = (newpixel >> 8) & 255;
+ int green = (int) lerp((double)oldGreen, (double)newGreen, amt) & 255;
+
+ int oldBlue = oldpixel & 255;
+ int newBlue = newpixel & 255;
+ int blue = (int) lerp((double)oldBlue, (double)newBlue, amt) & 255;
+
+ return (red << 16) | (green << 8) | blue;
+ }
+
+ int[] blurImage(BufferedImage image, int[] orig, int[] blur, double sensitivity)
+ {
+ int newPixel = 0;
+ double amt = 0;
+ int size = REGION_SIZE;
+
+ for (int i = 0; i < orig.length; i++)
+ {
+ int w = image.getWidth();
+ int[] pix = getSample(image, i % w, i / w, size);
+ if (pix.length == 0)
+ continue;
+
+ amt = getLerpAmount (rmsError(pix), sensitivity);
+ newPixel = lerpPixel(blur[ i ], orig[ i ], amt);
+ orig[ i ] = newPixel;
+ }
+
+ return orig;
+ }
+
+
+ public BufferedImage filter(BufferedImage image)
+ {
+ ConvolveOp convolver = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
+
+ // clone image into target
+ BufferedImage target = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
+ Graphics g = target.createGraphics();
+ g.drawImage(image, 0, 0, null);
+ g.dispose();
+
+ int w = target.getWidth();
+ int h = target.getHeight();
+
+ // get source pixels
+ int[] pixels = image.getRGB(0, 0, w, h, null, 0, w);
+
+ // blur the cloned image
+ target = convolver.filter(target, image);
+
+ // get the blurred pixels
+ int[] blurryPixels = target.getRGB(0, 0, w, h, null, 0, w);
+
+ // go thru the image and interpolate values
+ pixels = blurImage(image, pixels, blurryPixels, SENSITIVITY);
+
+ // replace original pixels with new ones
+ image.setRGB(0, 0, w, h, pixels, 0, w);
+ return image;
+ }
+} \ No newline at end of file
diff --git a/smartblur.js b/smartblur.js
new file mode 100644
index 0000000..181b1b4
--- /dev/null
+++ b/smartblur.js
@@ -0,0 +1,138 @@
+
+// http://asserttrue.blogspot.ca/2010/08/implementing-smart-blur-in-java.html
+SmartBlurFilter = function () {
+ var SENSITIVITY = 10
+ var REGION_SIZE = 5
+ var edge_zero_fill = true
+
+ var kx = -((REGION_SIZE/2)|0), ky = -((REGION_SIZE/2)|0), klen = REGION_SIZE*REGION_SIZE
+
+ var kernelArray = [
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1
+ ]
+
+ var kernel = new Kernel(9, 9, kernelArray)
+ kernel.normalize()
+
+ function lerp(a,b,amt) {
+ return a + amt * (b - a);
+ }
+
+ function rmsError(src, i, size) {
+ var ave = 0;
+
+ var len = size*size, data = src.data
+
+ var ix, iy
+ var r,g,b,a
+ var w = src.width, h = src.height
+ ix = i%w
+ iy = (i/w)|0
+ it = i * 4
+ r = g = b = a = 0
+ for (var j = 0; j < klen; j++) {
+ jx = ix + j%size + kx
+ jy = iy + ((j/size)|0) + ky
+ jt = 4 * (jx + jy*w)
+ if (0 > jx || jx > w || 0 > jy || jy > h) {
+ if (edge_zero_fill) {
+ continue
+ }
+ jt = it
+ }
+ r += data[jt]
+// g += data[jt+1]
+// b += data[jt+2]
+// a += data[jt+3]
+ }
+ r /= klen
+// g /= klen
+// b /= klen
+// a /= klen
+
+ var diff = 0
+ var accumulator = 0
+
+ for (var j = 0; j < klen; j++) {
+ jx = ix + j%size + kx
+ jy = iy + ((j/size)|0) + ky
+ jt = 4 * (jx + jy*w)
+ if (0 > jx || jx > w || 0 > jy || jy > h) {
+ if (edge_zero_fill) {
+ continue
+ }
+ jt = it
+ }
+ diff = data[ jt ] - ave
+ diff *= diff
+ accumulator += diff
+ }
+
+ var rms = accumulator / klen
+ rms = Math.sqrt(rms) / 255
+ return rms
+ }
+
+ function lerpPixel( src, dest, blur, i, amt) {
+ dest[i] = lerp(src[i], blur[i], amt)
+ dest[i+1] = lerp(src[i+1], blur[i+1], amt)
+ dest[i+2] = lerp(src[i+2], blur[i+2], amt)
+ dest[i+3] = lerp(src[i+3], blur[i+3], amt)
+ }
+
+ function blurImage( src, dest, blur, sensitivity ) {
+ var newPixel = 0
+ var amt = 0
+ var size = REGION_SIZE
+ var w = src.width, rms
+
+ var srcData = src.data
+ var destData = dest.data
+ var blurData = blur.data
+ for (var i = 0, len = srcData.length/4; i < len; i++) {
+
+ rms = rmsError(src, i, size)
+
+// amt = rms < sensitivity ? rms/sensitivity : 1.0
+ lerpPixel( srcData, destData, blurData, i*4, rms)
+ }
+
+ return dest
+ }
+
+ this.blur = function (src_canvas, dest_canvas) {
+ var convolver = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
+
+ // clone image into target
+ if (! dest_canvas) dest_canvas = document.createElement("canvas")
+ var w = dest_canvas.width = src_canvas.width
+ var h = dest_canvas.height = src_canvas.height
+ var srcctx = src_canvas.getContext('2d')
+ var destctx = dest_canvas.getContext('2d')
+
+ destctx.drawImage(src_canvas, 0, 0);
+
+ // get source pixels
+ var src = srcctx.getImageData(0,0,w,h)
+ var dest = destctx.getImageData(0,0,w,h)
+
+ // blur the cloned image
+ dest = convolver.filter(src, dest);
+
+ destctx.putImageData(dest, 0,0)
+ var blurred = destctx.getImageData(0,0,w,h)
+ var dest = srcctx.getImageData(0,0,w,h)
+ dest = blurImage(src, dest, blurred, SENSITIVITY)
+ destctx.putImageData(dest, 0,0)
+
+ return dest_canvas
+ }
+} \ No newline at end of file
diff --git a/test.html b/test.html
new file mode 100644
index 0000000..38aac26
--- /dev/null
+++ b/test.html
@@ -0,0 +1,21 @@
+<style>
+canvas { display: inline-block; }
+</style>
+<body></body>
+<script src="convolve.js"></script>
+<script src="smartblur.js"></script>
+<script src="fetch.js"></script>
+<script>
+
+var url = 'img/orange-01.jpg'
+var smart_blur = new SmartBlurFilter ()
+
+fromImage(url, function(src){
+ var dest = document.createElement("canvas")
+ var blurred = smart_blur.blur(src, dest)
+ document.body.appendChild(src)
+ document.body.appendChild(blurred)
+})
+
+</script>
+
diff --git a/webcam.html b/webcam.html
new file mode 100644
index 0000000..a2b9519
--- /dev/null
+++ b/webcam.html
@@ -0,0 +1,55 @@
+<style>
+canvas { display: inline-block; }
+</style>
+<body></body>
+<script src="convolve.js"></script>
+<script src="smartblur.js"></script>
+<script src="fetch.js"></script>
+<script>
+
+navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
+function getStream(cb){
+ var constraints = {
+ video: true,
+ audio: false
+ }
+ navigator.getUserMedia(constraints, success, error);
+ function success (stream) {
+ var video = document.createElement("video")
+ video.src = window.URL.createObjectURL(stream)
+ video.play()
+ cb(video)
+ }
+ function error(error){
+ console.log('navigator.getUserMedia error: ', error);
+ }
+}
+
+var smart_blur = new SmartBlurFilter ()
+var cam = document.createElement("canvas"), camctx = cam.getContext('2d')
+var blurred = document.createElement("canvas")
+document.body.appendChild(blurred)
+
+var camera
+getStream(function(video){
+ camera = video
+ document.body.appendChild(camera)
+ wait()
+})
+
+function wait () {
+ if (! camera.videoWidth) return requestAnimationFrame(wait)
+ cam.width = blurred.width = camera.videoWidth
+ cam.height = blurred.height = camera.videoHeight
+ animate()
+}
+function animate () {
+ requestAnimationFrame(animate)
+
+ camctx.drawImage(camera,0,0,cam.width,cam.height)
+ smart_blur.blur(cam, blurred)
+}
+
+
+</script>
+