Skip to content

Instantly share code, notes, and snippets.

@maettig
Forked from 140bytes/LICENSE.txt
Created May 7, 2012 08:09
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maettig/2626599 to your computer and use it in GitHub Desktop.
Save maettig/2626599 to your computer and use it in GitHub Desktop.
imageconvolution in 140byt.es

I implemented the imageconvolution() function from PHP in 140byt.es JavaScript. The function applies a 3 × 3 convolution matrix to an image. It can be used to create a surprisingly large amount of image filters like soften and sharpen.

Click here to see the script in action.

Note: It's possible to load images from foreign domains but due to security restrictions it's not possible to read and manipulate the pixel data from such images. Therefor the script will only work with images from the same domain.

function(a, b, c, d, e, f, g)
{
for (g = a.length;
g--; //for all bytes in the pixel array
b[g] = g % 4 > 2 //bytes 0, 1 and 2 are RGB, byte 3 is the alpha channel
? a[g] //copy alpha channel without changing it
: j < 0 //limit the calculated sum to >=0
? 0
: j >> 8 //limit the calculated sum to <=255
? 255 //Opera accepts -1 here, Firefox does not
: j)
for (var h = 9, //the 3x3 matrix contains 9 values
i = c, //this is the offset in the pixel array
j = f; //start with the offset instead of adding it later
h--; //for all values in the 3x3 matrix
i -= h % 3 //decrease the offset in the pixel array
? 1 //either by 1
: c - 2) //or by the image width
j += a[g + i * 4 + 4] //sum of 3x3 pixels
* d[h] / e //multiplied by the matrix, divided by the divisor
}
function(a,b,c,d,e,f,g){for(g=a.length;g--;b[g]=g%4>2?a[g]:j<0?0:j>>8?255:j)for(var h=9,i=c,j=f;h--;i-=h%3?1:c-2)j+=a[g+i*4+4]*d[h]/e}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2012 Thiemo Mättig <http://maettig.com>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "imageconvolution",
"description": "Apply a 3x3 convolution matrix to an image, using coefficient and offset.",
"keywords": [
"canvas",
"filter",
"image"
]
}
<!DOCTYPE html>
<style>
label { text-align: right; width: 8em; }
label, .matrix { display: inline-block; vertical-align: top; }
</style>
<p><label>Preset:</label> <select onchange="loadPreset(this)">
<option value="0,0,0,0,1,0,0,0,0,1,0">None (identity)</option>
<option value="0,1,0,0,0,0,0,0,0,1,0">Shift down</option>
<option value="0,0,0,0,2,0,0,0,0,1,-255">Contrast</option>
<option value="0,0,0,0,1,0,0,0,0,2,127">Brighten</option>
<option value="0,0,0,0,-1,0,0,0,0,1,255">Invert</option>
<option value="0,1,0,1,4,1,0,1,0,8,0">Soften</option>
<option value="1,1,1,1,1,1,1,1,1,9,0">Blur</option>
<option value="0,-1,0,-1,8,-1,0,-1,0,4,0">Sharpen</option>
<option value="0,-2,0,-2,9,-2,0,-2,0,1,0">Enhance</option>
<option value="-1,0,0,0,1,0,0,0,1,1,0">Emboss</option>
<option value="-1,0,0,0,0,0,0,0,1,1,127">Relief</option>
<option value="0,-1,0,-1,4,-1,0,-1,0,1,255">Outline</option>
</select></p>
<label>Matrix:</label> <div class="matrix">
<input type="number" value="0"> <input type="number" value="0"> <input type="number" value="0"><br>
<input type="number" value="0"> <input type="number" value="1"> <input type="number" value="0"><br>
<input type="number" value="0"> <input type="number" value="0"> <input type="number" value="0">
</div>
<p><label>Divisor:</label> <input id="div" name="div" type="number" value="1"><br>
<label>Offset:</label> <input id="offset" name="offset" type="number" value="0"></p>
<p><label></label> <input type="button" value="Refresh" onclick="refresh(this)"></p>
<label>Image file:</label> <div class="matrix"><input id="url" name="url" size="45"></div>
<p><label></label> <input type="button" value="Load" onclick="load(this)"></p>
<p><canvas id="c"></canvas> <small>Loading...</small></p>
<script>
var imageconvolution = function(a,b,c,d,e,f,g){for(g=a.length;g--;b[g]=g%4>2?a[g]:j<0?0:j>>8?255:j)for(var h=9,i=c,j=f;h--;i-=h%3?1:c-2)j+=a[g+i*4+4]*d[h]/e}
function load()
{
var s = inputs['url'].value.replace(/^\s+|\s+$/g, '');
if (s && image.src != s) image.src = s;
}
function refresh()
{
var matrix = [];
for (var i = inputs.length; i--; ) matrix[i] = inputs[i].value * 1;
var c = document.getElementsByTagName('canvas')[0];
c.width = image.width;
c.height = image.height;
var a = c.getContext('2d');
a.drawImage(image, 0, 0);
try
{
var sourceData = a.getImageData(0, 0, c.width, c.height);
var targetData = a.createImageData(sourceData);
var t0 = new Date().getTime();
imageconvolution(sourceData.data, targetData.data, sourceData.width, matrix,
inputs['div'].value * 1, inputs['offset'].value * 1);
var t1 = new Date().getTime();
a.putImageData(targetData, 0, 0);
document.getElementsByTagName('SMALL')[0].firstChild.data = '(' + Math.max(0, (t1 - t0) / 1000) + 's)';
}
catch (e) { alert(e); }
}
function loadPreset(a)
{
a = a.options[a.selectedIndex].value.split(',');
for (var i in a) document.getElementsByTagName('INPUT')[i].value = a[i];
refresh();
}
var inputs = document.getElementsByTagName('INPUT');
var image = new Image();
image.onload = refresh;
</script>
@maettig
Copy link
Author

maettig commented May 7, 2012

An important feature that is missing in my implementation is how it handles the edges. My function is simplified and wraps at the left and right but does nothing at the top and the bottom, resulting in a dark border. There are several ways to avoid this like simply skipping the top and bottom pixel rows.

The current implementation is 134 bytes. 4 more bytes can be saved by omitting the divisor parameter. Without this parameter the sum of all numbers in the array must be 1.

I would like to put the .data and .length properties back into the function. But you always need a second parameter since you can't create a second image without knowing the context. However, it's totally possible to avoid using a second image. The only thing you need to do is to store the line you are currently changing since it is needed to calculate the next line. Such a “clever” solution can save both memory and time but will make the code more complicated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment