├── LICENSE ├── README.md ├── circle.png ├── cs.png ├── grace.png ├── index.html └── js ├── fourier.js └── main.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License (MIT) 2 | 3 | Copyright (c) 2014 Anthony Liu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JS Fourier Image Analysis 2 | == 3 | 4 | This is a web app that computes the 2D Fourier transforms (FTs) of images. After a FT is computed, an image is generated representing the magnitudes of each of the constituent sinusoids. 5 | 6 | Check out a live demo at: http://turbomaze.github.io/JS-Fourier-Image-Analysis/ 7 | 8 | Low frequencies are in the center of this image, per usual. Entering a value in the "Low pass radius" box removes all sinusoids that are more than that many pixels away from the center: those with high frequency. Setting the "High pass radius" removes the sinusoids that are within the specified number of pixels. If you enter both a low and high pass radius, a band filter will be applied. That is, only sinusoids between the low and high radii are kept. 9 | 10 | Once you click the reconstruct button to restore the image, you can see how the result differs from the original. Green areas are the same as the original, blue areas are darker, and red areas are brighter. Currently (due to CORS issues), only images that are hosted on the same web server as the web app can be transformed. 11 | 12 | For more info about Fourier transforms/an example app that uses this code, check out [this blog post about evolutionary art](https://igliu.com/fourier-transform-for-evolutionary-art/). 13 | 14 | ## Usage 15 | To use this module, include the `js/fourier.js` file in your webpage. 16 | 17 | To compute the FFT of an input array, call the `Fourier.transform(data, out)` function where `data` is the array of vaalues you'd like to FFT and `out` is an empty, pre-declared array that will be filled with the transform. 18 | 19 | For the inverse FFT, use `Fourier.invert(transform, sig)` similarly. 20 | 21 | To compute the FFT of an image, first, draw it to a canvas and get the image data with `[CanvasRenderingContext2D].getImageData`. Then you can run the above functions on a copy of the resulting array. See the demo code in `js/main.js` for examples. 22 | 23 | You can compute ta low pass/high pass filter with the `Fourier.filter(data, dims, lowPass, highPass)` function. `data` is the FFT output, `dims` is a two-element array representing the dimensions of the original image, `lowPass` is the optional low pass radius and `highPass` is the optional high pass radius. 24 | 25 | ## License 26 | MIT License: http://igliu.mit-license.org/ 27 | -------------------------------------------------------------------------------- /circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbomaze/JS-Fourier-Image-Analysis/b24d961a5dd2976305771c854a57a1892713de3d/circle.png -------------------------------------------------------------------------------- /cs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbomaze/JS-Fourier-Image-Analysis/b24d961a5dd2976305771c854a57a1892713de3d/cs.png -------------------------------------------------------------------------------- /grace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/turbomaze/JS-Fourier-Image-Analysis/b24d961a5dd2976305771c854a57a1892713de3d/grace.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Image Fourier Transform 6 | 7 | 8 | 9 | 10 | 11 |

Image Fourier Transform

