├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── LICENSE.BSD ├── README.md ├── Website.png ├── addons.apt.packages ├── binding.gyp ├── cloudcv-bootstrap.jpg ├── cloudcv.js ├── cloudcv.json ├── debug.js ├── lib ├── config.js ├── download.js ├── logger.js └── swaggerSpec.js ├── package.json ├── public ├── bootstrap │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ └── bootstrap.min.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ └── js │ │ ├── bootstrap.js │ │ └── bootstrap.min.js ├── images │ ├── ar-demo-300x200.jpg │ ├── cloudcv-logo.png │ ├── contact-github.png │ ├── contact-gmail.png │ ├── contact-twitter.png │ ├── digital-ocean-logo-white.png │ ├── kid-128.jpg │ ├── kid.jpg │ ├── lena-128.png │ ├── lena-300x200.png │ ├── lena.png │ ├── mandril-300x200.png │ ├── mandrill-128.png │ ├── mandrill.png │ ├── sudoku-128.png │ ├── sudoku-300x200.png │ ├── sudoku-original.jpg │ └── sudoku.png ├── javascripts │ ├── README.md │ ├── bower.json │ ├── cloudcv.js │ ├── composer.json │ ├── form.jquery.json │ ├── ga.js │ ├── holder.js │ ├── jquery.form.js │ ├── package.json │ ├── socketed-transport.js │ ├── twitter-share.js │ └── upload.js └── stylesheets │ ├── github-forkme.css │ └── template.css ├── server.js ├── src ├── cloudcv.cpp ├── framework │ ├── Algorithm.cpp │ ├── Algorithm.hpp │ ├── AlgorithmExceptions.cpp │ ├── AlgorithmExceptions.hpp │ ├── AlgorithmInfo.cpp │ ├── AlgorithmInfo.hpp │ ├── Argument.cpp │ ├── Argument.hpp │ ├── CompilerSupport.hpp │ ├── ImageView.cpp │ ├── ImageView.hpp │ ├── Job.cpp │ ├── Job.hpp │ ├── Logger.hpp │ ├── ScopedTimer.hpp │ └── marshal │ │ ├── marshal.hpp │ │ └── opencv.hpp └── modules │ ├── HoughLines.cpp │ ├── HoughLines.hpp │ ├── IntegralImage.cpp │ └── IntegralImage.hpp ├── test ├── data │ ├── opencv-logo.jpg │ └── opencv-small.png ├── test_houghLines.js └── test_integralImage.js └── views ├── docs.jade ├── footer.jade ├── header.jade ├── includes └── disqus.jade ├── index.jade ├── layout.jade └── privacy.jade /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | log.txt 3 | npm-debug.log 4 | cloudcv-bootstrap.njsproj 5 | node_modules/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | sudo: false 5 | language: cpp 6 | addons: 7 | apt: 8 | sources: 9 | - ubuntu-toolchain-r-test 10 | packages: 11 | - g++-4.9 12 | env: 13 | matrix: 14 | - TRAVIS_NODE_VERSION="0.12" 15 | - TRAVIS_NODE_VERSION="iojs-3" 16 | - TRAVIS_NODE_VERSION="4" 17 | notifications: 18 | email: 19 | - ekhvedchenya@gmail.com 20 | cache: 21 | directories: 22 | - node_modules 23 | install: 24 | - rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION 25 | - if [[ $TRAVIS_NODE_VERSION == "0.8" ]]; then npm install npm@2 && node_modules/.bin/npm install npm; else npm install npm; fi 26 | - mv node_modules npm 27 | - npm/.bin/npm --version 28 | - if [[ $TRAVIS_OS_NAME == "linux" ]]; then export CXX=g++-4.9; fi 29 | - $CXX --version 30 | - npm/.bin/npm install 31 | - node_modules/.bin/node-gyp rebuild --directory test 32 | script: node_modules/.bin/tap --gc test/js/*-test.js 33 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Eugene Khvedchenya -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Ievgen Khvedchenia 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /LICENSE.BSD: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted provided that the following conditions are met: 3 | 4 | * Redistributions of source code must retain the above copyright 5 | notice, this list of conditions and the following disclaimer. 6 | * Redistributions in binary form must reproduce the above copyright 7 | notice, this list of conditions and the following disclaimer in the 8 | documentation and/or other materials provided with the distribution. 9 | * Neither the name of the nor the 10 | names of its contributors may be used to endorse or promote products 11 | derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudCV Boostrap 2 | 3 | ![CloudCV Boostrap](cloudcv-bootstrap.jpg) 4 | 5 | **A starter template for Node.js with OpenCV bindings**. 6 | 7 | [![Build Status](https://travis-ci.org/CloudCV/cloudcv-bootstrap.png?branch=master)](https://travis-ci.org/CloudCV/cloudcv-bootstrap) 8 | 9 | This project lets you to quickly prototype a REST API in a Node.js for a image processing service written in C++. 10 | 11 | ## Installation 12 | 13 | ```bash 14 | # Get the latest snapshot 15 | $ git clone —depth=1 --branch 0.0.1 https://github.com/CloudCV/cloudcv-bootstrap.git myproject 16 | $ cd myproject 17 | $ git remote rm origin 18 | 19 | # Install NPM dependencies 20 | $ npm install 21 | 22 | # Start local server 23 | $ npm start 24 | ``` 25 | 26 | After starting local webserver, navigate to http://localhost:3000 where you should see similar page: 27 | 28 | ![Website](Website.png) 29 | 30 | Here you can drop arbitrary image to extract dominant colors using REST-API. 31 | 32 | ## Quick start 33 | 34 | ```bash 35 | npm start 36 | ``` 37 | 38 | ## Unit testing 39 | 40 | ```bash 41 | npm test 42 | ``` 43 | 44 | ## Questions? 45 | 46 | Explore the series of blog posts: 47 | 1. https://computer-vision-talks.com/introducing-cloudcv-bootstrap/ 48 | 3. https://computer-vision-talks.com/marshalling-data-in-nodejs-c-modules/ 49 | 4. https://computer-vision-talks.com/how-to-debug-nodejs-addons-in-visual-studio/ 50 | 51 | CloudCV bootstrap is free software/open source, and is distributed under the [BSD license](http://opensource.org/licenses/BSD-3-Clause). It contains third-party code, see the included `third-party.txt` file for the license information on third-party code. 52 | 53 | CloudCV bootstrap is created and maintained by [Eugene Khvedchenya](http://computer-vision-talks.com) (Twitter: [@cvtalks](http://twitter.com/cvtalks)). Follow the official Twitter stream [@cvtalks](http://twitter.com/cvtalks) to get the frequent development updates. 54 | -------------------------------------------------------------------------------- /Website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/Website.png -------------------------------------------------------------------------------- /addons.apt.packages: -------------------------------------------------------------------------------- 1 | addons: 2 | apt: 3 | packages: 4 | - gcc-4.8 5 | - g++-4.8 -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 3 | 'target_defaults': { 4 | 'default_configuration': 'Release', 5 | 6 | }, 7 | 8 | 9 | 'targets': [ 10 | { 11 | 'target_name': "cloudcv", 12 | 13 | 'sources': [ 14 | "src/cloudcv.cpp", 15 | 16 | "src/framework/Logger.hpp", 17 | "src/framework/ScopedTimer.hpp", 18 | 19 | "src/framework/marshal/marshal.hpp", 20 | "src/framework/marshal/opencv.hpp", 21 | 22 | "src/framework/ImageView.hpp", 23 | "src/framework/ImageView.cpp", 24 | 25 | "src/framework/Job.hpp", 26 | "src/framework/Job.cpp", 27 | 28 | "src/framework/Algorithm.hpp", 29 | "src/framework/Algorithm.cpp", 30 | 31 | "src/framework/AlgorithmInfo.hpp", 32 | "src/framework/AlgorithmInfo.cpp", 33 | 34 | "src/framework/Argument.hpp", 35 | "src/framework/Argument.cpp", 36 | 37 | "src/framework/AlgorithmExceptions.hpp", 38 | "src/framework/AlgorithmExceptions.cpp", 39 | 40 | "src/modules/HoughLines.cpp", 41 | "src/modules/HoughLines.hpp", 42 | 43 | "src/modules/IntegralImage.hpp", 44 | "src/modules/IntegralImage.cpp" 45 | ], 46 | 47 | 'include_dirs': [ 48 | 'src/', 49 | "!(node -e \"require('native-opencv').libraries(true)\")" 57 | ], 58 | 59 | 'configurations': { 60 | 'Debug': { 61 | 'msvs_settings': { 62 | 'VCCLCompilerTool': { 63 | 'RuntimeTypeInfo': 'true', 64 | 'ExceptionHandling': '2', # /EHsc 65 | }, 66 | 'VCLinkerTool': { 67 | 'GenerateDebugInformation': 'true', 68 | } 69 | }, 70 | }, 71 | 'Release': { 72 | 'msvs_settings': { 73 | 'VCCLCompilerTool': { 74 | 'RuntimeTypeInfo': 'true', 75 | 'ExceptionHandling': '2', # /EHsc 76 | }, 77 | 'VCLinkerTool': { 78 | 'GenerateDebugInformation': 'true', 79 | } 80 | }, 81 | }, 82 | }, 83 | 84 | 'target_conditions': [ 85 | 86 | ['OS=="win"', { 87 | 88 | }], 89 | ['OS=="mac"', { 90 | 91 | 'defines': [ 92 | 'TARGET_PLATFORM_MAC', 93 | ], 94 | 95 | 'xcode_settings': { 96 | 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 97 | 'GCC_ENABLE_CPP_RTTI': 'YES', 98 | 'OTHER_CFLAGS': [ '-g', '-mmacosx-version-min=10.7', '-std=c++11', '-stdlib=libc++', '-O3', '-Wall' ], 99 | 'OTHER_CPLUSPLUSFLAGS': [ '-g', '-mmacosx-version-min=10.7', '-std=c++11', '-stdlib=libc++', '-O3', '-Wall' ] 100 | } 101 | }], 102 | 103 | 104 | ['OS=="linux" or OS=="freebsd" or OS=="openbsd" or OS=="solaris"', { 105 | 106 | 'defines': [ 107 | 'TARGET_PLATFORM_LINUX', 108 | ], 109 | 110 | 'libraries!': [ '-undefined dynamic_lookup' ], 111 | 112 | 'cflags_cc!': [ '-fno-exceptions', '-fno-rtti' ], 113 | "cflags": [ '-std=c++11', '-fexceptions', '-frtti' ], 114 | }], 115 | 116 | ['OS=="win"', { 117 | 'defines': [ 118 | 'TARGET_PLATFORM_WINDOWS', 119 | ] 120 | }] 121 | 122 | ] 123 | } 124 | ] 125 | } 126 | -------------------------------------------------------------------------------- /cloudcv-bootstrap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/cloudcv-bootstrap.jpg -------------------------------------------------------------------------------- /cloudcv.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | var fs = require('fs'); 15 | 16 | var DEBUG_ADDON = __dirname + "/build/Debug/cloudcv.node"; 17 | var RELEASE_ADDON = __dirname + "/build/Release/cloudcv.node"; 18 | 19 | 20 | if (fs.existsSync(RELEASE_ADDON)) 21 | var nativeModule = require(RELEASE_ADDON); 22 | else if (fs.existsSync(DEBUG_ADDON)) 23 | var nativeModule = require(DEBUG_ADDON); 24 | else 25 | throw new Error("Cannot find CloudCV addon"); 26 | 27 | var algorithms = nativeModule.getAlgorithms(); 28 | 29 | module.exports.getInfo = nativeModule.getInfo; 30 | module.exports.getAlgorithms = nativeModule.getAlgorithms; 31 | 32 | function registerAlgorithm(algName, index, array) { 33 | console.log('a[' + index + '] = ' + algName); 34 | 35 | module.exports[algName] = function(args, callback) { nativeModule.processFunction(algName, args, callback); }; 36 | } 37 | 38 | algorithms.forEach(registerAlgorithm); -------------------------------------------------------------------------------- /cloudcv.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "CloudCV API", 5 | "description": "Move your app forward with the Uber API", 6 | "version": "1.0.0" 7 | }, 8 | "host": "cloudcv.io", 9 | "basePath": "/api/v1", 10 | "schemes": [ 11 | "https" 12 | ], 13 | "produces": [ 14 | "application/json" 15 | ], 16 | "securityDefinitions": { 17 | "token": { 18 | "type": "apiKey", 19 | "name": "api_key", 20 | "in": "header" 21 | } 22 | }, 23 | "paths": { 24 | "/image/analyze/dominantColors": { 25 | "get": { 26 | "operationId": "getDominantColors", 27 | "summary": "Analyzes basic properties of the image", 28 | "description": "This endpoint can extract dominant colors from the image and return them \nas a JSON response.\n", 29 | "parameters": [ 30 | { 31 | "name": "image", 32 | "in": "query", 33 | "description": "Image file of supported format to analyze. Currently we suport png, jpg, bmp formats. Enpoint will download it from specified \npath and analyze.\n", 34 | "required": true, 35 | "type": "string" 36 | } 37 | ], 38 | "tags": [ 39 | "AnalyzeImage" 40 | ], 41 | "security": [ 42 | { 43 | "token": [] 44 | } 45 | ], 46 | "responses": { 47 | "200": { 48 | "description": "An array of products", 49 | "schema": { 50 | "$ref": "#/definitions/AnalyzeImageResponse" 51 | } 52 | }, 53 | "413": { 54 | "description": "Requested image too large", 55 | "schema": { 56 | "$ref": "#/definitions/Error" 57 | } 58 | }, 59 | "default": { 60 | "description": "Unexpected error", 61 | "schema": { 62 | "$ref": "#/definitions/Error" 63 | } 64 | } 65 | } 66 | }, 67 | "post": { 68 | "operationId": "postDominantColors", 69 | "summary": "Analyzes basic properties of the image", 70 | "consumes": [ 71 | "application/x-www-form-urlencoded" 72 | ], 73 | "description": "This endpoint can extract dominant colors from the image and return them \nas a JSON response.\n", 74 | "parameters": [ 75 | { 76 | "name": "image", 77 | "in": "formData", 78 | "description": "Encoded image data. Currently we suport png, jpg, bmp formats.\n", 79 | "required": true, 80 | "type": "file" 81 | } 82 | ], 83 | "tags": [ 84 | "AnalyzeImage" 85 | ], 86 | "security": [ 87 | { 88 | "token": [] 89 | } 90 | ], 91 | "responses": { 92 | "200": { 93 | "description": "Dominant colors and basic image properties.", 94 | "schema": { 95 | "$ref": "#/definitions/AnalyzeImageResponse" 96 | } 97 | }, 98 | "413": { 99 | "description": "Requested image too large", 100 | "schema": { 101 | "$ref": "#/definitions/Error" 102 | } 103 | }, 104 | "default": { 105 | "description": "Unexpected error", 106 | "schema": { 107 | "$ref": "#/definitions/Error" 108 | } 109 | } 110 | } 111 | } 112 | } 113 | }, 114 | "definitions": { 115 | "Color": { 116 | "properties": { 117 | "red": { 118 | "description": "Red component intensity (0..255)", 119 | "type": "integer", 120 | "format": "int32" 121 | }, 122 | "green": { 123 | "description": "Green component intensity (0..255)", 124 | "type": "integer", 125 | "format": "int32" 126 | }, 127 | "blue": { 128 | "description": "Blue component intensity (0..255)", 129 | "type": "integer", 130 | "format": "int32" 131 | }, 132 | "html": { 133 | "description": "HTML string color representation", 134 | "type": "string" 135 | } 136 | }, 137 | "xml": { 138 | "name": "Color" 139 | } 140 | }, 141 | "DominantColor": { 142 | "properties": { 143 | "value": { 144 | "description": "Dominant color value", 145 | "schema": { 146 | "$ref": "#/definitions/Color" 147 | } 148 | }, 149 | "amount": { 150 | "description": "Color amount in percents of entire image", 151 | "type": "number", 152 | "format": "float" 153 | } 154 | }, 155 | "xml": { 156 | "name": "DominantColor" 157 | } 158 | }, 159 | "AnalyzeImageResponse": { 160 | "properties": { 161 | "colors": { 162 | "description": "Dominant colors", 163 | "type": "array", 164 | "items": { 165 | "$ref": "#/definitions/DominantColor" 166 | } 167 | } 168 | }, 169 | "xml": { 170 | "name": "AnalyzeImageResponse" 171 | } 172 | }, 173 | "Error": { 174 | "properties": { 175 | "message": { 176 | "description": "Human-friendlty error description", 177 | "type": "string" 178 | } 179 | }, 180 | "xml": { 181 | "name": "Error" 182 | } 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /debug.js: -------------------------------------------------------------------------------- 1 | var DEBUG_ADDON = __dirname + "/build/Debug/cloudcv.node"; 2 | 3 | var cloudcv = require(DEBUG_ADDON); 4 | 5 | /* 6 | cloudcv.houghLines({ "image": "test/data/opencv-logo.jpg"}, function(error, result) { 7 | console.log((error)); 8 | console.log((result)); 9 | done(); 10 | });*/ 11 | 12 | cloudcv.houghLines({ }, function(error, result) { 13 | console.log((error)); 14 | console.log((result)); 15 | done(); 16 | }); -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | var config = { 15 | maxFileSize: 4 * 1048576, // 4 Megabyte should be enough 16 | }; 17 | 18 | module.exports = config; 19 | -------------------------------------------------------------------------------- /lib/download.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | var express = require('express') 15 | , http = require('http') 16 | , async = require('async') 17 | , request = require('request') 18 | , validator = require('validator') 19 | , logger = require('./logger.js') 20 | , config = require('./config.js') 21 | , inspect = require('util').inspect 22 | ; 23 | 24 | 25 | module.exports.plainDownloadViaHttpGet = function (url, callback) { 26 | 27 | logger.info('Downloading image from %s', url); 28 | 29 | var requestSettings = { 30 | method: 'GET', 31 | url: url, 32 | encoding: null 33 | }; 34 | 35 | var payloadSize = 0; 36 | 37 | var req = request(requestSettings, function (error, response, body) { 38 | callback(error, body); 39 | }).on('data', function (chunk) { 40 | payloadSize += chunk.length; 41 | 42 | if (payloadSize > config.maxFileSize) { 43 | logger.warn('Maximum payload size exceed. Stopping download.'); 44 | req.abort(); 45 | callback(new Error('Maximum payload size exceed.'), null); 46 | } 47 | }); 48 | }; -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | var path = require('path') 15 | , fs = require('fs') 16 | , winston = require('winston') 17 | ; 18 | 19 | 20 | var logsDir = process.env.LOGS || './logs' 21 | 22 | var logger = new (winston.Logger)({ 23 | transports: [ 24 | new (winston.transports.Console)(), 25 | new (winston.transports.File)({ 26 | name: 'info-file', 27 | filename: logsDir + '/api.cloudcv.io.info.log', 28 | level: 'info' 29 | }), 30 | new (winston.transports.File)({ 31 | name: 'error-file', 32 | filename: logsDir + '/api.cloudcv.io.error.log', 33 | level: 'error' 34 | }) 35 | ] 36 | }); 37 | 38 | if (!fs.existsSync(logsDir)) { 39 | fs.mkdirSync(logsDir); 40 | 41 | logger.info('Created logs directory %s', logsDir); 42 | } 43 | 44 | 45 | module.exports = logger; -------------------------------------------------------------------------------- /lib/swaggerSpec.js: -------------------------------------------------------------------------------- 1 | 2 | function capitalizeFirstLetter(string) { 3 | return string.charAt(0).toUpperCase() + string.slice(1); 4 | } 5 | 6 | function describeAlgorithm(algorithmName) { 7 | 8 | return { 9 | get: { 10 | summary: "Returns information about algorithm", 11 | description: "", 12 | operationId: "get" + capitalizeFirstLetter(algorithmName) + "Info", 13 | consumes: ["application/json"], 14 | produces: ["application/json"], 15 | }, 16 | post: { 17 | summary: "Invokes method", 18 | description: "", 19 | operationId: algorithmName, 20 | consumes: ["application/json"], 21 | produces: ["application/json"], 22 | parameters: [ 23 | { 24 | in: "body", 25 | name: "body", 26 | description: "Input arguments", 27 | required: true, 28 | schema: { 29 | "$ref": "#/definitions/" + capitalizeFirstLetter(algorithmName) + "Input" 30 | } 31 | } 32 | ], 33 | responses: { 34 | 200: { 35 | description: "successful operation", 36 | schema: { 37 | "$ref": "#/definitions/" + capitalizeFirstLetter(algorithmName) + "Result" 38 | } 39 | }, 40 | 400: { 41 | description: "Invalid status value" 42 | } 43 | }, 44 | }, 45 | }; 46 | } 47 | 48 | module.exports.getSpec = function (algorithms) { 49 | var i; 50 | var spec = { 51 | swagger: "2.0", 52 | info: { 53 | description: "This is a sample service of CloudCV server.", 54 | version: "1.0.0", 55 | title: "CloudCV", 56 | termsOfService: "http://cloudcv.io/terms/", 57 | contact: { 58 | email: "ekhvedchenya@gmail.com" 59 | }, 60 | license: { 61 | name: "Apache 2.0", 62 | url: "http://www.apache.org/licenses/LICENSE-2.0.html" 63 | }, 64 | basePath: "/v2", 65 | tags: [ 66 | { 67 | name: "pet", 68 | description: "Everything about your Pets", 69 | externalDocs: { 70 | description: "Find out more", 71 | url: "http://swagger.io" 72 | } 73 | }, 74 | { 75 | name: "store", 76 | description: "Access to Petstore orders" 77 | }, 78 | { 79 | name: "user", 80 | description: "Operations about user", 81 | externalDocs: { 82 | description: "Find out more about our store", 83 | url: "http://swagger.io" 84 | } 85 | } 86 | ], 87 | schemes: [ "http", "https" ], 88 | securityDefinitions: { 89 | /* 90 | petstore_auth: { 91 | "type": "oauth2", 92 | "authorizationUrl": "http://petstore.swagger.io/api/oauth/dialog", 93 | "flow": "implicit", 94 | "scopes": { 95 | "write:pets": "modify pets in your account", 96 | "read:pets": "read your pets" 97 | } 98 | }, 99 | api_key: { 100 | "type": "apiKey", 101 | name: "api_key", 102 | "in": "header" 103 | } 104 | */ 105 | }, 106 | }, 107 | paths: {}, 108 | definitions: {}, 109 | externalDocs: { 110 | description: "Find out more about Swagger", 111 | url: "http://swagger.io" 112 | }, 113 | host: "petstore.swagger.io", 114 | }; 115 | 116 | for (i = algorithms.length - 1; i >= 0; i--) { 117 | spec.paths[algorithms[i]] = describeAlgorithm(algorithms[i]); 118 | } 119 | 120 | return spec; 121 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudcv-bootstrap", 3 | "version": "1.0.7", 4 | "description": "A bootstrap project for prototyping cloud image processing server", 5 | "author": "Eugene Khvedchenya ", 6 | "license": "MIT", 7 | "main": "cloudcv.js", 8 | "scripts": { 9 | "test": "mocha test/*.js" 10 | }, 11 | "engines": { 12 | "node": ">=0.12" 13 | }, 14 | "dependencies": { 15 | "async": "^0.9.0", 16 | "body-parser": "^1.11.0", 17 | "cookie-parser": "^1.3.3", 18 | "errorhandler": "^1.3.3", 19 | "express": "^4.13.3", 20 | "jade": "^1.11.0", 21 | "json-middleware": "^1.0.2", 22 | "method-override": "^2.3.1", 23 | "multer": "^0.1.7", 24 | "nan": "^2.0.9", 25 | "nan-check": "0.0.6", 26 | "nan-marshal": "0.0.5", 27 | "native-opencv": "3.0.0-b", 28 | "node-gyp": "^3.2.1", 29 | "pmx": "^0.2.33", 30 | "request": "*", 31 | "validator": "^3.28.0", 32 | "winston": "^0.9.0" 33 | }, 34 | "devDependencies": { 35 | "mocha": "*" 36 | }, 37 | "keywords": [ 38 | "opencv", 39 | "computer", 40 | "vision" 41 | ], 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/CloudCV/cloudcv-bootstrap.git" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/bootstrap/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | .btn-default, 2 | .btn-primary, 3 | .btn-success, 4 | .btn-info, 5 | .btn-warning, 6 | .btn-danger { 7 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 8 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 9 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 10 | } 11 | 12 | .btn-default:active, 13 | .btn-primary:active, 14 | .btn-success:active, 15 | .btn-info:active, 16 | .btn-warning:active, 17 | .btn-danger:active, 18 | .btn-default.active, 19 | .btn-primary.active, 20 | .btn-success.active, 21 | .btn-info.active, 22 | .btn-warning.active, 23 | .btn-danger.active { 24 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 25 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 26 | } 27 | 28 | .btn:active, 29 | .btn.active { 30 | background-image: none; 31 | } 32 | 33 | .btn-default { 34 | text-shadow: 0 1px 0 #fff; 35 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6)); 36 | background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%); 37 | background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%); 38 | background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%); 39 | background-repeat: repeat-x; 40 | border-color: #e0e0e0; 41 | border-color: #ccc; 42 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); 43 | } 44 | 45 | .btn-default:active, 46 | .btn-default.active { 47 | background-color: #e6e6e6; 48 | border-color: #e0e0e0; 49 | } 50 | 51 | .btn-primary { 52 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 53 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 54 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 55 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 56 | background-repeat: repeat-x; 57 | border-color: #2d6ca2; 58 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 59 | } 60 | 61 | .btn-primary:active, 62 | .btn-primary.active { 63 | background-color: #3071a9; 64 | border-color: #2d6ca2; 65 | } 66 | 67 | .btn-success { 68 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 69 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 70 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 71 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 72 | background-repeat: repeat-x; 73 | border-color: #419641; 74 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 75 | } 76 | 77 | .btn-success:active, 78 | .btn-success.active { 79 | background-color: #449d44; 80 | border-color: #419641; 81 | } 82 | 83 | .btn-warning { 84 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 85 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 86 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 87 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 88 | background-repeat: repeat-x; 89 | border-color: #eb9316; 90 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 91 | } 92 | 93 | .btn-warning:active, 94 | .btn-warning.active { 95 | background-color: #ec971f; 96 | border-color: #eb9316; 97 | } 98 | 99 | .btn-danger { 100 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 101 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 102 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 103 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 104 | background-repeat: repeat-x; 105 | border-color: #c12e2a; 106 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 107 | } 108 | 109 | .btn-danger:active, 110 | .btn-danger.active { 111 | background-color: #c9302c; 112 | border-color: #c12e2a; 113 | } 114 | 115 | .btn-info { 116 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 117 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 118 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 119 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 120 | background-repeat: repeat-x; 121 | border-color: #2aabd2; 122 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 123 | } 124 | 125 | .btn-info:active, 126 | .btn-info.active { 127 | background-color: #31b0d5; 128 | border-color: #2aabd2; 129 | } 130 | 131 | .thumbnail, 132 | .img-thumbnail { 133 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 134 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 135 | } 136 | 137 | .dropdown-menu > li > a:hover, 138 | .dropdown-menu > li > a:focus, 139 | .dropdown-menu > .active > a, 140 | .dropdown-menu > .active > a:hover, 141 | .dropdown-menu > .active > a:focus { 142 | background-color: #357ebd; 143 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 144 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 145 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 146 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 147 | background-repeat: repeat-x; 148 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 149 | } 150 | 151 | .navbar { 152 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8)); 153 | background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%); 154 | background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 155 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 156 | background-repeat: repeat-x; 157 | border-radius: 4px; 158 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 159 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 160 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 161 | } 162 | 163 | .navbar .navbar-nav > .active > a { 164 | background-color: #f8f8f8; 165 | } 166 | 167 | .navbar-brand, 168 | .navbar-nav > li > a { 169 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 170 | } 171 | 172 | .navbar-inverse { 173 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222)); 174 | background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%); 175 | background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%); 176 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 177 | background-repeat: repeat-x; 178 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 179 | } 180 | 181 | .navbar-inverse .navbar-nav > .active > a { 182 | background-color: #222222; 183 | } 184 | 185 | .navbar-inverse .navbar-brand, 186 | .navbar-inverse .navbar-nav > li > a { 187 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 188 | } 189 | 190 | .navbar-static-top, 191 | .navbar-fixed-top, 192 | .navbar-fixed-bottom { 193 | border-radius: 0; 194 | } 195 | 196 | .alert { 197 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 198 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 199 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 200 | } 201 | 202 | .alert-success { 203 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc)); 204 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%); 205 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 206 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 207 | background-repeat: repeat-x; 208 | border-color: #b2dba1; 209 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 210 | } 211 | 212 | .alert-info { 213 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0)); 214 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%); 215 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 216 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 217 | background-repeat: repeat-x; 218 | border-color: #9acfea; 219 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 220 | } 221 | 222 | .alert-warning { 223 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0)); 224 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%); 225 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 226 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 227 | background-repeat: repeat-x; 228 | border-color: #f5e79e; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 230 | } 231 | 232 | .alert-danger { 233 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3)); 234 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%); 235 | background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 236 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 237 | background-repeat: repeat-x; 238 | border-color: #dca7a7; 239 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 240 | } 241 | 242 | .progress { 243 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5)); 244 | background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%); 245 | background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 246 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 247 | background-repeat: repeat-x; 248 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 249 | } 250 | 251 | .progress-bar { 252 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9)); 253 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%); 254 | background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%); 255 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 256 | background-repeat: repeat-x; 257 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 258 | } 259 | 260 | .progress-bar-success { 261 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44)); 262 | background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%); 263 | background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%); 264 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 265 | background-repeat: repeat-x; 266 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 267 | } 268 | 269 | .progress-bar-info { 270 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5)); 271 | background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%); 272 | background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 273 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 274 | background-repeat: repeat-x; 275 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 276 | } 277 | 278 | .progress-bar-warning { 279 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f)); 280 | background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%); 281 | background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 282 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 283 | background-repeat: repeat-x; 284 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 285 | } 286 | 287 | .progress-bar-danger { 288 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c)); 289 | background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%); 290 | background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%); 291 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 292 | background-repeat: repeat-x; 293 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 294 | } 295 | 296 | .list-group { 297 | border-radius: 4px; 298 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 299 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 300 | } 301 | 302 | .list-group-item.active, 303 | .list-group-item.active:hover, 304 | .list-group-item.active:focus { 305 | text-shadow: 0 -1px 0 #3071a9; 306 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3)); 307 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%); 308 | background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%); 309 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 310 | background-repeat: repeat-x; 311 | border-color: #3278b3; 312 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 313 | } 314 | 315 | .panel { 316 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 317 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 318 | } 319 | 320 | .panel-default > .panel-heading { 321 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8)); 322 | background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%); 323 | background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 324 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 325 | background-repeat: repeat-x; 326 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 327 | } 328 | 329 | .panel-primary > .panel-heading { 330 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd)); 331 | background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%); 332 | background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%); 333 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 334 | background-repeat: repeat-x; 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 336 | } 337 | 338 | .panel-success > .panel-heading { 339 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6)); 340 | background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%); 341 | background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 342 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 343 | background-repeat: repeat-x; 344 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 345 | } 346 | 347 | .panel-info > .panel-heading { 348 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3)); 349 | background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%); 350 | background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 351 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 352 | background-repeat: repeat-x; 353 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 354 | } 355 | 356 | .panel-warning > .panel-heading { 357 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc)); 358 | background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%); 359 | background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 360 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 361 | background-repeat: repeat-x; 362 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 363 | } 364 | 365 | .panel-danger > .panel-heading { 366 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc)); 367 | background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%); 368 | background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 369 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 370 | background-repeat: repeat-x; 371 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 372 | } 373 | 374 | .well { 375 | background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5)); 376 | background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%); 377 | background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 378 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 379 | background-repeat: repeat-x; 380 | border-color: #dcdcdc; 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 382 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 383 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 384 | } -------------------------------------------------------------------------------- /public/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,0%,#e6e6e6,100%);background-image:-moz-linear-gradient(top,#fff 0,#e6e6e6 100%);background-image:linear-gradient(to bottom,#fff 0,#e6e6e6 100%);background-repeat:repeat-x;border-color:#e0e0e0;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0)}.btn-default:active,.btn-default.active{background-color:#e6e6e6;border-color:#e0e0e0}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;border-color:#2d6ca2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.btn-primary:active,.btn-primary.active{background-color:#3071a9;border-color:#2d6ca2}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;border-color:#419641;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.btn-success:active,.btn-success.active{background-color:#449d44;border-color:#419641}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;border-color:#eb9316;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.btn-warning:active,.btn-warning.active{background-color:#ec971f;border-color:#eb9316}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;border-color:#c12e2a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.btn-danger:active,.btn-danger.active{background-color:#c9302c;border-color:#c12e2a}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;border-color:#2aabd2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.btn-info:active,.btn-info.active{background-color:#31b0d5;border-color:#2aabd2}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff,0%,#f8f8f8,100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar .navbar-nav>.active>a{background-color:#f8f8f8}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c,0%,#222,100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0)}.navbar-inverse .navbar-nav>.active>a{background-color:#222}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#c8e5bc,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#b9def0,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#f8efc0,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede,0%,#e7c3c3,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0%,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0%,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0%,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0%,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0%,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca,0%,#3278b3,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5,0%,#e8e8e8,100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0%,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8,0%,#d0e9c6,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7,0%,#c4e3f3,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3,0%,#faf2cc,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede,0%,#ebcccc,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8,0%,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/bootstrap/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootstrap.js v3.0.0 by @fat and @mdo 3 | * Copyright 2013 Twitter Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery); -------------------------------------------------------------------------------- /public/images/ar-demo-300x200.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/ar-demo-300x200.jpg -------------------------------------------------------------------------------- /public/images/cloudcv-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/cloudcv-logo.png -------------------------------------------------------------------------------- /public/images/contact-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/contact-github.png -------------------------------------------------------------------------------- /public/images/contact-gmail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/contact-gmail.png -------------------------------------------------------------------------------- /public/images/contact-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/contact-twitter.png -------------------------------------------------------------------------------- /public/images/digital-ocean-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/digital-ocean-logo-white.png -------------------------------------------------------------------------------- /public/images/kid-128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/kid-128.jpg -------------------------------------------------------------------------------- /public/images/kid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/kid.jpg -------------------------------------------------------------------------------- /public/images/lena-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/lena-128.png -------------------------------------------------------------------------------- /public/images/lena-300x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/lena-300x200.png -------------------------------------------------------------------------------- /public/images/lena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/lena.png -------------------------------------------------------------------------------- /public/images/mandril-300x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/mandril-300x200.png -------------------------------------------------------------------------------- /public/images/mandrill-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/mandrill-128.png -------------------------------------------------------------------------------- /public/images/mandrill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/mandrill.png -------------------------------------------------------------------------------- /public/images/sudoku-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/sudoku-128.png -------------------------------------------------------------------------------- /public/images/sudoku-300x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/sudoku-300x200.png -------------------------------------------------------------------------------- /public/images/sudoku-original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/sudoku-original.jpg -------------------------------------------------------------------------------- /public/images/sudoku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/public/images/sudoku.png -------------------------------------------------------------------------------- /public/javascripts/README.md: -------------------------------------------------------------------------------- 1 | #[jQuery Form Plugin](http://jquery.malsup.com/form/) 2 | 3 | ##Overview 4 | The jQuery Form Plugin allows you to easily and unobtrusively upgrade HTML forms to use AJAX. The main methods, ajaxForm and ajaxSubmit, gather information from the form element to determine how to manage the submit process. Both of these methods support numerous options which allows you to have full control over how the data is submitted. 5 | 6 | No special markup is needed, just a normal form. Submitting a form with AJAX doesn't get any easier than this! 7 | 8 | --- 9 | 10 | ##API 11 | 12 | ###jqXHR 13 | The jqXHR object is stored in element data-cache with the jqxhr key after each ajaxSubmit 14 | call. It can be accessed like this: 15 | ````javascript 16 | var form = $('#myForm').ajaxSubmit({ /* options */ }); 17 | var xhr = form.data('jqxhr'); 18 | 19 | xhr.done(function() { 20 | ... 21 | }); 22 | ```` 23 | 24 | ###ajaxForm( options ) 25 | Prepares a form to be submitted via AJAX by adding all of the necessary event listeners. It does **not** submit the form. Use `ajaxForm` in your document's `ready` function to prepare existing forms for AJAX submission, or with the `delegation` option to handle forms not yet added to the DOM. 26 | Use ajaxForm when you want the plugin to manage all the event binding for you. 27 | 28 | ````javascript 29 | // prepare all forms for ajax submission 30 | $('form').ajaxForm({ 31 | target: '#myResultsDiv' 32 | }); 33 | ```` 34 | 35 | ###ajaxSubmit( options ) 36 | Immediately submits the form via AJAX. In the most common use case this is invoked in response to the user clicking a submit button on the form. 37 | Use ajaxSubmit if you want to bind your own submit handler to the form. 38 | 39 | ````javascript 40 | // bind submit handler to form 41 | $('form').on('submit', function(e) { 42 | e.preventDefault(); // prevent native submit 43 | $(this).ajaxSubmit({ 44 | target: 'myResultsDiv' 45 | }) 46 | }); 47 | ```` 48 | 49 | --- 50 | 51 | ##Options 52 | Note: all standard [$.ajax](http://api.jquery.com/jQuery.ajax) options can be used. 53 | 54 | ###beforeSerialize 55 | Callback function invoked prior to form serialization. Provides an opportunity to manipulate the form before its values are retrieved. Returning `false` from the callback will prevent the form from being submitted. The callback is invoked with two arguments: the jQuery wrapped form object and the options object. 56 | 57 | ````javascript 58 | beforeSerialize: function($form, options) { 59 | // return false to cancel submit 60 | } 61 | ```` 62 | 63 | ###beforeSubmit 64 | Callback function invoked prior to form submission. This provides an opportunity to manipulate the form before it's values are retrieved. Returning `false` from the callback will prevent the form from being submitted. The callback is invoked with three arguments: the form data in array format, the jQuery wrapped form object, and the options Oobject. 65 | 66 | ````javascript 67 | beforeSubmit: function(arr, $form, options) { 68 | // form data array is an array of objects with name and value properties 69 | // [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ] 70 | // return false to cancel submit 71 | } 72 | ```` 73 | 74 | ###clearForm 75 | Boolean flag indicating whether the form should be cleared if the submit is successful 76 | 77 | ###data 78 | An object containing extra data that should be submitted along with the form. 79 | 80 | ````javascript 81 | data: { key1: 'value1', key2: 'value2' } 82 | ```` 83 | 84 | ###dataType 85 | Expected data type of the response. One of: null, 'xml', 'script', or 'json'. The dataType option provides a means for specifying how the server response should be handled. This maps directly to jQuery's dataType method. The following values are supported: 86 | 87 | * 'xml': server response is treated as XML and the 'success' callback method, if specified, will be passed the responseXML value 88 | * 'json': server response will be evaluted and passed to the 'success' callback, if specified 89 | * 'script': server response is evaluated in the global context 90 | 91 | ###delegation 92 | true to enable support for event delegation 93 | *requires jQuery v1.7+* 94 | 95 | ````javascript 96 | // prepare all existing and future forms for ajax submission 97 | $('form').ajaxForm({ 98 | delegation: true 99 | }); 100 | ```` 101 | 102 | ###error 103 | Callback function to be invoked upon error. 104 | 105 | ###forceSync 106 | Only applicable when explicity using the iframe option or when uploading files on browses that don't support XHR2. 107 | Set to `true` to remove the short delay before posting form when uploading files. The delay is used to allow the browser to render DOM updates prior to performing a native form submit. This improves usability when displaying notifications to the user, such as "Please Wait..." 108 | 109 | ###iframe 110 | Boolean flag indicating whether the form should *always* target the server response to an iframe instead of leveraging XHR when possible. 111 | 112 | ###iframeSrc 113 | String value that should be used for the iframe's src attribute when/if an iframe is used. 114 | 115 | ###iframeTarget 116 | Identifies the iframe element to be used as the response target for file uploads. By default, the plugin will create a temporary iframe element to capture the response when uploading files. This options allows you to use an existing iframe if you wish. When using this option the plugin will make no attempt at handling the response from the server. 117 | 118 | ###replaceTarget 119 | Optionally used along with the the target option. Set to true if the target should be replaced or false if only the target contents should be replaced. 120 | 121 | ###resetForm 122 | Boolean flag indicating whether the form should be reset if the submit is successful 123 | 124 | ###semantic 125 | Boolean flag indicating whether data must be submitted in strict semantic order (slower). Note that the normal form serialization is done in semantic order with the exception of input elements of `type="image"`. You should only set the semantic option to true if your server has strict semantic requirements and your form contains an input element of `type="image"`. 126 | 127 | ###success 128 | Callback function to be invoked after the form has been submitted. If a `success` callback function is provided it is invoked after the response has been returned from the server. It is passed the following arguments: 129 | 130 | 1. responseText or responseXML value (depending on the value of the dataType option). 131 | 2. statusText 132 | 3. xhr (or the jQuery-wrapped form element if using jQuery < 1.4) 133 | 4. jQuery-wrapped form element (or undefined if using jQuery < 1.4) 134 | 135 | ###target 136 | Identifies the element(s) in the page to be updated with the server response. This value may be specified as a jQuery selection string, a jQuery object, or a DOM element. 137 | 138 | ###type 139 | The method in which the form data should be submitted, 'GET' or 'POST'. 140 | 141 | ###uploadProgress 142 | Callback function to be invoked with upload progress information (if supported by the browser). The callback is passed the following arguments: 143 | 144 | 1. event; the browser event 145 | 2. position (integer) 146 | 3. total (integer) 147 | 4. percentComplete (integer) 148 | 149 | ###url 150 | URL to which the form data will be submitted. 151 | 152 | --- 153 | 154 | ##Utility Methods 155 | ###formSerialize 156 | Serializes the form into a query string. This method will return a string in the format: `name1=value1&name2=value2` 157 | 158 | ````javascript 159 | var queryString = $('#myFormId').formSerialize(); 160 | ```` 161 | 162 | ###fieldSerialize 163 | Serializes field elements into a query string. This is handy when you need to serialize only part of a form. This method will return a string in the format: `name1=value1&name2=value2` 164 | 165 | ````javascript 166 | var queryString = $('#myFormId .specialFields').fieldSerialize(); 167 | ```` 168 | 169 | ###fieldValue 170 | Returns the value(s) of the element(s) in the matched set in an array. This method always returns an array. If no valid value can be determined the array will be empty, otherwise it will contain one or more values. 171 | 172 | ###resetForm 173 | Resets the form to its original state by invoking the form element's native DOM method. 174 | 175 | ###clearForm 176 | Clears the form elements. This method emptys all of the text inputs, password inputs and textarea elements, clears the selection in any select elements, and unchecks all radio and checkbox inputs. It does *not* clear hidden field values. 177 | 178 | ###clearFields 179 | Clears selected field elements. This is handy when you need to clear only a part of the form. 180 | 181 | --- 182 | 183 | ##File Uploads 184 | The Form Plugin supports use of [XMLHttpRequest Level 2]("http://www.w3.org/TR/XMLHttpRequest/") and [FormData](https://developer.mozilla.org/en/XMLHttpRequest/FormData) objects on browsers that support these features. As of today (March 2012) that includes Chrome, Safari, and Firefox. On these browsers (and future Opera and IE10) files uploads will occur seamlessly through the XHR object and progress updates are available as the upload proceeds. For older browsers, a fallback technology is used which involves iframes. [More Info](http://malsup.com/jquery/form/#file-upload) 185 | 186 | --- 187 | 188 | ##Copyright and License 189 | Copyright 2006-2013 (c) M. Alsup 190 | 191 | All versions, present and past, of the jQuery Form plugin are dual licensed under the MIT and GPL licenses: 192 | 193 | * [MIT](http://malsup.github.com/mit-license.txt) 194 | * [GPL](http://malsup.github.com/gpl-license-v2.txt) 195 | 196 | You may use either license. The MIT License is recommended for most projects because it is simple and easy to understand and it places almost no restrictions on what you can do with the plugin. 197 | 198 | If the GPL suits your project better you are also free to use the plugin under that license. 199 | 200 | You don't have to do anything special to choose one license or the other and you don't have to notify anyone which license you are using. You are free to use the jQuery Form Plugin in commercial projects as long as the copyright header is left intact. 201 | 202 | 203 | --- 204 | 205 | Additional documentation and examples at: http://malsup.com/jquery/form/ 206 | -------------------------------------------------------------------------------- /public/javascripts/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-form", 3 | "version": "3.37.0", 4 | "main": "jquery.form.js", 5 | "author": "M. Alsup", 6 | "dependencies": { 7 | "jquery": ">=1.5" 8 | }, 9 | "ignore": [ 10 | "README.md", 11 | "composer.json", 12 | "form.jquery.json", 13 | "package.json" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /public/javascripts/cloudcv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eugene Khvedchenya / http://computer-vision-talks.com/ 3 | */ 4 | 5 | window.cloudcv = { 6 | REVISION: '0.0.1' 7 | }; 8 | 9 | (function(global) { 10 | "use strict"; 11 | 12 | var BASE_URL = "//" + location.host + '/api/v1/'; 13 | 14 | var analyze_t = (function () { 15 | function analyze_t(baseUrl) { 16 | 17 | this.dominantColors = function(image, callback) { 18 | 19 | if (image instanceof Blob) { 20 | 21 | var formData = new FormData(); 22 | formData.append('image', image); 23 | 24 | $.ajax({ 25 | url: baseUrl + 'dominantColors/', 26 | type: 'POST', 27 | data: formData, 28 | cache: false, 29 | contentType: false, 30 | processData: false, 31 | success: function (data) { callback(null, data); }, 32 | error: function (error) { callback({message:error.statusText}); } 33 | }); 34 | } 35 | else if (typeof image == 'string') { 36 | $.get(baseUrl + 'dominantColors?image=' + encodeURIComponent(image), function( response ) { 37 | callback(null, response); 38 | }); 39 | } 40 | else { 41 | callback(new Error('Unsupported type of image argument')); 42 | } 43 | }; 44 | } 45 | return analyze_t; 46 | })(); 47 | 48 | var image_t = (function () { 49 | function image_t(baseUrl) { 50 | this.analyze = new analyze_t(baseUrl + 'analyze/'); 51 | } 52 | return image_t; 53 | })(); 54 | 55 | // Common-used functions: 56 | 57 | /** 58 | * @brief Decode data-URL and return a Blob object with raw image data. 59 | * 60 | * @param dataURL - A string of DataURL format that is decoded and passed to create Blob of correct mime-type. 61 | */ 62 | function dataURLtoBlob(dataURL) 63 | { 64 | // convert base64 to raw binary data held in a string 65 | // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this 66 | var comp = dataURL.split(','); 67 | 68 | var byteString = window.atob(comp[1]); 69 | 70 | // separate out the mime component 71 | var mimeString = comp[0].split(':')[1].split(';')[0]; 72 | 73 | // write the bytes of the string to an ArrayBuffer 74 | var ab = new ArrayBuffer(byteString.length); 75 | var ia = new Uint8Array(ab); 76 | var i; 77 | 78 | for (i = 0; i < byteString.length; i++) 79 | { 80 | ia[i] = byteString.charCodeAt(i); 81 | } 82 | 83 | try { 84 | return new Blob([ab], {type: mimeString}); 85 | } catch (e) { 86 | // The BlobBuilder API has been deprecated in favour of Blob, but older 87 | // browsers don't know about the Blob constructor 88 | // IE10 also supports BlobBuilder, but since the `Blob` constructor 89 | // also works, there's no need to add `MSBlobBuilder`. 90 | var BlobBuilder = window.WebKitBlobBuilder || window.MozBlobBuilder; 91 | var bb = new BlobBuilder(); 92 | bb.append(ab); 93 | return bb.getBlob(mimeString); 94 | } 95 | } 96 | 97 | /** 98 | * @brief Encode image and return data-URL that with that information. 99 | * 100 | * @param image Can be instance of HTML Image or HTML Canvas. 101 | * @param quality Quality of the encoded image (0.8 by default). 102 | */ 103 | function getImageDataAsBlob(image) { 104 | 105 | if (image instanceof HTMLImageElement) { 106 | 107 | var canvas = document.createElement('canvas'); 108 | canvas.width = image.width; 109 | canvas.height = image.height; 110 | canvas.getContext("2d").drawImage(image, 0, 0); 111 | return getImageDataAsBlob(canvas, 1.0); 112 | 113 | } else if (image instanceof HTMLCanvasElement) { 114 | 115 | return dataURLtoBlob(image.toDataURL('image/jpeg', 1.0)); 116 | 117 | } else { 118 | 119 | console.error('Unsupported argument type passed to getImageDataURL' + typeof(image)); 120 | return null; 121 | } 122 | } 123 | 124 | // 125 | global.common = { 126 | 127 | getImageDataAsBlob: getImageDataAsBlob, 128 | 129 | bindHookOnFileInput: function(inputId, callback) { 130 | var input = document.getElementById(inputId); 131 | 132 | if (!input) { 133 | console.error('Cannot find element with id #' + inputId); 134 | return false; 135 | } 136 | 137 | input.addEventListener('change', function(e) { 138 | 139 | if (e.target.files.length != 1) 140 | return; 141 | 142 | var file = e.target.files[0]; 143 | var url = URL.createObjectURL(file); 144 | var srcImg = new Image(); 145 | 146 | srcImg.onload = function(e) { 147 | callback(srcImg, file); 148 | 149 | if (url) { 150 | URL.revokeObjectURL(url); 151 | url = null; 152 | } 153 | }; 154 | 155 | if (url) { 156 | srcImg.src = url; 157 | } 158 | else { 159 | var reader = new FileReader(); 160 | 161 | reader.onload = function (oFREvent) { 162 | var dataUrl = oFREvent.target.result; 163 | srcImg.src = dataUrl; 164 | }; 165 | 166 | reader.readAsDataURL(file); 167 | } 168 | 169 | }); 170 | } 171 | }; 172 | 173 | global.image = new image_t(BASE_URL + 'image/'); 174 | 175 | })(window.cloudcv); -------------------------------------------------------------------------------- /public/javascripts/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "malsup/form", 3 | "description": "A simple way to AJAX-ify any form on your page; with file upload and progress support.", 4 | "type": "component", 5 | "homepage": "http://jquery.malsup.com/form/", 6 | "keywords": [ 7 | "form", 8 | "upload", 9 | "ajax" 10 | ], 11 | "support": { 12 | "issues": "https://github.com/malsup/form/issues", 13 | "wiki": "http://jquery.malsup.com/form/" 14 | }, 15 | "authors": [ 16 | { 17 | "name": "M. Alsup", 18 | "homepage": "http://jquery.malsup.com" 19 | } 20 | ], 21 | "license": [ 22 | "MIT", 23 | "GPL-2.0" 24 | ], 25 | "require": { 26 | "components/jquery": ">=1.5" 27 | }, 28 | "extra": { 29 | "component": { 30 | "scripts": [ 31 | "jquery.form.js" 32 | ], 33 | "shim": { 34 | "deps": [ 35 | "jquery" 36 | ] 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/javascripts/form.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "form", 3 | "title": "Form", 4 | "description": "A simple way to AJAX-ify any form on your page; with file upload and progress support.", 5 | "keywords": [ 6 | "form", 7 | "upload", 8 | "ajax" 9 | ], 10 | "version": "3.26.0-2013.01.28", 11 | "author": { 12 | "name": "M. Alsup", 13 | "url": "http://jquery.malsup.com" 14 | }, 15 | "licenses": [ 16 | { 17 | "type": "MIT", 18 | "url": "http://malsup.github.com/mit-license.txt" 19 | }, 20 | { 21 | "type": "GPL", 22 | "url": "http://malsup.github.com/gpl-license-v2.txt" 23 | } 24 | ], 25 | "bugs": "https://github.com/malsup/form/issues", 26 | "homepage": "http://jquery.malsup.com/form/", 27 | "docs": "http://jquery.malsup.com/form/", 28 | "download": "http://malsup.github.com/jquery.form.js", 29 | "dependencies": { 30 | "jquery": ">=1.5" 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /public/javascripts/ga.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 5 | 6 | ga('create', 'UA-10687369-8', 'cloudcv.io'); 7 | ga('send', 'pageview'); -------------------------------------------------------------------------------- /public/javascripts/holder.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Holder - 2.0 - client side image placeholders 4 | (c) 2012-2013 Ivan Malopinsky / http://imsky.co 5 | 6 | Provided under the Apache 2.0 License: http://www.apache.org/licenses/LICENSE-2.0 7 | Commercial use requires attribution. 8 | 9 | */ 10 | 11 | var Holder = Holder || {}; 12 | (function (app, win) { 13 | 14 | var preempted = false, 15 | fallback = false, 16 | canvas = document.createElement('canvas'); 17 | 18 | //getElementsByClassName polyfill 19 | document.getElementsByClassName||(document.getElementsByClassName=function(e){var t=document,n,r,i,s=[];if(t.querySelectorAll)return t.querySelectorAll("."+e);if(t.evaluate){r=".//*[contains(concat(' ', @class, ' '), ' "+e+" ')]",n=t.evaluate(r,t,null,0,null);while(i=n.iterateNext())s.push(i)}else{n=t.getElementsByTagName("*"),r=new RegExp("(^|\\s)"+e+"(\\s|$)");for(i=0;i= 0.75) { 72 | text_height = Math.floor(text_height * 0.75 * (width/text_width)); 73 | } 74 | //Resetting font size if necessary 75 | ctx.font = "bold " + (text_height * ratio) + "px " + font; 76 | ctx.fillText(text, (width / 2), (height / 2), width); 77 | return canvas.toDataURL("image/png"); 78 | } 79 | 80 | function render(mode, el, holder, src) { 81 | var dimensions = holder.dimensions, 82 | theme = holder.theme, 83 | text = holder.text ? decodeURIComponent(holder.text) : holder.text; 84 | var dimensions_caption = dimensions.width + "x" + dimensions.height; 85 | theme = (text ? extend(theme, { 86 | text: text 87 | }) : theme); 88 | theme = (holder.font ? extend(theme, { 89 | font: holder.font 90 | }) : theme); 91 | if (mode == "image") { 92 | el.setAttribute("data-src", src); 93 | el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); 94 | if (fallback || !holder.auto) { 95 | el.style.width = dimensions.width + "px"; 96 | el.style.height = dimensions.height + "px"; 97 | } 98 | if (fallback) { 99 | el.style.backgroundColor = theme.background; 100 | } else { 101 | el.setAttribute("src", draw(ctx, dimensions, theme, ratio)); 102 | } 103 | } else if (mode == "background") { 104 | if (!fallback) { 105 | el.style.backgroundImage = "url(" + draw(ctx, dimensions, theme, ratio) + ")"; 106 | el.style.backgroundSize = dimensions.width + "px " + dimensions.height + "px"; 107 | } 108 | } else if (mode == "fluid") { 109 | el.setAttribute("data-src", src); 110 | el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); 111 | if (dimensions.height.substr(-1) == "%") { 112 | el.style.height = dimensions.height 113 | } else { 114 | el.style.height = dimensions.height + "px" 115 | } 116 | if (dimensions.width.substr(-1) == "%") { 117 | el.style.width = dimensions.width 118 | } else { 119 | el.style.width = dimensions.width + "px" 120 | } 121 | if (el.style.display == "inline" || el.style.display == "") { 122 | el.style.display = "block"; 123 | } 124 | if (fallback) { 125 | el.style.backgroundColor = theme.background; 126 | } else { 127 | el.holderData = holder; 128 | fluid_images.push(el); 129 | fluid_update(el); 130 | } 131 | } 132 | }; 133 | 134 | function fluid_update(element) { 135 | var images; 136 | if (element.nodeType == null) { 137 | images = fluid_images; 138 | } else { 139 | images = [element] 140 | } 141 | for (i in images) { 142 | var el = images[i] 143 | if (el.holderData) { 144 | var holder = el.holderData; 145 | el.setAttribute("src", draw(ctx, { 146 | height: el.clientHeight, 147 | width: el.clientWidth 148 | }, holder.theme, ratio)); 149 | } 150 | } 151 | } 152 | 153 | function parse_flags(flags, options) { 154 | 155 | var ret = { 156 | theme: settings.themes.gray 157 | }, render = false; 158 | 159 | for (sl = flags.length, j = 0; j < sl; j++) { 160 | var flag = flags[j]; 161 | if (app.flags.dimensions.match(flag)) { 162 | render = true; 163 | ret.dimensions = app.flags.dimensions.output(flag); 164 | } else if (app.flags.fluid.match(flag)) { 165 | render = true; 166 | ret.dimensions = app.flags.fluid.output(flag); 167 | ret.fluid = true; 168 | } else if (app.flags.colors.match(flag)) { 169 | ret.theme = app.flags.colors.output(flag); 170 | } else if (options.themes[flag]) { 171 | //If a theme is specified, it will override custom colors 172 | ret.theme = options.themes[flag]; 173 | } else if (app.flags.text.match(flag)) { 174 | ret.text = app.flags.text.output(flag); 175 | } else if (app.flags.font.match(flag)) { 176 | ret.font = app.flags.font.output(flag); 177 | } else if (app.flags.auto.match(flag)) { 178 | ret.auto = true; 179 | } 180 | } 181 | 182 | return render ? ret : false; 183 | 184 | }; 185 | 186 | 187 | 188 | if (!canvas.getContext) { 189 | fallback = true; 190 | } else { 191 | if (canvas.toDataURL("image/png") 192 | .indexOf("data:image/png") < 0) { 193 | //Android doesn't support data URI 194 | fallback = true; 195 | } else { 196 | var ctx = canvas.getContext("2d"); 197 | } 198 | } 199 | 200 | var dpr = 1, bsr = 1; 201 | 202 | if(!fallback){ 203 | dpr = window.devicePixelRatio || 1, 204 | bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; 205 | } 206 | 207 | var ratio = dpr / bsr; 208 | 209 | var fluid_images = []; 210 | 211 | var settings = { 212 | domain: "holder.js", 213 | images: "img", 214 | bgnodes: ".holderjs", 215 | themes: { 216 | "gray": { 217 | background: "#eee", 218 | foreground: "#aaa", 219 | size: 12 220 | }, 221 | "social": { 222 | background: "#3a5a97", 223 | foreground: "#fff", 224 | size: 12 225 | }, 226 | "industrial": { 227 | background: "#434A52", 228 | foreground: "#C2F200", 229 | size: 12 230 | } 231 | }, 232 | stylesheet: "" 233 | }; 234 | 235 | 236 | app.flags = { 237 | dimensions: { 238 | regex: /^(\d+)x(\d+)$/, 239 | output: function (val) { 240 | var exec = this.regex.exec(val); 241 | return { 242 | width: +exec[1], 243 | height: +exec[2] 244 | } 245 | } 246 | }, 247 | fluid: { 248 | regex: /^([0-9%]+)x([0-9%]+)$/, 249 | output: function (val) { 250 | var exec = this.regex.exec(val); 251 | return { 252 | width: exec[1], 253 | height: exec[2] 254 | } 255 | } 256 | }, 257 | colors: { 258 | regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i, 259 | output: function (val) { 260 | var exec = this.regex.exec(val); 261 | return { 262 | size: settings.themes.gray.size, 263 | foreground: "#" + exec[2], 264 | background: "#" + exec[1] 265 | } 266 | } 267 | }, 268 | text: { 269 | regex: /text\:(.*)/, 270 | output: function (val) { 271 | return this.regex.exec(val)[1]; 272 | } 273 | }, 274 | font: { 275 | regex: /font\:(.*)/, 276 | output: function (val) { 277 | return this.regex.exec(val)[1]; 278 | } 279 | }, 280 | auto: { 281 | regex: /^auto$/ 282 | } 283 | } 284 | 285 | for (var flag in app.flags) { 286 | if (!app.flags.hasOwnProperty(flag)) continue; 287 | app.flags[flag].match = function (val) { 288 | return val.match(this.regex) 289 | } 290 | } 291 | 292 | app.add_theme = function (name, theme) { 293 | name != null && theme != null && (settings.themes[name] = theme); 294 | return app; 295 | }; 296 | 297 | app.add_image = function (src, el) { 298 | var node = selector(el); 299 | if (node.length) { 300 | for (var i = 0, l = node.length; i < l; i++) { 301 | var img = document.createElement("img") 302 | img.setAttribute("data-src", src); 303 | node[i].appendChild(img); 304 | } 305 | } 306 | return app; 307 | }; 308 | 309 | app.run = function (o) { 310 | var options = extend(settings, o), 311 | images = [], imageNodes = [], bgnodes = []; 312 | 313 | if(typeof(options.images) == "string"){ 314 | imageNodes = selector(options.images); 315 | } 316 | else if (window.NodeList && options.images instanceof window.NodeList) { 317 | imageNodes = options.images; 318 | } else if (window.Node && options.images instanceof window.Node) { 319 | imageNodes = [options.images]; 320 | } 321 | 322 | if(typeof(options.bgnodes) == "string"){ 323 | bgnodes = selector(options.bgnodes); 324 | } else if (window.NodeList && options.elements instanceof window.NodeList) { 325 | bgnodes = options.bgnodes; 326 | } else if (window.Node && options.bgnodes instanceof window.Node) { 327 | bgnodes = [options.bgnodes]; 328 | } 329 | 330 | preempted = true; 331 | 332 | for (i = 0, l = imageNodes.length; i < l; i++) images.push(imageNodes[i]); 333 | 334 | var holdercss = document.getElementById("holderjs-style"); 335 | if (!holdercss) { 336 | holdercss = document.createElement("style"); 337 | holdercss.setAttribute("id", "holderjs-style"); 338 | holdercss.type = "text/css"; 339 | document.getElementsByTagName("head")[0].appendChild(holdercss); 340 | } 341 | 342 | if (!options.nocss) { 343 | if (holdercss.styleSheet) { 344 | holdercss.styleSheet.cssText += options.stylesheet; 345 | } else { 346 | holdercss.appendChild(document.createTextNode(options.stylesheet)); 347 | } 348 | } 349 | 350 | var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)"); 351 | 352 | for (var l = bgnodes.length, i = 0; i < l; i++) { 353 | var src = window.getComputedStyle(bgnodes[i], null) 354 | .getPropertyValue("background-image"); 355 | var flags = src.match(cssregex); 356 | var bgsrc = bgnodes[i].getAttribute("data-background-src"); 357 | 358 | if (flags) { 359 | var holder = parse_flags(flags[1].split("/"), options); 360 | if (holder) { 361 | render("background", bgnodes[i], holder, src); 362 | } 363 | } 364 | else if(bgsrc != null){ 365 | var holder = parse_flags(bgsrc.substr(bgsrc.lastIndexOf(options.domain) + options.domain.length + 1) 366 | .split("/"), options); 367 | if(holder){ 368 | render("background", bgnodes[i], holder, src); 369 | } 370 | } 371 | } 372 | 373 | for (l = images.length, i = 0; i < l; i++) { 374 | 375 | var attr_src = attr_data_src = src = null; 376 | 377 | try{ 378 | attr_src = images[i].getAttribute("src"); 379 | attr_datasrc = images[i].getAttribute("data-src"); 380 | }catch(e){} 381 | 382 | if (attr_datasrc == null && !! attr_src && attr_src.indexOf(options.domain) >= 0) { 383 | src = attr_src; 384 | } else if ( !! attr_datasrc && attr_datasrc.indexOf(options.domain) >= 0) { 385 | src = attr_datasrc; 386 | } 387 | 388 | if (src) { 389 | var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1) 390 | .split("/"), options); 391 | if (holder) { 392 | if (holder.fluid) { 393 | render("fluid", images[i], holder, src) 394 | } else { 395 | render("image", images[i], holder, src); 396 | } 397 | } 398 | } 399 | } 400 | return app; 401 | }; 402 | 403 | contentLoaded(win, function () { 404 | if (window.addEventListener) { 405 | window.addEventListener("resize", fluid_update, false); 406 | window.addEventListener("orientationchange", fluid_update, false); 407 | } else { 408 | window.attachEvent("onresize", fluid_update) 409 | } 410 | preempted || app.run(); 411 | }); 412 | 413 | if (typeof define === "function" && define.amd) { 414 | define("Holder", [], function () { 415 | return app; 416 | }); 417 | } 418 | 419 | })(Holder, window); 420 | -------------------------------------------------------------------------------- /public/javascripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-form", 3 | "title": "jQuery Form Plugin", 4 | "description": "A simple way to AJAX-ify any form on your page; with file upload and progress support.", 5 | "keywords": [ 6 | "form", 7 | "upload", 8 | "ajax" 9 | ], 10 | "version": "3.40.0", 11 | "author": { 12 | "name": "M. Alsup", 13 | "url": "http://jquery.malsup.com" 14 | }, 15 | "licenses": [ 16 | { 17 | "type": "MIT", 18 | "url": "http://malsup.github.com/mit-license.txt" 19 | }, 20 | { 21 | "type": "GPL", 22 | "url": "http://malsup.github.com/gpl-license-v2.txt" 23 | } 24 | ], 25 | "bugs": "https://github.com/malsup/form/issues", 26 | "homepage": "http://jquery.malsup.com/form/", 27 | "docs": "http://jquery.malsup.com/form/", 28 | "download": "http://malsup.github.com/jquery.form.js", 29 | "dependencies": { 30 | "jquery": ">=1.5" 31 | }, 32 | "jam": { 33 | "main": "jquery.form.js", 34 | "dependencies": { 35 | "jquery": ">=1.5.0" 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /public/javascripts/socketed-transport.js: -------------------------------------------------------------------------------- 1 | ;(function (window, document) { 2 | 'use strict'; 3 | 4 | window.VideoCapture = function(elements, success, error) { 5 | 6 | // Define our error message 7 | function sendError(message) { 8 | if (error) { 9 | var e = new Error(); 10 | e.message = message; 11 | error(e); 12 | } else { 13 | console.error(message); 14 | } 15 | } 16 | 17 | // Try to play the media stream 18 | function play() { 19 | var video = document.getElementById(elements.video); 20 | if (!video) { 21 | sendError('Unable to find the video element.'); 22 | return; 23 | } 24 | 25 | function successCallback(stream) { 26 | // Set the source of the video element with the stream from the camera 27 | if (video.mozSrcObject !== undefined) { 28 | video.mozSrcObject = stream; 29 | } else { 30 | video.src = (window.URL && window.URL.createObjectURL(stream)) || stream; 31 | } 32 | video.play(); 33 | } 34 | 35 | function errorCallback(error) { 36 | sendError('Unable to get webcam stream.'); 37 | } 38 | 39 | navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; 40 | window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; 41 | 42 | // Call the getUserMedia method with our callback functions 43 | if (navigator.getUserMedia) { 44 | navigator.getUserMedia({video: true}, successCallback, errorCallback); 45 | } else { 46 | sendError('Native web camera streaming (getUserMedia) not supported in this browser.'); 47 | } 48 | 49 | // Check the video dimensions when data has loaded 50 | video.addEventListener('loadeddata', function() { 51 | var attempts = 10; 52 | 53 | function checkVideo() { 54 | if (attempts > 0) { 55 | if (video.videoWidth > 0 && video.videoHeight > 0) { 56 | // Execute success callback function 57 | if (success) success(video); 58 | } else { 59 | // Wait a bit and try again 60 | window.setTimeout(checkVideo, 500); 61 | } 62 | } else { 63 | // Give up after 10 attempts 64 | sendError('Unable to play video stream. Is webcam working?'); 65 | } 66 | attempts--; 67 | } 68 | 69 | checkVideo(); 70 | }, false); 71 | } 72 | 73 | return { 74 | play: play 75 | }; 76 | }; 77 | })(window, document); 78 | 79 | 80 | function dataURItoBlob(dataURI) 81 | { 82 | // convert base64 to raw binary data held in a string 83 | // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this 84 | var comp = dataURI.split(','); 85 | 86 | var byteString = window.atob(comp[1]); 87 | 88 | // separate out the mime component 89 | var mimeString = comp[0].split(':')[1].split(';')[0]; 90 | 91 | // write the bytes of the string to an ArrayBuffer 92 | var ab = new ArrayBuffer(byteString.length); 93 | var ia = new Uint8Array(ab); 94 | var i; 95 | 96 | for (i = 0; i < byteString.length; i++) 97 | { 98 | ia[i] = byteString.charCodeAt(i); 99 | } 100 | 101 | return new Blob([ab], { type: mimeString }); 102 | } 103 | 104 | function setupCapture_canvas(video, socket, dataReady) { 105 | 106 | var interval = 30; 107 | var canvas = document.createElement("canvas"); 108 | 109 | canvas.width = videoElement.videoWidth; 110 | canvas.height = videoElement.videoHeight; 111 | console.log("Created canvas of size " + canvas.width + ";" + canvas.height); 112 | 113 | canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height); 114 | var dataUrl = canvas.toDataURL("image/jpeg"); 115 | socket.emit('frame', dataUrl); 116 | 117 | socket.on('nextFrame', function(data) { 118 | canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height); 119 | var dataUrl = canvas.toDataURL("image/jpeg"); 120 | socket.emit('frame', dataUrl); 121 | }); 122 | 123 | socket.on('result', function (data) { 124 | $("#faces").attr('src', data.color.histogramImage); 125 | $("#dominantColorsImage").attr('src', data.color.dominantColorsImage); 126 | 127 | }); 128 | } -------------------------------------------------------------------------------- /public/javascripts/twitter-share.js: -------------------------------------------------------------------------------- 1 | !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs'); 2 | -------------------------------------------------------------------------------- /public/javascripts/upload.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | status('Choose a file :)'); 4 | 5 | // Check to see when a user has selected a file 6 | var timerId; 7 | timerId = setInterval(function() { 8 | if($('#userPhotoInput').val() !== '') { 9 | clearInterval(timerId); 10 | 11 | $('#uploadForm').submit(); 12 | } 13 | }, 500); 14 | 15 | $('#uploadForm').submit(function() { 16 | status('uploading the file ...'); 17 | 18 | $(this).ajaxSubmit({ 19 | 20 | error: function(xhr) { 21 | status('Error: ' + xhr.status); 22 | }, 23 | 24 | success: function(response) { 25 | //TODO: We will fill this in later 26 | } 27 | }); 28 | 29 | // Have to stop the form from submitting and causing 30 | // a page refresh - don't forget this 31 | return false; 32 | }); 33 | 34 | function status(message) { 35 | $('#status').text(message); 36 | } 37 | }); -------------------------------------------------------------------------------- /public/stylesheets/github-forkme.css: -------------------------------------------------------------------------------- 1 | #forkongithub a{background:#000;color:#fff;text-decoration:none;text-align:center;font-weight:bold;padding:5px 40px;font-size:1.2rem;line-height:2rem;position:relative;transition:0.5s;z-index: 1110;} 2 | #forkongithub a:hover{background:#060;color:#fff;} 3 | #forkongithub a::before,#forkongithub a::after{content:"";width:100%;display:block;position:absolute;top:1px;left:0;height:1px;background:#fff;} 4 | #forkongithub a::after{bottom:1px;top:auto;} 5 | 6 | @media screen and (min-width:800px){#forkongithub{position:absolute;display:block;top:0;right:0;width:200px;overflow:hidden;height:200px;} 7 | #forkongithub a{width:200px;position:absolute;top:39px;right:-50px;transform:rotate(45deg);-webkit-transform:rotate(45deg);box-shadow:4px 4px 10px rgba(0,0,0,0.8);}} 8 | -------------------------------------------------------------------------------- /public/stylesheets/template.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 0px; 3 | padding-bottom: 75px; 4 | } 5 | 6 | .starter-template { 7 | padding: 40px 15px; 8 | text-align: center; 9 | } 10 | 11 | .bs-docs-example 12 | { 13 | position: relative; 14 | margin: 15px 0; 15 | padding: 39px 19px 14px; 16 | background-color: white; 17 | border: 1px solid #DDD; 18 | -webkit-border-radius: 4px; 19 | -moz-border-radius: 4px; 20 | border-radius: 4px; 21 | } 22 | 23 | .bs-docs-example::after 24 | { 25 | content: "Example"; 26 | position: absolute; 27 | top: -1px; 28 | left: -1px; 29 | padding: 3px 7px; 30 | font-size: 12px; 31 | font-weight: bold; 32 | background-color: whiteSmoke; 33 | border: 1px solid #DDD; 34 | color: #9DA0A4; 35 | -webkit-border-radius: 4px 0 4px 0; 36 | -moz-border-radius: 4px 0 4px 0; 37 | border-radius: 4px 0 4px 0; 38 | } 39 | 40 | .img-select-viewport { 41 | width: 128px; 42 | height: 128px; 43 | } 44 | 45 | .img-select-viewport { 46 | clear: both; 47 | position: relative; 48 | cursor: pointer; 49 | overflow: hidden; 50 | } 51 | 52 | 53 | .img-select-viewport canvas#file-preview { 54 | position: absolute; 55 | top: 0; 56 | left: 0; 57 | cursor: pointer; 58 | width: 100%; 59 | height: 100%; 60 | } 61 | 62 | .img-select-viewport input#file-selector { 63 | position: absolute; 64 | top: 0; 65 | left: 0; 66 | width: 100%; 67 | height: 100%; 68 | opacity: 0; 69 | cursor: pointer; 70 | } 71 | 72 | /* Side notes for calling out things 73 | -------------------------------------------------- */ 74 | 75 | /* Base styles (regardless of theme) */ 76 | .bs-callout { 77 | margin: 20px 0; 78 | padding: 15px 30px 15px 15px; 79 | border-left: 5px solid #eee; 80 | } 81 | .bs-callout h4 { 82 | margin-top: 0; 83 | } 84 | .bs-callout p:last-child { 85 | margin-bottom: 0; 86 | } 87 | .bs-callout code, 88 | .bs-callout .highlight { 89 | background-color: #fff; 90 | } 91 | 92 | /* Themes for different contexts */ 93 | .bs-callout-danger { 94 | background-color: #fcf2f2; 95 | border-color: #dFb5b4; 96 | } 97 | .bs-callout-warning { 98 | background-color: #fefbed; 99 | border-color: #f1e7bc; 100 | } 101 | .bs-callout-info { 102 | background-color: #f0f7fd; 103 | border-color: #d0e3f0; 104 | } 105 | 106 | #dropZone { 107 | color: #555; 108 | font-size: 18px; 109 | text-align: center; 110 | 111 | width: 400px; 112 | padding: 50px 0; 113 | margin: 50px auto; 114 | 115 | background: #eee; 116 | border: 1px solid #ccc; 117 | 118 | -webkit-border-radius: 5px; 119 | -moz-border-radius: 5px; 120 | border-radius: 5px; 121 | } 122 | 123 | #dropZone.hover { 124 | background: #ddd; 125 | border-color: #aaa; 126 | } 127 | 128 | #dropZone.error { 129 | background: #faa; 130 | border-color: #f00; 131 | } 132 | 133 | #dropZone.drop { 134 | background: #afa; 135 | border-color: #0f0; 136 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | var pmx = require('pmx'); pmx.init(); 15 | var util = require('util'); 16 | 17 | var express = require('express') 18 | , http = require('http') 19 | , path = require('path') 20 | , fs = require('fs') 21 | , cookieParser = require('cookie-parser') 22 | , multer = require('multer') 23 | , methodOverride = require('method-override') 24 | , async = require('async') 25 | , request = require('request') 26 | , cv = require('./cloudcv.js') 27 | , validator = require('validator') 28 | , logger = require('./lib/logger.js') 29 | , download = require('./lib/download.js') 30 | , config = require('./lib/config.js') 31 | , inspect = require('util').inspect 32 | , swagger = require("./lib/swaggerSpec.js"); 33 | ; 34 | var app = express(); 35 | 36 | var multerOptions = { 37 | inMemory:true, 38 | limits: { 39 | fileSize: config.maxFileSize, 40 | files: 1 41 | } 42 | } 43 | 44 | // Configuration 45 | app.set('port', process.env.PORT || 3000); 46 | app.set('title', 'cloudcv-bootstrap'); 47 | app.set('views', __dirname + '/views'); 48 | app.set('view engine', 'jade'); 49 | app.set('view options', { pretty: true, layout: false }); 50 | 51 | //app.use(json); 52 | app.use(methodOverride()); 53 | app.use(multer(multerOptions)); 54 | app.use(cookieParser('optional secret string')); 55 | app.use(pmx.expressErrorHandler()); 56 | app.use(express.static(path.join(__dirname, 'public'))); 57 | 58 | // Static pages 59 | 60 | var api_docs = []; 61 | var algs = cv.getAlgorithms(); 62 | for (var i = algs.length - 1; i >= 0; i--) 63 | { 64 | var info = cv.getInfo(algs[i]); 65 | api_docs.push(info); 66 | console.log(info.name) 67 | }; 68 | 69 | 70 | 71 | 72 | 73 | app.get('/', function (req, res) { res.render('index'); }); 74 | app.get('/docs', function (req, res) { res.render('docs', { api: api_docs}); }); 75 | app.get('/docs/:algorithmName', function (req, res) { res.render('docs', { api: api_docs, currentAlgorithm: req.params.algorithmName}); }); 76 | 77 | // Specifications: 78 | app.get('/swagger.json', function (req, res) { res.json(swagger.getSpec(algs)); }); 79 | 80 | // Bind handlers: 81 | function createHandler(method) { 82 | return function(req, res) { 83 | console.log('Called handler for ' + method); 84 | console.log(util.inspect(req.body)); 85 | console.log(util.inspect(req.params)); 86 | 87 | var inArgs = new Object(); 88 | 89 | Object.keys(req.params).forEach(function(key) { 90 | console.log(key +" is " + req.params[key] ); 91 | inArgs[key] = req.params[key]; 92 | }); 93 | 94 | Object.keys(req.files).forEach(function(key) { 95 | console.log(key +" is " + req.files[key] ); 96 | inArgs[key] = req.files[key].buffer; 97 | }); 98 | 99 | /* 100 | console.log('Arguments:', util.inspect(inArgs)); 101 | Object.keys(req.body).forEach(function(key) { 102 | console.log(key +" is " + req.body[key] ) 103 | inArgs[key] = req.body[key]; 104 | }); 105 | */ 106 | 107 | console.log('Arguments:', util.inspect(inArgs)); 108 | cv[method](inArgs, function(error, result) { 109 | if (error) { 110 | console.log('Error returned'); 111 | res.send(error); 112 | } 113 | else if (result) { 114 | console.log('Error returned'); 115 | res.send(result); 116 | } 117 | res.end(); 118 | }); 119 | }; 120 | } 121 | 122 | for (i = algs.length - 1; i >= 0; i--) 123 | { 124 | var info = cv.getInfo(algs[i]); 125 | var algorithmName = info.name; 126 | 127 | app.post('/api/' + algorithmName, createHandler(algorithmName)); 128 | } 129 | 130 | 131 | // Peace to everyone! 132 | app.use(function(req, res, next) { 133 | res.header("Access-Control-Allow-Origin", "*"); 134 | res.header("Access-Control-Allow-Headers", "X-Requested-With"); 135 | next(); 136 | }); 137 | 138 | logger.info("cloudcv-bootstrap server starting on port " + app.get('port')); 139 | 140 | http.createServer(app).listen(app.get('port'), function(){ 141 | logger.info("cloudcv-bootstrap server listening on port " + app.get('port')); 142 | }); -------------------------------------------------------------------------------- /src/cloudcv.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | #include "framework/marshal/marshal.hpp" 15 | #include "modules/HoughLines.hpp" 16 | #include "modules/IntegralImage.hpp" 17 | #include 18 | 19 | using namespace cloudcv; 20 | using Nan::GetFunction; 21 | using Nan::New; 22 | using Nan::Set; 23 | 24 | #if TARGET_PLATFORM_UNIX || TARGET_PLATFORM_MAC 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | void handler(int sig) 32 | { 33 | void *array[10]; 34 | size_t size; 35 | 36 | // get void*'s for all entries on the stack 37 | size = backtrace(array, 10); 38 | 39 | // print out all the frames to stderr 40 | fprintf(stderr, "Error: signal %d:\n", sig); 41 | backtrace_symbols_fd(array, size, STDERR_FILENO); 42 | exit(1); 43 | } 44 | #endif 45 | 46 | NAN_METHOD(getAlgorithms) 47 | { 48 | std::vector algorithms; 49 | 50 | for (auto alg : AlgorithmInfo::Get()) 51 | { 52 | algorithms.push_back(alg.first); 53 | } 54 | 55 | info.GetReturnValue().Set(Nan::Marshal(algorithms)); 56 | } 57 | 58 | NAN_METHOD(getInfo) 59 | { 60 | Nan::EscapableHandleScope scope; 61 | 62 | std::string algorithmName; 63 | std::string errorMessage; 64 | 65 | if (Nan::Check(info).ArgumentsCount(1) 66 | .Argument(0).IsString().Bind(algorithmName) 67 | .Error(&errorMessage)) 68 | { 69 | auto algorithm = AlgorithmInfo::Get().find(algorithmName); 70 | if (algorithm == AlgorithmInfo::Get().end()) 71 | { 72 | info.GetReturnValue().Set(Nan::Null()); 73 | return; 74 | } 75 | 76 | info.GetReturnValue().Set(Nan::Marshal(*algorithm->second.get())); 77 | } 78 | else 79 | { 80 | LOG_TRACE_MESSAGE(errorMessage); 81 | Nan::ThrowTypeError(errorMessage.c_str()); 82 | return; 83 | } 84 | } 85 | 86 | NAN_METHOD(processFunction) 87 | { 88 | Nan::HandleScope scope; 89 | 90 | std::string algorithmName; 91 | std::string errorMessage; 92 | v8::Local inputArguments; 93 | v8::Local resultsCallback; 94 | 95 | if (Nan::Check(info).ArgumentsCount(3) 96 | .Argument(0).IsString().Bind(algorithmName) 97 | .Argument(1).IsObject().Bind(inputArguments) 98 | .Argument(2).IsFunction().Bind(resultsCallback) 99 | .Error(&errorMessage)) 100 | { 101 | auto algorithm = AlgorithmInfo::Get().find(algorithmName); 102 | if (algorithm == AlgorithmInfo::Get().end()) 103 | { 104 | v8::Local argv[] = { Nan::Error("Algorithm not found"), Nan::Null() }; 105 | Nan::Callback(resultsCallback).Call(2, argv); 106 | return; 107 | } 108 | 109 | ProcessAlgorithm(algorithm->second, inputArguments, resultsCallback); 110 | } 111 | else 112 | { 113 | LOG_TRACE_MESSAGE(errorMessage); 114 | Nan::ThrowTypeError(errorMessage.c_str()); 115 | return; 116 | } 117 | } 118 | 119 | 120 | NAN_MODULE_INIT(RegisterModule) 121 | { 122 | #if TARGET_PLATFORM_UNIX || TARGET_PLATFORM_MAC 123 | signal(SIGSEGV, handler); // install our handler 124 | #endif 125 | 126 | AlgorithmInfo::Register(new HoughLinesAlgorithmInfo); 127 | AlgorithmInfo::Register(new IntegralImageAlgorithmInfo); 128 | 129 | Set(target, 130 | New("getAlgorithms").ToLocalChecked(), 131 | GetFunction(New(getAlgorithms)).ToLocalChecked()); 132 | 133 | Set(target, 134 | New("processFunction").ToLocalChecked(), 135 | GetFunction(New(processFunction)).ToLocalChecked()); 136 | 137 | Set(target, 138 | New("getInfo").ToLocalChecked(), 139 | GetFunction(New(getInfo)).ToLocalChecked()); 140 | } 141 | 142 | NODE_MODULE(cloudcv, RegisterModule); 143 | -------------------------------------------------------------------------------- /src/framework/Algorithm.cpp: -------------------------------------------------------------------------------- 1 | #include "framework/Algorithm.hpp" 2 | #include "framework/Logger.hpp" 3 | #include "framework/ScopedTimer.hpp" 4 | #include "framework/Job.hpp" 5 | #include "framework/marshal/marshal.hpp" 6 | //#include "framework/NanCheck.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace cloudcv 13 | { 14 | class AlgorithmTask : public Job 15 | { 16 | AlgorithmPtr m_algorithm; 17 | std::map m_input; 18 | std::map m_output; 19 | 20 | public: 21 | 22 | AlgorithmTask( 23 | AlgorithmPtr alg, 24 | std::map inArgs, 25 | std::map outArgs, 26 | Nan::Callback * callback) 27 | : Job(callback) 28 | , m_algorithm(alg) 29 | , m_input(inArgs) 30 | , m_output(outArgs) 31 | { 32 | TRACE_FUNCTION; 33 | LOG_TRACE_MESSAGE("Input arguments:" << inArgs.size()); 34 | LOG_TRACE_MESSAGE("Output arguments:" << outArgs.size()); 35 | } 36 | 37 | protected: 38 | 39 | // This function is executed in another thread at some point after it has been 40 | // scheduled. IT MUST NOT USE ANY V8 FUNCTIONALITY. Otherwise your extension 41 | // will crash randomly and you'll have a lot of fun debugging. 42 | // If you want to use parameters passed into the original call, you have to 43 | // convert them to PODs or some other fancy method. 44 | void ExecuteNativeCode() override 45 | { 46 | try 47 | { 48 | TRACE_FUNCTION; 49 | m_algorithm->process(m_input, m_output); 50 | } 51 | catch (ArgumentException& err) 52 | { 53 | LOG_TRACE_MESSAGE("ArgumentException:" << err.what()); 54 | SetErrorMessage(err.what()); 55 | } 56 | catch (cv::Exception& err) 57 | { 58 | LOG_TRACE_MESSAGE("cv::Exception:" << err.what()); 59 | SetErrorMessage(err.what()); 60 | } 61 | catch (std::runtime_error& err) 62 | { 63 | LOG_TRACE_MESSAGE("std::runtime_error:" << err.what()); 64 | SetErrorMessage(err.what()); 65 | } 66 | } 67 | 68 | // This function is executed in the main V8/JavaScript thread. That means it's 69 | // safe to use V8 functions again. Don't forget the HandleScope! 70 | v8::Local CreateCallbackResult() override 71 | { 72 | TRACE_FUNCTION; 73 | 74 | Nan::EscapableHandleScope scope; 75 | 76 | v8::Local outputArgument = Nan::New(); 77 | 78 | for (const auto& arg : m_output) 79 | { 80 | outputArgument->Set(Nan::Marshal(arg.first), arg.second->marshalFromNative()); 81 | } 82 | 83 | return scope.Escape(outputArgument); 84 | } 85 | }; 86 | 87 | 88 | void ProcessAlgorithm(AlgorithmInfoPtr algorithm, v8::Local inputArguments, v8::Local resultsCallback) 89 | { 90 | TRACE_FUNCTION; 91 | 92 | using namespace cloudcv; 93 | 94 | TRACE_FUNCTION; 95 | Nan::HandleScope scope; 96 | 97 | 98 | try 99 | { 100 | //Nan::TryCatch trycatch; 101 | 102 | auto info = algorithm; 103 | std::map inArgs, outArgs; 104 | 105 | for (auto arg : info->inputArguments()) 106 | { 107 | auto propertyName = Nan::Marshal(arg.first); 108 | v8::Local argumentValue = Nan::Null(); 109 | 110 | if (inputArguments->HasRealNamedProperty(propertyName->ToString())) 111 | argumentValue = inputArguments->Get(propertyName); 112 | 113 | LOG_TRACE_MESSAGE("Binding input argument " << arg.first); 114 | auto bind = arg.second->bind(argumentValue); 115 | 116 | inArgs.insert(std::make_pair(arg.first, bind)); 117 | } 118 | 119 | for (auto arg : info->outputArguments()) 120 | { 121 | LOG_TRACE_MESSAGE("Binding output argument " << arg.first); 122 | auto bind = arg.second->bind(); 123 | outArgs.insert(std::make_pair(arg.first, bind)); 124 | } 125 | 126 | //if (trycatch.HasCaught()) 127 | //{ 128 | // //auto msg = marshal(trycatch.Message()->Get()); 129 | // //LOG_TRACE_MESSAGE(msg); 130 | //} 131 | 132 | //if (trycatch.CanContinue()) 133 | { 134 | Nan::Callback * callback = new Nan::Callback(resultsCallback); 135 | Nan::AsyncQueueWorker(new AlgorithmTask(algorithm->create(), inArgs, outArgs, callback)); 136 | } 137 | } 138 | catch (cv::Exception& er) 139 | { 140 | LOG_TRACE_MESSAGE(er.what()); 141 | std::string error = er.what(); 142 | v8::Local argv[] = { Nan::Marshal(error), Nan::Null() }; 143 | Nan::Callback(resultsCallback).Call(2, argv); 144 | } 145 | catch (ArgumentException& er) 146 | { 147 | Nan::EscapableHandleScope scope; 148 | LOG_TRACE_MESSAGE(er.what()); 149 | std::string error = er.what(); 150 | v8::Local argv[] = { scope.Escape(Nan::Marshal(error)), Nan::Null() }; 151 | Nan::Callback(resultsCallback).Call(2, (argv)); 152 | } 153 | catch (std::runtime_error& er) 154 | { 155 | LOG_TRACE_MESSAGE(er.what()); 156 | std::string error = er.what(); 157 | v8::Local argv[] = { Nan::Marshal(error), Nan::Null() }; 158 | Nan::Callback(resultsCallback).Call(2, argv); 159 | } 160 | catch (...) 161 | { 162 | LOG_TRACE_MESSAGE("Unknown error"); 163 | } 164 | 165 | } 166 | 167 | 168 | 169 | } -------------------------------------------------------------------------------- /src/framework/Algorithm.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "framework/AlgorithmExceptions.hpp" 13 | #include "framework/AlgorithmInfo.hpp" 14 | #include "framework/Argument.hpp" 15 | 16 | #include "framework/ImageView.hpp" 17 | #include "framework/marshal/marshal.hpp" 18 | #include "framework/marshal/opencv.hpp" 19 | 20 | 21 | namespace cloudcv 22 | { 23 | class ParameterBinding; 24 | class AlgorithmInfo; 25 | 26 | typedef std::shared_ptr AlgorithmInfoPtr; 27 | typedef std::shared_ptr InputArgumentPtr; 28 | typedef std::shared_ptr OutputArgumentPtr; 29 | typedef std::shared_ptr ParameterBindingPtr; 30 | 31 | class Algorithm 32 | { 33 | public: 34 | virtual ~Algorithm() = default; 35 | virtual void process( 36 | const std::map& inArgs, 37 | const std::map& outArgs 38 | ) = 0; 39 | 40 | 41 | protected: 42 | 43 | 44 | template 45 | static inline const typename T::type& getInput(const std::map& inputArgs) 46 | { 47 | auto it = inputArgs.find(T::name()); 48 | 49 | if (inputArgs.end() == it) 50 | { 51 | throw ArgumentException(T::name(), "Cannot find parameter"); 52 | } 53 | 54 | auto binding = it->second; 55 | 56 | auto * bind = dynamic_cast*>(it->second.get()); 57 | if (bind == nullptr) 58 | { 59 | throw ArgumentTypeMismatchException(T::name(), binding->type(), TypedBinding::static_type()); 60 | } 61 | 62 | return bind->get(); 63 | } 64 | 65 | template 66 | static inline typename T::type& getOutput(const std::map& outputArgs) 67 | { 68 | auto it = outputArgs.find(T::name()); 69 | 70 | if (outputArgs.end() == it) 71 | { 72 | throw ArgumentException(T::name(), "Cannot find parameter"); 73 | } 74 | 75 | auto binding = it->second; 76 | 77 | auto * bind = dynamic_cast*>(it->second.get()); 78 | if (bind == nullptr) 79 | throw ArgumentTypeMismatchException(T::name(), binding->type(), TypedBinding::static_type()); 80 | 81 | return bind->get(); 82 | } 83 | }; 84 | 85 | typedef std::shared_ptr AlgorithmPtr; 86 | 87 | void ProcessAlgorithm(AlgorithmInfoPtr algorithm, v8::Local args, v8::Local resultsCallback); 88 | 89 | } -------------------------------------------------------------------------------- /src/framework/AlgorithmExceptions.cpp: -------------------------------------------------------------------------------- 1 | #include "framework/AlgorithmExceptions.hpp" 2 | 3 | namespace cloudcv 4 | { 5 | ArgumentException::ArgumentException(const std::string& argumentName, const std::string& message) 6 | : std::runtime_error("ArgumentException") 7 | , m_argumentName(argumentName) 8 | , m_message(message) 9 | { 10 | } 11 | 12 | const std::string& ArgumentException::argument() const CLOUDCV_NOTHROW 13 | { 14 | return m_argumentName; 15 | } 16 | 17 | const char * ArgumentException::what() const CLOUDCV_NOTHROW 18 | { 19 | return m_message.c_str(); 20 | } 21 | 22 | 23 | MissingInputArgumentException::MissingInputArgumentException(std::string argumentName) 24 | : ArgumentException(argumentName, "Missing argument") 25 | { 26 | } 27 | 28 | ArgumentTypeMismatchException::ArgumentTypeMismatchException(std::string argumentName, std::string actualType, std::string expectedType) 29 | : ArgumentException(argumentName, "Expected type " + expectedType + " got: " + actualType) 30 | { 31 | } 32 | 33 | ArgumentBindException::ArgumentBindException(std::string argumentName, std::string message) 34 | : ArgumentException(argumentName, message) 35 | { 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/framework/AlgorithmExceptions.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "framework/CompilerSupport.hpp" 7 | 8 | namespace cloudcv 9 | { 10 | class ArgumentException : public std::runtime_error 11 | { 12 | public: 13 | ArgumentException(const std::string& argumentName, const std::string& message); 14 | 15 | const std::string& argument() const CLOUDCV_NOTHROW; 16 | 17 | const char * what() const CLOUDCV_NOTHROW override; 18 | 19 | private: 20 | std::string m_argumentName; 21 | std::string m_message; 22 | }; 23 | 24 | class MissingInputArgumentException : public ArgumentException 25 | { 26 | public: 27 | MissingInputArgumentException(std::string argumentName); 28 | }; 29 | 30 | class ArgumentTypeMismatchException : public ArgumentException 31 | { 32 | public: 33 | ArgumentTypeMismatchException( 34 | std::string argumentName, 35 | std::string actualType, 36 | std::string expectedType); 37 | }; 38 | 39 | class ArgumentBindException : public ArgumentException 40 | { 41 | public: 42 | ArgumentBindException(std::string argumentName, std::string message); 43 | }; 44 | } -------------------------------------------------------------------------------- /src/framework/AlgorithmInfo.cpp: -------------------------------------------------------------------------------- 1 | #include "framework/AlgorithmInfo.hpp" 2 | #include "framework/AlgorithmExceptions.hpp" 3 | 4 | namespace cloudcv 5 | { 6 | AlgorithmInfo::AlgorithmInfo( 7 | const std::string& name, 8 | std::initializer_list> in, 9 | std::initializer_list> out) 10 | : m_name(name) 11 | 12 | { 13 | for (auto i : in) 14 | { 15 | auto res = m_inputParams.insert(i); 16 | if (!res.second) 17 | throw ArgumentException(i.first, "Duplicate argument name"); 18 | } 19 | 20 | for (auto o : out) 21 | { 22 | auto res = m_outputParams.insert(o); 23 | if (!res.second) 24 | throw ArgumentException(o.first, "Duplicate argument name"); 25 | } 26 | 27 | } 28 | 29 | std::map AlgorithmInfo::m_algorithms; 30 | 31 | void AlgorithmInfo::Register(AlgorithmInfo * info) 32 | { 33 | m_algorithms.insert(make_pair(info->name(), std::shared_ptr(info))); 34 | } 35 | 36 | const std::map& AlgorithmInfo::Get() 37 | { 38 | return m_algorithms; 39 | } 40 | } -------------------------------------------------------------------------------- /src/framework/AlgorithmInfo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "framework/Argument.hpp" 13 | #include "framework/marshal/marshal.hpp" 14 | 15 | namespace cloudcv 16 | { 17 | class Algorithm; 18 | class AlgorithmInfo; 19 | typedef std::shared_ptr AlgorithmInfoPtr; 20 | 21 | class AlgorithmInfo 22 | { 23 | public: 24 | inline std::string name() const 25 | { 26 | return m_name; 27 | } 28 | 29 | inline const std::map& inputArguments() const 30 | { 31 | return m_inputParams; 32 | } 33 | 34 | inline const std::map& outputArguments() const 35 | { 36 | return m_outputParams; 37 | } 38 | 39 | virtual std::shared_ptr create() const = 0; 40 | 41 | static void Register(AlgorithmInfo * info); 42 | 43 | static const std::map& Get(); 44 | 45 | 46 | protected: 47 | AlgorithmInfo( 48 | const std::string& name, 49 | std::initializer_list> in, 50 | std::initializer_list> out); 51 | 52 | private: 53 | std::string m_name; 54 | std::map m_inputParams; 55 | std::map m_outputParams; 56 | 57 | static std::map m_algorithms; 58 | }; 59 | } 60 | 61 | namespace Nan 62 | { 63 | namespace marshal 64 | { 65 | using namespace cloudcv; 66 | 67 | template<> 68 | struct Serializer 69 | { 70 | template 71 | static inline void load(InputArchive& ar, InputArgument& val) = delete; 72 | 73 | template 74 | static inline void save(OutputArchive& ar, const InputArgument& val) 75 | { 76 | val.serialize(ar); 77 | } 78 | }; 79 | 80 | template<> 81 | struct Serializer 82 | { 83 | template 84 | static inline void load(InputArchive& ar, OutputArgument& val) = delete; 85 | 86 | template 87 | static inline void save(OutputArchive& ar, const OutputArgument& val) 88 | { 89 | val.serialize(ar); 90 | } 91 | }; 92 | 93 | template<> 94 | struct Serializer 95 | { 96 | template 97 | static inline void load(InputArchive& ar, AlgorithmInfo& val) = delete; 98 | 99 | template 100 | static inline void save(OutputArchive& ar, const AlgorithmInfo& val) 101 | { 102 | ar & make_nvp("name", val.name()); 103 | ar & make_nvp("inputArguments", val.inputArguments()); 104 | ar & make_nvp("outputArguments", val.outputArguments()); 105 | } 106 | }; 107 | } 108 | } -------------------------------------------------------------------------------- /src/framework/Argument.cpp: -------------------------------------------------------------------------------- 1 | #include "framework/Argument.hpp" 2 | 3 | namespace cloudcv 4 | { 5 | 6 | 7 | 8 | } -------------------------------------------------------------------------------- /src/framework/Argument.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "framework/ImageView.hpp" 15 | #include "framework/marshal/opencv.hpp" 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | 22 | namespace cloudcv 23 | { 24 | template 25 | struct is_fixed_array : std::false_type { 26 | }; 27 | 28 | template 29 | struct is_fixed_array : std::true_type { 30 | }; 31 | 32 | template 33 | struct is_fixed_array< std::array > : std::true_type { 34 | }; 35 | 36 | template 37 | struct is_vector : std::false_type { 38 | }; 39 | 40 | template 41 | struct is_vector< std::vector > : std::true_type { 42 | }; 43 | 44 | template struct underlying_type { 45 | typedef T value_type; 46 | }; 47 | 48 | template struct underlying_type< std::vector > { 49 | typedef T value_type; 50 | }; 51 | 52 | template struct underlying_type { 53 | typedef T value_type; 54 | }; 55 | 56 | 57 | class InputArgument; 58 | class OutputArgument; 59 | class ParameterBinding; 60 | 61 | class ParameterBinding 62 | { 63 | public: 64 | virtual std::string type() const = 0; 65 | 66 | virtual ~ParameterBinding() = default; 67 | 68 | virtual v8::Local marshalFromNative() const = 0; 69 | }; 70 | 71 | template 72 | class TypedBinding : public ParameterBinding 73 | { 74 | public: 75 | TypedBinding(T value) 76 | : m_value(value) 77 | { 78 | } 79 | 80 | TypedBinding() 81 | { 82 | } 83 | 84 | static std::string static_type() { return typeid(T).name(); } 85 | 86 | std::string type() const override { return static_type(); } 87 | 88 | inline const T& get() const 89 | { 90 | return m_value; 91 | } 92 | 93 | inline T& get() 94 | { 95 | return m_value; 96 | } 97 | 98 | inline v8::Local marshalFromNative() const override 99 | { 100 | Nan::EscapableHandleScope scope; 101 | const T& val = get(); 102 | return scope.Escape(Nan::Marshal(val)); 103 | } 104 | 105 | private: 106 | T m_value; 107 | }; 108 | 109 | 110 | template 111 | std::shared_ptr wrap_as_bind(const T& value) 112 | { 113 | return std::shared_ptr(new TypedBinding(value)); 114 | } 115 | 116 | 117 | class InputArgument 118 | { 119 | public: 120 | virtual ~InputArgument() = default; 121 | 122 | virtual std::shared_ptr bind(v8::Local value) = 0; 123 | 124 | const std::string& name() const { return m_name; } 125 | const std::string& type() const { return m_type; } 126 | 127 | //! Serialize argument information 128 | virtual void serialize(Nan::marshal::SaveArchive& value) const = 0; 129 | 130 | protected: 131 | InputArgument(const std::string& name, const std::string& type) 132 | : m_name(name) 133 | , m_type(type) 134 | { 135 | } 136 | 137 | private: 138 | std::string m_name; 139 | std::string m_type; 140 | }; 141 | 142 | typedef std::shared_ptr InputArgumentPtr; 143 | 144 | class OutputArgument 145 | { 146 | public: 147 | 148 | virtual ~OutputArgument() = default; 149 | 150 | virtual std::shared_ptr bind() = 0; 151 | 152 | const std::string& name() const { return m_name; } 153 | const std::string& type() const { return m_type; } 154 | 155 | virtual void serialize(Nan::marshal::SaveArchive& value) const = 0; 156 | 157 | protected: 158 | OutputArgument(const std::string& name, const std::string& type) 159 | : m_name(name) 160 | , m_type(type) 161 | { 162 | } 163 | 164 | private: 165 | std::string m_name; 166 | std::string m_type; 167 | }; 168 | 169 | typedef std::shared_ptr OutputArgumentPtr; 170 | 171 | template 172 | class TypedOutputArgument : public OutputArgument 173 | { 174 | public: 175 | static std::pair Create(const char * name) 176 | { 177 | return std::make_pair(name, std::shared_ptr(new TypedOutputArgument(name))); 178 | } 179 | 180 | virtual std::shared_ptr bind() override 181 | { 182 | return wrap_as_bind(T()); 183 | } 184 | 185 | virtual void serialize(Nan::marshal::SaveArchive& value) const override 186 | { 187 | value & Nan::marshal::make_nvp("name", name()); 188 | 189 | using namespace std; 190 | 191 | if (is_fixed_array::value) 192 | { 193 | std::string elementType = typeid(typename underlying_type::value_type).name(); 194 | value & Nan::marshal::make_nvp("type", std::string("array")); 195 | //value & Nan::marshal::make_nvp("size", is_fixed_array::size()); 196 | value & Nan::marshal::make_nvp("elementType", elementType); 197 | } 198 | else if (is_vector::value) 199 | { 200 | std::string elementType = typeid(typename underlying_type::value_type).name(); 201 | value & Nan::marshal::make_nvp("type", std::string("array")); 202 | value & Nan::marshal::make_nvp("elementType", elementType); 203 | } 204 | else 205 | { 206 | value & Nan::marshal::make_nvp("type", type()); 207 | } 208 | } 209 | 210 | protected: 211 | TypedOutputArgument(const char * name) 212 | : OutputArgument(name, typeid(T).name()) 213 | { 214 | } 215 | }; 216 | 217 | 218 | 219 | template 220 | class RequiredArgument : public InputArgument 221 | { 222 | public: 223 | static inline std::pair Create(const char * name) 224 | { 225 | return std::make_pair(name, std::shared_ptr(new RequiredArgument(name))); 226 | } 227 | 228 | std::shared_ptr bind(v8::Local value) override 229 | { 230 | if (value->IsUndefined() || value->IsNull()) 231 | { 232 | throw ArgumentBindException(name(), "Missing required argument \"" + name() + "\""); 233 | } 234 | 235 | return wrap_as_bind(Nan::Marshal(value)); 236 | } 237 | 238 | //! Serialize argument information 239 | virtual void serialize(Nan::marshal::SaveArchive& value) const override 240 | { 241 | value & Nan::marshal::make_nvp("name", name()); 242 | value & Nan::marshal::make_nvp("type", type()); 243 | } 244 | 245 | protected: 246 | inline RequiredArgument(const char * name) 247 | : InputArgument(name, typeid(T).name()) 248 | { 249 | } 250 | }; 251 | 252 | 253 | template 254 | class RangedArgument : public InputArgument 255 | { 256 | public: 257 | static inline std::pair Create(const char * name, T minValue, T defaultValue, T maxValue) 258 | { 259 | return std::make_pair(name, std::shared_ptr(new RangedArgument(name, minValue, defaultValue, maxValue))); 260 | } 261 | 262 | std::shared_ptr bind(v8::Local value) override 263 | { 264 | if (value->IsUndefined() || value->IsNull()) 265 | { 266 | return wrap_as_bind(m_default); 267 | } 268 | 269 | return wrap_as_bind(validate(Nan::Marshal(value))); 270 | } 271 | 272 | //! Serialize argument information 273 | virtual void serialize(Nan::marshal::SaveArchive& value) const override 274 | { 275 | using namespace Nan::marshal; 276 | 277 | value & make_nvp("name", name()); 278 | value & make_nvp("type", type()); 279 | 280 | value & make_nvp("min", m_min); 281 | value & make_nvp("max", m_max); 282 | value & make_nvp("default", m_default); 283 | } 284 | 285 | protected: 286 | inline RangedArgument(const char * name, T minValue, T defaultValue, T maxValue) 287 | : InputArgument(name, typeid(T).name()) 288 | , m_min(minValue) 289 | , m_max(maxValue) 290 | , m_default(defaultValue) 291 | { 292 | m_default = validate(m_default); 293 | } 294 | 295 | inline T validate(T value) const 296 | { 297 | if (value < m_min) 298 | throw ArgumentBindException(name(), "Value cannot be less than " + std::to_string(m_min)); 299 | 300 | if (value > m_max) 301 | throw ArgumentBindException(name(), "Value cannot be greater than " + std::to_string(m_max)); 302 | 303 | return value; 304 | } 305 | private: 306 | T m_min; 307 | T m_max; 308 | T m_default; 309 | }; 310 | 311 | template 312 | static inline std::pair inputArgument() 313 | { 314 | return RequiredArgument::Create(T::name()); 315 | } 316 | 317 | template 318 | static inline std::pair inputArgument(typename T::type minValue, typename T::type defaultValue, typename T::type maxValue) 319 | { 320 | return RangedArgument::Create(T::name(), minValue, defaultValue, maxValue); 321 | } 322 | 323 | template 324 | static inline std::pair outputArgument() 325 | { 326 | return TypedOutputArgument::Create(T::name()); 327 | } 328 | } -------------------------------------------------------------------------------- /src/framework/CompilerSupport.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if _MSC_VER 6 | 7 | #define CLOUDCV_NOTHROW _THROW0() 8 | 9 | #else 10 | 11 | #define CLOUDCV_NOTHROW noexcept 12 | 13 | #endif -------------------------------------------------------------------------------- /src/framework/ImageView.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | #include 14 | #include 15 | #include 16 | 17 | #include "framework/Logger.hpp" 18 | #include "ImageView.hpp" 19 | #include "Algorithm.hpp" 20 | #include "framework/marshal/marshal.hpp" 21 | 22 | 23 | 24 | 25 | namespace cloudcv 26 | { 27 | class ImageView::ImageSourceImpl 28 | { 29 | public: 30 | ImageSourceImpl() 31 | { 32 | } 33 | 34 | ImageSourceImpl(cv::Mat image) 35 | { 36 | image.copyTo(m_holder); 37 | } 38 | 39 | virtual ~ImageSourceImpl() = default; 40 | 41 | inline const cv::Mat& getImage() const 42 | { 43 | return m_holder; 44 | } 45 | 46 | inline cv::Mat& getImage() 47 | { 48 | return m_holder; 49 | } 50 | private: 51 | cv::Mat m_holder; 52 | }; 53 | 54 | ImageView::ImageView() 55 | : m_impl(std::shared_ptr(new ImageSourceImpl())) 56 | { 57 | 58 | } 59 | 60 | ImageView::ImageView(std::shared_ptr impl) 61 | : m_impl(impl) 62 | { 63 | } 64 | 65 | const cv::Mat& ImageView::getImage() const 66 | { 67 | if (m_impl.get() != nullptr) 68 | { 69 | return m_impl->getImage(); 70 | } 71 | 72 | throw std::runtime_error("Image is empty"); 73 | } 74 | 75 | cv::Mat& ImageView::getImage() 76 | { 77 | if (m_impl.get() != nullptr) 78 | { 79 | return m_impl->getImage(); 80 | } 81 | 82 | throw std::runtime_error("Image is empty"); 83 | } 84 | 85 | cv::Mat ImageView::getImage(int flags /* = cv::IMREAD_COLOR */) const 86 | { 87 | const cv::Mat& src = getImage(); 88 | 89 | cv::Mat result; 90 | 91 | switch (flags) 92 | { 93 | case cv::IMREAD_GRAYSCALE: 94 | if (src.channels() == 3 || src.channels() == 4) 95 | cv::cvtColor(src, result, cv::COLOR_RGB2GRAY); 96 | else if (src.channels() == 1) 97 | result = src; 98 | else 99 | throw std::runtime_error("Cannot convert image to grayscale"); 100 | break; 101 | 102 | case cv::IMREAD_COLOR: 103 | if (src.channels() == 3 || src.channels() == 4) 104 | result = src; 105 | else if (src.channels() == 1) 106 | cv::cvtColor(src, result, cv::COLOR_GRAY2RGB); 107 | else 108 | throw std::runtime_error("Cannot convert image to RGB"); 109 | break; 110 | 111 | case cv::IMREAD_UNCHANGED: 112 | default: 113 | result = src; 114 | break; 115 | } 116 | 117 | return result; 118 | } 119 | 120 | 121 | ImageView ImageView::CreateImageSource(v8::Local bufferOrString) 122 | { 123 | LOG_TRACE_MESSAGE("ImageView::CreateImageSource"); 124 | 125 | if (node::Buffer::HasInstance(bufferOrString)) 126 | return CreateImageSource(bufferOrString->ToObject()); 127 | 128 | if (bufferOrString->IsString()) 129 | return CreateImageSource(Nan::Marshal(bufferOrString->ToString())); 130 | 131 | throw std::runtime_error("Invalid input argument type. Cannot create ImageSource"); 132 | } 133 | 134 | ImageView ImageView::CreateImageSource(v8::Local imageBuffer) 135 | { 136 | LOG_TRACE_MESSAGE("ImageSource [Buffer]"); 137 | auto mImageData = node::Buffer::Data(imageBuffer); 138 | auto mImageDataLen = node::Buffer::Length(imageBuffer); 139 | 140 | cv::Mat m = cv::imdecode(cv::_InputArray(mImageData, (int)mImageDataLen), cv::IMREAD_UNCHANGED); 141 | return ImageView(std::shared_ptr(new ImageSourceImpl(m))); 142 | } 143 | 144 | ImageView ImageView::CreateImageSource(const std::string& filepath) 145 | { 146 | LOG_TRACE_MESSAGE("ImageSource [File]:" << filepath); 147 | cv::Mat m = cv::imread(filepath, cv::IMREAD_UNCHANGED); 148 | return ImageView(std::shared_ptr(new ImageSourceImpl(m))); 149 | } 150 | 151 | } -------------------------------------------------------------------------------- /src/framework/ImageView.hpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | #pragma once 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | namespace cloudcv 23 | { 24 | /** 25 | * @brief Image source is accessor to image, stored in external resource. 26 | * @details This abstract class has particular implementations to retrieve 27 | * image data from file system and image buffer. 28 | */ 29 | class ImageView 30 | { 31 | public: 32 | /** 33 | * @brief Main method to retrieve image. This method will work synchronously. 34 | * @details Returns the image stored in the ImageSource object. 35 | * @return This function returns the loaded image. It can also return an 36 | * empty object if the image could not been loaded. 37 | */ 38 | cv::Mat getImage(int flags) const; 39 | 40 | const cv::Mat& getImage() const; 41 | cv::Mat& getImage(); 42 | 43 | virtual ~ImageView() {} 44 | 45 | /** 46 | * @brief Creates an ImageSource that points to particular file on filesystem. 47 | */ 48 | static ImageView CreateImageSource(const std::string& filepath); 49 | 50 | /** 51 | * @brief Creates an ImageSource that points to file's binary content that was 52 | * loaded using Node.js. 53 | */ 54 | static ImageView CreateImageSource(v8::Local bufferOrString); 55 | 56 | /** 57 | * @brief Creates an ImageSource that points to file's binary content that was 58 | * loaded using Node.js. 59 | */ 60 | static ImageView CreateImageSource(v8::Local imageBuffer); 61 | 62 | class ImageSourceImpl; 63 | 64 | ImageView(); 65 | 66 | protected: 67 | ImageView(std::shared_ptr impl); 68 | 69 | 70 | private: 71 | std::shared_ptr m_impl; 72 | }; 73 | 74 | 75 | } -------------------------------------------------------------------------------- /src/framework/Job.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | #include "framework/Job.hpp" 14 | #include "framework/Logger.hpp" 15 | 16 | #include 17 | #include 18 | 19 | namespace cloudcv { 20 | 21 | Job::Job(Nan::Callback *callback) 22 | : Nan::AsyncWorker(callback) 23 | { 24 | } 25 | 26 | Job::~Job() 27 | { 28 | } 29 | 30 | void Job::Execute() 31 | { 32 | try 33 | { 34 | ExecuteNativeCode(); 35 | } 36 | catch (cv::Exception& exc) 37 | { 38 | SetErrorMessage(exc.what()); 39 | } 40 | catch (std::runtime_error& e) 41 | { 42 | SetErrorMessage(e.what()); 43 | } 44 | } 45 | 46 | void Job::HandleOKCallback() 47 | { 48 | Nan::HandleScope scope; 49 | 50 | v8::Local argv[] = { 51 | Nan::Null(), 52 | CreateCallbackResult() 53 | }; 54 | 55 | callback->Call(2, argv); 56 | } 57 | 58 | void Job::SetErrorMessage(const std::string& errorMessage) 59 | { 60 | LOG_TRACE_MESSAGE("Error message:" << errorMessage); 61 | 62 | Nan::AsyncWorker::SetErrorMessage(errorMessage.c_str()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/framework/Job.hpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | #pragma once 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | namespace cloudcv { 20 | 21 | /** 22 | * @brief A base class for asynchronous task running in worker pool 23 | */ 24 | class Job : public Nan::AsyncWorker 25 | { 26 | public: 27 | virtual ~Job(); 28 | explicit Job(Nan::Callback *callback); 29 | 30 | void Execute() override; 31 | 32 | virtual void HandleOKCallback() override; 33 | 34 | protected: 35 | void SetErrorMessage(const std::string& errorMessage); 36 | 37 | virtual void ExecuteNativeCode() = 0; 38 | 39 | virtual v8::Local CreateCallbackResult() = 0; 40 | 41 | private: 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/framework/Logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct FunctionTraceLoggerCookie 10 | { 11 | FunctionTraceLoggerCookie(const char * name) 12 | : mName(name) 13 | { 14 | std::clog << "Enter function: " << mName << std::endl; 15 | } 16 | 17 | ~FunctionTraceLoggerCookie() 18 | { 19 | std::clog << "Leave function: " << mName << std::endl; 20 | } 21 | 22 | const char * mName; 23 | }; 24 | 25 | 26 | template 27 | inline std::ostream& operator<<(std::ostream& out, const std::vector& v) { 28 | std::copy(v.begin(), v.end(), std::ostream_iterator(out, ", ")); 29 | return out; 30 | } 31 | 32 | template 33 | inline std::ostream& operator<<(std::ostream& out, const std::array& v) { 34 | std::copy(v.begin(), v.end(), std::ostream_iterator(out, ", ")); 35 | return out; 36 | } 37 | 38 | template 39 | inline std::ostream& operator<<(std::ostream& out, const cv::Rect_& res) { 40 | return out << "cv::Rect { x:" << res.x << "; y:" << res.y << ", width:" << res.width << ", height:" << res.height << "}"; 41 | } 42 | 43 | 44 | #if 0 && (defined(_DEBUG) || defined(DEBUG)) 45 | #if _MSC_VER 46 | #define TRACE_FUNCTION FunctionTraceLoggerCookie(__FUNCTION__) 47 | #else 48 | #define TRACE_FUNCTION FunctionTraceLoggerCookie(__PRETTY_FUNCTION__) 49 | #endif 50 | 51 | #define LOG_TRACE_MESSAGE(x) std::cout << x << std::endl 52 | #else 53 | #define TRACE_FUNCTION static_cast(0) 54 | #define LOG_TRACE_MESSAGE(x) static_cast(0) 55 | #endif 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/framework/ScopedTimer.hpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | #pragma once 15 | 16 | #include 17 | #include 18 | 19 | struct ScopedTimer 20 | { 21 | inline ScopedTimer() 22 | { 23 | m_startTime = cv::getTickCount(); 24 | } 25 | 26 | inline double executionTimeMs() const 27 | { 28 | return (cv::getTickCount() - m_startTime) * 1000. / cv::getTickFrequency(); 29 | } 30 | 31 | int64_t m_startTime; 32 | }; 33 | -------------------------------------------------------------------------------- /src/framework/marshal/marshal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /src/framework/marshal/opencv.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "framework/Logger.hpp" 8 | #include "framework/ImageView.hpp" 9 | 10 | namespace Nan 11 | { 12 | namespace marshal 13 | { 14 | using namespace cloudcv; 15 | 16 | template 17 | struct Serializer < cv::Size_ > 18 | { 19 | template 20 | static inline void load(InputArchive& ar, cv::Size_& val) 21 | { 22 | TRACE_FUNCTION; 23 | 24 | ar & make_nvp("width", val.width); 25 | ar & make_nvp("height", val.height); 26 | } 27 | 28 | template 29 | static inline void save(OutputArchive& ar, const cv::Size_& val) 30 | { 31 | ar & make_nvp("width", val.width); 32 | ar & make_nvp("height", val.height); 33 | } 34 | }; 35 | 36 | template <> 37 | struct Serializer < cv::String > 38 | { 39 | template 40 | static inline void load(InputArchive& ar, cv::String& val) 41 | { 42 | Nan::Utf8String cStr(ar); 43 | val = cv::String(*cStr, cStr.length()); 44 | } 45 | 46 | template 47 | static inline void save(OutputArchive& ar, const cv::String& val) 48 | { 49 | ar = Nan::New(val.c_str()); 50 | } 51 | }; 52 | 53 | template 54 | struct Serializer < cv::Point_ > 55 | { 56 | template 57 | static inline void load(InputArchive& ar, cv::Point_& val) 58 | { 59 | ar & make_nvp("x", val.x); 60 | ar & make_nvp("y", val.y); 61 | } 62 | 63 | template 64 | static inline void save(OutputArchive& ar, const cv::Point_& val) 65 | { 66 | ar & make_nvp("x", val.x); 67 | ar & make_nvp("y", val.y); 68 | } 69 | }; 70 | 71 | template 72 | struct Serializer < cv::Point3_ > 73 | { 74 | template 75 | static inline void load(InputArchive& ar, cv::Point3_& val) 76 | { 77 | ar & make_nvp("x", val.x); 78 | ar & make_nvp("y", val.y); 79 | ar & make_nvp("z", val.z); 80 | } 81 | 82 | template 83 | static inline void save(OutputArchive& ar, const cv::Point3_& val) 84 | { 85 | ar & make_nvp("x", val.x); 86 | ar & make_nvp("y", val.y); 87 | ar & make_nvp("z", val.z); 88 | } 89 | }; 90 | 91 | template 92 | struct Serializer < cv::Rect_ > 93 | { 94 | template 95 | static inline void load(InputArchive& ar, cv::Rect_& val) 96 | { 97 | ar & make_nvp("x", val.x); 98 | ar & make_nvp("y", val.y); 99 | ar & make_nvp("width", val.width); 100 | ar & make_nvp("height", val.height); 101 | } 102 | 103 | template 104 | static inline void save(OutputArchive& ar, const cv::Rect_& val) 105 | { 106 | ar & make_nvp("x", val.x); 107 | ar & make_nvp("y", val.y); 108 | ar & make_nvp("width", val.width); 109 | ar & make_nvp("height", val.height); 110 | } 111 | }; 112 | 113 | template<> 114 | struct Serializer < cv::Scalar > 115 | { 116 | template 117 | static inline void load(InputArchive& ar, cv::Scalar& val) 118 | { 119 | ar & val.val; 120 | } 121 | 122 | template 123 | static inline void save(OutputArchive& ar, const cv::Scalar& val) 124 | { 125 | ar & val.val; 126 | } 127 | }; 128 | 129 | template 130 | struct Serializer < cv::Vec > 131 | { 132 | template 133 | static inline void load(InputArchive& ar, cv::Vec& val) 134 | { 135 | ar & val.val; 136 | } 137 | 138 | template 139 | static inline void save(OutputArchive& ar, const cv::Vec& val) 140 | { 141 | ar & val.val; 142 | } 143 | }; 144 | 145 | template<> 146 | struct Serializer < cv::Mat > 147 | { 148 | template 149 | static inline void load(InputArchive& ar, cv::Mat& val) = delete; 150 | 151 | template 152 | static inline void save(OutputArchive& ar, const cv::Mat& val) 153 | { 154 | // TODO: Implement 155 | //ar = cloudcv::ImageView::ViewForImage(val); 156 | } 157 | }; 158 | 159 | template<> 160 | struct Serializer < ImageView > 161 | { 162 | template 163 | static inline void load(InputArchive& ar, ImageView& val) 164 | { 165 | val = ImageView::CreateImageSource(ar.target()); 166 | } 167 | 168 | template 169 | static inline void save(OutputArchive& ar, const ImageView& val) 170 | { 171 | const cv::Mat& m = val.getImage(); 172 | 173 | ar & make_nvp("rows", m.rows); 174 | ar & make_nvp("cols", m.cols); 175 | ar & make_nvp("channels", m.channels()); 176 | ar & make_nvp("type", m.type()); 177 | 178 | switch (m.type()) 179 | { 180 | case CV_32F: 181 | { 182 | auto start = m.ptr(0); 183 | auto end = start + m.total(); 184 | ar & make_nvp("data", std::vector(start, end)); 185 | break; 186 | } 187 | case CV_8U: 188 | default: 189 | { 190 | auto start = m.ptr(0); 191 | auto end = start + m.elemSize() * m.total(); 192 | ar & make_nvp("data", std::vector(start, end)); 193 | break; 194 | } 195 | } 196 | } 197 | }; 198 | } 199 | } -------------------------------------------------------------------------------- /src/modules/HoughLines.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Bootstrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | #include "framework/marshal/marshal.hpp" 15 | #include "framework/Job.hpp" 16 | #include "framework/ImageView.hpp" 17 | #include "framework/Logger.hpp" 18 | #include "framework/Algorithm.hpp" 19 | #include "modules/HoughLines.hpp" 20 | #include 21 | #include 22 | 23 | namespace cloudcv 24 | { 25 | class HoughLinesAlgorithm : public Algorithm 26 | { 27 | public: 28 | struct image 29 | { 30 | static const char * name() { return "image"; }; 31 | typedef ImageView type; 32 | }; 33 | 34 | struct rho 35 | { 36 | static const char * name() { return "rho"; }; 37 | typedef float type; 38 | }; 39 | 40 | struct theta 41 | { 42 | static const char * name() { return "theta"; }; 43 | typedef float type; 44 | }; 45 | 46 | struct threshold 47 | { 48 | static const char * name() { return "threshold"; }; 49 | typedef int type; 50 | }; 51 | 52 | struct lines 53 | { 54 | static const char * name() { return "lines"; }; 55 | typedef std::vector type; 56 | }; 57 | 58 | void process( 59 | const std::map& inArgs, 60 | const std::map& outArgs 61 | ) override 62 | { 63 | TRACE_FUNCTION; 64 | ImageView source = getInput(inArgs); 65 | const float _rho = getInput(inArgs); 66 | const float _theta = getInput(inArgs); 67 | const int _threshold = getInput(inArgs); 68 | cv::Mat inputImage = source.getImage(cv::IMREAD_GRAYSCALE); 69 | 70 | std::vector &_lines = getOutput(outArgs); 71 | 72 | cv::HoughLines(inputImage, _lines, _rho, _theta, _threshold); 73 | LOG_TRACE_MESSAGE("Detected " << _lines.size() << " lines"); 74 | } 75 | }; 76 | 77 | 78 | 79 | HoughLinesAlgorithmInfo::HoughLinesAlgorithmInfo() 80 | : AlgorithmInfo("houghLines", 81 | { 82 | { inputArgument() }, 83 | { inputArgument(1, 2, 100) }, 84 | { inputArgument(1, 2, 100) }, 85 | { inputArgument(1, 2, 255) } 86 | }, 87 | { 88 | { outputArgument() } 89 | } 90 | ) 91 | { 92 | } 93 | 94 | AlgorithmPtr HoughLinesAlgorithmInfo::create() const 95 | { 96 | return AlgorithmPtr(new HoughLinesAlgorithm()); 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /src/modules/HoughLines.hpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Bootstrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | #pragma once 15 | 16 | #include "framework/Algorithm.hpp" 17 | 18 | namespace cloudcv 19 | { 20 | class HoughLinesAlgorithmInfo : public AlgorithmInfo 21 | { 22 | public: 23 | HoughLinesAlgorithmInfo(); 24 | 25 | AlgorithmPtr create() const override; 26 | }; 27 | } -------------------------------------------------------------------------------- /src/modules/IntegralImage.cpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Bootstrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | #include "modules/IntegralImage.hpp" 15 | #include "framework/Algorithm.hpp" 16 | #include "framework/ImageView.hpp" 17 | #include "framework/Logger.hpp" 18 | 19 | namespace cloudcv 20 | { 21 | class IntegralImageAlgorithm : public Algorithm 22 | { 23 | public: 24 | struct image 25 | { 26 | static const char * name() { return "image"; }; 27 | typedef ImageView type; 28 | }; 29 | 30 | struct integralImage 31 | { 32 | static const char * name() { return "integralImage"; }; 33 | typedef ImageView type; 34 | }; 35 | 36 | 37 | void process( 38 | const std::map& inArgs, 39 | const std::map& outArgs 40 | ) override 41 | { 42 | TRACE_FUNCTION; 43 | ImageView _image = getInput(inArgs); 44 | ImageView &_integralImage = getOutput(outArgs); 45 | 46 | cv::integral(_image.getImage(cv::IMREAD_GRAYSCALE), _integralImage.getImage()); 47 | } 48 | }; 49 | 50 | 51 | 52 | IntegralImageAlgorithmInfo::IntegralImageAlgorithmInfo() 53 | : AlgorithmInfo("integralImage", 54 | { 55 | { inputArgument() } 56 | }, 57 | { 58 | { outputArgument() } 59 | } 60 | ) 61 | { 62 | } 63 | 64 | AlgorithmPtr IntegralImageAlgorithmInfo::create() const 65 | { 66 | return AlgorithmPtr(new IntegralImageAlgorithm()); 67 | } 68 | 69 | 70 | 71 | } -------------------------------------------------------------------------------- /src/modules/IntegralImage.hpp: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Bootstrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | #pragma once 15 | 16 | #include "framework/Algorithm.hpp" 17 | 18 | namespace cloudcv 19 | { 20 | class IntegralImageAlgorithmInfo : public AlgorithmInfo 21 | { 22 | public: 23 | IntegralImageAlgorithmInfo(); 24 | 25 | AlgorithmPtr create() const override; 26 | }; 27 | } -------------------------------------------------------------------------------- /test/data/opencv-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/test/data/opencv-logo.jpg -------------------------------------------------------------------------------- /test/data/opencv-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudCV/cloudcv-bootstrap/ae5363938a21fb97b0365f5c469245ff5018b673/test/data/opencv-small.png -------------------------------------------------------------------------------- /test/test_houghLines.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | var assert = require("assert") 15 | var fs = require('fs'); 16 | var inspect = require('util').inspect; 17 | 18 | var cloudcv = require("../cloudcv.js"); 19 | 20 | describe('cv', function() { 21 | 22 | describe('houghLines', function() { 23 | 24 | it('getInfo', function(done) { 25 | console.log(JSON.stringify(cloudcv.getInfo('houghLines'))); 26 | done(); 27 | }); 28 | 29 | //console.log(cloudcv.houghLines); 30 | it('process (File)', function(done) { 31 | cloudcv.houghLines({ "image": "test/data/opencv-logo.jpg"}, function(error, result) { 32 | 33 | console.log(inspect(error)); 34 | console.log(inspect(result)); 35 | done(); 36 | }); 37 | }); 38 | 39 | it('process (Buffer)', function(done) { 40 | var imageData = fs.readFileSync("test/data/opencv-logo.jpg"); 41 | 42 | cloudcv.houghLines({ "image": imageData }, function(error, result) { 43 | console.log(inspect(error)); 44 | console.log(inspect(result)); 45 | done(); 46 | }); 47 | }); 48 | 49 | it('shouldReturnError (Missing argument)', function(done) { 50 | 51 | cloudcv.houghLines({}, function(error, result) { 52 | console.log(inspect(error)); 53 | console.log(inspect(result)); 54 | done(); 55 | }); 56 | }); 57 | 58 | it('shouldReturnError (Incorrect type)', function(done) { 59 | 60 | cloudcv.houghLines({ "image": 2}, function(error, result) { 61 | console.log(inspect(error)); 62 | console.log(inspect(result)); 63 | done(); 64 | }); 65 | }); 66 | 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/test_integralImage.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * CloudCV Boostrap - A starter template for Node.js with OpenCV bindings. 3 | * This project lets you to quickly prototype a REST API 4 | * in a Node.js for a image processing service written in C++. 5 | * 6 | * Author: Eugene Khvedchenya 7 | * 8 | * More information: 9 | * - https://cloudcv.io 10 | * - http://computer-vision-talks.com 11 | * 12 | **********************************************************************************/ 13 | 14 | var assert = require("assert") 15 | var fs = require('fs'); 16 | var inspect = require('util').inspect; 17 | 18 | var cloudcv = require("../cloudcv.js"); 19 | 20 | describe('cv', function() { 21 | 22 | describe('integralImage', function() { 23 | 24 | it('getInfo', function(done) { 25 | console.log(JSON.stringify(cloudcv.getInfo('integralImage'))); 26 | done(); 27 | }); 28 | 29 | it('process (File)', function(done) { 30 | cloudcv.integralImage({ "image": "test/data/opencv-logo.jpg"}, function(error, result) { 31 | console.log(inspect(error)); 32 | done(); 33 | }); 34 | }); 35 | 36 | it('process (Buffer)', function(done) { 37 | var imageData = fs.readFileSync("test/data/opencv-logo.jpg"); 38 | 39 | cloudcv.integralImage({ "image": imageData }, function(error, result) { 40 | console.log(inspect(error)); 41 | done(); 42 | }); 43 | }); 44 | 45 | 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /views/docs.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | 5 | div.container 6 | h1 Welcome to CloudCV demonstration 7 | 8 | div.row 9 | .col-md-2 10 | .list-group 11 | each algorithm in api 12 | if (currentAlgorithm == algorithm.name) 13 | a.list-group-item.active(href="/docs/" + algorithm.name) #{algorithm.name} 14 | else 15 | a.list-group-item(href="/docs/" + algorithm.name) #{algorithm.name} 16 | 17 | .col-md-12 18 | each algorithm in api 19 | if (currentAlgorithm == algorithm.name) 20 | p.load Here comes information of selected algorithm, which is #{algorithm.name} 21 | -------------------------------------------------------------------------------- /views/footer.jade: -------------------------------------------------------------------------------- 1 | block footer 2 | 3 | nav.navbar.navbar-inverse.row-fluid.navbar-fixed-bottom 4 | div.container 5 | div.navbar-inner 6 | 7 | ul.nav.navbar-nav 8 | li 9 | a(href="/privacy") Privacy statement 10 | li 11 | a(href="/about") About 12 | 13 | ul.nav.navbar-nav.navbar-right 14 | li 15 | a(href="https://www.digitalocean.com/?refcode=b93faa829f80",alt="Digital Ocean",title="Support this site by purchasing yur own cloud with this referal link") 16 | img(src="/images/digital-ocean-logo-white.png") 17 | 18 | script(src="//code.jquery.com/jquery-2.0.3.min.js") 19 | script(src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js") 20 | script(src="/javascripts/ga.js") 21 | script(src="/javascripts/holder.js") 22 | script(src="/javascripts/cloudcv.js") 23 | -------------------------------------------------------------------------------- /views/header.jade: -------------------------------------------------------------------------------- 1 | nav.navbar.navbar-default.navbar-inverse(role="navigation") 2 | div.container 3 | div.navbar-header 4 | button.navbar-toggle(type="button",data-toggle="collapse",data-target="#bs-example-navbar-collapse-1") 5 | span.sr-only Toggle navigation 6 | span.icon-bar 7 | span.icon-bar 8 | span.icon-bar 9 | a.navbar-brand(href="/") CloudCV 10 | 11 | //- Collect the nav links, forms, and other content for toggling 12 | div.collapse.navbar-collapse(id="bs-example-navbar-collapse-1") 13 | ul.nav.navbar-nav 14 | //- li 15 | a(href="/docs") 16 | i.fa.fa-fighter-jet 17 | | API Docs 18 | 19 | ul.nav.navbar-nav.navbar-right 20 | li 21 | a(href="https://ua.linkedin.com/in/cvtalks/") 22 | i.fa.fa-linkedin 23 | |  Ievgen Khvedchenia 24 | li 25 | a(href="https://github.com/BloodAxe/CloudCV") 26 | i.fa.fa-github-alt 27 | |  CloudCV 28 | li 29 | a(href="https://twitter.com/cvtalks") 30 | i.fa.fa-twitter 31 | |  @cvtalks 32 | 33 | 34 | span(id="forkongithub") 35 | a(href="https://github.com/BloodAxe/CloudCV") Fork me on GitHub 36 | -------------------------------------------------------------------------------- /views/includes/disqus.jade: -------------------------------------------------------------------------------- 1 | div(id="disqus_thread") 2 | script(type="text/javascript"). 3 | /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */ 4 | var disqus_shortname = 'cloudcvio'; // required: replace example with your forum shortname 5 | 6 | /* * * DON'T EDIT BELOW THIS LINE * * */ 7 | (function() { 8 | var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; 9 | dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js'; 10 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); 11 | })(); 12 | 13 | noscript Please enable JavaScript to view the 14 | a(href="https://disqus.com/?ref_noscript") comments powered by Disqus. 15 | 16 | a.dsq-brlink(href="https://disqus.com") 17 | | Comments powered by 18 | span.logo-disqus Disqus 19 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | script. 5 | 6 | function rgbToHex(r, g, b) { 7 | return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); 8 | } 9 | 10 | function renderDominantColorsResponse(error, response) { 11 | if (error) { 12 | //$('#resultsPanel').hide(0); 13 | //$('#processingErrorMessage').html(error.message); 14 | //$('#processingError').show(); 15 | } 16 | else { 17 | //$('#processingError').hide(0); 18 | //$('#sourceImage').attr('src', imageSrc); 19 | $('#dominantColors').empty(); 20 | for (var i = 0; i < response.dominantColors.length; i++) { 21 | var c = response.dominantColors[i].color; 22 | 23 | $('#dominantColors').append('
'); 24 | } 25 | //#$('#response-raw').html(JSON.stringify(response, undefined, '\t')); 26 | //$('#resultsPanel').show(); 27 | } 28 | } 29 | function extractDominantColorsFromUrl(imageUrl) { 30 | cloudcv.image.analyze.dominantColors(imageUrl, renderDominantColorsResponse(e, r) ); 31 | } 32 | function extractDominantColorsFromFile(image, file) { 33 | //var s = image.src; 34 | //$('#selected-image').attr('src', URL.createObjectURL(file)).width(200).height(200); 35 | var imageBlob = cloudcv.common.getImageDataAsBlob(image); 36 | cloudcv.image.analyze.dominantColors(imageBlob, function(e, r) { renderDominantColorsResponse(e, r, URL.createObjectURL(file)); }); 37 | } 38 | 39 | div.container 40 | h1 Welcome to CloudCV demonstration 41 | 42 | div 43 | img.pull-left(src="/images/cloudcv-logo.png") 44 | 45 | p.lead This website demonstrate interopability between Node.js and OpenCV library. It's hosted in the cloud environment. 46 | | Here you will see that it's really simple to perform CPU-intense image processing routines in the cloud. 47 | | A Node.js server handle client requests and calls C++ back-end. In this showcase demonstration you can try the following examples: 48 | 49 | h2 Examples 50 | div.row 51 | .col-md-8 52 | div#dropZone 53 | img(id='sourceImage',style='max-width:100%;max-height:512px',src="/images/mandril-300x200.png",alt="...") 54 | .col-md-4(id='dominantColors') 55 | 56 | block scripts 57 | script. 58 | $(document).ready(function () { 59 | 60 | var srcImg = $("#sourceImage"); 61 | 62 | var dropZone = $('#dropZone'), 63 | maxFileSize = 1000000; // максимальный размер фалйа - 1 мб. 64 | 65 | // Проверка поддержки браузером 66 | if (typeof(window.FileReader) == 'undefined') { 67 | //dropZone.text('Не поддерживается браузером!'); 68 | dropZone.addClass('error'); 69 | } 70 | 71 | // Добавляем класс hover при наведении 72 | dropZone[0].ondragover = function() { 73 | dropZone.addClass('hover'); 74 | return false; 75 | }; 76 | 77 | // Убираем класс hover 78 | dropZone[0].ondragleave = function() { 79 | dropZone.removeClass('hover'); 80 | return false; 81 | }; 82 | 83 | // Обрабатываем событие Drop 84 | dropZone[0].ondrop = function(event) { 85 | event.preventDefault(); 86 | dropZone.removeClass('hover'); 87 | dropZone.addClass('drop'); 88 | 89 | var file = event.dataTransfer.files[0]; 90 | 91 | // Проверяем размер файла 92 | if (file.size > maxFileSize) { 93 | //dropZone.text('Файл слишком большой!'); 94 | dropZone.addClass('error'); 95 | } 96 | else 97 | { 98 | var url = URL.createObjectURL(file); 99 | 100 | if (url) { 101 | srcImg.attr('src', url); 102 | } 103 | else { 104 | var reader = new FileReader(); 105 | 106 | reader.onload = function (oFREvent) { 107 | var dataUrl = oFREvent.target.result; 108 | srcImg.attr('src', dataUrl); 109 | }; 110 | 111 | reader.readAsDataURL(file); 112 | } 113 | } 114 | // Создаем запрос 115 | /* 116 | #var xhr = new XMLHttpRequest(); 117 | xhr.upload.addEventListener('progress', uploadProgress, false); 118 | xhr.onreadystatechange = stateChange; 119 | xhr.open('POST', '/upload.php'); 120 | xhr.setRequestHeader('X-FILE-NAME', file.name); 121 | xhr.send(file); 122 | */ 123 | }; 124 | 125 | // Показываем процент загрузки 126 | function uploadProgress(event) { 127 | var percent = parseInt(event.loaded / event.total * 100); 128 | dropZone.text('Загрузка: ' + percent + '%'); 129 | } 130 | 131 | // Пост обрабочик 132 | function stateChange(event) { 133 | if (event.target.readyState == 4) { 134 | if (event.target.status == 200) { 135 | dropZone.text('Загрузка успешно завершена!'); 136 | } else { 137 | dropZone.text('Произошла ошибка!'); 138 | dropZone.addClass('error'); 139 | } 140 | } 141 | } 142 | 143 | srcImg.load(function (e) { 144 | extractDominantColorsFromFile(srcImg.get(0)); 145 | }); 146 | 147 | //$('#processingError').hide(0); 148 | //$('#resultsPanel').hide(0); 149 | //cloudcv.common.bindHookOnFileInput('file-selector', extractDominantColorsFromFile); 150 | //$('#output a').click(function (e) { 151 | // e.preventDefault() 152 | // $(this).tab('show') 153 | //}) 154 | }); -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | block variables 4 | head 5 | - var title = title || "CloudCV - Cloud image processing platform" 6 | title #{title} 7 | 8 | meta(name="viewport",content="width=device-width, initial-scale=1.0") 9 | 10 | link(rel='stylesheet',href='//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css') 11 | link(rel='stylesheet',href='//netdna.bootstrapcdn.com/font-awesome/4.0.2/css/font-awesome.min.css') 12 | 13 | link(rel='stylesheet',href='/stylesheets/github-forkme.css') 14 | link(rel='stylesheet',href='/stylesheets/template.css') 15 | 16 | body 17 | include header 18 | block content 19 | include footer 20 | block scripts 21 | 22 | -------------------------------------------------------------------------------- /views/privacy.jade: -------------------------------------------------------------------------------- 1 | block variables 2 | - title="Privacy statement - CloudCV" 3 | 4 | extends layout 5 | 6 | block content 7 | div.container 8 | h1 Privacy statement 9 | 10 | p CloudCV will never, under any circumstances: 11 | ul 12 | li Save, store and distribute uploaded images to trird-parties. As much as possible all image processing will be performed in-memory. --------------------------------------------------------------------------------