<repo_name>DavisReef/angular-depth-viewer<file_sep>/src/lib/directive.js
'use strict';

import Depth from './Depth/Depth';

/**
 * Depth photo directive
 */
export default function DepthDirective(depthConfig) {
    'ngInject';

    return {
        restrict: 'EA',
        scope: {
            src: '@',
            debug: '@',
            fit: '@',
            width: '@',
            height: '@',
            enlarge: '@',
            trace: '@',
            quality: '@',
            size: '@',

            animate: '@',
            animateDuration: '@',

            depthFactor: '@',
            depthFocus: '@',
            depthEqualize: '@',
            depthmapSrc: '@',
            depthPreview: '@',
            depthBlurSize: '@',

            mouseTracking: '@',
            gyroTracking: '@',
            gyroTrackingFactor: '@',
            faceTracking: '@',
            faceTrackingFactor: '@',
            faceTrackingDebug: '@',

            refocus: '@',
            focusPoint: '@',
            focusPointChangeable: '@',
            focusBlur: '@',
            focusBlurChangeable: '@',
            onRefocus: '&'
        },
        link: (scope, element, attrs) => {
            let depth;
            let options = {};

            let optionsList = {
                src: 'string',
                debug: 'boolean',
                trace: 'boolean',
                fit: 'string|false',
                enlarge: 'number',
                quality: 'number|false',
                size: 'size',

                animate: 'boolean',
                animateDuration: 'number',

                depthFactor: 'number',
                depthFocus: 'number',
                depthEqualize: 'boolean',
                depthmapSrc: 'string|null',
                depthPreview: 'number',
                depthBlurSize: 'number',

                mouseTracking: 'boolean',
                gyroTracking: 'boolean',
                gyroTrackingFactor: 'number',
                faceTracking: 'boolean',
                faceTrackingFactor: 'number',
                faceTrackingDebug: 'boolean',

                refocus: 'boolean',
                focusPoint: 'point',
                focusPointChangeable: 'boolean',
                focusBlur: 'number',
                focusBlurChangeable: 'boolean'
            };

            // init options
            Object.keys(optionsList).forEach(key => initOption(key, optionsList[key]));

            // parse width/height
            if (attrs.width && attrs.height) {
                options.size = {width: parseInt(attrs.width), height: parseInt(attrs.height)};
                attrs.size = `${options.size.width}x${options.size.height}`;
            }

            options.debug && debugPrint('options', options);

            depth = new Depth(element[0], options);

            // show when ready
            depth.on('ready', () => {
                element.addClass('ready');
            });

            depth.on('refocus', () => {
                if (angular.isFunction(scope.onRefocus)) {
                    scope.$evalAsync(scope.onRefocus({
                        refocus: {
                            focusPoint: depth.options.focusPoint,
                            focusBlur: depth.options.focusBlur
                        }
                    }));
                }
            });

            scope.$on('$destroy', () => depth && depth.destroy());

            /**
             * Init directive option
             */
            function initOption(name, type = 'string') {
                if ('undefined' !== typeof attrs[name]) {
                    options[name] = convertOption(attrs[name], type);
                } else if ('undefined' !== typeof depthConfig.defaults[name]) {
                    options[name] = convertOption(depthConfig.defaults[name], type);
                }

                attrs.$observe(name, (val) => {
                    if (depth && 'undefined' !== typeof val) {
                        options[name] = convertOption(val, type);
                        options.debug && debugPrint(`${name} => ${angular.toJson(val)}`, typeof val);
                        depth.setOption(name, options[name]);
                    }
                });
            }
        }
    };
}

/**
 * Convert option
 * @param {*} val
 * @param {string} type
 * @returns {*}
 */
function convertOption(val, type) {
    switch (type) {
        case 'boolean':
            val = convertBool(val);
            break;

        case 'number|false':
            val = val === '' ? false : parseFloat(val);
            break;

        case 'string|false':
            val = val === '' ? false : val;
            break;

        case 'string|null':
            val = val === '' ? null : val;
            break;

        case 'number':
            val = val === '' ? .0 : parseFloat(val);
            break;

        case 'point':
            // XxY
            if (val === '') {
                val = null;
            } else {
                val = val.split(/[x\,\;\*]/);
                val = {x: parseFloat(val[0]), y: parseFloat(val[1])};
            }
            break;

        case 'size':
            // WxH
            if (val === '') {
                val = null;
            } else {
                val = val.split(/[x\,\;\*]/);
                val = {width: parseInt(val[0]), height: parseInt(val[1])};
            }
            break;

        default:
            break;
    }

    return val;
}

/**
 * Convert boolean value
 */
function convertBool(val) {
    val = val.toString().trim().toLowerCase();

    switch (val) {
        case 'false':
        case '0':
        case '':
            return false;
            break;
        default:
            return true;
    }
}

/**
 * Debug print
 * @param objects
 */
