├── __init__.py ├── templates ├── partials │ ├── nav │ │ └── header.html │ └── fb │ │ └── init.html ├── 404.html ├── about.html ├── base.html └── index.html ├── static ├── sass │ ├── _filters.scss │ ├── _variables.scss │ ├── _fonts.scss │ ├── main.scss │ ├── _footer.scss │ ├── _header.scss │ ├── _images.scss │ └── _layout.scss ├── img │ ├── filters │ │ ├── kenya │ │ │ ├── kenya-lg.png │ │ │ ├── kenya-h200.png │ │ │ └── kenya-h300.png │ │ ├── syria │ │ │ ├── syria-lg.png │ │ │ ├── syria-h200.png │ │ │ └── syria-h300.png │ │ ├── lebanon │ │ │ ├── lebanon-lg.png │ │ │ ├── lebanon-h200.png │ │ │ └── lebanon-h300.png │ │ └── myanmar │ │ │ ├── myanmar-lg.png │ │ │ ├── myanmar-h200.png │ │ │ └── myanmar-h300.png │ └── avatars │ │ ├── default-avatar-h200.jpg │ │ └── default-avatar-h300.jpg ├── css │ ├── build.css.map │ └── build.css └── js │ ├── main.js │ └── lib │ ├── binaryajax.js │ ├── jquery.canvasResize.js │ ├── exif.js │ └── jquery.exif.js ├── .gitignore ├── README.md ├── package.json ├── application.py └── gruntfile.js /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/partials/nav/header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/sass/_filters.scss: -------------------------------------------------------------------------------- 1 | .beirut { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | .sass-cache/ 4 | -------------------------------------------------------------------------------- /static/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | $header_height: 60px; 2 | 3 | -------------------------------------------------------------------------------- /static/sass/_fonts.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Montserrat); 2 | -------------------------------------------------------------------------------- /static/sass/main.scss: -------------------------------------------------------------------------------- 1 | @import 'header'; 2 | @import 'footer'; 3 | @import 'layout'; 4 | @import 'images'; 5 | @import 'filters'; 6 | -------------------------------------------------------------------------------- /static/img/filters/kenya/kenya-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/kenya/kenya-lg.png -------------------------------------------------------------------------------- /static/img/filters/syria/syria-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/syria/syria-lg.png -------------------------------------------------------------------------------- /static/img/filters/kenya/kenya-h200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/kenya/kenya-h200.png -------------------------------------------------------------------------------- /static/img/filters/kenya/kenya-h300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/kenya/kenya-h300.png -------------------------------------------------------------------------------- /static/img/filters/lebanon/lebanon-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/lebanon/lebanon-lg.png -------------------------------------------------------------------------------- /static/img/filters/myanmar/myanmar-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/myanmar/myanmar-lg.png -------------------------------------------------------------------------------- /static/img/filters/syria/syria-h200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/syria/syria-h200.png -------------------------------------------------------------------------------- /static/img/filters/syria/syria-h300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/syria/syria-h300.png -------------------------------------------------------------------------------- /static/img/avatars/default-avatar-h200.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/avatars/default-avatar-h200.jpg -------------------------------------------------------------------------------- /static/img/avatars/default-avatar-h300.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/avatars/default-avatar-h300.jpg -------------------------------------------------------------------------------- /static/img/filters/lebanon/lebanon-h200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/lebanon/lebanon-h200.png -------------------------------------------------------------------------------- /static/img/filters/lebanon/lebanon-h300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/lebanon/lebanon-h300.png -------------------------------------------------------------------------------- /static/img/filters/myanmar/myanmar-h200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/myanmar/myanmar-h200.png -------------------------------------------------------------------------------- /static/img/filters/myanmar/myanmar-h300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fidiego/solidiaritize-v0.0.1/master/static/img/filters/myanmar/myanmar-h300.png -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |

Ooop!

5 |

It looks like whatver you're looking for is missing. 6 |

7 | {% endblock content %} 8 | -------------------------------------------------------------------------------- /static/sass/_footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | position: absolute; 3 | bottom: 0; 4 | left: 0; 5 | width: 100vw; 6 | height: 40px; 7 | .footer-inner { 8 | max-width: 300px; 9 | margin: 0 auto; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /static/sass/_header.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | .header { 3 | height: $header_height; 4 | width: 100vw; 5 | position: absolute; 6 | top: 0; 7 | left: 0; 8 | text-align: center; 9 | font-style: bold; 10 | font-size: 2em; 11 | font-family: 'Montserrat', sans-serif; 12 | padding-top: 15px; 13 | color: #55496D; 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidaritize 2 | A super simple canvas-based application 3 | 4 | ## Setting up for development 5 | 6 | Requirements 7 | 8 | 1. pip + virtualenv(wrapper) 9 | 2. npm 10 | 11 | First time setup 12 | 13 | $ mkvirtualenv solidaritize 14 | $ workon solidaritize 15 | $ pip install -r requirements.txt 16 | $ npm install 17 | 18 | To run locally 19 | 20 | $ python application.py 21 | 22 | To build 23 | 24 | $ ./build.sh 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sppgen", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "build.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "grunt": "^0.4.5", 13 | "grunt-contrib-cssmin": "^0.14.0", 14 | "grunt-contrib-htmlmin": "^0.6.0", 15 | "grunt-processhtml": "^0.3.8", 16 | "grunt-shell": "^1.1.2", 17 | "node-sass": "^3.4.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/about.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_title %}About{% endblock page_title %} 4 | 5 | {% block content %} 6 |
7 |

About

8 |
9 | This project was created in response to the lack of support, sympathy, and empathy towards the peoples of muslim countries whose recent tragedies are overlooked. 10 |
11 |
12 | {% endblock content %} 13 | {% block end_scripts %}{% endblock end_scripts %} 14 | -------------------------------------------------------------------------------- /application.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | from flask import Flask 3 | from flask import render_template 4 | from flask_frozen import Freezer 5 | 6 | app = Flask(__name__) 7 | freezer = Freezer(app) 8 | 9 | 10 | @app.route('/') 11 | def index(): 12 | return render_template('index.html') 13 | 14 | @app.route('/error/') 15 | def error(): 16 | return render_template('404.html') 17 | 18 | @app.route('/about/') 19 | def about(): 20 | return render_template('about.html') 21 | 22 | if __name__ == '__main__': 23 | if len(sys.argv) > 1 and sys.argv[1] == "build": 24 | os.system('rm -r build') 25 | freezer.freeze() 26 | else: 27 | app.run() 28 | -------------------------------------------------------------------------------- /static/sass/_images.scss: -------------------------------------------------------------------------------- 1 | .avatar { 2 | width: 200px; 3 | height: 200px; 4 | border-radius: 4px; 5 | background-image: url(/static/img/avatars/default-avatar-h200.jpg); 6 | background-position: center center; 7 | background-size: cover; 8 | &.avatar-lg { 9 | width: 300px; 10 | height: 300px; 11 | background-image: url(/static/img/avatars/default-avatar-h300.jpg); 12 | } 13 | &.centered { 14 | margin: 0 auto; 15 | } 16 | } 17 | 18 | .sources { 19 | width: 200px; 20 | height: 40px; 21 | margin: 0 auto; 22 | margin-top: 15px; 23 | text-align: center; 24 | &.sources-lg { 25 | width: 300px; 26 | } 27 | .src { 28 | padding-top: 10px; 29 | height: 100%; 30 | float: left; 31 | color: #fff; 32 | width: 100%; 33 | &.src-fb { 34 | background-color: #3b5998; 35 | } 36 | &.src-tw { 37 | background-color: #4099FF; 38 | } 39 | &.src-ul { 40 | background-color: #665884; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | {# HEAD #} 3 | {% block head %} 4 | 5 | 6 | {% block page_title %} 7 | Solidaritize 8 | {% endblock page_title %} 9 | 10 | 11 | 12 | 13 | 14 | {% block head_scripts %}{% endblock head_scripts %} 15 | 16 | {% endblock head %} 17 | 18 | {# BODY #} 19 | {% block body %} 20 | 21 | {% include "partials/fb/init.html" %} 22 | 23 |
24 | Solidaritize 25 |
26 |
27 | {% block content %} 28 | {% endblock content %} 29 |
30 | 32 | 33 | 34 | {% block end_scripts %} 35 | 36 | 37 | 38 | 39 | 40 | {% endblock end_scripts %} 41 | 42 | 43 | {% endblock body %} 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/sass/_layout.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'fonts'; 3 | html { 4 | overflow: hidden; 5 | font-family: 'Montserrat', sans-serif; 6 | } 7 | 8 | html, 9 | body { 10 | margin: 0px; 11 | padding: 0px; 12 | background-color: #f3f3f3; 13 | } 14 | 15 | .content { 16 | width: 100vw; 17 | margin-top: $header_height; 18 | height: calc(100vh - #{$header_height}); 19 | } 20 | 21 | .content-small { 22 | max-width: 300px; 23 | &.centered { 24 | margin: 0 auto; 25 | } 26 | } 27 | 28 | .content-narrow { 29 | max-width: 500px; 30 | margin: 0 auto; 31 | } 32 | 33 | #simple-version-001 { 34 | $this_height: 384px; 35 | height: $this_height; 36 | margin-top: 90px; 37 | #filter-form { 38 | margin-top: 30px; 39 | select { 40 | width: 100%; 41 | } 42 | } 43 | } 44 | 45 | .hidden { 46 | display: none; 47 | } 48 | 49 | .text--center { 50 | text-align: center; 51 | } 52 | 53 | .padded { 54 | padding-top: 30px; 55 | } 56 | 57 | .avatar-container { 58 | position: relative; 59 | color: #fff; 60 | width: 100%; 61 | .upload-prompt { 62 | font-family: sans-serif, arial; 63 | width: calc(100% - 40px); 64 | background-color: rgba(0, 0, 0, .5); 65 | padding: 0px 20px; 66 | position: absolute; 67 | bottom: 0; 68 | left: 0; 69 | text-align: center; 70 | } 71 | } 72 | #download-button { 73 | text-decoration: none; 74 | } 75 | .download-button { 76 | width: 100%; 77 | height: 40px; 78 | text-align: center; 79 | text-decoration: none; 80 | text-transform: uppercase; 81 | font-size: 1.4em; 82 | border: 2px solid #55496D; 83 | background-color: #55496D; 84 | padding-top: 15px; 85 | color: #fff; 86 | 87 | } 88 | .download-instructions { 89 | text-align: center; 90 | } 91 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 |
4 |
5 | 6 |
7 |

8 | 9 |
10 | Upload an Image 11 | or click below to use your facebook profile picture. 12 |

13 |
14 |
15 |
16 |
17 | 18 |
19 | 22 | 25 |
26 |
27 | 34 | 35 |
36 | 37 | 40 | 41 | 44 |
45 | {% endblock content %} 46 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | uglify: { 5 | build: { 6 | options: { 7 | mangle: true, 8 | exportAll: false, 9 | beautify: false 10 | }, 11 | files: { 12 | 'build/static/js/build.min.js': ['build/static/js/lib/jquery-2.1.4.js', 'build/static/js/**/*.js'] 13 | } 14 | } 15 | }, 16 | processhtml: { 17 | dist: { 18 | files: { 19 | 'build/index.html': ['build/index.html'] 20 | } 21 | } 22 | }, 23 | htmlmin: { 24 | build: { 25 | options: { 26 | removeComments: true, 27 | collapseWhitespace: true 28 | }, 29 | files: { 30 | 'build/index.html': 'build/index.html' 31 | } 32 | }, 33 | }, 34 | shell: { 35 | options: { 36 | stderr: false 37 | }, 38 | target: { 39 | command: 'rm -r build/static/js/lib/ && rm -r build/static/sass/ && rm build/static/js/main.js' 40 | } 41 | }, 42 | // DEVELOPMENT 43 | // sass: { 44 | // dist: { 45 | // files: { 46 | // 'static/styles/css/main.css': 'static/scss/main.scss' 47 | // } 48 | // } 49 | // }, 50 | // watch: { 51 | // files: ['dev/scss/**/*.scss'], 52 | // tasks: ['sass'] 53 | // }, 54 | }); 55 | 56 | grunt.loadNpmTasks('grunt-contrib-uglify'); 57 | grunt.loadNpmTasks('grunt-processhtml'); 58 | grunt.loadNpmTasks('grunt-contrib-htmlmin'); 59 | grunt.loadNpmTasks('grunt-shell'); 60 | 61 | grunt.registerTask('build', ['uglify', 'processhtml', 'htmlmin', 'shell']); 62 | grunt.registerTask('default', ['build']); 63 | }; 64 | -------------------------------------------------------------------------------- /static/css/build.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "AAAQ,+DAAuD;ACC/D,OAAQ;EACJ,MAAM,ECFM,IAAI;EDGhB,KAAK,EAAE,KAAK;EACZ,QAAQ,EAAE,QAAQ;EAClB,GAAG,EAAE,CAAC;EACN,IAAI,EAAE,CAAC;EACP,UAAU,EAAE,MAAM;EAClB,UAAU,EAAE,IAAI;EAChB,SAAS,EAAE,GAAG;EACd,WAAW,EAAE,wBAAwB;EACrC,WAAW,EAAE,IAAI;EACjB,KAAK,EAAE,OAAO;;AEZlB,OAAQ;EACJ,QAAQ,EAAE,QAAQ;EAClB,MAAM,EAAE,CAAC;EACT,IAAI,EAAE,CAAC;EACP,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,IAAI;EACZ,qBAAc;IACV,SAAS,EAAE,KAAK;IAChB,MAAM,EAAE,MAAM;;ACNtB,IAAK;EACD,QAAQ,EAAE,MAAM;EAChB,WAAW,EAAE,wBAAwB;;AAGzC;IACK;EACD,MAAM,EAAE,GAAG;EACX,OAAO,EAAE,GAAG;EACZ,gBAAgB,EAAE,OAAO;;AAG7B,QAAS;EACL,KAAK,EAAE,KAAK;EACZ,UAAU,EFhBE,IAAI;EEiBhB,MAAM,EAAE,kBAA+B;;AAG3C,cAAe;EACX,SAAS,EAAE,KAAK;EAChB,uBAAW;IACP,MAAM,EAAE,MAAM;;AAItB,eAAgB;EACZ,SAAS,EAAE,KAAK;EAChB,MAAM,EAAE,MAAM;;AAGlB,mBAAoB;EAEhB,MAAM,EADQ,KAAK;EAEnB,UAAU,EAAE,IAAI;EAChB,gCAAa;IACT,UAAU,EAAE,IAAI;IAChB,uCAAO;MACH,KAAK,EAAE,IAAI;;AAKvB,OAAQ;EACJ,OAAO,EAAE,IAAI;;AAGjB,aAAc;EACV,UAAU,EAAE,MAAM;;AAGtB,OAAQ;EACJ,WAAW,EAAE,IAAI;;AAGrB,iBAAkB;EACd,QAAQ,EAAE,QAAQ;EAClB,KAAK,EAAE,IAAI;EACX,KAAK,EAAE,IAAI;EACX,gCAAe;IACX,WAAW,EAAE,iBAAiB;IAC9B,KAAK,EAAE,iBAAiB;IACxB,gBAAgB,EAAE,kBAAiB;IACnC,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,QAAQ;IAClB,MAAM,EAAE,CAAC;IACT,IAAI,EAAE,CAAC;IACP,UAAU,EAAE,MAAM;;AAG1B,gBAAiB;EACb,eAAe,EAAE,IAAI;;AAEzB,gBAAiB;EACb,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,MAAM;EAClB,eAAe,EAAE,IAAI;EACrB,cAAc,EAAE,SAAS;EACzB,SAAS,EAAE,KAAK;EAChB,MAAM,EAAE,iBAAiB;EACzB,gBAAgB,EAAE,OAAO;EACzB,WAAW,EAAE,IAAI;EACjB,KAAK,EAAE,IAAI;;AAGf,sBAAuB;EACnB,UAAU,EAAE,MAAM;;ACxFtB,OAAQ;EACJ,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,KAAK;EACb,aAAa,EAAE,GAAG;EAClB,gBAAgB,EAAE,gDAAgD;EAClE,mBAAmB,EAAE,aAAa;EAClC,eAAe,EAAE,KAAK;EACtB,iBAAY;IACR,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,KAAK;IACb,gBAAgB,EAAE,gDAAgD;EAEtE,gBAAW;IACP,MAAM,EAAE,MAAM;;AAItB,QAAS;EACL,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,IAAI;EACZ,MAAM,EAAE,MAAM;EACd,UAAU,EAAE,IAAI;EAChB,UAAU,EAAE,MAAM;EAClB,mBAAa;IACT,KAAK,EAAE,KAAK;EAEhB,aAAK;IACD,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,IAAI;IACZ,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,oBAAS;MACL,gBAAgB,EAAE,OAAO;IAE7B,oBAAS;MACL,gBAAgB,EAAE,OAAO;IAE7B,oBAAS;MACL,gBAAgB,EAAE,OAAO", 4 | "sources": ["../sass/_fonts.scss","../sass/_header.scss","../sass/_variables.scss","../sass/_footer.scss","../sass/_layout.scss","../sass/_images.scss"], 5 | "names": [], 6 | "file": "build.css" 7 | } -------------------------------------------------------------------------------- /templates/partials/fb/init.html: -------------------------------------------------------------------------------- 1 | 69 | -------------------------------------------------------------------------------- /static/css/build.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Montserrat); 2 | .header { 3 | height: 60px; 4 | width: 100vw; 5 | position: absolute; 6 | top: 0; 7 | left: 0; 8 | text-align: center; 9 | font-style: bold; 10 | font-size: 2em; 11 | font-family: 'Montserrat', sans-serif; 12 | padding-top: 15px; 13 | color: #55496D; } 14 | 15 | .footer { 16 | position: absolute; 17 | bottom: 0; 18 | left: 0; 19 | width: 100vw; 20 | height: 40px; } 21 | .footer .footer-inner { 22 | max-width: 300px; 23 | margin: 0 auto; } 24 | 25 | html { 26 | overflow: hidden; 27 | font-family: 'Montserrat', sans-serif; } 28 | 29 | html, 30 | body { 31 | margin: 0px; 32 | padding: 0px; 33 | background-color: #f3f3f3; } 34 | 35 | .content { 36 | width: 100vw; 37 | margin-top: 60px; 38 | height: calc(100vh - 60px); } 39 | 40 | .content-small { 41 | max-width: 300px; } 42 | .content-small.centered { 43 | margin: 0 auto; } 44 | 45 | .content-narrow { 46 | max-width: 500px; 47 | margin: 0 auto; } 48 | 49 | #simple-version-001 { 50 | height: 384px; 51 | margin-top: 90px; } 52 | #simple-version-001 #filter-form { 53 | margin-top: 30px; } 54 | #simple-version-001 #filter-form select { 55 | width: 100%; } 56 | 57 | .hidden { 58 | display: none; } 59 | 60 | .text--center { 61 | text-align: center; } 62 | 63 | .padded { 64 | padding-top: 30px; } 65 | 66 | .avatar-container { 67 | position: relative; 68 | color: #fff; 69 | width: 100%; } 70 | .avatar-container .upload-prompt { 71 | font-family: sans-serif, arial; 72 | width: calc(100% - 40px); 73 | background-color: rgba(0, 0, 0, 0.5); 74 | padding: 0px 20px; 75 | position: absolute; 76 | bottom: 0; 77 | left: 0; 78 | text-align: center; } 79 | 80 | #download-button { 81 | text-decoration: none; } 82 | 83 | .download-button { 84 | width: 100%; 85 | height: 40px; 86 | text-align: center; 87 | text-decoration: none; 88 | text-transform: uppercase; 89 | font-size: 1.4em; 90 | border: 2px solid #55496D; 91 | background-color: #55496D; 92 | padding-top: 15px; 93 | color: #fff; } 94 | 95 | .download-instructions { 96 | text-align: center; } 97 | 98 | .avatar { 99 | width: 200px; 100 | height: 200px; 101 | border-radius: 4px; 102 | background-image: url(/static/img/avatars/default-avatar-h200.jpg); 103 | background-position: center center; 104 | background-size: cover; } 105 | .avatar.avatar-lg { 106 | width: 300px; 107 | height: 300px; 108 | background-image: url(/static/img/avatars/default-avatar-h300.jpg); } 109 | .avatar.centered { 110 | margin: 0 auto; } 111 | 112 | .sources { 113 | width: 200px; 114 | height: 40px; 115 | margin: 0 auto; 116 | margin-top: 15px; 117 | text-align: center; } 118 | .sources.sources-lg { 119 | width: 300px; } 120 | .sources .src { 121 | padding-top: 10px; 122 | height: 100%; 123 | float: left; 124 | color: #fff; 125 | width: 100%; } 126 | .sources .src.src-fb { 127 | background-color: #3b5998; } 128 | .sources .src.src-tw { 129 | background-color: #4099FF; } 130 | .sources .src.src-ul { 131 | background-color: #665884; } 132 | 133 | /*# sourceMappingURL=build.css.map */ 134 | -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | var IMAGE_CONTENT; 2 | var IMG; 3 | var FILE; 4 | $(document).ready(function() { 5 | function checkFBLogin() { 6 | FB.getLoginStatus(function(response) { 7 | if (response.status === 'connected') { 8 | return true; 9 | } else if (response.status === 'not_authorized') { 10 | FB.login() 11 | } else { 12 | return false 13 | } 14 | }); 15 | } 16 | 17 | // IMAGE STUFF 18 | 19 | var profileImageInput = document.getElementById('profileImageInput'); 20 | profileImageInput.addEventListener('change', handleImage, false); 21 | var CANVAS = document.getElementById("avatarCanvas"); 22 | CANVAS.width = 300; 23 | CANVAS.height = 300; 24 | var CONTEXT = document.getElementById("avatarCanvas").getContext("2d"); 25 | 26 | function setContext(imageFile, context) { 27 | var img = new Image(); 28 | img.onload = function() { 29 | var w = img.width; 30 | var h = img.height; 31 | var crop_x = 0; 32 | var crop_y = 0; 33 | var crop_w = 0; 34 | var crop_h = 0; 35 | if (w < h){ // image is tall 36 | console.log('image is tall') 37 | crop_y = (h - w) / 2; 38 | crop_h = h - (h - w); 39 | crop_w = w; 40 | } else if (h < w) { // image is wide 41 | console.log('image is wide') 42 | crop_x = (w - h) / 2; 43 | crop_w = w - (w - h); 44 | crop_h = h 45 | } else { 46 | crop_w = w; 47 | crop_h = h; 48 | } 49 | console.log('NEW DIMENSIONS (w, h):', crop_w, crop_h) 50 | console.log(w, h, crop_x, crop_y, crop_w, crop_h) 51 | context.drawImage(img, crop_x, crop_y, crop_w, crop_h, 0, 0, CANVAS.width, CANVAS.height); 52 | } 53 | img.src = imageFile; 54 | img.setAttribute('crossOrigin', 'anonymous'); 55 | IMAGE_CONTENT = imageFile; 56 | IMG = img; 57 | } 58 | // ON IMAGE INPUT FILE CHANGE 59 | function handleImage(e) { 60 | var reader = new FileReader(); 61 | reader.onloadend = function(event) { 62 | setContext(event.target.result, CONTEXT) 63 | } 64 | FILE = e.target.files[0]; 65 | reader.readAsDataURL(FILE); 66 | $('select').fadeIn('slow'); 67 | $('.upload-prompt').fadeOut('fast'); 68 | }; 69 | 70 | // ON FETCHING IMAGE FROM FACEBOOK 71 | function paintWithURL(url) { 72 | var img = new Image(); 73 | img.src = url; 74 | img.setAttribute('crossOrigin', 'anonymous'); 75 | // $('canvas.avatar') 76 | // .css('background-image', 'url(' + url + ')') 77 | // .css('background-position', 'center center') 78 | // .css('background-size', 'cover') 79 | CONTEXT.drawImage(img, 0, 0, CANVAS.width, CANVAS.height); 80 | IMAGE_CONTENT = url; 81 | $('select').fadeIn('slow'); 82 | $('.upload-prompt').fadeOut('fast'); 83 | }; 84 | 85 | // APPLY FILTER 86 | function applyFilter(name) { 87 | if (name === null || name === undefined || name === "null") { 88 | console.log('no name') 89 | return 90 | } 91 | $(CANVAS).fadeOut('fast', function() { 92 | console.log('removing old content'); 93 | CONTEXT.clearRect(0, 0, CANVAS.width, CANVAS.height); 94 | setContext(IMAGE_CONTENT, CONTEXT) 95 | 96 | var filterImage = new Image(); 97 | filterImage.src = "/static/img/filters/" + name + "/" + name + "-h300.png"; 98 | filterImage.setAttribute('crossOrigin', 'anonymous'); 99 | filterImage.onload = function() { 100 | CONTEXT.globalCompositeOperation = "soft-light"; 101 | CONTEXT.drawImage(filterImage, 0, 0, CANVAS.width, CANVAS.height); 102 | }; 103 | }) 104 | 105 | $(CANVAS).fadeIn() 106 | $('.download-button').fadeIn(); 107 | $('.download-instructions').fadeIn(); 108 | }; 109 | 110 | $('#filter-select').change(function() { 111 | var selectedValue = $(this).find(':selected')[0].value; 112 | applyFilter(selectedValue) 113 | }); 114 | 115 | // FACEBOOK 116 | function getFaceBookImage() { 117 | FB.api( 118 | '/me/picture/?width=9999', 119 | function(response) { 120 | if (response && !response.error) { 121 | paintWithURL(response.data.url) 122 | } else { 123 | console.log('error') 124 | } 125 | } 126 | ); 127 | }; 128 | 129 | // ON FACEBOOK CLICK 130 | $('div.src.src-fb').click(function() { 131 | console.log('facebook clicked') 132 | FB.getLoginStatus(function(response) { 133 | if (response.status === 'connected') { 134 | getFaceBookImage(); 135 | } else if (response.status === 'not_authorized') { 136 | // The person is logged into Facebook, but not your app. 137 | FB.login() 138 | } else { 139 | // The person is not logged into Facebook, so we're not sure if 140 | // they are logged into this app or not. 141 | alert('You must be logged into facebook to use this feature.') 142 | } 143 | }); 144 | }); 145 | 146 | // ON UPLOAD CLICK 147 | $('div.avatar-container').click(function() { 148 | $('input#profileImageInput').click() 149 | }); 150 | $('div.src.src-ul').click(function() { 151 | $('input#profileImageInput').click() 152 | }); 153 | 154 | var button = document.getElementById('download-button'); 155 | button.addEventListener('click', function(e) { 156 | var dataURL = CANVAS.toDataURL('image/png'); 157 | button.href = dataURL; 158 | 159 | }); 160 | 161 | // ON HOVER 162 | // $('.avatar').hover( 163 | // function() { 164 | // if (IMAGE_CONTENTS) { 165 | // $('i#clear-avatar').fadeIn().removeClass('hidden') 166 | // } 167 | // }, 168 | // function() { 169 | // $('i#clear-avatar').fadeOut().addClass('hidden') 170 | // } 171 | // ); 172 | // $('i#clear-avatar').click(function() { 173 | // console.log(IMAGE_CONTENTS) 174 | // IMAGE_CONTENTS = null; 175 | // $('div.avatar').removeAttr('style'); 176 | // console.log(IMAGE_CONTENTS) 177 | // }) 178 | 179 | }); 180 | -------------------------------------------------------------------------------- /static/js/lib/binaryajax.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Binary Ajax 0.1.10 4 | * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ 5 | * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt] 6 | */ 7 | 8 | var BinaryFile = function(strData, iDataOffset, iDataLength) { 9 | var data = strData; 10 | var dataOffset = iDataOffset || 0; 11 | var dataLength = 0; 12 | 13 | this.getRawData = function() { 14 | return data; 15 | } 16 | 17 | if (typeof strData == "string") { 18 | dataLength = iDataLength || data.length; 19 | 20 | this.getByteAt = function(iOffset) { 21 | return data.charCodeAt(iOffset + dataOffset) & 0xFF; 22 | } 23 | 24 | this.getBytesAt = function(iOffset, iLength) { 25 | var aBytes = []; 26 | 27 | for (var i = 0; i < iLength; i++) { 28 | aBytes[i] = data.charCodeAt((iOffset + i) + dataOffset) & 0xFF 29 | } 30 | ; 31 | 32 | return aBytes; 33 | } 34 | } else if (typeof strData == "unknown") { 35 | dataLength = iDataLength || IEBinary_getLength(data); 36 | 37 | this.getByteAt = function(iOffset) { 38 | return IEBinary_getByteAt(data, iOffset + dataOffset); 39 | } 40 | 41 | this.getBytesAt = function(iOffset, iLength) { 42 | return new VBArray(IEBinary_getBytesAt(data, iOffset + dataOffset, iLength)).toArray(); 43 | } 44 | } 45 | 46 | this.getLength = function() { 47 | return dataLength; 48 | } 49 | 50 | this.getSByteAt = function(iOffset) { 51 | var iByte = this.getByteAt(iOffset); 52 | if (iByte > 127) 53 | return iByte - 256; 54 | else 55 | return iByte; 56 | } 57 | 58 | this.getShortAt = function(iOffset, bBigEndian) { 59 | var iShort = bBigEndian ? 60 | (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1) 61 | : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset) 62 | if (iShort < 0) 63 | iShort += 65536; 64 | return iShort; 65 | } 66 | this.getSShortAt = function(iOffset, bBigEndian) { 67 | var iUShort = this.getShortAt(iOffset, bBigEndian); 68 | if (iUShort > 32767) 69 | return iUShort - 65536; 70 | else 71 | return iUShort; 72 | } 73 | this.getLongAt = function(iOffset, bBigEndian) { 74 | var iByte1 = this.getByteAt(iOffset), 75 | iByte2 = this.getByteAt(iOffset + 1), 76 | iByte3 = this.getByteAt(iOffset + 2), 77 | iByte4 = this.getByteAt(iOffset + 3); 78 | 79 | var iLong = bBigEndian ? 80 | (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4 81 | : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1; 82 | if (iLong < 0) 83 | iLong += 4294967296; 84 | return iLong; 85 | } 86 | this.getSLongAt = function(iOffset, bBigEndian) { 87 | var iULong = this.getLongAt(iOffset, bBigEndian); 88 | if (iULong > 2147483647) 89 | return iULong - 4294967296; 90 | else 91 | return iULong; 92 | } 93 | 94 | this.getStringAt = function(iOffset, iLength) { 95 | var aStr = []; 96 | 97 | var aBytes = this.getBytesAt(iOffset, iLength); 98 | for (var j = 0; j < iLength; j++) { 99 | aStr[j] = String.fromCharCode(aBytes[j]); 100 | } 101 | return aStr.join(""); 102 | } 103 | 104 | this.getCharAt = function(iOffset) { 105 | return String.fromCharCode(this.getByteAt(iOffset)); 106 | } 107 | this.toBase64 = function() { 108 | return window.btoa(data); 109 | } 110 | this.fromBase64 = function(strBase64) { 111 | data = window.atob(strBase64); 112 | } 113 | } 114 | 115 | 116 | var BinaryAjax = (function() { 117 | 118 | function createRequest() { 119 | var oHTTP = null; 120 | if (window.ActiveXObject) { 121 | oHTTP = new ActiveXObject("Microsoft.XMLHTTP"); 122 | } else if (window.XMLHttpRequest) { 123 | oHTTP = new XMLHttpRequest(); 124 | } 125 | return oHTTP; 126 | } 127 | 128 | function getHead(strURL, fncCallback, fncError) { 129 | var oHTTP = createRequest(); 130 | if (oHTTP) { 131 | if (fncCallback) { 132 | if (typeof(oHTTP.onload) != "undefined") { 133 | oHTTP.onload = function() { 134 | if (oHTTP.status == "200") { 135 | fncCallback(this); 136 | } else { 137 | if (fncError) 138 | fncError(); 139 | } 140 | oHTTP = null; 141 | }; 142 | } else { 143 | oHTTP.onreadystatechange = function() { 144 | if (oHTTP.readyState == 4) { 145 | if (oHTTP.status == "200") { 146 | fncCallback(this); 147 | } else { 148 | if (fncError) 149 | fncError(); 150 | } 151 | oHTTP = null; 152 | } 153 | }; 154 | } 155 | } 156 | oHTTP.open("HEAD", strURL, true); 157 | oHTTP.send(null); 158 | } else { 159 | if (fncError) 160 | fncError(); 161 | } 162 | } 163 | 164 | function sendRequest(strURL, fncCallback, fncError, aRange, bAcceptRanges, iFileSize) { 165 | var oHTTP = createRequest(); 166 | if (oHTTP) { 167 | 168 | var iDataOffset = 0; 169 | if (aRange && !bAcceptRanges) { 170 | iDataOffset = aRange[0]; 171 | } 172 | var iDataLen = 0; 173 | if (aRange) { 174 | iDataLen = aRange[1] - aRange[0] + 1; 175 | } 176 | 177 | if (fncCallback) { 178 | if (typeof(oHTTP.onload) != "undefined") { 179 | oHTTP.onload = function() { 180 | if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") { 181 | oHTTP.binaryResponse = new BinaryFile(oHTTP.responseText, iDataOffset, iDataLen); 182 | oHTTP.fileSize = iFileSize || oHTTP.getResponseHeader("Content-Length"); 183 | fncCallback(oHTTP); 184 | } else { 185 | if (fncError) 186 | fncError(); 187 | } 188 | oHTTP = null; 189 | }; 190 | } else { 191 | oHTTP.onreadystatechange = function() { 192 | if (oHTTP.readyState == 4) { 193 | if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") { 194 | // IE6 craps if we try to extend the XHR object 195 | var oRes = { 196 | status: oHTTP.status, 197 | // IE needs responseBody, Chrome/Safari needs responseText 198 | binaryResponse: new BinaryFile( 199 | typeof oHTTP.responseBody == "unknown" ? oHTTP.responseBody : oHTTP.responseText, iDataOffset, iDataLen 200 | ), 201 | fileSize: iFileSize || oHTTP.getResponseHeader("Content-Length") 202 | }; 203 | fncCallback(oRes); 204 | } else { 205 | if (fncError) 206 | fncError(); 207 | } 208 | oHTTP = null; 209 | } 210 | }; 211 | } 212 | } 213 | oHTTP.open("GET", strURL, true); 214 | 215 | if (oHTTP.overrideMimeType) 216 | oHTTP.overrideMimeType('text/plain; charset=x-user-defined'); 217 | 218 | if (aRange && bAcceptRanges) { 219 | oHTTP.setRequestHeader("Range", "bytes=" + aRange[0] + "-" + aRange[1]); 220 | } 221 | 222 | oHTTP.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 1970 00:00:00 GMT"); 223 | 224 | oHTTP.send(null); 225 | } else { 226 | if (fncError) 227 | fncError(); 228 | } 229 | } 230 | 231 | return function(strURL, fncCallback, fncError, aRange) { 232 | 233 | if (aRange) { 234 | getHead( 235 | strURL, 236 | function(oHTTP) { 237 | var iLength = parseInt(oHTTP.getResponseHeader("Content-Length"), 10); 238 | var strAcceptRanges = oHTTP.getResponseHeader("Accept-Ranges"); 239 | 240 | var iStart, iEnd; 241 | iStart = aRange[0]; 242 | if (aRange[0] < 0) 243 | iStart += iLength; 244 | iEnd = iStart + aRange[1] - 1; 245 | 246 | sendRequest(strURL, fncCallback, fncError, [iStart, iEnd], (strAcceptRanges == "bytes"), iLength); 247 | } 248 | ); 249 | 250 | } else { 251 | sendRequest(strURL, fncCallback, fncError); 252 | } 253 | } 254 | 255 | }()); 256 | 257 | /* 258 | document.write( 259 | "\r\n" 267 | ); 268 | */ 269 | 270 | document.write( 271 | "\r\n" 287 | ); -------------------------------------------------------------------------------- /static/js/lib/jquery.canvasResize.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery canvasResize plugin 3 | * 4 | * Version: 1.2.0 5 | * Date (d/m/y): 02/10/12 6 | * Update (d/m/y): 14/05/13 7 | * Original author: @gokercebeci 8 | * Licensed under the MIT license 9 | * - This plugin working with jquery.exif.js 10 | * (It's under the MPL License http://www.nihilogic.dk/licenses/mpl-license.txt) 11 | * Demo: http://ios6-image-resize.gokercebeci.com/ 12 | * 13 | * - I fixed iOS6 Safari's image file rendering issue for large size image (over mega-pixel) 14 | * using few functions from https://github.com/stomita/ios-imagefile-megapixel 15 | * (detectSubsampling, ) 16 | * And fixed orientation issue by edited http://blog.nihilogic.dk/2008/05/jquery-exif-data-plugin.html 17 | * Thanks, Shinichi Tomita and Jacob Seidelin 18 | */ 19 | 20 | (function($) { 21 | var pluginName = 'canvasResize', 22 | methods = { 23 | newsize: function(w, h, W, H, C) { 24 | var c = C ? 'h' : ''; 25 | if ((W && w > W) || (H && h > H)) { 26 | var r = w / h; 27 | if ((r >= 1 || H === 0) && W && !C) { 28 | w = W; 29 | h = (W / r) >> 0; 30 | } else if (C && r <= (W / H)) { 31 | w = W; 32 | h = (W / r) >> 0; 33 | c = 'w'; 34 | } else { 35 | w = (H * r) >> 0; 36 | h = H; 37 | } 38 | } 39 | return { 40 | 'width': w, 41 | 'height': h, 42 | 'cropped': c 43 | }; 44 | }, 45 | dataURLtoBlob: function(data) { 46 | var mimeString = data.split(',')[0].split(':')[1].split(';')[0]; 47 | var byteString = atob(data.split(',')[1]); 48 | var ab = new ArrayBuffer(byteString.length); 49 | var ia = new Uint8Array(ab); 50 | for (var i = 0; i < byteString.length; i++) { 51 | ia[i] = byteString.charCodeAt(i); 52 | } 53 | var bb = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder); 54 | if (bb) { 55 | // console.log('BlobBuilder'); 56 | bb = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder)(); 57 | bb.append(ab); 58 | return bb.getBlob(mimeString); 59 | } else { 60 | // console.log('Blob'); 61 | bb = new Blob([ab], { 62 | 'type': (mimeString) 63 | }); 64 | return bb; 65 | } 66 | }, 67 | /** 68 | * Detect subsampling in loaded image. 69 | * In iOS, larger images than 2M pixels may be subsampled in rendering. 70 | */ 71 | detectSubsampling: function(img) { 72 | var iw = img.width, ih = img.height; 73 | if (iw * ih > 1048576) { // subsampling may happen over megapixel image 74 | var canvas = document.createElement('canvas'); 75 | canvas.width = canvas.height = 1; 76 | var ctx = canvas.getContext('2d'); 77 | ctx.drawImage(img, -iw + 1, 0); 78 | // subsampled image becomes half smaller in rendering size. 79 | // check alpha channel value to confirm image is covering edge pixel or not. 80 | // if alpha value is 0 image is not covering, hence subsampled. 81 | return ctx.getImageData(0, 0, 1, 1).data[3] === 0; 82 | } else { 83 | return false; 84 | } 85 | }, 86 | /** 87 | * Update the orientation according to the specified rotation angle 88 | */ 89 | rotate: function(orientation, angle) { 90 | var o = { 91 | // nothing 92 | 1: {90: 6, 180: 3, 270: 8}, 93 | // horizontal flip 94 | 2: {90: 7, 180: 4, 270: 5}, 95 | // 180 rotate left 96 | 3: {90: 8, 180: 1, 270: 6}, 97 | // vertical flip 98 | 4: {90: 5, 180: 2, 270: 7}, 99 | // vertical flip + 90 rotate right 100 | 5: {90: 2, 180: 7, 270: 4}, 101 | // 90 rotate right 102 | 6: {90: 3, 180: 8, 270: 1}, 103 | // horizontal flip + 90 rotate right 104 | 7: {90: 4, 180: 5, 270: 2}, 105 | // 90 rotate left 106 | 8: {90: 1, 180: 6, 270: 3} 107 | }; 108 | return o[orientation][angle] ? o[orientation][angle] : orientation; 109 | }, 110 | /** 111 | * Transform canvas coordination according to specified frame size and orientation 112 | * Orientation value is from EXIF tag 113 | */ 114 | transformCoordinate: function(canvas, width, height, orientation) { 115 | //console.log(width, height); 116 | switch (orientation) { 117 | case 5: 118 | case 6: 119 | case 7: 120 | case 8: 121 | canvas.width = height; 122 | canvas.height = width; 123 | break; 124 | default: 125 | canvas.width = width; 126 | canvas.height = height; 127 | } 128 | var ctx = canvas.getContext('2d'); 129 | switch (orientation) { 130 | case 1: 131 | // nothing 132 | break; 133 | case 2: 134 | // horizontal flip 135 | ctx.translate(width, 0); 136 | ctx.scale(-1, 1); 137 | break; 138 | case 3: 139 | // 180 rotate left 140 | ctx.translate(width, height); 141 | ctx.rotate(Math.PI); 142 | break; 143 | case 4: 144 | // vertical flip 145 | ctx.translate(0, height); 146 | ctx.scale(1, -1); 147 | break; 148 | case 5: 149 | // vertical flip + 90 rotate right 150 | ctx.rotate(0.5 * Math.PI); 151 | ctx.scale(1, -1); 152 | break; 153 | case 6: 154 | // 90 rotate right 155 | ctx.rotate(0.5 * Math.PI); 156 | ctx.translate(0, -height); 157 | break; 158 | case 7: 159 | // horizontal flip + 90 rotate right 160 | ctx.rotate(0.5 * Math.PI); 161 | ctx.translate(width, -height); 162 | ctx.scale(-1, 1); 163 | break; 164 | case 8: 165 | // 90 rotate left 166 | ctx.rotate(-0.5 * Math.PI); 167 | ctx.translate(-width, 0); 168 | break; 169 | default: 170 | break; 171 | } 172 | }, 173 | /** 174 | * Detecting vertical squash in loaded image. 175 | * Fixes a bug which squash image vertically while drawing into canvas for some images. 176 | */ 177 | detectVerticalSquash: function(img, iw, ih) { 178 | var canvas = document.createElement('canvas'); 179 | canvas.width = 1; 180 | canvas.height = ih; 181 | var ctx = canvas.getContext('2d'); 182 | ctx.drawImage(img, 0, 0); 183 | var data = ctx.getImageData(0, 0, 1, ih).data; 184 | // search image edge pixel position in case it is squashed vertically. 185 | var sy = 0; 186 | var ey = ih; 187 | var py = ih; 188 | while (py > sy) { 189 | var alpha = data[(py - 1) * 4 + 3]; 190 | if (alpha === 0) { 191 | ey = py; 192 | } else { 193 | sy = py; 194 | } 195 | py = (ey + sy) >> 1; 196 | } 197 | var ratio = py / ih; 198 | return ratio === 0 ? 1 : ratio; 199 | }, 200 | callback: function(d) { 201 | return d; 202 | } 203 | }, 204 | defaults = { 205 | width: 300, 206 | height: 0, 207 | crop: false, 208 | quality: 80, 209 | 'callback': methods.callback 210 | }; 211 | function Plugin(file, options) { 212 | this.file = file; 213 | this.options = $.extend({}, defaults, options); 214 | this._defaults = defaults; 215 | this._name = pluginName; 216 | this.init(); 217 | } 218 | Plugin.prototype = { 219 | init: function() { 220 | //this.options.init(this); 221 | var $this = this; 222 | var file = this.file; 223 | 224 | var reader = new FileReader(); 225 | reader.onloadend = function(e) { 226 | var dataURL = e.target.result; 227 | var img = new Image(); 228 | img.onload = function(e) { 229 | // Read Orientation Data in EXIF 230 | $(img).exifLoadFromDataURL(function() { 231 | var orientation = $(img).exif('Orientation')[0] || 1; 232 | orientation = methods.rotate(orientation, $this.options.rotate); 233 | 234 | // CW or CCW ? replace width and height 235 | var size = (orientation >= 5 && orientation <= 8) 236 | ? methods.newsize(img.height, img.width, $this.options.width, $this.options.height, $this.options.crop) 237 | : methods.newsize(img.width, img.height, $this.options.width, $this.options.height, $this.options.crop); 238 | 239 | var iw = img.width, ih = img.height; 240 | var width = size.width, height = size.height; 241 | 242 | //console.log(iw, ih, size.width, size.height, orientation); 243 | 244 | var canvas = document.createElement("canvas"); 245 | var ctx = canvas.getContext("2d"); 246 | ctx.save(); 247 | methods.transformCoordinate(canvas, width, height, orientation); 248 | 249 | // over image size 250 | if (methods.detectSubsampling(img)) { 251 | iw /= 2; 252 | ih /= 2; 253 | } 254 | var d = 1024; // size of tiling canvas 255 | var tmpCanvas = document.createElement('canvas'); 256 | tmpCanvas.width = tmpCanvas.height = d; 257 | var tmpCtx = tmpCanvas.getContext('2d'); 258 | var vertSquashRatio = methods.detectVerticalSquash(img, iw, ih); 259 | var sy = 0; 260 | while (sy < ih) { 261 | var sh = sy + d > ih ? ih - sy : d; 262 | var sx = 0; 263 | while (sx < iw) { 264 | var sw = sx + d > iw ? iw - sx : d; 265 | tmpCtx.clearRect(0, 0, d, d); 266 | tmpCtx.drawImage(img, -sx, -sy); 267 | var dx = Math.floor(sx * width / iw); 268 | var dw = Math.ceil(sw * width / iw); 269 | var dy = Math.floor(sy * height / ih / vertSquashRatio); 270 | var dh = Math.ceil(sh * height / ih / vertSquashRatio); 271 | ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh); 272 | sx += d; 273 | } 274 | sy += d; 275 | } 276 | ctx.restore(); 277 | tmpCanvas = tmpCtx = null; 278 | 279 | // if cropped or rotated width and height data replacing issue 280 | var newcanvas = document.createElement('canvas'); 281 | newcanvas.width = size.cropped === 'h' ? height : width; 282 | newcanvas.height = size.cropped === 'w' ? width : height; 283 | var x = size.cropped === 'h' ? (height - width) * .5 : 0; 284 | var y = size.cropped === 'w' ? (width - height) * .5 : 0; 285 | newctx = newcanvas.getContext('2d'); 286 | newctx.drawImage(canvas, x, y, width, height); 287 | 288 | if (file.type === "image/png") { 289 | var data = newcanvas.toDataURL(file.type); 290 | } else { 291 | var data = newcanvas.toDataURL("image/jpeg", ($this.options.quality * .01)); 292 | } 293 | 294 | // CALLBACK 295 | $this.options.callback(data, width, height); 296 | 297 | }); 298 | }; 299 | img.src = dataURL; 300 | // ===================================================== 301 | }; 302 | reader.readAsDataURL(file); 303 | 304 | } 305 | }; 306 | $[pluginName] = function(file, options) { 307 | if (typeof file === 'string') 308 | return methods[file](options); 309 | else 310 | new Plugin(file, options); 311 | }; 312 | 313 | })(jQuery); -------------------------------------------------------------------------------- /static/js/lib/exif.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Javascript EXIF Reader 0.1.6 3 | * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ 4 | * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt] 5 | */ 6 | 7 | 8 | var EXIF = (function() { 9 | 10 | var debug = false; 11 | 12 | var ExifTags = { 13 | 14 | // version tags 15 | 0x9000: "ExifVersion", // EXIF version 16 | 0xA000: "FlashpixVersion", // Flashpix format version 17 | 18 | // colorspace tags 19 | 0xA001: "ColorSpace", // Color space information tag 20 | 21 | // image configuration 22 | 0xA002: "PixelXDimension", // Valid width of meaningful image 23 | 0xA003: "PixelYDimension", // Valid height of meaningful image 24 | 0x9101: "ComponentsConfiguration", // Information about channels 25 | 0x9102: "CompressedBitsPerPixel", // Compressed bits per pixel 26 | 27 | // user information 28 | 0x927C: "MakerNote", // Any desired information written by the manufacturer 29 | 0x9286: "UserComment", // Comments by user 30 | 31 | // related file 32 | 0xA004: "RelatedSoundFile", // Name of related sound file 33 | 34 | // date and time 35 | 0x9003: "DateTimeOriginal", // Date and time when the original image was generated 36 | 0x9004: "DateTimeDigitized", // Date and time when the image was stored digitally 37 | 0x9290: "SubsecTime", // Fractions of seconds for DateTime 38 | 0x9291: "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal 39 | 0x9292: "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized 40 | 41 | // picture-taking conditions 42 | 0x829A: "ExposureTime", // Exposure time (in seconds) 43 | 0x829D: "FNumber", // F number 44 | 0x8822: "ExposureProgram", // Exposure program 45 | 0x8824: "SpectralSensitivity", // Spectral sensitivity 46 | 0x8827: "ISOSpeedRatings", // ISO speed rating 47 | 0x8828: "OECF", // Optoelectric conversion factor 48 | 0x9201: "ShutterSpeedValue", // Shutter speed 49 | 0x9202: "ApertureValue", // Lens aperture 50 | 0x9203: "BrightnessValue", // Value of brightness 51 | 0x9204: "ExposureBias", // Exposure bias 52 | 0x9205: "MaxApertureValue", // Smallest F number of lens 53 | 0x9206: "SubjectDistance", // Distance to subject in meters 54 | 0x9207: "MeteringMode", // Metering mode 55 | 0x9208: "LightSource", // Kind of light source 56 | 0x9209: "Flash", // Flash status 57 | 0x9214: "SubjectArea", // Location and area of main subject 58 | 0x920A: "FocalLength", // Focal length of the lens in mm 59 | 0xA20B: "FlashEnergy", // Strobe energy in BCPS 60 | 0xA20C: "SpatialFrequencyResponse", // 61 | 0xA20E: "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit 62 | 0xA20F: "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit 63 | 0xA210: "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution 64 | 0xA214: "SubjectLocation", // Location of subject in image 65 | 0xA215: "ExposureIndex", // Exposure index selected on camera 66 | 0xA217: "SensingMethod", // Image sensor type 67 | 0xA300: "FileSource", // Image source (3 == DSC) 68 | 0xA301: "SceneType", // Scene type (1 == directly photographed) 69 | 0xA302: "CFAPattern", // Color filter array geometric pattern 70 | 0xA401: "CustomRendered", // Special processing 71 | 0xA402: "ExposureMode", // Exposure mode 72 | 0xA403: "WhiteBalance", // 1 = auto white balance, 2 = manual 73 | 0xA404: "DigitalZoomRation", // Digital zoom ratio 74 | 0xA405: "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm) 75 | 0xA406: "SceneCaptureType", // Type of scene 76 | 0xA407: "GainControl", // Degree of overall image gain adjustment 77 | 0xA408: "Contrast", // Direction of contrast processing applied by camera 78 | 0xA409: "Saturation", // Direction of saturation processing applied by camera 79 | 0xA40A: "Sharpness", // Direction of sharpness processing applied by camera 80 | 0xA40B: "DeviceSettingDescription", // 81 | 0xA40C: "SubjectDistanceRange", // Distance to subject 82 | 83 | // other tags 84 | 0xA005: "InteroperabilityIFDPointer", 85 | 0xA420: "ImageUniqueID" // Identifier assigned uniquely to each image 86 | }; 87 | 88 | var TiffTags = { 89 | 0x0100: "ImageWidth", 90 | 0x0101: "ImageHeight", 91 | 0x8769: "ExifIFDPointer", 92 | 0x8825: "GPSInfoIFDPointer", 93 | 0xA005: "InteroperabilityIFDPointer", 94 | 0x0102: "BitsPerSample", 95 | 0x0103: "Compression", 96 | 0x0106: "PhotometricInterpretation", 97 | 0x0112: "Orientation", 98 | 0x0115: "SamplesPerPixel", 99 | 0x011C: "PlanarConfiguration", 100 | 0x0212: "YCbCrSubSampling", 101 | 0x0213: "YCbCrPositioning", 102 | 0x011A: "XResolution", 103 | 0x011B: "YResolution", 104 | 0x0128: "ResolutionUnit", 105 | 0x0111: "StripOffsets", 106 | 0x0116: "RowsPerStrip", 107 | 0x0117: "StripByteCounts", 108 | 0x0201: "JPEGInterchangeFormat", 109 | 0x0202: "JPEGInterchangeFormatLength", 110 | 0x012D: "TransferFunction", 111 | 0x013E: "WhitePoint", 112 | 0x013F: "PrimaryChromaticities", 113 | 0x0211: "YCbCrCoefficients", 114 | 0x0214: "ReferenceBlackWhite", 115 | 0x0132: "DateTime", 116 | 0x010E: "ImageDescription", 117 | 0x010F: "Make", 118 | 0x0110: "Model", 119 | 0x0131: "Software", 120 | 0x013B: "Artist", 121 | 0x8298: "Copyright" 122 | }; 123 | 124 | var GPSTags = { 125 | 0x0000: "GPSVersionID", 126 | 0x0001: "GPSLatitudeRef", 127 | 0x0002: "GPSLatitude", 128 | 0x0003: "GPSLongitudeRef", 129 | 0x0004: "GPSLongitude", 130 | 0x0005: "GPSAltitudeRef", 131 | 0x0006: "GPSAltitude", 132 | 0x0007: "GPSTimeStamp", 133 | 0x0008: "GPSSatellites", 134 | 0x0009: "GPSStatus", 135 | 0x000A: "GPSMeasureMode", 136 | 0x000B: "GPSDOP", 137 | 0x000C: "GPSSpeedRef", 138 | 0x000D: "GPSSpeed", 139 | 0x000E: "GPSTrackRef", 140 | 0x000F: "GPSTrack", 141 | 0x0010: "GPSImgDirectionRef", 142 | 0x0011: "GPSImgDirection", 143 | 0x0012: "GPSMapDatum", 144 | 0x0013: "GPSDestLatitudeRef", 145 | 0x0014: "GPSDestLatitude", 146 | 0x0015: "GPSDestLongitudeRef", 147 | 0x0016: "GPSDestLongitude", 148 | 0x0017: "GPSDestBearingRef", 149 | 0x0018: "GPSDestBearing", 150 | 0x0019: "GPSDestDistanceRef", 151 | 0x001A: "GPSDestDistance", 152 | 0x001B: "GPSProcessingMethod", 153 | 0x001C: "GPSAreaInformation", 154 | 0x001D: "GPSDateStamp", 155 | 0x001E: "GPSDifferential" 156 | }; 157 | 158 | var StringValues = { 159 | ExposureProgram: { 160 | 0: "Not defined", 161 | 1: "Manual", 162 | 2: "Normal program", 163 | 3: "Aperture priority", 164 | 4: "Shutter priority", 165 | 5: "Creative program", 166 | 6: "Action program", 167 | 7: "Portrait mode", 168 | 8: "Landscape mode" 169 | }, 170 | MeteringMode: { 171 | 0: "Unknown", 172 | 1: "Average", 173 | 2: "CenterWeightedAverage", 174 | 3: "Spot", 175 | 4: "MultiSpot", 176 | 5: "Pattern", 177 | 6: "Partial", 178 | 255: "Other" 179 | }, 180 | LightSource: { 181 | 0: "Unknown", 182 | 1: "Daylight", 183 | 2: "Fluorescent", 184 | 3: "Tungsten (incandescent light)", 185 | 4: "Flash", 186 | 9: "Fine weather", 187 | 10: "Cloudy weather", 188 | 11: "Shade", 189 | 12: "Daylight fluorescent (D 5700 - 7100K)", 190 | 13: "Day white fluorescent (N 4600 - 5400K)", 191 | 14: "Cool white fluorescent (W 3900 - 4500K)", 192 | 15: "White fluorescent (WW 3200 - 3700K)", 193 | 17: "Standard light A", 194 | 18: "Standard light B", 195 | 19: "Standard light C", 196 | 20: "D55", 197 | 21: "D65", 198 | 22: "D75", 199 | 23: "D50", 200 | 24: "ISO studio tungsten", 201 | 255: "Other" 202 | }, 203 | Flash: { 204 | 0x0000: "Flash did not fire", 205 | 0x0001: "Flash fired", 206 | 0x0005: "Strobe return light not detected", 207 | 0x0007: "Strobe return light detected", 208 | 0x0009: "Flash fired, compulsory flash mode", 209 | 0x000D: "Flash fired, compulsory flash mode, return light not detected", 210 | 0x000F: "Flash fired, compulsory flash mode, return light detected", 211 | 0x0010: "Flash did not fire, compulsory flash mode", 212 | 0x0018: "Flash did not fire, auto mode", 213 | 0x0019: "Flash fired, auto mode", 214 | 0x001D: "Flash fired, auto mode, return light not detected", 215 | 0x001F: "Flash fired, auto mode, return light detected", 216 | 0x0020: "No flash function", 217 | 0x0041: "Flash fired, red-eye reduction mode", 218 | 0x0045: "Flash fired, red-eye reduction mode, return light not detected", 219 | 0x0047: "Flash fired, red-eye reduction mode, return light detected", 220 | 0x0049: "Flash fired, compulsory flash mode, red-eye reduction mode", 221 | 0x004D: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", 222 | 0x004F: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", 223 | 0x0059: "Flash fired, auto mode, red-eye reduction mode", 224 | 0x005D: "Flash fired, auto mode, return light not detected, red-eye reduction mode", 225 | 0x005F: "Flash fired, auto mode, return light detected, red-eye reduction mode" 226 | }, 227 | SensingMethod: { 228 | 1: "Not defined", 229 | 2: "One-chip color area sensor", 230 | 3: "Two-chip color area sensor", 231 | 4: "Three-chip color area sensor", 232 | 5: "Color sequential area sensor", 233 | 7: "Trilinear sensor", 234 | 8: "Color sequential linear sensor" 235 | }, 236 | SceneCaptureType: { 237 | 0: "Standard", 238 | 1: "Landscape", 239 | 2: "Portrait", 240 | 3: "Night scene" 241 | }, 242 | SceneType: { 243 | 1: "Directly photographed" 244 | }, 245 | CustomRendered: { 246 | 0: "Normal process", 247 | 1: "Custom process" 248 | }, 249 | WhiteBalance: { 250 | 0: "Auto white balance", 251 | 1: "Manual white balance" 252 | }, 253 | GainControl: { 254 | 0: "None", 255 | 1: "Low gain up", 256 | 2: "High gain up", 257 | 3: "Low gain down", 258 | 4: "High gain down" 259 | }, 260 | Contrast: { 261 | 0: "Normal", 262 | 1: "Soft", 263 | 2: "Hard" 264 | }, 265 | Saturation: { 266 | 0: "Normal", 267 | 1: "Low saturation", 268 | 2: "High saturation" 269 | }, 270 | Sharpness: { 271 | 0: "Normal", 272 | 1: "Soft", 273 | 2: "Hard" 274 | }, 275 | SubjectDistanceRange: { 276 | 0: "Unknown", 277 | 1: "Macro", 278 | 2: "Close view", 279 | 3: "Distant view" 280 | }, 281 | FileSource: { 282 | 3: "DSC" 283 | }, 284 | Components: { 285 | 0: "", 286 | 1: "Y", 287 | 2: "Cb", 288 | 3: "Cr", 289 | 4: "R", 290 | 5: "G", 291 | 6: "B" 292 | } 293 | }; 294 | 295 | function addEvent(element, event, handler) { 296 | if (element.addEventListener) { 297 | element.addEventListener(event, handler, false); 298 | } else if (element.attachEvent) { 299 | element.attachEvent("on" + event, handler); 300 | } 301 | } 302 | 303 | function imageHasData(img) { 304 | return !!(img.exifdata); 305 | } 306 | 307 | function getImageData(img, callback) { 308 | BinaryAjax(img.src, function(http) { 309 | var data = findEXIFinJPEG(http.binaryResponse); 310 | img.exifdata = data || {}; 311 | if (callback) { 312 | callback.call(img) 313 | } 314 | }); 315 | } 316 | 317 | function findEXIFinJPEG(file) { 318 | if (file.getByteAt(0) != 0xFF || file.getByteAt(1) != 0xD8) { 319 | return false; // not a valid jpeg 320 | } 321 | 322 | var offset = 2, 323 | length = file.getLength(), 324 | marker; 325 | 326 | while (offset < length) { 327 | if (file.getByteAt(offset) != 0xFF) { 328 | if (debug) 329 | console.log("Not a valid marker at offset " + offset + ", found: " + file.getByteAt(offset)); 330 | return false; // not a valid marker, something is wrong 331 | } 332 | 333 | marker = file.getByteAt(offset + 1); 334 | 335 | // we could implement handling for other markers here, 336 | // but we're only looking for 0xFFE1 for EXIF data 337 | 338 | if (marker == 22400) { 339 | if (debug) 340 | console.log("Found 0xFFE1 marker"); 341 | 342 | return readEXIFData(file, offset + 4, file.getShortAt(offset + 2, true) - 2); 343 | 344 | // offset += 2 + file.getShortAt(offset+2, true); 345 | 346 | } else if (marker == 225) { 347 | // 0xE1 = Application-specific 1 (for EXIF) 348 | if (debug) 349 | console.log("Found 0xFFE1 marker"); 350 | 351 | return readEXIFData(file, offset + 4, file.getShortAt(offset + 2, true) - 2); 352 | 353 | } else { 354 | offset += 2 + file.getShortAt(offset + 2, true); 355 | } 356 | 357 | } 358 | 359 | } 360 | 361 | 362 | function readTags(file, tiffStart, dirStart, strings, bigEnd) { 363 | var entries = file.getShortAt(dirStart, bigEnd), 364 | tags = {}, 365 | entryOffset, tag, 366 | i; 367 | 368 | for (i = 0; i < entries; i++) { 369 | entryOffset = dirStart + i * 12 + 2; 370 | tag = strings[file.getShortAt(entryOffset, bigEnd)]; 371 | if (!tag && debug) 372 | console.log("Unknown tag: " + file.getShortAt(entryOffset, bigEnd)); 373 | tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd); 374 | } 375 | return tags; 376 | } 377 | 378 | 379 | function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) { 380 | var type = file.getShortAt(entryOffset + 2, bigEnd), 381 | numValues = file.getLongAt(entryOffset + 4, bigEnd), 382 | valueOffset = file.getLongAt(entryOffset + 8, bigEnd) + tiffStart, 383 | offset, 384 | vals, val, n, 385 | numerator, denominator; 386 | 387 | switch (type) { 388 | case 1: // byte, 8-bit unsigned int 389 | case 7: // undefined, 8-bit byte, value depending on field 390 | if (numValues == 1) { 391 | return file.getByteAt(entryOffset + 8, bigEnd); 392 | } else { 393 | offset = numValues > 4 ? valueOffset : (entryOffset + 8); 394 | vals = []; 395 | for (n = 0; n < numValues; n++) { 396 | vals[n] = file.getByteAt(offset + n); 397 | } 398 | return vals; 399 | } 400 | 401 | case 2: // ascii, 8-bit byte 402 | offset = numValues > 4 ? valueOffset : (entryOffset + 8); 403 | return file.getStringAt(offset, numValues - 1); 404 | 405 | case 3: // short, 16 bit int 406 | if (numValues == 1) { 407 | return file.getShortAt(entryOffset + 8, bigEnd); 408 | } else { 409 | offset = numValues > 2 ? valueOffset : (entryOffset + 8); 410 | vals = []; 411 | for (n = 0; n < numValues; n++) { 412 | vals[n] = file.getShortAt(offset + 2 * n, bigEnd); 413 | } 414 | return vals; 415 | } 416 | 417 | case 4: // long, 32 bit int 418 | if (numValues == 1) { 419 | return file.getLongAt(entryOffset + 8, bigEnd); 420 | } else { 421 | vals = []; 422 | for (var n = 0; n < numValues; n++) { 423 | vals[n] = file.getLongAt(valueOffset + 4 * n, bigEnd); 424 | } 425 | return vals; 426 | } 427 | 428 | case 5: // rational = two long values, first is numerator, second is denominator 429 | if (numValues == 1) { 430 | numerator = file.getLongAt(valueOffset, bigEnd); 431 | denominator = file.getLongAt(valueOffset + 4, bigEnd); 432 | val = new Number(numerator / denominator); 433 | val.numerator = numerator; 434 | val.denominator = denominator; 435 | return val; 436 | } else { 437 | vals = []; 438 | for (n = 0; n < numValues; n++) { 439 | numerator = file.getLongAt(valueOffset + 8 * n, bigEnd); 440 | denominator = file.getLongAt(valueOffset + 4 + 8 * n, bigEnd); 441 | vals[n] = new Number(numerator / denominator); 442 | vals[n].numerator = numerator; 443 | vals[n].denominator = denominator; 444 | } 445 | return vals; 446 | } 447 | 448 | case 9: // slong, 32 bit signed int 449 | if (numValues == 1) { 450 | return file.getSLongAt(entryOffset + 8, bigEnd); 451 | } else { 452 | vals = []; 453 | for (n = 0; n < numValues; n++) { 454 | vals[n] = file.getSLongAt(valueOffset + 4 * n, bigEnd); 455 | } 456 | return vals; 457 | } 458 | 459 | case 10: // signed rational, two slongs, first is numerator, second is denominator 460 | if (numValues == 1) { 461 | return file.getSLongAt(valueOffset, bigEnd) / file.getSLongAt(valueOffset + 4, bigEnd); 462 | } else { 463 | vals = []; 464 | for (n = 0; n < numValues; n++) { 465 | vals[n] = file.getSLongAt(valueOffset + 8 * n, bigEnd) / file.getSLongAt(valueOffset + 4 + 8 * n, bigEnd); 466 | } 467 | return vals; 468 | } 469 | } 470 | } 471 | 472 | 473 | function readEXIFData(file, start) { 474 | if (file.getStringAt(start, 4) != "Exif") { 475 | if (debug) 476 | console.log("Not valid EXIF data! " + file.getStringAt(start, 4)); 477 | return false; 478 | } 479 | 480 | var bigEnd, 481 | tags, tag, 482 | exifData, gpsData, 483 | tiffOffset = start + 6; 484 | 485 | // test for TIFF validity and endianness 486 | if (file.getShortAt(tiffOffset) == 0x4949) { 487 | bigEnd = false; 488 | } else if (file.getShortAt(tiffOffset) == 0x4D4D) { 489 | bigEnd = true; 490 | } else { 491 | if (debug) 492 | console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)"); 493 | return false; 494 | } 495 | 496 | if (file.getShortAt(tiffOffset + 2, bigEnd) != 0x002A) { 497 | if (debug) 498 | console.log("Not valid TIFF data! (no 0x002A)"); 499 | return false; 500 | } 501 | 502 | if (file.getLongAt(tiffOffset + 4, bigEnd) != 0x00000008) { 503 | if (debug) 504 | console.log("Not valid TIFF data! (First offset not 8)", file.getShortAt(tiffOffset + 4, bigEnd)); 505 | return false; 506 | } 507 | 508 | tags = readTags(file, tiffOffset, tiffOffset + 8, TiffTags, bigEnd); 509 | 510 | if (tags.ExifIFDPointer) { 511 | exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd); 512 | for (tag in exifData) { 513 | switch (tag) { 514 | case "LightSource" : 515 | case "Flash" : 516 | case "MeteringMode" : 517 | case "ExposureProgram" : 518 | case "SensingMethod" : 519 | case "SceneCaptureType" : 520 | case "SceneType" : 521 | case "CustomRendered" : 522 | case "WhiteBalance" : 523 | case "GainControl" : 524 | case "Contrast" : 525 | case "Saturation" : 526 | case "Sharpness" : 527 | case "SubjectDistanceRange" : 528 | case "FileSource" : 529 | exifData[tag] = StringValues[tag][exifData[tag]]; 530 | break; 531 | 532 | case "ExifVersion" : 533 | case "FlashpixVersion" : 534 | exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]); 535 | break; 536 | 537 | case "ComponentsConfiguration" : 538 | exifData[tag] = 539 | StringValues.Components[exifData[tag][0]] 540 | + StringValues.Components[exifData[tag][1]] 541 | + StringValues.Components[exifData[tag][2]] 542 | + StringValues.Components[exifData[tag][3]]; 543 | break; 544 | } 545 | tags[tag] = exifData[tag]; 546 | } 547 | } 548 | 549 | if (tags.GPSInfoIFDPointer) { 550 | gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd); 551 | for (tag in gpsData) { 552 | switch (tag) { 553 | case "GPSVersionID" : 554 | gpsData[tag] = gpsData[tag][0] 555 | + "." + gpsData[tag][1] 556 | + "." + gpsData[tag][2] 557 | + "." + gpsData[tag][3]; 558 | break; 559 | } 560 | tags[tag] = gpsData[tag]; 561 | } 562 | } 563 | 564 | return tags; 565 | } 566 | 567 | 568 | function getData(img, callback) { 569 | if (!img.complete) 570 | return false; 571 | if (!imageHasData(img)) { 572 | getImageData(img, callback); 573 | } else { 574 | if (callback) { 575 | callback.call(img); 576 | } 577 | } 578 | return true; 579 | } 580 | 581 | function getTag(img, tag) { 582 | if (!imageHasData(img)) 583 | return; 584 | return img.exifdata[tag]; 585 | } 586 | 587 | function getAllTags(img) { 588 | if (!imageHasData(img)) 589 | return {}; 590 | var a, 591 | data = img.exifdata, 592 | tags = {}; 593 | for (a in data) { 594 | if (data.hasOwnProperty(a)) { 595 | tags[a] = data[a]; 596 | } 597 | } 598 | return tags; 599 | } 600 | 601 | function pretty(img) { 602 | if (!imageHasData(img)) 603 | return ""; 604 | var a, 605 | data = img.exifdata, 606 | strPretty = ""; 607 | for (a in data) { 608 | if (data.hasOwnProperty(a)) { 609 | if (typeof data[a] == "object") { 610 | if (data[a] instanceof Number) { 611 | strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a].denominator + "]\r\n"; 612 | } else { 613 | strPretty += a + " : [" + data[a].length + " values]\r\n"; 614 | } 615 | } else { 616 | strPretty += a + " : " + data[a] + "\r\n"; 617 | } 618 | } 619 | } 620 | return strPretty; 621 | } 622 | 623 | function readFromBinaryFile(file) { 624 | return findEXIFinJPEG(file); 625 | } 626 | 627 | 628 | return { 629 | readFromBinaryFile: readFromBinaryFile, 630 | pretty: pretty, 631 | getTag: getTag, 632 | getAllTags: getAllTags, 633 | getData: getData, 634 | Tags: ExifTags, 635 | TiffTags: TiffTags, 636 | GPSTags: GPSTags, 637 | StringValues: StringValues 638 | }; 639 | 640 | })(); -------------------------------------------------------------------------------- /static/js/lib/jquery.exif.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Javascript EXIF Reader - jQuery plugin 0.1.3 4 | * Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/ 5 | * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt] 6 | */ 7 | 8 | (function($) { 9 | 10 | 11 | var BinaryFile = function(strData, iDataOffset, iDataLength) { 12 | var data = strData; 13 | var dataOffset = iDataOffset || 0; 14 | var dataLength = 0; 15 | 16 | this.getRawData = function() { 17 | return data; 18 | }; 19 | 20 | if (typeof strData == "string") { 21 | dataLength = iDataLength || data.length; 22 | 23 | this.getByteAt = function(iOffset) { 24 | return data.charCodeAt(iOffset + dataOffset) & 0xFF; 25 | }; 26 | } else if (typeof strData == "unknown") { 27 | dataLength = iDataLength || IEBinary_getLength(data); 28 | 29 | this.getByteAt = function(iOffset) { 30 | return IEBinary_getByteAt(data, iOffset + dataOffset); 31 | }; 32 | } 33 | 34 | this.getLength = function() { 35 | return dataLength; 36 | }; 37 | 38 | this.getSByteAt = function(iOffset) { 39 | var iByte = this.getByteAt(iOffset); 40 | if (iByte > 127) 41 | return iByte - 256; 42 | else 43 | return iByte; 44 | }; 45 | 46 | this.getShortAt = function(iOffset, bBigEndian) { 47 | var iShort = bBigEndian ? 48 | (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1) 49 | : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset); 50 | if (iShort < 0) iShort += 65536; 51 | return iShort; 52 | }; 53 | this.getSShortAt = function(iOffset, bBigEndian) { 54 | var iUShort = this.getShortAt(iOffset, bBigEndian); 55 | if (iUShort > 32767) 56 | return iUShort - 65536; 57 | else 58 | return iUShort; 59 | }; 60 | this.getLongAt = function(iOffset, bBigEndian) { 61 | var iByte1 = this.getByteAt(iOffset), 62 | iByte2 = this.getByteAt(iOffset + 1), 63 | iByte3 = this.getByteAt(iOffset + 2), 64 | iByte4 = this.getByteAt(iOffset + 3); 65 | 66 | var iLong = bBigEndian ? 67 | (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4 68 | : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1; 69 | if (iLong < 0) iLong += 4294967296; 70 | return iLong; 71 | }; 72 | this.getSLongAt = function(iOffset, bBigEndian) { 73 | var iULong = this.getLongAt(iOffset, bBigEndian); 74 | if (iULong > 2147483647) 75 | return iULong - 4294967296; 76 | else 77 | return iULong; 78 | }; 79 | this.getStringAt = function(iOffset, iLength) { 80 | var aStr = []; 81 | for (var i=iOffset,j=0;i\r\n" 230 | + "Function IEBinary_getByteAt(strBinary, iOffset)\r\n" 231 | + " IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n" 232 | + "End Function\r\n" 233 | + "Function IEBinary_getLength(strBinary)\r\n" 234 | + " IEBinary_getLength = LenB(strBinary)\r\n" 235 | + "End Function\r\n" 236 | + "\r\n" 237 | ); 238 | 239 | 240 | var EXIF = {}; 241 | 242 | (function() { 243 | 244 | var bDebug = false; 245 | 246 | EXIF.Tags = { 247 | 248 | // version tags 249 | 0x9000 : "ExifVersion", // EXIF version 250 | 0xA000 : "FlashpixVersion", // Flashpix format version 251 | 252 | // colorspace tags 253 | 0xA001 : "ColorSpace", // Color space information tag 254 | 255 | // image configuration 256 | 0xA002 : "PixelXDimension", // Valid width of meaningful image 257 | 0xA003 : "PixelYDimension", // Valid height of meaningful image 258 | 0x9101 : "ComponentsConfiguration", // Information about channels 259 | 0x9102 : "CompressedBitsPerPixel", // Compressed bits per pixel 260 | 261 | // user information 262 | 0x927C : "MakerNote", // Any desired information written by the manufacturer 263 | 0x9286 : "UserComment", // Comments by user 264 | 265 | // related file 266 | 0xA004 : "RelatedSoundFile", // Name of related sound file 267 | 268 | // date and time 269 | 0x9003 : "DateTimeOriginal", // Date and time when the original image was generated 270 | 0x9004 : "DateTimeDigitized", // Date and time when the image was stored digitally 271 | 0x9290 : "SubsecTime", // Fractions of seconds for DateTime 272 | 0x9291 : "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal 273 | 0x9292 : "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized 274 | 275 | // picture-taking conditions 276 | 0x829A : "ExposureTime", // Exposure time (in seconds) 277 | 0x829D : "FNumber", // F number 278 | 0x8822 : "ExposureProgram", // Exposure program 279 | 0x8824 : "SpectralSensitivity", // Spectral sensitivity 280 | 0x8827 : "ISOSpeedRatings", // ISO speed rating 281 | 0x8828 : "OECF", // Optoelectric conversion factor 282 | 0x9201 : "ShutterSpeedValue", // Shutter speed 283 | 0x9202 : "ApertureValue", // Lens aperture 284 | 0x9203 : "BrightnessValue", // Value of brightness 285 | 0x9204 : "ExposureBias", // Exposure bias 286 | 0x9205 : "MaxApertureValue", // Smallest F number of lens 287 | 0x9206 : "SubjectDistance", // Distance to subject in meters 288 | 0x9207 : "MeteringMode", // Metering mode 289 | 0x9208 : "LightSource", // Kind of light source 290 | 0x9209 : "Flash", // Flash status 291 | 0x9214 : "SubjectArea", // Location and area of main subject 292 | 0x920A : "FocalLength", // Focal length of the lens in mm 293 | 0xA20B : "FlashEnergy", // Strobe energy in BCPS 294 | 0xA20C : "SpatialFrequencyResponse", // 295 | 0xA20E : "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit 296 | 0xA20F : "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit 297 | 0xA210 : "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution 298 | 0xA214 : "SubjectLocation", // Location of subject in image 299 | 0xA215 : "ExposureIndex", // Exposure index selected on camera 300 | 0xA217 : "SensingMethod", // Image sensor type 301 | 0xA300 : "FileSource", // Image source (3 == DSC) 302 | 0xA301 : "SceneType", // Scene type (1 == directly photographed) 303 | 0xA302 : "CFAPattern", // Color filter array geometric pattern 304 | 0xA401 : "CustomRendered", // Special processing 305 | 0xA402 : "ExposureMode", // Exposure mode 306 | 0xA403 : "WhiteBalance", // 1 = auto white balance, 2 = manual 307 | 0xA404 : "DigitalZoomRation", // Digital zoom ratio 308 | 0xA405 : "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm) 309 | 0xA406 : "SceneCaptureType", // Type of scene 310 | 0xA407 : "GainControl", // Degree of overall image gain adjustment 311 | 0xA408 : "Contrast", // Direction of contrast processing applied by camera 312 | 0xA409 : "Saturation", // Direction of saturation processing applied by camera 313 | 0xA40A : "Sharpness", // Direction of sharpness processing applied by camera 314 | 0xA40B : "DeviceSettingDescription", // 315 | 0xA40C : "SubjectDistanceRange", // Distance to subject 316 | 317 | // other tags 318 | 0xA005 : "InteroperabilityIFDPointer", 319 | 0xA420 : "ImageUniqueID" // Identifier assigned uniquely to each image 320 | }; 321 | 322 | EXIF.TiffTags = { 323 | 0x0100 : "ImageWidth", 324 | 0x0101 : "ImageHeight", 325 | 0x8769 : "ExifIFDPointer", 326 | 0x8825 : "GPSInfoIFDPointer", 327 | 0xA005 : "InteroperabilityIFDPointer", 328 | 0x0102 : "BitsPerSample", 329 | 0x0103 : "Compression", 330 | 0x0106 : "PhotometricInterpretation", 331 | 0x0112 : "Orientation", 332 | 0x0115 : "SamplesPerPixel", 333 | 0x011C : "PlanarConfiguration", 334 | 0x0212 : "YCbCrSubSampling", 335 | 0x0213 : "YCbCrPositioning", 336 | 0x011A : "XResolution", 337 | 0x011B : "YResolution", 338 | 0x0128 : "ResolutionUnit", 339 | 0x0111 : "StripOffsets", 340 | 0x0116 : "RowsPerStrip", 341 | 0x0117 : "StripByteCounts", 342 | 0x0201 : "JPEGInterchangeFormat", 343 | 0x0202 : "JPEGInterchangeFormatLength", 344 | 0x012D : "TransferFunction", 345 | 0x013E : "WhitePoint", 346 | 0x013F : "PrimaryChromaticities", 347 | 0x0211 : "YCbCrCoefficients", 348 | 0x0214 : "ReferenceBlackWhite", 349 | 0x0132 : "DateTime", 350 | 0x010E : "ImageDescription", 351 | 0x010F : "Make", 352 | 0x0110 : "Model", 353 | 0x0131 : "Software", 354 | 0x013B : "Artist", 355 | 0x8298 : "Copyright" 356 | }; 357 | 358 | EXIF.GPSTags = { 359 | 0x0000 : "GPSVersionID", 360 | 0x0001 : "GPSLatitudeRef", 361 | 0x0002 : "GPSLatitude", 362 | 0x0003 : "GPSLongitudeRef", 363 | 0x0004 : "GPSLongitude", 364 | 0x0005 : "GPSAltitudeRef", 365 | 0x0006 : "GPSAltitude", 366 | 0x0007 : "GPSTimeStamp", 367 | 0x0008 : "GPSSatellites", 368 | 0x0009 : "GPSStatus", 369 | 0x000A : "GPSMeasureMode", 370 | 0x000B : "GPSDOP", 371 | 0x000C : "GPSSpeedRef", 372 | 0x000D : "GPSSpeed", 373 | 0x000E : "GPSTrackRef", 374 | 0x000F : "GPSTrack", 375 | 0x0010 : "GPSImgDirectionRef", 376 | 0x0011 : "GPSImgDirection", 377 | 0x0012 : "GPSMapDatum", 378 | 0x0013 : "GPSDestLatitudeRef", 379 | 0x0014 : "GPSDestLatitude", 380 | 0x0015 : "GPSDestLongitudeRef", 381 | 0x0016 : "GPSDestLongitude", 382 | 0x0017 : "GPSDestBearingRef", 383 | 0x0018 : "GPSDestBearing", 384 | 0x0019 : "GPSDestDistanceRef", 385 | 0x001A : "GPSDestDistance", 386 | 0x001B : "GPSProcessingMethod", 387 | 0x001C : "GPSAreaInformation", 388 | 0x001D : "GPSDateStamp", 389 | 0x001E : "GPSDifferential" 390 | }; 391 | 392 | EXIF.StringValues = { 393 | ExposureProgram : { 394 | 0 : "Not defined", 395 | 1 : "Manual", 396 | 2 : "Normal program", 397 | 3 : "Aperture priority", 398 | 4 : "Shutter priority", 399 | 5 : "Creative program", 400 | 6 : "Action program", 401 | 7 : "Portrait mode", 402 | 8 : "Landscape mode" 403 | }, 404 | MeteringMode : { 405 | 0 : "Unknown", 406 | 1 : "Average", 407 | 2 : "CenterWeightedAverage", 408 | 3 : "Spot", 409 | 4 : "MultiSpot", 410 | 5 : "Pattern", 411 | 6 : "Partial", 412 | 255 : "Other" 413 | }, 414 | LightSource : { 415 | 0 : "Unknown", 416 | 1 : "Daylight", 417 | 2 : "Fluorescent", 418 | 3 : "Tungsten (incandescent light)", 419 | 4 : "Flash", 420 | 9 : "Fine weather", 421 | 10 : "Cloudy weather", 422 | 11 : "Shade", 423 | 12 : "Daylight fluorescent (D 5700 - 7100K)", 424 | 13 : "Day white fluorescent (N 4600 - 5400K)", 425 | 14 : "Cool white fluorescent (W 3900 - 4500K)", 426 | 15 : "White fluorescent (WW 3200 - 3700K)", 427 | 17 : "Standard light A", 428 | 18 : "Standard light B", 429 | 19 : "Standard light C", 430 | 20 : "D55", 431 | 21 : "D65", 432 | 22 : "D75", 433 | 23 : "D50", 434 | 24 : "ISO studio tungsten", 435 | 255 : "Other" 436 | }, 437 | Flash : { 438 | 0x0000 : "Flash did not fire", 439 | 0x0001 : "Flash fired", 440 | 0x0005 : "Strobe return light not detected", 441 | 0x0007 : "Strobe return light detected", 442 | 0x0009 : "Flash fired, compulsory flash mode", 443 | 0x000D : "Flash fired, compulsory flash mode, return light not detected", 444 | 0x000F : "Flash fired, compulsory flash mode, return light detected", 445 | 0x0010 : "Flash did not fire, compulsory flash mode", 446 | 0x0018 : "Flash did not fire, auto mode", 447 | 0x0019 : "Flash fired, auto mode", 448 | 0x001D : "Flash fired, auto mode, return light not detected", 449 | 0x001F : "Flash fired, auto mode, return light detected", 450 | 0x0020 : "No flash function", 451 | 0x0041 : "Flash fired, red-eye reduction mode", 452 | 0x0045 : "Flash fired, red-eye reduction mode, return light not detected", 453 | 0x0047 : "Flash fired, red-eye reduction mode, return light detected", 454 | 0x0049 : "Flash fired, compulsory flash mode, red-eye reduction mode", 455 | 0x004D : "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", 456 | 0x004F : "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", 457 | 0x0059 : "Flash fired, auto mode, red-eye reduction mode", 458 | 0x005D : "Flash fired, auto mode, return light not detected, red-eye reduction mode", 459 | 0x005F : "Flash fired, auto mode, return light detected, red-eye reduction mode" 460 | }, 461 | SensingMethod : { 462 | 1 : "Not defined", 463 | 2 : "One-chip color area sensor", 464 | 3 : "Two-chip color area sensor", 465 | 4 : "Three-chip color area sensor", 466 | 5 : "Color sequential area sensor", 467 | 7 : "Trilinear sensor", 468 | 8 : "Color sequential linear sensor" 469 | }, 470 | SceneCaptureType : { 471 | 0 : "Standard", 472 | 1 : "Landscape", 473 | 2 : "Portrait", 474 | 3 : "Night scene" 475 | }, 476 | SceneType : { 477 | 1 : "Directly photographed" 478 | }, 479 | CustomRendered : { 480 | 0 : "Normal process", 481 | 1 : "Custom process" 482 | }, 483 | WhiteBalance : { 484 | 0 : "Auto white balance", 485 | 1 : "Manual white balance" 486 | }, 487 | GainControl : { 488 | 0 : "None", 489 | 1 : "Low gain up", 490 | 2 : "High gain up", 491 | 3 : "Low gain down", 492 | 4 : "High gain down" 493 | }, 494 | Contrast : { 495 | 0 : "Normal", 496 | 1 : "Soft", 497 | 2 : "Hard" 498 | }, 499 | Saturation : { 500 | 0 : "Normal", 501 | 1 : "Low saturation", 502 | 2 : "High saturation" 503 | }, 504 | Sharpness : { 505 | 0 : "Normal", 506 | 1 : "Soft", 507 | 2 : "Hard" 508 | }, 509 | SubjectDistanceRange : { 510 | 0 : "Unknown", 511 | 1 : "Macro", 512 | 2 : "Close view", 513 | 3 : "Distant view" 514 | }, 515 | FileSource : { 516 | 3 : "DSC" 517 | }, 518 | 519 | Components : { 520 | 0 : "", 521 | 1 : "Y", 522 | 2 : "Cb", 523 | 3 : "Cr", 524 | 4 : "R", 525 | 5 : "G", 526 | 6 : "B" 527 | } 528 | }; 529 | 530 | function addEvent(oElement, strEvent, fncHandler) 531 | { 532 | if (oElement.addEventListener) { 533 | oElement.addEventListener(strEvent, fncHandler, false); 534 | } else if (oElement.attachEvent) { 535 | oElement.attachEvent("on" + strEvent, fncHandler); 536 | } 537 | } 538 | 539 | 540 | function imageHasData(oImg) 541 | { 542 | return !!(oImg.exifdata); 543 | } 544 | 545 | function getImageData(oImg, fncCallback) 546 | { 547 | BinaryAjax( 548 | oImg.src, 549 | function(oHTTP) { 550 | var oEXIF = findEXIFinJPEG(oHTTP.binaryResponse); 551 | oImg.exifdata = oEXIF || {}; 552 | if (fncCallback) fncCallback(); 553 | } 554 | ); 555 | } 556 | 557 | function findEXIFinJPEG(oFile) { 558 | var aMarkers = []; 559 | 560 | if (oFile.getByteAt(0) != 0xFF || oFile.getByteAt(1) != 0xD8) { 561 | return false; // not a valid jpeg 562 | } 563 | 564 | var iOffset = 2; 565 | var iLength = oFile.getLength(); 566 | while (iOffset < iLength) { 567 | if (oFile.getByteAt(iOffset) != 0xFF) { 568 | if (bDebug) console.log("Not a valid marker at offset " + iOffset + ", found: " + oFile.getByteAt(iOffset)); 569 | return false; // not a valid marker, something is wrong 570 | } 571 | 572 | var iMarker = oFile.getByteAt(iOffset+1); 573 | 574 | // we could implement handling for other markers here, 575 | // but we're only looking for 0xFFE1 for EXIF data 576 | 577 | if (iMarker == 22400) { 578 | if (bDebug) console.log("Found 0xFFE1 marker"); 579 | return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2); 580 | // iOffset += 2 + oFile.getShortAt(iOffset+2, true); 581 | // WTF? 582 | 583 | } else if (iMarker == 225) { 584 | // 0xE1 = Application-specific 1 (for EXIF) 585 | if (bDebug) console.log("Found 0xFFE1 marker"); 586 | return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset+2, true)-2); 587 | 588 | } else { 589 | iOffset += 2 + oFile.getShortAt(iOffset+2, true); 590 | } 591 | 592 | } 593 | 594 | } 595 | 596 | 597 | function readTags(oFile, iTIFFStart, iDirStart, oStrings, bBigEnd) 598 | { 599 | var iEntries = oFile.getShortAt(iDirStart, bBigEnd); 600 | var oTags = {}; 601 | for (var i=0;i 4 ? iValueOffset : (iEntryOffset + 8); 624 | var aVals = []; 625 | for (var n=0;n 4 ? iValueOffset : (iEntryOffset + 8); 634 | return oFile.getStringAt(iStringOffset, iNumValues-1); 635 | // break; 636 | 637 | case 3: // short, 16 bit int 638 | if (iNumValues == 1) { 639 | return oFile.getShortAt(iEntryOffset + 8, bBigEnd); 640 | } else { 641 | var iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8); 642 | var aVals = []; 643 | for (var n=0;n