12 | 13 |
14 | Status: 15 |
16 |
17 |
18 |
19 |
20 | 1. Original image 21 |
22 | 23 |
24 | 25 | 26 |
27 | 28 |
29 | 30 | 31 |
32 |
33 | 2. Fourier image (log of magnitude) 34 |
35 | 36 |
37 | px 38 |
39 | px 40 |
41 | 42 |
43 |
44 |
45 |
46 |
47 | 3. Reconstructed image 48 |
49 |
50 | 51 |
52 |
53 | 4. Difference image 54 |
55 |
56 | 57 |
58 |
59 | 60 | 61 | -------------------------------------------------------------------------------- /js/fourier.js: -------------------------------------------------------------------------------- 1 | /******************\ 2 | | Fourier Image | 3 | | @author Anthony | 4 | | @version 1.1.2 | 5 | | @date 2014/06/14 | 6 | | @edit 2017/01/23 | 7 | \******************/ 8 | 9 | var Fourier = (function() { 10 | /****************** 11 | * work functions */ 12 | function filter(data, dims, lowPass, highPass) { 13 | var lowPassSq = Math.pow(lowPass, 2); 14 | var highPassSq = Math.pow(highPass, 2); 15 | var N = dims[1]; 16 | var M = dims[0]; 17 | for (var k = 0; k < N; k++) { 18 | for (var l = 0; l < M; l++) { 19 | var idx = k*M + l; 20 | var d = Math.pow(k-M/2, 2) + Math.pow(l-N/2, 2); 21 | if ( 22 | d > lowPassSq && isNaN(highPass) || 23 | d < highPassSq && isNaN(lowPass) || 24 | d < lowPassSq && !isNaN(lowPass) && !isNaN(highPass) || 25 | d > highPassSq && !isNaN(lowPass) && !isNaN(highPass) 26 | ) { 27 | data[idx] = new Fourier.Complex(0, 0); 28 | } 29 | } 30 | } 31 | } 32 | 33 | function FFT(sig, out) { 34 | if (sig.length === 0) { 35 | var e = new Error("Cannot transform an image with size of zero."); 36 | e.name = RangeError; 37 | e.givenLength = sig.length; 38 | throw e; 39 | } 40 | if (sig.length & (sig.length - 1)) { 41 | var e = new Error("Unimplemented: Only FFT of signals of length power of 2 supported by this implementation. Given: " + sig.length); 42 | e.name = RangeError; 43 | e.givenLength = sig.length; 44 | throw e; 45 | } 46 | rec_FFT_radix2(out, 0, sig, 0, sig.length, 1, 2); 47 | } 48 | 49 | function rec_FFT_radix2(out, start, sig, offset, N, s) { 50 | if (N === 1) { 51 | out[start] = new Complex(sig[offset], 0); // array 52 | } else { 53 | rec_FFT_radix2(out, start, sig, offset, N/2, 2*s); 54 | rec_FFT_radix2(out, start+N/2, sig, offset+s, N/2, 2*s); 55 | for (var k = 0; k < N/2; k++) { 56 | var twiddle = cisExp(-2*Math.PI*k/N); 57 | var t = out[start+k]; 58 | out[start+k] = t.plus(twiddle.times(out[start+k+N/2])); 59 | out[start+k+N/2] = t.minus( 60 | twiddle.times(out[start+k+N/2]) 61 | ); 62 | } 63 | } 64 | } 65 | 66 | function invFFT(transform, sig) { 67 | if (transform.length === 0) { 68 | var e = new Error("Cannot transform an image with size of zero."); 69 | e.name = RangeError; 70 | e.givenLength = transform.length; 71 | throw e; 72 | } 73 | if (transform.length & (transform.length - 1)) { 74 | var e = new Error("Unimplemented: Only FFT of signals of length power of 2 supported by this implementation. Given: " + transform.length); 75 | e.name = RangeError; 76 | e.givenLength = transform.length; 77 | throw e; 78 | } 79 | rec_invFFT_radix2(sig, 0, transform, 0, transform.length, 1); 80 | for (var ai = 0; ai < sig.length; ai++) { 81 | sig[ai] = sig[ai].real/sig.length; 82 | } 83 | } 84 | 85 | function rec_invFFT_radix2(sig, start, transform, offset, N, s) { 86 | if (N === 1) { 87 | sig[start] = transform[offset]; 88 | } else { 89 | rec_invFFT_radix2(sig, start, transform, offset, N/2, 2*s); 90 | rec_invFFT_radix2(sig, start+N/2, transform, offset+s, N/2, 2*s); 91 | for (var k = 0; k < N/2; k++) { 92 | var twiddle = cisExp(2*Math.PI*k/N); 93 | var t = sig[start+k]; 94 | sig[start+k] = t.plus(twiddle.times(sig[start+k+N/2])); 95 | sig[start+k+N/2] = t.minus( 96 | twiddle.times(sig[start+k+N/2]) 97 | ); 98 | } 99 | } 100 | } 101 | 102 | function shiftFFT(transform, dims) { 103 | return flipRightHalf( 104 | halfShiftFFT( 105 | halfShiftFFT( 106 | transform, 107 | dims 108 | ), 109 | dims 110 | ), 111 | dims 112 | ); 113 | } 114 | 115 | function unshiftFFT(transform, dims) { 116 | return halfShiftFFT( 117 | halfShiftFFT( 118 | flipRightHalf( 119 | transform, 120 | dims 121 | ), 122 | dims 123 | ), 124 | dims 125 | ); 126 | } 127 | 128 | function halfShiftFFT(transform, dims) { 129 | var ret = []; 130 | var N = dims[1]; 131 | var M = dims[0]; 132 | for (var n = 0, vOff = N/2; n < N; n++) { 133 | for (var m = 0; m < M/2; m++) { 134 | var idx = vOff*dims[0] + m; 135 | ret.push(transform[idx]); 136 | } 137 | vOff += vOff >= N/2 ? -N/2 : (N/2)+1; 138 | } 139 | for (var n = 0, vOff = N/2; n < N; n++) { 140 | for (var m = M/2; m < M; m++) { 141 | var idx = vOff*dims[0] + m; 142 | ret.push(transform[idx]); 143 | } 144 | vOff += vOff >= N/2 ? -N/2 : (N/2)+1; 145 | } 146 | return ret; 147 | } 148 | 149 | function flipRightHalf(transform, dims) { 150 | var ret = []; 151 | 152 | // flip the right half of the image across the x axis 153 | var N = dims[1]; 154 | var M = dims[0]; 155 | for (var n = 0; n < N; n++) { 156 | for (var m = 0; m < M; m++) { 157 | var $n = m < M/2 ? n : (N-1)-n; 158 | var idx = $n*dims[0] + m; 159 | ret.push(transform[idx]); 160 | } 161 | } 162 | 163 | return ret; 164 | } 165 | 166 | /******************** 167 | * helper functions */ 168 | function cisExp(x) { // e^ix = cos x + i*sin x 169 | return new Complex(Math.cos(x), Math.sin(x)); 170 | } 171 | 172 | /*********** 173 | * objects */ 174 | function Complex(re, im) { 175 | this.real = re; 176 | this.imag = im; 177 | } 178 | Complex.prototype.magnitude2 = function() { 179 | return this.real*this.real + this.imag*this.imag; 180 | }; 181 | Complex.prototype.magnitude = function() { 182 | return Math.sqrt(this.magnitude2()); 183 | }; 184 | Complex.prototype.plus = function(z) { 185 | return new Complex(this.real+z.real, this.imag+z.imag); 186 | }; 187 | Complex.prototype.minus = function(z) { 188 | return new Complex(this.real-z.real, this.imag-z.imag); 189 | }; 190 | Complex.prototype.times = function(z) { 191 | if (typeof z === 'object') { // complex multiplication 192 | var rePart = this.real*z.real - this.imag*z.imag; 193 | var imPart = this.real*z.imag + this.imag*z.real; 194 | return new Complex(rePart, imPart); 195 | } else { // scalar multiplication 196 | return new Complex(z*this.real, z*this.imag); 197 | } 198 | }; 199 | 200 | return { 201 | Complex: Complex, 202 | transform: FFT, 203 | invert: invFFT, 204 | shift: shiftFFT, 205 | unshift: unshiftFFT, 206 | filter: filter 207 | }; 208 | })(); 209 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | /******************\ 2 | | Fourier Image | 3 | | @author Anthony | 4 | | @version 1.1.2 | 5 | | @date 2014/06/14 | 6 | | @edit 2017/11/11 | 7 | \******************/ 8 | 9 | var FourierImageAnalysis = (function() { 10 | /********** 11 | * config */ 12 | var dims = [-1, -1]; // will be set later 13 | var cc = 9e-3; // contrast constant 14 | 15 | /********************* 16 | * working variables */ 17 | var canvases; 18 | var ctxs; 19 | var h; 20 | var $h; // h hat 21 | var h_; // h prime, the reconstructed h values 22 | 23 | /****************** 24 | * work functions */ 25 | function initFourierImageAnalysis() { 26 | // event listeners 27 | $s('#draw-cs-btn').addEventListener('click', function() { 28 | loadImage('cs.png'); 29 | }); 30 | 31 | $s('#draw-circle-btn').addEventListener('click', function() { 32 | loadImage('circle.png'); 33 | }); 34 | 35 | $s('#draw-grace-btn').addEventListener('click', function() { 36 | loadImage('grace.png'); 37 | }); 38 | 39 | $s('#draw-img-btn').addEventListener('click', function() { 40 | loadImage($s('#img-url').value); 41 | }); 42 | 43 | $s('#transform-btn').addEventListener('click', function() { 44 | var start = +new Date(); 45 | 46 | if (!h()) { 47 | return alert( 48 | 'You need to draw an image to canvas 1 first.' 49 | ); 50 | } 51 | 52 | // placed in a callback so the UI has a chance to update 53 | disableButtons(transformAction); 54 | }); 55 | 56 | $s('#reconstruct-btn').addEventListener('click', function() { 57 | var start = +new Date(); 58 | 59 | if (!$h()) { 60 | return alert( 61 | 'You first need to compute the Fourier transform.' 62 | ); 63 | } 64 | 65 | // placed in a callback so the UI has a chance to update 66 | disableButtons(reconstructAction); 67 | }); 68 | 69 | $s('#difference-btn').addEventListener('click', function() { 70 | var start = +new Date(); 71 | 72 | if (!h_()) { 73 | return alert('You haven\'t reconstructed an image yet.'); 74 | } 75 | 76 | // placed in a callback so the UI has a chance to update 77 | disableButtons(differenceAction); 78 | }); 79 | 80 | // initialize the working variables 81 | canvases = [], ctxs = []; 82 | h = $h = h_ = function() { return false; }; 83 | } 84 | 85 | function loadImage(loc) { 86 | var start = +new Date(); 87 | 88 | // placed in a callback so the UI has a chance to update 89 | disableButtons(function() { 90 | // draw the initial image 91 | var img = new Image(); 92 | img.addEventListener('error', function() { 93 | $s('#errfield').innerHTML = "Unable to load image " + loc; 94 | enableButtons(); 95 | }); 96 | img.addEventListener('load', function() { 97 | try { 98 | // make each canvas the image's exact size 99 | dims[0] = img.width; 100 | dims[1] = img.height; 101 | for (var ai = 0; ai < 4; ai++) { 102 | canvases[ai] = $s('#canvas'+ai); 103 | canvases[ai].width = dims[0]; 104 | canvases[ai].height = dims[1]; 105 | ctxs[ai] = canvases[ai].getContext('2d'); 106 | } 107 | 108 | // draw the image to the canvas 109 | ctxs[0].drawImage(img, 0, 0, img.width, img.height); 110 | 111 | // grab the pixels 112 | var imageData = ctxs[0].getImageData( 113 | 0, 0, dims[0], dims[1] 114 | ); 115 | var h_es = []; // the h values 116 | for (var ai = 0; ai < imageData.data.length; ai+=4) { 117 | // greyscale, so you only need every 4th value 118 | h_es.push(imageData.data[ai]); 119 | } 120 | 121 | // initialize the h values 122 | h = function(n, m) { 123 | if (arguments.length === 0) return h_es; 124 | 125 | var idx = n*dims[0] + m; 126 | return h_es[idx]; 127 | }; // make it a function so the code matches the math 128 | $s('#errfield').innerHTML = ""; 129 | } catch (e) { 130 | $s('#errfield').innerHTML = e.message; 131 | } 132 | 133 | enableButtons(); 134 | 135 | var duration = +new Date() - start; 136 | console.log( 137 | 'It took ' + duration + 'ms to draw the image.' 138 | ); 139 | }); 140 | img.crossOrigin = "anonymous"; 141 | img.src = loc; 142 | }); 143 | } 144 | 145 | function transformAction() { 146 | var start = +new Date(); 147 | try { 148 | // compute the h hat values 149 | var h_hats = []; 150 | Fourier.transform(h(), h_hats); 151 | h_hats = Fourier.shift(h_hats, dims); 152 | 153 | // get the largest magnitude 154 | var maxMagnitude = 0; 155 | for (var ai = 0; ai < h_hats.length; ai++) { 156 | var mag = h_hats[ai].magnitude(); 157 | if (mag > maxMagnitude) { 158 | maxMagnitude = mag; 159 | } 160 | } 161 | 162 | // apply a low or high pass filter 163 | var lowPassRadius = parseInt( 164 | $s('#low-freq-radius').value 165 | ); // low pass radius 166 | var highPassRadius= parseInt( 167 | $s('#high-freq-radius').value 168 | ); // high pass radius 169 | Fourier.filter(h_hats, dims, lowPassRadius, highPassRadius); 170 | 171 | // store them in a nice function to match the math 172 | $h = function(k, l) { 173 | if (arguments.length === 0) return h_hats; 174 | 175 | var idx = k*dims[0] + l; 176 | return h_hats[idx]; 177 | }; 178 | 179 | // draw the pixels 180 | var currImageData = ctxs[1].getImageData( 181 | 0, 0, dims[0], dims[1] 182 | ); 183 | var logOfMaxMag = Math.log(cc*maxMagnitude+1); 184 | for (var k = 0; k < dims[1]; k++) { 185 | for (var l = 0; l < dims[0]; l++) { 186 | var idxInPixels = 4*(dims[0]*k + l); 187 | currImageData.data[idxInPixels+3] = 255; // full alpha 188 | var color = Math.log(cc*$h(l, k).magnitude()+1); 189 | color = Math.round(255*(color/logOfMaxMag)); 190 | // RGB are the same -> gray 191 | for (var c = 0; c < 3; c++) { // lol c++ 192 | currImageData.data[idxInPixels+c] = color; 193 | } 194 | } 195 | } 196 | ctxs[1].putImageData(currImageData, 0, 0); 197 | 198 | $s('#errfield').innerHTML = ""; 199 | } catch (e) { 200 | $s('#errfield').innerHTML = e.message; 201 | } 202 | enableButtons(); 203 | 204 | var duration = +new Date() - start; 205 | console.log('It took '+duration+'ms to compute the FT.'); 206 | } 207 | 208 | function reconstructAction() { 209 | var start = +new Date(); 210 | try { 211 | // compute the h prime values 212 | var h_primes = []; 213 | var h_hats = $h(); 214 | h_hats = Fourier.unshift(h_hats, dims); 215 | Fourier.invert(h_hats, h_primes); 216 | 217 | // store them in a nice function to match the math 218 | h_ = function(n, m) { 219 | if (arguments.length === 0) return h_primes; 220 | 221 | var idx = n*dims[0] + m; 222 | return round(h_primes[idx], 2); 223 | }; 224 | 225 | // draw the pixels 226 | var currImageData = ctxs[2].getImageData( 227 | 0, 0, dims[0], dims[1] 228 | ); 229 | for (var n = 0; n < dims[1]; n++) { 230 | for (var m = 0; m < dims[0]; m++) { 231 | var idxInPixels = 4*(dims[0]*n + m); 232 | currImageData.data[idxInPixels+3] = 255; // full alpha 233 | for (var c = 0; c < 3; c++) { // RGB are the same, lol c++ 234 | currImageData.data[idxInPixels+c] = h_(n, m); 235 | } 236 | } 237 | } 238 | ctxs[2].putImageData(currImageData, 0, 0); 239 | $s('#errfield').innerHTML = ""; 240 | } catch (e) { 241 | $s('#errfield').innerHTML = e.message; 242 | } 243 | 244 | enableButtons(); 245 | 246 | var duration = +new Date() - start; 247 | console.log( 248 | 'It took ' + duration + 'ms to reconstruct the image.' 249 | ); 250 | } 251 | 252 | function differenceAction() { 253 | var start = +new Date(); 254 | try { 255 | // find the range of the errors 256 | var minError = Infinity; 257 | var maxError = 0; 258 | for (var n = 0; n < dims[1]; n++) { 259 | for (var m = 0; m < dims[0]; m++) { 260 | var error = h_(n, m) - h(n, m); 261 | if (error < minError) minError = error; 262 | if (error > maxError) maxError = error; 263 | } 264 | } 265 | 266 | // draw the pixels 267 | var currImageData = ctxs[3].getImageData( 268 | 0, 0, dims[0], dims[1] 269 | ); 270 | for (var n = 0; n < dims[1]; n++) { 271 | for (var m = 0; m < dims[0]; m++) { 272 | var idxInPixels = 4*(dims[0]*n + m); 273 | var error = h_(n, m) - h(n, m); 274 | var color = getCoolColor( 275 | error, [minError, maxError] 276 | ); 277 | for (var c = 0; c < 3; c++) { 278 | currImageData.data[idxInPixels+c] = color[c]; 279 | } 280 | currImageData.data[idxInPixels+3] = 255; 281 | } 282 | } 283 | ctxs[3].putImageData(currImageData, 0, 0); 284 | $s('#errfield').innerHTML = ""; 285 | } catch (e) { 286 | $s('#errfield').innerHTML = e.message; 287 | } 288 | 289 | enableButtons(); 290 | 291 | var duration = +new Date() - start; 292 | console.log( 293 | 'It took ' + duration + 294 | 'ms to compute the difference.' 295 | ); 296 | } 297 | 298 | /******************** 299 | * helper functions */ 300 | function disableButtons(callback) { 301 | $s('#draw-cs-btn').disabled = true; 302 | $s('#draw-circle-btn').disabled = true; 303 | $s('#draw-grace-btn').disabled = true; 304 | $s('#draw-img-btn').disabled = true; 305 | $s('#transform-btn').disabled = true; 306 | $s('#reconstruct-btn').disabled = true; 307 | $s('#difference-btn').disabled = true; 308 | 309 | setTimeout(callback, 6); // 6ms for the UI to update 310 | } 311 | 312 | function enableButtons() { 313 | $s('#draw-cs-btn').disabled = false; 314 | $s('#draw-circle-btn').disabled = false; 315 | $s('#draw-grace-btn').disabled = false; 316 | $s('#draw-img-btn').disabled = false; 317 | $s('#transform-btn').disabled = false; 318 | $s('#reconstruct-btn').disabled = false; 319 | $s('#difference-btn').disabled = false; 320 | } 321 | 322 | // returns array of pixel colors in the image 323 | function getCoolColor(n, range) { 324 | if (n === range[0] && range[0] === range[1]) { 325 | return getCoolColor(2*n, [n-1, 2*n+1]); 326 | } 327 | 328 | var raw = [1.0, 1.0, 1.0]; // white 329 | 330 | if (n < range[0]) n = range[0]; 331 | if (n > range[1]) n = range[1]; 332 | var dn = range[1] - range[0]; 333 | 334 | if (n < (range[0] + 0.25 * dn)) { 335 | raw[0] = 0; 336 | raw[1] = 4 * (n - range[0]) / dn; 337 | } else if (n < (range[0] + 0.5 * dn)) { 338 | raw[0] = 0; 339 | raw[2] = 1 + 4 * (range[0] + 0.25 * dn - n) / dn; 340 | } else if (n < (range[0] + 0.75 * dn)) { 341 | raw[0] = 4 * (n - range[0] - 0.5 * dn) / dn; 342 | raw[2] = 0; 343 | } else { 344 | raw[1] = 1 + 4 * (range[0] + 0.75 * dn - n) / dn; 345 | raw[2] = 0; 346 | } 347 | 348 | var color = [ 349 | tightMap(raw[0], 0, 1, 0, 255), 350 | tightMap(raw[1], 0, 1, 0, 255), 351 | tightMap(raw[2], 0, 1, 0, 255) 352 | ]; 353 | return color; 354 | } 355 | 356 | function round(n, places) { 357 | var mult = Math.pow(10, places); 358 | return Math.round(mult*n)/mult; 359 | } 360 | 361 | function tightMap(n, d1, d2, r1, r2) { // enforces boundaries 362 | var raw = map(n, d1, d2, r1, r2); 363 | if (raw < r1) return r1; 364 | else if (raw > r2) return r2; 365 | else return raw; 366 | } 367 | 368 | // given an n in [d1, d2], return a "linearly related" 369 | // number in [r1, r2] 370 | function map(n, d1, d2, r1, r2) { 371 | var Rd = d2-d1; 372 | var Rr = r2-r1; 373 | return (Rr/Rd)*(n - d1) + r1; 374 | } 375 | 376 | function $s(id) { // for convenience 377 | if (id.charAt(0) !== '#') return false; 378 | return document.getElementById(id.substring(1)); 379 | } 380 | 381 | return { 382 | init: initFourierImageAnalysis 383 | }; 384 | })(); 385 | 386 | window.addEventListener('load', FourierImageAnalysis.init); 387 | --------------------------------------------------------------------------------