function debugPrint(...objects) {
    objects.unshift('[directive]');
    console.log(...objects);
}
<file_sep>/dev/gulp/tasks/demo/optimize-assets.js
/**
 * Optimize assets
 *  - concatenate
 *  - minify
 **/

var gulp = require('gulp');
var gulpIf = require('gulp-if');
var config = require('../../config');
var gulpNgAnnotate = require('gulp-ng-annotate');
var gulpUglify = require('gulp-uglify');

gulp.task('demo:optimize-assets', function () {
    return gulp.src(config.paths.demo_build + '/**')

        // minify js
        .pipe(gulpIf('*.js', gulpNgAnnotate()))
        .pipe(gulpIf('*.js', gulpUglify()))

        .pipe(gulp.dest(config.paths.demo_build));
});
<file_sep>/src/lib/Depth/Facetracker.js
/**
 * Face tracker
 *
 * Events:
 *      track {x: -1..1, y: -1..1}
 *      noCamera
 *
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 */

import headtrackr from 'headtrackr';
import EventEmitter from 'eventemitter3';

const CANVAS_WIDTH = 320;
const CANVAS_HEIGHT = 240;

export default class Facetracker extends EventEmitter {

    constructor(options = {}) {

        super();

        // default options
        this._options = {
            debug: true,
            debugOverlay: true
        };

        this._isTracking = false;

        this.options = options;
        this._attachEventlisteners();
        this.start();
    }

    /**
     * Options property setter
     * @param val
     */
    set options(val) {
        this._options = Object.assign(this._options, val);
    }

    get x() {
        return Math.max(-1, Math.min(1, this._x || 0));
    }

    get y() {
        return Math.max(-1, Math.min(1, this._y || 0));
    }

    get isTracking() {
        return this._isTracking;
    }

    /**
     * Listen to headtrackr events
     * @private
     */
    _attachEventlisteners() {
        document.addEventListener('headtrackrStatus', (event) => {
            if (event.status === 'no camera') {
                if (this._options.debug) {
                    this._debugPrint('No camera access');
                }

                this.emit('noCamera');

                this._isTracking = false;
            }
        });

        document.addEventListener('facetrackingEvent', (event) => {
            this._x = (event.x / CANVAS_WIDTH) * 2 - 1 /* -1..1 */;
            this._y = (event.y / CANVAS_HEIGHT) * 2 - 1 /* -1..1 */;
            this.emit('track', {x: this.x, y: this.y});
        });
    }

    /**
     * Initlaization
     * @private
     */
    start() {
        this._canvas = this._createCanvas();
        this._video = this._createVideo();

        this._ht = new headtrackr.Tracker({ui: false, debug: this._options.debugOverlay ? this._canvas : undefined});
        this._ht.init(this._video, this._canvas);
        this._ht.start();

        this._isTracking = true;
    }

    /**
     * Stop tracking/free resources
     */
    stop() {
        // stop video

        // !!! todo submit PR to headtrackr with a fix to stopStream()
        this._ht.stream && this._ht.stream.getVideoTracks().forEach((track) => {
            track.stop();
        });

        this._ht.stop();
        this._ht = null;

        // remove canvas element

        if (this._canvasAppended) {
            document.body.removeChild(this._canvas);
            this._canvasAppended = false;
        }

        // destroy elements
        this._canvas = null;
        this._video = null;

        this._isTracking = false;
    }

    /**
     * Create canvas
     * @returns {HTMLCanvasElement}
     * @private
     */
    _createCanvas() {
        let canvas = document.createElement('canvas');
        canvas.setAttribute('style', 'display:none');
        canvas.setAttribute('width', CANVAS_WIDTH);
        canvas.setAttribute('height', CANVAS_HEIGHT);

        // show video overlay in the center
        if (this._options.debugOverlay) {

            canvas.setAttribute(
                'style',
                'position:absolute;opacity:0.5;left:50%;top:50%;margin-left:-160px;margin-top:-120px'
            );

            document.body.appendChild(canvas);
            this._canvasAppended = true;
        }

        return canvas;
    }

    /**
     * Create video element
     * @returns {Element}
     * @private
     */
    _createVideo() {
        let video = document.createElement('video');
        video.setAttribute('autoplay', 'true');
        video.setAttribute('loop', 'true');
        return video;
    }

    /**
     * Debug printout
     * @param objects
     * @private
     */
    _debugPrint(...objects) {
        objects.unshift('[facetracker]');
        console.log(...objects);
    }
}
<file_sep>/src/lib/Depth/Normalizer/HistorgramEqualizer.js
/**
 * Historgram equalizer algorithm for Normalizer
 *
 * Based on https://pixelero.wordpress.com/2014/11/16/median-filter-for-html5js/
 *
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 * @author <NAME> <<EMAIL>>
 */

export default class HistorgramEqualizer {

