Skip to content

Instantly share code, notes, and snippets.

@biovisualize
Last active January 27, 2016 20:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save biovisualize/7b2ceeff1a6e963e535c to your computer and use it in GitHub Desktop.
Save biovisualize/7b2ceeff1a6e963e535c to your computer and use it in GitHub Desktop.
Phosphenes visualization iBionics
onmessage = function (event) {
var img = event.data[0];
var data = img.data;
var W = event.data[1];
var H = event.data[2];
var W2 = event.data[3];
var H2 = event.data[4];
var img2 = event.data[5];
var data2 = img2.data;
var ratio_w = W / W2;
var ratio_h = H / H2;
var ratio_w_half = Math.ceil(ratio_w/2);
var ratio_h_half = Math.ceil(ratio_h/2);
for(var j = 0; j < H2; j++) {
for(var i = 0; i < W2; i++) {
var x2 = (i + j*W2) * 4;
var weight = 0;
var weights = 0;
var gx_r = gx_g = gx_b = gx_a = 0;
var center_y = (j + 0.5) * ratio_h;
for(var yy = Math.floor(j * ratio_h); yy < (j + 1) * ratio_h; yy++) {
var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
var center_x = (i + 0.5) * ratio_w;
var w0 = dy*dy; //pre-calc part of w
for(var xx = Math.floor(i * ratio_w); xx < (i + 1) * ratio_w; xx++) {
var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
var w = Math.sqrt(w0 + dx*dx);
if(w >= -1 && w <= 1){
//hermite filter
weight = 2 * w*w*w - 3*w*w + 1;
if(weight > 0) {
dx = 4*(xx + yy*W);
gx_r += weight * data[dx];
gx_g += weight * data[dx + 1];
gx_b += weight * data[dx + 2];
gx_a += weight * data[dx + 3];
weights += weight;
}
}
}
}
data2[x2] = gx_r / weights;
data2[x2 + 1] = gx_g / weights;
data2[x2 + 2] = gx_b / weights;
data2[x2 + 3] = gx_a / weights;
}
}
postMessage({data: img2});
};
//name: Hermite resize
//about: Fast image resize/resample using Hermite filter with JavaScript.
//author: ViliusL
//forkedby: calvintwr https://github.com/calvintwr/Hermite-resize
var Hermite = {
init: function(workerPath) {
var copy = Object.create(this);
if (!workerPath) throw new Error('Fail to provide worker\'s path when initializing.');
copy._workerPath = workerPath;
copy.init = undefined;
return copy;
},
resize: function(obj, callback) {
var self = this;
var mandatory = ['source', 'width', 'height'];
for(var i=0; i<mandatory.length; i++) {
var param = mandatory[i];
if(typeof obj[param] === 'undefined') {
throw new Error('#resize() is expecting mandatory param `'+ param +'`.');
}
}
if(typeof callback !== 'function') {
throw new Error('#resize() is expecting `callback` to be a function.');
}
var sourceElement = this._extract(obj.source);
var mimeType = this._mimeConverter(obj.format);
var canvas = (sourceElement[1] === 'CANVAS') ? sourceElement[0] : this._imageToCanvas(sourceElement[0]);
var startTime = Date.now();
var originalWidth = canvas.width;
var originalHeight = canvas.height;
var resizeToWidth = Math.round(obj.width);
var resizeToHeight = Math.round(obj.height);
var original = canvas.getContext('2d').getImageData(0, 0, originalWidth, originalHeight);
var resizedImage = canvas.getContext('2d').getImageData(0, 0, resizeToWidth, resizeToHeight);
//canvas.getContext('2d').clearRect(0, 0, originalWidth, originalHeight);
var worker = new Worker(self._workerPath);
worker.onmessage = function(event){
resizedImage = event.data.data;
console.log('Hermite resize completed in ' + (Math.round(Date.now() - startTime)/1000) + 's');
canvas.getContext('2d').clearRect(0, 0, resizeToWidth, resizeToHeight);
canvas.height = resizeToHeight;
canvas.width = resizeToWidth;
canvas.getContext('2d').putImageData(resizedImage, 0, 0);
if (obj.output === 'canvas') {
return callback(canvas);
} else if (obj.output === 'image') {
_makeIntoImage(self._canvas, self._mimeType, self._outputImageQuality, callback);
} else if (typeof obj.output === 'undefined') {
// when not defined, assume whatever element type is the input, is the desired output
if(sourceElement[1] === 'IMG') return callback( self._canvasToImage(canvas, obj.mimeType, obj.quality) );
return callback(canvas);
} else {
throw new Error('`output` is not valid.');
}
};
worker.postMessage([original, originalWidth, originalHeight, resizeToWidth, resizeToHeight, resizedImage]);
},
_extract: function(source) {
var results;
if (source.tagName) {
// getElementById sources will pass this
results = [source, source.tagName];
} else if (source[0].tagName) {
results = [source[0], source[0].tagName];
} else {
throw new Error('#resize() found `source` element to be invalid.');
}
if (['CANVAS','IMG'].indexOf(results[1]) === -1) throw new Error('#resize() expects element type of `img` or `canvas`.');
return results;
},
_imageToCanvas: function(image) {
// create a off-screen canvas
var c = document.createElement('canvas');
var context = c.getContext('2d');
c.height = image.height;
c.width = image.width;
context.drawImage(image, 0, 0, image.width, image.height);
return c;
},
_canvasToImage: function(canvas, mimeType, quality, callback) {
var image = new Image();
image.src = canvas.toDataURL(mimeType, quality);
return image;
},
_mimeConverter: function(format) {
// if undefined, assume no compression.
if (typeof format === 'undefined') return 'image/png';
var formats = ['raw', 'png', 'jpg', 'gif'];
var index = formats.indexOf(format);
if (index === -1) throw new Error('#inputImage() outputType can only be `raw`, `png`, `jpg` or `gif`');
if (index === 0 || index === 1) return 'image/png';
if (index === 2) return 'image/jpg';
if (index === 3) return 'image/gif';
throw new Error('#_mimeConverter fell through all cases!');
}
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="hermite.js"></script>
<style>
.panel {
fill: black;
}
canvas {
display: none;
}
.slider {
width: 400px;
}
#phosphene-image {
width: 400px;
}
</style>
</head>
<body>
<canvas id="phosphene-image"></canvas>
<div class="container"></div>
<div class="controls">
<input class="slider" type="range" min="8" max="32" value="8" step="1"><br>
<div class="count"></div>
</div>
<script>
var link = 'https://lh3.googleusercontent.com/-2Dq25Dtewk4/VqkoGGLdtxI/AAAAAAAAC7Q/r3BSJnQoSo0/s609-Ic42/BionicEye2.jpg';
var outputW = 400;
var hermite = Hermite.init('hermite-worker.js');
var phospheneCanvas = document.getElementById('phosphene-image');
var phospheneCtx = phospheneCanvas.getContext('2d');
var root = d3.select('.container').append('svg');
var panel = root.append('rect').classed('panel', true);
root.append('defs').append('filter')
.attr({
id: 'f1',
x: 0,
y: 0
})
.append('feGaussianBlur')
.attr({
in: 'SourceGraphic',
stdDeviation: 2
});
var imgData, img;
d3.select('.slider').on('input', function(d){
buildGrid(this.value);
});
img = new Image;
img.onload = function(){
buildGrid(8);
};
img.crossOrigin = '';
img.src = link;
function buildGrid(outputResolution){
d3.select('.count').text(outputResolution * outputResolution + ' phosphenes');
var imgW = outputResolution;
var imgH = imgW * img.height / img.width;
phospheneCanvas.width = img.width;
phospheneCanvas.height = img.height ;
phospheneCtx.drawImage(img, 0, 0, img.width, img.height);
hermite.resize({
source: phospheneCanvas,
width: imgW,
height: imgH
}, function(output) {
imgData = phospheneCtx.getImageData(0, 0, imgW, imgH).data;
var data = [];
var dataIdx = -1;
var r, g, b, gray;
for(var i = 0; i < imgData.length / 4; i++){
if(i % imgW === 0){
dataIdx++;
data.push([]);
}
r = imgData[i*4];
g = imgData[i*4+1];
b = imgData[i*4+2];
gray = r*0.2126 + g*0.7152 + b*0.0722;
data[dataIdx].push(gray);
}
buildPhosphenes(data, imgH, imgW);
});
}
function buildPhosphenes(data, imgH, imgW){
var outputH = outputW * imgH / imgW;
var cellSize = outputW / imgW;
root.attr({width: outputW, height: outputH});
panel.attr({width: outputW, height: outputH});
var rowGroup = root.selectAll('g.row')
.data(data);
rowGroup.enter().append('g')
.classed('row', true)
.attr({
transform: function(d, i){
var y = cellSize * i;
var x = 0;
if(i % 2 === 0){
x += cellSize / 2;
}
return 'translate(' + [x, y] + ')';
}
});
rowGroup
.attr({
transform: function(d, i){
var y = cellSize * i;
var x = 0;
if(i % 2 === 0){
x += cellSize / 4;
}
else{
x -= cellSize / 4;
}
return 'translate(' + [x, y] + ')';
}
});
rowGroup.exit().remove();
var cells = rowGroup.selectAll('circle.cell')
.data(function(d, i){
return d;
});
cells.enter().append('circle')
.classed('cell', true);
cells.attr({
r: (cellSize / 2) * 0.9,
fill: function(d){
return d3.rgb(d, d, d).toString();
},
filter: 'url(#f1)',
cy: cellSize / 2,
cx: function(d, i){
return cellSize * i + cellSize / 2;
}
});
cells.exit().remove();
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment