D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
biovisualize
Full window
Github gist
Phosphene viualization single file
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://d3js.org/d3.v3.min.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 type="javascript/worker"> 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}); }; </script> <script> //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!'); } }; </script> <script> function getInlineJS() { var js = document.querySelector('[type="javascript/worker"]').textContent; var blob = new Blob([js], {"type": "text\/plain"}); return URL.createObjectURL(blob); } var link = 'https://lh3.googleusercontent.com/-wsJQUCf8bBY/VqkpGaZG-qI/AAAAAAAAC7Y/BzdFJUXN1-w/s461-Ic42/MongolianSmileBW2.jpg'; var outputW = 400; var hermite = Hermite.init(getInlineJS()); 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>
https://d3js.org/d3.v3.min.js