    /**
     * @param {imageData} imageData
     */
    process(imageData) {

        let data = imageData.data;

        let hists = this._calculateHistograms(data);

        hists[0] = this._blurHistogram(hists[0]);
        hists[1] = this._blurHistogram(hists[1]);
        hists[2] = this._blurHistogram(hists[2]);

        const bins = 64;

        //	histograms of how values of each channel are distributed:
        //	note: this is a modified version: it doesn't equalize the channels,
        //	but tries to equalize their sum:
        let tst = this._equalizeHistogramSum(hists[0], hists[1], hists[2], bins),
            rarr = tst[0],
            garr = tst[1],
            barr = tst[2];

        // apply normalized histogram for red channel to depth map
        for (let i = 0; i < data.length; i += 4) {
            data[i] = data[i + 1] = data[i + 2] = rarr[data[i]] << 2;
            data[i + 3] = 0xff; // discard alpha channel
        }

        return imageData;
    }

    /**
     * Loops through an image, and adds rgb-values to corresponding histogram bins:
     * @param {ImageData} data
     * @returns {*[]}
     * @private
     */
    _calculateHistograms(data, N = 5) {
        let rarr = new Uint32Array(256),
            garr = new Uint32Array(256),
            barr = new Uint32Array(256);

        //  in case of a very large image this could be sped up
        //  by sample only every 2nd, 3rd or Nth pixel: i+=6, i+=10, i+=2+4*N
        for (let i = 0; i < data.length; i += 2 + 4 * N) {
            rarr[data[i++]]++;
            garr[data[i++]]++;
            barr[data[i]]++;
        }

        return [rarr, garr, barr];
    }

    /**
     * Applies a running average of three [previous,this,next] to an array:
     * @param {array} hst
     * @returns {Uint32Array}
     * @private
     */
    _blurHistogram(hst) {
        let hst2 = new Uint32Array(hst.length);
        hst2[0] = hst2[1] = 1.5 * hst[0];
        let i = hst.length - 1;
        hst2[i] = hst[i - 1] = 1.5 * hst[i];

        for (i = 1; i < hst.length - 1; i++) {
            hst2[i - 1] = hst2[i] = hst2[i + 1] = hst[i];
        }

        for (i = 0; i < hst2.length; i++) {
            hst2[i] /= 3;
        }

        return hst2;
    }

    _equalizeHistogramSum(rh, gh, bh, bins = 64) {
        let total_sum = 0, N = rh.length;

        for (let i = 0; i < N; i++) {
            total_sum += rh[i] + gh[i] + bh[i];
        }

        let m = total_sum / bins,
            sum = 0,
            ir = 0, ig = 0, ib = 0,
            j = 0,
            r, g, b, min, max,
            rh2 = new Uint32Array(N),
            gh2 = new Uint32Array(N),
            bh2 = new Uint32Array(N);

        while (ir < N || ig < N || ib < N) {

            r = ir < N ? rh[ir] : 0;
            g = ig < N ? gh[ig] : 0;
            b = ib < N ? bh[ib] : 0;

            if (r < g) {
                min = r;
                max = g;
            } else {
                min = g;
                max = r;
            }

            if (b < min) {
                min = b;
            } else if (b > max) {
                max = b;
            }

            if (max >= 0.9 * (r + g + b)) {

                rh2[ir++] =
                    gh2[ig++] =
                        bh2[ib++] = j;

                sum += r + g + b;

            } else {

                if (r == min) {
                    rh2[ir++] = j;
                } else if (g == min) {
                    gh2[ig++] = j;
                } else {
                    bh2[ib++] = j;
                }
                if (r == max) {
                    rh2[ir++] = j;
                } else if (g == max) {
                    gh2[ig++] = j;
                } else {
                    bh2[ib++] = j;
                }
                sum += min + max;
            }

            while (sum > m) {
                sum -= m;
                j++;
            }

        }

        return [rh2, gh2, bh2];
    }
}
<file_sep>/dev/shim/depth-reader.js
module.exports = window.DepthReader;
<file_sep>/src/demo/vendors.js
'use strict';

import 'angular';
import PIXI from 'pixi.js/bin/pixi.min.js';
<file_sep>/src/lib/Depth/filters/DepthPerspectiveFilter.js
/**

 Experimental shader giving perspective effect on image with depthmap.

 Quality is controlled by defined profiles 1, 2 and 3.

 ---------------
 The MIT License

 Copyright (c) 2014 <NAME>. http://panrafal.github.com/depthy

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.

 @author <NAME>
 @author <NAME> <<EMAIL>>
 @author <NAME> <<EMAIL>>

 */

'use strict';

import fragment from './DepthPerspectiveFilter.frag!text';
import PIXI from 'pixi.js/bin/pixi.min.js';

export default class DepthPerspectiveFilter extends PIXI.AbstractFilter {

