15c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Copyright 2014 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)'use strict'; 62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * A namespace for image filter utilities. 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)var filter = {}; 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Create a filter from name and options. 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} name Maps to a filter method name. 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} options A map of filter-specific options. 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {function(ImageData,ImageData,number,number)} created function. 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.create = function(name, options) { 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var filterFunc = filter[name](options); 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return function() { 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var time = Date.now(); 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filterFunc.apply(null, arguments); 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dst = arguments[0]; 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var mPixPerSec = dst.width * dst.height / 1000 / (Date.now() - time); 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ImageUtil.trace.report(name, Math.round(mPixPerSec * 10) / 10 + 'Mps'); 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Apply a filter to a image by splitting it into strips. 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * To be used with large images to avoid freezing up the UI. 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {HTMLCanvasElement} dstCanvas Destination canvas. 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {HTMLCanvasElement} srcCanvas Source canvas. 372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {function(ImageData,ImageData,number,number)} filterFunc Filter. 382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {function(number, number)} progressCallback Progress callback. 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} maxPixelsPerStrip Pixel number to process at once. 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.applyByStrips = function( 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstCanvas, srcCanvas, filterFunc, progressCallback, maxPixelsPerStrip) { 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstContext = dstCanvas.getContext('2d'); 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcContext = srcCanvas.getContext('2d'); 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var source = srcContext.getImageData(0, 0, srcCanvas.width, srcCanvas.height); 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var stripCount = Math.ceil(srcCanvas.width * srcCanvas.height / 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) (maxPixelsPerStrip || 1000000)); // 1 Mpix is a reasonable default. 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var strip = srcContext.getImageData(0, 0, 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcCanvas.width, Math.ceil(srcCanvas.height / stripCount)); 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var offset = 0; 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) function filterStrip() { 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If the strip overlaps the bottom of the source image we cannot shrink it 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // and we cannot fill it partially (since canvas.putImageData always draws 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // the entire buffer). 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Instead we move the strip up several lines (converting those lines 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // twice is a small price to pay). 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (offset > source.height - strip.height) { 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) offset = source.height - strip.height; 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filterFunc(strip, source, 0, offset); 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstContext.putImageData(strip, 0, offset); 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) offset += strip.height; 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (offset < source.height) { 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) setTimeout(filterStrip, 0); 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ImageUtil.trace.reportTimer('filter-commit'); 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) progressCallback(offset, source.height); 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ImageUtil.trace.resetTimer('filter-commit'); 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filterStrip(); 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Return a color histogram for an image. 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {HTMLCanvasElement|ImageData} source Image data to analyze. 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {{r: Array.<number>, g: Array.<number>, b: Array.<number>}} 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * histogram. 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.getHistogram = function(source) { 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var imageData; 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (source.constructor.name == 'HTMLCanvasElement') { 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) imageData = source.getContext('2d'). 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) getImageData(0, 0, source.width, source.height); 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) imageData = source; 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var r = []; 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var g = []; 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var b = []; 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i != 256; i++) { 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) r.push(0); 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) g.push(0); 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) b.push(0); 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var data = imageData.data; 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var maxIndex = 4 * imageData.width * imageData.height; 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var index = 0; index != maxIndex;) { 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) r[data[index++]]++; 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) g[data[index++]]++; 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) b[data[index++]]++; 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) index++; 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return { r: r, g: g, b: b }; 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Compute the function for every integer value from 0 up to maxArg. 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Rounds and clips the results to fit the [0..255] range. 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Useful to speed up pixel manipulations. 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} maxArg Maximum argument value (inclusive). 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {function(number): number} func Function to precompute. 1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @return {Uint8Array} Computed results. 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.precompute = function(maxArg, func) { 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var results = new Uint8Array(maxArg + 1); 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var arg = 0; arg <= maxArg; arg++) { 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) results[arg] = Math.max(0, Math.min(0xFF, Math.round(func(arg)))); 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return results; 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Convert pixels by applying conversion tables to each channel individually. 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} rMap Red channel conversion table. 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} gMap Green channel conversion table. 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} bMap Blue channel conversion table. 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {ImageData} dst Destination image data. Can be smaller than the 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * source, must completely fit inside the source. 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {ImageData} src Source image data. 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} offsetX Horizontal offset of dst relative to src. 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} offsetY Vertical offset of dst relative to src. 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.mapPixels = function(rMap, gMap, bMap, dst, src, offsetX, offsetY) { 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstData = dst.data; 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstWidth = dst.width; 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstHeight = dst.height; 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcData = src.data; 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcWidth = src.width; 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcHeight = src.height; 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (offsetX < 0 || offsetX + dstWidth > srcWidth || 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) offsetY < 0 || offsetY + dstHeight > srcHeight) 1621320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci throw new Error('Invalid offset'); 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstIndex = 0; 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var y = 0; y != dstHeight; y++) { 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcIndex = (offsetX + (offsetY + y) * srcWidth) * 4; 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var x = 0; x != dstWidth; x++) { 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = rMap[srcData[srcIndex++]]; 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = gMap[srcData[srcIndex++]]; 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = bMap[srcData[srcIndex++]]; 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstIndex++; 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcIndex++; 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Number of digits after period(in binary form) to preserve. 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @type {number} 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.FIXED_POINT_SHIFT = 16; 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Maximum value that can be represented in fixed point without overflow. 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @type {number} 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.MAX_FLOAT_VALUE = 0x7FFFFFFF >> filter.FIXED_POINT_SHIFT; 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Converts floating point to fixed. 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} x Number to convert. 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {number} Converted number. 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.floatToFixedPoint = function(x) { 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Math.round on negative arguments causes V8 to deoptimize the calling 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // function, so we are using >> 0 instead. 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return (x * (1 << filter.FIXED_POINT_SHIFT)) >> 0; 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Perform an image convolution with a symmetrical 5x5 matrix: 2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 0 0 w3 0 0 2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 0 w2 w1 w2 0 2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * w3 w1 w0 w1 w3 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 0 w2 w1 w2 0 2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 0 0 w3 0 0 2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} weights See the picture above. 2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {ImageData} dst Destination image data. Can be smaller than the 2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * source, must completely fit inside the source. 2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {ImageData} src Source image data. 2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} offsetX Horizontal offset of dst relative to src. 2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} offsetY Vertical offset of dst relative to src. 2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.convolve5x5 = function(weights, dst, src, offsetX, offsetY) { 2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var w0 = filter.floatToFixedPoint(weights[0]); 2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var w1 = filter.floatToFixedPoint(weights[1]); 2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var w2 = filter.floatToFixedPoint(weights[2]); 2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var w3 = filter.floatToFixedPoint(weights[3]); 2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstData = dst.data; 2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstWidth = dst.width; 2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstHeight = dst.height; 2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstStride = dstWidth * 4; 2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcData = src.data; 2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcWidth = src.width; 2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcHeight = src.height; 2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcStride = srcWidth * 4; 2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcStride2 = srcStride * 2; 2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (offsetX < 0 || offsetX + dstWidth > srcWidth || 2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) offsetY < 0 || offsetY + dstHeight > srcHeight) 2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) throw new Error('Invalid offset'); 2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Javascript is not very good at inlining constants. 2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We inline manually and assert that the constant is equal to the variable. 2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (filter.FIXED_POINT_SHIFT != 16) 2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) throw new Error('Wrong fixed point shift'); 2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var margin = 2; 2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var startX = Math.max(0, margin - offsetX); 2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var endX = Math.min(dstWidth, srcWidth - margin - offsetX); 2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var startY = Math.max(0, margin - offsetY); 2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var endY = Math.min(dstHeight, srcHeight - margin - offsetY); 2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var y = startY; y != endY; y++) { 2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstIndex = y * dstStride + startX * 4; 2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcIndex = (y + offsetY) * srcStride + (startX + offsetX) * 4; 2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var x = startX; x != endX; x++) { 2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var c = 0; c != 3; c++) { 2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var sum = w0 * srcData[srcIndex] + 2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) w1 * (srcData[srcIndex - 4] + 2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcData[srcIndex + 4] + 2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcData[srcIndex - srcStride] + 2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcData[srcIndex + srcStride]) + 2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) w2 * (srcData[srcIndex - srcStride - 4] + 2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcData[srcIndex + srcStride - 4] + 2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcData[srcIndex - srcStride + 4] + 2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcData[srcIndex + srcStride + 4]) + 2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) w3 * (srcData[srcIndex - 8] + 2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcData[srcIndex + 8] + 2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcData[srcIndex - srcStride2] + 2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcData[srcIndex + srcStride2]); 2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (sum < 0) 2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = 0; 2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else if (sum > 0xFF0000) 2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = 0xFF; 2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else 2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = sum >> 16; 2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcIndex++; 2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcIndex++; 2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstIndex++; 2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Compute the average color for the image. 2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 2862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {ImageData} imageData Image data to analyze. 2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {{r: number, g: number, b: number}} average color. 2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.getAverageColor = function(imageData) { 2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var data = imageData.data; 2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var width = imageData.width; 2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var height = imageData.height; 2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var total = 0; 2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var r = 0; 2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var g = 0; 2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var b = 0; 2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var maxIndex = 4 * width * height; 3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i != maxIndex;) { 3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) total++; 3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) r += data[i++]; 3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) g += data[i++]; 3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) b += data[i++]; 3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) i++; 3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (total == 0) return { r: 0, g: 0, b: 0 }; 3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return { r: r / total, g: g / total, b: b / total }; 3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Compute the average color with more weight given to pixes at the center. 3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 3142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) * @param {ImageData} imageData Image data to analyze. 3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {{r: number, g: number, b: number}} weighted average color. 3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.getWeightedAverageColor = function(imageData) { 3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var data = imageData.data; 3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var width = imageData.width; 3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var height = imageData.height; 3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var total = 0; 3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var r = 0; 3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var g = 0; 3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var b = 0; 3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var center = Math.floor(width / 2); 3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var maxDist = center * Math.sqrt(2); 3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) maxDist *= 2; // Weaken the effect of distance 3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var i = 0; 3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var x = 0; x != width; x++) { 3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var y = 0; y != height; y++) { 3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dist = Math.sqrt( 3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) (x - center) * (x - center) + (y - center) * (y - center)); 3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var weight = (maxDist - dist) / maxDist; 3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) total += weight; 3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) r += data[i++] * weight; 3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) g += data[i++] * weight; 3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) b += data[i++] * weight; 3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) i++; 3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (total == 0) return { r: 0, g: 0, b: 0 }; 3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return { r: r / total, g: g / total, b: b / total }; 3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Copy part of src image to dst, applying matrix color filter on-the-fly. 3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * The copied part of src should completely fit into dst (there is no clipping 3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * on either side). 3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} matrix 3x3 color matrix. 3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {ImageData} dst Destination image data. 3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {ImageData} src Source image data. 3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} offsetX X offset in source to start processing. 3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} offsetY Y offset in source to start processing. 3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.colorMatrix3x3 = function(matrix, dst, src, offsetX, offsetY) { 3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var c11 = filter.floatToFixedPoint(matrix[0]); 3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var c12 = filter.floatToFixedPoint(matrix[1]); 3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var c13 = filter.floatToFixedPoint(matrix[2]); 3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var c21 = filter.floatToFixedPoint(matrix[3]); 3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var c22 = filter.floatToFixedPoint(matrix[4]); 3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var c23 = filter.floatToFixedPoint(matrix[5]); 3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var c31 = filter.floatToFixedPoint(matrix[6]); 3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var c32 = filter.floatToFixedPoint(matrix[7]); 3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var c33 = filter.floatToFixedPoint(matrix[8]); 3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstData = dst.data; 3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstWidth = dst.width; 3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstHeight = dst.height; 3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcData = src.data; 3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcWidth = src.width; 3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcHeight = src.height; 3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (offsetX < 0 || offsetX + dstWidth > srcWidth || 3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) offsetY < 0 || offsetY + dstHeight > srcHeight) 3821320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci throw new Error('Invalid offset'); 3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Javascript is not very good at inlining constants. 3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We inline manually and assert that the constant is equal to the variable. 3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (filter.FIXED_POINT_SHIFT != 16) 3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) throw new Error('Wrong fixed point shift'); 3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var dstIndex = 0; 3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var y = 0; y != dstHeight; y++) { 3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var srcIndex = (offsetX + (offsetY + y) * srcWidth) * 4; 3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var x = 0; x != dstWidth; x++) { 3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var r = srcData[srcIndex++]; 3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var g = srcData[srcIndex++]; 3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var b = srcData[srcIndex++]; 3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) srcIndex++; 3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var rNew = r * c11 + g * c12 + b * c13; 3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var gNew = r * c21 + g * c22 + b * c23; 4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var bNew = r * c31 + g * c32 + b * c33; 4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (rNew < 0) { 4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = 0; 4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else if (rNew > 0xFF0000) { 4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = 0xFF; 4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = rNew >> 16; 4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (gNew < 0) { 4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = 0; 4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else if (gNew > 0xFF0000) { 4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = 0xFF; 4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = gNew >> 16; 4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (bNew < 0) { 4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = 0; 4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else if (bNew > 0xFF0000) { 4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = 0xFF; 4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstData[dstIndex++] = bNew >> 16; 4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dstIndex++; 4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Return a convolution filter function bound to specific weights. 4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * 4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} weights Weights for the convolution matrix 4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * (not normalized). 4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {function(ImageData,ImageData,number,number)} Convolution filter. 4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.createConvolutionFilter = function(weights) { 4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Normalize the weights to sum to 1. 4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var total = 0; 4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i != weights.length; i++) { 4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) total += weights[i] * (i ? 4 : 1); 4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var normalized = []; 4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (i = 0; i != weights.length; i++) { 4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) normalized.push(weights[i] / total); 4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (; i < 4; i++) { 4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) normalized.push(0); 4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var maxWeightedSum = 0xFF * 4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Math.abs(normalized[0]) + 4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Math.abs(normalized[1]) * 4 + 4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Math.abs(normalized[2]) * 4 + 4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Math.abs(normalized[3]) * 4; 4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (maxWeightedSum > filter.MAX_FLOAT_VALUE) 4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) throw new Error('convolve5x5 cannot convert the weights to fixed point'); 4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.convolve5x5.bind(null, normalized); 4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Creates matrix filter. 4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} matrix Color transformation matrix. 4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {function(ImageData,ImageData,number,number)} Matrix filter. 4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.createColorMatrixFilter = function(matrix) { 4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var r = 0; r != 3; r++) { 4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var maxRowSum = 0; 4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var c = 0; c != 3; c++) { 4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) maxRowSum += 0xFF * Math.abs(matrix[r * 3 + c]); 4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (maxRowSum > filter.MAX_FLOAT_VALUE) 4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) throw new Error( 4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'colorMatrix3x3 cannot convert the matrix to fixed point'); 4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.colorMatrix3x3.bind(null, matrix); 4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Return a blur filter. 4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} options Blur options. 4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {function(ImageData,ImageData,number,number)} Blur filter. 4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.blur = function(options) { 4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (options.radius == 1) 4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.createConvolutionFilter( 4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [1, options.strength]); 4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else if (options.radius == 2) 4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.createConvolutionFilter( 4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [1, options.strength, options.strength]); 4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else 4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.createConvolutionFilter( 4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [1, options.strength, options.strength, options.strength]); 4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Return a sharpen filter. 5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} options Sharpen options. 5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {function(ImageData,ImageData,number,number)} Sharpen filter. 5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.sharpen = function(options) { 5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (options.radius == 1) 5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.createConvolutionFilter( 5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [5, -options.strength]); 5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else if (options.radius == 2) 5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.createConvolutionFilter( 5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [10, -options.strength, -options.strength]); 5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else 5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.createConvolutionFilter( 5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [15, -options.strength, -options.strength, -options.strength]); 5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Return an exposure filter. 5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} options exposure options. 5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {function(ImageData,ImageData,number,number)} Exposure filter. 5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.exposure = function(options) { 5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var pixelMap = filter.precompute( 5231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci 255, 5241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci function(value) { 5251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci if (options.brightness > 0) { 5261320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci value *= (1 + options.brightness); 5271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } else { 5281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci value += (0xFF - value) * options.brightness; 5291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci } 5301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci return 0x80 + 5311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci (value - 0x80) * Math.tan((options.contrast + 1) * Math.PI / 4); 5321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci }); 5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.mapPixels.bind(null, pixelMap, pixelMap, pixelMap); 5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 5385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Return a color autofix filter. 5395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} options Histogram for autofix. 5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {function(ImageData,ImageData,number,number)} Autofix filter. 5415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.autofix = function(options) { 5435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.mapPixels.bind(null, 5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filter.autofix.stretchColors(options.histogram.r), 5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filter.autofix.stretchColors(options.histogram.g), 5465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filter.autofix.stretchColors(options.histogram.b)); 5475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 5505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Return a conversion table that stretches the range of colors used 5515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * in the image to 0..255. 5525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} channelHistogram Histogram to calculate range. 5535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {Uint8Array} Color mapping array. 5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.autofix.stretchColors = function(channelHistogram) { 5565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var range = filter.autofix.getRange(channelHistogram); 5575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.precompute( 5585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 255, 5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) function(x) { 5605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return (x - range.first) / (range.last - range.first) * 255; 5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 5625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ); 5635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 5645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 5665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Return a range that encloses non-zero elements values in a histogram array. 5675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} channelHistogram Histogram to analyze. 5685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {{first: number, last: number}} Channel range in histogram. 5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.autofix.getRange = function(channelHistogram) { 5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var first = 0; 5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while (first < channelHistogram.length && channelHistogram[first] == 0) 5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) first++; 5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var last = channelHistogram.length - 1; 5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while (last >= 0 && channelHistogram[last] == 0) 5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) last--; 5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (first >= last) // Stretching does not make sense 5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return {first: 0, last: channelHistogram.length - 1}; 5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else 5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return {first: first, last: last}; 5835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 5845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Minimum channel offset that makes visual difference. If autofix calculated 5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * offset is less than SENSITIVITY, probably autofix is not needed. 5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Reasonable empirical value. 5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @type {number} 5905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.autofix.SENSITIVITY = 8; 5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 5945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array.<number>} channelHistogram Histogram to analyze. 5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {boolean} True if stretching this range to 0..255 would make 5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * a visible difference. 5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.autofix.needsStretching = function(channelHistogram) { 5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var range = filter.autofix.getRange(channelHistogram); 6005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return (range.first >= filter.autofix.SENSITIVITY || 6015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) range.last <= 255 - filter.autofix.SENSITIVITY); 6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {{r: Array.<number>, g: Array.<number>, b: Array.<number>}} histogram 6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {boolean} True if the autofix would make a visible difference. 6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)filter.autofix.isApplicable = function(histogram) { 6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return filter.autofix.needsStretching(histogram.r) || 6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filter.autofix.needsStretching(histogram.g) || 6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) filter.autofix.needsStretching(histogram.b); 6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 613