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