    constructor(texture, quality) {

        super();

        this.passes = [this];

        // set the uniforms
        this.uniforms = {
            displacementMap: {type: 'sampler2D', value: texture},
            scale: {type: '1f', value: 0.015},
            offset: {type: 'v2', value: {x: 0, y: 0}},
            mapDimensions: {type: 'v2', value: {x: 1, y: 5112}},
            dimensions: {type: '4fv', value: [0, 0, 0, 0]},
            focus: {type: '1f', value: 0.5},
            enlarge: {type: '1f', value: 1.06},
            refocusedImage: {type: 'sampler2D', value: texture/* !!! init with null */},
            refocusEnabled: {type: '1f', value: 1}
        };

        if (texture.baseTexture.hasLoaded) {
            this.uniforms.mapDimensions.value.x = texture.width;
            this.uniforms.mapDimensions.value.y = texture.height;
        } else {
            this.boundLoadedFunction = this.onTextureLoaded.bind(this);
            texture.baseTexture.on('loaded', this.boundLoadedFunction);
        }

        this.fragmentSrc = fragment;
        this.quality = quality;

        if (quality) {
            this.fragmentSrc = (`#define QUALITY ${quality} \n`) + this.fragmentSrc;
        }
    }

    onTextureLoaded() {
        this.uniforms.mapDimensions.value.x = this.uniforms.displacementMap.value.width;
        this.uniforms.mapDimensions.value.y = this.uniforms.displacementMap.value.height;
        this.uniforms.displacementMap.value.baseTexture.off('loaded', this.boundLoadedFunction);
    }

    /**
     * The texture used for the displacemtent map * must be power of 2 texture at the moment
     */

    set map(value) {
        this.uniforms.displacementMap.value = value;
    }

    get map() {
        return this.uniforms.displacementMap.value;
    }

    /**
     * The multiplier used to scale the displacement result from the map calculation.
     */

    set scale(value) {
        this.uniforms.scale.value = value;
    }

    get scale() {
        return this.uniforms.scale.value;
    }

    /**
     * Focus point in paralax
     */

    set focus(value) {
        this.uniforms.focus.value = Math.min(1, Math.max(0, value));
    }

    get focus() {
        return this.uniforms.focus.value;
    }

    /**
     * Image enlargment
     */

    set enlarge(value) {
        this.uniforms.enlarge.value = value;
    }

    get enlarge() {
        return this.uniforms.enlarge.value;
    }

    /**
     * The offset used to move the displacement map.
     */

    set offset(value) {
        this.uniforms.offset.value = value;
    }

    get offset() {
        return this.uniforms.offset.value;
    }

    /**
     * Refocused image
     * @param {PIXI.Texture} value
     */
    set refocusedImage(value) {
        this.uniforms.refocusedImage.value = value;
    }

    /**
     * Refocused image
     * @return {PIXI.Texture}
     */
    get refocusedImage() {
        return this.uniforms.refocusedImage.value;
    }

    set refocusEnabled(value) {
        this.uniforms.refocusEnabled.value = value;
    }

    get refocusEnabled() {
        return this.uniforms.refocusEnabled.value;
    }
}
<file_sep>/README.md
# Depth Viewer

__AngularJS Component / Library__   

Viewer component for images with embedded depth map created with devices based on Intel RealSense tech and others. 

## Development Requirements

|Dependency|OS X Installation|
|:--|:--|
|node.js 4.x+|`brew install nodejs`|
|gulp|`npm install -g gulp`|
|jspm|`npm install -g jspm`|

## Development

### Installation

```
npm i
jspm i
```

### Demo App

`gulp demo:serve`

### Build for Bower

`gulp dist`

Dependencies must be listed in `bower.json` and `dev/gulp/tasks/dist.js`

## Available Gulp Tasks

|Command|Desc|
|:--|:--|
|`gulp demo:cleanup`|Demp App: Remove build files|
|`gulp demo:serve` _(default)_|Demp App: Launch demo with live reload|
|`gulp demo:build:development`|Demp App: Build demo app for development environment|
|`gulp demo:build:production`|Demp App: Build demo app for production environment|
|`gulp demo:bundle-vendors`|Demp App: Bundle vendors for faster reload in develeopment|
|`gulp demo:compile-scripts`|Demp App: Compile scripts into sfx bundle|
|`gulp demo:optimize-assets`|Demp App: Optimize assets|
|`gulp demo:set-environment:production`|Demp App: Update environment|
|`gulp demo:set-environment:development`|Demp App: Update environment|

## License

MIT
<file_sep>/dev/gulp/tasks/demo/compile-scripts.js
/**
 * Compile scripts into a self-containg bundle
 */

var gulp = require('gulp');
var gulpShell = require('gulp-shell');
var utils = require('../../utils');
var config = require('../../config');

