[
  {
    "path": ".gitignore",
    "content": ".DS_Store\nThumbs.db\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (c) 2016 Will Boyd\n\nThis software is released under the MIT license: http://opensource.org/licenses/MIT\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# diff-cam-engine\n\nCore engine for building motion detection web apps.\n\n### Usage\n\n`diff-cam-engine.js` provides a `DiffCamEngine` object that accesses the webcam, captures images from it, and evaluates motion.\n\nYou'll want to use the `adapter.js` shim, which is available here: https://github.com/webrtc/adapter. Add it before `diff-cam-engine.js`.\n\nWith that in place, call `DiffCamEngine.init()` to initialize. This will set things up and ask the user for permission to access the webcam.\n\n``` javascript\nDiffCamEngine.init({\n\t// config options go here\n});\n```\n\nThen call `start()` to begin the actual motion sensing. Do not call `start()` before `init()` has finished. It's a good idea to call `start()` from `initSuccessCallback()` (more on this later).\n\nCall `stop()` to turn things off.\n\n### Configuration\n\nYou can customize how `DiffCamEngine` behaves by passing an object of name/value pairs into `init()`. For example:\n\n``` javascript\nDiffCamEngine.init({\n\tvideo: myVideo,\n\tcaptureIntervalTime: 50,\n\tcaptureCallback: myCaptureHandler\n\t// etc.\n});\n```\n\n##### Variables\n\nThe following variables can be passed into `init()`:\n\n| Name | Default | Description |\n| --- | --- | --- |\n| video | internal &lt;video&gt; (not visible) | The &lt;video&gt; element for showing the live webcam stream |\n| motionCanvas | internal &lt;canvas&gt; (not visible) | The &lt;canvas&gt; element for showing the visual motion heatmap |\n| captureIntervalTime | 100 | Number of ms between capturing images from the stream |\n| captureWidth | 640 | Width of captured images from stream |\n| captureHeight | 480 | Height of capture images from stream |\n| diffWidth | 64 | Width of (usually downsized) images used for diffing and showing motion |\n| diffHeight | 48 | Height of (usually downsized) images used for diffing and showing motion |\n| pixelDiffThreshold | 32 | Minimum difference in a pixel to be considered changed |\n| scoreThreshold | 16 | Minimum number of changed pixels for an image to be considered as having motion |\n| includeMotionBox | false | Flag to calculate and display (on motionCanvas) the bounding box of motion |\n| includeMotionPixels | false | Flag to include data indicating all the changed pixels |\n\n##### Callbacks\n\nThere are also a couple callback functions you can specify. This is the primary way of interacting with `DiffCamEngine` during execution. Pass these into `init()` just like the variables above.\n\n| Name | Description |\n| --- | --- |\n| initSuccessCallback | Called when init has successfully completed |\n| initErrorCallback | Called when init fails |\n| startCompleteCallback | Called once the webcam has begun streaming |\n| captureCallback | Called after a captured image from the stream has been evaluated |\n\n`captureCallback` is the only one with a parameter. This parameter is an object with multiple properties on it. These properties are:\n\n| Property | Description |\n| --- | --- |\n| imageData | The imageData object for the captured image |\n| score | The evaluated score for the captured image |\n| hasMotion | Whether or not the score meets the score threshold for motion |\n| motionBox | An object containg x min/max and y min/max for a box wrapping the region in which motion occurred, only returned if includeMotionBox is `true` |\n| motionPixels | An object containg each pixel in the image that changed (indicating motion), only returned if includeMotionPixels is `true` |\n| getURL | Convenience function, returns imageData converted to a URL suitable for setting as the src of an image |\n| checkMotionPixel | Convenience function, takes in an x and y cooridnate, returns a boolean indicating if the pixel at that location has changed |\n\n### Functions\n\n`DiffCamEngine` exposes the following public functions:\n\n| Function | Description |\n| --- | --- |\n| init | Initializes everything and requests permission to access the webcam |\n| start | Begin streaming from the webcam |\n| stop | Stop streaming from the webcam |\n| getPixelDiffThreshold | Get pixelDiffThreshold during execution |\n| setPixelDiffThreshold | Set pixelDiffThreshold during execution |\n| getScoreThreshold | Get scoreThreshold during execution |\n| setScoreThreshold | Set scoreThreshold during execution |\n\n### Examples\n\nCheck out https://github.com/lonekorean/diff-cam-scratchpad (specifically /diff-cam-example and /turret-security) for examples that use diff-cam-engine. Check out https://github.com/lonekorean/diff-cam-feed for a more complex web app powered by diff-cam-engine.\n"
  },
  {
    "path": "diff-cam-engine.js",
    "content": "var DiffCamEngine = (function() {\n\tvar stream;\t\t\t\t\t// stream obtained from webcam\n\tvar video;\t\t\t\t\t// shows stream\n\tvar captureCanvas;\t\t\t// internal canvas for capturing full images from video\n\tvar captureContext;\t\t\t// context for capture canvas\n\tvar diffCanvas;\t\t\t\t// internal canvas for diffing downscaled captures\n\tvar diffContext;\t\t\t// context for diff canvas\n\tvar motionCanvas;\t\t\t// receives processed diff images\n\tvar motionContext;\t\t\t// context for motion canvas\n\n\tvar initSuccessCallback;\t// called when init succeeds\n\tvar initErrorCallback;\t\t// called when init fails\n\tvar startCompleteCallback;\t// called when start is complete\n\tvar captureCallback;\t\t// called when an image has been captured and diffed\n\n\tvar captureInterval;\t\t// interval for continuous captures\n\tvar captureIntervalTime;\t// time between captures, in ms\n\tvar captureWidth;\t\t\t// full captured image width\n\tvar captureHeight;\t\t\t// full captured image height\n\tvar diffWidth;\t\t\t\t// downscaled width for diff/motion\n\tvar diffHeight;\t\t\t\t// downscaled height for diff/motion\n\tvar isReadyToDiff;\t\t\t// has a previous capture been made to diff against?\n\tvar pixelDiffThreshold;\t\t// min for a pixel to be considered significant\n\tvar scoreThreshold;\t\t\t// min for an image to be considered significant\n\tvar includeMotionBox;\t\t// flag to calculate and draw motion bounding box\n\tvar includeMotionPixels;\t// flag to create object denoting pixels with motion\n\n\tfunction init(options) {\n\t\t// sanity check\n\t\tif (!options) {\n\t\t\tthrow 'No options object provided';\n\t\t}\n\n\t\t// incoming options with defaults\n\t\tvideo = options.video || document.createElement('video');\n\t\tmotionCanvas = options.motionCanvas || document.createElement('canvas');\n\t\tcaptureIntervalTime = options.captureIntervalTime || 100;\n\t\tcaptureWidth = options.captureWidth || 640;\n\t\tcaptureHeight = options.captureHeight || 480;\n\t\tdiffWidth = options.diffWidth || 64;\n\t\tdiffHeight = options.diffHeight || 48;\n\t\tpixelDiffThreshold = options.pixelDiffThreshold || 32;\n\t\tscoreThreshold = options.scoreThreshold || 16;\n\t\tincludeMotionBox = options.includeMotionBox || false;\n\t\tincludeMotionPixels = options.includeMotionPixels || false;\n\n\t\t// callbacks\n\t\tinitSuccessCallback = options.initSuccessCallback || function() {};\n\t\tinitErrorCallback = options.initErrorCallback || function() {};\n\t\tstartCompleteCallback = options.startCompleteCallback || function() {};\n\t\tcaptureCallback = options.captureCallback || function() {};\n\n\t\t// non-configurable\n\t\tcaptureCanvas = document.createElement('canvas');\n\t\tdiffCanvas = document.createElement('canvas');\n\t\tisReadyToDiff = false;\n\n\t\t// prep video\n\t\tvideo.autoplay = true;\n\n\t\t// prep capture canvas\n\t\tcaptureCanvas.width = captureWidth;\n\t\tcaptureCanvas.height = captureHeight;\n\t\tcaptureContext = captureCanvas.getContext('2d');\n\n\t\t// prep diff canvas\n\t\tdiffCanvas.width = diffWidth;\n\t\tdiffCanvas.height = diffHeight;\n\t\tdiffContext = diffCanvas.getContext('2d');\n\n\t\t// prep motion canvas\n\t\tmotionCanvas.width = diffWidth;\n\t\tmotionCanvas.height = diffHeight;\n\t\tmotionContext = motionCanvas.getContext('2d');\n\n\t\trequestWebcam();\n\t}\n\n\tfunction requestWebcam() {\n\t\tvar constraints = {\n\t\t\taudio: false,\n\t\t\tvideo: { width: captureWidth, height: captureHeight }\n\t\t};\n\n\t\tnavigator.mediaDevices.getUserMedia(constraints)\n\t\t\t.then(initSuccess)\n\t\t\t.catch(initError);\n\t}\n\n\tfunction initSuccess(requestedStream) {\n\t\tstream = requestedStream;\n\t\tinitSuccessCallback();\n\t}\n\n\tfunction initError(error) {\n\t\tconsole.log(error);\n\t\tinitErrorCallback();\n\t}\n\n\tfunction start() {\n\t\tif (!stream) {\n\t\t\tthrow 'Cannot start after init fail';\n\t\t}\n\n\t\t// streaming takes a moment to start\n\t\tvideo.addEventListener('canplay', startComplete);\n\t\tvideo.srcObject = stream;\n\t}\n\n\tfunction startComplete() {\n\t\tvideo.removeEventListener('canplay', startComplete);\n\t\tcaptureInterval = setInterval(capture, captureIntervalTime);\n\t\tstartCompleteCallback();\n\t}\n\n\tfunction stop() {\n\t\tclearInterval(captureInterval);\n\t\tvideo.src = '';\n\t\tmotionContext.clearRect(0, 0, diffWidth, diffHeight);\n\t\tisReadyToDiff = false;\n\t}\n\n\tfunction capture() {\n\t\t// save a full-sized copy of capture\n\t\tcaptureContext.drawImage(video, 0, 0, captureWidth, captureHeight);\n\t\tvar captureImageData = captureContext.getImageData(0, 0, captureWidth, captureHeight);\n\n\t\t// diff current capture over previous capture, leftover from last time\n\t\tdiffContext.globalCompositeOperation = 'difference';\n\t\tdiffContext.drawImage(video, 0, 0, diffWidth, diffHeight);\n\t\tvar diffImageData = diffContext.getImageData(0, 0, diffWidth, diffHeight);\n\n\t\tif (isReadyToDiff) {\n\t\t\tvar diff = processDiff(diffImageData);\n\n\t\t\tmotionContext.putImageData(diffImageData, 0, 0);\n\t\t\tif (diff.motionBox) {\n\t\t\t\tmotionContext.strokeStyle = '#fff';\n\t\t\t\tmotionContext.strokeRect(\n\t\t\t\t\tdiff.motionBox.x.min + 0.5,\n\t\t\t\t\tdiff.motionBox.y.min + 0.5,\n\t\t\t\t\tdiff.motionBox.x.max - diff.motionBox.x.min,\n\t\t\t\t\tdiff.motionBox.y.max - diff.motionBox.y.min\n\t\t\t\t);\n\t\t\t}\n\t\t\tcaptureCallback({\n\t\t\t\timageData: captureImageData,\n\t\t\t\tscore: diff.score,\n\t\t\t\thasMotion: diff.score >= scoreThreshold,\n\t\t\t\tmotionBox: diff.motionBox,\n\t\t\t\tmotionPixels: diff.motionPixels,\n\t\t\t\tgetURL: function() {\n\t\t\t\t\treturn getCaptureUrl(this.imageData);\n\t\t\t\t},\n\t\t\t\tcheckMotionPixel: function(x, y) {\n\t\t\t\t\treturn checkMotionPixel(this.motionPixels, x, y)\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t// draw current capture normally over diff, ready for next time\n\t\tdiffContext.globalCompositeOperation = 'source-over';\n\t\tdiffContext.drawImage(video, 0, 0, diffWidth, diffHeight);\n\t\tisReadyToDiff = true;\n\t}\n\n\tfunction processDiff(diffImageData) {\n\t\tvar rgba = diffImageData.data;\n\n\t\t// pixel adjustments are done by reference directly on diffImageData\n\t\tvar score = 0;\n\t\tvar motionPixels = includeMotionPixels ? [] : undefined;\n\t\tvar motionBox = undefined;\n\t\tfor (var i = 0; i < rgba.length; i += 4) {\n\t\t\tvar pixelDiff = rgba[i] * 0.3 + rgba[i + 1] * 0.6 + rgba[i + 2] * 0.1;\n\t\t\tvar normalized = Math.min(255, pixelDiff * (255 / pixelDiffThreshold));\n\t\t\trgba[i] = 0;\n\t\t\trgba[i + 1] = normalized;\n\t\t\trgba[i + 2] = 0;\n\n\t\t\tif (pixelDiff >= pixelDiffThreshold) {\n\t\t\t\tscore++;\n\t\t\t\tcoords = calculateCoordinates(i / 4);\n\n\t\t\t\tif (includeMotionBox) {\n\t\t\t\t\tmotionBox = calculateMotionBox(motionBox, coords.x, coords.y);\n\t\t\t\t}\n\n\t\t\t\tif (includeMotionPixels) {\n\t\t\t\t\tmotionPixels = calculateMotionPixels(motionPixels, coords.x, coords.y, pixelDiff);\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tscore: score,\n\t\t\tmotionBox: score > scoreThreshold ? motionBox : undefined,\n\t\t\tmotionPixels: motionPixels\n\t\t};\n\t}\n\n\tfunction calculateCoordinates(pixelIndex) {\n\t\treturn {\n\t\t\tx: pixelIndex % diffWidth,\n\t\t\ty: Math.floor(pixelIndex / diffWidth)\n\t\t};\n\t}\n\n\tfunction calculateMotionBox(currentMotionBox, x, y) {\n\t\t// init motion box on demand\n\t\tvar motionBox = currentMotionBox || {\n\t\t\tx: { min: coords.x, max: x },\n\t\t\ty: { min: coords.y, max: y }\n\t\t};\n\n\t\tmotionBox.x.min = Math.min(motionBox.x.min, x);\n\t\tmotionBox.x.max = Math.max(motionBox.x.max, x);\n\t\tmotionBox.y.min = Math.min(motionBox.y.min, y);\n\t\tmotionBox.y.max = Math.max(motionBox.y.max, y);\n\n\t\treturn motionBox;\n\t}\n\n\tfunction calculateMotionPixels(motionPixels, x, y, pixelDiff) {\n\t\tmotionPixels[x] = motionPixels[x] || [];\n\t\tmotionPixels[x][y] = true;\n\n\t\treturn motionPixels;\n\t}\n\n\tfunction getCaptureUrl(captureImageData) {\n\t\t// may as well borrow captureCanvas\n\t\tcaptureContext.putImageData(captureImageData, 0, 0);\n\t\treturn captureCanvas.toDataURL();\n\t}\n\n\tfunction checkMotionPixel(motionPixels, x, y) {\n\t\treturn motionPixels && motionPixels[x] && motionPixels[x][y];\n\t}\n\n\tfunction getPixelDiffThreshold() {\n\t\treturn pixelDiffThreshold;\n\t}\n\n\tfunction setPixelDiffThreshold(val) {\n\t\tpixelDiffThreshold = val;\n\t}\n\n\tfunction getScoreThreshold() {\n\t\treturn scoreThreshold;\n\t}\n\n\tfunction setScoreThreshold(val) {\n\t\tscoreThreshold = val;\n\t}\n\n\treturn {\n\t\t// public getters/setters\n\t\tgetPixelDiffThreshold: getPixelDiffThreshold,\n\t\tsetPixelDiffThreshold: setPixelDiffThreshold,\n\t\tgetScoreThreshold: getScoreThreshold,\n\t\tsetScoreThreshold: setScoreThreshold,\n\n\t\t// public functions\n\t\tinit: init,\n\t\tstart: start,\n\t\tstop: stop\n\t};\n})();\n"
  }
]