| 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 | |
| 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="../lib/d3.js"></script>--> | |
| <script src="http://d3js.org/d3.v3.min.js"></script> | |
| <script src="hermite.js"></script> | |
| <style> | |
| .panel { | |
| fill: black; | |
| } | |
| .controls { | |
| position: absolute; | |
| } | |
| canvas { | |
| margin-top: 40px; | |
| float: left; | |
| } | |
| #imageLoader, .slider, .count { | |
| float: left; | |
| } | |
| .slider { | |
| width: 400px; | |
| } | |
| #base-image, #phosphene-image { | |
| width: 400px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="controls"> | |
| <input type="file" id="imageLoader" name="imageLoader"></input> | |
| <input class="slider" type="range" min="8" max="32" value="8" step="1"> | |
| <div class="count"></div> | |
| </div> | |
| <canvas id="base-image"></canvas> | |
| <canvas id="phosphene-image"></canvas> | |
| <div class="container"></div> | |
| <script> | |
| var outputW = 400; | |
| var hermite = Hermite.init('hermite-worker.js'); | |
| var imgLoader = document.getElementById('imageLoader'); | |
| imgLoader.addEventListener('change', handleImage, false); | |
| var baseCanvas = document.getElementById('base-image'); | |
| var baseCtx = baseCanvas.getContext('2d'); | |
| 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); | |
| }); | |
| function handleImage(e){ | |
| var reader = new FileReader(); | |
| reader.onload = function(event){ | |
| img = new Image; | |
| img.onload = function(){ | |
| buildBaseImg(); | |
| buildGrid(8); | |
| }; | |
| img.src = event.target.result; | |
| }; | |
| reader.readAsDataURL(e.target.files[0]); | |
| } | |
| function buildBaseImg(){ | |
| var imgW = img.width; | |
| var imgH = img.height; | |
| baseCanvas.width = imgW; | |
| baseCanvas.height = imgH; | |
| baseCtx.clearRect(0, 0, imgW, imgH); | |
| baseCtx.drawImage(img, 0, 0, imgW, imgH); | |
| imgData = baseCtx.getImageData(0, 0, imgW, imgH).data; | |
| } | |
| 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 cellSize = outputW / imgW; | |
| 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); | |
| } | |
| var outputH = outputW * imgH / 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