gulp.task('demo:compile-scripts', (function () {

  var command = 'jspm bundle-sfx ' + // create self-sufficient bundle
                utils.escapeShellArg(config.demoComplileScripts.src) + ' ' + // source
                utils.escapeShellArg(config.demoComplileScripts.dest) + // dest
                ''; // minify

  return gulpShell.task([command]);
})());
<file_sep>/src/lib/Depth/filters/filters.js
'use strict';

import PIXI from 'pixi.js/bin/pixi.min.js';
import './ColorMatrixFilter2';
import DepthPerspectiveFilter from './DepthPerspectiveFilter';
import BokehBlurFilter from './BokehBlurFilter';
import BlurCalculationFilter from './BlurCalculationFilter';
import RefocusCompoundFilter from './RefocusCompoundFilter';

/**
 * Create PIXI filter to discard alpha channel
 * @param alphaConst
 * @returns {PIXI.ColorMatrixFilter2}
 */
export function createDiscardAlpaFilter(alphaConst = 0.0) {
    let filter = new PIXI.ColorMatrixFilter2();
    filter.matrix = [1.0, 0.0, 0.0, 0.0,
                     0.0, 1.0, 0.0, 0.0,
                     0.0, 0.0, 1.0, 0.0,
                     0.0, 0.0, 0.0, 0.0];
    filter.shift = [0.0, 0.0, 0.0, alphaConst];
    return filter;
}

/**
 * Depth blur filter
 * @return {PIXI.BlurFilter}
 */
export function createDepthBlurFilter() {
    return new PIXI.filters.BlurFilter();
}

/**
 *
 * @param {PIXI.RenderTexture} texture
 * @param {number} quality
 */
export function createDepthPerspectiveFilter(texture, quality) {
    return new DepthPerspectiveFilter(texture, quality);
}

/**
 * @param {PIXI.RenderTexture} texture
 * @param {number} quality
 */
export function createRefocusCompoundFilter(texture) {
    return new RefocusCompoundFilter(texture);
}
<file_sep>/dev/gulp/tasks/demo/serve.js
/**
 * Serve the client with live reload
 **/

'use strict';

var gulp = require('gulp');
var browserSync = require('browser-sync');
var config = require('../../config');
var path = require('path');
var os = require('os');

gulp.task('demo:serve', ['demo:build:development'], function () {

    // serve with BrowserSync
    browserSync({
        startPath: '/intel-cf-depth/src/demo/',
        server: {
            baseDir: config.paths.root,
            routes: {
                "/intel-cf-depth": ''
            }
        },
        notify: true
    });

    gulp.watch(config.demoBundleVendors.src, ['demo:bundle-vendors']);

});
<file_sep>/src/lib/Depth/Normalizer/Normalizer.js
/**
 * Image brightness normalizer
 */

import RangeStretcher from './RangeStretcher';
import HistorgramEqualizer from './HistorgramEqualizer';

export default class Normalizer {

    /**
     * @param {string} uri
     */
    constructor(uri) {
        this.debug = true;
        this._image = new Image();
        this._image.src = uri;
    }

    /**
     * Process image
     * @returns {Promise}
     */
    process() {
        return new Promise((resolve, reject) => {

            if (this._image.complete /* image is loaded */) {
                this._process();
                resolve(this._uri);
            } else /* image is not yet loaded */ {
                this._image.onload = (e) => {
                    this._image.onload = null;
                    this._process();
                    resolve(this._uri);
                };
            }

        });
    }

    _process() {
        // create canvas
        this._canvas = document.createElement('canvas');
        this._canvas.width = this._image.width;
        this._canvas.height = this._image.height;

        // draw original image
        this._ctx = this._canvas.getContext('2d');
        this._ctx.drawImage(this._image, 0, 0);

        if (this._debug) {
            this._debugPrint('Map before normalization', this._canvas.toDataURL());
        }

        this._imageData = this._ctx.getImageData(0, 0, this._image.width, this._image.height);

        // normalize map
        this._runProcessor(new HistorgramEqualizer());
        this._runProcessor(new RangeStretcher());

        // put image data to canvas, update uri
        this._ctx.putImageData(this._imageData, 0, 0);
        this._uri = this._canvas.toDataURL();
    }

    /**
     * @param {object} processor
     * @private
     */
    _runProcessor(processor) {
        this._imageData = processor.process(this._imageData);

        if (this._debug) {
            this._ctx.putImageData(this._imageData, 0, 0);
            this._debugPrint('Normalized map', this._canvas.toDataURL());
        }
    }

    /**
     * Debug print
     * @param {*} objects
     * @private
     */
    _debugPrint(...objects) {
        objects.unshift('[debug]');
        console.log(...objects);
    }

    // <editor-fold desc="Accessors" defaultstate="collapsed">

    set debug(val) {
        this._debug = !!val;
    }

    get debug() {
        return !!this._debug;
    }

    // </editor-fold>
}
<file_sep>/dev/shim/eventemitter3.js
module.exports = window.EventEmitter;
<file_sep>/src/lib/Depth/Normalizer/RangeStretcher.js
/**
 * Range stretcher algorithm for Normalizer
 *
 * @author <NAME> <<EMAIL>>
 */

