├── app ├── CNAME ├── styles │ ├── _rwd.scss │ ├── fonts │ │ ├── icomoon.eot │ │ ├── icomoon.ttf │ │ └── icomoon.woff │ ├── _mixins.scss │ ├── _poor.scss │ ├── _bootstrap-mixins.scss │ ├── _animations.scss │ ├── _bootstrap.scss │ ├── _icomoon.scss │ └── main.scss ├── .buildignore ├── robots.txt ├── icon.png ├── logo.png ├── favicon.png ├── preview.gif ├── samples │ ├── hut-depth.jpg │ ├── hut-image.jpg │ ├── hut-thumb.jpg │ ├── mango-depth.jpg │ ├── mango-image.jpg │ ├── mango-thumb.jpg │ ├── shelf-depth.jpg │ ├── shelf-image.jpg │ ├── shelf-thumb.jpg │ ├── flowers-depth.jpg │ ├── flowers-image.jpg │ ├── flowers-thumb.jpg │ ├── tunnel-depth.jpg │ ├── tunnel-image.jpg │ ├── tunnel-thumb.jpg │ ├── hut-alternative.jpg │ ├── mango-alternative.jpg │ ├── shelf-alternative.jpg │ ├── tunnel-alternative.jpg │ └── flowers-alternative.jpg ├── views │ ├── howto-lensblur.html │ ├── options-style.html │ ├── options-debug.html │ ├── alert-noimage.html │ ├── alert-modal.html │ ├── alert-webgl.html │ ├── options-movement.html │ ├── options-animation.html │ ├── alert-nodepth.html │ ├── share-popup.html │ ├── export-gif-popup.html │ ├── export-png-modal.html │ ├── export-jpg-modal.html │ ├── options-popup.html │ ├── export-anaglyph-modal.html │ ├── export-webm-popup.html │ ├── export-webm-modal.html │ ├── image-info-modal.html │ ├── share-png-modal.html │ ├── export-gif-modal.html │ ├── draw.html │ └── main.html ├── scripts │ ├── directives │ │ ├── depthyViewer.js │ │ ├── fileselect.js │ │ ├── visibleClass.js │ │ ├── pixi.js │ │ ├── shareurls.js │ │ └── rangeStepper.js │ ├── modernizr-platform.js │ ├── controllers │ │ ├── exportpngmodal.js │ │ ├── exportanaglyphmodal.js │ │ ├── exportwebmmodal.js │ │ ├── exportjpgmodal.js │ │ ├── imageinfomodal.js │ │ ├── sharepngmodal.js │ │ ├── exportgifmodal.js │ │ ├── draw.js │ │ └── main.js │ ├── pixi │ │ ├── utils.js │ │ ├── ColorMatrixFilter2.js │ │ ├── DepthDisplacementFilter.js │ │ └── DepthPerspectiveFilter.glsl │ ├── services │ │ ├── UpdateCheck.js │ │ └── statemodal.js │ ├── classes │ │ ├── dataURITools.js │ │ ├── DepthyDrawer.js │ │ └── GDepthEncoder.js │ ├── vendor │ │ ├── LensBlurDepthExtractor.js │ │ └── md5.js │ ├── app.js │ └── modernizr.js ├── images │ └── logo.svg ├── 404.html └── index.html ├── .gitattributes ├── .bowerrc ├── .gitignore ├── .travis.yml ├── Depthy.sublime-project ├── test ├── runner.html ├── spec │ ├── services │ │ └── statemodal.js │ ├── directives │ │ ├── pixi.js │ │ └── fileselect.js │ ├── controllers │ │ ├── main.js │ │ ├── viewer.js │ │ ├── exportmodal.js │ │ ├── sharepngmodal.js │ │ └── imageinfomodal.js │ └── classes │ │ └── GDepthEncoder.js └── .jshintrc ├── docker ├── sources.list └── Dockerfile ├── .editorconfig ├── .jshintrc ├── bower.json ├── LICENSE ├── package.json ├── karma-e2e.conf.js ├── karma.conf.js └── README.md /app/CNAME: -------------------------------------------------------------------------------- 1 | depthy.me -------------------------------------------------------------------------------- /app/styles/_rwd.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /app/.buildignore: -------------------------------------------------------------------------------- 1 | *.coffee -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/icon.png -------------------------------------------------------------------------------- /app/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/logo.png -------------------------------------------------------------------------------- /app/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/favicon.png -------------------------------------------------------------------------------- /app/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/preview.gif -------------------------------------------------------------------------------- /app/samples/hut-depth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/hut-depth.jpg -------------------------------------------------------------------------------- /app/samples/hut-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/hut-image.jpg -------------------------------------------------------------------------------- /app/samples/hut-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/hut-thumb.jpg -------------------------------------------------------------------------------- /app/samples/mango-depth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/mango-depth.jpg -------------------------------------------------------------------------------- /app/samples/mango-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/mango-image.jpg -------------------------------------------------------------------------------- /app/samples/mango-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/mango-thumb.jpg -------------------------------------------------------------------------------- /app/samples/shelf-depth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/shelf-depth.jpg -------------------------------------------------------------------------------- /app/samples/shelf-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/shelf-image.jpg -------------------------------------------------------------------------------- /app/samples/shelf-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/shelf-thumb.jpg -------------------------------------------------------------------------------- /app/samples/flowers-depth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/flowers-depth.jpg -------------------------------------------------------------------------------- /app/samples/flowers-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/flowers-image.jpg -------------------------------------------------------------------------------- /app/samples/flowers-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/flowers-thumb.jpg -------------------------------------------------------------------------------- /app/samples/tunnel-depth.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/tunnel-depth.jpg -------------------------------------------------------------------------------- /app/samples/tunnel-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/tunnel-image.jpg -------------------------------------------------------------------------------- /app/samples/tunnel-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/tunnel-thumb.jpg -------------------------------------------------------------------------------- /app/styles/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/styles/fonts/icomoon.eot -------------------------------------------------------------------------------- /app/styles/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/styles/fonts/icomoon.ttf -------------------------------------------------------------------------------- /app/styles/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/styles/fonts/icomoon.woff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | app/bower_components 6 | *.sublime-workspace 7 | -------------------------------------------------------------------------------- /app/samples/hut-alternative.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/hut-alternative.jpg -------------------------------------------------------------------------------- /app/samples/mango-alternative.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/mango-alternative.jpg -------------------------------------------------------------------------------- /app/samples/shelf-alternative.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/shelf-alternative.jpg -------------------------------------------------------------------------------- /app/samples/tunnel-alternative.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/tunnel-alternative.jpg -------------------------------------------------------------------------------- /app/samples/flowers-alternative.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cafe/depthy/master/app/samples/flowers-alternative.jpg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.8' 4 | - '0.10' 5 | before_script: 6 | - 'npm install -g bower grunt-cli' 7 | - 'bower install' 8 | -------------------------------------------------------------------------------- /Depthy.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": ".", 6 | "folder_exclude_patterns": ["node_modules", ".tmp"] 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /app/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | @function remify($v) { 4 | @if $v == false or unit($v) != "px" { 5 | @return $v; 6 | } @else { 7 | @return ($v / $font-size-base) * 1rem; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/views/howto-lensblur.html: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/views/options-style.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docker/sources.list: -------------------------------------------------------------------------------- 1 | deb http://archive.ubuntu.com/ubuntu trusty main restricted universe multiverse 2 | deb http://archive.ubuntu.com/ubuntu trusty-security main restricted universe multiverse 3 | deb http://archive.ubuntu.com/ubuntu trusty-updates main restricted universe multiverse 4 | deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse 5 | 6 | -------------------------------------------------------------------------------- /test/spec/services/statemodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Service: Statemodal', function () { 4 | 5 | // load the service's module 6 | beforeEach(module('depthyApp')); 7 | 8 | // instantiate service 9 | var Statemodal; 10 | beforeEach(inject(function (_Statemodal_) { 11 | Statemodal = _Statemodal_; 12 | })); 13 | 14 | it('should do something', function () { 15 | expect(!!Statemodal).toBe(true); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /app/views/options-debug.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
6 |
7 |
8 | 9 |
12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /app/views/alert-noimage.html: -------------------------------------------------------------------------------- 1 |
2 |

{{depthy.getLoadError() || 'No image!'}}

3 |
4 |
5 |

6 | Sorry, this image could not be loaded. 7 |

8 |
9 |
Try another
10 |
Open the gallery
11 |
12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /app/views/alert-modal.html: -------------------------------------------------------------------------------- 1 | 7 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/spec/directives/pixi.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Directive: pixiview', function () { 4 | 5 | // load the directive's module 6 | beforeEach(module('depthyApp')); 7 | 8 | var element, 9 | scope; 10 | 11 | beforeEach(inject(function ($rootScope) { 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should make hidden element visible', inject(function ($compile) { 16 | element = angular.element(''); 17 | element = $compile(element)(scope); 18 | expect(element.text()).toBe('this is the pixiview directive'); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /test/spec/directives/fileselect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Directive: fileselect', function () { 4 | 5 | // load the directive's module 6 | beforeEach(module('depthyApp')); 7 | 8 | var element, 9 | scope; 10 | 11 | beforeEach(inject(function ($rootScope) { 12 | scope = $rootScope.$new(); 13 | })); 14 | 15 | it('should make hidden element visible', inject(function ($compile) { 16 | element = angular.element(''); 17 | element = $compile(element)(scope); 18 | expect(element.text()).toBe('this is the fileselect directive'); 19 | })); 20 | }); 21 | -------------------------------------------------------------------------------- /app/views/alert-webgl.html: -------------------------------------------------------------------------------- 1 |
2 |

So sad!

3 |
4 |
5 |

6 | Sorry, Depthy works only on browsers supporting WebGL. 7 |

8 |

9 | Unfortunately, there's no official support on iOS. It should work on your Mac / PC / Android though. 10 |

11 |

12 | It's strange, because it should work on Android. Check the latest Chrome, Firefox or Opera. 13 | Built-in browsers may not be fully supported. 14 |

15 |
16 | -------------------------------------------------------------------------------- /test/spec/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: MainCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('depthyApp')); 7 | 8 | var MainCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | MainCtrl = $controller('MainCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/viewer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ViewerCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('depthyApp')); 7 | 8 | var ViewerCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | ViewerCtrl = $controller('ViewerCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/spec/controllers/exportmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ExportmodalCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('depthyApp')); 7 | 8 | var ExportmodalCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | ExportmodalCtrl = $controller('ExportmodalCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:trusty 2 | MAINTAINER Sébastien M-B 3 | 4 | RUN locale-gen en_US.UTF-8 5 | ENV LANG en_US.UTF-8 6 | ENV LANGUAGE en_US:en 7 | ENV LC_ALL en_US.UTF-8 8 | 9 | ADD sources.list /etc/apt/ 10 | 11 | RUN apt-get update 12 | RUN apt-get install -y git 13 | RUN apt-get install -y nodejs npm ruby-compass 14 | 15 | RUN ln -s /usr/bin/nodejs /usr/bin/node 16 | RUN npm install -g grunt-cli bower 17 | 18 | RUN git clone https://github.com/panrafal/depthy.git 19 | WORKDIR /depthy 20 | RUN npm install 21 | RUN bower install --allow-root --config.interactive=false 22 | 23 | EXPOSE 9000 24 | ENTRYPOINT grunt serve 25 | 26 | -------------------------------------------------------------------------------- /test/spec/controllers/sharepngmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: SharepngmodalCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('depthyApp')); 7 | 8 | var SharepngmodalCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | SharepngmodalCtrl = $controller('SharepngmodalCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /app/views/options-movement.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
9 | 10 |
11 |
12 | 13 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /test/spec/controllers/imageinfomodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Controller: ImageinfomodalCtrl', function () { 4 | 5 | // load the controller's module 6 | beforeEach(module('depthyApp')); 7 | 8 | var ImageinfomodalCtrl, 9 | scope; 10 | 11 | // Initialize the controller and a mock scope 12 | beforeEach(inject(function ($controller, $rootScope) { 13 | scope = $rootScope.$new(); 14 | ImageinfomodalCtrl = $controller('ImageinfomodalCtrl', { 15 | $scope: scope 16 | }); 17 | })); 18 | 19 | it('should attach a list of awesomeThings to the scope', function () { 20 | expect(scope.awesomeThings.length).toBe(3); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /app/scripts/directives/depthyViewer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .directive('depthyViewer', function () { 5 | return { 6 | restrict: 'A', 7 | scope: true, 8 | controller: function($scope, $element, $attrs) { 9 | var viewer, 10 | options = $scope.$parent.$eval($attrs.depthyViewer); 11 | 12 | $scope.$parent.$watch($attrs.depthyViewer, function(newOptions) { 13 | if (viewer && newOptions) { 14 | viewer.setOptions(options); 15 | } 16 | }, true); 17 | 18 | viewer = new DepthyViewer($element[0], options); 19 | 20 | this.getViewer = function() { 21 | return viewer; 22 | }; 23 | 24 | }, 25 | }; 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /app/scripts/modernizr-platform.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | Modernizr.addTest('ios', /iPad|iPhone|iPod/.test(window.navigator.userAgent)); 4 | Modernizr.addTest('android', /Android/i.test(window.navigator.userAgent)); 5 | Modernizr.addTest('winphone', /IEMobile/i.test(window.navigator.userAgent)); 6 | Modernizr.addTest('mobile', /iPad|iPhone|iPod|Android|IEMobile/.test(window.navigator.userAgent)); 7 | Modernizr.addTest('ff', /Firefox/.test(window.navigator.userAgent)); 8 | Modernizr.addTest('ie', /; MSIE/.test(window.navigator.userAgent)); 9 | Modernizr.addTest('chrome', /\bChrome\b/.test(window.navigator.userAgent)); 10 | Modernizr.addTest('safari', /\bSafari\b/.test(window.navigator.userAgent) && !/\bChrome\b/.test(window.navigator.userAgent)); 11 | })(); 12 | -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": true, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "after": false, 23 | "afterEach": false, 24 | "angular": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "browser": false, 28 | "describe": false, 29 | "expect": false, 30 | "inject": false, 31 | "it": false, 32 | "jasmine": false, 33 | "spyOn": false 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /app/scripts/controllers/exportpngmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .controller('ExportPngModalCtrl', function ($scope, $sce, $timeout, $window, depthy) { 5 | $scope.loading = true; 6 | // wait for animation 7 | $timeout(function() { 8 | depthy.getViewer().exportToPNG(null).then( 9 | function(url) { 10 | // shorten this! 11 | url = URL.createObjectURL($window.dataURItoBlob(url)); 12 | var img = angular.element('img[image-source="export-png-modal"]')[0]; 13 | img.onload = function() { 14 | $scope.loading = false; 15 | $scope.$safeApply(); 16 | }; 17 | img.src = url; 18 | angular.element('a[image-source="export-png-modal"]').attr('href', url); 19 | } 20 | ); 21 | }, depthy.modalWait); 22 | }); 23 | -------------------------------------------------------------------------------- /app/scripts/controllers/exportanaglyphmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .controller('ExportAnaglyphModalCtrl', function ($scope, $sce, $timeout, $window, depthy) { 5 | $scope.loading = true; 6 | // wait for animation 7 | $timeout(function() { 8 | depthy.getViewer().exportAnaglyph({}).then( 9 | function(url) { 10 | // shorten this! 11 | url = URL.createObjectURL($window.dataURItoBlob(url)); 12 | var img = angular.element('img[image-source="export-anaglyph-modal"]')[0]; 13 | img.onload = function() { 14 | $scope.loading = false; 15 | $scope.$safeApply(); 16 | }; 17 | img.src = url; 18 | angular.element('a[image-source="export-anaglyph-modal"]').attr('href', url); 19 | } 20 | ); 21 | }, depthy.modalWait); 22 | }); 23 | -------------------------------------------------------------------------------- /app/views/options-animation.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
6 | 7 |
8 |
9 | 10 | 11 |
16 |
17 | 18 | 19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": true, 7 | "curly": false, 8 | "eqeqeq": true, 9 | "immed": true, 10 | "indent": 2, 11 | "latedef": true, 12 | "newcap": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": true, 18 | "strict": true, 19 | "trailing": true, 20 | "smarttabs": true, 21 | "globals": { 22 | "angular": false, 23 | "$": false, 24 | "Modernizr": false, 25 | "PIXI": false, 26 | "GIF": false, 27 | "isJpg": false, 28 | "isPng": false, 29 | "DepthReader": false, 30 | "DepthyViewer": false, 31 | "DepthyDrawer": false, 32 | "GDepthEncoder": false, 33 | "Promise": false, 34 | "_": true, 35 | "requestAnimFrame": true, 36 | "screenfull": true, 37 | "IScroll": true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/views/alert-nodepth.html: -------------------------------------------------------------------------------- 1 |
2 |

This image is flat as a pancake!

3 |
4 |
5 |

6 | Depthy works with photos shot using LensBlur camera mode, or created with Depthy itself. 7 |

8 |
9 |
Load depthmap
10 |
Draw it
11 |
Try another
12 |
13 |
14 | 15 | Google Photos strips out the depthmap. Select Documents when prompted and find your LensBlur photo on your device.
16 |
17 | -------------------------------------------------------------------------------- /app/styles/_poor.scss: -------------------------------------------------------------------------------- 1 | #oldbrowser { 2 | display:none; 3 | 4 | 5 | } 6 | 7 | // html.csstransforms, 8 | html.no-csstransforms, 9 | html.no-rgba, 10 | html.no-backgroundsize, 11 | html.no-webgl, 12 | html.no-todataurljpeg, 13 | html.no-todataurlpng, 14 | html.no-datauri, 15 | html.no-js, 16 | html.no-canvas { 17 | 18 | body { 19 | } 20 | 21 | #oldbrowser { 22 | display:block; 23 | position:fixed; 24 | width:100%; 25 | height:100%; 26 | z-index:3000; 27 | left:0px; 28 | top:0px; 29 | background: $body-bg; 30 | text-align: center; 31 | 32 | .container { 33 | margin-top: 4em; 34 | max-width: 600px; 35 | } 36 | 37 | h1 { 38 | text-indent: -9000px; 39 | background: url('../logo.png') 50% 0% no-repeat; 40 | height: 200px; 41 | } 42 | 43 | .preview { 44 | width: 200px; 45 | height: 200px; 46 | background: url('../preview.gif') 50% 50% no-repeat; 47 | } 48 | } 49 | #main { 50 | display:none; 51 | } 52 | } -------------------------------------------------------------------------------- /app/scripts/pixi/utils.js: -------------------------------------------------------------------------------- 1 | 2 | PIXI.glReadPixels = function(gl, frameBuffer, x, y, width, height, pixels) { 3 | if (!pixels) pixels = new Uint8Array(4 * width * height); 4 | 5 | if (frameBuffer instanceof PIXI.RenderTexture) { 6 | frameBuffer = frameBuffer.textureBuffer.frameBuffer; 7 | } 8 | 9 | gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer); 10 | gl.viewport(0, 0, width, height); 11 | gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); 12 | gl.bindFramebuffer(gl.FRAMEBUFFER, null); 13 | 14 | return pixels; 15 | }; 16 | 17 | PIXI.glReadPixelsToCanvas = function(gl, frameBuffer, x, y, width, height) { 18 | var canvas = document.createElement('canvas'), 19 | ctx = canvas.getContext('2d'), 20 | imgdata = ctx.createImageData(width, height); 21 | 22 | canvas.width = width; 23 | canvas.height = height; 24 | 25 | PIXI.glReadPixels(gl, frameBuffer, x, y, width, height, new Uint8Array(imgdata.data.buffer)); 26 | 27 | ctx.putImageData(imgdata, 0, 0); 28 | 29 | return canvas; 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "depthy", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.2.16", 6 | "angular-animate": "~1.2.16", 7 | "angular-ga": "~0.1.0", 8 | "angular-motion": "~0.3.2", 9 | "angular-sanitize": "~1.2.16", 10 | "angular-strap": "~2.0.1", 11 | "angular-touch": "~1.2.16", 12 | "angular-ui-bootstrap": "~0.11.0", 13 | "angular-ui-router": "~0.2.10", 14 | "bootstrap-sass-official": "~3.1.0", 15 | "gif.js": "panrafal/gif.js#master", 16 | "is-jpg": "~0.1.2", 17 | "is-png": "~0.1.1", 18 | "jquery": "~1.11.0", 19 | "lodash": "~2.4.1", 20 | "pixi.js": "panrafal/pixi.js#build/depthy", 21 | "promise": "http://s3.amazonaws.com/es6-promises/promise-1.0.0.js", 22 | "screenfull": "~1.2.1", 23 | "iscroll": "~5.1.1", 24 | "stats.js": "*", 25 | "whammy": "antimatter15/whammy" 26 | }, 27 | "devDependencies": { 28 | "angular-mocks": "1.2.15", 29 | "angular-scenario": "1.2.15" 30 | }, 31 | "resolutions": { 32 | "angular": "~1.2.16" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/scripts/services/UpdateCheck.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .service('UpdateCheck', function UpdateCheck($window, $q) { 5 | 6 | this.check = function(update) { 7 | var appCache = $window.applicationCache; 8 | if (!appCache) { 9 | return $q.reject(); 10 | } 11 | 12 | var deferred = $q.defer(); 13 | // Check if a new cache is available on page load. 14 | $window.addEventListener('load', function(e) { 15 | 16 | if (update && appCache.status === appCache.IDLE) { 17 | console.log('Updating the app cache!'); 18 | appCache.update(); 19 | } 20 | 21 | appCache.addEventListener('updateready', function(e) { 22 | if (appCache.status === window.applicationCache.UPDATEREADY) { 23 | console.log('Got update!'); 24 | deferred.resolve(true); 25 | } else { 26 | deferred.resolve(false); 27 | } 28 | }, false); 29 | 30 | appCache.addEventListener('noupdate', function(e) { 31 | deferred.resolve(false); 32 | }, false); 33 | 34 | }, false); 35 | 36 | return deferred.promise; 37 | } 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /app/views/share-popup.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 Rafał Lindemann. http://panrafal.github.com/depthy 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/views/export-gif-popup.html: -------------------------------------------------------------------------------- 1 | 4 | 20 | 26 | -------------------------------------------------------------------------------- /app/views/export-png-modal.html: -------------------------------------------------------------------------------- 1 | 5 | 25 | 28 | -------------------------------------------------------------------------------- /app/views/export-jpg-modal.html: -------------------------------------------------------------------------------- 1 | 5 | 25 | 28 | -------------------------------------------------------------------------------- /app/scripts/classes/dataURITools.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | window.dataURItoArrayBuffer = function dataURItoArrayBuffer(dataURI) { 5 | // convert base64 to raw binary data held in a string 6 | // doesn't handle URLEncoded DataURIs 7 | var byteString; 8 | if (dataURI.split(',')[0].indexOf('base64') >= 0) 9 | byteString = atob(dataURI.split(',')[1]); 10 | else 11 | byteString = window.unescape(dataURI.split(',')[1]); 12 | // separate out the mime component 13 | var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 14 | 15 | // write the bytes of the string to an ArrayBuffer 16 | var ab = new ArrayBuffer(byteString.length); 17 | var ia = new Uint8Array(ab); 18 | for (var i = 0; i < byteString.length; i++) { 19 | ia[i] = byteString.charCodeAt(i); 20 | } 21 | 22 | return {buffer: ab, mime: mimeString}; 23 | }; 24 | 25 | window.dataURItoBlob = function dataURItoBlob(dataURI) { 26 | var buffer = window.dataURItoArrayBuffer(dataURI); 27 | // write the ArrayBuffer to a blob, and you're done 28 | return new Blob([buffer.buffer],{type: buffer.mime}); 29 | }; 30 | 31 | 32 | })(); 33 | // credit goes to http://stackoverflow.com/questions/4998908/convert-data-uri-to-file-then-append-to-formdata 34 | 35 | -------------------------------------------------------------------------------- /app/views/options-popup.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | -------------------------------------------------------------------------------- /app/views/export-anaglyph-modal.html: -------------------------------------------------------------------------------- 1 | 5 | 27 | 30 | -------------------------------------------------------------------------------- /app/views/export-webm-popup.html: -------------------------------------------------------------------------------- 1 | 4 | 23 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "depthy", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "devDependencies": { 6 | "grunt": "~0.4.1", 7 | "grunt-angular-templates": "~0.5.4", 8 | "grunt-autoprefixer": "~0.6.5", 9 | "grunt-concurrent": "~0.5.0", 10 | "grunt-contrib-clean": "~0.5.0", 11 | "grunt-contrib-compass": "~0.7.2", 12 | "grunt-contrib-concat": "~0.4.0", 13 | "grunt-contrib-connect": "~0.7.1", 14 | "grunt-contrib-copy": "~0.5.0", 15 | "grunt-contrib-cssmin": "~0.9.0", 16 | "grunt-contrib-htmlmin": "~0.3.0", 17 | "grunt-contrib-imagemin": "~0.7.0", 18 | "grunt-contrib-jshint": "~0.10.0", 19 | "grunt-contrib-uglify": "~0.4.0", 20 | "grunt-contrib-watch": "~0.6.1", 21 | "grunt-google-cdn": "~0.2.2", 22 | "grunt-karma": "~0.8.2", 23 | "grunt-newer": "~0.7.0", 24 | "grunt-ngmin": "~0.0.3", 25 | "grunt-rev": "~0.1.0", 26 | "grunt-svgmin": "~0.4.0", 27 | "grunt-usemin": "~2.1.1", 28 | "jshint-stylish": "~0.1.3", 29 | "karma": "~0.12.9", 30 | "karma-ng-html2js-preprocessor": "~0.1.0", 31 | "karma-ng-scenario": "~0.1.0", 32 | "load-grunt-tasks": "~0.4.0", 33 | "time-grunt": "~0.3.1", 34 | "grunt-manifest": "~0.4.0", 35 | "karma-jasmine": "~0.1.5", 36 | "karma-phantomjs-launcher": "~0.1.4", 37 | "karma-chrome-launcher": "~0.1.4" 38 | }, 39 | "engines": { 40 | "node": ">=0.10.0" 41 | }, 42 | "scripts": { 43 | "test": "grunt test" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/styles/_bootstrap-mixins.scss: -------------------------------------------------------------------------------- 1 | // Buttons 2 | 3 | @mixin button-variant($color, $background, $border) { 4 | color: $color; 5 | background-color: $background; 6 | transition: all 0.2s ease; 7 | // border-color: $background; 8 | border: none; 9 | box-shadow: none; 10 | 11 | 12 | &:hover, 13 | &:focus, 14 | &:active, 15 | &.active, 16 | .open > &.dropdown-toggle { 17 | color: $color; 18 | background-color: lighten($background, $contrast / 2); 19 | // border-color: darken($border, 12%); 20 | } 21 | &:active, 22 | &.active, 23 | .open > &.dropdown-toggle { 24 | background-color: lighten($background, $contrast); 25 | } 26 | &.disabled, 27 | &[disabled], 28 | fieldset[disabled] & { 29 | &, 30 | &:hover, 31 | &:focus, 32 | &:active, 33 | &.active { 34 | opacity: 1; 35 | background-color: desaturate($background, 50%); 36 | color: darken($color, $contrast * 4); 37 | border-color: $border; 38 | } 39 | } 40 | 41 | .badge { 42 | color: $background; 43 | background-color: $color; 44 | } 45 | } 46 | 47 | 48 | 49 | // Alerts 50 | 51 | @mixin alert-variant($background, $border, $text-color) { 52 | background-color: fade-out($background, 0.8); 53 | border-color: fade-out(darken($background, $contrast), 0.6); 54 | color: $text-color; 55 | 56 | hr { 57 | border-top-color: darken($border, 5%); 58 | } 59 | .alert-link { 60 | color: darken($text-color, 10%); 61 | } 62 | 63 | .icon:first-child { 64 | color: $background; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /karma-e2e.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['ng-scenario'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'test/e2e/**/*.js' 15 | ], 16 | 17 | // list of files / patterns to exclude 18 | exclude: [], 19 | 20 | // web server port 21 | port: 8080, 22 | 23 | // level of logging 24 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 25 | logLevel: config.LOG_INFO, 26 | 27 | 28 | // enable / disable watching file and executing tests whenever any file changes 29 | autoWatch: false, 30 | 31 | 32 | // Start these browsers, currently available: 33 | // - Chrome 34 | // - ChromeCanary 35 | // - Firefox 36 | // - Opera 37 | // - Safari (only Mac) 38 | // - PhantomJS 39 | // - IE (only Windows) 40 | browsers: ['Chrome'], 41 | 42 | 43 | // Continuous Integration mode 44 | // if true, it capture browsers, run tests and exit 45 | singleRun: false 46 | 47 | // Uncomment the following lines if you are using grunt's server to run the tests 48 | // proxies: { 49 | // '/': 'http://localhost:9000/' 50 | // }, 51 | // URL root prevent conflicts with the site root 52 | // urlRoot: '_karma_' 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /app/scripts/controllers/exportwebmmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .controller('ExportWebmModalCtrl', function ($scope, $modalInstance, $rootElement, depthy, ga, $timeout, $sce) { 5 | $scope.exportProgress = 0; 6 | $scope.imageReady = false; 7 | $scope.shareUrl = ''; 8 | $scope.tweetUrl = null; 9 | $scope.imageOverLimit = false; 10 | 11 | $timeout(function() { 12 | var exportPromise = depthy.exportWebmAnimation(), 13 | sharePromise = null, 14 | imageDataUri = null, 15 | exportStarted = new Date(), 16 | gaLabel = 'size ' + depthy.exportSize + ' dur ' + depthy.viewer.animDuration; 17 | 18 | ga('send', 'event', 'webm', 'start', gaLabel); 19 | 20 | exportPromise.then( 21 | function exportSuccess(blob) { 22 | ga('send', 'timing', 'webm', 'created', new Date() - exportStarted, gaLabel); 23 | ga('send', 'event', 'webm', 'created', gaLabel, blob.size); 24 | 25 | $scope.size = blob.size; 26 | $scope.videoUrl = $sce.trustAsResourceUrl(URL.createObjectURL(blob)); 27 | $scope.ready = true; 28 | 29 | }, 30 | function exportFailed() { 31 | $scope.exportError = 'Export failed'; 32 | }, 33 | function exportProgress(p) { 34 | $scope.exportProgress = p; 35 | $scope.$safeApply(); 36 | } 37 | ); 38 | 39 | $modalInstance.result.finally(function() { 40 | if (exportPromise) exportPromise.abort(); 41 | if ($scope.videoUrl) URL.revokeObjectURL($scope.videoUrl.toString()); 42 | }); 43 | }, depthy.modalWait); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /app/scripts/directives/fileselect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .directive('fileselect', function ($parse) { 5 | return { 6 | restrict: 'A', 7 | scope: true, 8 | link: function postLink(scope, element, attrs) { 9 | 10 | var fileInput = document.createElement('input'); 11 | fileInput.type = 'file'; 12 | fileInput.style.visibility = 'hidden'; 13 | fileInput.style.position = 'absolute'; 14 | fileInput.style.left = '-9000px'; 15 | 16 | element.append(fileInput); 17 | 18 | var onDrag = function(e) { 19 | e.stopPropagation(); 20 | e.preventDefault(); 21 | }; 22 | 23 | var onDrop = function(e) { 24 | e.stopPropagation(); 25 | e.preventDefault(); 26 | 27 | console.log(e); 28 | 29 | var dt = e.originalEvent.dataTransfer; 30 | var files = dt.files; 31 | 32 | handleFiles(files); 33 | scope.$apply(); 34 | }; 35 | 36 | function handleFiles(files) { 37 | scope.$broadcast('fileselect', files); 38 | if (attrs.fileselect) { 39 | $parse(attrs.fileselect).assign(scope, files); 40 | } 41 | } 42 | 43 | scope.selectFile = function(e) { 44 | fileInput.click(); 45 | if (e) e.preventDefault(); 46 | }; 47 | 48 | element.on('dragenter', onDrag); 49 | element.on('dragover', onDrag); 50 | element.on('drop', onDrop); 51 | fileInput.addEventListener('change', function() { 52 | handleFiles(_.filter(this.files)); 53 | fileInput.value = ''; 54 | scope.$apply(); 55 | }, false); 56 | 57 | } 58 | }; 59 | }); 60 | -------------------------------------------------------------------------------- /app/scripts/controllers/exportjpgmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .controller('ExportJpgModalCtrl', function ($scope, $sce, $timeout, $window, depthy) { 5 | $scope.loading = true; 6 | // wait for animation 7 | $timeout(function() { 8 | var imageUrl, depthUrl, originalUrl; 9 | depthy.getViewer().exportSourceImage(depthy.opened.imageSource, {quality: 0.9}).then( 10 | function(url) { 11 | imageUrl = url; 12 | return depthy.getViewer().exportDepthmap(); 13 | } 14 | ).then( 15 | function(url) { 16 | depthUrl = url; 17 | if (depthy.opened.originalSource) { 18 | return depthy.getViewer().exportSourceImage(depthy.opened.originalSource, {quality: 0.9}); 19 | } else { 20 | return false; 21 | } 22 | } 23 | ).then( 24 | function(url) { 25 | originalUrl = url; 26 | // ready! let's do this! 27 | return GDepthEncoder.encodeDepthmap(window.dataURItoArrayBuffer(imageUrl).buffer, depthUrl, originalUrl, { 28 | 'GFocus:BlurAtInfinity': '0.5', 29 | 'GFocus:FocalDistance': '10.0', 30 | 'GFocus:FocalPointX': '0.5', 31 | 'GFocus:FocalPointY': '0.5', 32 | 'GDepth:Format': 'RangeInverse', 33 | 'GDepth:Near': '5.0', 34 | 'GDepth:Far': '20.0', 35 | }); 36 | } 37 | ).then( 38 | function(blob) { 39 | var url = URL.createObjectURL(blob); 40 | var img = angular.element('img[image-source="export-jpg-modal"]')[0]; 41 | img.onload = function() { 42 | $scope.loading = false; 43 | $scope.$safeApply(); 44 | }; 45 | img.src = url; 46 | angular.element('a[image-source="export-jpg-modal"]').attr('href', url); 47 | } 48 | ); 49 | 50 | }, depthy.modalWait); 51 | }); 52 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // http://karma-runner.github.io/0.10/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | // testing framework to use (jasmine/mocha/qunit/...) 10 | frameworks: ['jasmine'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'app/bower_components/angular/angular.js', 15 | 'app/bower_components/angular-mocks/angular-mocks.js', 16 | 'app/bower_components/angular-sanitize/angular-sanitize.js', 17 | 'app/scripts/modernizr.js', 18 | 'app/scripts/vendor/md5.js', 19 | 'app/scripts/classes/dataURITools.js', 20 | 'app/scripts/classes/GDepthEncoder.js', 21 | 'app/scripts/vendor/LensBlurDepthExtractor.js', 22 | // 'app/scripts/*.js', 23 | // 'app/scripts/**/*.js', 24 | // 'test/mock/**/*.js', 25 | // 'test/spec/**/*.js' 26 | 'test/spec/classes/*.js', 27 | ], 28 | 29 | // list of files / patterns to exclude 30 | exclude: [], 31 | 32 | // web server port 33 | port: 8080, 34 | 35 | // level of logging 36 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 37 | logLevel: config.LOG_INFO, 38 | 39 | 40 | // enable / disable watching file and executing tests whenever any file changes 41 | autoWatch: true, 42 | 43 | 44 | // Start these browsers, currently available: 45 | // - Chrome 46 | // - ChromeCanary 47 | // - Firefox 48 | // - Opera 49 | // - Safari (only Mac) 50 | // - PhantomJS 51 | // - IE (only Windows) 52 | browsers: ['Chrome'], 53 | 54 | 55 | // Continuous Integration mode 56 | // if true, it capture browsers, run tests and exit 57 | singleRun: false 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /app/scripts/controllers/imageinfomodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .controller('ImageInfoModalCtrl', function ($scope, $modalInstance, ga, depthy, $timeout, StateModal) { 5 | $scope.info = {}; 6 | $scope.loading = 2; 7 | 8 | // wait for dom 9 | $timeout(function() { 10 | if (depthy.hasDepthmap()) { 11 | depthy.getViewer().exportDepthmap().then(function(url) { 12 | var img = angular.element('img[image-source="depth"]')[0]; 13 | img.onload = function() { 14 | --$scope.loading; 15 | $scope.$safeApply(); 16 | }; 17 | img.src = url; 18 | angular.element('a[image-source="depth"]').attr('href', url); 19 | }); 20 | } else --$scope.loading; 21 | if (depthy.hasOriginalImage()) { 22 | var img = angular.element('img[image-source="alternative"]')[0]; 23 | img.onload = function() { 24 | --$scope.loading; 25 | $scope.$safeApply(); 26 | }; 27 | img.src = depthy.opened.originalSource; 28 | angular.element('a[image-source="alternative"]').attr('href', depthy.opened.originalSource); 29 | } else --$scope.loading; 30 | }, depthy.modalWait); 31 | 32 | $scope.isDepthmapProcessing = false; 33 | $scope.$watch('info.depthFiles', function(files) { 34 | if (files && files.length) { 35 | $scope.isDepthmapProcessing = true; 36 | depthy.loadLocalDepthmap(files[0]).then( 37 | function(fromLensblur) { 38 | $scope.isDepthmapProcessing = false; 39 | ga('send', 'event', 'depthmap', 'parsed', fromLensblur ? 'from-lensblur' : 'from-file'); 40 | $modalInstance.dismiss(); 41 | }, 42 | function(error) { 43 | $scope.isDepthmapProcessing = false; 44 | ga('send', 'event', 'depthmap', 'error', error); 45 | StateModal.showAlert(error, {stateOptions: {location: 'replace'}}); 46 | } 47 | ); 48 | // depthy.handleCompoundFile(files[0]); 49 | } 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /app/views/export-webm-modal.html: -------------------------------------------------------------------------------- 1 | 6 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/views/image-info-modal.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 32 | 36 | 37 | -------------------------------------------------------------------------------- /app/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 22 | 23 | 24 | 26 | 27 | -------------------------------------------------------------------------------- /app/scripts/services/statemodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .service('StateModal', function StateModal($rootScope, $modal, $state, $location, $window, $q) { 5 | 6 | /** Changes $state to show a modal... 7 | Returns deffered which will go back in history if rejected, or replace the location if resolved. 8 | Back buttons will reject the promise. */ 9 | this.stateDeferred = function(state, options) { 10 | options = options || {}; 11 | var deferred = $q.defer(), deregister; 12 | deferred.state = state; 13 | 14 | // if ($state.current.name === state) state = false; 15 | if (state && !options.stateCurrent && state !== true) $state.go(state, options.stateParams, options.stateOptions); 16 | 17 | deferred.promise.then( 18 | function() { 19 | if (deregister) deregister(); 20 | if (state && $state.current.name === state) $location.replace(); 21 | }, 22 | function() { 23 | if (deregister) deregister(); 24 | if (state && $state.current.name === state) $window.history.back(); 25 | } 26 | ); 27 | 28 | if (state) { 29 | deregister = $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) { 30 | deregister(); 31 | deregister = null; 32 | if (state === true || fromState.name === state) { 33 | state = false; 34 | deferred.reject(); 35 | } 36 | }); 37 | } 38 | return deferred; 39 | }; 40 | 41 | this.showModal = function(state, options) { 42 | var deferred = this.stateDeferred(state, options), 43 | modal; 44 | 45 | modal = $modal.open(options || {}); 46 | 47 | modal.result.then( 48 | function() { 49 | deferred.resolve(); 50 | }, 51 | function() { 52 | deferred.reject(); 53 | } 54 | ); 55 | 56 | deferred.promise.finally(function() { 57 | modal.close(); 58 | }); 59 | 60 | return modal; 61 | }; 62 | 63 | this.showAlert = function(message, options, state) { 64 | return this.showModal(state || 'alert', angular.extend({ 65 | templateUrl: 'views/alert-modal.html', 66 | windowClass: 'alert-modal', 67 | scope: angular.extend($rootScope.$new(), {message: message}), 68 | }, options || {})); 69 | }; 70 | 71 | 72 | }); 73 | -------------------------------------------------------------------------------- /app/scripts/pixi/ColorMatrixFilter2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Mat Groves http://matgroves.com/ @Doormat23 3 | */ 4 | 5 | /** 6 | * 7 | * The ColorMatrixFilter2 class lets you apply a 4x4 matrix transformation on the RGBA 8 | * color and alpha values of every pixel on your displayObject to produce a result 9 | * with a new set of RGBA color and alpha values. Its pretty powerful! 10 | * @class ColorMatrixFilter 11 | * @contructor 12 | */ 13 | PIXI.ColorMatrixFilter2 = function() 14 | { 15 | 'use strict'; 16 | PIXI.AbstractFilter.call( this ); 17 | 18 | this.passes = [this]; 19 | 20 | // set the uniforms 21 | this.uniforms = { 22 | matrix: {type: 'mat4', value: [1,0,0,0, 23 | 0,1,0,0, 24 | 0,0,1,0, 25 | 0,0,0,1]}, 26 | shift: {type: '4fv', value: [0.0,0.0,0.0,0.0]}, 27 | }; 28 | 29 | this.fragmentSrc = [ 30 | 'precision mediump float;', 31 | 'varying vec2 vTextureCoord;', 32 | 'varying vec4 vColor;', 33 | 'uniform float invert;', 34 | 'uniform mat4 matrix;', 35 | 'uniform vec4 shift;', 36 | 'uniform sampler2D uSampler;', 37 | 38 | 'void main(void) {', 39 | ' gl_FragColor = texture2D(uSampler, vTextureCoord) * matrix + shift;', 40 | // ' gl_FragColor = gl_FragColor;', 41 | '}' 42 | ]; 43 | }; 44 | 45 | PIXI.ColorMatrixFilter2.prototype = Object.create( PIXI.AbstractFilter.prototype ); 46 | PIXI.ColorMatrixFilter2.prototype.constructor = PIXI.ColorMatrixFilter2; 47 | 48 | /** 49 | * Sets the matrix of the color matrix filter 50 | * 51 | * @property matrix 52 | * @type Array and array of 16 numbers 53 | * @default [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] 54 | */ 55 | Object.defineProperty(PIXI.ColorMatrixFilter2.prototype, 'matrix', { 56 | get: function() { 57 | return this.uniforms.matrix.value; 58 | }, 59 | set: function(value) { 60 | this.uniforms.matrix.value = value; 61 | } 62 | }); 63 | 64 | /** 65 | * Sets the constant channel shift 66 | * 67 | * @property shift 68 | * @type Array and array of 26 numbers 69 | * @default [0,0,0,0] 70 | */ 71 | Object.defineProperty(PIXI.ColorMatrixFilter2.prototype, 'shift', { 72 | get: function() { 73 | return this.uniforms.shift.value; 74 | }, 75 | set: function(value) { 76 | this.uniforms.shift.value = value; 77 | } 78 | }); -------------------------------------------------------------------------------- /app/styles/_animations.scss: -------------------------------------------------------------------------------- 1 | 2 | .anim-fade { 3 | transition: all 0.2s ease-in-out; 4 | 5 | &.ng-hide-remove, &.ng-enter, 6 | &.ng-hide-active, &.ng-leave-active, 7 | &.ng-hide { 8 | opacity: 0; 9 | } 10 | &.ng-hide-remove-active, &.ng-enter-active { 11 | opacity: 1; 12 | } 13 | 14 | } 15 | 16 | .anim-slideup-centered { 17 | transition: all 0.5s ease-in-out; 18 | &.ng-hide { 19 | display: block!important; 20 | } 21 | 22 | &.ng-hide-remove, &.ng-enter, 23 | &.ng-hide-active, &.ng-leave-active, 24 | &.ng-hide { 25 | visibility: hidden; 26 | transform: translate(-50%, 100%) translateY(remify(60px)); 27 | } 28 | &.ng-hide-remove-active, &.ng-enter-active { 29 | visibility: visible; 30 | transform: translateX(-50%); 31 | } 32 | } 33 | 34 | .anim-expand { 35 | transition: all 0.3s ease-in-out; 36 | 37 | &.ng-animate { 38 | visibility: visible; 39 | max-height: 150px; 40 | overflow:hidden; 41 | 42 | &.long { 43 | transition-duration: 0.3s; 44 | max-height: 300px; 45 | } 46 | &.verylong { 47 | transition-duration: 0.4s; 48 | max-height: 600px; 49 | } 50 | &.short { 51 | transition-duration: 0.2s; 52 | max-height: 50px; 53 | } 54 | 55 | &.ng-hide-remove:not(.ng-hide-remove-active), 56 | &.ng-enter:not(.ng-enter-active), 57 | &.ng-hide-active, 58 | &.ng-leave-active, 59 | &.ng-hide { 60 | overflow:hidden; 61 | display: block!important; 62 | visibility: hidden; 63 | max-height: 0px; 64 | margin-top: 0px; 65 | margin-bottom: 0px; 66 | padding-top: 0px; 67 | padding-bottom: 0px; 68 | } 69 | // &.ng-hide-remove-active, &.ng-enter-active { 70 | // max-height: 100px; 71 | // } 72 | 73 | } 74 | 75 | } 76 | 77 | 78 | 79 | 80 | @keyframes icon-rotate { 81 | 0% { 82 | transform: rotate(0deg); 83 | } 84 | 100% { 85 | transform: rotate(359deg); 86 | } 87 | } 88 | 89 | @keyframes slide-from-bottom { 90 | 0% { 91 | transform: translateY(100%); 92 | } 93 | 100% { 94 | transform: translateY(0%); 95 | } 96 | } 97 | 98 | @keyframes slide-from-left { 99 | 0% { 100 | transform: translateX(-100%); 101 | } 102 | 100% { 103 | transform: translateX(0%); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/views/share-png-modal.html: -------------------------------------------------------------------------------- 1 | 5 | 43 | 46 | -------------------------------------------------------------------------------- /app/scripts/directives/visibleClass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('visibleClass', []) 4 | .factory('VisibleClassService', function ($window) { 5 | var targets = []; 6 | 7 | function doUpdate() { 8 | var $container = $($window), 9 | scrollTop = 0, // $container.scrollTop(), 10 | scrollHeight = $container.height(), 11 | scrollBottom = scrollTop + scrollHeight, 12 | pos, visible; 13 | 14 | 15 | _.each(targets, function(item) { 16 | pos = item.el[0].getBoundingClientRect(); 17 | // if (item.el[0].className.match(/hide(?: |$)/)) return; 18 | visible = pos.top < scrollBottom && pos.bottom > scrollTop; 19 | 20 | if (item.v !== visible) { 21 | if (visible) { 22 | if (item.cls) item.el.addClass(item.cls); 23 | if (item.func) item.func(visible); 24 | item.v = visible; 25 | } else if (!item.sticky) { 26 | if (item.cls) item.el.removeClass(item.cls); 27 | if (item.func) item.func(visible); 28 | item.v = visible; 29 | } 30 | } 31 | }); 32 | } 33 | 34 | $($window).on('scroll', function() { 35 | doUpdate(); 36 | }); 37 | 38 | 39 | var svc = { 40 | addTarget: function(tgt) { 41 | targets.push(tgt); 42 | }, 43 | doUpdate: doUpdate, 44 | update: _.throttle(doUpdate, 25) 45 | }; 46 | 47 | return svc; 48 | }) 49 | .directive('visibleClass', function (VisibleClassService) { 50 | return { 51 | restrict: 'A', 52 | link: function postLink($scope, $element, $attrs) { 53 | VisibleClassService.addTarget({ 54 | el: $element, 55 | cls: $attrs.visibleClass, 56 | }); 57 | } 58 | }; 59 | }) 60 | .directive('shownClass', function (VisibleClassService) { 61 | return { 62 | restrict: 'A', 63 | link: function postLink($scope, $element, $attrs) { 64 | VisibleClassService.addTarget({ 65 | el: $element, 66 | cls: $attrs.shownClass, 67 | sticky: true, 68 | }); 69 | } 70 | }; 71 | }) 72 | .directive('shownEval', function (VisibleClassService) { 73 | return { 74 | restrict: 'A', 75 | link: function postLink($scope, $element, $attrs) { 76 | var expr = $attrs.shownEval; 77 | VisibleClassService.addTarget({ 78 | el: $element, 79 | func: function(visible) { $scope.$eval(expr, {'$visible': visible}); $scope.$apply(); }, 80 | sticky: true, 81 | }); 82 | } 83 | }; 84 | }); 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DEPTHY 2 | ====== 3 | 4 | Images with depthmap playground. 5 | 6 | Depthy shows Google Camera Lens Blur photos with 3D parallax effect and creates animated GIFs from them. Plus extracts the depth map and enables you to create your own! 7 | 8 | This is the source of the http://depthy.me/ webapp. Contributions more than welcome! 9 | 10 | ## How to build 11 | 12 | - Install node + npm 13 | - Run anywhere: `npm install -g grunt-cli bower` 14 | - Run in project directory: `npm install` and `bower install` 15 | - For local development server run: `grunt serve` 16 | - For deployment: `grunt build` 17 | 18 | ## Docker image 19 | If you want to simply run depthy locally, you can use [Docker.io](https://www.docker.com/). 20 | 21 | Once docker installed, simple run: 22 | ``` 23 | $ docker run --rm -t -i -p 9000:9000 essembeh/depthy 24 | ``` 25 | 26 | Then go to [localhost:9000](http://localhost:9000) 27 | 28 | ## Authors 29 | 30 | **[Rafał Lindemann](http://www.stamina.pl/)** (idea, code, ux) with much appreciated help of 31 | **[Łukasz Marcinkowski](http://th7.org/)** (idea, code) 32 | 33 | ## How to help 34 | 35 | There is a lot of stuff you can do with depthmaps. If you have ideas and you know how to code, 36 | You already know how to help ;) I'm pretty lax on formalities, just make it work and at least 37 | try to follow conventions of the code... 38 | 39 | ## License 40 | 41 | The MIT License 42 | 43 | Copyright (c) 2014 Rafał Lindemann. http://panrafal.github.com/depthy 44 | 45 | Permission is hereby granted, free of charge, to any person obtaining a copy 46 | of this software and associated documentation files (the "Software"), to deal 47 | in the Software without restriction, including without limitation the rights 48 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 49 | copies of the Software, and to permit persons to whom the Software is 50 | furnished to do so, subject to the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be included in 53 | all copies or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 56 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 57 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 58 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 59 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 60 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 61 | THE SOFTWARE. 62 | 63 | -------------------------------------------------------------------------------- /app/scripts/directives/pixi.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .directive('pixi', function ($parse) { 5 | return { 6 | // template: '', 7 | restrict: 'A', 8 | scope: false, 9 | controller: function postLink($scope, $element, $attrs) { 10 | 11 | var self = this, 12 | stageAttr = $parse($attrs.pixi), 13 | stage = stageAttr($scope), 14 | renderFunc = $scope.$eval($attrs.pixiRender); 15 | 16 | if (!stage) { 17 | // create a new instance of a pixi stage 18 | stage = new PIXI.Stage($scope.$eval($attrs.pixiBackground || '0')); 19 | stageAttr.assign($scope, stage); 20 | } 21 | 22 | var antialias = $scope.$eval($attrs.pixiAntialias || 'false'), 23 | transparent = $scope.$eval($attrs.pixiTransparent || 'false'), 24 | rendererType = $scope.$eval($attrs.pixiRenderer || 'auto'), 25 | renderer; 26 | // create a renderer instance. 27 | switch(rendererType) { 28 | case 'canvas': 29 | renderer = new PIXI.CanvasRenderer($element.width(), $element.height(), $element[0], transparent); 30 | break; 31 | case 'webgl': 32 | try { 33 | renderer = new PIXI.WebGLRenderer($element.width(), $element.height(), $element[0], transparent, antialias); 34 | } catch (e) { 35 | $scope.$emit('pixi.webgl.init.exception', e); 36 | return; 37 | } 38 | break; 39 | default: 40 | renderer = PIXI.autoDetectRenderer($element.width(), $element.height(), $element[0], antialias, transparent); 41 | } 42 | 43 | this.render = function render(force) { 44 | 45 | var doRender = true; 46 | if (renderFunc) doRender = renderFunc(stage, renderer); 47 | 48 | // render the stage 49 | if (force || doRender !== false) renderer.render(stage); 50 | }; 51 | 52 | function renderLoop() { 53 | self.render(); 54 | requestAnimFrame( renderLoop ); 55 | } 56 | 57 | requestAnimFrame( renderLoop ); 58 | 59 | this.getStage = function() { 60 | return stage; 61 | }; 62 | 63 | this.getRenderer = function() { 64 | return renderer; 65 | }; 66 | 67 | this.getContext = function() { 68 | return renderer.gl ? renderer.gl : renderer.context; 69 | }; 70 | 71 | 72 | // $($window).resize(function() { 73 | // renderer.resize(element.width(), element.height()) 74 | // }) 75 | 76 | } 77 | }; 78 | }); 79 | -------------------------------------------------------------------------------- /app/styles/_bootstrap.scss: -------------------------------------------------------------------------------- 1 | 2 | // Core variables and mixins 3 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/variables"; 4 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/mixins"; 5 | 6 | @import "_bootstrap-mixins"; 7 | 8 | // Reset 9 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/normalize"; 10 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/print"; 11 | 12 | // Core CSS 13 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/scaffolding"; 14 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/type"; 15 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/code"; 16 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/grid"; 17 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/tables"; 18 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/forms"; 19 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/buttons"; 20 | 21 | // Components 22 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/component-animations"; 23 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/glyphicons"; 24 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/dropdowns"; 25 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/button-groups"; 26 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/input-groups"; 27 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/navs"; 28 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/navbar"; 29 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/breadcrumbs"; 30 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/pagination"; 31 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/pager"; 32 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/labels"; 33 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/badges"; 34 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/jumbotron"; 35 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/thumbnails"; 36 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/alerts"; 37 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/progress-bars"; 38 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/media"; 39 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/list-group"; 40 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/panels"; 41 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/wells"; 42 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/close"; 43 | 44 | // Components w/ JavaScript 45 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/modals"; 46 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/tooltip"; 47 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/popovers"; 48 | // @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/carousel"; 49 | 50 | // Utility classes 51 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/utilities"; 52 | @import "bootstrap-sass-official/vendor/assets/stylesheets/bootstrap/responsive-utilities"; 53 | -------------------------------------------------------------------------------- /app/scripts/vendor/LensBlurDepthExtractor.js: -------------------------------------------------------------------------------- 1 | /** 2 | MIT licensed 3 | https://github.com/spite/android-lens-blur-depth-extractor 4 | Copyright (C) 2014 Jaume Sanchez Elias http://twitter.com/thespite 5 | */ 6 | (function() { 7 | 8 | 'use strict'; 9 | 10 | var DepthReader = function() { 11 | 12 | this.focus = { 13 | blurAtInfinity: 0, 14 | focalDistance: 0, 15 | focalPoint: 0, 16 | focalPointX: 0, 17 | focalPointY: 0 18 | } 19 | this.image = { 20 | mime: '', 21 | data: null 22 | } 23 | this.depth = { 24 | format: '', 25 | near: 0, 26 | far: 0, 27 | mime: '', 28 | data: null 29 | } 30 | 31 | } 32 | 33 | function memcpy( dst, dstOffset, src, srcOffset, length ) { 34 | 35 | var dstU8 = new Uint8Array( dst, dstOffset, length ); 36 | var srcU8 = new Uint8Array( src, srcOffset, length ); 37 | dstU8.set( srcU8 ); 38 | 39 | }; 40 | 41 | function ab2str( buf ) { 42 | 43 | return String.fromCharCode.apply( null, new Uint8Array( buf ) ); 44 | 45 | }; 46 | 47 | function matchAttribute( id, str ) { 48 | 49 | var re = new RegExp( id + '="([\\S]*)"', 'gmi' ); 50 | var m = re.exec( str ); 51 | if( m ) return m[ 1 ]; 52 | return null; 53 | 54 | }; 55 | 56 | DepthReader.prototype.parseFile = function( arrayBuffer, onSuccess, onError ) { 57 | 58 | var byteArray = new Uint8Array( arrayBuffer ), 59 | str = ''; 60 | 61 | if( byteArray[ 0 ] === 0xff && byteArray[ 1 ] === 0xd8 ) { 62 | 63 | var boundaries = []; 64 | for (var i = 0; i < byteArray.byteLength; i++) { 65 | if( byteArray[ i ] === 0xff && byteArray[ i + 1 ] === 0xe1 ) { 66 | boundaries.push( i ); 67 | i++; 68 | } 69 | } 70 | boundaries.push( byteArray.byteLength ); 71 | 72 | for( var j = 0; j < boundaries.length - 1; j++ ) { 73 | 74 | if( byteArray[ boundaries[ j ] ] == 0xff && byteArray[ boundaries[ j ] + 1 ] == 0xe1 ) { 75 | 76 | var length = byteArray[ boundaries[ j ] + 2 ] * 256 + byteArray[ boundaries[ j ] + 3 ]; 77 | 78 | var offset = 79; 79 | if( offset > length ) offset = 0; 80 | length += 2; 81 | 82 | var tmp = new ArrayBuffer( length - offset ); 83 | memcpy( tmp, 0, arrayBuffer, boundaries[ j ] + offset, length - offset); 84 | var tmpStr = ab2str( tmp ); 85 | str += tmpStr; 86 | 87 | } 88 | 89 | } 90 | 91 | this.focus.blurAtInfinity = matchAttribute( 'GFocus:BlurAtInfinity', str ); 92 | this.focus.focalDistance = matchAttribute( 'GFocus:focalDistance', str ); 93 | this.focus.focalPoint = matchAttribute( 'GFocus:focalPoint', str ); 94 | this.focus.focalPointX = matchAttribute( 'GFocus:focalPointX', str ); 95 | this.focus.focalPointY = matchAttribute( 'GFocus:focalPointY', str ); 96 | 97 | this.image.mime = matchAttribute( 'GImage:Mime', str ); 98 | this.image.data = matchAttribute( 'GImage:Data', str ); 99 | 100 | this.depth.format = matchAttribute( 'GDepth:Format', str ); 101 | this.depth.near = matchAttribute( 'GDepth:Near', str ); 102 | this.depth.far = matchAttribute( 'GDepth:Far', str ); 103 | this.depth.mime = matchAttribute( 'GDepth:Mime', str ); 104 | this.depth.data = matchAttribute( 'GDepth:Data', str ); 105 | 106 | if( this.depth.data === null ) { 107 | if( onError ) onError( 'No depth data found' ); 108 | return; 109 | } 110 | 111 | if( onSuccess ) onSuccess(); 112 | 113 | } else { 114 | if( onError ) onError( 'File is not a JPEG' ); 115 | } 116 | 117 | }; 118 | 119 | DepthReader.prototype.loadFile = function( file, onSuccess, onError ) { 120 | 121 | var xhr = new XMLHttpRequest(); 122 | xhr.open( 'get', file ); 123 | xhr.responseType = 'arraybuffer'; 124 | var self = this; 125 | xhr.onload = function() { 126 | self.parseFile( this.response, onSuccess, onError ); 127 | }; 128 | xhr.send( null ); 129 | 130 | }; 131 | 132 | window.DepthReader = DepthReader; 133 | 134 | } )(); -------------------------------------------------------------------------------- /app/styles/_icomoon.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icomoon'; 3 | src:url('fonts/icomoon.eot'); 4 | src:url('fonts/icomoon.eot?#iefixb85li4') format('embedded-opentype'), 5 | url('fonts/icomoon.woff') format('woff'), 6 | url('fonts/icomoon.ttf') format('truetype'), 7 | url('fonts/icomoon.svg#icomoon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | .icon { 13 | font-family: 'icomoon'; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | 21 | /* Enable Ligatures ================ */ 22 | -webkit-font-feature-settings: "liga"; 23 | -moz-font-feature-settings: "liga=1"; 24 | -moz-font-feature-settings: "liga"; 25 | -ms-font-feature-settings: "liga" 1; 26 | -o-font-feature-settings: "liga"; 27 | font-feature-settings: "liga"; 28 | 29 | /* Better Font Rendering =========== */ 30 | -webkit-font-smoothing: antialiased; 31 | -moz-osx-font-smoothing: grayscale; 32 | } 33 | 34 | .icon-arrow-sm-left:before { 35 | content: "\e60d"; 36 | } 37 | .icon-arrow-sm-right:before { 38 | content: "\e620"; 39 | } 40 | .icon-warning:before { 41 | content: "\e609"; 42 | } 43 | .icon-navicon-bold:before { 44 | content: "\e621"; 45 | } 46 | .icon-chevron-up:before { 47 | content: "\e61a"; 48 | } 49 | .icon-chevron-right:before { 50 | content: "\e61b"; 51 | } 52 | .icon-chevron-left:before { 53 | content: "\e61c"; 54 | } 55 | .icon-back:before { 56 | content: "\e61c"; 57 | } 58 | .icon-chevron-down:before { 59 | content: "\e61d"; 60 | } 61 | .icon-share:before { 62 | content: "\e60b"; 63 | } 64 | .icon-droplet:before { 65 | content: "\e62e"; 66 | } 67 | .icon-image:before { 68 | content: "\e611"; 69 | } 70 | .icon-stack:before { 71 | content: "\e612"; 72 | } 73 | .icon-open:before { 74 | content: "\e613"; 75 | } 76 | .icon-history:before { 77 | content: "\e623"; 78 | } 79 | .icon-stopwatch:before { 80 | content: "\e628"; 81 | } 82 | .icon-download:before { 83 | content: "\e614"; 84 | } 85 | .icon-upload:before { 86 | content: "\e615"; 87 | } 88 | .icon-disk:before { 89 | content: "\e62f"; 90 | } 91 | .icon-undo:before { 92 | content: "\e61e"; 93 | } 94 | .icon-redo:before { 95 | content: "\e61f"; 96 | } 97 | .icon-expand:before { 98 | content: "\e616"; 99 | } 100 | .icon-contract:before { 101 | content: "\e617"; 102 | } 103 | .icon-settings:before { 104 | content: "\e618"; 105 | } 106 | .icon-trophy:before { 107 | content: "\e60a"; 108 | } 109 | .icon-meter:before { 110 | content: "\e607"; 111 | } 112 | .icon-magnet:before { 113 | content: "\e630"; 114 | } 115 | .icon-remove:before { 116 | content: "\e619"; 117 | } 118 | .icon-target:before { 119 | content: "\e631"; 120 | } 121 | .icon-powercord:before { 122 | content: "\e626"; 123 | } 124 | .icon-cloud:before { 125 | content: "\e627"; 126 | } 127 | .icon-link:before { 128 | content: "\e62a"; 129 | } 130 | .icon-eye:before { 131 | content: "\e624"; 132 | } 133 | .icon-heart:before { 134 | content: "\e629"; 135 | } 136 | .icon-info:before { 137 | content: "\e625"; 138 | } 139 | .icon-error:before { 140 | content: "\e62b"; 141 | } 142 | .icon-loop:before { 143 | content: "\e60c"; 144 | } 145 | .icon-googleplus:before { 146 | content: "\e60e"; 147 | } 148 | .icon-facebook:before { 149 | content: "\e60f"; 150 | } 151 | .icon-twitter:before { 152 | content: "\e610"; 153 | } 154 | .icon-github:before { 155 | content: "\e622"; 156 | } 157 | .icon-reddit:before { 158 | content: "\e62c"; 159 | } 160 | .icon-pinterest:before { 161 | content: "\e62d"; 162 | } 163 | .icon-yes:before { 164 | content: "\e600"; 165 | } 166 | .icon-no:before { 167 | content: "\e601"; 168 | } 169 | .icon-setup:before { 170 | content: "\e602"; 171 | } 172 | .icon-movie:before { 173 | content: "\e605"; 174 | } 175 | .icon-loop-circle:before { 176 | content: "\e603"; 177 | } 178 | .icon-loop-ellipse:before { 179 | content: "\e604"; 180 | } 181 | .icon-loop-flat:before { 182 | content: "\e606"; 183 | } 184 | .icon-draw:before { 185 | content: "\e608"; 186 | } 187 | -------------------------------------------------------------------------------- /app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /app/styles/main.scss: -------------------------------------------------------------------------------- 1 | $icon-font-path: "/bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/"; 2 | 3 | // http://colorschemedesigner.com/#00428sOsOFfFf 4 | 5 | $brand-primary: #007DA1; 6 | $brand-success: #0A95BB; 7 | $brand-danger: #AA3C2F; 8 | $brand-warning: #C66025; 9 | $brand-info: #666; 10 | $brand-neutral: #666; 11 | 12 | 13 | $brand-primary: #167D2E; 14 | $brand-success: #17A139; 15 | $brand-danger: #D4402A; 16 | $brand-warning: #D98915; 17 | $brand-info: #666; 18 | $brand-neutral: #666; 19 | 20 | $contrast: 10%; 21 | 22 | $brand-primary-dark: darken($brand-primary, $contrast); //#0165BD; 23 | $brand-danger-dark: darken($brand-danger, $contrast); 24 | $brand-success-dark: darken($brand-success, $contrast); 25 | $brand-warning-dark: darken($brand-warning, $contrast); 26 | $brand-info-dark: darken($brand-info, $contrast); 27 | $brand-neutral-dark: #222; 28 | 29 | $brand-neutral-vdark: #111; 30 | 31 | $brand-neutral-bright: #888; 32 | 33 | 34 | $body-bg: #000; 35 | $text-color: #eee; 36 | $headings-color: $brand-primary; 37 | $text-color-dark: #444; 38 | $text-muted: $brand-neutral; 39 | 40 | $font-family-sans-serif: 'Roboto Condensed', Helvetica, Arial, sans-serif; 41 | 42 | $headings-font-family: $font-family-sans-serif; 43 | $headings-font-weight: 700; 44 | 45 | $padding-base-vertical: 10px; 46 | $padding-base-horizontal: 15px; 47 | $padding-large-vertical: 15px; 48 | $padding-large-horizontal: 30px; 49 | 50 | 51 | $border-color: $brand-neutral-dark; 52 | $border-width: 4px; 53 | $border-radius-base: 0px; 54 | $border-radius-large: 0px; 55 | $border-radius-small: 0px; 56 | 57 | // states 58 | $state-danger-bg: $brand-danger; 59 | $state-danger-text: $text-color; 60 | $state-danger-border: $brand-danger-dark; 61 | $state-warning-bg: $brand-warning; 62 | $state-warning-text: $text-color; 63 | $state-warning-border: $brand-warning-dark; 64 | $state-info-bg: $brand-info; 65 | $state-info-text: $text-color; 66 | $state-info-border: $brand-info-dark; 67 | $state-success-bg: $brand-success; 68 | $state-success-text: $text-color; 69 | $state-success-border: $brand-success-dark; 70 | 71 | 72 | // wells 73 | $well-bg: rgba(0,0,0,0.2); 74 | 75 | // buttons 76 | $btn-default-color: $text-color; 77 | $btn-default-bg: $body-bg; 78 | $btn-default-border: lighten($body-bg, 22%); 79 | 80 | // popovers 81 | $popover-bg: $body-bg; 82 | $popover-arrow-color: $body-bg; 83 | $popover-arrow-width: 6px; 84 | $popover-max-width: 400px; 85 | 86 | // modals 87 | $modal-content-bg: $body-bg; 88 | $modal-header-border-color: lighten($body-bg, 10%); 89 | 90 | // panels 91 | $panel-bg: $body-bg; 92 | $panel-header-bg: $brand-primary; 93 | $panel-header-border: darken($panel-header-bg, $contrast); 94 | $panel-header-color: $text-color; 95 | $panel-footer-bg: $brand-neutral-dark; 96 | $panel-footer-border: $brand-neutral-vdark; 97 | $panel-footer-color: $text-color; 98 | $panel-default-text: $text-color; 99 | 100 | // code 101 | $code-color: inherit; 102 | $code-bg: fade-out($text-color, 0.8); 103 | 104 | // thumbnails 105 | $thumbnail-border: $border-color; 106 | $thumbnail-bg: $body-bg; 107 | $thumbnail-matte: $brand-neutral-dark; 108 | $thumbnail-padding: 1px; 109 | 110 | @import "_bootstrap.scss"; 111 | @import "_icomoon.scss"; 112 | 113 | $navbar-height: 40px; 114 | $navbar-bg: $body-bg; 115 | 116 | $leftpane-width: 375px; 117 | $leftpane-bg: $brand-neutral-vdark; 118 | 119 | $about-bg: transparent; //$brand-primary-dark; 120 | 121 | $gallery-width: 70px; 122 | $gallery-bg: darken($leftpane-bg, 15%); 123 | 124 | $draw-sm-size: 640px; 125 | $draw-lg-size: 980px; 126 | $draw-pane-width: 320px; 127 | $draw-pane-height: 50px; 128 | 129 | $facebook-color: desaturate(darken(#3B5998, $contrast), $contrast); 130 | $google-color: desaturate(darken(#D74635, $contrast), $contrast); 131 | $twitter-color: desaturate(darken(#00C3F8, $contrast), $contrast); 132 | 133 | 134 | $media-tiny: "(max-width: #{$screen-xs-max})"; 135 | $media-small: "(min-width: #{$screen-sm-min}) and (max-width: #{$screen-sm-max})"; 136 | $media-medium: "(min-width: #{$screen-md-min}) and (max-width: #{$screen-md-max})"; 137 | $media-large: "(min-width: #{$screen-lg-min})"; 138 | 139 | $media-phone: $media-tiny; 140 | $media-tablet: $media-small; 141 | $media-mobile: "(max-width: #{$screen-xs-max})"; 142 | $media-desktop: "(min-width: #{$screen-desktop})"; 143 | 144 | $media-full: "(min-width: #{$screen-lg-min})"; 145 | 146 | 147 | @import "_mixins.scss"; 148 | @import "_common.scss"; 149 | @import "_animations.scss"; 150 | @import "_elements.scss"; 151 | @import "_rwd.scss"; 152 | @import "_poor.scss"; 153 | -------------------------------------------------------------------------------- /app/scripts/controllers/sharepngmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .controller('SharePngModalCtrl', function ($scope, $sce, $timeout, $modalInstance, $state, $q, ga, depthy) { 5 | var uploadPromise; 6 | 7 | $scope.image = depthy.opened; 8 | $scope.shareImage = depthy.opened; 9 | 10 | 11 | function upload(imageDataUri) { 12 | ga('send', 'event', 'png', 'upload', '', imageDataUri.length); 13 | uploadPromise = $.ajax({ 14 | url: 'https://api.imgur.com/3/image.json', 15 | method: 'POST', 16 | headers: { 17 | Authorization: 'Client-ID ' + depthy.imgurId, 18 | Accept: 'application/json' 19 | }, 20 | data: { 21 | image: imageDataUri.substr('data:image/png;base64,'.length), 22 | type: 'base64', 23 | name: $scope.image.title, 24 | title: $scope.image.title + ' #depthy', 25 | description: 'View this image in 3D on http://depthy.me' 26 | }, 27 | xhr: function() { 28 | var xhr = new window.XMLHttpRequest(); 29 | //Upload progress 30 | xhr.upload.addEventListener('progress', function(evt){ 31 | if (evt.lengthComputable) { 32 | $scope.uploadProgress = evt.loaded / evt.total; 33 | $scope.$safeApply(); 34 | } 35 | }, false); 36 | return xhr; 37 | }, 38 | }).done(function(response, status, xhr) { 39 | console.log(response, status, xhr); 40 | var id = response.data.id, 41 | deleteHash = response.data.deletehash; 42 | 43 | if (response.data.type === 'image/png') { 44 | ga('send', 'event', 'png', 'upload-success'); 45 | $scope.shareImage = $scope.image.createShareImage({ 46 | url: 'https://i.imgur.com/' + id, 47 | state: 'imgur', 48 | stateParams: {id: id}, 49 | thumb: 'https://i.imgur.com/' + id + 's.jpg', 50 | store: depthy.stores.imgur, 51 | storeUrl: 'https://imgur.com/' + id, 52 | storeKey: deleteHash 53 | }); 54 | 55 | $scope.share = $scope.image.getShareInfo(); 56 | 57 | uploadPromise = null; 58 | 59 | // update description 60 | $.ajax({ 61 | url: 'https://api.imgur.com/3/image/' + deleteHash, 62 | method: 'POST', 63 | headers: { 64 | Authorization: 'Client-ID ' + depthy.imgurId, 65 | Accept: 'application/json' 66 | }, 67 | data: { 68 | description: 'View this image in 3D on ' + $scope.share.url, 69 | } 70 | }); 71 | } else { 72 | $scope.uploadError = 'This file is too big to upload it to imgur... Sorry :('; 73 | 74 | ga('send', 'event', 'png', 'upload-converted'); 75 | $.ajax({ 76 | url: 'https://api.imgur.com/3/image/' + deleteHash, 77 | method: 'DELETE', 78 | headers: { 79 | Authorization: 'Client-ID ' + depthy.imgurId, 80 | Accept: 'application/json' 81 | }, 82 | }); 83 | } 84 | 85 | $scope.$safeApply(); 86 | }).fail(function(xhr, status) { 87 | var response = xhr.responseJSON || {}; 88 | 89 | uploadPromise = null; 90 | $scope.uploadError = (response.data || {}).error || 'Something went wrong... Please try again.'; 91 | console.error('Share failed with ', response); 92 | ga('send', 'event', 'png', 'upload-error', status + ': ' + $scope.uploadError); 93 | $scope.$safeApply(); 94 | }); 95 | 96 | } 97 | 98 | 99 | function generateAndUpload(size, ratio, sizeLimit) { 100 | size = Math.round(size); 101 | console.group('Trying PNG size ' + size); 102 | depthy.getViewer().exportToPNG({width: size, height: size}).then( 103 | function(dataUrl) { 104 | console.log('PNG size: ', dataUrl.length); 105 | console.groupEnd(); 106 | if (dataUrl.length > sizeLimit) { 107 | if (size > 500) { 108 | generateAndUpload(size * ratio, ratio, sizeLimit); 109 | } else { 110 | $scope.uploadError = 'This file is too big to upload it to imgur... Sorry :('; 111 | } 112 | } else { 113 | upload(dataUrl); 114 | } 115 | } 116 | ); 117 | } 118 | 119 | $scope.share = $scope.image.getShareInfo(); 120 | if (!$scope.share) { 121 | // wait for DOM 122 | $timeout(function() { 123 | generateAndUpload(850, 0.8, 950000); 124 | }, depthy.modalWait); 125 | } 126 | 127 | 128 | 129 | $modalInstance.result.finally(function() { 130 | console.log('close'); 131 | if (uploadPromise) uploadPromise.abort(); 132 | }); 133 | 134 | 135 | }); 136 | -------------------------------------------------------------------------------- /app/views/export-gif-modal.html: -------------------------------------------------------------------------------- 1 | 7 | 79 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /app/scripts/controllers/exportgifmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .controller('ExportGifModalCtrl', function ($scope, $modalInstance, $rootElement, depthy, ga, $timeout) { 5 | $scope.exportProgress = -1; 6 | $scope.imageReady = false; 7 | $scope.shareUrl = ''; 8 | $scope.tweetUrl = null; 9 | $scope.imageOverLimit = false; 10 | 11 | $timeout(function() { 12 | var exportPromise = depthy.exportGifAnimation(), 13 | sharePromise = null, 14 | imageDataUri = null, 15 | exportStarted = new Date(), 16 | gaLabel = 'size ' + depthy.exportSize + ' dur ' + depthy.viewer.animDuration; 17 | 18 | ga('send', 'event', 'gif', 'start', gaLabel); 19 | 20 | exportPromise.then( 21 | function exportSuccess(blob) { 22 | ga('send', 'timing', 'gif', 'created', new Date() - exportStarted, gaLabel); 23 | ga('send', 'event', 'gif', 'created', gaLabel, blob.size); 24 | $scope.imageSize = blob.size; 25 | $scope.imageOverLimit = blob.size > 2097152; 26 | 27 | var imageReader = new FileReader(); 28 | imageReader.onload = function() { 29 | imageDataUri = imageReader.result; 30 | var url = URL.createObjectURL(blob); 31 | 32 | // this is way way waaay quicker if you set data uris directly...... 33 | angular.element('img[image-source="export-gif"]').attr('src', url); 34 | angular.element('a[image-source="export-gif"]').attr('href', url); 35 | 36 | // var img = $rootElement.find('.export-modal .export-image img')[0]; 37 | // if (Modernizr.android && Modernizr.chrome) { 38 | // // chrome on Android can save only data uris, it's opposite for others 39 | // img.src = imageDataUri; 40 | // } else { 41 | // img.src = ; 42 | // } 43 | $scope.imageReady = true; 44 | $scope.$safeApply(); 45 | }; 46 | imageReader.readAsDataURL(blob); 47 | }, 48 | function exportFailed() { 49 | $scope.exportError = 'Export failed'; 50 | }, 51 | function exportProgress(p) { 52 | $scope.exportProgress = p; 53 | $scope.$safeApply(); 54 | // console.log(p) 55 | } 56 | ); 57 | 58 | $scope.share = function() { 59 | ga('send', 'event', 'gif', 'upload', gaLabel, $scope.imageSize); 60 | $scope.shareUrl = 'sharing'; 61 | $scope.shareError = null; 62 | $scope.shareProgress = 0; 63 | sharePromise = $.ajax({ 64 | url: 'https://api.imgur.com/3/image.json', 65 | method: 'POST', 66 | headers: { 67 | Authorization: 'Client-ID ' + depthy.imgurId, 68 | Accept: 'application/json' 69 | }, 70 | data: { 71 | image: imageDataUri.substr('data:image/gif;base64,'.length), 72 | type: 'base64', 73 | name: depthy.opened.title, 74 | title: depthy.opened.title + ' #depthy', 75 | description: 'Created using http://depthy.me' 76 | }, 77 | xhr: function() { 78 | var xhr = new window.XMLHttpRequest(); 79 | //Upload progress 80 | xhr.upload.addEventListener('progress', function(evt){ 81 | if (evt.lengthComputable) { 82 | $scope.shareProgress = evt.loaded / evt.total; 83 | $scope.$safeApply(); 84 | } 85 | }, false); 86 | return xhr; 87 | }, 88 | }).done(function(response, status, xhr) { 89 | console.log(response, status, xhr); 90 | var id = response.data.id; 91 | 92 | ga('send', 'event', 'gif', 'upload-success'); 93 | $scope.shareUrl = 'https://imgur.com/' + id; 94 | $scope.share = { 95 | url: $scope.shareUrl, 96 | title: depthy.opened.title + ' #depthy', 97 | img: 'https://i.imgur.com/' + id + '.gif' 98 | }; 99 | sharePromise = null; 100 | $scope.$safeApply(); 101 | }).fail(function(xhr, status) { 102 | var response = xhr.responseJSON || {}; 103 | 104 | sharePromise = null; 105 | $scope.shareUrl = ''; 106 | $scope.shareError = (response.data || {}).error || 'Something went wrong... Please try again.'; 107 | console.error('Share failed with ', response); 108 | ga('send', 'event', 'gif', 'upload-error', status + ': ' + $scope.shareError); 109 | $scope.$safeApply(); 110 | }); 111 | 112 | }; 113 | 114 | $modalInstance.result.finally(function() { 115 | console.log('close'); 116 | if (exportPromise) exportPromise.abort(); 117 | if (sharePromise) sharePromise.abort(); 118 | if ($scope.imageUrl) URL.revokeObjectURL($scope.imageUrl); 119 | }); 120 | }, depthy.modalWait); 121 | 122 | }); 123 | -------------------------------------------------------------------------------- /app/scripts/pixi/DepthDisplacementFilter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * The DepthDisplacementFilter class uses the pixel values from the specified texture (called the displacement map) to perform a displacement of an object. 4 | * You can use this filter to apply all manor of crazy warping effects 5 | * Currently the r property of the texture is used offset the x and the g propery of the texture is used to offset the y. 6 | * @class DepthDisplacementFilter 7 | * @contructor 8 | * @param texture {Texture} The texture used for the displacemtent map * must be power of 2 texture at the moment 9 | */ 10 | 'use strict'; 11 | PIXI.DepthDisplacementFilter = function(texture) 12 | { 13 | PIXI.AbstractFilter.call( this ); 14 | 15 | this.passes = [this]; 16 | // texture.baseTexture._powerOf2 = true; 17 | 18 | // set the uniforms 19 | this.uniforms = { 20 | displacementMap: {type: 'sampler2D', value:texture}, 21 | scale: {type: '1f', value:0.015}, 22 | offset: {type: '2f', value:{x:0, y:0}}, 23 | mapDimensions: {type: '2f', value:{x:1, y:5112}}, 24 | dimensions: {type: '4fv', value:[0,0,0,0]}, 25 | focus: {type: '1f', value:0.5} 26 | }; 27 | 28 | if(texture.baseTexture.hasLoaded) 29 | { 30 | this.uniforms.mapDimensions.value.x = texture.width; 31 | this.uniforms.mapDimensions.value.y = texture.height; 32 | } 33 | else 34 | { 35 | this.boundLoadedFunction = this.onTextureLoaded.bind(this); 36 | 37 | texture.baseTexture.on('loaded', this.boundLoadedFunction); 38 | } 39 | 40 | this.fragmentSrc = [ 41 | 'precision mediump float;', 42 | 'varying vec2 vTextureCoord;', 43 | 'varying vec4 vColor;', 44 | 'uniform sampler2D displacementMap;', 45 | 'uniform sampler2D uSampler;', 46 | 'uniform float scale;', 47 | 'uniform vec2 offset;', 48 | 'uniform vec4 dimensions;', 49 | 'uniform vec2 mapDimensions;', 50 | 'uniform float focus;', 51 | 52 | 'void main(void) {', 53 | ' float aspect = dimensions.x / dimensions.y;', 54 | ' vec2 scale2 = vec2(scale * min(1.0, 1.0 / aspect), scale * min(1.0, aspect)) * vec2(1, -1) * vec2(1);', 55 | ' vec2 mapCords = vTextureCoord;', 56 | ' mapCords.y *= -1.0;', 57 | ' mapCords.y += 1.0;', 58 | ' float map = texture2D(displacementMap, mapCords).r;', 59 | ' map = map * -1.0 + focus;', 60 | ' vec2 disCords = vTextureCoord;', 61 | ' disCords += offset * map * scale2;', 62 | ' gl_FragColor = texture2D(uSampler, disCords) * vColor;', 63 | // ' gl_FragColor = vec4(1,1,1,0.5);', 64 | // ' gl_FragColor *= texture2D(displacementMap, mapCords);', 65 | '}' 66 | ]; 67 | }; 68 | 69 | PIXI.DepthDisplacementFilter.prototype = Object.create( PIXI.AbstractFilter.prototype ); 70 | PIXI.DepthDisplacementFilter.prototype.constructor = PIXI.DepthDisplacementFilter; 71 | 72 | PIXI.DepthDisplacementFilter.prototype.onTextureLoaded = function() 73 | { 74 | this.uniforms.mapDimensions.value.x = this.uniforms.displacementMap.value.width; 75 | this.uniforms.mapDimensions.value.y = this.uniforms.displacementMap.value.height; 76 | 77 | this.uniforms.displacementMap.value.baseTexture.off('loaded', this.boundLoadedFunction); 78 | }; 79 | 80 | /** 81 | * The texture used for the displacemtent map * must be power of 2 texture at the moment 82 | * 83 | * @property map 84 | * @type Texture 85 | */ 86 | Object.defineProperty(PIXI.DepthDisplacementFilter.prototype, 'map', { 87 | get: function() { 88 | return this.uniforms.displacementMap.value; 89 | }, 90 | set: function(value) { 91 | this.uniforms.displacementMap.value = value; 92 | } 93 | }); 94 | 95 | /** 96 | * The multiplier used to scale the displacement result from the map calculation. 97 | * 98 | * @property scale 99 | * @type Point 100 | */ 101 | Object.defineProperty(PIXI.DepthDisplacementFilter.prototype, 'scale', { 102 | get: function() { 103 | return this.uniforms.scale.value; 104 | }, 105 | set: function(value) { 106 | this.uniforms.scale.value = value; 107 | } 108 | }); 109 | 110 | /** 111 | * Focus point in paralax 112 | * 113 | * @property focus 114 | * @type float 115 | */ 116 | Object.defineProperty(PIXI.DepthDisplacementFilter.prototype, 'focus', { 117 | get: function() { 118 | return this.uniforms.focus.value; 119 | }, 120 | set: function(value) { 121 | this.uniforms.focus.value = Math.min(1,Math.max(0,value)); 122 | } 123 | }); 124 | 125 | /** 126 | * The offset used to move the displacement map. 127 | * 128 | * @property offset 129 | * @type Point 130 | */ 131 | Object.defineProperty(PIXI.DepthDisplacementFilter.prototype, 'offset', { 132 | get: function() { 133 | return this.uniforms.offset.value; 134 | }, 135 | set: function(value) { 136 | this.uniforms.offset.value = value; 137 | } 138 | }); -------------------------------------------------------------------------------- /app/scripts/directives/shareurls.js: -------------------------------------------------------------------------------- 1 | /** 2 | Angular ShareUrls 3 | 4 | Based on https://github.com/bradvin/social-share-urls 5 | 6 | Copyright 2014 Rafał Lindemann http://github.com/panrafal 7 | */ 8 | angular.module('shareUrls', []) 9 | .provider('ShareUrls', function () { 10 | 'use strict'; 11 | 12 | var provider = this, 13 | templates = { 14 | facebook: { 15 | url: 'http://www.facebook.com/sharer.php?s=100&p[url]={url}&p[images][0]={img}&p[title]={title}&p[summary]={desc}' 16 | }, 17 | 'facebook-feed': { 18 | url: 'https://www.facebook.com/dialog/feed?app_id={app_id}&link={url}&picture={img}&name={title}&description={desc}&redirect_uri={redirect_url}' 19 | }, 20 | 'facebook-likebox': { 21 | // &width=50&height=80 22 | url: '//www.facebook.com/plugins/like.php?href={url}&colorscheme={scheme}&layout={layout}&action={action}&show_faces=false&send=false&appId=&locale={locale}' 23 | }, 24 | twitter: { 25 | url: 'https://twitter.com/share?url={url}&text={title}&via={via}&hashtags={hashtags}' 26 | }, 27 | 'twitter-follow': { 28 | url: 'https://twitter.com/intent/user?screen_name={name}' 29 | }, 30 | google: { 31 | url: 'https://plus.google.com/share?url={url}', 32 | }, 33 | pinterest: { 34 | url: 'https://pinterest.com/pin/create/bookmarklet/?media={img}&url={url}&is_video={is_video}&description={title}', 35 | }, 36 | linkedin: { 37 | url: 'http://www.linkedin.com/shareArticle?url={url}&title={title}', 38 | }, 39 | buffer: { 40 | url: 'http://bufferapp.com/add?text={title}&url={url}', 41 | }, 42 | digg: { 43 | url: 'http://digg.com/submit?url={url}&title={title}', 44 | }, 45 | tumblr: { 46 | url: 'http://www.tumblr.com/share/link?url={url}&name={title}&description={desc}', 47 | }, 48 | reddit: { 49 | url: 'http://reddit.com/submit?url={url}&title={title}', 50 | }, 51 | stumbleupon: { 52 | url: 'http://www.stumbleupon.com/submit?url={url}&title={title}', 53 | }, 54 | delicious: { 55 | url: 'https://delicious.com/save?v=5&provider={provider}&noui&jump=close&url={url}&title={title}', 56 | }, 57 | }; 58 | 59 | this.defaults = { 60 | popupWidth: 600, 61 | popupHeight: 300, 62 | }; 63 | 64 | 65 | function generateUrl(url, opt) { 66 | var prop, arg, argNe; 67 | for (prop in opt) { 68 | arg = '{' + prop + '}'; 69 | if (url.indexOf(arg) !== -1) { 70 | url = url.replace(new RegExp(arg, 'g'), encodeURIComponent(opt[prop])); 71 | } 72 | argNe = '{' + prop + '-ne}'; 73 | if (url.indexOf(argNe) !== -1) { 74 | url = url.replace(new RegExp(argNe, 'g'), opt[prop]); 75 | } 76 | } 77 | return cleanUrl(url); 78 | } 79 | 80 | function cleanUrl(fullUrl) { 81 | //firstly, remove any expressions we may have left in the url 82 | fullUrl = fullUrl.replace(/\{([^{}]*)}/g, ''); 83 | 84 | //then remove any empty parameters left in the url 85 | var params = fullUrl.match(/[^\=\&\?]+=[^\=\&\?]+/g), 86 | url = fullUrl.split('?')[0]; 87 | if (params && params.length > 0) { 88 | url += '?' + params.join('&'); 89 | } 90 | 91 | return url; 92 | } 93 | 94 | 95 | this.$get = function($window, $location) { 96 | var ShareUrls = { 97 | defaults: angular.extend({url: $location.absUrl()}, provider.defaults), 98 | 99 | getUrl: function(type, opts) { 100 | var template = templates[type]; 101 | if (!template) throw 'Unknown template ' + type; 102 | opts = angular.extend({}, template.defaults || {}, this.defaults || {}, opts || {}); 103 | 104 | return generateUrl(template.url, opts); 105 | }, 106 | 107 | openPopup: function(type, opts) { 108 | var template = templates[type]; 109 | if (!template) throw 'Unknown template ' + type; 110 | opts = angular.extend({}, template.defaults || {}, this.defaults || {}, opts || {}); 111 | 112 | 113 | var width = opts.popupWidth || 800, 114 | height = opts.popupHeight || 500, 115 | px = Math.floor(((screen.availWidth || 1024) - width) / 2), 116 | py = Math.floor(((screen.availHeight || 700) - height) / 2), 117 | url = generateUrl(template.url, opts); 118 | 119 | // open popup 120 | var popup = $window.open(url, 'social', 121 | 'width=' + width + ',height=' + height + 122 | ',left=' + px + ',top=' + py + 123 | ',location=0,menubar=0,toolbar=0,status=0,scrollbars=1,resizable=1'); 124 | 125 | if (popup) { 126 | popup.focus(); 127 | } 128 | 129 | return !!popup; 130 | } 131 | 132 | }; 133 | 134 | return ShareUrls; 135 | }; 136 | }) 137 | .directive('sharePopup', function (ShareUrls) { 138 | 'use strict'; 139 | return { 140 | restrict: 'A', 141 | link: function postLink($scope, $element, $attrs) { 142 | 143 | $element.on('click', function(e) { 144 | ShareUrls.openPopup($attrs.sharePopup, $attrs.shareOptions ? $scope.$eval($attrs.shareOptions) : {}); 145 | e.preventDefault(); 146 | }); 147 | 148 | } 149 | }; 150 | }); 151 | 152 | -------------------------------------------------------------------------------- /test/spec/classes/GDepthEncoder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('GDepthEncoder', function () { 4 | 5 | var xmpDataURI = 'data:image/jpeg;base64,/9j/4QAqRXhpZgAASUkqAAgAAAABAJiCAgAFAAAAGgAAAAAAAABUZXN0AAAAAP/sABFEdWNreQABAAQAAABLAAD/4QSkaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjUtYzAyMSA3OS4xNTQ5MTEsIDIwMTMvMTAvMjktMTE6NDc6MTYgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcFJpZ2h0cz0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3JpZ2h0cy8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtcFJpZ2h0czpNYXJrZWQ9IlRydWUiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo2ZWJlYzg1YS1kODBmLWMxNGYtYWY3ZC1hZGMzN2M0ZTQwYTciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODc3NjlBMzNGNThFMTFFM0EwNzhCNTg4QTI3MThFNzkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODc3NjlBMzJGNThFMTFFM0EwNzhCNTg4QTI3MThFNzkiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZlYmVjODVhLWQ4MGYtYzE0Zi1hZjdkLWFkYzM3YzRlNDBhNyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2ZWJlYzg1YS1kODBmLWMxNGYtYWY3ZC1hZGMzN2M0ZTQwYTciLz4gPGRjOnJpZ2h0cz4gPHJkZjpBbHQ+IDxyZGY6bGkgeG1sOmxhbmc9IngtZGVmYXVsdCI+VGVzdDwvcmRmOmxpPiA8L3JkZjpBbHQ+IDwvZGM6cmlnaHRzPiA8ZGM6dGl0bGU+IDxyZGY6QWx0PiA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPlRlc3Q8L3JkZjpsaT4gPC9yZGY6QWx0PiA8L2RjOnRpdGxlPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/tAFBQaG90b3Nob3AgMy4wADhCSU0EBAAAAAAAGBwBWgADGyVHHAIAAAIAAhwCdAAEVGVzdDhCSU0EJQAAAAAAEN5W4ZqfXiPJ8CAePrIPGv//7gAOQWRvYmUAZMAAAAAB/9sAhAADAgICAgIDAgIDBQMDAwUFBAMDBAUGBQUFBQUGCAYHBwcHBggICQoKCgkIDAwMDAwMDg4ODg4QEBAQEBAQEBAQAQMEBAYGBgwICAwSDgwOEhQQEBAQFBEQEBAQEBEREBAQEBAQERAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAgACADAREAAhEBAxEB/8QASwABAQAAAAAAAAAAAAAAAAAAAAkBAQAAAAAAAAAAAAAAAAAAAAAQAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AKpgAAAAAAAAAAAAAAA//9k='; 6 | var simpleDataURI = 'data:image/jpeg;base64,/9j/4QAqRXhpZgAASUkqAAgAAAABAJiCAgAFAAAAGgAAAAAAAABUZXN0AAAAAP/sABFEdWNreQABAAQAAABLAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNBCUAAAAAABAAAAAAAAAAAAAAAAAAAAAA/+4ADkFkb2JlAGTAAAAAAf/bAIQAAwICAgICAwICAwUDAwMFBQQDAwQFBgUFBQUFBggGBwcHBwYICAkKCgoJCAwMDAwMDA4ODg4OEBAQEBAQEBAQEAEDBAQGBgYMCAgMEg4MDhIUEBAQEBQREBAQEBARERAQEBAQEBEQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ/8AAEQgAIAAgAwERAAIRAQMRAf/EAEsAAQEAAAAAAAAAAAAAAAAAAAAJAQEAAAAAAAAAAAAAAAAAAAAAEAEAAAAAAAAAAAAAAAAAAAAAEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCVQAAAAAAAAAAAAAAAP//Z'; 7 | 8 | var longDataURI = 'data:image/jpeg;base64,'; 9 | for (var i = 0; i < 1600; ++i) { 10 | longDataURI += 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; 11 | } 12 | 13 | it('should build XMP', function () { 14 | var xmp = GDepthEncoder.buildXMP({test: 'TEST!'}); 15 | expect(xmp).toMatch(/^$/); 16 | }); 17 | 18 | it('should understand dataURI', function() { 19 | var info = GDepthEncoder.dataURIinfo(xmpDataURI); 20 | expect(info.mime).toBe('image/jpeg'); 21 | expect(info.data).not.toBe(null); 22 | expect(info.data.length).toBeGreaterThan(500); 23 | }); 24 | 25 | it('should accept only ArrayBuffer', function () { 26 | expect(function() { GDepthEncoder.encodeDepthmap([1,2,3]); }).toThrow(); 27 | }); 28 | 29 | it('should encode image', function () { 30 | var result = GDepthEncoder.encodeDepthmap(dataURItoArrayBuffer(xmpDataURI).buffer, longDataURI, simpleDataURI); 31 | 32 | expect(result.type).toBe('image/jpeg'); 33 | expect(result.size).toBeGreaterThan(1500); 34 | 35 | var dr = null, parsed = false, loaded = false; 36 | runs(function() { 37 | var fr = new FileReader(); 38 | fr.onload = function() { 39 | dr = new DepthReader(); 40 | dr.parseFile(fr.result, function() { 41 | // console.log(dr); 42 | parsed = true; 43 | }); 44 | }; 45 | fr.readAsArrayBuffer(result); 46 | }); 47 | 48 | waitsFor(function() { 49 | return parsed; 50 | }, 'Result should be parsed', 100); 51 | 52 | runs(function() { 53 | expect(dr.depth.mime).toBe('image/jpeg'); 54 | // expect(dr.depth.data).toBe(GDepthEncoder.dataURIinfo(longDataURI).data); 55 | expect(dr.depth.data.length).toEqual(longDataURI.length - 23); 56 | expect(dr.image.mime).toBe('image/jpeg'); 57 | expect(dr.image.data).toBe(GDepthEncoder.dataURIinfo(simpleDataURI).data); 58 | }); 59 | 60 | runs(function() { 61 | var img = document.createElement('img'); 62 | 63 | img.onload = function() { 64 | loaded = {w:img.width, h:img.height}; 65 | }; 66 | img.src = URL.createObjectURL(result); 67 | }); 68 | 69 | waitsFor(function() { 70 | return loaded; 71 | }, 'Result should be loadable', 100); 72 | 73 | runs(function() { 74 | expect(loaded).toEqual({w:32, h:32}); 75 | }); 76 | 77 | console.log('Encoded: %d / %s', result.size, result.type, result); 78 | // expect(xmp).toMatch(/^$/); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /app/scripts/vendor/md5.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | var CryptoJS=CryptoJS||function(s,p){var m={},l=m.lib={},n=function(){},r=l.Base={extend:function(b){n.prototype=this;var h=new n;b&&h.mixIn(b);h.hasOwnProperty("init")||(h.init=function(){h.$super.init.apply(this,arguments)});h.init.prototype=h;h.$super=this;return h},create:function(){var b=this.extend();b.init.apply(b,arguments);return b},init:function(){},mixIn:function(b){for(var h in b)b.hasOwnProperty(h)&&(this[h]=b[h]);b.hasOwnProperty("toString")&&(this.toString=b.toString)},clone:function(){return this.init.prototype.extend(this)}}, 8 | q=l.WordArray=r.extend({init:function(b,h){b=this.words=b||[];this.sigBytes=h!=p?h:4*b.length},toString:function(b){return(b||t).stringify(this)},concat:function(b){var h=this.words,a=b.words,j=this.sigBytes;b=b.sigBytes;this.clamp();if(j%4)for(var g=0;g>>2]|=(a[g>>>2]>>>24-8*(g%4)&255)<<24-8*((j+g)%4);else if(65535>>2]=a[g>>>2];else h.push.apply(h,a);this.sigBytes+=b;return this},clamp:function(){var b=this.words,h=this.sigBytes;b[h>>>2]&=4294967295<< 9 | 32-8*(h%4);b.length=s.ceil(h/4)},clone:function(){var b=r.clone.call(this);b.words=this.words.slice(0);return b},random:function(b){for(var h=[],a=0;a>>2]>>>24-8*(j%4)&255;g.push((k>>>4).toString(16));g.push((k&15).toString(16))}return g.join("")},parse:function(b){for(var a=b.length,g=[],j=0;j>>3]|=parseInt(b.substr(j, 10 | 2),16)<<24-4*(j%8);return new q.init(g,a/2)}},a=v.Latin1={stringify:function(b){var a=b.words;b=b.sigBytes;for(var g=[],j=0;j>>2]>>>24-8*(j%4)&255));return g.join("")},parse:function(b){for(var a=b.length,g=[],j=0;j>>2]|=(b.charCodeAt(j)&255)<<24-8*(j%4);return new q.init(g,a)}},u=v.Utf8={stringify:function(b){try{return decodeURIComponent(escape(a.stringify(b)))}catch(g){throw Error("Malformed UTF-8 data");}},parse:function(b){return a.parse(unescape(encodeURIComponent(b)))}}, 11 | g=l.BufferedBlockAlgorithm=r.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(b){"string"==typeof b&&(b=u.parse(b));this._data.concat(b);this._nDataBytes+=b.sigBytes},_process:function(b){var a=this._data,g=a.words,j=a.sigBytes,k=this.blockSize,m=j/(4*k),m=b?s.ceil(m):s.max((m|0)-this._minBufferSize,0);b=m*k;j=s.min(4*b,j);if(b){for(var l=0;l>>32-j)+k}function m(a,k,b,h,l,j,m){a=a+(k&h|b&~h)+l+m;return(a<>>32-j)+k}function l(a,k,b,h,l,j,m){a=a+(k^b^h)+l+m;return(a<>>32-j)+k}function n(a,k,b,h,l,j,m){a=a+(b^(k|~h))+l+m;return(a<>>32-j)+k}for(var r=CryptoJS,q=r.lib,v=q.WordArray,t=q.Hasher,q=r.algo,a=[],u=0;64>u;u++)a[u]=4294967296*s.abs(s.sin(u+1))|0;q=q.MD5=t.extend({_doReset:function(){this._hash=new v.init([1732584193,4023233417,2562383102,271733878])}, 15 | _doProcessBlock:function(g,k){for(var b=0;16>b;b++){var h=k+b,w=g[h];g[h]=(w<<8|w>>>24)&16711935|(w<<24|w>>>8)&4278255360}var b=this._hash.words,h=g[k+0],w=g[k+1],j=g[k+2],q=g[k+3],r=g[k+4],s=g[k+5],t=g[k+6],u=g[k+7],v=g[k+8],x=g[k+9],y=g[k+10],z=g[k+11],A=g[k+12],B=g[k+13],C=g[k+14],D=g[k+15],c=b[0],d=b[1],e=b[2],f=b[3],c=p(c,d,e,f,h,7,a[0]),f=p(f,c,d,e,w,12,a[1]),e=p(e,f,c,d,j,17,a[2]),d=p(d,e,f,c,q,22,a[3]),c=p(c,d,e,f,r,7,a[4]),f=p(f,c,d,e,s,12,a[5]),e=p(e,f,c,d,t,17,a[6]),d=p(d,e,f,c,u,22,a[7]), 16 | c=p(c,d,e,f,v,7,a[8]),f=p(f,c,d,e,x,12,a[9]),e=p(e,f,c,d,y,17,a[10]),d=p(d,e,f,c,z,22,a[11]),c=p(c,d,e,f,A,7,a[12]),f=p(f,c,d,e,B,12,a[13]),e=p(e,f,c,d,C,17,a[14]),d=p(d,e,f,c,D,22,a[15]),c=m(c,d,e,f,w,5,a[16]),f=m(f,c,d,e,t,9,a[17]),e=m(e,f,c,d,z,14,a[18]),d=m(d,e,f,c,h,20,a[19]),c=m(c,d,e,f,s,5,a[20]),f=m(f,c,d,e,y,9,a[21]),e=m(e,f,c,d,D,14,a[22]),d=m(d,e,f,c,r,20,a[23]),c=m(c,d,e,f,x,5,a[24]),f=m(f,c,d,e,C,9,a[25]),e=m(e,f,c,d,q,14,a[26]),d=m(d,e,f,c,v,20,a[27]),c=m(c,d,e,f,B,5,a[28]),f=m(f,c, 17 | d,e,j,9,a[29]),e=m(e,f,c,d,u,14,a[30]),d=m(d,e,f,c,A,20,a[31]),c=l(c,d,e,f,s,4,a[32]),f=l(f,c,d,e,v,11,a[33]),e=l(e,f,c,d,z,16,a[34]),d=l(d,e,f,c,C,23,a[35]),c=l(c,d,e,f,w,4,a[36]),f=l(f,c,d,e,r,11,a[37]),e=l(e,f,c,d,u,16,a[38]),d=l(d,e,f,c,y,23,a[39]),c=l(c,d,e,f,B,4,a[40]),f=l(f,c,d,e,h,11,a[41]),e=l(e,f,c,d,q,16,a[42]),d=l(d,e,f,c,t,23,a[43]),c=l(c,d,e,f,x,4,a[44]),f=l(f,c,d,e,A,11,a[45]),e=l(e,f,c,d,D,16,a[46]),d=l(d,e,f,c,j,23,a[47]),c=n(c,d,e,f,h,6,a[48]),f=n(f,c,d,e,u,10,a[49]),e=n(e,f,c,d, 18 | C,15,a[50]),d=n(d,e,f,c,s,21,a[51]),c=n(c,d,e,f,A,6,a[52]),f=n(f,c,d,e,q,10,a[53]),e=n(e,f,c,d,y,15,a[54]),d=n(d,e,f,c,w,21,a[55]),c=n(c,d,e,f,v,6,a[56]),f=n(f,c,d,e,D,10,a[57]),e=n(e,f,c,d,t,15,a[58]),d=n(d,e,f,c,B,21,a[59]),c=n(c,d,e,f,r,6,a[60]),f=n(f,c,d,e,z,10,a[61]),e=n(e,f,c,d,j,15,a[62]),d=n(d,e,f,c,x,21,a[63]);b[0]=b[0]+c|0;b[1]=b[1]+d|0;b[2]=b[2]+e|0;b[3]=b[3]+f|0},_doFinalize:function(){var a=this._data,k=a.words,b=8*this._nDataBytes,h=8*a.sigBytes;k[h>>>5]|=128<<24-h%32;var l=s.floor(b/ 19 | 4294967296);k[(h+64>>>9<<4)+15]=(l<<8|l>>>24)&16711935|(l<<24|l>>>8)&4278255360;k[(h+64>>>9<<4)+14]=(b<<8|b>>>24)&16711935|(b<<24|b>>>8)&4278255360;a.sigBytes=4*(k.length+1);this._process();a=this._hash;k=a.words;for(b=0;4>b;b++)h=k[b],k[b]=(h<<8|h>>>24)&16711935|(h<<24|h>>>8)&4278255360;return a},clone:function(){var a=t.clone.call(this);a._hash=this._hash.clone();return a}});r.MD5=t._createHelper(q);r.HmacMD5=t._createHmacHelper(q)})(Math); 20 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp', [ 4 | 'ngAnimate', 5 | 'ngTouch', 6 | 'ga', 7 | 'shareUrls', 8 | // 'visibleClass', 9 | // 'mgcrea.ngStrap.modal', 10 | // 'mgcrea.ngStrap.popover', 11 | // 'mgcrea.ngStrap.button' 12 | 'ui.router', 13 | 'ui.bootstrap.buttons', 14 | 'ui.bootstrap.modal', 15 | 'ui.bootstrap.transition', 16 | 'ui.bootstrap.dropdown', 17 | ]) 18 | //fix blob 19 | .config(function($compileProvider) { 20 | $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|blob):|data:image\//); 21 | $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); 22 | }) 23 | .config(function ($stateProvider, $urlRouterProvider, $locationProvider) { 24 | $locationProvider.html5Mode(false); 25 | // $locationProvider.hashPrefix('!'); 26 | 27 | $urlRouterProvider.otherwise('/'); 28 | 29 | $stateProvider 30 | .state('index', { 31 | url: '/', 32 | onEnter: ['depthy', '$state', function (depthy, $state) { 33 | if (!$state.current.name) { 34 | // first timer 35 | depthy.leftpaneOpen(); 36 | depthy.loadSampleImage('flowers'); 37 | } 38 | }] 39 | }) 40 | .state('sample', { 41 | url: '/sample/:id', 42 | controller: ['$stateParams', 'depthy', function ($stateParams, depthy) { 43 | depthy.loadSampleImage($stateParams.id); 44 | }] 45 | }) 46 | .state('imgur', { 47 | url: '/ip/:id', 48 | controller: ['$stateParams', '$state', 'depthy', function ($stateParams, $state, depthy) { 49 | depthy.loadUrlDirectImage('http://i.imgur.com/' + $stateParams.id + '.png', true, { 50 | state: 'imgur', 51 | stateParams: {id: $stateParams.id}, 52 | thumb: 'http://i.imgur.com/' + $stateParams.id + 's.jpg', 53 | storeUrl: 'http://imgur.com/' + $stateParams.id, 54 | store: depthy.stores.imgur 55 | }); 56 | }] 57 | }) 58 | .state('imgur2', { 59 | url: '/ii/:id', 60 | controller: ['$stateParams', 'depthy', function ($stateParams, depthy) { 61 | depthy.loadUrlImage('http://i.imgur.com/' + $stateParams.id + '.png', { 62 | state: 'imgur2', 63 | stateParams: {id: $stateParams.id}, 64 | thumb: 'http://i.imgur.com/' + $stateParams.id + 'm.jpg', 65 | storeUrl: 'http://imgur.com/' + $stateParams.id, 66 | store: depthy.stores.imgur 67 | }); 68 | }] 69 | }) 70 | .state('url-png', { 71 | url: '/up?url', 72 | controller: ['$stateParams', '$state', 'depthy', function ($stateParams, $state, depthy) { 73 | depthy.loadUrlDirectImage($stateParams.url, true, { 74 | state: 'url-png', 75 | stateParams: {url: $stateParams.url}, 76 | // thumb: $stateParams.url, 77 | }); 78 | }] 79 | }) 80 | .state('url-auto', { 81 | url: '/u?url', 82 | controller: ['$stateParams', '$state', 'depthy', function ($stateParams, $state, depthy) { 83 | depthy.loadUrlImage($stateParams.url, { 84 | state: 'url-auto', 85 | stateParams: {url: $stateParams.url}, 86 | // thumb: $stateParams.url, 87 | }); 88 | }] 89 | }) 90 | // hollow state for locally loaded files 91 | .state('local', { 92 | url: '/local/:id', 93 | hollow: true, 94 | controller: ['$stateParams', 'depthy', function($stateParams, depthy) { 95 | depthy.loadLocalImage($stateParams.id); 96 | }] 97 | }) 98 | // hollow states for back button on alerts 99 | .state('alert', { 100 | url: '/alert', 101 | }) 102 | .state('image', { 103 | url: '/image', 104 | }) 105 | .state('image.options', { 106 | url: '/options', 107 | }) 108 | .state('image.info', { 109 | url: '/info', 110 | }) 111 | .state('export', { 112 | url: '/export', 113 | }) 114 | .state('export.png', { 115 | url: '/png', 116 | }) 117 | .state('export.jpg', { 118 | url: '/jpg', 119 | }) 120 | .state('export.gif', { 121 | url: '/gif', 122 | }) 123 | .state('export.gif.options', { 124 | url: '/options', 125 | }) 126 | .state('export.gif.run', { 127 | url: '/run', 128 | }) 129 | .state('export.webm', { 130 | url: '/webm', 131 | }) 132 | .state('export.webm.options', { 133 | url: '/options', 134 | }) 135 | .state('export.webm.run', { 136 | url: '/run', 137 | }) 138 | .state('export.anaglyph', { 139 | url: '/anaglyph', 140 | }) 141 | .state('share', { 142 | url: '/share', 143 | }) 144 | .state('share.options', { 145 | url: '/options', 146 | }) 147 | .state('share.png', { 148 | url: '/png', 149 | }) 150 | .state('draw', { 151 | url: '/draw', 152 | hollow: true, 153 | onEnter: ['depthy', '$timeout', function(depthy, $timeout) { 154 | $timeout(function() { 155 | depthy.drawModeEnable(); 156 | }) 157 | }], 158 | onExit: ['depthy', function(depthy) { 159 | depthy.drawModeDisable(); 160 | }], 161 | }) 162 | .state('pane', { 163 | url: '/pane', 164 | hollow: true, 165 | onEnter: ['depthy', function(depthy) { 166 | depthy.leftpaneOpen(); 167 | }], 168 | // onExit: ['depthy', function(depthy) { 169 | // depthy.leftpaneClose(); 170 | // }] 171 | }) 172 | .state('howto', { 173 | url: '/howto', 174 | }) 175 | .state('howto.lensblur', { 176 | url: '/lensblur', 177 | onEnter: ['StateModal', function(StateModal) { 178 | StateModal.showModal('howto.lensblur', { 179 | stateCurrent: true, 180 | templateUrl: 'views/howto-lensblur.html', 181 | }); 182 | }] 183 | }) 184 | ; 185 | }) 186 | .run(function($rootScope, ga, $location, $state) { 187 | // check first state 188 | var stateChangeStart = $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState) { 189 | stateChangeStart(); 190 | console.log(event, toState, toParams, fromState); 191 | if (toState.hollow || !toState.controller && !toState.onEnter && !toState.template && !toState.templateUrl) { 192 | console.warn('Hollow state %s', toState.name); 193 | event.preventDefault(); 194 | $state.go('index'); 195 | } 196 | }); 197 | $rootScope.$on('$stateChangeSuccess', function() { 198 | ga('set', 'page', $location.url()); 199 | ga('send', 'pageview'); 200 | }); 201 | }); -------------------------------------------------------------------------------- /app/views/draw.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | Here you can draw the depthmap with your 6 | {{Modernizr.mobile ? 'finger' : 'mouse'}}. Use the sliders to 7 | setup the brush.
8 | Remember that black means near the screen, and white is far away.
9 | Use opacity to make subtle changes, and level to draw at the same level as the point you start. 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 |
27 |
34 |
depth
35 |
36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 | 44 |
45 |
52 |
size
53 |
54 | 55 | 56 | 57 |
58 |
59 |
60 | 61 |
62 |
69 |
hardness
70 |
71 | 72 | 73 | 74 | 75 |
76 |
77 |
78 | 79 |
80 |
87 |
opacity
88 |
89 | 90 | 91 | 92 |
93 |
94 |
95 |
96 |
97 | 98 |
99 | 100 | 101 | 138 | 139 |
-------------------------------------------------------------------------------- /app/scripts/controllers/draw.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .controller('DrawCtrl', function ($scope, $element, depthy, $window, $timeout) { 5 | 6 | var drawer = depthy.drawMode, 7 | viewer = depthy.getViewer(), 8 | lastPointerPos = null, 9 | oldViewerOpts = angular.extend({}, depthy.viewer); 10 | 11 | drawer.setOptions(depthy.drawOptions || { 12 | depth: 0.5, 13 | size: 0.05, 14 | hardness: 0.5, 15 | opacity: 0.25, 16 | }); 17 | 18 | angular.extend(depthy.viewer, { 19 | animate: false, 20 | fit: 'contain', 21 | upscale: 2, 22 | // depthPreview: 0.75, 23 | // orient: false, 24 | // hover: false, 25 | }); 26 | 27 | $scope.drawer = drawer; 28 | $scope.drawOpts = drawer.getOptions(); 29 | 30 | $scope.preview = 1; 31 | $scope.brushMode = false; 32 | 33 | $scope.$watch('drawOpts', function(options) { 34 | if (drawer && options) { 35 | drawer.setOptions(options); 36 | } 37 | }, true); 38 | 39 | $scope.$watch('preview', function(preview) { 40 | depthy.viewer.orient = preview === 2; 41 | depthy.viewer.hover = preview === 2; 42 | depthy.viewer.animate = preview === 2 && oldViewerOpts.animate; 43 | depthy.viewer.quality = preview === 2 ? false : 1; 44 | depthy.animateOption(depthy.viewer, { 45 | depthPreview: preview === 0 ? 1 : preview === 1 ? 0.75 : 0, 46 | depthScale: preview === 2 ? oldViewerOpts.depthScale : 0, 47 | depthBlurSize: preview === 2 ? oldViewerOpts.depthBlurSize : 0, 48 | enlarge: 1.0, 49 | }, 250); 50 | }); 51 | 52 | $scope.togglePreview = function() { 53 | $scope.preview = ++$scope.preview % 3; 54 | }; 55 | 56 | $scope.done = function() { 57 | $window.history.back(); 58 | }; 59 | 60 | $scope.cancel = function() { 61 | drawer.cancel(); 62 | $window.history.back(); 63 | }; 64 | 65 | $scope.brushIcon = function() { 66 | switch($scope.brushMode) { 67 | case 'picker': 68 | return 'target'; 69 | case 'level': 70 | return 'magnet'; 71 | default: 72 | return 'draw'; 73 | } 74 | }; 75 | 76 | 77 | $element.on('touchstart mousedown', function(e) { 78 | var event = e.originalEvent, 79 | pointerEvent = event.touches ? event.touches[0] : event; 80 | 81 | if (event.target.id !== 'draw') return; 82 | 83 | lastPointerPos = viewer.screenToImagePos({x: pointerEvent.pageX, y: pointerEvent.pageY}); 84 | 85 | if ($scope.brushMode === 'picker' || $scope.brushMode === 'level') { 86 | $scope.drawOpts.depth = drawer.getDepthAtPos(lastPointerPos); 87 | console.log('Picked %s', $scope.drawOpts.depth); 88 | if ($scope.brushMode === 'picker') { 89 | $scope.brushMode = false; 90 | lastPointerPos = null; 91 | $scope.$safeApply(); 92 | event.preventDefault(); 93 | event.stopPropagation(); 94 | return; 95 | } else { 96 | $scope.$safeApply(); 97 | } 98 | } 99 | 100 | drawer.drawBrush(lastPointerPos); 101 | event.preventDefault(); 102 | event.stopPropagation(); 103 | }); 104 | 105 | $element.on('touchmove mousemove', function(e) { 106 | if (lastPointerPos) { 107 | var event = e.originalEvent, 108 | pointerEvent = event.touches ? event.touches[0] : event, 109 | pointerPos = viewer.screenToImagePos({x: pointerEvent.pageX, y: pointerEvent.pageY}); 110 | 111 | drawer.drawBrushTo(pointerPos); 112 | 113 | lastPointerPos = pointerPos; 114 | } 115 | }); 116 | 117 | $element.on('touchend mouseup', function(event) { 118 | // console.log(event); 119 | if (lastPointerPos) { 120 | lastPointerPos = null; 121 | $scope.$safeApply(); 122 | } 123 | }); 124 | 125 | function getSliderForKey(e) { 126 | var id = 'draw-brush-depth'; 127 | if (e.shiftKey && e.altKey) { 128 | id = 'draw-brush-hardness'; 129 | } else if (e.altKey) { 130 | id = 'draw-brush-opacity'; 131 | } else if (e.shiftKey) { 132 | id = 'draw-brush-size'; 133 | } 134 | var el = $element.find('.' + id + ' [range-stepper]'); 135 | el.click(); // simulate click to notify change 136 | return el.controller('rangeStepper'); 137 | } 138 | 139 | function onKeyDown(e) { 140 | var s, handled = false; 141 | console.log('keydown which %d shift %s alt %s ctrl %s', e.which, e.shiftKey, e.altKey, e.ctrlKey); 142 | if (e.which === 48) { // 0 143 | getSliderForKey(e).percent(0.5); 144 | handled = true; 145 | } else if (e.which >= 49 && e.which <= 57) { // 1-9 146 | getSliderForKey(e).percent((e.which - 49) / 8); 147 | handled = true; 148 | } else if (e.which === 189) { // - 149 | s = getSliderForKey(e); 150 | s.percent(s.percent() - 0.025); 151 | handled = true; 152 | } else if (e.which === 187) { // + 153 | s = getSliderForKey(e); 154 | s.percent(s.percent() + 0.025); 155 | handled = true; 156 | } else if (e.which === 32) { 157 | $element.find('.draw-preview').click(); 158 | handled = true; 159 | } else if (e.which === 90) { // z 160 | $element.find('.draw-undo').click(); 161 | handled = true; 162 | } else if (e.which === 80) { // p 163 | $element.find('.draw-picker').click(); 164 | handled = true; 165 | } else if (e.which === 76) { // l 166 | $element.find('.draw-level').click(); 167 | handled = true; 168 | } 169 | 170 | if (handled) { 171 | e.preventDefault(); 172 | $scope.$safeApply(); 173 | } 174 | 175 | } 176 | 177 | $($window).on('keydown', onKeyDown); 178 | 179 | $element.find('.draw-brush-depth').on('touchstart mousedown click', function() { 180 | $scope.brushMode = false; 181 | }); 182 | 183 | $element.on('$destroy', function() { 184 | $element.off('touchstart mousedown'); 185 | $element.off('touchmove mousemove'); 186 | $($window).off('keydown', onKeyDown); 187 | 188 | depthy.animateOption(depthy.viewer, { 189 | depthPreview: oldViewerOpts.depthPreview, 190 | depthScale: oldViewerOpts.depthScale, 191 | depthBlurSize: oldViewerOpts.depthBlurSize, 192 | enlarge: oldViewerOpts.enlarge, 193 | }, 250); 194 | 195 | $timeout(function() { 196 | angular.extend(depthy.viewer, oldViewerOpts); 197 | }, 251); 198 | 199 | if (drawer.isCanceled()) { 200 | // restore opened depthmap 201 | viewer.setDepthmap(depthy.opened.depthSource, depthy.opened.depthUsesAlpha); 202 | } else { 203 | if (drawer.isModified()) { 204 | depthy.drawOptions = drawer.getOptions(); 205 | 206 | // remember drawn depthmap 207 | // store it as jpg 208 | viewer.exportDepthmap().then(function(url) { 209 | depthy.opened.markAsModified(); 210 | depthy.opened.depthSource = url; //viewer.getDepthmap().texture; 211 | depthy.opened.depthUsesAlpha = false; 212 | viewer.setDepthmap(url); 213 | depthy.opened.onDepthmapOpened(); 214 | }); 215 | } 216 | } 217 | 218 | drawer.destroy(true); 219 | 220 | }); 221 | 222 | 223 | 224 | }); -------------------------------------------------------------------------------- /app/scripts/classes/DepthyDrawer.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT Licensed 3 | 4 | Copyright (c) 2014 Rafał Lindemann. http://panrafal.github.com/depthy 5 | */ 6 | (function(root){ 7 | 'use strict'; 8 | 9 | var DepthyDrawer = root.DepthyDrawer = function(viewer) { 10 | 11 | var self = this, 12 | texture, undoTexture, 13 | lastUndoTime, redoMode = false, 14 | brush, brushCanvas, brushTexture, brushContainer, brushLastPos, brushDirty, 15 | options, 16 | renderer = viewer.getRenderer(), 17 | depthmap = viewer.getDepthmap(), 18 | image = viewer.getImage(), 19 | modified = false, canceled = false, 20 | unit; 21 | 22 | options = { 23 | depth: 0.5, 24 | size: 0.02, 25 | hardness: 0.0, 26 | opacity: 1.0, 27 | spacing: 0.1, 28 | slope: 0.5, 29 | blend: PIXI.blendModes.NORMAL, 30 | undoTimeout: 2000, 31 | }; 32 | 33 | function initialize() { 34 | 35 | // always start with a new one... 36 | if (true || depthmap.texture instanceof PIXI.RenderTexture === false) { 37 | // replace the texture 38 | viewer.setDepthmap(viewer.exportDepthmapAsTexture({width: 1000, height: 1000})); 39 | depthmap = viewer.getDepthmap(); 40 | } 41 | 42 | texture = depthmap.texture; 43 | 44 | unit = Math.max(image.size.width, image.size.height); 45 | 46 | // setup undo... 47 | // undoTexture = new PIXI.RenderTexture(texture.width, texture.height); 48 | 49 | // setup brush 50 | brushCanvas = document.createElement('canvas'); 51 | // brushCanvas.id = 'draw-brushcanvas'; 52 | // viewer.getElement().appendChild(brushCanvas); 53 | brushDirty = true; 54 | } 55 | 56 | function updateBrush() { 57 | 58 | var ctx = brushCanvas.getContext('2d'), 59 | depth = options.depth, 60 | size = options.size * unit, 61 | hardness = Math.max(0, Math.min(0.99, options.hardness)) 62 | 63 | brushCanvas.width = brushCanvas.height = size; 64 | ctx.clearRect(0, 0, size, size); 65 | 66 | 67 | var grd = ctx.createRadialGradient(size / 2, size / 2, size / 2 * hardness, size / 2, size / 2, size / 2), 68 | color = Math.round((depth) * 0xFF) 69 | grd.addColorStop(0, 'rgba(' + color + ',' + color + ',' + color + ',' + options.opacity + ')'); 70 | grd.addColorStop(options.slope, 'rgba(' + color + ',' + color + ',' + color + ',' + (0.5 * options.opacity) + ')'); 71 | grd.addColorStop(1, 'rgba(' + color + ',' + color + ',' + color + ',0)'); 72 | ctx.fillStyle = grd; 73 | ctx.fillRect(0, 0, size, size); 74 | 75 | // console.log('updateBrush!', options, color, grd); 76 | 77 | if (!brushTexture) { 78 | brushTexture = PIXI.Texture.fromCanvas(brushCanvas); 79 | } else { 80 | brushTexture.width = brushTexture.height = size; 81 | // PIXI.texturesToUpdate.push(brushTexture.baseTexture); 82 | PIXI.updateWebGLTexture(brushTexture.baseTexture, renderer.gl); 83 | } 84 | if (!brush) { 85 | brushContainer = new PIXI.DisplayObjectContainer(); 86 | brush = new PIXI.Sprite(brushTexture); 87 | brush.anchor.x = 0.5; 88 | brush.anchor.y = 0.5; 89 | brushContainer.addChild(brush); 90 | } 91 | brush.width = brush.height = size; 92 | brushDirty = false; 93 | } 94 | 95 | function throttledStoreUndo(force) { 96 | if (force || redoMode || !lastUndoTime || new Date() - lastUndoTime > options.undoTimeout) { 97 | storeUndo(); 98 | } 99 | lastUndoTime = new Date(); 100 | } 101 | 102 | function storeUndo() { 103 | if (!undoTexture) { 104 | undoTexture = new PIXI.RenderTexture(texture.width, texture.height); 105 | } 106 | console.log('Undo stored!'); 107 | redoMode = false; 108 | var sprite = new PIXI.Sprite(texture); 109 | undoTexture.render(sprite, null, true); 110 | } 111 | 112 | function toggleUndo() { 113 | if (!undoTexture) return; 114 | 115 | var tmp = undoTexture; 116 | undoTexture = texture; 117 | texture = tmp; 118 | redoMode = !redoMode; 119 | 120 | console.log('Toggle undo!', texture); 121 | 122 | depthmap.shared = true; // won't be destroyed 123 | viewer.setDepthmap(texture); 124 | depthmap = viewer.getDepthmap(); 125 | } 126 | 127 | this.setOptions = function(newOptions) { 128 | for(var k in newOptions) { 129 | if (options[k] === newOptions[k]) continue; 130 | options[k] = newOptions[k]; 131 | switch(k) { 132 | case 'depth': 133 | case 'size': 134 | case 'hardness': 135 | case 'opacity': 136 | case 'spacing': 137 | case 'slope': 138 | brushDirty = true; 139 | break; 140 | } 141 | } 142 | } 143 | 144 | this.getOptions = function() { 145 | var oc = {}; 146 | for(var k in options) oc[k] = options[k]; 147 | return oc; 148 | }; 149 | 150 | /* -1 undo, 1 redo, 0 no history */ 151 | this.getUndoMode = function() { 152 | return redoMode ? 1 : undoTexture ? -1 : 0; 153 | } 154 | 155 | this.toggleUndo = toggleUndo; 156 | 157 | this.drawBrush = function(pos) { 158 | if (brushDirty) updateBrush(); 159 | throttledStoreUndo(); 160 | brushLastPos = {x: pos.x, y: pos.y}; 161 | brush.x = pos.x * depthmap.size.width; 162 | brush.y = pos.y * depthmap.size.height; 163 | // brush.alpha = options.opacity; 164 | brush.blendMode = options.blend; 165 | // console.log('Draw', brush.x, brush.y); 166 | texture.render(brushContainer, null, false); 167 | depthmap.renderDirty = true; 168 | modified = true; 169 | }; 170 | 171 | this.drawBrushTo = function(pos) { 172 | var from = {x: brushLastPos.x, y: brushLastPos.y}, 173 | to = pos, 174 | dst = Math.sqrt(Math.pow((to.x - from.x) * depthmap.size.width, 2) + Math.pow((to.y - from.y) * depthmap.size.height, 2)), 175 | step = Math.round(Math.max(1, options.spacing * options.size * unit)), 176 | steps = dst / step; 177 | // console.log(dst, step, steps/*, from, to*/); 178 | for (var i = 1; i <= steps; ++i) { 179 | var prg = i / steps; 180 | this.drawBrush({x: from.x + (to.x - from.x) * prg, y: from.y + (to.y - from.y) * prg}); 181 | } 182 | }; 183 | 184 | this.getDepthAtPos = function(pos) { 185 | return PIXI.glReadPixels(renderer.gl, texture, pos.x * depthmap.size.width, pos.y * depthmap.size.height, 1, 1)[0] / 255; 186 | }; 187 | 188 | this.isModified = function() { 189 | return modified; 190 | }; 191 | 192 | this.isCanceled = function() { 193 | return canceled; 194 | }; 195 | 196 | this.cancel = function() { 197 | canceled = true; 198 | }; 199 | 200 | this.destroy = function(destroyTexture) { 201 | if (undoTexture) undoTexture.destroy(); 202 | if (brushTexture) brushTexture.destroy(); 203 | if (destroyTexture && texture) texture.destroy(); 204 | depthmap.shared = false; 205 | }; 206 | 207 | initialize(); 208 | }; 209 | 210 | })(window); -------------------------------------------------------------------------------- /app/scripts/controllers/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .controller('MainCtrl', function ($rootScope, $window, $scope, $timeout, ga, depthy, $element, $modal, $state, StateModal) { 5 | 6 | $rootScope.depthy = depthy; 7 | $rootScope.viewer = depthy.viewer; // shortcut 8 | $rootScope.Modernizr = window.Modernizr; 9 | $rootScope.Math = window.Math; 10 | $rootScope.screenfull = screenfull; 11 | 12 | $scope.version = depthy.getVersion(); 13 | 14 | ga('set', 'dimension1', (Modernizr.webgl ? 'webgl' : 'no-webgl') + ' ' + (Modernizr.webp ? 'webp' : 'no-webp')); 15 | 16 | $rootScope.$safeApply = function(fn) { 17 | var phase = this.$root.$$phase; 18 | if(phase === '$apply' || phase === '$digest') { 19 | if(fn && (typeof(fn) === 'function')) { 20 | fn(); 21 | } 22 | } else { 23 | this.$apply(fn); 24 | } 25 | }; 26 | 27 | $scope.loadSample = function(name) { 28 | $state.go('sample', {id: name}); 29 | // depthy.leftpaneOpen(true); 30 | }; 31 | 32 | $scope.openImage = function(image) { 33 | $state.go(image.state, image.stateParams); 34 | // depthy.leftpaneOpen(true); 35 | }; 36 | 37 | 38 | 39 | $scope.$watch('compoundFiles', function(files) { 40 | if (files && files.length) { 41 | depthy.loadLocalImage(files[0]).then( 42 | function() { 43 | ga('send', 'event', 'image', 'parsed', depthy.hasDepthmap() ? 'depthmap' : 'no-depthmap'); 44 | depthy.leftpaneClose(); 45 | depthy.opened.openState(); 46 | }, 47 | function(e) { 48 | ga('send', 'event', 'image', 'error', e); 49 | depthy.leftpaneClose(); 50 | } 51 | ); 52 | // depthy.handleCompoundFile(files[0]); 53 | } 54 | }); 55 | 56 | 57 | $scope.$watch('depthy.useOriginalImage', function() { 58 | depthy.refreshOpenedImage(); 59 | }); 60 | 61 | 62 | $scope.imageOptions = function() { 63 | depthy.openPopup('image.options'); 64 | }; 65 | 66 | $scope.shareOptions = function() { 67 | depthy.openPopup('share.options'); 68 | }; 69 | 70 | $scope.imageInfo = function() { 71 | StateModal.showModal('image.info', { 72 | templateUrl: 'views/image-info-modal.html', 73 | windowClass: 'info-modal', 74 | controller: 'ImageInfoModalCtrl', 75 | }); 76 | }; 77 | 78 | $scope.exportAnimationOptions = function(type) { 79 | var oldAnimate = depthy.viewer.animate; 80 | depthy.viewer.animate = true; 81 | 82 | if (type === 'gif') { 83 | depthy.exportSize = Math.min(500, depthy.exportSize); 84 | } 85 | 86 | depthy.openPopup('export.' + type + '.options').promise.finally(function() { 87 | depthy.viewer.animate = oldAnimate; 88 | }); 89 | 90 | }; 91 | 92 | $scope.exportAnimationRun = function(type) { 93 | depthy.exportActive = true; 94 | StateModal.showModal('export.' + type + '.run', { 95 | // stateOptions: {location: 'replace'}, 96 | templateUrl: 'views/export-' + type + '-modal.html', 97 | controller: 'Export' + type.substr(0,1).toUpperCase() + type.substr(1) + 'ModalCtrl', 98 | // backdrop: 'static', 99 | windowClass: 'export-' + type + '-modal', 100 | }).result.finally(function() { 101 | depthy.exportActive = false; 102 | }); 103 | }; 104 | 105 | $scope.exportPngRun = function() { 106 | StateModal.showModal('export.png', { 107 | // stateOptions: {location: 'replace'}, 108 | templateUrl: 'views/export-png-modal.html', 109 | controller: 'ExportPngModalCtrl', 110 | windowClass: 'export-png-modal', 111 | }).result.finally(function() { 112 | }); 113 | }; 114 | 115 | $scope.exportJpgRun = function() { 116 | StateModal.showModal('export.jpg', { 117 | // stateOptions: {location: 'replace'}, 118 | templateUrl: 'views/export-jpg-modal.html', 119 | controller: 'ExportJpgModalCtrl', 120 | windowClass: 'export-jpg-modal', 121 | }).result.finally(function() { 122 | }); 123 | }; 124 | 125 | $scope.exportAnaglyphRun = function() { 126 | StateModal.showModal('export.anaglyph', { 127 | // stateOptions: {location: 'replace'}, 128 | templateUrl: 'views/export-anaglyph-modal.html', 129 | controller: 'ExportAnaglyphModalCtrl', 130 | windowClass: 'export-anaglyph-modal modal-lg', 131 | }).result.finally(function() { 132 | }); 133 | }; 134 | 135 | $scope.sharePngRun = function() { 136 | StateModal.showModal('share.png', { 137 | // stateOptions: {location: 'replace'}, 138 | templateUrl: 'views/share-png-modal.html', 139 | controller: 'SharePngModalCtrl', 140 | // backdrop: 'static', 141 | // keyboard: false, 142 | windowClass: 'share-png-modal', 143 | }).result.finally(function() { 144 | }); 145 | }; 146 | 147 | $scope.drawDepthmap = function() { 148 | $state.go('draw'); 149 | }; 150 | 151 | $scope.debugClicksLeft = 2; 152 | $scope.debugClicked = function() { 153 | if (--$scope.debugClicksLeft === 0) depthy.enableDebug(); 154 | }; 155 | 156 | $scope.$watch('(depthy.activePopup.state === "export.gif.options" || depthy.activePopup.state === "export.webm.options" || depthy.exportActive) && depthy.exportSize', function(size) { 157 | if (size) { 158 | depthy.isViewerOverriden(true); 159 | depthy.viewer.size = {width: size, height: size}; 160 | if (depthy.viewer.fit) $scope.oldFit = depthy.viewer.fit; 161 | depthy.viewer.fit = false; 162 | console.log('Store fit ' + $scope.oldFit) 163 | } else { 164 | if ($scope.oldFit) { 165 | depthy.viewer.fit = $scope.oldFit; 166 | console.log('Restore fit ' + $scope.oldFit) 167 | } 168 | $($window).resize(); 169 | depthy.isViewerOverriden(false); 170 | } 171 | }); 172 | 173 | $scope.$watch('viewer.fit', function(fit) { 174 | if (fit === 'cover') { 175 | depthy.viewer.upscale = 4; 176 | } else if (fit === 'contain') { 177 | depthy.viewer.upscale = 1; 178 | } 179 | }); 180 | 181 | 182 | $scope.$on('pixi.webgl.init.exception', function(evt, exception) { 183 | console.error('WebGL Init Exception', exception); 184 | Modernizr.webgl = false; 185 | ga('send', 'event', 'webgl', 'exception', exception.toString(), {nonInteraction: 1}); 186 | }); 187 | 188 | $($window).on('resize', function() { 189 | var $viewer = $('#viewer'); 190 | depthy.viewer.size = { 191 | width: $viewer.width(), 192 | height: $viewer.height(), 193 | }; 194 | console.log('Resize %dx%d', $viewer.width(), $viewer.height()); 195 | $scope.$safeApply(); 196 | }); 197 | $($window).resize(); 198 | 199 | $($window).on('online offline', function() { 200 | $scope.$safeApply(); 201 | }); 202 | 203 | $timeout(function() { 204 | $scope.scroll = new IScroll('#leftpane', { 205 | mouseWheel: true, 206 | scrollbars: 'custom', 207 | click: false, 208 | fadeScrollbars: true, 209 | interactiveScrollbars: true, 210 | resizeScrollbars: false, 211 | eventPassthrough: 'horizontal', 212 | }); 213 | // refresh on every digest... 214 | $scope.$watch(function() { 215 | setTimeout(function() { 216 | $scope.scroll.refresh(); 217 | }, 100); 218 | }); 219 | }); 220 | 221 | 222 | 223 | 224 | }); -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DEPTHY 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 39 | 40 | 41 | 42 |
43 |
44 |

Depthy

45 |

Depthy shows Google Camera Lens Blur photos with 3D parallax effect and creates animated GIFs from them. Plus extracts the depth map and enables you to create your own!

46 |
47 |

Unfortunately your browser is not supported.
Use something newer, like Chrome for example.

48 |
49 |
50 | 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /app/views/main.html: -------------------------------------------------------------------------------- 1 |
11 |
12 | 19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 |

27 | the third dimension viewer 28 |

29 |
30 |
31 |
Open photo
32 |
33 |
34 | 35 |
36 | 37 | You're working offline.
Some feature may not be available! 38 |
39 |
40 | 41 |
Reload
42 |
43 | There is a new version available! 44 |
45 |
46 |
47 | 48 |
Got it!
49 |
50 | New stuff in this version: 51 |
{{info}}
52 |
53 |
54 | 55 | 60 | 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
Follow
69 | Follow 70 |
71 |
72 | 73 | 77 | 78 | 79 |
80 |
81 | 82 | 83 |
84 |
85 | 86 |
87 | 88 |
89 |

Move your {{Modernizr.devicemotion && Modernizr.mobile ? 'device' : 'cursor'}} around to see the effect

90 |
91 | 92 |
93 | 94 |
95 | 96 |
97 | 98 |
101 | 102 |
103 |
104 | 105 |
106 |
107 | 108 | 109 | 110 | 111 |
112 |
113 |
114 | 115 | 127 | 128 |
-------------------------------------------------------------------------------- /app/scripts/classes/GDepthEncoder.js: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License 3 | 4 | Copyright (c) 2014 Rafał Lindemann. http://panrafal.github.com/depthy 5 | */ 6 | (function() { 7 | 'use strict'; 8 | 9 | function makeUint16Buffer(arr, littleEndian) { 10 | var ab = new ArrayBuffer(arr.length * 2), 11 | dv = new DataView(ab); 12 | for (var i = 0; i < arr.length; ++i) { 13 | dv.setUint16(i * 2, arr[i], littleEndian); 14 | } 15 | return new Uint8Array(ab); 16 | } 17 | 18 | function makeUint32Buffer(arr, littleEndian) { 19 | var ab = new ArrayBuffer(arr.length * 4), 20 | dv = new DataView(ab); 21 | for (var i = 0; i < arr.length; ++i) { 22 | dv.setUint32(i * 4, arr[i], littleEndian); 23 | } 24 | return new Uint8Array(ab); 25 | } 26 | 27 | window.GDepthEncoder = { 28 | 29 | xmlns: { 30 | 'GFocus': 'http://ns.google.com/photos/1.0/focus/', 31 | 'GImage': 'http://ns.google.com/photos/1.0/image/', 32 | 'GDepth': 'http://ns.google.com/photos/1.0/depthmap/', 33 | 'xmpNote': 'http://ns.adobe.com/xmp/note/', 34 | }, 35 | xmpHeader: 'http://ns.adobe.com/xap/1.0/', 36 | xmpExtensionHeader: 'http://ns.adobe.com/xmp/extension/', 37 | 38 | // This is NOT a general purpose XMP builder! 39 | buildXMP: function(props, xmlns) { 40 | var xmp = [], k; 41 | xmlns = xmlns || this.xmlns; 42 | xmp.push(''); 43 | xmp.push(''); 44 | xmp.push(''); 53 | return xmp.join(''); 54 | }, 55 | 56 | dataURIinfo: function(uri) { 57 | var match = uri.match(/^data:(.+?);(.+?),(.+)$/); 58 | return match ? { 59 | mime: match[1], 60 | encoding: match[2], 61 | data: match[3] 62 | } : null; 63 | }, 64 | 65 | /** 66 | @param ArrayBuffer buffer image JPG as an ArrayBuffer 67 | @param dataURI depthmap 68 | @param dataURI original 69 | */ 70 | encodeDepthmap: function(buffer, depthmap, original, metadata) { 71 | var props = {}, extProps = {}, standardXMP, extendedXMP; 72 | depthmap = this.dataURIinfo(depthmap || ''); 73 | original = this.dataURIinfo(original || ''); 74 | if (original) { 75 | props['GImage:Mime'] = original.mime; 76 | extProps['GImage:Data'] = original.data; 77 | console.log('Original size', original.data.length); 78 | } 79 | if (depthmap) { 80 | props['GDepth:Format'] = 'RangeInverse'; 81 | props['GDepth:Mime'] = depthmap.mime; 82 | extProps['GDepth:Data'] = depthmap.data; 83 | console.log('Depthmap size', depthmap.data.length); 84 | } 85 | for (var k in metadata || {}) { 86 | props[k] = metadata[k]; 87 | } 88 | standardXMP = this.buildXMP(props); 89 | extendedXMP = this.buildXMP(extProps); 90 | 91 | return this.encodeXMP(buffer, standardXMP, extendedXMP); 92 | }, 93 | 94 | encodeXMP: function(buffer, standardXMP, extendedXMP) { 95 | var data = new DataView(buffer), 96 | offset = 0, 97 | parts = [], 98 | xmpWritten = false, 99 | self = this; 100 | 101 | function writeXMP() { 102 | if (!xmpWritten) { 103 | parts.push.apply(parts, self.buildXMPsegments(standardXMP, extendedXMP)); 104 | console.log('XMP written!'); 105 | xmpWritten = true; 106 | } 107 | } 108 | 109 | while (offset < data.byteLength) { 110 | var segType, segSize, app1Header, segStart, b; 111 | segStart = offset; 112 | console.log('Offset ' + offset); 113 | if ((b = data.getUint8(offset++)) !== 0xFF) { 114 | throw 'Bad JPG Format, 0xFF expected, got ' + b; 115 | } 116 | do { 117 | segType = data.getUint8(offset++); 118 | if (segType === 0xFF) { 119 | console.log('Padding 0xFF found'); 120 | parts.push(new Uint8Array([0xFF])); 121 | } else break; 122 | } while (true); 123 | if (segType === 0xC0 || segType === 0xC2 || segType === 0xDA) { 124 | writeXMP(); // right before SOF / SOS 125 | } 126 | if (segType === 0xDA) { 127 | // copy the rest on SOS... no XMP should exist beyound that point 128 | console.log('SOS found, copy remaining bytes ' + (buffer.byteLength - segStart)); 129 | parts.push(new Uint8Array(buffer, segStart, buffer.byteLength - segStart)); 130 | break; 131 | } 132 | if (segType === 0x00 || (segType >= 0xD0 && segType <= 0xD9)) { 133 | parts.push(new Uint8Array(buffer, segStart, 2)); 134 | console.log('Found ctrl segment ' + segType); 135 | continue; 136 | } 137 | segSize = data.getUint16(offset); 138 | offset += 2; 139 | if (segType === 0xE1) { 140 | // read header 141 | app1Header = ''; 142 | while ((b = data.getUint8(offset++)) !== 0x0) { 143 | app1Header += String.fromCharCode(b); 144 | } 145 | console.log('Found APP1 ' + app1Header); 146 | // ignore any existing XMP 147 | if (app1Header === this.xmpHeader || app1Header === this.xmpExtensionHeader) { 148 | console.log('Found old XMP, skipping'); 149 | offset += segSize - (offset - segStart - 2); 150 | continue; 151 | } 152 | } 153 | // copying segment 154 | console.log('Copying segment ' + segType + ', size: ' + segSize + ', left:' + (segSize - (offset - segStart - 2))); 155 | offset += segSize - (offset - segStart - 2); 156 | parts.push(new Uint8Array(buffer, segStart, 2 + segSize)); 157 | if (segType === 0xE1) { 158 | writeXMP(); // right after EXIF 159 | } 160 | } 161 | // console.log('Parts', parts); 162 | return new Blob(parts, {type: 'image/jpeg'}); 163 | }, 164 | 165 | buildXMPsegments: function(standardXMP, extendedXMP) { 166 | var extendedUid, parts = []; 167 | if (extendedXMP) { 168 | extendedUid = CryptoJS.MD5(extendedXMP).toString().toUpperCase(); 169 | console.log('ExtendedUID', extendedUid); 170 | standardXMP = standardXMP.replace(/(',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.flexbox=function(){return F("flexWrap")},q.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},q.webgl=function(){return!!a.WebGLRenderingContext},q.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:w(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},q.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},q.webworkers=function(){return!!a.Worker};for(var G in q)y(q,G)&&(v=G.toLowerCase(),e[v]=q[G](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)y(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},z(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=w,e.prefixed=function(a,b,c){return b?F(a,b,c):F(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f= vectorCutoff && confidenceSum < CONFIDENCE_MAX) { 172 | #endif 173 | float depth = 1.0 - texture2D(displacementMap, vpos * vec2(1, -1) + vec2(0, 1)).r; 174 | depth = clamp(depth, dmin, dmax); 175 | float confidence; 176 | 177 | #if METHOD == 1 178 | confidence = step(dpos, depth + 0.001); 179 | 180 | #elif METHOD == 2 181 | confidence = 1.0 - abs(dpos - depth); 182 | if (confidence < 1.0 - minConfidence * 2.0) confidence = 0.0; 183 | 184 | #elif METHOD == 5 185 | confidence = 1.0 - abs(dpos - depth); 186 | confidence = pow(confidence, maskPower); 187 | 188 | #endif 189 | 190 | #ifndef BRANCHLOOP 191 | confidence *= step(vectorCutoff, dpos); 192 | confidence *= step(confidenceSum, CONFIDENCE_MAX); 193 | #endif 194 | 195 | #ifndef ANTIALIAS 196 | #elif ANTIALIAS == 1 // go back halfstep, go forward fullstep - branched 197 | if (confidence > AA_TRIGGER && i == j) { 198 | j -= 0.5; 199 | } else { 200 | j += 1.0; 201 | } 202 | // confidence *= CONFIDENCE_MAX / 3.0; 203 | 204 | #elif ANTIALIAS == 2 // go back halfstep, go forward fullstep - mult 205 | j += 1.0 + step(AA_TRIGGER, confidence) 206 | * step(i, j) * -1.5; 207 | // confidence *= CONFIDENCE_MAX / 3.0; 208 | 209 | #elif ANTIALIAS == 11 210 | if (confidence >= AA_TRIGGER && i == j && steps - i > 1.0) { 211 | loopStep = AA_POWER * 2.0 / min(AA_MAXITER, steps - i - 1.0); 212 | j -= AA_POWER + loopStep; 213 | } 214 | confidence *= loopStep; 215 | j += loopStep; 216 | #elif ANTIALIAS == 12 217 | float _if_aa = step(AA_TRIGGER, confidence) 218 | * step(i, j) 219 | * step(1.5, steps - i); 220 | loopStep = _if_aa * (AA_POWER * 2.0 / min(AA_MAXITER, max(0.1, steps - i - 1.0)) - 1.0) + 1.0; 221 | confidence *= loopStep; 222 | j += _if_aa * -(AA_POWER + loopStep) + loopStep; 223 | #endif 224 | 225 | 226 | #ifdef BRANCHSAMPLE 227 | if (confidence > 0.0) { 228 | #endif 229 | 230 | #ifdef CORRECT 231 | #define CORRECTION_MATH +( ( vec2((depth - dpos) / (dstep * correctPower)) * vstep )) 232 | #else 233 | #define CORRECTION_MATH 234 | #endif 235 | 236 | #ifdef COLORAVG 237 | colSum += texture2D(uSampler, vpos CORRECTION_MATH) * confidence; 238 | #else 239 | posSum += (vpos CORRECTION_MATH) * confidence; 240 | #endif 241 | confidenceSum += confidence; 242 | 243 | #ifdef BRANCHSAMPLE 244 | } 245 | #endif 246 | 247 | 248 | #if DEBUG > 2 249 | gl_FragColor = vec4(vector[0] / 2.0 + 1.0, vector[1].xy / 2.0 + 1.0); 250 | #elif DEBUG > 1 251 | gl_FragColor = vec4(confidenceSum, depth, dpos, 0); 252 | #elif DEBUG > 0 253 | gl_FragColor = vec4(confidence, depth, dpos, 0); 254 | #endif 255 | #ifdef DEBUGBREAK 256 | if (i == float(DEBUGBREAK)) { 257 | dpos = 0.0; 258 | } 259 | #endif 260 | 261 | #ifdef BRANCHLOOP 262 | } 263 | #endif 264 | }; 265 | 266 | #if defined(COLORAVG) && DEBUG == 0 267 | gl_FragColor = colSum / vec4(confidenceSum); 268 | #elif !defined(COLORAVG) && DEBUG == 0 269 | gl_FragColor = texture2D(uSampler, posSum / confidenceSum); 270 | #endif 271 | 272 | } -------------------------------------------------------------------------------- /app/scripts/directives/rangeStepper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('depthyApp') 4 | .directive('rangeStepper', function ($parse, $timeout, $compile) { 5 | return { 6 | restrict: 'A', 7 | scope: true, 8 | require: ['rangeStepper', 'ngModel'], 9 | // template: '
', 10 | // transclude: true, 11 | controller: function() { 12 | }, 13 | compile: function($element, $attrs) { 14 | // console.log($element); 15 | var labelTemplate; 16 | if (!$element.find('.rs-value, .rs-thumb').length) { 17 | labelTemplate = $element.html(); 18 | $element.html(''); 19 | } 20 | return function postLink($scope, $element, $attrs, ctrls) { 21 | var values = $scope.$parent.$eval($attrs.values), 22 | options, 23 | position, defaultFormatter, 24 | rangeStepper = ctrls[0], 25 | ngModel = ctrls[1]; 26 | 27 | options = angular.extend({ 28 | // snap to defined values - 0.0 - 1.0 29 | snap: 0.1, 30 | // step between defined values - 0.0 - 1.0 31 | step: 0, 32 | // interpolated values will have this precision 33 | precision: 0.001, 34 | labelTemplate: labelTemplate || '{{getLabel(v)}}', 35 | valuesTemplate: '
', 36 | thumbTemplate: '
', 37 | format: 0.1, 38 | units: '', 39 | }, $scope.$parent.$eval($attrs.rangeStepper) || {}); 40 | 41 | if (angular.isFunction(options.format)) { 42 | defaultFormatter = options.format; 43 | } else if (angular.isNumber(options.format)) { 44 | var precision = options.format; 45 | defaultFormatter = function(v) { 46 | return precise(v, precision); 47 | }; 48 | } else if (options.format === '%') { 49 | defaultFormatter = function(v) {return Math.round(v * 100);}; 50 | // } else if (angular.isString(options.format)) { 51 | // defaultFormatter = $format(options.format); 52 | } else { 53 | defaultFormatter = function(v) {return v;}; 54 | } 55 | 56 | if (!values || values.length < 2) { 57 | console.error('Values are missing! Expr: %s, evaled to %o', $attrs.values, values); 58 | } 59 | 60 | function initialize() { 61 | // setup templates 62 | if ($element.find('.rs-value').length === 0) { 63 | $element.append($compile(options.valuesTemplate.replace(//i, options.labelTemplate))($scope)); 64 | } 65 | if ($element.find('.rs-thumb').length === 0) { 66 | $element.append($compile(options.thumbTemplate.replace(//i, options.labelTemplate))($scope)); 67 | } 68 | $timeout(updateValues); 69 | } 70 | 71 | function precise(v, precision) { 72 | if (!precision) return v; 73 | return +(Math.round(v / precision) * precision).toFixed(8); 74 | } 75 | 76 | function pxToPosition(px) { 77 | var rect = $element[0].getBoundingClientRect(); 78 | return (px - rect.left) / rect.width * (values.length); 79 | } 80 | 81 | function positionClamp(pos) { 82 | return Math.max(0, Math.min( values.length - 1, (pos >> 0) + precise(pos % 1, options.step) )); 83 | } 84 | 85 | function getValueAt(i) { 86 | var v = values[Math.max(0, Math.min(values.length - 1, Math.round(i)))]; 87 | return angular.isObject(v) ? v.value : v; 88 | } 89 | 90 | function getValue(v) { 91 | return angular.isObject(v) && v.value !== undefined ? v.value : v; 92 | } 93 | 94 | function getLabel(v, format, units) { 95 | if (angular.isObject(v)) { 96 | if (v.label !== undefined) return v.label; 97 | v = v.value; 98 | } 99 | 100 | return (format || defaultFormatter)(v) + (units || options.units); 101 | } 102 | 103 | function interpolate(a, b, t) { 104 | if (angular.isObject(a)) { 105 | var result = {}; 106 | for (var k in a) { 107 | result[k] = interpolate(a[k], b[k], t); 108 | } 109 | return result; 110 | } else { 111 | return precise(a + (b - a) * t, options.precision); 112 | } 113 | } 114 | 115 | function locate(v, a, b) { 116 | if (angular.isObject(v)) { 117 | var result = true; 118 | for (var k in v) { 119 | var pos = locate(v[k], a[k], b[k]); 120 | if (pos === false) return false; 121 | else if (pos !== true) { 122 | if (result === true || Math.abs(result - pos) < 0.01) { 123 | result = pos; 124 | } else { 125 | // console.warn('Partial range!'); 126 | return false; 127 | } 128 | } 129 | } 130 | return result; 131 | } else if (a === b) { 132 | return v === a; 133 | } else if (v === a) { 134 | return 0; 135 | } else if (v === b) { 136 | return 1; 137 | } else { 138 | return (v - a) / (b - a); 139 | } 140 | } 141 | 142 | function equals(a, b) { 143 | return locate(a, b, b) === true; 144 | } 145 | 146 | function positionToValue(pos) { 147 | pos = positionClamp(pos); 148 | if (options.snap && Math.abs(pos - Math.round(pos)) <= options.snap / 2) pos = Math.round(pos); 149 | if (pos % 1 === 0) return values[pos]; 150 | 151 | var value = getValueAt(Math.floor(pos)), 152 | next = getValueAt(Math.floor(pos) + 1); 153 | return {value: interpolate(value, next, pos % 1)}; 154 | } 155 | 156 | function valueToPosition(v) { 157 | var i = values.indexOf(v); 158 | if (i >= 0) return i; 159 | v = getValue(v); 160 | for (i = 0; i < values.length - 1; ++i) { 161 | var from = getValueAt(i), 162 | to = getValueAt(i + 1), 163 | pos = locate(v, from, to); 164 | if (pos !== false && pos !== true && pos >= 0 && pos <= 1) { 165 | return i + pos; 166 | } 167 | } 168 | if (v >= getValueAt(values.length - 1)) return values.length - 1; 169 | // console.warn('Value %s is out of bounds!', v); 170 | return false; 171 | } 172 | 173 | function updateValues() { 174 | $element.find('.rs-value, .rs-thumb').css('width', (1 / values.length * 100) + '%'); 175 | } 176 | 177 | function setPosition(pos) { 178 | position = positionClamp(pos); 179 | $scope.position = position; 180 | 181 | $element.find('.rs-thumb').css('transform', 'translateX('+(position * 100)+'%)'); 182 | 183 | if (pos === false) { 184 | $scope.v = ngModel.$viewValue; 185 | return; 186 | } 187 | 188 | var value = positionToValue(position), 189 | valueValue = getValue(value); 190 | 191 | $scope.v = value; 192 | // console.log('setPosition pos %s (%s) value %o getValue %o', pos, position, value, valueValue); 193 | if (!equals(ngModel.$viewValue, valueValue)) { 194 | ngModel.$setViewValue(valueValue); 195 | } 196 | } 197 | 198 | $scope.dragging = false; 199 | $scope.values = values; 200 | 201 | $scope.getLabel = getLabel; 202 | $scope.getValue = getValue; 203 | 204 | ngModel.$render = function() { 205 | setPosition(valueToPosition(ngModel.$viewValue)); 206 | }; 207 | 208 | $element.on('mousedown touchstart', '.rs-value', function(event) { 209 | var pointer = event.originalEvent.touches && event.originalEvent.touches[0] || event; 210 | if (event.touches && event.touches.length > 1) return; 211 | 212 | event.preventDefault(); 213 | // console.log(event); 214 | 215 | setPosition(Math.floor(positionClamp( pxToPosition(pointer.pageX) ))); 216 | $scope.$apply(); 217 | }); 218 | 219 | $element.on('mousedown touchstart', '.rs-thumb', function(event) { 220 | var pointer = event.originalEvent.touches && event.originalEvent.touches[0] || event; 221 | if (event.touches && event.touches.length > 1) return; 222 | 223 | event.preventDefault(); 224 | if ($scope.dragging) return; 225 | 226 | $scope.dragging = true; 227 | $element.addClass('rs-dragging'); 228 | var dragPos = pxToPosition(pointer.pageX), 229 | onMove = function(event) { 230 | var pointer = event.originalEvent.touches && event.originalEvent.touches[0] || event; 231 | event.preventDefault(); 232 | var newPos = pxToPosition(pointer.pageX); 233 | setPosition( positionClamp( position + newPos - dragPos )); 234 | dragPos = newPos; 235 | $scope.$apply(); 236 | }, 237 | onEnd = function(event) { 238 | event.preventDefault(); 239 | $scope.dragging = false; 240 | $element.removeClass('rs-dragging'); 241 | $('body').off('mousemove touchmove', onMove) 242 | .off('mouseup touchend', onEnd); 243 | $timeout(function() { 244 | setPosition( valueToPosition(positionToValue(position)) ); 245 | }); 246 | $scope.$apply(); 247 | }; 248 | 249 | $('body').on('mousemove touchmove', onMove) 250 | .on('mouseup touchend', onEnd); 251 | 252 | }); 253 | 254 | // setup API 255 | rangeStepper.percent = function(p) { 256 | if (p !== undefined) { 257 | setPosition(p * (values.length - 1)); 258 | } 259 | return position / (values.length - 1); 260 | }; 261 | 262 | 263 | initialize(); 264 | 265 | }; 266 | } 267 | }; 268 | }); 269 | --------------------------------------------------------------------------------