|
<!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> |
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.