export default class RangeStretcher {

    /**
     * @param {imageData} imageData
     */
    process(imageData) {

        // number of levels for histogram
        const levels = 256;

        // treshold for searching white/black point 0..1
        const bpTreshold = 0.001;
        const wpTreshold = 0.01;

        let data = imageData.data;
        let hist = new Uint32Array(levels);
        let sum = 0;

        // calculate histogram
        for (let i = 0; i < data.length; i += 4) {
            hist[~~(data[i] / 256 * levels)]++;
            sum += data[i];
        }

        // find everage
        let avg = sum / levels;

        // find white and black points

        let whitePointTreshold = wpTreshold * avg;
        let blackPointTreshold = bpTreshold * avg;
        let b = 0;
        let w = hist.length - 1;

        while (hist[b] < bpTreshold) {
            b++;
        }

        while (hist[w] < whitePointTreshold) {
            w--;
        }

        // offset and scale image data

        let scale = levels / (w - b + 1);
        let offset = b / levels * 255;

        for (let i = 0; i < data.length; i += 4) {
            data[i] = ~~(-offset + data[i] * scale);

            for (let j = 0; j < 3; j++) {
                data[i + j] = data[i];
            }

            data[i + 3] = 255; // discard alpha
        }

        return imageData;

    }
}
<file_sep>/src/lib/Depth/filters/BokehBlurFilter.js
/**
 * Bokeh blur filter
 * Based on http://devlog-martinsh.blogspot.com/2011/11/glsl-depth-of-field-with-bokeh-v21.html
 * @author <NAME>
 */

'use strict';

import fragment from './BokehBlurFilter.frag!text';
import PIXI from 'pixi.js/bin/pixi.min.js';

export default class BokehBlurFilter extends PIXI.AbstractFilter {

    /**
     * @param {PIXI.Texture} displacementMap
     */
    constructor(displacementMap) {

        super();

        this.uniforms = {
            displacementMap: {type: 'sampler2D', value: displacementMap},
            mapDimensions: {type: 'v2', value: {x: 1, y: 5112}},
            focusPoint: {type: 'v2', value: {x: 0.5671875, y: 0.6875}/* todo: set focus point automatically */},
            blurIntensity: {type: '1f', value: 1},
            blurGrowth: {type: '1f', value: 3},
            blur: {type: '1f', value: 0.75/* todo: adjust default value and range */},
            cssWidth: {type: '1f', value: 100.0},
            cssHeight: {type: '1f', value: 100.0}
        };

        if (displacementMap.baseTexture.hasLoaded) {
            this.uniforms.mapDimensions.value.x = displacementMap.width;
            this.uniforms.mapDimensions.value.y = displacementMap.height;
        } else {
            this.boundLoadedFunction = this.onTextureLoaded.bind(this);
            displacementMap.baseTexture.on('loaded', this.boundLoadedFunction);
        }

        this.displacementMap = displacementMap;
        this.fragmentSrc = fragment;
    }

    onTextureLoaded() {
        this.uniforms.mapDimensions.value.x = this.uniforms.displacementMap.value.width;
        this.uniforms.mapDimensions.value.y = this.uniforms.displacementMap.value.height;
        this.uniforms.displacementMap.value.baseTexture.off('loaded', this.boundLoadedFunction);
    }

    // <editor-fold desc="Accessors" defaultstate="collapsed">

    set displacementMap(value) {
        this.uniforms.displacementMap.value = value;
    }

    get displacementMap() {
        return this.uniforms.displacementMap.value;
    }

    /**
     * Focus point: {x: 0..1, y: 0..1}
     * Starting from top-left corner
     */

    set focusPoint(value) {
        this.uniforms.focusPoint.value = value;
    }

    get focusPoint() {
        return this.uniforms.focusPoint.value;
    }

    set blur(value) {
        this.uniforms.blur.value = value;
    }

    get blur() {
        return this.uniforms.blur.value;
    }

    set blurIntensity(value) {
        this.uniforms.blurIntensity.value = value;
    }

    get blurIntensity() {
        return this.uniforms.blurIntensity.value;
    }

    set blurGrowth(value) {
        this.uniforms.blurGrowth.value = value;
    }

    get blurGrowth() {
        return this.uniforms.blurGrowth.value;
    }

    set cssWidth(value) {
        this.uniforms.cssWidth.value = value;
    }

    get cssWidth() {
        return this.uniforms.cssWidth.value;
    }

    set cssHeight(value) {
        this.uniforms.cssHeight.value = value;
    }

    get cssHeight() {
        return this.uniforms.cssHeight.value;
    }

    // </editor-fold>
}
<file_sep>/src/lib/Depth/Error.js
/**
 * Depth photo error
 */

'use strict';

