123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410 |
- /*
- Smooth.js version 0.1.5
- Turn arrays into smooth functions.
- Copyright 2012 Spencer Cohen
- Licensed under MIT license (see "Smooth.js MIT license.txt")
- */
- /*Constants (these are accessible by Smooth.WHATEVER in user space)
- */
- (function() {
- var AbstractInterpolator, CubicInterpolator, Enum, LinearInterpolator, NearestInterpolator, PI, SincFilterInterpolator, Smooth, clipClamp, clipMirror, clipPeriodic, defaultConfig, getColumn, getType, isValidNumber, k, makeLanczosWindow, makeScaledFunction, makeSincKernel, normalizeScaleTo, root, shallowCopy, sin, sinc, v, validateNumber, validateVector,
- __hasProp = Object.prototype.hasOwnProperty,
- __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
- Enum = {
- /*Interpolation methods
- */
- METHOD_NEAREST: 'nearest',
- METHOD_LINEAR: 'linear',
- METHOD_CUBIC: 'cubic',
- METHOD_LANCZOS: 'lanczos',
- METHOD_SINC: 'sinc',
- /*Input clipping modes
- */
- CLIP_CLAMP: 'clamp',
- CLIP_ZERO: 'zero',
- CLIP_PERIODIC: 'periodic',
- CLIP_MIRROR: 'mirror',
- /* Constants for control over the cubic interpolation tension
- */
- CUBIC_TENSION_DEFAULT: 0,
- CUBIC_TENSION_CATMULL_ROM: 0
- };
- defaultConfig = {
- method: Enum.METHOD_CUBIC,
- cubicTension: Enum.CUBIC_TENSION_DEFAULT,
- clip: Enum.CLIP_CLAMP,
- scaleTo: 0,
- sincFilterSize: 2,
- sincWindow: void 0
- };
- /*Index clipping functions
- */
- clipClamp = function(i, n) {
- return Math.max(0, Math.min(i, n - 1));
- };
- clipPeriodic = function(i, n) {
- i = i % n;
- if (i < 0) i += n;
- return i;
- };
- clipMirror = function(i, n) {
- var period;
- period = 2 * (n - 1);
- i = clipPeriodic(i, period);
- if (i > n - 1) i = period - i;
- return i;
- };
- /*
- Abstract scalar interpolation class which provides common functionality for all interpolators
-
- Subclasses must override interpolate().
- */
- AbstractInterpolator = (function() {
- function AbstractInterpolator(array, config) {
- var clipHelpers;
- this.array = array.slice(0);
- this.length = this.array.length;
- clipHelpers = {
- clamp: this.clipHelperClamp,
- zero: this.clipHelperZero,
- periodic: this.clipHelperPeriodic,
- mirror: this.clipHelperMirror
- };
- this.clipHelper = clipHelpers[config.clip];
- if (this.clipHelper == null) throw "Invalid clip: " + config.clip;
- }
- AbstractInterpolator.prototype.getClippedInput = function(i) {
- if ((0 <= i && i < this.length)) {
- return this.array[i];
- } else {
- return this.clipHelper(i);
- }
- };
- AbstractInterpolator.prototype.clipHelperClamp = function(i) {
- return this.array[clipClamp(i, this.length)];
- };
- AbstractInterpolator.prototype.clipHelperZero = function(i) {
- return 0;
- };
- AbstractInterpolator.prototype.clipHelperPeriodic = function(i) {
- return this.array[clipPeriodic(i, this.length)];
- };
- AbstractInterpolator.prototype.clipHelperMirror = function(i) {
- return this.array[clipMirror(i, this.length)];
- };
- AbstractInterpolator.prototype.interpolate = function(t) {
- throw 'Subclasses of AbstractInterpolator must override the interpolate() method.';
- };
- return AbstractInterpolator;
- })();
- NearestInterpolator = (function(_super) {
- __extends(NearestInterpolator, _super);
- function NearestInterpolator() {
- NearestInterpolator.__super__.constructor.apply(this, arguments);
- }
- NearestInterpolator.prototype.interpolate = function(t) {
- return this.getClippedInput(Math.round(t));
- };
- return NearestInterpolator;
- })(AbstractInterpolator);
- LinearInterpolator = (function(_super) {
- __extends(LinearInterpolator, _super);
- function LinearInterpolator() {
- LinearInterpolator.__super__.constructor.apply(this, arguments);
- }
- LinearInterpolator.prototype.interpolate = function(t) {
- var a, b, k;
- k = Math.floor(t);
- a = this.getClippedInput(k);
- b = this.getClippedInput(k + 1);
- t -= k;
- return (1 - t) * a + t * b;
- };
- return LinearInterpolator;
- })(AbstractInterpolator);
- CubicInterpolator = (function(_super) {
- __extends(CubicInterpolator, _super);
- function CubicInterpolator(array, config) {
- this.tangentFactor = 1 - Math.max(0, Math.min(1, config.cubicTension));
- CubicInterpolator.__super__.constructor.apply(this, arguments);
- }
- CubicInterpolator.prototype.getTangent = function(k) {
- return this.tangentFactor * (this.getClippedInput(k + 1) - this.getClippedInput(k - 1)) / 2;
- };
- CubicInterpolator.prototype.interpolate = function(t) {
- var k, m, p, t2, t3;
- k = Math.floor(t);
- m = [this.getTangent(k), this.getTangent(k + 1)];
- p = [this.getClippedInput(k), this.getClippedInput(k + 1)];
- t -= k;
- t2 = t * t;
- t3 = t * t2;
- return (2 * t3 - 3 * t2 + 1) * p[0] + (t3 - 2 * t2 + t) * m[0] + (-2 * t3 + 3 * t2) * p[1] + (t3 - t2) * m[1];
- };
- return CubicInterpolator;
- })(AbstractInterpolator);
- sin = Math.sin, PI = Math.PI;
- sinc = function(x) {
- if (x === 0) {
- return 1;
- } else {
- return sin(PI * x) / (PI * x);
- }
- };
- makeLanczosWindow = function(a) {
- return function(x) {
- return sinc(x / a);
- };
- };
- makeSincKernel = function(window) {
- return function(x) {
- return sinc(x) * window(x);
- };
- };
- SincFilterInterpolator = (function(_super) {
- __extends(SincFilterInterpolator, _super);
- function SincFilterInterpolator(array, config) {
- var window;
- SincFilterInterpolator.__super__.constructor.apply(this, arguments);
- this.a = config.sincFilterSize;
- window = config.sincWindow;
- if (window == null) throw 'No sincWindow provided';
- this.kernel = makeSincKernel(window);
- }
- SincFilterInterpolator.prototype.interpolate = function(t) {
- var k, n, sum, _ref, _ref2;
- k = Math.floor(t);
- sum = 0;
- for (n = _ref = k - this.a + 1, _ref2 = k + this.a; _ref <= _ref2 ? n <= _ref2 : n >= _ref2; _ref <= _ref2 ? n++ : n--) {
- sum += this.kernel(t - n) * this.getClippedInput(n);
- }
- return sum;
- };
- return SincFilterInterpolator;
- })(AbstractInterpolator);
- getColumn = function(arr, i) {
- var row, _i, _len, _results;
- _results = [];
- for (_i = 0, _len = arr.length; _i < _len; _i++) {
- row = arr[_i];
- _results.push(row[i]);
- }
- return _results;
- };
- makeScaledFunction = function(f, baseScale, scaleRange) {
- var scaleFactor, translation;
- if (scaleRange.join === '0,1') {
- return f;
- } else {
- scaleFactor = baseScale / (scaleRange[1] - scaleRange[0]);
- translation = scaleRange[0];
- return function(t) {
- return f(scaleFactor * (t - translation));
- };
- }
- };
- getType = function(x) {
- return Object.prototype.toString.call(x).slice('[object '.length, -1);
- };
- validateNumber = function(n) {
- if (isNaN(n)) throw 'NaN in Smooth() input';
- if (getType(n) !== 'Number') throw 'Non-number in Smooth() input';
- if (!isFinite(n)) throw 'Infinity in Smooth() input';
- };
- validateVector = function(v, dimension) {
- var n, _i, _len, _results;
- if (getType(v) !== 'Array') throw 'Non-vector in Smooth() input';
- if (v.length !== dimension) throw 'Inconsistent dimension in Smooth() input';
- _results = [];
- for (_i = 0, _len = v.length; _i < _len; _i++) {
- n = v[_i];
- _results.push(validateNumber(n));
- }
- return _results;
- };
- isValidNumber = function(n) {
- return (getType(n) === 'Number') && isFinite(n) && !isNaN(n);
- };
- normalizeScaleTo = function(s) {
- var invalidErr;
- invalidErr = "scaleTo param must be number or array of two numbers";
- switch (getType(s)) {
- case 'Number':
- if (!isValidNumber(s)) throw invalidErr;
- s = [0, s];
- break;
- case 'Array':
- if (s.length !== 2) throw invalidErr;
- if (!(isValidNumber(s[0]) && isValidNumber(s[1]))) throw invalidErr;
- break;
- default:
- throw invalidErr;
- }
- return s;
- };
- shallowCopy = function(obj) {
- var copy, k, v;
- copy = {};
- for (k in obj) {
- if (!__hasProp.call(obj, k)) continue;
- v = obj[k];
- copy[k] = v;
- }
- return copy;
- };
- Smooth = function(arr, config) {
- var baseScale, dataType, dimension, i, interpolator, interpolatorClass, interpolatorClasses, interpolators, k, n, scaleRange, smoothFunc, v;
- if (config == null) config = {};
- config = shallowCopy(config);
- if (config.scaleTo == null) config.scaleTo = config.period;
- if (config.sincFilterSize == null) {
- config.sincFilterSize = config.lanczosFilterSize;
- }
- for (k in defaultConfig) {
- if (!__hasProp.call(defaultConfig, k)) continue;
- v = defaultConfig[k];
- if (config[k] == null) config[k] = v;
- }
- interpolatorClasses = {
- nearest: NearestInterpolator,
- linear: LinearInterpolator,
- cubic: CubicInterpolator,
- lanczos: SincFilterInterpolator,
- sinc: SincFilterInterpolator
- };
- interpolatorClass = interpolatorClasses[config.method];
- if (interpolatorClass == null) throw "Invalid method: " + config.method;
- if (config.method === 'lanczos') {
- config.sincWindow = makeLanczosWindow(config.sincFilterSize);
- }
- if (arr.length < 2) throw 'Array must have at least two elements';
- dataType = getType(arr[0]);
- smoothFunc = (function() {
- var _i, _j, _len, _len2;
- switch (dataType) {
- case 'Number':
- if (Smooth.deepValidation) {
- for (_i = 0, _len = arr.length; _i < _len; _i++) {
- n = arr[_i];
- validateNumber(n);
- }
- }
- interpolator = new interpolatorClass(arr, config);
- return function(t) {
- return interpolator.interpolate(t);
- };
- case 'Array':
- dimension = arr[0].length;
- if (!dimension) throw 'Vectors must be non-empty';
- if (Smooth.deepValidation) {
- for (_j = 0, _len2 = arr.length; _j < _len2; _j++) {
- v = arr[_j];
- validateVector(v, dimension);
- }
- }
- interpolators = (function() {
- var _results;
- _results = [];
- for (i = 0; 0 <= dimension ? i < dimension : i > dimension; 0 <= dimension ? i++ : i--) {
- _results.push(new interpolatorClass(getColumn(arr, i), config));
- }
- return _results;
- })();
- return function(t) {
- var interpolator, _k, _len3, _results;
- _results = [];
- for (_k = 0, _len3 = interpolators.length; _k < _len3; _k++) {
- interpolator = interpolators[_k];
- _results.push(interpolator.interpolate(t));
- }
- return _results;
- };
- default:
- throw "Invalid element type: " + dataType;
- }
- })();
- if (config.scaleTo) {
- scaleRange = normalizeScaleTo(config.scaleTo);
- if (config.clip === Smooth.CLIP_PERIODIC) {
- baseScale = arr.length;
- } else {
- baseScale = arr.length - 1;
- }
- smoothFunc = makeScaledFunction(smoothFunc, baseScale, scaleRange);
- }
- return smoothFunc;
- };
- for (k in Enum) {
- if (!__hasProp.call(Enum, k)) continue;
- v = Enum[k];
- Smooth[k] = v;
- }
- Smooth.deepValidation = true;
- root = typeof exports !== "undefined" && exports !== null ? exports : window;
- root.Smooth = Smooth;
- }).call(this);
|