export default class extends Error {
    constructor(message, code) {
        super();
        this.message = message || '';
        this.code = code || 0;
    }
}
<file_sep>/src/lib/index.js
/**
 * Depth Viewer
 */

'use strict';

import 'angular';
import DepthDirective from './directive';
import DepthDirectiveConfig from './config';

export default angular
    .module('angular-depth', [])
    .provider('depthConfig', DepthDirectiveConfig)
    .directive('depth', DepthDirective);
<file_sep>/src/demo/demoImages.js
export default [
    'images/xdm_sample_1-sm.jpg',
    'images/xdm_sample_2-sm.jpg',
    'images/xdm_sample_3-sm.jpg',
    'images/xdm_sample_4-sm.jpg',
    'images/xdm_sample_5-sm.jpg',
    'images/xdm_sample_6-sm.jpg',
    'images/xdm_sample_7-sm.jpg',
    'images/dec14-2-sm.jpg',
    'images/dec14-3-sm.jpg',
    'images/dec14-4-sm.jpg',
    'images/IMG_DEPTH_2015120822065086-sm.jpg',
    'images/IMG_DEPTH_20151209000719899-sm.jpg',
    'images/glb1-sm.jpg',
    'images/glb2-sm.jpg',
    'images/z1-sm.jpg',
    'images/z2-sm.jpg',
    'images/z3-sm.jpg',
    'images/z4-sm.jpg',
    'images/z5-sm.jpg',
    'images/z6-sm.jpg',
    'images/z7-sm.jpg',
    'images/z8-sm.jpg',
    'images/z9-sm.jpg',
    'images/z10-sm.jpg',
    'images/z11-sm.jpg',
    'images/z12-sm.jpg',
    'images/z13-sm.jpg',
    'images/z14-sm.jpg',
    'images/z15-sm.jpg',
    'images/z16-sm.jpg',
    'images/z17-sm.jpg',
    'images/z18-sm.jpg',
    'images/z19-sm.jpg',
    'images/z20-sm.jpg',
    'images/z21-sm.jpg',
    'images/z22-sm.jpg',
    'images/z23-sm.jpg',
    'images/z24-sm.jpg',
    'images/z25-sm.jpg',
    'images/z26-sm.jpg',
    'images/z27-sm.jpg',
    'images/z28-sm.jpg',
    'images/z29-sm.jpg',
    'images/z30-sm.jpg',
    'images/z31-sm.jpg',
    'images/z32-sm.jpg',
    'images/z33-sm.jpg',
    'images/z34-sm.jpg',
    'images/z35-sm.jpg',
    'images/z36-sm.jpg',
    'images/z37-sm.jpg',
    'images/z38-sm.jpg',
    'images/z39-sm.jpg',
    'images/z40-sm.jpg',
    'images/z41-sm.jpg',
    'images/d1-sm.jpg',
    'images/d2-sm.jpg',
    'images/d3-sm.jpg',
    'images/d4-sm.jpg',
    'images/d5-sm.jpg',
    'images/d6-sm.jpg',
    'images/d7-sm.jpg',
    'images/d8-sm.jpg',
    'images/d9-sm.jpg',
    'images/d10-sm.jpg',
    'images/d11-sm.jpg',
    'images/d12-sm.jpg',
    'images/d13-sm.jpg',
    'images/d14-sm.jpg',
    'images/d15-sm.jpg',
    'images/d16-sm.jpg',
    'images/d17-sm.jpg',
    'images/d18-sm.jpg',
    'images/d19-sm.jpg',
    'images/d20-sm.jpg',
    'images/d21-sm.jpg',
    'images/d22-sm.jpg',
    'images/glb3.jpg',
    'images/glb4.jpg'
];
<file_sep>/dev/gulp/config.js
/**
 * Gulp configuration
 */

'use strict';

// paths
exports.paths = {};
exports.paths.root = __dirname + '/../..';
exports.paths.tests = exports.paths.root + '/src/tests';
exports.paths.demo = exports.paths.root + '/src/demo';
exports.paths.demo_build = exports.paths.root + '/src/demo/build';

exports.demoBundleVendors = {
    src: exports.paths.demo + '/vendors.js',
    dest: exports.paths.demo_build + '/vendors-bundle.js'
}

exports.demoComplileScripts = {
    src: exports.paths.demo + '/app.js',
    dest: exports.paths.demo_build + '/build.js'
}
<file_sep>/src/demo/app.js
/**
 * Demo app
 */

'use strict';

import angular from 'angular';
import depthViewerModule from '../lib/index';
import dat from 'dat-gui';
import demoImages from './demoImages';

// load app modules
let app = angular.module('app', [depthViewerModule.name]);

app.config(($sceProvider, depthConfigProvider) => {
    'ngInject';

    $sceProvider.enabled(false);

    depthConfigProvider.defaults.debug = true;
    depthConfigProvider.defaults.src = 'images/xdm_sample_6-sm.jpg';
    depthConfigProvider.defaults.size = '640x480';
});

// main controller
app.controller('MainController', ($scope, depthConfig) => {
    'ngInject';

    let gui = new dat.GUI({width: 350});
    let localSrcFileInput = document.getElementById('local-src-file-input');

    let srcSelector = {
        src: '',
        localSrc: () => localSrcFileInput.click()
    };

    $scope.attrs = angular.copy(depthConfig.defaults);

    function onChange() {
        $scope.$digest();
    }

    localSrcFileInput.addEventListener('change', function() {
        let file = localSrcFileInput.files[0];

        if (!file) {
            return;
        }

        let reader = new FileReader();

        reader.onload = (event) => {
            $scope.$apply(() => $scope.attrs.src = event.target.result);
        };

        reader.readAsDataURL(file);
    });

    // monitor parameters
    gui.add(srcSelector, 'src', demoImages).onChange(() => $scope.$apply(() => $scope.attrs.src = srcSelector.src));
    gui.add(srcSelector, 'localSrc');

    gui.add($scope.attrs, 'debug').onChange(onChange);
    gui.add($scope.attrs, 'trace').onChange(onChange);
    gui.add($scope.attrs, 'size').onChange(onChange);
    gui.add($scope.attrs, 'enlarge', 0.5, 2).onChange(onChange);

    gui.add($scope.attrs, 'animate').onChange(onChange);
    gui.add($scope.attrs, 'animateDuration', 0, 10).onChange(onChange);

    gui.add($scope.attrs, 'depthFocus', 0, 1).onChange(onChange);
    gui.add($scope.attrs, 'depthFactor', 0, 5).onChange(onChange);
    gui.add($scope.attrs, 'depthEqualize').onChange(onChange);
    gui.add($scope.attrs, 'depthmapSrc').onChange(onChange);
    gui.add($scope.attrs, 'depthPreview', 0, 1).onChange(onChange);
    gui.add($scope.attrs, 'depthBlurSize', 0, 25).onChange(onChange);

    gui.add($scope.attrs, 'mouseTracking').onChange(onChange);
    gui.add($scope.attrs, 'gyroTracking').onChange(onChange);
    gui.add($scope.attrs, 'gyroTrackingFactor', 0, 3).onChange(onChange);
    gui.add($scope.attrs, 'faceTracking').onChange(onChange);
    gui.add($scope.attrs, 'faceTrackingDebug').onChange(onChange);
    gui.add($scope.attrs, 'faceTrackingFactor', 0, 5).onChange(onChange);

    gui.add($scope.attrs, 'refocus').onChange(onChange);
    gui.add($scope.attrs, 'focusPoint').listen().onChange(onChange);
    gui.add($scope.attrs, 'focusPointChangeable').onChange(onChange);
    gui.add($scope.attrs, 'focusBlur', 0, 1).listen().onChange(onChange);
    gui.add($scope.attrs, 'focusBlurChangeable').onChange(onChange);

    $scope.onRefocus = (refocus) => {
        $scope.attrs.focusPoint = `${refocus.focusPoint.x}x${refocus.focusPoint.y}`;
        $scope.attrs.focusBlur = refocus.focusBlur;
    }
});

// bootstrap applicatiuon
angular.element(window.document).ready(() => angular.bootstrap(window.document, ['app']));
<file_sep>/dev/gulp/tasks/demo/build.js
/**
 * Build
 **/

'use strict';

var gulp = require('gulp');
var runSequence = require('run-sequence');

gulp.task('demo:build:development', function (callback) {
    runSequence(
        'demo:cleanup',
        'demo:set-environment:development',
        'demo:bundle-vendors',
        callback
    );
});

gulp.task('demo:build:production', function (callback) {
    runSequence(
        'demo:cleanup',
        'demo:set-environment:production',
        'demo:compile-scripts',
        'demo:optimize-assets',
        callback
    );
});
<file_sep>/dev/gulp/tasks/demo/cleanup.js
/**
   * Cleanup build files
   **/

  'use strict';

  var gulp = require('gulp');
  var del = require('del');
  var config = require('../../config');

  gulp.task('demo:cleanup', function () {
    del.sync(config.demoBundleVendors.dest + '*');
});
<file_sep>/dev/gulp/tasks/demo/set-environment.js
/**
 * Set environment (and write it to relevant files)
 **/

'use strict';

var gulp = require('gulp');
var fs = require('fs');
var config = require('../../config');

function setEnvironment(environment) {
  // set environment
  fs.writeFileSync(config.paths.demo + '/environment', environment); // global
  fs.writeFileSync(config.paths.demo + '/_environment.js', 'export const environment="' + environment + '";'); // js
}

// create tasks for each environment

gulp.task('demo:set-environment:production', function () {
  setEnvironment('production');
});

gulp.task('demo:set-environment:development', function () {
  setEnvironment('development');
});
