├── .editorconfig ├── .gitignore ├── .npmignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── build.js ├── dist ├── glitch-canvas-browser-with-polyfills.js ├── glitch-canvas-browser-with-polyfills.min.js ├── glitch-canvas-browser.es6.js ├── glitch-canvas-browser.es6.min.js ├── glitch-canvas-browser.es6.umd.js ├── glitch-canvas-browser.es6.umd.min.js ├── glitch-canvas-browser.js ├── glitch-canvas-browser.min.js └── glitch-canvas-node.js ├── examples ├── browser-example.html ├── browser-module-example.html ├── img │ └── lincoln.jpg ├── node-example.js └── node-output │ └── .gitkeep ├── glitch-canvas-browser ├── glitch-canvas-browser-with-polyfills.js ├── glitch-canvas-browser-with-polyfills.min.js ├── glitch-canvas-browser.es6.js ├── glitch-canvas-browser.es6.min.js ├── glitch-canvas-browser.es6.umd.js ├── glitch-canvas-browser.es6.umd.min.js ├── glitch-canvas-browser.js ├── glitch-canvas-browser.min.js ├── package.json └── readme.md ├── glitch-example.png ├── package-lock.json ├── package.json ├── readme.md ├── src ├── browser.js ├── glitch │ ├── base64Map.js │ ├── base64ToByteArray.js │ ├── browser │ │ └── imageDataToBase64.js │ ├── byteArrayToBase64.js │ ├── glitchByteArray.js │ ├── glitchImageData.js │ ├── jpgHeaderLength.js │ └── node │ │ └── imageDataToBase64.js ├── index.js ├── input │ ├── browser │ │ └── fromImage.js │ ├── defaultParams.js │ ├── node │ │ ├── fromBuffer.js │ │ └── fromStream.js │ └── sanitizeInput.js ├── output │ ├── browser │ │ └── toImage.js │ ├── node │ │ ├── toBuffer.js │ │ ├── toJPGStream.js │ │ └── toPNGStream.js │ └── toImageData.js ├── util │ ├── browser.js │ ├── canvas.js │ ├── canvasFromImage.js │ ├── clamp.js │ ├── clone.js │ ├── copyImageData.js │ ├── getImageSize.js │ ├── isImageData.js │ ├── loadBase64Image.js │ ├── node-canvas.js │ └── object-assign.js └── workers │ └── glitchWorker.js └── test ├── browser.karma.conf.js ├── img └── lincoln.jpg ├── test-browser.html ├── test-browser.js ├── test-browser.min.html ├── test-node.js ├── tester.html └── tests.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{css,scss,less,mjs,js,json,ts,sass,html,hbs,mustache,phtml,html.twig,md,yml}] 2 | charset = utf-8 3 | indent_style = tab 4 | indent_size = 4 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | quote_type = single 10 | 11 | [*.md] 12 | indent_size = 4 13 | trim_trailing_whitespace = false 14 | 15 | [site/templates/**.php] 16 | indent_size = 4 17 | indent_style = tab 18 | 19 | [site/snippets/**.php] 20 | indent_size = 4 21 | indent_style = tab 22 | 23 | [site/{models, config}/**.php] 24 | indent_size = 4 25 | indent_style = tab 26 | 27 | [site/plugins/**.{php, js}] 28 | indent_size = 4 29 | indent_style = tab 30 | 31 | [package.json,.{babelrc,editorconfig,eslintrc,lintstagedrc,stylelintrc}] 32 | indent_style = tab 33 | indent_size = 4 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Mac OS files 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | 7 | # Thumbnails 8 | ._* 9 | 10 | # Files that might appear on external disk 11 | .Spotlight-V100 12 | .Trashes 13 | 14 | # Windows image file caches 15 | Thumbs.db 16 | ehthumbs.db 17 | 18 | # Folder config file 19 | Desktop.ini 20 | 21 | # Recycle Bin used on file shares 22 | $RECYCLE.BIN/ 23 | 24 | # SublimeText project files 25 | *.sublime-workspace 26 | 27 | # nodeJS modules 28 | node_modules 29 | test/node_modules 30 | examples/node-output 31 | 32 | todo.md -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | examples 3 | example.png 4 | test 5 | dist/tmp 6 | glitch-canvas-browser 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "arrowParens": "avoid", 11 | "proseWrap": "preserve" 12 | } 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | dist: trusty # needs Ubuntu Trusty 5 | sudo: false # no need for virtualization. 6 | addons: 7 | chrome: stable # have Travis install chrome stable. 8 | before_script: # https://github.com/travis-ci/travis-ci/issues/9024#issuecomment-356282802 9 | - "sudo chown root /opt/google/chrome/chrome-sandbox" 10 | - "sudo chmod 4755 /opt/google/chrome/chrome-sandbox" 11 | cache: 12 | npm: true 13 | directories: 14 | - node_modules 15 | install: 16 | - npm install 17 | script: 18 | - npm test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2017 Georg Fischer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | var fs = require( 'fs' ); 2 | var { rollup } = require( 'rollup' ); 3 | var buble = require( '@rollup/plugin-buble' ); 4 | var UglifyJS = require( 'uglify-js' ); 5 | var UglifyES = require( 'uglify-es' ); 6 | var replace = require( 'rollup-plugin-replace' ); 7 | var commonjs = require( 'rollup-plugin-commonjs' ); 8 | var nodeResolve = require( 'rollup-plugin-node-resolve' ); 9 | 10 | var program = require( 'commander' ); 11 | var version = require('./package.json').version; 12 | 13 | program 14 | .version( version ) 15 | .option('-b, --browser', 'generate browser version (node is default)' ) 16 | .option('-e, --es6', 'export es6 code' ) 17 | .option('-m, --minify', 'minify output' ) 18 | .option('-u, --umd', 'export UMD Bundle (optional for es6 builds)' ) 19 | .option('-p, --polyfill', 'add polyfills (for Promise and Object.assign)' ) 20 | .parse( process.argv ); 21 | 22 | var env = program.browser ? 'browser' : 'node'; 23 | var isBrowser = env === 'browser'; 24 | 25 | var es5Build = ! program.es6 || env === 'node'; 26 | var minifyBuild = !! program.minify; 27 | var bundleUMD = !! program.umd; 28 | var polyfill = !! program.polyfill; 29 | 30 | var globalPath = 'src/'; 31 | var buildPaths = [ 'dist/' ]; 32 | var minifyExtension = 'min'; 33 | var es6Extension = 'es6'; 34 | var umdExtension = 'umd'; 35 | 36 | var moduleName = 'glitch'; 37 | var fileName = 'glitch-canvas-{APPENDIX}'; 38 | var mainFilePath = isBrowser ? 'browser.js' : 'index.js'; 39 | 40 | if ( isBrowser ) { 41 | buildPaths.push( 'glitch-canvas-browser/' ); 42 | } 43 | 44 | var stringsToReplace = { 45 | browser: { 46 | "import Canvas from './node-canvas.js'": "import Canvas from './browser.js'", 47 | "// PROMISE_POLYFILL_HERE": '' 48 | }, 49 | node: { 50 | "import Canvas from 'canvas'": "var Canvas = require( 'canvas' );" 51 | } 52 | }; 53 | 54 | if ( polyfill ) { 55 | stringsToReplace.browser[ 'const objectAssign = Object.assign' ] = "import objectAssign from 'object-assign'"; 56 | 57 | var promisePolyfillStr = ` 58 | import p from 'es6-promise'; 59 | p.polyfill(); 60 | `; 61 | 62 | stringsToReplace.browser[ '// PROMISE_POLYFILL_HERE' ] = promisePolyfillStr; 63 | } 64 | 65 | console.log( 'building with options: env:', env, 'es6:', ! es5Build, 'minify:', minifyBuild, 'umd', bundleUMD ); 66 | 67 | buildPaths.forEach( buildPath => { 68 | createES6Bundle( globalPath + mainFilePath, buildPath ) 69 | .then( fileContent => { 70 | console.log( 'build complete. file saved to ' + buildPath + getOutputFileName( mainFilePath ) ); 71 | } ); 72 | } ); 73 | 74 | function createES6Bundle ( filePath, buildPath ) { 75 | const format = ( es5Build || bundleUMD ) ? 'umd' : 'es'; 76 | 77 | return processES6File( filePath, format, moduleName ) 78 | .then( fileContent => { 79 | return processFileContent( fileContent ); 80 | } ) 81 | .then( fileContent => { 82 | return saveFile( buildPath + getOutputFileName( mainFilePath ), fileContent ); 83 | } ); 84 | } 85 | 86 | function processES6File ( filePath, format = 'es', moduleName ) { 87 | const rollupPlugins = [ ]; 88 | 89 | if ( stringsToReplace[env] && Object.keys( stringsToReplace[env] ).length ) { 90 | const replaceOptions = { 91 | values: stringsToReplace[env], 92 | delimiters: [ '', '' ] 93 | }; 94 | 95 | rollupPlugins.push( replace( replaceOptions ) ); 96 | } 97 | 98 | rollupPlugins.push( 99 | nodeResolve(), 100 | commonjs() 101 | ); 102 | 103 | if ( es5Build ) { 104 | rollupPlugins.push( buble() ); 105 | } 106 | 107 | const rollupOptions = { 108 | input: filePath, 109 | plugins: rollupPlugins 110 | }; 111 | 112 | return rollup( rollupOptions ) 113 | .then( bundle => { 114 | const bundleOpts = { format }; 115 | 116 | if ( moduleName ) { 117 | bundleOpts.name = moduleName; 118 | } 119 | 120 | return bundle.generate( bundleOpts ) 121 | .then( bundleData => { 122 | return bundleData.output[0].code; 123 | } ); 124 | } ); 125 | } 126 | 127 | function processFileContent ( fileContent ) { 128 | return replaceImportedScripts ( fileContent ) 129 | .then( fileContent => { 130 | return isBrowser ? workersToBlobURL( fileContent ) : workersToWorkerFunction( fileContent ); 131 | } ) 132 | .then( fileContent => { 133 | if ( minifyBuild ) { 134 | return compressFileContent( fileContent ); 135 | } else { 136 | return fileContent; 137 | } 138 | } ); 139 | } 140 | 141 | function loadFile ( path ) { 142 | return new Promise( function ( resolve, reject ) { 143 | fs.readFile( path, 'utf8', ( err, data ) => { 144 | if ( err ) { 145 | reject( err ); 146 | } else { 147 | resolve( data ); 148 | } 149 | } ); 150 | } ); 151 | } 152 | 153 | function saveFile ( filePath, fileContent ) { 154 | return new Promise( function ( resolve, reject ) { 155 | fs.writeFile( filePath, fileContent, 'utf8', ( err, res ) => { 156 | if ( err ) { 157 | reject( err ); 158 | } else { 159 | resolve( fileContent ); 160 | } 161 | } ); 162 | } ); 163 | } 164 | 165 | function compressFileContent ( fileContent ) { 166 | let res; 167 | 168 | if ( es5Build ) { 169 | res = UglifyJS.minify( fileContent ); 170 | } else { 171 | res = UglifyES.minify( fileContent ); 172 | } 173 | 174 | if ( res.error ) { 175 | console.log( res.error ); 176 | } 177 | 178 | return res.code; 179 | } 180 | 181 | function replaceImportedScripts ( fileContent ) { 182 | let scriptPaths = getImportedScriptPaths( fileContent ); 183 | 184 | let loadScripts = scriptPaths.map( ( scriptPath ) => { 185 | return loadFile( globalPath + scriptPath ); 186 | } ); 187 | 188 | return Promise.all( loadScripts ) 189 | .then( ( scriptContents, scriptIndex ) => { 190 | return scriptContents.reduce( ( fileContent, scriptContent, scriptContentIndex ) => { 191 | return replaceImportedScript ( fileContent, scriptPaths[scriptContentIndex], scriptContent ); 192 | }, fileContent ); 193 | } ); 194 | } 195 | 196 | function workersToBlobURL ( fileContent ) { 197 | const workerPaths = getWorkerPaths( fileContent ); 198 | 199 | return Promise.all( workerPaths.map( ( workerPath ) => { 200 | const p = workerPath.indexOf( globalPath ) === 0 ? workerPath : globalPath + workerPath; 201 | 202 | return processES6File( p ); 203 | // return processWorkerFile( p ); 204 | } ) ) 205 | .then( ( workerContents ) => { 206 | return workerContents.map( ( workerContent, index ) => { 207 | return fileToBlobURL( workerContent ); 208 | } ); 209 | } ) 210 | .then( ( blobURLs ) => { 211 | return blobURLs.reduce( ( fileContent, blobUrl, workerIndex ) => { 212 | let sanitizedScriptPath = sanitizePathForRegEx( workerPaths[workerIndex] ); 213 | let pattern = "[\'\"]" + sanitizedScriptPath + '[\'\"]'; 214 | let regex = new RegExp( pattern, 'mig' ); 215 | 216 | return fileContent.replace( regex, blobUrl ); 217 | }, fileContent ); 218 | } ); 219 | } 220 | 221 | function workersToWorkerFunction ( fileContent ) { 222 | const workerPaths = getWorkerPaths( fileContent ); 223 | 224 | return Promise.all( workerPaths.map( ( workerPath ) => { 225 | const p = workerPath.indexOf( globalPath ) === 0 ? workerPath : globalPath + workerPath; 226 | 227 | return processES6File( p ); 228 | } ) ) 229 | .then( ( workerContents ) => { 230 | return workerContents.map( ( workerContent, index ) => { 231 | return fileToWorkerFunction( workerContent ); 232 | } ); 233 | } ) 234 | .then( ( workerFunctionStrings ) => { 235 | return workerFunctionStrings.reduce( ( fileContent, workerFunctionString, workerIndex ) => { 236 | let sanitizedScriptPath = sanitizePathForRegEx( workerPaths[workerIndex] ); 237 | let pattern = "[\'\"]" + sanitizedScriptPath + '[\'\"]'; 238 | let regex = new RegExp( pattern, 'mig' ); 239 | 240 | return fileContent.replace( regex, workerFunctionString ); 241 | }, fileContent ); 242 | } ); 243 | } 244 | 245 | function fileToBlobURL ( fileContent, type = 'text/javascript' ) { 246 | if ( minifyBuild ) { 247 | fileContent = compressFileContent( fileContent ); 248 | } 249 | 250 | const fileContentStr = JSON.stringify( fileContent ); 251 | 252 | return "URL.createObjectURL(new Blob([" + fileContentStr + "],{type:'" + type + "'}))"; 253 | } 254 | 255 | function fileToWorkerFunction ( fileContent ) { 256 | if ( minifyBuild ) { 257 | fileContent = compressFileContent( fileContent ); 258 | } 259 | 260 | return "function(){\n" + fileContent + "\n}"; 261 | } 262 | 263 | function getWorkerPaths ( fileContent ) { 264 | // let regex = /[\'\”](.*Worker.js)[\'\”]/mig; 265 | const regex = /new Worker\s?\(\s?([\"\'][a-zA-Z0-9\/.+=-]+)[\"\']\s?\)/g; 266 | 267 | let matches; 268 | let result = [ ]; 269 | 270 | while ( ( matches = regex.exec( fileContent ) ) !== null ) { 271 | // This is necessary to avoid infinite loops with zero-width matches 272 | if ( matches.index === regex.lastIndex ) { 273 | regex.lastIndex++; 274 | } 275 | 276 | // The result can be accessed through the `m`-variable. 277 | matches.forEach( ( match, groupIndex ) => { 278 | if ( groupIndex === 1 ) { 279 | result.push( match ); 280 | } 281 | } ); 282 | } 283 | 284 | return result.map( path => { 285 | return path.replace( /[\'\"]/g, '' ); 286 | }); 287 | } 288 | 289 | function getImportedScriptPaths ( fileContent ) { 290 | // let regex = /(importScripts|\(|"|')([a-zA-Z0-9\/\_\-]*\.js)/mig; 291 | let regex = /importScripts\([\"\'\s]+([a-zA-Z0-9\/\_\-]*\.js)[\"\'\s]+\)/mig; 292 | 293 | let matches; 294 | let result = [ ]; 295 | 296 | while ( ( matches = regex.exec( fileContent ) ) !== null ) { 297 | // This is necessary to avoid infinite loops with zero-width matches 298 | if ( matches.index === regex.lastIndex ) { 299 | regex.lastIndex++; 300 | } 301 | 302 | // The result can be accessed through the `m`-variable. 303 | matches.forEach( ( match, groupIndex ) => { 304 | if ( groupIndex === 1 ) { 305 | result.push( match ); 306 | } 307 | } ); 308 | } 309 | 310 | return result; 311 | } 312 | 313 | function replaceImportedScript ( fileContent, scriptPath, scriptContent ) { 314 | let sanitizedScriptPath = sanitizePathForRegEx( scriptPath ); 315 | let pattern = 'importScripts.*' + sanitizedScriptPath + '*.*\;'; 316 | let regex = new RegExp( pattern, 'mig' ); 317 | 318 | let res = fileContent.replace( regex, scriptContent ); 319 | 320 | return res; 321 | } 322 | 323 | function sanitizePathForRegEx ( path ) { 324 | return path 325 | .replace( /\//g, '\\/' ) 326 | .replace( /\-/g, '\\-' ) 327 | .replace( /\(/g, '\\(' ) 328 | .replace( /\"/g, '\\"' ) 329 | .replace( /\./g, '\\.' ); 330 | } 331 | 332 | function getOutputFileName ( filePath ) { 333 | let appendix = env; 334 | 335 | if ( polyfill ) { 336 | appendix += '-with-polyfills'; 337 | } 338 | 339 | if ( ! es5Build && es6Extension && es6Extension.length ) { 340 | appendix += '.' + es6Extension; 341 | } 342 | 343 | if ( bundleUMD && ! es5Build ) { 344 | appendix += '.' + umdExtension; 345 | } 346 | 347 | if ( minifyBuild ) { 348 | appendix += '.' + minifyExtension; 349 | } 350 | 351 | 352 | return fileName.replace( '{APPENDIX}', appendix ) + '.js'; 353 | } -------------------------------------------------------------------------------- /dist/glitch-canvas-browser-with-polyfills.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).glitch=e()}(this,function(){"use strict";var a={amount:35,iterations:20,quality:30,seed:25};function p(i){return"object"!=typeof(i=function(t){var e=!1;if(void 0!==t)try{e=JSON.parse(JSON.stringify(t))}catch(t){}return e}(i))&&(i={}),Object.keys(a).filter(function(t){return"iterations"!==t}).forEach(function(t){var e,n,r;"number"!=typeof i[t]||isNaN(i[t])?i[t]=a[t]:i[t]=(e=i[t],r=100,e<(n=0)?n:r>4);break;case 2:s.push((15&e)<<4|p>>2);break;case 3:s.push((3&e)<<6|p)}e=p}return s}function jpgHeaderLength(a){for(var e=417,s=0,t=a.length;s>2]);break;case 1:t.push(base64Map[(3&s)<<4|p>>4]);break;case 2:t.push(base64Map[(15&s)<<2|p>>6]),t.push(base64Map[63&p])}s=p}return 0===e?(t.push(base64Map[(3&s)<<4]),t.push("==")):1===e&&(t.push(base64Map[(15&s)<<2]),t.push("=")),t.join("")}function glitchImageData(a,e,s){if(isImageData(a))return byteArrayToBase64(glitchByteArray(base64ToByteArray(e),s.seed,s.amount,s.iterations));throw new Error("glitchImageData: imageData seems to be corrupt.")}function fail(a){self.postMessage({err:a.message||a})}function success(a){self.postMessage({base64URL:a})}onmessage=function(a){var e=a.data.imageData,s=a.data.params,t=a.data.base64URL;if(e&&t&&s)try{void 0===e.width&&"number"==typeof a.data.imageDataWidth&&(e.width=a.data.imageDataWidth),void 0===e.height&&"number"==typeof a.data.imageDataHeight&&(e.height=a.data.imageDataHeight),success(glitchImageData(e,t,s))}catch(a){fail(a)}else a.data.imageData?fail("Parameters are missing."):fail("ImageData is missing.");self.close()};'],{type:"text/javascript"}))),e={getParams:function(){return r},getInput:t,getOutput:u},n={fromImageData:function(t){return c(f,t)},fromImage:function(t){return c(g,t)}},i={toImage:function(t){return h(v,t,!0)},toDataURL:function(t){return h(f)},toImageData:function(t){return h(m,t,!0)}};function t(){var t=b({},e);return a||b(t,n),t}function u(){var t=b({},e);return o||b(t,i),t}function f(t){return t}function c(n,r,i){return a=function(){return new Promise(function(t,e){if(i)n(r,t,e);else if(n===f)t(r);else try{t(n(r,t,e))}catch(t){e(t)}})},(l()?d:u)()}function h(r,i,a){return o=function(n){return new Promise(function(t,e){a?r(n,i,t,e):r===f?t(n):r(n,i).then(t,e)})},(l()?d:t)()}function l(){return a&&o}function d(){return new Promise(function(e,n){a().then(function(t){return n=t,o=r,new Promise(function(t,e){y(n,o.quality).then(function(t){return r=n,i=t,a=o,new Promise(function(e,n){s.addEventListener("message",function(t){t.data&&t.data.base64URL?e(t.data.base64URL):t.data&&t.data.err?n(t.data.err):n(t)}),s.postMessage({params:a,base64URL:i,imageData:r,imageDataWidth:r.width,imageDataHeight:r.height})});var r,i,a},e).then(t,e)});var n,o},n).then(function(t){o(t).then(e,n)},n)})}return t()}}); -------------------------------------------------------------------------------- /dist/glitch-canvas-browser.es6.js: -------------------------------------------------------------------------------- 1 | function clamp (value, min, max) { 2 | return value < min ? min : value > max ? max : value; 3 | } 4 | 5 | function clone (obj) { 6 | let result = false; 7 | 8 | if (typeof obj !== 'undefined') { 9 | try { 10 | result = JSON.parse(JSON.stringify(obj)); 11 | } catch (e) {} 12 | } 13 | 14 | return result; 15 | } 16 | 17 | var defaultParams = { 18 | amount: 35, 19 | iterations: 20, 20 | quality: 30, 21 | seed: 25, 22 | }; 23 | 24 | function sanitizeInput (params) { 25 | params = clone(params); 26 | 27 | if (typeof params !== 'object') { 28 | params = {}; 29 | } 30 | 31 | Object.keys(defaultParams) 32 | .filter(key => key !== 'iterations') 33 | .forEach(key => { 34 | if (typeof params[key] !== 'number' || isNaN(params[key])) { 35 | params[key] = defaultParams[key]; 36 | } else { 37 | params[key] = clamp(params[key], 0, 100); 38 | } 39 | 40 | params[key] = Math.round(params[key]); 41 | }); 42 | 43 | if ( 44 | typeof params.iterations !== 'number' || 45 | isNaN(params.iterations) || 46 | params.iterations <= 0 47 | ) { 48 | params.iterations = defaultParams.iterations; 49 | } 50 | 51 | params.iterations = Math.round(params.iterations); 52 | 53 | return params; 54 | } 55 | 56 | class Canvas { 57 | constructor(width = 300, height = 150) { 58 | if (typeof window === 'undefined') { 59 | this.canvasEl = { width, height }; 60 | this.ctx = null; 61 | } else { 62 | this.canvasEl = document.createElement('canvas'); 63 | this.canvasEl.width = width; 64 | this.canvasEl.height = height; 65 | this.ctx = this.canvasEl.getContext('2d'); 66 | } 67 | } 68 | 69 | getContext() { 70 | return this.ctx; 71 | } 72 | 73 | toDataURL(type, encoderOptions, cb) { 74 | if (typeof cb === 'function') { 75 | cb(this.canvasEl.toDataURL(type, encoderOptions)); 76 | } else { 77 | return this.canvasEl.toDataURL(type, encoderOptions); 78 | } 79 | } 80 | 81 | get width() { 82 | return this.canvasEl.width; 83 | } 84 | 85 | set width(newWidth) { 86 | this.canvasEl.width = newWidth; 87 | } 88 | 89 | get height() { 90 | return this.canvasEl.height; 91 | } 92 | 93 | set height(newHeight) { 94 | this.canvasEl.height = newHeight; 95 | } 96 | } 97 | 98 | if (typeof window !== 'undefined') { 99 | Canvas.Image = Image; 100 | } 101 | 102 | function imageToImageData (image) { 103 | if (image instanceof HTMLImageElement) { 104 | // http://stackoverflow.com/a/3016076/229189 105 | if ( 106 | !image.naturalWidth || 107 | !image.naturalHeight || 108 | image.complete === false 109 | ) { 110 | throw new Error( 111 | "This this image hasn't finished loading: " + image.src 112 | ); 113 | } 114 | 115 | const canvas = new Canvas(image.naturalWidth, image.naturalHeight); 116 | const ctx = canvas.getContext('2d'); 117 | 118 | ctx.drawImage(image, 0, 0, canvas.width, canvas.height); 119 | 120 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 121 | 122 | if (imageData.data && imageData.data.length) { 123 | if (typeof imageData.width === 'undefined') { 124 | imageData.width = image.naturalWidth; 125 | } 126 | 127 | if (typeof imageData.height === 'undefined') { 128 | imageData.height = image.naturalHeight; 129 | } 130 | } 131 | 132 | return imageData; 133 | } else { 134 | throw new Error('This object does not seem to be an image.'); 135 | } 136 | } 137 | 138 | const Image$1 = Canvas.Image; 139 | 140 | function loadBase64Image (base64URL) { 141 | return new Promise((resolve, reject) => { 142 | const image = new Image$1(); 143 | 144 | image.onload = () => { 145 | resolve(image); 146 | }; 147 | 148 | image.onerror = reject; 149 | 150 | try { 151 | image.src = base64URL; 152 | } catch (err) { 153 | reject(err); 154 | } 155 | }); 156 | } 157 | 158 | function base64URLToImage (base64URL, opts, resolve, reject) { 159 | loadBase64Image(base64URL).then(resolve, reject); 160 | } 161 | 162 | function getImageSize (image) { 163 | return { 164 | width: image.width || image.naturalWidth, 165 | height: image.height || image.naturalHeight, 166 | }; 167 | } 168 | 169 | function canvasFromImage (image) { 170 | const size = getImageSize(image); 171 | const canvas = new Canvas(size.width, size.height); 172 | const ctx = canvas.getContext('2d'); 173 | 174 | ctx.drawImage(image, 0, 0, size.width, size.height); 175 | 176 | return { 177 | canvas: canvas, 178 | ctx: ctx, 179 | }; 180 | } 181 | 182 | function base64URLToImageData (base64URL, options, resolve, reject) { 183 | loadBase64Image(base64URL).then(image => { 184 | const size = getImageSize(image); 185 | const imageData = canvasFromImage(image).ctx.getImageData( 186 | 0, 187 | 0, 188 | size.width, 189 | size.height 190 | ); 191 | 192 | if (!imageData.width) { 193 | imageData.width = size.width; 194 | } 195 | 196 | if (!imageData.height) { 197 | imageData.height = size.height; 198 | } 199 | 200 | resolve(imageData); 201 | }, reject); 202 | } 203 | 204 | function isImageData (imageData) { 205 | return ( 206 | imageData && 207 | typeof imageData.width === 'number' && 208 | typeof imageData.height === 'number' && 209 | imageData.data && 210 | typeof imageData.data.length === 'number' && 211 | typeof imageData.data === 'object' 212 | ); 213 | } 214 | 215 | function imageDataToBase64 (imageData, quality) { 216 | return new Promise((resolve, reject) => { 217 | if (isImageData(imageData)) { 218 | const canvas = new Canvas(imageData.width, imageData.height); 219 | const ctx = canvas.getContext('2d'); 220 | ctx.putImageData(imageData, 0, 0); 221 | 222 | const base64URL = canvas.toDataURL('image/jpeg', quality / 100); 223 | 224 | resolve(base64URL); 225 | } else { 226 | reject(new Error('object is not valid imageData')); 227 | } 228 | }); 229 | } 230 | 231 | // import objectAssign from 'object-assign' 232 | const objectAssign = Object.assign; 233 | 234 | // constructing an object that allows for a chained interface. 235 | // for example stuff like: 236 | // 237 | // glitch( params ) 238 | // .toImage( img ) 239 | // .toImageData() 240 | // 241 | // etc... 242 | 243 | function glitch(params) { 244 | params = sanitizeInput(params); 245 | 246 | let inputFn; 247 | let outputFn; 248 | 249 | const worker = new Worker(URL.createObjectURL(new Blob(["function isImageData (imageData) {\n\treturn (\n\t\timageData &&\n\t\ttypeof imageData.width === 'number' &&\n\t\ttypeof imageData.height === 'number' &&\n\t\timageData.data &&\n\t\ttypeof imageData.data.length === 'number' &&\n\t\ttypeof imageData.data === 'object'\n\t);\n}\n\nconst base64Chars =\n\t'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\nconst base64Map$1 = base64Chars.split('');\nconst reversedBase64Map$1 = {};\n\nbase64Map$1.forEach((val, key) => {\n\treversedBase64Map$1[val] = key;\n});\n\nvar maps = {\n\tbase64Map: base64Map$1,\n\treversedBase64Map: reversedBase64Map$1,\n};\n\nconst reversedBase64Map = maps.reversedBase64Map;\n\n// https://github.com/mutaphysis/smackmyglitchupjs/blob/master/glitch.html\n// base64 is 2^6, byte is 2^8, every 4 base64 values create three bytes\nfunction base64ToByteArray (base64URL) {\n\tconst result = [];\n\tlet prev;\n\n\tconst srcURL = base64URL.replace('data:image/jpeg;base64,', '');\n\n\tfor (var i = 0, len = srcURL.length; i < len; i++) {\n\t\tsrcURL[i];\n\t\tconst currentChar = reversedBase64Map[srcURL[i]];\n\t\tconst digitNum = i % 4;\n\n\t\tswitch (digitNum) {\n\t\t\t// case 0: first digit - do nothing, not enough info to work with\n\t\t\tcase 1: // second digit\n\t\t\t\tresult.push((prev << 2) | (currentChar >> 4));\n\t\t\t\tbreak;\n\n\t\t\tcase 2: // third digit\n\t\t\t\tresult.push(((prev & 0x0f) << 4) | (currentChar >> 2));\n\t\t\t\tbreak;\n\n\t\t\tcase 3: // fourth digit\n\t\t\t\tresult.push(((prev & 3) << 6) | currentChar);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tprev = currentChar;\n\t}\n\n\treturn result;\n}\n\n// http://stackoverflow.com/a/10424014/229189\n\nfunction jpgHeaderLength (byteArr) {\n\tlet result = 417;\n\n\t// https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure\n\t// looks for the first occurence of 0xFF, 0xDA in the byteArray\n\t// which is the start of scan\n\tfor (let i = 0, len = byteArr.length; i < len; i++) {\n\t\tif (byteArr[i] === 0xff && byteArr[i + 1] === 0xda) {\n\t\t\tresult = i + 2;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nfunction glitchByteArray (byteArray, seed, amount, iterationCount) {\n\tconst headerLength = jpgHeaderLength(byteArray);\n\tconst maxIndex = byteArray.length - headerLength - 4;\n\n\tconst amountPercent = amount / 100;\n\tconst seedPercent = seed / 100;\n\n\tfor (\n\t\tvar iterationIndex = 0;\n\t\titerationIndex < iterationCount;\n\t\titerationIndex++\n\t) {\n\t\tconst minPixelIndex =\n\t\t\t((maxIndex / iterationCount) * iterationIndex) | 0;\n\t\tconst maxPixelIndex =\n\t\t\t((maxIndex / iterationCount) * (iterationIndex + 1)) | 0;\n\n\t\tconst delta = maxPixelIndex - minPixelIndex;\n\t\tlet pixelIndex = (minPixelIndex + delta * seedPercent) | 0;\n\n\t\tif (pixelIndex > maxIndex) {\n\t\t\tpixelIndex = maxIndex;\n\t\t}\n\n\t\tconst indexInByteArray = ~~(headerLength + pixelIndex);\n\n\t\tbyteArray[indexInByteArray] = ~~(amountPercent * 256);\n\t}\n\n\treturn byteArray;\n}\n\nconst base64Map = maps.base64Map;\n\nfunction byteArrayToBase64 (byteArray) {\n\tconst result = ['data:image/jpeg;base64,'];\n\tlet byteNum;\n\tlet previousByte;\n\n\tfor (let i = 0, len = byteArray.length; i < len; i++) {\n\t\tconst currentByte = byteArray[i];\n\t\tbyteNum = i % 3;\n\n\t\tswitch (byteNum) {\n\t\t\tcase 0: // first byte\n\t\t\t\tresult.push(base64Map[currentByte >> 2]);\n\t\t\t\tbreak;\n\t\t\tcase 1: // second byte\n\t\t\t\tresult.push(\n\t\t\t\t\tbase64Map[((previousByte & 3) << 4) | (currentByte >> 4)]\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 2: // third byte\n\t\t\t\tresult.push(\n\t\t\t\t\tbase64Map[((previousByte & 0x0f) << 2) | (currentByte >> 6)]\n\t\t\t\t);\n\t\t\t\tresult.push(base64Map[currentByte & 0x3f]);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tpreviousByte = currentByte;\n\t}\n\n\tif (byteNum === 0) {\n\t\tresult.push(base64Map[(previousByte & 3) << 4]);\n\t\tresult.push('==');\n\t} else {\n\t\tif (byteNum === 1) {\n\t\t\tresult.push(base64Map[(previousByte & 0x0f) << 2]);\n\t\t\tresult.push('=');\n\t\t}\n\t}\n\n\treturn result.join('');\n}\n\nfunction glitchImageData (imageData, base64URL, params) {\n\tif (isImageData(imageData)) {\n\t\tconst byteArray = base64ToByteArray(base64URL);\n\t\tconst glitchedByteArray = glitchByteArray(\n\t\t\tbyteArray,\n\t\t\tparams.seed,\n\t\t\tparams.amount,\n\t\t\tparams.iterations\n\t\t);\n\t\tconst glitchedBase64URL = byteArrayToBase64(glitchedByteArray);\n\t\treturn glitchedBase64URL;\n\t} else {\n\t\tthrow new Error('glitchImageData: imageData seems to be corrupt.');\n\t}\n}\n\nonmessage = msg => {\n\tconst imageData = msg.data.imageData;\n\tconst params = msg.data.params;\n\tconst base64URL = msg.data.base64URL;\n\n\tif (imageData && base64URL && params) {\n\t\ttry {\n\t\t\t// phantomjs seems to have some memory loss so we need to make sure\n\t\t\tif (\n\t\t\t\ttypeof imageData.width === 'undefined' &&\n\t\t\t\ttypeof msg.data.imageDataWidth === 'number'\n\t\t\t) {\n\t\t\t\timageData.width = msg.data.imageDataWidth;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\ttypeof imageData.height === 'undefined' &&\n\t\t\t\ttypeof msg.data.imageDataHeight === 'number'\n\t\t\t) {\n\t\t\t\timageData.height = msg.data.imageDataHeight;\n\t\t\t}\n\n\t\t\tconst glitchedBase64URL = glitchImageData(\n\t\t\t\timageData,\n\t\t\t\tbase64URL,\n\t\t\t\tparams\n\t\t\t);\n\t\t\tsuccess(glitchedBase64URL);\n\t\t} catch (err) {\n\t\t\tfail(err);\n\t\t}\n\t} else {\n\t\tif (msg.data.imageData) {\n\t\t\tfail('Parameters are missing.');\n\t\t} else {\n\t\t\tfail('ImageData is missing.');\n\t\t}\n\t}\n\n\tself.close();\n};\n\nfunction fail(err) {\n\tself.postMessage({ err: err.message || err });\n}\n\nfunction success(base64URL) {\n\tself.postMessage({ base64URL: base64URL });\n}\n"],{type:'text/javascript'}))); 250 | 251 | const api = { getParams, getInput, getOutput }; 252 | const inputMethods = { fromImageData, fromImage }; 253 | const outputMethods = { toImage, toDataURL, toImageData }; 254 | 255 | function getParams() { 256 | return params; 257 | } 258 | 259 | function getInput() { 260 | const result = objectAssign({}, api); 261 | 262 | if (!inputFn) { 263 | objectAssign(result, inputMethods); 264 | } 265 | 266 | return result; 267 | } 268 | 269 | function getOutput() { 270 | const result = objectAssign({}, api); 271 | 272 | if (!outputFn) { 273 | objectAssign(result, outputMethods); 274 | } 275 | 276 | return result; 277 | } 278 | 279 | function noTransform(x) { 280 | return x; 281 | } 282 | 283 | function fromImage(inputOptions) { 284 | return setInput(imageToImageData, inputOptions); 285 | } 286 | function fromImageData(inputOptions) { 287 | return setInput(noTransform, inputOptions); 288 | } 289 | 290 | function toDataURL(outputOptions) { 291 | return setOutput(noTransform); 292 | } 293 | function toImage(outputOptions) { 294 | return setOutput(base64URLToImage, outputOptions, true); 295 | } 296 | function toImageData(outputOptions) { 297 | return setOutput(base64URLToImageData, outputOptions, true); 298 | } 299 | 300 | function setInput(fn, inputOptions, canResolve) { 301 | inputFn = () => { 302 | return new Promise((resolve, reject) => { 303 | if (canResolve) { 304 | fn(inputOptions, resolve, reject); 305 | } else { 306 | if (fn === noTransform) { 307 | resolve(inputOptions); 308 | } else { 309 | try { 310 | resolve(fn(inputOptions, resolve, reject)); 311 | } catch (err) { 312 | reject(err); 313 | } 314 | } 315 | } 316 | }); 317 | }; 318 | 319 | if (isReady()) { 320 | return getResult(); 321 | } else { 322 | return getOutput(); 323 | } 324 | } 325 | 326 | function setOutput(fn, outputOptions, canResolve) { 327 | outputFn = base64URL => { 328 | return new Promise((resolve, reject) => { 329 | if (canResolve) { 330 | fn(base64URL, outputOptions, resolve, reject); 331 | } else { 332 | if (fn === noTransform) { 333 | resolve(base64URL); 334 | } else { 335 | fn(base64URL, outputOptions).then(resolve, reject); 336 | } 337 | } 338 | }); 339 | }; 340 | 341 | if (isReady()) { 342 | return getResult(); 343 | } else { 344 | return getInput(); 345 | } 346 | } 347 | 348 | function isReady() { 349 | return inputFn && outputFn; 350 | } 351 | 352 | function getResult() { 353 | return new Promise((resolve, reject) => { 354 | inputFn() 355 | .then(imageData => { 356 | return glitch(imageData, params); 357 | }, reject) 358 | .then(base64URL => { 359 | outputFn(base64URL).then(resolve, reject); 360 | }, reject); 361 | }); 362 | } 363 | 364 | function glitch(imageData, params) { 365 | return new Promise((resolve, reject) => { 366 | imageDataToBase64(imageData, params.quality) 367 | .then(base64URL => { 368 | return glitchInWorker(imageData, base64URL, params); 369 | }, reject) 370 | .then(resolve, reject); 371 | }); 372 | } 373 | 374 | function glitchInWorker(imageData, base64URL, params) { 375 | return new Promise((resolve, reject) => { 376 | worker.addEventListener('message', event => { 377 | if (event.data && event.data.base64URL) { 378 | resolve(event.data.base64URL); 379 | } else { 380 | if (event.data && event.data.err) { 381 | reject(event.data.err); 382 | } else { 383 | reject(event); 384 | } 385 | } 386 | }); 387 | 388 | worker.postMessage({ 389 | params: params, 390 | base64URL: base64URL, 391 | imageData: imageData, 392 | 393 | // phantomjs tends to forget about those two 394 | // so we send them separately 395 | imageDataWidth: imageData.width, 396 | imageDataHeight: imageData.height, 397 | }); 398 | }); 399 | } 400 | 401 | return getInput(); 402 | } 403 | 404 | export default glitch; 405 | -------------------------------------------------------------------------------- /dist/glitch-canvas-browser.es6.min.js: -------------------------------------------------------------------------------- 1 | function clamp(e,a,t){return et?t:e}function clone(e){let a=!1;if(void 0!==e)try{a=JSON.parse(JSON.stringify(e))}catch(e){}return a}var defaultParams={amount:35,iterations:20,quality:30,seed:25};function sanitizeInput(e){return"object"!=typeof(e=clone(e))&&(e={}),Object.keys(defaultParams).filter(e=>"iterations"!==e).forEach(a=>{"number"!=typeof e[a]||isNaN(e[a])?e[a]=defaultParams[a]:e[a]=clamp(e[a],0,100),e[a]=Math.round(e[a])}),("number"!=typeof e.iterations||isNaN(e.iterations)||e.iterations<=0)&&(e.iterations=defaultParams.iterations),e.iterations=Math.round(e.iterations),e}class Canvas{constructor(e=300,a=150){"undefined"==typeof window?(this.canvasEl={width:e,height:a},this.ctx=null):(this.canvasEl=document.createElement("canvas"),this.canvasEl.width=e,this.canvasEl.height=a,this.ctx=this.canvasEl.getContext("2d"))}getContext(){return this.ctx}toDataURL(e,a,t){if("function"!=typeof t)return this.canvasEl.toDataURL(e,a);t(this.canvasEl.toDataURL(e,a))}get width(){return this.canvasEl.width}set width(e){this.canvasEl.width=e}get height(){return this.canvasEl.height}set height(e){this.canvasEl.height=e}}function imageToImageData(e){if(e instanceof HTMLImageElement){if(!e.naturalWidth||!e.naturalHeight||!1===e.complete)throw new Error("This this image hasn't finished loading: "+e.src);const a=new Canvas(e.naturalWidth,e.naturalHeight),t=a.getContext("2d");t.drawImage(e,0,0,a.width,a.height);const n=t.getImageData(0,0,a.width,a.height);return n.data&&n.data.length&&(void 0===n.width&&(n.width=e.naturalWidth),void 0===n.height&&(n.height=e.naturalHeight)),n}throw new Error("This object does not seem to be an image.")}"undefined"!=typeof window&&(Canvas.Image=Image);const Image$1=Canvas.Image;function loadBase64Image(e){return new Promise((a,t)=>{const n=new Image$1;n.onload=(()=>{a(n)}),n.onerror=t;try{n.src=e}catch(e){t(e)}})}function base64URLToImage(e,a,t,n){loadBase64Image(e).then(t,n)}function getImageSize(e){return{width:e.width||e.naturalWidth,height:e.height||e.naturalHeight}}function canvasFromImage(e){const a=getImageSize(e),t=new Canvas(a.width,a.height),n=t.getContext("2d");return n.drawImage(e,0,0,a.width,a.height),{canvas:t,ctx:n}}function base64URLToImageData(e,a,t,n){loadBase64Image(e).then(e=>{const a=getImageSize(e),n=canvasFromImage(e).ctx.getImageData(0,0,a.width,a.height);n.width||(n.width=a.width),n.height||(n.height=a.height),t(n)},n)}function isImageData(e){return e&&"number"==typeof e.width&&"number"==typeof e.height&&e.data&&"number"==typeof e.data.length&&"object"==typeof e.data}function imageDataToBase64(e,a){return new Promise((t,n)=>{if(isImageData(e)){const n=new Canvas(e.width,e.height);n.getContext("2d").putImageData(e,0,0),t(n.toDataURL("image/jpeg",a/100))}else n(new Error("object is not valid imageData"))})}const objectAssign=Object.assign;function glitch(e){let a,t;e=sanitizeInput(e);const n=new Worker(URL.createObjectURL(new Blob(['function isImageData(a){return a&&"number"==typeof a.width&&"number"==typeof a.height&&a.data&&"number"==typeof a.data.length&&"object"==typeof a.data}const base64Chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",base64Map$1=base64Chars.split(""),reversedBase64Map$1={};base64Map$1.forEach((a,e)=>{reversedBase64Map$1[a]=e});var maps={base64Map:base64Map$1,reversedBase64Map:reversedBase64Map$1};const reversedBase64Map=maps.reversedBase64Map;function base64ToByteArray(a){const e=[];let s;const t=a.replace("data:image/jpeg;base64,","");for(var r=0,i=t.length;r>4);break;case 2:e.push((15&s)<<4|a>>2);break;case 3:e.push((3&s)<<6|a)}s=a}return e}function jpgHeaderLength(a){let e=417;for(let s=0,t=a.length;si&&(s=i),a[~~(r+s)]=~~(256*n)}return a}const base64Map=maps.base64Map;function byteArrayToBase64(a){const e=["data:image/jpeg;base64,"];let s,t;for(let r=0,i=a.length;r>2]);break;case 1:e.push(base64Map[(3&t)<<4|i>>4]);break;case 2:e.push(base64Map[(15&t)<<2|i>>6]),e.push(base64Map[63&i])}t=i}return 0===s?(e.push(base64Map[(3&t)<<4]),e.push("==")):1===s&&(e.push(base64Map[(15&t)<<2]),e.push("=")),e.join("")}function glitchImageData(a,e,s){if(isImageData(a)){return byteArrayToBase64(glitchByteArray(base64ToByteArray(e),s.seed,s.amount,s.iterations))}throw new Error("glitchImageData: imageData seems to be corrupt.")}function fail(a){self.postMessage({err:a.message||a})}function success(a){self.postMessage({base64URL:a})}onmessage=(a=>{const e=a.data.imageData,s=a.data.params,t=a.data.base64URL;if(e&&t&&s)try{void 0===e.width&&"number"==typeof a.data.imageDataWidth&&(e.width=a.data.imageDataWidth),void 0===e.height&&"number"==typeof a.data.imageDataHeight&&(e.height=a.data.imageDataHeight),success(glitchImageData(e,t,s))}catch(a){fail(a)}else a.data.imageData?fail("Parameters are missing."):fail("ImageData is missing.");self.close()});'],{type:"text/javascript"}))),s={getParams:function(){return e},getInput:o,getOutput:h},i={fromImageData:function(e){return c(g,e)},fromImage:function(e){return c(imageToImageData,e)}},r={toImage:function(e){return u(base64URLToImage,e,!0)},toDataURL:function(e){return u(g)},toImageData:function(e){return u(base64URLToImageData,e,!0)}};function o(){const e=objectAssign({},s);return a||objectAssign(e,i),e}function h(){const e=objectAssign({},s);return t||objectAssign(e,r),e}function g(e){return e}function c(e,t,n){return a=(()=>new Promise((a,s)=>{if(n)e(t,a,s);else if(e===g)a(t);else try{a(e(t,a,s))}catch(e){s(e)}})),m()?d():h()}function u(e,a,n){return t=(t=>new Promise((s,i)=>{n?e(t,a,s,i):e===g?s(t):e(t,a).then(s,i)})),m()?d():o()}function m(){return a&&t}function d(){return new Promise((s,i)=>{a().then(a=>(function(e,a){return new Promise((t,s)=>{imageDataToBase64(e,a.quality).then(t=>(function(e,a,t){return new Promise((s,i)=>{n.addEventListener("message",e=>{e.data&&e.data.base64URL?s(e.data.base64URL):e.data&&e.data.err?i(e.data.err):i(e)}),n.postMessage({params:t,base64URL:a,imageData:e,imageDataWidth:e.width,imageDataHeight:e.height})})})(e,t,a),s).then(t,s)})})(a,e),i).then(e=>{t(e).then(s,i)},i)})}return o()}export default glitch; -------------------------------------------------------------------------------- /dist/glitch-canvas-browser.es6.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).glitch=e()}(this,function(){"use strict";var t={amount:35,iterations:20,quality:30,seed:25};function e(e){var a,n,i;return"object"!=typeof(e=function(t){let e=!1;if(void 0!==t)try{e=JSON.parse(JSON.stringify(t))}catch(t){}return e}(e))&&(e={}),Object.keys(t).filter(t=>"iterations"!==t).forEach(s=>{"number"!=typeof e[s]||isNaN(e[s])?e[s]=t[s]:e[s]=(a=e[s],i=100,a<(n=0)?n:a>i?i:a),e[s]=Math.round(e[s])}),("number"!=typeof e.iterations||isNaN(e.iterations)||e.iterations<=0)&&(e.iterations=t.iterations),e.iterations=Math.round(e.iterations),e}class a{constructor(t=300,e=150){"undefined"==typeof window?(this.canvasEl={width:t,height:e},this.ctx=null):(this.canvasEl=document.createElement("canvas"),this.canvasEl.width=t,this.canvasEl.height=e,this.ctx=this.canvasEl.getContext("2d"))}getContext(){return this.ctx}toDataURL(t,e,a){if("function"!=typeof a)return this.canvasEl.toDataURL(t,e);a(this.canvasEl.toDataURL(t,e))}get width(){return this.canvasEl.width}set width(t){this.canvasEl.width=t}get height(){return this.canvasEl.height}set height(t){this.canvasEl.height=t}}function n(t){if(t instanceof HTMLImageElement){if(!t.naturalWidth||!t.naturalHeight||!1===t.complete)throw new Error("This this image hasn't finished loading: "+t.src);const e=new a(t.naturalWidth,t.naturalHeight),n=e.getContext("2d");n.drawImage(t,0,0,e.width,e.height);const i=n.getImageData(0,0,e.width,e.height);return i.data&&i.data.length&&(void 0===i.width&&(i.width=t.naturalWidth),void 0===i.height&&(i.height=t.naturalHeight)),i}throw new Error("This object does not seem to be an image.")}"undefined"!=typeof window&&(a.Image=Image);const i=a.Image;function s(t){return new Promise((e,a)=>{const n=new i;n.onload=(()=>{e(n)}),n.onerror=a;try{n.src=t}catch(t){a(t)}})}function r(t,e,a,n){s(t).then(a,n)}function o(t){return{width:t.width||t.naturalWidth,height:t.height||t.naturalHeight}}function h(t,e,n,i){s(t).then(t=>{const e=o(t),i=function(t){const e=o(t),n=new a(e.width,e.height),i=n.getContext("2d");return i.drawImage(t,0,0,e.width,e.height),{canvas:n,ctx:i}}(t).ctx.getImageData(0,0,e.width,e.height);i.width||(i.width=e.width),i.height||(i.height=e.height),n(i)},i)}const c=Object.assign;return function(t){let i,s;t=e(t);const o=new Worker(URL.createObjectURL(new Blob(['function isImageData(a){return a&&"number"==typeof a.width&&"number"==typeof a.height&&a.data&&"number"==typeof a.data.length&&"object"==typeof a.data}const base64Chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",base64Map$1=base64Chars.split(""),reversedBase64Map$1={};base64Map$1.forEach((a,e)=>{reversedBase64Map$1[a]=e});var maps={base64Map:base64Map$1,reversedBase64Map:reversedBase64Map$1};const reversedBase64Map=maps.reversedBase64Map;function base64ToByteArray(a){const e=[];let s;const t=a.replace("data:image/jpeg;base64,","");for(var r=0,i=t.length;r>4);break;case 2:e.push((15&s)<<4|a>>2);break;case 3:e.push((3&s)<<6|a)}s=a}return e}function jpgHeaderLength(a){let e=417;for(let s=0,t=a.length;si&&(s=i),a[~~(r+s)]=~~(256*n)}return a}const base64Map=maps.base64Map;function byteArrayToBase64(a){const e=["data:image/jpeg;base64,"];let s,t;for(let r=0,i=a.length;r>2]);break;case 1:e.push(base64Map[(3&t)<<4|i>>4]);break;case 2:e.push(base64Map[(15&t)<<2|i>>6]),e.push(base64Map[63&i])}t=i}return 0===s?(e.push(base64Map[(3&t)<<4]),e.push("==")):1===s&&(e.push(base64Map[(15&t)<<2]),e.push("=")),e.join("")}function glitchImageData(a,e,s){if(isImageData(a)){return byteArrayToBase64(glitchByteArray(base64ToByteArray(e),s.seed,s.amount,s.iterations))}throw new Error("glitchImageData: imageData seems to be corrupt.")}function fail(a){self.postMessage({err:a.message||a})}function success(a){self.postMessage({base64URL:a})}onmessage=(a=>{const e=a.data.imageData,s=a.data.params,t=a.data.base64URL;if(e&&t&&s)try{void 0===e.width&&"number"==typeof a.data.imageDataWidth&&(e.width=a.data.imageDataWidth),void 0===e.height&&"number"==typeof a.data.imageDataHeight&&(e.height=a.data.imageDataHeight),success(glitchImageData(e,t,s))}catch(a){fail(a)}else a.data.imageData?fail("Parameters are missing."):fail("ImageData is missing.");self.close()});'],{type:"text/javascript"}))),u={getParams:function(){return t},getInput:f,getOutput:m},g={fromImageData:function(t){return l(p,t)},fromImage:function(t){return l(n,t)}},d={toImage:function(t){return b(r,t,!0)},toDataURL:function(t){return b(p)},toImageData:function(t){return b(h,t,!0)}};function f(){const t=c({},u);return i||c(t,g),t}function m(){const t=c({},u);return s||c(t,d),t}function p(t){return t}function l(t,e,a){return i=(()=>new Promise((n,i)=>{if(a)t(e,n,i);else if(t===p)n(e);else try{n(t(e,n,i))}catch(t){i(t)}})),w()?y():m()}function b(t,e,a){return s=(n=>new Promise((i,s)=>{a?t(n,e,i,s):t===p?i(n):t(n,e).then(i,s)})),w()?y():f()}function w(){return i&&s}function y(){return new Promise((e,n)=>{i().then(e=>(function(t,e){return new Promise((n,i)=>{(function(t,e){return new Promise((n,i)=>{if(function(t){return t&&"number"==typeof t.width&&"number"==typeof t.height&&t.data&&"number"==typeof t.data.length&&"object"==typeof t.data}(t)){const i=new a(t.width,t.height);i.getContext("2d").putImageData(t,0,0),n(i.toDataURL("image/jpeg",e/100))}else i(new Error("object is not valid imageData"))})})(t,e.quality).then(a=>(function(t,e,a){return new Promise((n,i)=>{o.addEventListener("message",t=>{t.data&&t.data.base64URL?n(t.data.base64URL):t.data&&t.data.err?i(t.data.err):i(t)}),o.postMessage({params:a,base64URL:e,imageData:t,imageDataWidth:t.width,imageDataHeight:t.height})})})(t,a,e),i).then(n,i)})})(e,t),n).then(t=>{s(t).then(e,n)},n)})}return f()}}); -------------------------------------------------------------------------------- /dist/glitch-canvas-browser.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).glitch=e()}(this,function(){"use strict";var r={amount:35,iterations:20,quality:30,seed:25};function p(i){return"object"!=typeof(i=function(t){var e=!1;if(void 0!==t)try{e=JSON.parse(JSON.stringify(t))}catch(t){}return e}(i))&&(i={}),Object.keys(r).filter(function(t){return"iterations"!==t}).forEach(function(t){var e,a,n;"number"!=typeof i[t]||isNaN(i[t])?i[t]=r[t]:i[t]=(e=i[t],n=100,e<(a=0)?a:n>4);break;case 2:s.push((15&e)<<4|p>>2);break;case 3:s.push((3&e)<<6|p)}e=p}return s}function jpgHeaderLength(a){for(var e=417,s=0,t=a.length;s>2]);break;case 1:t.push(base64Map[(3&s)<<4|p>>4]);break;case 2:t.push(base64Map[(15&s)<<2|p>>6]),t.push(base64Map[63&p])}s=p}return 0===e?(t.push(base64Map[(3&s)<<4]),t.push("==")):1===e&&(t.push(base64Map[(15&s)<<2]),t.push("=")),t.join("")}function glitchImageData(a,e,s){if(isImageData(a))return byteArrayToBase64(glitchByteArray(base64ToByteArray(e),s.seed,s.amount,s.iterations));throw new Error("glitchImageData: imageData seems to be corrupt.")}function fail(a){self.postMessage({err:a.message||a})}function success(a){self.postMessage({base64URL:a})}onmessage=function(a){var e=a.data.imageData,s=a.data.params,t=a.data.base64URL;if(e&&t&&s)try{void 0===e.width&&"number"==typeof a.data.imageDataWidth&&(e.width=a.data.imageDataWidth),void 0===e.height&&"number"==typeof a.data.imageDataHeight&&(e.height=a.data.imageDataHeight),success(glitchImageData(e,t,s))}catch(a){fail(a)}else a.data.imageData?fail("Parameters are missing."):fail("ImageData is missing.");self.close()};'],{type:"text/javascript"}))),e={getParams:function(){return n},getInput:t,getOutput:h},a={fromImageData:function(t){return c(u,t)},fromImage:function(t){return c(m,t)}},i={toImage:function(t){return f(l,t,!0)},toDataURL:function(t){return f(u)},toImageData:function(t){return f(v,t,!0)}};function t(){var t=w({},e);return r||w(t,a),t}function h(){var t=w({},e);return s||w(t,i),t}function u(t){return t}function c(a,n,i){return r=function(){return new Promise(function(t,e){if(i)a(n,t,e);else if(a===u)t(n);else try{t(a(n,t,e))}catch(t){e(t)}})},(g()?d:h)()}function f(n,i,r){return s=function(a){return new Promise(function(t,e){r?n(a,i,t,e):n===u?t(a):n(a,i).then(t,e)})},(g()?d:t)()}function g(){return r&&s}function d(){return new Promise(function(e,a){r().then(function(t){return a=t,s=n,new Promise(function(t,e){b(a,s.quality).then(function(t){return n=a,i=t,r=s,new Promise(function(e,a){o.addEventListener("message",function(t){t.data&&t.data.base64URL?e(t.data.base64URL):t.data&&t.data.err?a(t.data.err):a(t)}),o.postMessage({params:r,base64URL:i,imageData:n,imageDataWidth:n.width,imageDataHeight:n.height})});var n,i,r},e).then(t,e)});var a,s},a).then(function(t){s(t).then(e,a)},a)})}return t()}}); -------------------------------------------------------------------------------- /examples/browser-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | glitch-canvas example 6 | 12 | 13 | 14 |

toDataURL

15 |
16 |

toImageData

17 |
18 | 19 | 20 | 21 | 70 | 71 | -------------------------------------------------------------------------------- /examples/browser-module-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | glitch-canvas example 6 | 12 | 13 | 14 |

toDataURL

15 |
16 |

toImageData

17 |
18 | 19 |

toImage

20 |
21 | 22 | 78 | 79 | -------------------------------------------------------------------------------- /examples/img/lincoln.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snorpey/glitch-canvas/72283d134e9c5666d20fa985cd715c49138e1b75/examples/img/lincoln.jpg -------------------------------------------------------------------------------- /examples/node-example.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var glitch = require('../dist/glitch-canvas-node.js'); 3 | var imagePath = 'img/lincoln.jpg'; 4 | 5 | fromBufferToDataURL(); 6 | fromBufferToPng(); 7 | fromBufferToJPGStream(); 8 | fromBufferToPNGStream(); 9 | fromStreamToPNGStream(); 10 | 11 | function fromBufferToDataURL () { 12 | fs.readFile( __dirname + '/' + imagePath, function ( err, buffer ) { 13 | if ( err ) { 14 | throw err; 15 | } 16 | 17 | glitch() 18 | .fromBuffer( buffer ) 19 | .toDataURL() 20 | .then ( function ( dataURL ) { 21 | console.log( 'fromBufferToDataURL complete. Length of dataURL:', dataURL.length ); 22 | }, function ( err ) { 23 | throw err; 24 | } ); 25 | } ); 26 | } 27 | 28 | function fromBufferToPng () { 29 | fs.readFile( __dirname + '/' + imagePath, function ( err, buffer ) { 30 | if ( err ) { 31 | throw err; 32 | } 33 | 34 | glitch() 35 | .fromBuffer( buffer ) 36 | .toBuffer() 37 | .then( function ( imageBuffer ) { 38 | fs.writeFile( __dirname + '/node-output/fromBufferToPng.png', imageBuffer, function ( err ) { 39 | if ( err ) { 40 | throw err; 41 | } else { 42 | console.log( 'fromBufferToPng complete. File saved to', __dirname + '/node-output/fromBufferToPng.png' ); 43 | } 44 | } ); 45 | } ); 46 | } ); 47 | } 48 | 49 | function fromBufferToJPGStream () { 50 | fs.readFile( __dirname + '/' + imagePath, function ( err, buffer ) { 51 | if ( err ) { 52 | throw err; 53 | } 54 | 55 | var fileStream = fs.createWriteStream( __dirname + '/node-output/fromBufferToJPGStream.jpg' ); 56 | 57 | glitch() 58 | .fromBuffer( buffer ) 59 | .toJPGStream() 60 | .then( function ( jpgStream ) { 61 | jpgStream.on( 'data', function ( chunk ) { fileStream.write( chunk ); } ); 62 | jpgStream.on( 'end', function () { console.log( 'fromBufferToJPGStream complete. File saved to', __dirname + '/node-output/fromBufferToJPGStream.jpg' ); } ); 63 | } ); 64 | } ); 65 | } 66 | 67 | function fromBufferToPNGStream () { 68 | fs.readFile( __dirname + '/' + imagePath, function ( err, buffer ) { 69 | if ( err ) { 70 | throw err; 71 | } 72 | 73 | var fileStream = fs.createWriteStream( __dirname + '/node-output/fromBufferToPNGStream.jpg' ); 74 | 75 | glitch() 76 | .fromBuffer( buffer ) 77 | .toPNGStream() 78 | .then( function ( pngStream ) { 79 | pngStream.on( 'data', function ( chunk ) { fileStream.write( chunk ); } ); 80 | pngStream.on( 'end', function () { console.log( 'fromBufferToPNGStream complete. File saved to', __dirname + '/node-output/fromBufferToPNGStream.jpg' ); } ); 81 | }, function( err ) { 82 | console.log( 'There was an error', err ); 83 | } ); 84 | } ); 85 | } 86 | 87 | function fromStreamToPNGStream () { 88 | var readStream = fs.createReadStream( __dirname + '/' + imagePath ); 89 | var fileStream = fs.createWriteStream( __dirname + '/node-output/fromStreamToPNGStream.jpg' ); 90 | 91 | glitch() 92 | .fromStream( readStream ) 93 | .toPNGStream() 94 | .then( function ( pngStream ) { 95 | pngStream.on( 'data', function ( chunk ) { fileStream.write( chunk ); } ); 96 | pngStream.on( 'end', function () { console.log( 'fromStreamToPNGStream complete. File saved to', __dirname + '/node-output/fromBufferToPNGStream.jpg' ); } ); 97 | }, function( err ) { 98 | console.log( 'There was an error', err ); 99 | } ); 100 | } -------------------------------------------------------------------------------- /examples/node-output/.gitkeep: -------------------------------------------------------------------------------- 1 | . -------------------------------------------------------------------------------- /glitch-canvas-browser/glitch-canvas-browser-with-polyfills.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).glitch=e()}(this,function(){"use strict";var a={amount:35,iterations:20,quality:30,seed:25};function p(i){return"object"!=typeof(i=function(t){var e=!1;if(void 0!==t)try{e=JSON.parse(JSON.stringify(t))}catch(t){}return e}(i))&&(i={}),Object.keys(a).filter(function(t){return"iterations"!==t}).forEach(function(t){var e,n,r;"number"!=typeof i[t]||isNaN(i[t])?i[t]=a[t]:i[t]=(e=i[t],r=100,e<(n=0)?n:r>4);break;case 2:s.push((15&e)<<4|p>>2);break;case 3:s.push((3&e)<<6|p)}e=p}return s}function jpgHeaderLength(a){for(var e=417,s=0,t=a.length;s>2]);break;case 1:t.push(base64Map[(3&s)<<4|p>>4]);break;case 2:t.push(base64Map[(15&s)<<2|p>>6]),t.push(base64Map[63&p])}s=p}return 0===e?(t.push(base64Map[(3&s)<<4]),t.push("==")):1===e&&(t.push(base64Map[(15&s)<<2]),t.push("=")),t.join("")}function glitchImageData(a,e,s){if(isImageData(a))return byteArrayToBase64(glitchByteArray(base64ToByteArray(e),s.seed,s.amount,s.iterations));throw new Error("glitchImageData: imageData seems to be corrupt.")}function fail(a){self.postMessage({err:a.message||a})}function success(a){self.postMessage({base64URL:a})}onmessage=function(a){var e=a.data.imageData,s=a.data.params,t=a.data.base64URL;if(e&&t&&s)try{void 0===e.width&&"number"==typeof a.data.imageDataWidth&&(e.width=a.data.imageDataWidth),void 0===e.height&&"number"==typeof a.data.imageDataHeight&&(e.height=a.data.imageDataHeight),success(glitchImageData(e,t,s))}catch(a){fail(a)}else a.data.imageData?fail("Parameters are missing."):fail("ImageData is missing.");self.close()};'],{type:"text/javascript"}))),e={getParams:function(){return r},getInput:t,getOutput:u},n={fromImageData:function(t){return c(f,t)},fromImage:function(t){return c(g,t)}},i={toImage:function(t){return h(v,t,!0)},toDataURL:function(t){return h(f)},toImageData:function(t){return h(m,t,!0)}};function t(){var t=b({},e);return a||b(t,n),t}function u(){var t=b({},e);return o||b(t,i),t}function f(t){return t}function c(n,r,i){return a=function(){return new Promise(function(t,e){if(i)n(r,t,e);else if(n===f)t(r);else try{t(n(r,t,e))}catch(t){e(t)}})},(l()?d:u)()}function h(r,i,a){return o=function(n){return new Promise(function(t,e){a?r(n,i,t,e):r===f?t(n):r(n,i).then(t,e)})},(l()?d:t)()}function l(){return a&&o}function d(){return new Promise(function(e,n){a().then(function(t){return n=t,o=r,new Promise(function(t,e){y(n,o.quality).then(function(t){return r=n,i=t,a=o,new Promise(function(e,n){s.addEventListener("message",function(t){t.data&&t.data.base64URL?e(t.data.base64URL):t.data&&t.data.err?n(t.data.err):n(t)}),s.postMessage({params:a,base64URL:i,imageData:r,imageDataWidth:r.width,imageDataHeight:r.height})});var r,i,a},e).then(t,e)});var n,o},n).then(function(t){o(t).then(e,n)},n)})}return t()}}); -------------------------------------------------------------------------------- /glitch-canvas-browser/glitch-canvas-browser.es6.js: -------------------------------------------------------------------------------- 1 | function clamp (value, min, max) { 2 | return value < min ? min : value > max ? max : value; 3 | } 4 | 5 | function clone (obj) { 6 | let result = false; 7 | 8 | if (typeof obj !== 'undefined') { 9 | try { 10 | result = JSON.parse(JSON.stringify(obj)); 11 | } catch (e) {} 12 | } 13 | 14 | return result; 15 | } 16 | 17 | var defaultParams = { 18 | amount: 35, 19 | iterations: 20, 20 | quality: 30, 21 | seed: 25, 22 | }; 23 | 24 | function sanitizeInput (params) { 25 | params = clone(params); 26 | 27 | if (typeof params !== 'object') { 28 | params = {}; 29 | } 30 | 31 | Object.keys(defaultParams) 32 | .filter(key => key !== 'iterations') 33 | .forEach(key => { 34 | if (typeof params[key] !== 'number' || isNaN(params[key])) { 35 | params[key] = defaultParams[key]; 36 | } else { 37 | params[key] = clamp(params[key], 0, 100); 38 | } 39 | 40 | params[key] = Math.round(params[key]); 41 | }); 42 | 43 | if ( 44 | typeof params.iterations !== 'number' || 45 | isNaN(params.iterations) || 46 | params.iterations <= 0 47 | ) { 48 | params.iterations = defaultParams.iterations; 49 | } 50 | 51 | params.iterations = Math.round(params.iterations); 52 | 53 | return params; 54 | } 55 | 56 | class Canvas { 57 | constructor(width = 300, height = 150) { 58 | if (typeof window === 'undefined') { 59 | this.canvasEl = { width, height }; 60 | this.ctx = null; 61 | } else { 62 | this.canvasEl = document.createElement('canvas'); 63 | this.canvasEl.width = width; 64 | this.canvasEl.height = height; 65 | this.ctx = this.canvasEl.getContext('2d'); 66 | } 67 | } 68 | 69 | getContext() { 70 | return this.ctx; 71 | } 72 | 73 | toDataURL(type, encoderOptions, cb) { 74 | if (typeof cb === 'function') { 75 | cb(this.canvasEl.toDataURL(type, encoderOptions)); 76 | } else { 77 | return this.canvasEl.toDataURL(type, encoderOptions); 78 | } 79 | } 80 | 81 | get width() { 82 | return this.canvasEl.width; 83 | } 84 | 85 | set width(newWidth) { 86 | this.canvasEl.width = newWidth; 87 | } 88 | 89 | get height() { 90 | return this.canvasEl.height; 91 | } 92 | 93 | set height(newHeight) { 94 | this.canvasEl.height = newHeight; 95 | } 96 | } 97 | 98 | if (typeof window !== 'undefined') { 99 | Canvas.Image = Image; 100 | } 101 | 102 | function imageToImageData (image) { 103 | if (image instanceof HTMLImageElement) { 104 | // http://stackoverflow.com/a/3016076/229189 105 | if ( 106 | !image.naturalWidth || 107 | !image.naturalHeight || 108 | image.complete === false 109 | ) { 110 | throw new Error( 111 | "This this image hasn't finished loading: " + image.src 112 | ); 113 | } 114 | 115 | const canvas = new Canvas(image.naturalWidth, image.naturalHeight); 116 | const ctx = canvas.getContext('2d'); 117 | 118 | ctx.drawImage(image, 0, 0, canvas.width, canvas.height); 119 | 120 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 121 | 122 | if (imageData.data && imageData.data.length) { 123 | if (typeof imageData.width === 'undefined') { 124 | imageData.width = image.naturalWidth; 125 | } 126 | 127 | if (typeof imageData.height === 'undefined') { 128 | imageData.height = image.naturalHeight; 129 | } 130 | } 131 | 132 | return imageData; 133 | } else { 134 | throw new Error('This object does not seem to be an image.'); 135 | } 136 | } 137 | 138 | const Image$1 = Canvas.Image; 139 | 140 | function loadBase64Image (base64URL) { 141 | return new Promise((resolve, reject) => { 142 | const image = new Image$1(); 143 | 144 | image.onload = () => { 145 | resolve(image); 146 | }; 147 | 148 | image.onerror = reject; 149 | 150 | try { 151 | image.src = base64URL; 152 | } catch (err) { 153 | reject(err); 154 | } 155 | }); 156 | } 157 | 158 | function base64URLToImage (base64URL, opts, resolve, reject) { 159 | loadBase64Image(base64URL).then(resolve, reject); 160 | } 161 | 162 | function getImageSize (image) { 163 | return { 164 | width: image.width || image.naturalWidth, 165 | height: image.height || image.naturalHeight, 166 | }; 167 | } 168 | 169 | function canvasFromImage (image) { 170 | const size = getImageSize(image); 171 | const canvas = new Canvas(size.width, size.height); 172 | const ctx = canvas.getContext('2d'); 173 | 174 | ctx.drawImage(image, 0, 0, size.width, size.height); 175 | 176 | return { 177 | canvas: canvas, 178 | ctx: ctx, 179 | }; 180 | } 181 | 182 | function base64URLToImageData (base64URL, options, resolve, reject) { 183 | loadBase64Image(base64URL).then(image => { 184 | const size = getImageSize(image); 185 | const imageData = canvasFromImage(image).ctx.getImageData( 186 | 0, 187 | 0, 188 | size.width, 189 | size.height 190 | ); 191 | 192 | if (!imageData.width) { 193 | imageData.width = size.width; 194 | } 195 | 196 | if (!imageData.height) { 197 | imageData.height = size.height; 198 | } 199 | 200 | resolve(imageData); 201 | }, reject); 202 | } 203 | 204 | function isImageData (imageData) { 205 | return ( 206 | imageData && 207 | typeof imageData.width === 'number' && 208 | typeof imageData.height === 'number' && 209 | imageData.data && 210 | typeof imageData.data.length === 'number' && 211 | typeof imageData.data === 'object' 212 | ); 213 | } 214 | 215 | function imageDataToBase64 (imageData, quality) { 216 | return new Promise((resolve, reject) => { 217 | if (isImageData(imageData)) { 218 | const canvas = new Canvas(imageData.width, imageData.height); 219 | const ctx = canvas.getContext('2d'); 220 | ctx.putImageData(imageData, 0, 0); 221 | 222 | const base64URL = canvas.toDataURL('image/jpeg', quality / 100); 223 | 224 | resolve(base64URL); 225 | } else { 226 | reject(new Error('object is not valid imageData')); 227 | } 228 | }); 229 | } 230 | 231 | // import objectAssign from 'object-assign' 232 | const objectAssign = Object.assign; 233 | 234 | // constructing an object that allows for a chained interface. 235 | // for example stuff like: 236 | // 237 | // glitch( params ) 238 | // .toImage( img ) 239 | // .toImageData() 240 | // 241 | // etc... 242 | 243 | function glitch(params) { 244 | params = sanitizeInput(params); 245 | 246 | let inputFn; 247 | let outputFn; 248 | 249 | const worker = new Worker(URL.createObjectURL(new Blob(["function isImageData (imageData) {\n\treturn (\n\t\timageData &&\n\t\ttypeof imageData.width === 'number' &&\n\t\ttypeof imageData.height === 'number' &&\n\t\timageData.data &&\n\t\ttypeof imageData.data.length === 'number' &&\n\t\ttypeof imageData.data === 'object'\n\t);\n}\n\nconst base64Chars =\n\t'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\nconst base64Map$1 = base64Chars.split('');\nconst reversedBase64Map$1 = {};\n\nbase64Map$1.forEach((val, key) => {\n\treversedBase64Map$1[val] = key;\n});\n\nvar maps = {\n\tbase64Map: base64Map$1,\n\treversedBase64Map: reversedBase64Map$1,\n};\n\nconst reversedBase64Map = maps.reversedBase64Map;\n\n// https://github.com/mutaphysis/smackmyglitchupjs/blob/master/glitch.html\n// base64 is 2^6, byte is 2^8, every 4 base64 values create three bytes\nfunction base64ToByteArray (base64URL) {\n\tconst result = [];\n\tlet prev;\n\n\tconst srcURL = base64URL.replace('data:image/jpeg;base64,', '');\n\n\tfor (var i = 0, len = srcURL.length; i < len; i++) {\n\t\tsrcURL[i];\n\t\tconst currentChar = reversedBase64Map[srcURL[i]];\n\t\tconst digitNum = i % 4;\n\n\t\tswitch (digitNum) {\n\t\t\t// case 0: first digit - do nothing, not enough info to work with\n\t\t\tcase 1: // second digit\n\t\t\t\tresult.push((prev << 2) | (currentChar >> 4));\n\t\t\t\tbreak;\n\n\t\t\tcase 2: // third digit\n\t\t\t\tresult.push(((prev & 0x0f) << 4) | (currentChar >> 2));\n\t\t\t\tbreak;\n\n\t\t\tcase 3: // fourth digit\n\t\t\t\tresult.push(((prev & 3) << 6) | currentChar);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tprev = currentChar;\n\t}\n\n\treturn result;\n}\n\n// http://stackoverflow.com/a/10424014/229189\n\nfunction jpgHeaderLength (byteArr) {\n\tlet result = 417;\n\n\t// https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure\n\t// looks for the first occurence of 0xFF, 0xDA in the byteArray\n\t// which is the start of scan\n\tfor (let i = 0, len = byteArr.length; i < len; i++) {\n\t\tif (byteArr[i] === 0xff && byteArr[i + 1] === 0xda) {\n\t\t\tresult = i + 2;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn result;\n}\n\nfunction glitchByteArray (byteArray, seed, amount, iterationCount) {\n\tconst headerLength = jpgHeaderLength(byteArray);\n\tconst maxIndex = byteArray.length - headerLength - 4;\n\n\tconst amountPercent = amount / 100;\n\tconst seedPercent = seed / 100;\n\n\tfor (\n\t\tvar iterationIndex = 0;\n\t\titerationIndex < iterationCount;\n\t\titerationIndex++\n\t) {\n\t\tconst minPixelIndex =\n\t\t\t((maxIndex / iterationCount) * iterationIndex) | 0;\n\t\tconst maxPixelIndex =\n\t\t\t((maxIndex / iterationCount) * (iterationIndex + 1)) | 0;\n\n\t\tconst delta = maxPixelIndex - minPixelIndex;\n\t\tlet pixelIndex = (minPixelIndex + delta * seedPercent) | 0;\n\n\t\tif (pixelIndex > maxIndex) {\n\t\t\tpixelIndex = maxIndex;\n\t\t}\n\n\t\tconst indexInByteArray = ~~(headerLength + pixelIndex);\n\n\t\tbyteArray[indexInByteArray] = ~~(amountPercent * 256);\n\t}\n\n\treturn byteArray;\n}\n\nconst base64Map = maps.base64Map;\n\nfunction byteArrayToBase64 (byteArray) {\n\tconst result = ['data:image/jpeg;base64,'];\n\tlet byteNum;\n\tlet previousByte;\n\n\tfor (let i = 0, len = byteArray.length; i < len; i++) {\n\t\tconst currentByte = byteArray[i];\n\t\tbyteNum = i % 3;\n\n\t\tswitch (byteNum) {\n\t\t\tcase 0: // first byte\n\t\t\t\tresult.push(base64Map[currentByte >> 2]);\n\t\t\t\tbreak;\n\t\t\tcase 1: // second byte\n\t\t\t\tresult.push(\n\t\t\t\t\tbase64Map[((previousByte & 3) << 4) | (currentByte >> 4)]\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 2: // third byte\n\t\t\t\tresult.push(\n\t\t\t\t\tbase64Map[((previousByte & 0x0f) << 2) | (currentByte >> 6)]\n\t\t\t\t);\n\t\t\t\tresult.push(base64Map[currentByte & 0x3f]);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tpreviousByte = currentByte;\n\t}\n\n\tif (byteNum === 0) {\n\t\tresult.push(base64Map[(previousByte & 3) << 4]);\n\t\tresult.push('==');\n\t} else {\n\t\tif (byteNum === 1) {\n\t\t\tresult.push(base64Map[(previousByte & 0x0f) << 2]);\n\t\t\tresult.push('=');\n\t\t}\n\t}\n\n\treturn result.join('');\n}\n\nfunction glitchImageData (imageData, base64URL, params) {\n\tif (isImageData(imageData)) {\n\t\tconst byteArray = base64ToByteArray(base64URL);\n\t\tconst glitchedByteArray = glitchByteArray(\n\t\t\tbyteArray,\n\t\t\tparams.seed,\n\t\t\tparams.amount,\n\t\t\tparams.iterations\n\t\t);\n\t\tconst glitchedBase64URL = byteArrayToBase64(glitchedByteArray);\n\t\treturn glitchedBase64URL;\n\t} else {\n\t\tthrow new Error('glitchImageData: imageData seems to be corrupt.');\n\t}\n}\n\nonmessage = msg => {\n\tconst imageData = msg.data.imageData;\n\tconst params = msg.data.params;\n\tconst base64URL = msg.data.base64URL;\n\n\tif (imageData && base64URL && params) {\n\t\ttry {\n\t\t\t// phantomjs seems to have some memory loss so we need to make sure\n\t\t\tif (\n\t\t\t\ttypeof imageData.width === 'undefined' &&\n\t\t\t\ttypeof msg.data.imageDataWidth === 'number'\n\t\t\t) {\n\t\t\t\timageData.width = msg.data.imageDataWidth;\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\ttypeof imageData.height === 'undefined' &&\n\t\t\t\ttypeof msg.data.imageDataHeight === 'number'\n\t\t\t) {\n\t\t\t\timageData.height = msg.data.imageDataHeight;\n\t\t\t}\n\n\t\t\tconst glitchedBase64URL = glitchImageData(\n\t\t\t\timageData,\n\t\t\t\tbase64URL,\n\t\t\t\tparams\n\t\t\t);\n\t\t\tsuccess(glitchedBase64URL);\n\t\t} catch (err) {\n\t\t\tfail(err);\n\t\t}\n\t} else {\n\t\tif (msg.data.imageData) {\n\t\t\tfail('Parameters are missing.');\n\t\t} else {\n\t\t\tfail('ImageData is missing.');\n\t\t}\n\t}\n\n\tself.close();\n};\n\nfunction fail(err) {\n\tself.postMessage({ err: err.message || err });\n}\n\nfunction success(base64URL) {\n\tself.postMessage({ base64URL: base64URL });\n}\n"],{type:'text/javascript'}))); 250 | 251 | const api = { getParams, getInput, getOutput }; 252 | const inputMethods = { fromImageData, fromImage }; 253 | const outputMethods = { toImage, toDataURL, toImageData }; 254 | 255 | function getParams() { 256 | return params; 257 | } 258 | 259 | function getInput() { 260 | const result = objectAssign({}, api); 261 | 262 | if (!inputFn) { 263 | objectAssign(result, inputMethods); 264 | } 265 | 266 | return result; 267 | } 268 | 269 | function getOutput() { 270 | const result = objectAssign({}, api); 271 | 272 | if (!outputFn) { 273 | objectAssign(result, outputMethods); 274 | } 275 | 276 | return result; 277 | } 278 | 279 | function noTransform(x) { 280 | return x; 281 | } 282 | 283 | function fromImage(inputOptions) { 284 | return setInput(imageToImageData, inputOptions); 285 | } 286 | function fromImageData(inputOptions) { 287 | return setInput(noTransform, inputOptions); 288 | } 289 | 290 | function toDataURL(outputOptions) { 291 | return setOutput(noTransform); 292 | } 293 | function toImage(outputOptions) { 294 | return setOutput(base64URLToImage, outputOptions, true); 295 | } 296 | function toImageData(outputOptions) { 297 | return setOutput(base64URLToImageData, outputOptions, true); 298 | } 299 | 300 | function setInput(fn, inputOptions, canResolve) { 301 | inputFn = () => { 302 | return new Promise((resolve, reject) => { 303 | if (canResolve) { 304 | fn(inputOptions, resolve, reject); 305 | } else { 306 | if (fn === noTransform) { 307 | resolve(inputOptions); 308 | } else { 309 | try { 310 | resolve(fn(inputOptions, resolve, reject)); 311 | } catch (err) { 312 | reject(err); 313 | } 314 | } 315 | } 316 | }); 317 | }; 318 | 319 | if (isReady()) { 320 | return getResult(); 321 | } else { 322 | return getOutput(); 323 | } 324 | } 325 | 326 | function setOutput(fn, outputOptions, canResolve) { 327 | outputFn = base64URL => { 328 | return new Promise((resolve, reject) => { 329 | if (canResolve) { 330 | fn(base64URL, outputOptions, resolve, reject); 331 | } else { 332 | if (fn === noTransform) { 333 | resolve(base64URL); 334 | } else { 335 | fn(base64URL, outputOptions).then(resolve, reject); 336 | } 337 | } 338 | }); 339 | }; 340 | 341 | if (isReady()) { 342 | return getResult(); 343 | } else { 344 | return getInput(); 345 | } 346 | } 347 | 348 | function isReady() { 349 | return inputFn && outputFn; 350 | } 351 | 352 | function getResult() { 353 | return new Promise((resolve, reject) => { 354 | inputFn() 355 | .then(imageData => { 356 | return glitch(imageData, params); 357 | }, reject) 358 | .then(base64URL => { 359 | outputFn(base64URL).then(resolve, reject); 360 | }, reject); 361 | }); 362 | } 363 | 364 | function glitch(imageData, params) { 365 | return new Promise((resolve, reject) => { 366 | imageDataToBase64(imageData, params.quality) 367 | .then(base64URL => { 368 | return glitchInWorker(imageData, base64URL, params); 369 | }, reject) 370 | .then(resolve, reject); 371 | }); 372 | } 373 | 374 | function glitchInWorker(imageData, base64URL, params) { 375 | return new Promise((resolve, reject) => { 376 | worker.addEventListener('message', event => { 377 | if (event.data && event.data.base64URL) { 378 | resolve(event.data.base64URL); 379 | } else { 380 | if (event.data && event.data.err) { 381 | reject(event.data.err); 382 | } else { 383 | reject(event); 384 | } 385 | } 386 | }); 387 | 388 | worker.postMessage({ 389 | params: params, 390 | base64URL: base64URL, 391 | imageData: imageData, 392 | 393 | // phantomjs tends to forget about those two 394 | // so we send them separately 395 | imageDataWidth: imageData.width, 396 | imageDataHeight: imageData.height, 397 | }); 398 | }); 399 | } 400 | 401 | return getInput(); 402 | } 403 | 404 | export default glitch; 405 | -------------------------------------------------------------------------------- /glitch-canvas-browser/glitch-canvas-browser.es6.min.js: -------------------------------------------------------------------------------- 1 | function clamp(e,a,t){return et?t:e}function clone(e){let a=!1;if(void 0!==e)try{a=JSON.parse(JSON.stringify(e))}catch(e){}return a}var defaultParams={amount:35,iterations:20,quality:30,seed:25};function sanitizeInput(e){return"object"!=typeof(e=clone(e))&&(e={}),Object.keys(defaultParams).filter(e=>"iterations"!==e).forEach(a=>{"number"!=typeof e[a]||isNaN(e[a])?e[a]=defaultParams[a]:e[a]=clamp(e[a],0,100),e[a]=Math.round(e[a])}),("number"!=typeof e.iterations||isNaN(e.iterations)||e.iterations<=0)&&(e.iterations=defaultParams.iterations),e.iterations=Math.round(e.iterations),e}class Canvas{constructor(e=300,a=150){"undefined"==typeof window?(this.canvasEl={width:e,height:a},this.ctx=null):(this.canvasEl=document.createElement("canvas"),this.canvasEl.width=e,this.canvasEl.height=a,this.ctx=this.canvasEl.getContext("2d"))}getContext(){return this.ctx}toDataURL(e,a,t){if("function"!=typeof t)return this.canvasEl.toDataURL(e,a);t(this.canvasEl.toDataURL(e,a))}get width(){return this.canvasEl.width}set width(e){this.canvasEl.width=e}get height(){return this.canvasEl.height}set height(e){this.canvasEl.height=e}}function imageToImageData(e){if(e instanceof HTMLImageElement){if(!e.naturalWidth||!e.naturalHeight||!1===e.complete)throw new Error("This this image hasn't finished loading: "+e.src);const a=new Canvas(e.naturalWidth,e.naturalHeight),t=a.getContext("2d");t.drawImage(e,0,0,a.width,a.height);const n=t.getImageData(0,0,a.width,a.height);return n.data&&n.data.length&&(void 0===n.width&&(n.width=e.naturalWidth),void 0===n.height&&(n.height=e.naturalHeight)),n}throw new Error("This object does not seem to be an image.")}"undefined"!=typeof window&&(Canvas.Image=Image);const Image$1=Canvas.Image;function loadBase64Image(e){return new Promise((a,t)=>{const n=new Image$1;n.onload=(()=>{a(n)}),n.onerror=t;try{n.src=e}catch(e){t(e)}})}function base64URLToImage(e,a,t,n){loadBase64Image(e).then(t,n)}function getImageSize(e){return{width:e.width||e.naturalWidth,height:e.height||e.naturalHeight}}function canvasFromImage(e){const a=getImageSize(e),t=new Canvas(a.width,a.height),n=t.getContext("2d");return n.drawImage(e,0,0,a.width,a.height),{canvas:t,ctx:n}}function base64URLToImageData(e,a,t,n){loadBase64Image(e).then(e=>{const a=getImageSize(e),n=canvasFromImage(e).ctx.getImageData(0,0,a.width,a.height);n.width||(n.width=a.width),n.height||(n.height=a.height),t(n)},n)}function isImageData(e){return e&&"number"==typeof e.width&&"number"==typeof e.height&&e.data&&"number"==typeof e.data.length&&"object"==typeof e.data}function imageDataToBase64(e,a){return new Promise((t,n)=>{if(isImageData(e)){const n=new Canvas(e.width,e.height);n.getContext("2d").putImageData(e,0,0),t(n.toDataURL("image/jpeg",a/100))}else n(new Error("object is not valid imageData"))})}const objectAssign=Object.assign;function glitch(e){let a,t;e=sanitizeInput(e);const n=new Worker(URL.createObjectURL(new Blob(['function isImageData(a){return a&&"number"==typeof a.width&&"number"==typeof a.height&&a.data&&"number"==typeof a.data.length&&"object"==typeof a.data}const base64Chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",base64Map$1=base64Chars.split(""),reversedBase64Map$1={};base64Map$1.forEach((a,e)=>{reversedBase64Map$1[a]=e});var maps={base64Map:base64Map$1,reversedBase64Map:reversedBase64Map$1};const reversedBase64Map=maps.reversedBase64Map;function base64ToByteArray(a){const e=[];let s;const t=a.replace("data:image/jpeg;base64,","");for(var r=0,i=t.length;r>4);break;case 2:e.push((15&s)<<4|a>>2);break;case 3:e.push((3&s)<<6|a)}s=a}return e}function jpgHeaderLength(a){let e=417;for(let s=0,t=a.length;si&&(s=i),a[~~(r+s)]=~~(256*n)}return a}const base64Map=maps.base64Map;function byteArrayToBase64(a){const e=["data:image/jpeg;base64,"];let s,t;for(let r=0,i=a.length;r>2]);break;case 1:e.push(base64Map[(3&t)<<4|i>>4]);break;case 2:e.push(base64Map[(15&t)<<2|i>>6]),e.push(base64Map[63&i])}t=i}return 0===s?(e.push(base64Map[(3&t)<<4]),e.push("==")):1===s&&(e.push(base64Map[(15&t)<<2]),e.push("=")),e.join("")}function glitchImageData(a,e,s){if(isImageData(a)){return byteArrayToBase64(glitchByteArray(base64ToByteArray(e),s.seed,s.amount,s.iterations))}throw new Error("glitchImageData: imageData seems to be corrupt.")}function fail(a){self.postMessage({err:a.message||a})}function success(a){self.postMessage({base64URL:a})}onmessage=(a=>{const e=a.data.imageData,s=a.data.params,t=a.data.base64URL;if(e&&t&&s)try{void 0===e.width&&"number"==typeof a.data.imageDataWidth&&(e.width=a.data.imageDataWidth),void 0===e.height&&"number"==typeof a.data.imageDataHeight&&(e.height=a.data.imageDataHeight),success(glitchImageData(e,t,s))}catch(a){fail(a)}else a.data.imageData?fail("Parameters are missing."):fail("ImageData is missing.");self.close()});'],{type:"text/javascript"}))),s={getParams:function(){return e},getInput:o,getOutput:h},i={fromImageData:function(e){return c(g,e)},fromImage:function(e){return c(imageToImageData,e)}},r={toImage:function(e){return u(base64URLToImage,e,!0)},toDataURL:function(e){return u(g)},toImageData:function(e){return u(base64URLToImageData,e,!0)}};function o(){const e=objectAssign({},s);return a||objectAssign(e,i),e}function h(){const e=objectAssign({},s);return t||objectAssign(e,r),e}function g(e){return e}function c(e,t,n){return a=(()=>new Promise((a,s)=>{if(n)e(t,a,s);else if(e===g)a(t);else try{a(e(t,a,s))}catch(e){s(e)}})),m()?d():h()}function u(e,a,n){return t=(t=>new Promise((s,i)=>{n?e(t,a,s,i):e===g?s(t):e(t,a).then(s,i)})),m()?d():o()}function m(){return a&&t}function d(){return new Promise((s,i)=>{a().then(a=>(function(e,a){return new Promise((t,s)=>{imageDataToBase64(e,a.quality).then(t=>(function(e,a,t){return new Promise((s,i)=>{n.addEventListener("message",e=>{e.data&&e.data.base64URL?s(e.data.base64URL):e.data&&e.data.err?i(e.data.err):i(e)}),n.postMessage({params:t,base64URL:a,imageData:e,imageDataWidth:e.width,imageDataHeight:e.height})})})(e,t,a),s).then(t,s)})})(a,e),i).then(e=>{t(e).then(s,i)},i)})}return o()}export default glitch; -------------------------------------------------------------------------------- /glitch-canvas-browser/glitch-canvas-browser.es6.umd.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).glitch=e()}(this,function(){"use strict";var t={amount:35,iterations:20,quality:30,seed:25};function e(e){var a,n,i;return"object"!=typeof(e=function(t){let e=!1;if(void 0!==t)try{e=JSON.parse(JSON.stringify(t))}catch(t){}return e}(e))&&(e={}),Object.keys(t).filter(t=>"iterations"!==t).forEach(s=>{"number"!=typeof e[s]||isNaN(e[s])?e[s]=t[s]:e[s]=(a=e[s],i=100,a<(n=0)?n:a>i?i:a),e[s]=Math.round(e[s])}),("number"!=typeof e.iterations||isNaN(e.iterations)||e.iterations<=0)&&(e.iterations=t.iterations),e.iterations=Math.round(e.iterations),e}class a{constructor(t=300,e=150){"undefined"==typeof window?(this.canvasEl={width:t,height:e},this.ctx=null):(this.canvasEl=document.createElement("canvas"),this.canvasEl.width=t,this.canvasEl.height=e,this.ctx=this.canvasEl.getContext("2d"))}getContext(){return this.ctx}toDataURL(t,e,a){if("function"!=typeof a)return this.canvasEl.toDataURL(t,e);a(this.canvasEl.toDataURL(t,e))}get width(){return this.canvasEl.width}set width(t){this.canvasEl.width=t}get height(){return this.canvasEl.height}set height(t){this.canvasEl.height=t}}function n(t){if(t instanceof HTMLImageElement){if(!t.naturalWidth||!t.naturalHeight||!1===t.complete)throw new Error("This this image hasn't finished loading: "+t.src);const e=new a(t.naturalWidth,t.naturalHeight),n=e.getContext("2d");n.drawImage(t,0,0,e.width,e.height);const i=n.getImageData(0,0,e.width,e.height);return i.data&&i.data.length&&(void 0===i.width&&(i.width=t.naturalWidth),void 0===i.height&&(i.height=t.naturalHeight)),i}throw new Error("This object does not seem to be an image.")}"undefined"!=typeof window&&(a.Image=Image);const i=a.Image;function s(t){return new Promise((e,a)=>{const n=new i;n.onload=(()=>{e(n)}),n.onerror=a;try{n.src=t}catch(t){a(t)}})}function r(t,e,a,n){s(t).then(a,n)}function o(t){return{width:t.width||t.naturalWidth,height:t.height||t.naturalHeight}}function h(t,e,n,i){s(t).then(t=>{const e=o(t),i=function(t){const e=o(t),n=new a(e.width,e.height),i=n.getContext("2d");return i.drawImage(t,0,0,e.width,e.height),{canvas:n,ctx:i}}(t).ctx.getImageData(0,0,e.width,e.height);i.width||(i.width=e.width),i.height||(i.height=e.height),n(i)},i)}const c=Object.assign;return function(t){let i,s;t=e(t);const o=new Worker(URL.createObjectURL(new Blob(['function isImageData(a){return a&&"number"==typeof a.width&&"number"==typeof a.height&&a.data&&"number"==typeof a.data.length&&"object"==typeof a.data}const base64Chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",base64Map$1=base64Chars.split(""),reversedBase64Map$1={};base64Map$1.forEach((a,e)=>{reversedBase64Map$1[a]=e});var maps={base64Map:base64Map$1,reversedBase64Map:reversedBase64Map$1};const reversedBase64Map=maps.reversedBase64Map;function base64ToByteArray(a){const e=[];let s;const t=a.replace("data:image/jpeg;base64,","");for(var r=0,i=t.length;r>4);break;case 2:e.push((15&s)<<4|a>>2);break;case 3:e.push((3&s)<<6|a)}s=a}return e}function jpgHeaderLength(a){let e=417;for(let s=0,t=a.length;si&&(s=i),a[~~(r+s)]=~~(256*n)}return a}const base64Map=maps.base64Map;function byteArrayToBase64(a){const e=["data:image/jpeg;base64,"];let s,t;for(let r=0,i=a.length;r>2]);break;case 1:e.push(base64Map[(3&t)<<4|i>>4]);break;case 2:e.push(base64Map[(15&t)<<2|i>>6]),e.push(base64Map[63&i])}t=i}return 0===s?(e.push(base64Map[(3&t)<<4]),e.push("==")):1===s&&(e.push(base64Map[(15&t)<<2]),e.push("=")),e.join("")}function glitchImageData(a,e,s){if(isImageData(a)){return byteArrayToBase64(glitchByteArray(base64ToByteArray(e),s.seed,s.amount,s.iterations))}throw new Error("glitchImageData: imageData seems to be corrupt.")}function fail(a){self.postMessage({err:a.message||a})}function success(a){self.postMessage({base64URL:a})}onmessage=(a=>{const e=a.data.imageData,s=a.data.params,t=a.data.base64URL;if(e&&t&&s)try{void 0===e.width&&"number"==typeof a.data.imageDataWidth&&(e.width=a.data.imageDataWidth),void 0===e.height&&"number"==typeof a.data.imageDataHeight&&(e.height=a.data.imageDataHeight),success(glitchImageData(e,t,s))}catch(a){fail(a)}else a.data.imageData?fail("Parameters are missing."):fail("ImageData is missing.");self.close()});'],{type:"text/javascript"}))),u={getParams:function(){return t},getInput:f,getOutput:m},g={fromImageData:function(t){return l(p,t)},fromImage:function(t){return l(n,t)}},d={toImage:function(t){return b(r,t,!0)},toDataURL:function(t){return b(p)},toImageData:function(t){return b(h,t,!0)}};function f(){const t=c({},u);return i||c(t,g),t}function m(){const t=c({},u);return s||c(t,d),t}function p(t){return t}function l(t,e,a){return i=(()=>new Promise((n,i)=>{if(a)t(e,n,i);else if(t===p)n(e);else try{n(t(e,n,i))}catch(t){i(t)}})),w()?y():m()}function b(t,e,a){return s=(n=>new Promise((i,s)=>{a?t(n,e,i,s):t===p?i(n):t(n,e).then(i,s)})),w()?y():f()}function w(){return i&&s}function y(){return new Promise((e,n)=>{i().then(e=>(function(t,e){return new Promise((n,i)=>{(function(t,e){return new Promise((n,i)=>{if(function(t){return t&&"number"==typeof t.width&&"number"==typeof t.height&&t.data&&"number"==typeof t.data.length&&"object"==typeof t.data}(t)){const i=new a(t.width,t.height);i.getContext("2d").putImageData(t,0,0),n(i.toDataURL("image/jpeg",e/100))}else i(new Error("object is not valid imageData"))})})(t,e.quality).then(a=>(function(t,e,a){return new Promise((n,i)=>{o.addEventListener("message",t=>{t.data&&t.data.base64URL?n(t.data.base64URL):t.data&&t.data.err?i(t.data.err):i(t)}),o.postMessage({params:a,base64URL:e,imageData:t,imageDataWidth:t.width,imageDataHeight:t.height})})})(t,a,e),i).then(n,i)})})(e,t),n).then(t=>{s(t).then(e,n)},n)})}return f()}}); -------------------------------------------------------------------------------- /glitch-canvas-browser/glitch-canvas-browser.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).glitch=e()}(this,function(){"use strict";var r={amount:35,iterations:20,quality:30,seed:25};function p(i){return"object"!=typeof(i=function(t){var e=!1;if(void 0!==t)try{e=JSON.parse(JSON.stringify(t))}catch(t){}return e}(i))&&(i={}),Object.keys(r).filter(function(t){return"iterations"!==t}).forEach(function(t){var e,a,n;"number"!=typeof i[t]||isNaN(i[t])?i[t]=r[t]:i[t]=(e=i[t],n=100,e<(a=0)?a:n>4);break;case 2:s.push((15&e)<<4|p>>2);break;case 3:s.push((3&e)<<6|p)}e=p}return s}function jpgHeaderLength(a){for(var e=417,s=0,t=a.length;s>2]);break;case 1:t.push(base64Map[(3&s)<<4|p>>4]);break;case 2:t.push(base64Map[(15&s)<<2|p>>6]),t.push(base64Map[63&p])}s=p}return 0===e?(t.push(base64Map[(3&s)<<4]),t.push("==")):1===e&&(t.push(base64Map[(15&s)<<2]),t.push("=")),t.join("")}function glitchImageData(a,e,s){if(isImageData(a))return byteArrayToBase64(glitchByteArray(base64ToByteArray(e),s.seed,s.amount,s.iterations));throw new Error("glitchImageData: imageData seems to be corrupt.")}function fail(a){self.postMessage({err:a.message||a})}function success(a){self.postMessage({base64URL:a})}onmessage=function(a){var e=a.data.imageData,s=a.data.params,t=a.data.base64URL;if(e&&t&&s)try{void 0===e.width&&"number"==typeof a.data.imageDataWidth&&(e.width=a.data.imageDataWidth),void 0===e.height&&"number"==typeof a.data.imageDataHeight&&(e.height=a.data.imageDataHeight),success(glitchImageData(e,t,s))}catch(a){fail(a)}else a.data.imageData?fail("Parameters are missing."):fail("ImageData is missing.");self.close()};'],{type:"text/javascript"}))),e={getParams:function(){return n},getInput:t,getOutput:h},a={fromImageData:function(t){return c(u,t)},fromImage:function(t){return c(m,t)}},i={toImage:function(t){return f(l,t,!0)},toDataURL:function(t){return f(u)},toImageData:function(t){return f(v,t,!0)}};function t(){var t=w({},e);return r||w(t,a),t}function h(){var t=w({},e);return s||w(t,i),t}function u(t){return t}function c(a,n,i){return r=function(){return new Promise(function(t,e){if(i)a(n,t,e);else if(a===u)t(n);else try{t(a(n,t,e))}catch(t){e(t)}})},(g()?d:h)()}function f(n,i,r){return s=function(a){return new Promise(function(t,e){r?n(a,i,t,e):n===u?t(a):n(a,i).then(t,e)})},(g()?d:t)()}function g(){return r&&s}function d(){return new Promise(function(e,a){r().then(function(t){return a=t,s=n,new Promise(function(t,e){b(a,s.quality).then(function(t){return n=a,i=t,r=s,new Promise(function(e,a){o.addEventListener("message",function(t){t.data&&t.data.base64URL?e(t.data.base64URL):t.data&&t.data.err?a(t.data.err):a(t)}),o.postMessage({params:r,base64URL:i,imageData:n,imageDataWidth:n.width,imageDataHeight:n.height})});var n,i,r},e).then(t,e)});var a,s},a).then(function(t){s(t).then(e,a)},a)})}return t()}}); -------------------------------------------------------------------------------- /glitch-canvas-browser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glitch-canvas-browser", 3 | "version": "1.1.9", 4 | "description": "JavaScript library for applying a glitch effect to a canvas element", 5 | "main": "glitch-canvas-browser.js", 6 | "browser": "glitch-canvas-browser.js", 7 | "module": "glitch-canvas-browser.es6.js", 8 | "directories": { 9 | "examples": "examples", 10 | "test": "test" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/snorpey/glitch-canvas.git" 15 | }, 16 | "keywords": [ 17 | "glitch", 18 | "effect", 19 | "fx", 20 | "javascript", 21 | "canvas" 22 | ], 23 | "author": "Georg Fischer ", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/snorpey/glitch-canvas/issues" 27 | }, 28 | "homepage": "https://github.com/snorpey/glitch-canvas#readme" 29 | } 30 | -------------------------------------------------------------------------------- /glitch-canvas-browser/readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/snorpey/glitch-canvas.png?branch=master)](https://travis-ci.org/snorpey/glitch-canvas) 2 | [![Greenkeeper badge](https://badges.greenkeeper.io/snorpey/glitch-canvas.svg)](https://greenkeeper.io/) 3 | 4 | glitch-canvas-browser 5 | ===================== 6 | 7 | ```$ npm install glitch-canvas-browser``` 8 | 9 | what is it? 10 | ----------- 11 | glitch-canvas is a javascript library for applying a glitch effect to a canvas element. 12 | 13 | this is the dependency-free, browser-only version of the glitch-canvas package. 14 | 15 | for more information and documentation, check out the [glitch-canvas package](https://www.npmjs.com/package/glitch-canvas) or the [glitch-canvas repository](https://github.com/snorpey/glitch-canvas/). -------------------------------------------------------------------------------- /glitch-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snorpey/glitch-canvas/72283d134e9c5666d20fa985cd715c49138e1b75/glitch-example.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glitch-canvas", 3 | "version": "1.1.12", 4 | "description": "JavaScript library for applying a glitch effect to a canvas element", 5 | "main": "dist/glitch-canvas-node.js", 6 | "browser": "dist/glitch-canvas-browser.js", 7 | "module": "dist/glitch-canvas-browser.es6.js", 8 | "directories": { 9 | "examples": "examples", 10 | "test": "test" 11 | }, 12 | "scripts": { 13 | "build": "npm run build-node && npm run build-browsers", 14 | "build-node": "node build.js", 15 | "build-browsers": "node build.js -b && node build.js -bp && node build.js -bm && node build.js -bmp && node build.js -be && node build.js -bem && node build.js -beu && node build.js -bemu", 16 | "test": "npm run test-node && npm run test-browsers", 17 | "test-node": "mocha ./test/test-node.js", 18 | "test-browsers": "npm run test-browser-min && npm run test-browser-polyfills && npm run test-browser-polyfills-min && npm run test-browser-polyfills-es6-umd && npm run test-browser-polyfills-es6-umd-min", 19 | "test-browser": "karma start --single-run --browsers ChromeHeadless test/browser.karma.conf.js --lib=glitch-canvas-browser.js", 20 | "test-browser-min": "karma start --single-run --browsers ChromeHeadless test/browser.karma.conf.js --lib=glitch-canvas-browser.min.js", 21 | "test-browser-polyfills": "karma start --single-run --browsers ChromeHeadless test/browser.karma.conf.js --lib=glitch-canvas-browser-with-polyfills.js", 22 | "test-browser-polyfills-min": "karma start --single-run --browsers ChromeHeadless test/browser.karma.conf.js --lib=glitch-canvas-browser-with-polyfills.min.js", 23 | "test-browser-polyfills-es6-umd": "karma start --single-run --browsers ChromeHeadless test/browser.karma.conf.js --lib=glitch-canvas-browser.es6.umd.js", 24 | "test-browser-polyfills-es6-umd-min": "karma start --single-run --browsers ChromeHeadless test/browser.karma.conf.js --lib=glitch-canvas-browser.es6.umd.min.js" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/snorpey/glitch-canvas.git" 29 | }, 30 | "keywords": [ 31 | "glitch", 32 | "effect", 33 | "fx", 34 | "javascript", 35 | "canvas" 36 | ], 37 | "author": "Georg Fischer ", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/snorpey/glitch-canvas/issues" 41 | }, 42 | "homepage": "https://github.com/snorpey/glitch-canvas#readme", 43 | "dependencies": { 44 | "canvas": "^2.7.0" 45 | }, 46 | "devDependencies": { 47 | "@rollup/plugin-buble": "^0.21.3", 48 | "acorn": "^7.1.1", 49 | "chai": "^4.2.0", 50 | "commander": "^5.0.0", 51 | "es6-promise": "^4.2.8", 52 | "expect.js": "^0.3.1", 53 | "karma": "^6.3.2", 54 | "karma-chai": "^0.1.0", 55 | "karma-chrome-launcher": "^3.1.0", 56 | "karma-mocha": "^2.0.1", 57 | "mocha": "^9.2.2", 58 | "object-assign": "^4.1.1", 59 | "rollup": "^2.47.0", 60 | "rollup-plugin-commonjs": "^10.1.0", 61 | "rollup-plugin-node-resolve": "^5.2.0", 62 | "rollup-plugin-replace": "^2.2.0", 63 | "uglify-es": "^3.3.9", 64 | "uglify-js": "^3.9.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/snorpey/glitch-canvas.png?branch=master)](https://travis-ci.org/snorpey/glitch-canvas) 2 | [![Greenkeeper badge](https://badges.greenkeeper.io/snorpey/glitch-canvas.svg)](https://greenkeeper.io/) 3 | 4 | glitch-canvas 5 | ============= 6 | 7 | downloads 8 | --------- 9 | 10 | * [glitch-canvas-browser.min.js](https://raw.githubusercontent.com/snorpey/glitch-canvas/master/dist/glitch-canvas-browser.min.js) 6kb (3kb gzipped) 11 | * [glitch-canvas-browser.js](https://raw.githubusercontent.com/snorpey/glitch-canvas/master/dist/glitch-canvas-browser.js) 15kb (4kb gzipped) 12 | * [glitch-canvas-browser-with-polyfills.min.js](https://raw.githubusercontent.com/snorpey/glitch-canvas/master/dist/glitch-canvas-browser-with-polyfills.min.js) 14kb (5kb gzipped) 13 | * [glitch-canvas-browser-with-polyfills.js](https://raw.githubusercontent.com/snorpey/glitch-canvas/master/dist/glitch-canvas-browser-with-polyfills.js) 46kb (12kb gzipped) 14 | * [glitch-canvas-browser.es6.min.js](https://raw.githubusercontent.com/snorpey/glitch-canvas/master/dist/glitch-canvas-browser.es6.min.js) 7kb (3kb gzipped) 15 | * [glitch-canvas-master.zip](https://github.com/snorpey/glitch-canvas/archive/master.zip) 516kb 16 | 17 | ```$ npm install glitch-canvas``` 18 | 19 | what is it? 20 | ----------- 21 | glitch-canvas is a javascript library for applying a glitch effect to a canvas element. it can be used to transform images to look like this: 22 | 23 | ![glitched image](glitch-example.png) 24 | 25 | for a live example, you can check out my [jpg-glitch](http://snorpey.github.io/jpg-glitch) editor online. 26 | 27 | how to use it 28 | ------------- 29 | this library can be used in web browsers as well as in node. it supports loading as an AMD module, as a CommonJS module or a global variable.. 30 | 31 | a simple example: 32 | 33 | ```javascript 34 | glitch( { seed: 25, quality: 30 } ) 35 | .fromImage( image ) 36 | .toDataURL() 37 | .then( function( dataURL ) { 38 | var glitchedImg = new Image(); 39 | glitchedImg.src = dataURL; 40 | } ); 41 | ``` 42 | 43 | as you can see, there are __three__ calls happening: 44 | 45 | 1. ``glitch()`` is called with the __glitch parameters__ 46 | 2. then ``fromImage()`` is called with the __input__ image as parameter 47 | 3. and finally ``toDataURL()`` is called to __output__ a dataURL. 48 | 49 | when using the library, always follow these three steps: 50 | 51 | 1. _glitch_ 52 | 2. _input_ 53 | 3. _output_ 54 | 55 | all input and output methods are asynchronous and use promises for flow control. 56 | 57 | for an explanation of all available methods and parameters, check out the [reference](#reference) section below. 58 | 59 | you can find more examples for both node and the browser in the [examples](examples) folder of this repository. 60 | 61 | reference 62 | === 63 | 64 | * [``glitch()``](#glitch) 65 | * input: [``fromImage()``](#fromimage), [``fromImageData()``](#fromimagedata), [``fromBuffer()``](#frombuffer), [``fromStream()``](#fromstream) 66 | * output: [``toDataURL()``](#todataurl), [``toImageData()``](#toimagedata), [``toBuffer()``](#tobuffer), [``toJPGStream()``](#tojpgstream), [``toPNGStream()``](#topngstream) 67 | 68 | glitch() 69 | --- 70 | ``glitch()`` can take the following parameters that control how the glitched image is going to look: 71 | 72 | ```javascript 73 | 74 | // the parameters listed are the default params 75 | 76 | var glitchParams = { 77 | seed: 25, // integer between 0 and 99 78 | quality: 30, // integer between 0 and 99 79 | amount: 35, // integer between 0 and 99 80 | iterations: 20 // integer 81 | }; 82 | 83 | ``` 84 | 85 | _please note_: depending on the size, quality and contents of the source image and the number of iterations, the visual effect of the `seed` and `amount` parameters can be marginal or not even noticeable. 86 | 87 | it returns an object containing all __input methods__. 88 | 89 | fromImage() 90 | --- 91 | ``fromImage()`` expects an ``Image`` object as its only parameter. it returns an object containing all _input methods_. 92 | 93 | example: 94 | 95 | ```javascript 96 | var image = new Image(); 97 | 98 | image.onload = function () { 99 | glitch() 100 | .fromImage( image ) 101 | .toDataURL() 102 | .then( function( dataURL ) { 103 | var glitchedImg = new Image(); 104 | glitchedImg.src = dataURL; 105 | document.body.appendChild( glitchedImg ); 106 | } ); 107 | }; 108 | 109 | image.src = 'lincoln.jpg' 110 | ``` 111 | 112 | _please note_: this method is not available in node. 113 | _important_: when using the library in a browser, make sure the image was loaded before glitching it. 114 | 115 | fromImageData() 116 | --- 117 | ``fromImageData()`` expects an ``ImageData`` object as its only parameter. it returns an object containing all _input methods_. 118 | 119 | example: 120 | 121 | ```javascript 122 | var canvas = document.createElement( 'canvas' ); 123 | var ctx = canvas.getContext( '2d' ); 124 | 125 | ctx.fillStyle = 'red'; 126 | ctx.fillRect( 30, 30, 90, 45 ); 127 | ctx.fillStyle = 'green'; 128 | ctx.fillRect( 10, 20, 50, 60 ); 129 | 130 | var imageData = ctx.getImageData( 0, 0, canvas.width, canvas.height ); 131 | 132 | glitch() 133 | .fromImageData( imageData ) 134 | .toDataURL() 135 | .then( function( dataURL ) { 136 | var glitchedImg = new Image(); 137 | glitchedImg.src = dataURL; 138 | document.body.appendChild( glitchedImg ); 139 | } ); 140 | ``` 141 | 142 | fromBuffer() 143 | --- 144 | ``fromBuffer()`` expects a ``Buffer`` object as its only parameter. it returns an object containing all _input methods_. 145 | 146 | it uses [image#src=buffer](https://github.com/Automattic/node-canvas#imagesrcbuffer) from [node-canvas](https://github.com/Automattic/node-canvas) internally. 147 | 148 | example: 149 | 150 | ```javascript 151 | var fs = require('fs'); 152 | 153 | fs.readFile( './lincoln.jpg', function ( err, buffer ) { 154 | if ( err ) { throw err; } 155 | 156 | glitch() 157 | .fromBuffer( buffer ) 158 | .toBuffer() 159 | .then( function ( glitchedBuffer ) { 160 | fs.writeFile( __dirname + '/glitched-lincoln.png', glitchedBuffer, function ( err ) { 161 | if ( err ) { throw err; } 162 | console.log( 'file glitched.' ); 163 | } ); 164 | } ); 165 | } ); 166 | ``` 167 | 168 | _please note_: this method is only available in node. 169 | 170 | fromStream() 171 | --- 172 | ``fromStream()`` expects a ``ReadableStream`` object as its only parameter. it returns an object containing all _input methods_. 173 | 174 | it uses [image#src=buffer](https://github.com/Automattic/node-canvas#imagesrcbuffer) from [node-canvas](https://github.com/Automattic/node-canvas) internally. 175 | 176 | example: 177 | 178 | ```javascript 179 | var fs = require('fs'); 180 | 181 | var inputStream = fs.createReadStream( './lincoln.jpg' ); 182 | var outputStream = fs.createWriteStream( './glitched-lincoln.png' ); 183 | 184 | glitch() 185 | .fromStream( inputStream ) 186 | .toPNGStream() 187 | .then( function ( pngStream ) { 188 | pngStream.on( 'data', function ( chunk ) { outputStream.write( chunk ); } ); 189 | pngStream.on( 'end', function () { console.log( 'png file saved.' ); } ); 190 | } ); 191 | ``` 192 | 193 | _please note_: this method is only available in node. currently, theres no input sanitation for this method, so you'll want to make sure that the input stream contains an image. 194 | 195 | toDataURL() 196 | --- 197 | ``toDataURL()`` does not take any parameters. it returns a ``String`` containing the base64-encoded image url. 198 | 199 | example: 200 | 201 | ```javascript 202 | var image = new Image(); 203 | 204 | image.onload = function () { 205 | glitch() 206 | .fromImage( image ) 207 | .toDataURL() 208 | .then( function( dataURL ) { 209 | var glitchedImg = new Image(); 210 | glitchedImg.src = dataURL; 211 | document.body.appendChild( glitchedImg ); 212 | } ); 213 | }; 214 | 215 | image.src = 'lincoln.jpg' 216 | ``` 217 | 218 | toImageData() 219 | --- 220 | ``toImageData()`` does not take any parameters. it returns an ``ImageData`` object. 221 | 222 | example: 223 | 224 | ```javascript 225 | var image = new Image(); 226 | 227 | image.onload = function () { 228 | glitch() 229 | .fromImage( image ) 230 | .toImageData() 231 | .then( function ( imageData ) { 232 | var canvas = document.createElement( 'canvas' ); 233 | var ctx = canvas.getContext( '2d' ); 234 | ctx.putImageData( imageData, 0, 0 ); 235 | 236 | document.body.appendChild( canvas ); 237 | } ); 238 | }; 239 | 240 | image.src = 'lincoln.jpg' 241 | ``` 242 | 243 | toImage() 244 | --- 245 | ``toImage()`` does not take any parameters. it returns an ``Image`` object. 246 | 247 | example: 248 | 249 | ```javascript 250 | var image = new Image(); 251 | 252 | image.onload = function () { 253 | glitch() 254 | .fromImage( image ) 255 | .toImage() 256 | .then( function ( glitchedImage ) { 257 | document.body.appendChild( glitchedImage ); 258 | } ); 259 | }; 260 | 261 | image.src = 'lincoln.jpg' 262 | ``` 263 | 264 | _please note_: this method is only available in the browser. 265 | 266 | toBuffer() 267 | --- 268 | ``toBuffer()`` doesn't take any parameters. it uses [canvas#tobuffer](https://github.com/Automattic/node-canvas#canvastobuffer) from [node-canvas](https://github.com/Automattic/node-canvas) internally. 269 | 270 | it returns a ``Buffer`` object. 271 | 272 | example: 273 | 274 | ```javascript 275 | var fs = require('fs'); 276 | 277 | fs.readFile( './lincoln.jpg', function ( err, buffer ) { 278 | if ( err ) { throw err; } 279 | 280 | glitch() 281 | .fromBuffer( buffer ) 282 | .toBuffer() 283 | .then( function ( imageBuffer ) { 284 | fs.writeFile( './glitched-lincoln.png', imageBuffer, function ( err ) { 285 | if ( err ) { throw err; } 286 | console.log( 'created a pdf file.' ); 287 | } ); 288 | } ); 289 | } ); 290 | ``` 291 | 292 | _please note_: this method is only available in node. 293 | 294 | toJPGStream() 295 | --- 296 | ``toJPGStream()`` can take the following parameters: 297 | 298 | ```javascript 299 | var jpgStreamParams = { 300 | bufsize: 4096, // output buffer size in bytes 301 | quality: 75, // jpg quality (0-100) default: 75 302 | progressive: false // true for progressive compression 303 | }; 304 | 305 | ``` 306 | 307 | it uses [canvas#jpegstream()](https://github.com/Automattic/node-canvas#canvasjpegstream-and-canvassyncjpegstream) from [node-canvas](https://github.com/Automattic/node-canvas) internally. 308 | 309 | it returns a ``JPEGStream`` object. 310 | 311 | example: 312 | 313 | ```javascript 314 | var fs = require('fs'); 315 | fs.readFile( __dirname + '/lincoln.jpg', function ( err, buffer ) { 316 | if ( err ) { 317 | throw err; 318 | } 319 | 320 | var fileStream = fs.createWriteStream( __dirname + '/glitched-lincoln.jpg' ); 321 | 322 | glitch() 323 | .fromBuffer( buffer ) 324 | .toJPGStream() 325 | .then( function ( jpgStream ) { 326 | jpgStream.on( 'data', function ( chunk ) { fileStream.write( chunk ); } ); 327 | jpgStream.on( 'end', function () { console.log( 'file glitched.' ); } ); 328 | } ); 329 | } ); 330 | ``` 331 | 332 | _please note_: this method is only available in node. 333 | 334 | toJPEGStream() 335 | --- 336 | see [``toJPGStream()``](#tojpgstream). 337 | 338 | toPNGStream() 339 | --- 340 | ``toPNGStream()`` doesn't take any parameters. it uses [canvas#pngstream()](https://github.com/Automattic/node-canvas#canvaspngstream) from [node-canvas](https://github.com/Automattic/node-canvas) internally. 341 | 342 | it returns a ``PNGStream`` object. 343 | 344 | example: 345 | 346 | ```javascript 347 | var fs = require('fs'); 348 | fs.readFile( __dirname + '/lincoln.jpg', function ( err, buffer ) { 349 | if ( err ) { throw err; } 350 | 351 | var fileStream = fs.createWriteStream( __dirname + '/glitched-lincoln.png' ); 352 | 353 | glitch() 354 | .fromBuffer( buffer ) 355 | .toPNGStream() 356 | .then( function ( pngStream ) { 357 | pngStream.on( 'data', function ( chunk ) { fileStream.write( chunk ); } ); 358 | pngStream.on( 'end', function () { console.log( 'file glitched.' ); } ); 359 | }, function( err ) { 360 | console.log( 'There was an error', err ); 361 | } ); 362 | } ); 363 | ``` 364 | 365 | _please note_: this method is only available in node. 366 | 367 | development 368 | === 369 | 370 | `npm run build` will build the node-ready and browser-ready versions, which are saved to the `dist-node` and `dist` directories. 371 | 372 | `npm run test` will run the tests in both the browser and node. 373 | 374 | you can find the source code for the library in the ``src`` folder. it is using es6-style syntax. 375 | 376 | license 377 | === 378 | 379 | [mit](LICENSE) 380 | 381 | third party code 382 | --- 383 | 384 | most of the folder structure and the npm script setup were taken from [nonalnawson](https://github.com/nolanlawson)'s [hello javascript](https://github.com/nolanlawson/hello-javascript) repo (Apache-2.0 license). 385 | 386 | dependencies: 387 | 388 | * [es6-promise](https://github.com/stefanpenner/es6-promise) by [stefanpenner](https://github.com/stefanpenner), MIT license 389 | * [canvas-browserify](https://github.com/dominictarr/canvas-browserify) by [dominictarr](https://github.com/dominictarr), MIT license 390 | 391 | 392 | glitch-canvas in the wild 393 | ------------------------- 394 | * [jpg-glitch](http://snorpey.github.io/jpg-glitch): glitch editor 395 | * [glitch-img](https://github.com/kunjinkao/glitch-img): glitch-canvas web component 396 | * [fuzzy.cc](http://www.fuzzywobble.com/project.php?p=77&n=glitch-image-on-hover): hover effect 397 | * [G͋l̷i᷉t͠c̭h](http://rawgit.com/DUQE/glitch/master/index.html): glitch html 398 | * [Be Aug Aware](https://augaware.org/) 399 | * [Viewport Glitcher](https://github.com/zky829/viewport-glitcher/) 400 | 401 | implementations in other languages 402 | ---------------------------------- 403 | * python: [jpglitch](https://github.com/Kareeeeem/jpglitch) 404 | 405 | if you want to add your site or project to one of these lists, you can create a pull request or send me an email. 406 | 407 | missing somehing? 408 | --- 409 | 410 | found a bug? missing a feature? instructions unclear? are you using this library in an interesting project? maybe have a look at the [issues](../../issues), open a pull request and let me know. thanks! 411 | 412 | most importantly 413 | --- 414 | 415 | thank you for taking a look at this repo. have a great day :) 416 | 417 | -------------------------------------------------------------------------------- /src/browser.js: -------------------------------------------------------------------------------- 1 | import sanitizeInput from './input/sanitizeInput'; 2 | import imageToImageData from './input/browser/fromImage'; 3 | import base64URLToImage from './output/browser/toImage'; 4 | import base64URLToImageData from './output/toImageData'; 5 | import imageDataToBase64 from './glitch/browser/imageDataToBase64'; 6 | import glitchImageData from './glitch/glitchImageData'; 7 | import objectAssign from './util/object-assign.js'; 8 | 9 | // PROMISE_POLYFILL_HERE 10 | 11 | // constructing an object that allows for a chained interface. 12 | // for example stuff like: 13 | // 14 | // glitch( params ) 15 | // .toImage( img ) 16 | // .toImageData() 17 | // 18 | // etc... 19 | 20 | export default function glitch(params) { 21 | params = sanitizeInput(params); 22 | 23 | let inputFn; 24 | let outputFn; 25 | 26 | const worker = new Worker('workers/glitchWorker.js'); 27 | 28 | const api = { getParams, getInput, getOutput }; 29 | const inputMethods = { fromImageData, fromImage }; 30 | const outputMethods = { toImage, toDataURL, toImageData }; 31 | 32 | function getParams() { 33 | return params; 34 | } 35 | 36 | function getInput() { 37 | const result = objectAssign({}, api); 38 | 39 | if (!inputFn) { 40 | objectAssign(result, inputMethods); 41 | } 42 | 43 | return result; 44 | } 45 | 46 | function getOutput() { 47 | const result = objectAssign({}, api); 48 | 49 | if (!outputFn) { 50 | objectAssign(result, outputMethods); 51 | } 52 | 53 | return result; 54 | } 55 | 56 | function noTransform(x) { 57 | return x; 58 | } 59 | 60 | function fromImage(inputOptions) { 61 | return setInput(imageToImageData, inputOptions); 62 | } 63 | function fromImageData(inputOptions) { 64 | return setInput(noTransform, inputOptions); 65 | } 66 | 67 | function toDataURL(outputOptions) { 68 | return setOutput(noTransform); 69 | } 70 | function toImage(outputOptions) { 71 | return setOutput(base64URLToImage, outputOptions, true); 72 | } 73 | function toImageData(outputOptions) { 74 | return setOutput(base64URLToImageData, outputOptions, true); 75 | } 76 | 77 | function setInput(fn, inputOptions, canResolve) { 78 | inputFn = () => { 79 | return new Promise((resolve, reject) => { 80 | if (canResolve) { 81 | fn(inputOptions, resolve, reject); 82 | } else { 83 | if (fn === noTransform) { 84 | resolve(inputOptions); 85 | } else { 86 | try { 87 | resolve(fn(inputOptions, resolve, reject)); 88 | } catch (err) { 89 | reject(err); 90 | } 91 | } 92 | } 93 | }); 94 | }; 95 | 96 | if (isReady()) { 97 | return getResult(); 98 | } else { 99 | return getOutput(); 100 | } 101 | } 102 | 103 | function setOutput(fn, outputOptions, canResolve) { 104 | outputFn = base64URL => { 105 | return new Promise((resolve, reject) => { 106 | if (canResolve) { 107 | fn(base64URL, outputOptions, resolve, reject); 108 | } else { 109 | if (fn === noTransform) { 110 | resolve(base64URL); 111 | } else { 112 | fn(base64URL, outputOptions).then(resolve, reject); 113 | } 114 | } 115 | }); 116 | }; 117 | 118 | if (isReady()) { 119 | return getResult(); 120 | } else { 121 | return getInput(); 122 | } 123 | } 124 | 125 | function isReady() { 126 | return inputFn && outputFn; 127 | } 128 | 129 | function getResult() { 130 | return new Promise((resolve, reject) => { 131 | inputFn() 132 | .then(imageData => { 133 | return glitch(imageData, params); 134 | }, reject) 135 | .then(base64URL => { 136 | outputFn(base64URL).then(resolve, reject); 137 | }, reject); 138 | }); 139 | } 140 | 141 | function glitch(imageData, params) { 142 | return new Promise((resolve, reject) => { 143 | imageDataToBase64(imageData, params.quality) 144 | .then(base64URL => { 145 | return glitchInWorker(imageData, base64URL, params); 146 | }, reject) 147 | .then(resolve, reject); 148 | }); 149 | } 150 | 151 | function glitchInWorker(imageData, base64URL, params) { 152 | return new Promise((resolve, reject) => { 153 | worker.addEventListener('message', event => { 154 | if (event.data && event.data.base64URL) { 155 | resolve(event.data.base64URL); 156 | } else { 157 | if (event.data && event.data.err) { 158 | reject(event.data.err); 159 | } else { 160 | reject(event); 161 | } 162 | } 163 | }); 164 | 165 | worker.postMessage({ 166 | params: params, 167 | base64URL: base64URL, 168 | imageData: imageData, 169 | 170 | // phantomjs tends to forget about those two 171 | // so we send them separately 172 | imageDataWidth: imageData.width, 173 | imageDataHeight: imageData.height, 174 | }); 175 | }); 176 | } 177 | 178 | return getInput(); 179 | } 180 | -------------------------------------------------------------------------------- /src/glitch/base64Map.js: -------------------------------------------------------------------------------- 1 | const base64Chars = 2 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 3 | const base64Map = base64Chars.split(''); 4 | const reversedBase64Map = {}; 5 | 6 | base64Map.forEach((val, key) => { 7 | reversedBase64Map[val] = key; 8 | }); 9 | 10 | export default { 11 | base64Map, 12 | reversedBase64Map, 13 | }; 14 | -------------------------------------------------------------------------------- /src/glitch/base64ToByteArray.js: -------------------------------------------------------------------------------- 1 | import maps from './base64Map.js'; 2 | 3 | const reversedBase64Map = maps.reversedBase64Map; 4 | 5 | // https://github.com/mutaphysis/smackmyglitchupjs/blob/master/glitch.html 6 | // base64 is 2^6, byte is 2^8, every 4 base64 values create three bytes 7 | export default function (base64URL) { 8 | const result = []; 9 | let prev; 10 | 11 | const srcURL = base64URL.replace('data:image/jpeg;base64,', ''); 12 | 13 | for (var i = 0, len = srcURL.length; i < len; i++) { 14 | const char = srcURL[i]; 15 | const currentChar = reversedBase64Map[srcURL[i]]; 16 | const digitNum = i % 4; 17 | 18 | switch (digitNum) { 19 | // case 0: first digit - do nothing, not enough info to work with 20 | case 1: // second digit 21 | result.push((prev << 2) | (currentChar >> 4)); 22 | break; 23 | 24 | case 2: // third digit 25 | result.push(((prev & 0x0f) << 4) | (currentChar >> 2)); 26 | break; 27 | 28 | case 3: // fourth digit 29 | result.push(((prev & 3) << 6) | currentChar); 30 | break; 31 | } 32 | 33 | prev = currentChar; 34 | } 35 | 36 | return result; 37 | } 38 | -------------------------------------------------------------------------------- /src/glitch/browser/imageDataToBase64.js: -------------------------------------------------------------------------------- 1 | import Canvas from '../../util/canvas.js'; 2 | import isImageData from '../../util/isImageData'; 3 | 4 | export default function (imageData, quality) { 5 | return new Promise((resolve, reject) => { 6 | if (isImageData(imageData)) { 7 | const canvas = new Canvas(imageData.width, imageData.height); 8 | const ctx = canvas.getContext('2d'); 9 | ctx.putImageData(imageData, 0, 0); 10 | 11 | const base64URL = canvas.toDataURL('image/jpeg', quality / 100); 12 | 13 | resolve(base64URL); 14 | } else { 15 | reject(new Error('object is not valid imageData')); 16 | } 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /src/glitch/byteArrayToBase64.js: -------------------------------------------------------------------------------- 1 | import maps from './base64Map.js'; 2 | const base64Map = maps.base64Map; 3 | 4 | export default function (byteArray) { 5 | const result = ['data:image/jpeg;base64,']; 6 | let byteNum; 7 | let previousByte; 8 | 9 | for (let i = 0, len = byteArray.length; i < len; i++) { 10 | const currentByte = byteArray[i]; 11 | byteNum = i % 3; 12 | 13 | switch (byteNum) { 14 | case 0: // first byte 15 | result.push(base64Map[currentByte >> 2]); 16 | break; 17 | case 1: // second byte 18 | result.push( 19 | base64Map[((previousByte & 3) << 4) | (currentByte >> 4)] 20 | ); 21 | break; 22 | case 2: // third byte 23 | result.push( 24 | base64Map[((previousByte & 0x0f) << 2) | (currentByte >> 6)] 25 | ); 26 | result.push(base64Map[currentByte & 0x3f]); 27 | break; 28 | } 29 | 30 | previousByte = currentByte; 31 | } 32 | 33 | if (byteNum === 0) { 34 | result.push(base64Map[(previousByte & 3) << 4]); 35 | result.push('=='); 36 | } else { 37 | if (byteNum === 1) { 38 | result.push(base64Map[(previousByte & 0x0f) << 2]); 39 | result.push('='); 40 | } 41 | } 42 | 43 | return result.join(''); 44 | } 45 | -------------------------------------------------------------------------------- /src/glitch/glitchByteArray.js: -------------------------------------------------------------------------------- 1 | import jpgHeaderLength from './jpgHeaderLength'; 2 | 3 | export default function (byteArray, seed, amount, iterationCount) { 4 | const headerLength = jpgHeaderLength(byteArray); 5 | const maxIndex = byteArray.length - headerLength - 4; 6 | 7 | const amountPercent = amount / 100; 8 | const seedPercent = seed / 100; 9 | 10 | for ( 11 | var iterationIndex = 0; 12 | iterationIndex < iterationCount; 13 | iterationIndex++ 14 | ) { 15 | const minPixelIndex = 16 | ((maxIndex / iterationCount) * iterationIndex) | 0; 17 | const maxPixelIndex = 18 | ((maxIndex / iterationCount) * (iterationIndex + 1)) | 0; 19 | 20 | const delta = maxPixelIndex - minPixelIndex; 21 | let pixelIndex = (minPixelIndex + delta * seedPercent) | 0; 22 | 23 | if (pixelIndex > maxIndex) { 24 | pixelIndex = maxIndex; 25 | } 26 | 27 | const indexInByteArray = ~~(headerLength + pixelIndex); 28 | 29 | byteArray[indexInByteArray] = ~~(amountPercent * 256); 30 | } 31 | 32 | return byteArray; 33 | } 34 | -------------------------------------------------------------------------------- /src/glitch/glitchImageData.js: -------------------------------------------------------------------------------- 1 | import isImageData from '../util/isImageData'; 2 | import base64ToByteArray from './base64ToByteArray'; 3 | import glitchByteArray from './glitchByteArray'; 4 | import byteArrayToBase64 from './byteArrayToBase64'; 5 | 6 | export default function (imageData, base64URL, params) { 7 | if (isImageData(imageData)) { 8 | const byteArray = base64ToByteArray(base64URL); 9 | const glitchedByteArray = glitchByteArray( 10 | byteArray, 11 | params.seed, 12 | params.amount, 13 | params.iterations 14 | ); 15 | const glitchedBase64URL = byteArrayToBase64(glitchedByteArray); 16 | return glitchedBase64URL; 17 | } else { 18 | throw new Error('glitchImageData: imageData seems to be corrupt.'); 19 | return; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/glitch/jpgHeaderLength.js: -------------------------------------------------------------------------------- 1 | // http://stackoverflow.com/a/10424014/229189 2 | 3 | export default function (byteArr) { 4 | let result = 417; 5 | 6 | // https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure 7 | // looks for the first occurence of 0xFF, 0xDA in the byteArray 8 | // which is the start of scan 9 | for (let i = 0, len = byteArr.length; i < len; i++) { 10 | if (byteArr[i] === 0xff && byteArr[i + 1] === 0xda) { 11 | result = i + 2; 12 | break; 13 | } 14 | } 15 | 16 | return result; 17 | } 18 | -------------------------------------------------------------------------------- /src/glitch/node/imageDataToBase64.js: -------------------------------------------------------------------------------- 1 | import Canvas from '../../util/canvas.js'; 2 | import isImageData from '../../util/isImageData'; 3 | 4 | export default function (imageData, quality) { 5 | return new Promise((resolve, reject) => { 6 | if (isImageData(imageData)) { 7 | const canvas = new Canvas(imageData.width, imageData.height); 8 | const ctx = canvas.getContext('2d'); 9 | ctx.putImageData(imageData, 0, 0); 10 | 11 | let base64URL = canvas.toDataURL('image/jpeg', quality / 100); 12 | 13 | switch (base64URL.length % 4) { 14 | case 3: 15 | base64URL += '='; 16 | break; 17 | case 2: 18 | base64URL += '=='; 19 | break; 20 | case 1: 21 | base64URL += '==='; 22 | break; 23 | } 24 | 25 | resolve(base64URL); 26 | } else { 27 | reject(new Error('object is not valid imageData')); 28 | } 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import sanitizeInput from './input/sanitizeInput'; 2 | import fromBufferToImageData from './input/node/fromBuffer'; 3 | import fromStreamToImageData from './input/node/fromStream'; 4 | import base64URLToBuffer from './output/node/toBuffer'; 5 | import base64URLToImageData from './output/toImageData'; 6 | import base64URLToPNGStream from './output/node/toPNGStream'; 7 | import base64URLToJPGStream from './output/node/toJPGStream'; 8 | import imageDataToBase64 from './glitch/node/imageDataToBase64'; 9 | import glitchImageData from './glitch/glitchImageData'; 10 | import objectAssign from './util/object-assign.js'; 11 | 12 | // constructing an object that allows for a chained interface. 13 | // for example stuff like: 14 | // 15 | // glitch( params ) 16 | // .fromBuffer( buffer ) 17 | // .toImageData() 18 | // 19 | // etc... 20 | // 21 | 22 | export default function (params) { 23 | params = sanitizeInput(params); 24 | 25 | let inputFn; 26 | let outputFn; 27 | 28 | const api = { getParams, getInput, getOutput }; 29 | const inputMethods = { fromBuffer, fromImageData, fromStream }; 30 | 31 | const outputMethods = { 32 | toBuffer, 33 | toDataURL, 34 | toImageData, 35 | toPNGStream, 36 | toJPGStream, 37 | toJPEGStream, 38 | }; 39 | 40 | function getParams() { 41 | return params; 42 | } 43 | 44 | function getInput() { 45 | const result = objectAssign({}, api); 46 | 47 | if (!inputFn) { 48 | objectAssign(result, inputMethods); 49 | } 50 | 51 | return result; 52 | } 53 | 54 | function getOutput() { 55 | const result = objectAssign({}, api); 56 | 57 | if (!outputFn) { 58 | objectAssign(result, outputMethods); 59 | } 60 | 61 | return result; 62 | } 63 | 64 | function noTransform(x) { 65 | return x; 66 | } 67 | 68 | function fromBuffer(inputOptions) { 69 | return setInput(fromBufferToImageData, inputOptions); 70 | } 71 | function fromStream(inputOptions) { 72 | return setInput(fromStreamToImageData, inputOptions, true); 73 | } 74 | function fromImageData(inputOptions) { 75 | return setInput(noTransform, inputOptions); 76 | } 77 | 78 | function toBuffer(outputOptions) { 79 | return setOutput(base64URLToBuffer, outputOptions, true); 80 | } 81 | function toDataURL(outputOptions) { 82 | return setOutput(noTransform, outputOptions); 83 | } 84 | function toImageData(outputOptions) { 85 | return setOutput(base64URLToImageData, outputOptions, true); 86 | } 87 | function toPNGStream(outputOptions) { 88 | return setOutput(base64URLToPNGStream, outputOptions, true); 89 | } 90 | function toJPGStream(outputOptions) { 91 | return setOutput(base64URLToJPGStream, outputOptions, true); 92 | } 93 | function toJPEGStream(outputOptions) { 94 | return setOutput(base64URLToJPGStream, outputOptions, true); 95 | } 96 | 97 | function setInput(fn, inputOptions, canResolve) { 98 | inputFn = () => { 99 | return new Promise((resolve, reject) => { 100 | if (canResolve) { 101 | fn(inputOptions, resolve, reject); 102 | } else { 103 | if (fn === noTransform) { 104 | resolve(inputOptions); 105 | } else { 106 | try { 107 | resolve(fn(inputOptions, resolve, reject)); 108 | } catch (err) { 109 | reject(err); 110 | } 111 | } 112 | } 113 | }); 114 | }; 115 | 116 | if (isReady()) { 117 | return getResult(); 118 | } else { 119 | return getOutput(); 120 | } 121 | } 122 | 123 | function setOutput(fn, outputOptions, canResolve) { 124 | outputFn = base64URL => { 125 | return new Promise((resolve, reject) => { 126 | if (canResolve) { 127 | fn(base64URL, outputOptions, resolve, reject); 128 | } else { 129 | if (fn === noTransform) { 130 | resolve(base64URL); 131 | } else { 132 | fn(base64URL, outputOptions).then(resolve, reject); 133 | } 134 | } 135 | }); 136 | }; 137 | 138 | if (isReady()) { 139 | return getResult(); 140 | } else { 141 | return getInput(); 142 | } 143 | } 144 | 145 | function isReady() { 146 | return inputFn && outputFn; 147 | } 148 | 149 | function getResult() { 150 | return new Promise((resolve, reject) => { 151 | inputFn() 152 | .then(imageData => { 153 | return glitch(imageData, params); 154 | }, reject) 155 | .then(base64URL => { 156 | outputFn(base64URL).then(resolve, reject); 157 | }, reject); 158 | }); 159 | } 160 | 161 | function glitch(imageData, params) { 162 | return new Promise((resolve, reject) => { 163 | imageDataToBase64(imageData, params.quality).then(base64URL => { 164 | try { 165 | resolve(glitchImageData(imageData, base64URL, params)); 166 | } catch (err) { 167 | reject(err); 168 | } 169 | }, reject); 170 | }); 171 | } 172 | 173 | return getInput(); 174 | } 175 | -------------------------------------------------------------------------------- /src/input/browser/fromImage.js: -------------------------------------------------------------------------------- 1 | import Canvas from '../../util/canvas.js'; 2 | 3 | const Image = Canvas.Image; 4 | 5 | export default function (image) { 6 | if (image instanceof HTMLImageElement) { 7 | // http://stackoverflow.com/a/3016076/229189 8 | if ( 9 | !image.naturalWidth || 10 | !image.naturalHeight || 11 | image.complete === false 12 | ) { 13 | throw new Error( 14 | "This this image hasn't finished loading: " + image.src 15 | ); 16 | } 17 | 18 | const canvas = new Canvas(image.naturalWidth, image.naturalHeight); 19 | const ctx = canvas.getContext('2d'); 20 | 21 | ctx.drawImage(image, 0, 0, canvas.width, canvas.height); 22 | 23 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 24 | 25 | if (imageData.data && imageData.data.length) { 26 | if (typeof imageData.width === 'undefined') { 27 | imageData.width = image.naturalWidth; 28 | } 29 | 30 | if (typeof imageData.height === 'undefined') { 31 | imageData.height = image.naturalHeight; 32 | } 33 | } 34 | 35 | return imageData; 36 | } else { 37 | throw new Error('This object does not seem to be an image.'); 38 | return; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/input/defaultParams.js: -------------------------------------------------------------------------------- 1 | export default { 2 | amount: 35, 3 | iterations: 20, 4 | quality: 30, 5 | seed: 25, 6 | }; 7 | -------------------------------------------------------------------------------- /src/input/node/fromBuffer.js: -------------------------------------------------------------------------------- 1 | // https://github.com/Automattic/node-canvas#imagesrcbuffer 2 | import Canvas from '../../util/canvas.js'; 3 | 4 | const Image = Canvas.Image; 5 | 6 | export default function (buffer) { 7 | if (buffer instanceof Buffer) { 8 | const image = new Image(); 9 | image.src = buffer; 10 | 11 | const canvas = new Canvas(image.width, image.height); 12 | const ctx = canvas.getContext('2d'); 13 | 14 | ctx.drawImage(image, 0, 0, image.width, image.height); 15 | 16 | return ctx.getImageData(0, 0, canvas.width, canvas.height); 17 | } else { 18 | throw new Error("Can't work with the buffer object provided."); 19 | return; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/input/node/fromStream.js: -------------------------------------------------------------------------------- 1 | import stream from 'stream'; 2 | // https://github.com/Automattic/node-canvas#imagesrcbuffer 3 | import Canvas from '../../util/canvas.js'; 4 | 5 | const Readable = stream.Readable; 6 | const Image = Canvas.Image; 7 | 8 | export default function (stream, resolve, reject) { 9 | if (stream instanceof Readable) { 10 | const bufferContent = []; 11 | 12 | stream.on('data', chunk => { 13 | bufferContent.push(chunk); 14 | }); 15 | 16 | stream.on('end', () => { 17 | try { 18 | const buffer = Buffer.concat(bufferContent); 19 | const image = new Image(); 20 | image.src = buffer; 21 | 22 | const canvas = new Canvas(image.width, image.height); 23 | const ctx = canvas.getContext('2d'); 24 | 25 | ctx.drawImage(image, 0, 0, canvas.width, canvas.height); 26 | 27 | resolve(ctx.getImageData(0, 0, canvas.width, canvas.height)); 28 | } catch (err) { 29 | reject(err); 30 | } 31 | }); 32 | } else { 33 | reject(new Error("Can't work with the buffer object provided.")); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/input/sanitizeInput.js: -------------------------------------------------------------------------------- 1 | import clamp from '../util/clamp'; 2 | import clone from '../util/clone'; 3 | import defaultParams from './defaultParams'; 4 | 5 | export default function (params) { 6 | params = clone(params); 7 | 8 | if (typeof params !== 'object') { 9 | params = {}; 10 | } 11 | 12 | Object.keys(defaultParams) 13 | .filter(key => key !== 'iterations') 14 | .forEach(key => { 15 | if (typeof params[key] !== 'number' || isNaN(params[key])) { 16 | params[key] = defaultParams[key]; 17 | } else { 18 | params[key] = clamp(params[key], 0, 100); 19 | } 20 | 21 | params[key] = Math.round(params[key]); 22 | }); 23 | 24 | if ( 25 | typeof params.iterations !== 'number' || 26 | isNaN(params.iterations) || 27 | params.iterations <= 0 28 | ) { 29 | params.iterations = defaultParams.iterations; 30 | } 31 | 32 | params.iterations = Math.round(params.iterations); 33 | 34 | return params; 35 | } 36 | -------------------------------------------------------------------------------- /src/output/browser/toImage.js: -------------------------------------------------------------------------------- 1 | import loadBase64Image from '../../util/loadBase64Image'; 2 | 3 | export default function (base64URL, opts, resolve, reject) { 4 | loadBase64Image(base64URL).then(resolve, reject); 5 | } 6 | -------------------------------------------------------------------------------- /src/output/node/toBuffer.js: -------------------------------------------------------------------------------- 1 | import loadBase64Image from '../../util/loadBase64Image'; 2 | import canvasFromImage from '../../util/canvasFromImage'; 3 | 4 | export default function (base64URL, options, resolve, reject) { 5 | loadBase64Image(base64URL).then(image => { 6 | const buffer = canvasFromImage(image).canvas.toBuffer(); 7 | resolve(buffer); 8 | }, reject); 9 | } 10 | -------------------------------------------------------------------------------- /src/output/node/toJPGStream.js: -------------------------------------------------------------------------------- 1 | // https://github.com/Automattic/node-canvas#canvasjpegstream-and-canvassyncjpegstream 2 | import loadBase64Image from '../../util/loadBase64Image'; 3 | import canvasFromImage from '../../util/canvasFromImage'; 4 | 5 | export default function (base64URL, options, resolve, reject) { 6 | options = options || {}; 7 | 8 | const streamParams = { 9 | bufsize: options.bufsize || 4096, 10 | quality: options.quality || 75, 11 | progressive: options.progressive || false, 12 | }; 13 | 14 | loadBase64Image(base64URL).then(image => { 15 | const c = canvasFromImage(image); 16 | const stream = c.canvas.jpegStream(streamParams); 17 | resolve(stream); 18 | }, reject); 19 | } 20 | -------------------------------------------------------------------------------- /src/output/node/toPNGStream.js: -------------------------------------------------------------------------------- 1 | // https://github.com/Automattic/node-canvas#canvaspngstream 2 | import loadBase64Image from '../../util/loadBase64Image'; 3 | import canvasFromImage from '../../util/canvasFromImage'; 4 | 5 | export default function (base64URL, options, resolve, reject) { 6 | loadBase64Image(base64URL).then( 7 | image => { 8 | const stream = canvasFromImage(image).canvas.pngStream(); 9 | resolve(stream); 10 | }, 11 | function (err) { 12 | reject(err); 13 | } 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /src/output/toImageData.js: -------------------------------------------------------------------------------- 1 | import loadBase64Image from '../util/loadBase64Image'; 2 | import canvasFromImage from '../util/canvasFromImage'; 3 | import getImageSize from '../util/getImageSize'; 4 | 5 | export default function (base64URL, options, resolve, reject) { 6 | loadBase64Image(base64URL).then(image => { 7 | const size = getImageSize(image); 8 | const imageData = canvasFromImage(image).ctx.getImageData( 9 | 0, 10 | 0, 11 | size.width, 12 | size.height 13 | ); 14 | 15 | if (!imageData.width) { 16 | imageData.width = size.width; 17 | } 18 | 19 | if (!imageData.height) { 20 | imageData.height = size.height; 21 | } 22 | 23 | resolve(imageData); 24 | }, reject); 25 | } 26 | -------------------------------------------------------------------------------- /src/util/browser.js: -------------------------------------------------------------------------------- 1 | class Canvas { 2 | constructor(width = 300, height = 150) { 3 | if (typeof window === 'undefined') { 4 | this.canvasEl = { width, height }; 5 | this.ctx = null; 6 | } else { 7 | this.canvasEl = document.createElement('canvas'); 8 | this.canvasEl.width = width; 9 | this.canvasEl.height = height; 10 | this.ctx = this.canvasEl.getContext('2d'); 11 | } 12 | } 13 | 14 | getContext() { 15 | return this.ctx; 16 | } 17 | 18 | toDataURL(type, encoderOptions, cb) { 19 | if (typeof cb === 'function') { 20 | cb(this.canvasEl.toDataURL(type, encoderOptions)); 21 | } else { 22 | return this.canvasEl.toDataURL(type, encoderOptions); 23 | } 24 | } 25 | 26 | get width() { 27 | return this.canvasEl.width; 28 | } 29 | 30 | set width(newWidth) { 31 | this.canvasEl.width = newWidth; 32 | } 33 | 34 | get height() { 35 | return this.canvasEl.height; 36 | } 37 | 38 | set height(newHeight) { 39 | this.canvasEl.height = newHeight; 40 | } 41 | } 42 | 43 | if (typeof window !== 'undefined') { 44 | Canvas.Image = Image; 45 | } 46 | 47 | export default Canvas; 48 | -------------------------------------------------------------------------------- /src/util/canvas.js: -------------------------------------------------------------------------------- 1 | // import Canvas from 'canvas'; 2 | // import Canvas from './browser.js'; 3 | import Canvas from './node-canvas.js'; 4 | export default Canvas; 5 | -------------------------------------------------------------------------------- /src/util/canvasFromImage.js: -------------------------------------------------------------------------------- 1 | import Canvas from './canvas.js'; 2 | import getImageSize from './getImageSize'; 3 | 4 | export default function (image) { 5 | const size = getImageSize(image); 6 | const canvas = new Canvas(size.width, size.height); 7 | const ctx = canvas.getContext('2d'); 8 | 9 | ctx.drawImage(image, 0, 0, size.width, size.height); 10 | 11 | return { 12 | canvas: canvas, 13 | ctx: ctx, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/util/clamp.js: -------------------------------------------------------------------------------- 1 | export default function (value, min, max) { 2 | return value < min ? min : value > max ? max : value; 3 | } 4 | -------------------------------------------------------------------------------- /src/util/clone.js: -------------------------------------------------------------------------------- 1 | export default function (obj) { 2 | let result = false; 3 | 4 | if (typeof obj !== 'undefined') { 5 | try { 6 | result = JSON.parse(JSON.stringify(obj)); 7 | } catch (e) {} 8 | } 9 | 10 | return result; 11 | } 12 | -------------------------------------------------------------------------------- /src/util/copyImageData.js: -------------------------------------------------------------------------------- 1 | import Canvas from './util/canvas.js'; 2 | import isImageData from '../util/isImageData'; 3 | 4 | export default function (imageData) { 5 | if (isImageData(imageData)) { 6 | if (typeof Uint8ClampedArray === 'undefined') { 7 | if (typeof window === 'undefined') { 8 | throw new Error( 9 | "Can't copy imageData in webworker without Uint8ClampedArray support." 10 | ); 11 | return imageData; 12 | } else { 13 | return copyImageDataWithCanvas(imageData); 14 | } 15 | } else { 16 | const clampedArray = new Uint8ClampedArray(imageData.data); 17 | 18 | if (typeof ImageData === 'undefined') { 19 | // http://stackoverflow.com/a/15238036/229189 20 | return { 21 | width: imageData.width, 22 | height: imageData.height, 23 | data: clampedArray, 24 | }; 25 | } else { 26 | // http://stackoverflow.com/a/15908922/229189#comment57192591_15908922 27 | let result; 28 | 29 | try { 30 | result = new ImageData( 31 | clampedArray, 32 | imageData.width, 33 | imageData.height 34 | ); 35 | } catch (err) { 36 | if (typeof window === 'undefined') { 37 | throw new Error( 38 | "Can't copy imageData in webworker without proper ImageData() support." 39 | ); 40 | result = imageData; 41 | } else { 42 | result = copyImageDataWithCanvas(imageData); 43 | } 44 | } 45 | 46 | return result; 47 | } 48 | } 49 | } else { 50 | throw new Error('Given imageData object is not useable.'); 51 | return; 52 | } 53 | } 54 | 55 | // http://stackoverflow.com/a/11918126/229189 56 | function copyImageDataWithCanvas(imageData) { 57 | const canvas = new Canvas(imageData.width, imageData.height); 58 | const ctx = canvas.getContext('2d'); 59 | ctx.putImageData(imageData, 0, 0); 60 | 61 | return ctx.getImageData(0, 0, imageData.width, imageData.height); 62 | } 63 | -------------------------------------------------------------------------------- /src/util/getImageSize.js: -------------------------------------------------------------------------------- 1 | export default function (image) { 2 | return { 3 | width: image.width || image.naturalWidth, 4 | height: image.height || image.naturalHeight, 5 | }; 6 | } 7 | -------------------------------------------------------------------------------- /src/util/isImageData.js: -------------------------------------------------------------------------------- 1 | export default function (imageData) { 2 | return ( 3 | imageData && 4 | typeof imageData.width === 'number' && 5 | typeof imageData.height === 'number' && 6 | imageData.data && 7 | typeof imageData.data.length === 'number' && 8 | typeof imageData.data === 'object' 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/util/loadBase64Image.js: -------------------------------------------------------------------------------- 1 | import Canvas from './canvas.js'; 2 | 3 | const Image = Canvas.Image; 4 | 5 | export default function (base64URL) { 6 | return new Promise((resolve, reject) => { 7 | const image = new Image(); 8 | 9 | image.onload = () => { 10 | resolve(image); 11 | }; 12 | 13 | image.onerror = reject; 14 | 15 | try { 16 | image.src = base64URL; 17 | } catch (err) { 18 | reject(err); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/util/node-canvas.js: -------------------------------------------------------------------------------- 1 | const { createCanvas, Image } = require('canvas'); 2 | 3 | class Canvas { 4 | constructor(width = 300, height = 150) { 5 | this.canvasEl = createCanvas(width, height); 6 | this.canvasEl.width = width; 7 | this.canvasEl.height = height; 8 | this.ctx = this.canvasEl.getContext('2d'); 9 | } 10 | 11 | getContext() { 12 | return this.ctx; 13 | } 14 | 15 | toDataURL(type, encoderOptions, cb) { 16 | if (typeof cb === 'function') { 17 | cb(this.canvasEl.toDataURL(type, encoderOptions)); 18 | } else { 19 | return this.canvasEl.toDataURL(type, encoderOptions); 20 | } 21 | } 22 | 23 | toBuffer(params) { 24 | return this.canvasEl.toBuffer(params); 25 | } 26 | 27 | pngStream(params) { 28 | return this.canvasEl.createPNGStream(params); 29 | } 30 | 31 | jpgStream(params) { 32 | return this.canvasEl.createJPEGStream(params); 33 | } 34 | 35 | jpegStream(params) { 36 | return this.canvasEl.createJPEGStream(params); 37 | } 38 | 39 | get width() { 40 | return this.canvasEl.width; 41 | } 42 | 43 | set width(newWidth) { 44 | this.canvasEl.width = newWidth; 45 | } 46 | 47 | get height() { 48 | return this.canvasEl.height; 49 | } 50 | 51 | set height(newHeight) { 52 | this.canvasEl.height = newHeight; 53 | } 54 | } 55 | 56 | Canvas.Image = Image; 57 | 58 | export default Canvas; 59 | -------------------------------------------------------------------------------- /src/util/object-assign.js: -------------------------------------------------------------------------------- 1 | // import objectAssign from 'object-assign' 2 | const objectAssign = Object.assign; 3 | export default objectAssign; 4 | -------------------------------------------------------------------------------- /src/workers/glitchWorker.js: -------------------------------------------------------------------------------- 1 | import glitchImageData from '../glitch/glitchImageData'; 2 | 3 | onmessage = msg => { 4 | const imageData = msg.data.imageData; 5 | const params = msg.data.params; 6 | const base64URL = msg.data.base64URL; 7 | 8 | if (imageData && base64URL && params) { 9 | try { 10 | // phantomjs seems to have some memory loss so we need to make sure 11 | if ( 12 | typeof imageData.width === 'undefined' && 13 | typeof msg.data.imageDataWidth === 'number' 14 | ) { 15 | imageData.width = msg.data.imageDataWidth; 16 | } 17 | 18 | if ( 19 | typeof imageData.height === 'undefined' && 20 | typeof msg.data.imageDataHeight === 'number' 21 | ) { 22 | imageData.height = msg.data.imageDataHeight; 23 | } 24 | 25 | const glitchedBase64URL = glitchImageData( 26 | imageData, 27 | base64URL, 28 | params 29 | ); 30 | success(glitchedBase64URL); 31 | } catch (err) { 32 | fail(err); 33 | } 34 | } else { 35 | if (msg.data.imageData) { 36 | fail('Parameters are missing.'); 37 | } else { 38 | fail('ImageData is missing.'); 39 | } 40 | } 41 | 42 | self.close(); 43 | }; 44 | 45 | function fail(err) { 46 | self.postMessage({ err: err.message || err }); 47 | } 48 | 49 | function success(base64URL) { 50 | self.postMessage({ base64URL: base64URL }); 51 | } 52 | -------------------------------------------------------------------------------- /test/browser.karma.conf.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/a/30777737 2 | function mergeFilesWithArgv(staticFiles) { 3 | const source = staticFiles; 4 | const argv = process.argv; 5 | 6 | argv.forEach(arg => { 7 | const index = arg.indexOf('--lib='); 8 | 9 | if (index !== -1) { 10 | source.unshift('../dist/' + arg.substring(6)); 11 | } 12 | }); 13 | 14 | return source; 15 | } 16 | 17 | module.exports = function (config) { 18 | config.set({ 19 | frameworks: ['mocha', 'chai'], 20 | files: mergeFilesWithArgv([ 21 | './test-browser.js', 22 | { 23 | pattern: 'img/*.jpg', 24 | included: false, 25 | }, 26 | ]), 27 | reporters: ['progress'], 28 | port: 9876, // karma web server port 29 | colors: true, 30 | logLevel: config.LOG_INFO, 31 | browsers: ['ChromeHeadless'], 32 | autoWatch: false, 33 | concurrency: Infinity, 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /test/img/lincoln.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snorpey/glitch-canvas/72283d134e9c5666d20fa985cd715c49138e1b75/test/img/lincoln.jpg -------------------------------------------------------------------------------- /test/test-browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | glitch-canvas browser tests 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /test/test-browser.js: -------------------------------------------------------------------------------- 1 | var defaultParams = { 2 | amount: 35, 3 | iterations: 20, 4 | quality: 30, 5 | seed: 25, 6 | }; 7 | 8 | var imagePath = 'http://localhost:9876/base/img/lincoln.jpg'; 9 | 10 | describe('browser tests for glitch-canvas', function () { 11 | it('should add a global function named "glitch"', function () { 12 | expect(glitch).to.be.a('function'); 13 | }); 14 | 15 | it('should return an object', function () { 16 | expect(glitch()).to.be.an('object'); 17 | }); 18 | 19 | it('should return all input methods available in the browser', function () { 20 | var g = glitch(); 21 | expect(g.fromImage).to.be.a('function'); 22 | expect(g.fromImageData).to.be.a('function'); 23 | }); 24 | 25 | it('should have a getParams method', function () { 26 | expect(glitch().getParams).to.be.a('function'); 27 | }); 28 | 29 | // getParams() 30 | 31 | describe('#getParams()', function () { 32 | it('should return an object with all parameters', function () { 33 | var params = glitch().getParams(); 34 | expect(params.amount).to.be.a('number'); 35 | expect(params.iterations).to.be.a('number'); 36 | expect(params.quality).to.be.a('number'); 37 | expect(params.seed).to.be.a('number'); 38 | }); 39 | 40 | it('should set the default no parameters are submitted', function () { 41 | var params = glitch().getParams(); 42 | 43 | for (var key in defaultParams) { 44 | expect(params[key]).to.be.equal(defaultParams[key]); 45 | } 46 | }); 47 | 48 | it('should add missing parameters', function () { 49 | var params = glitch({ amount: 10, blur: 90 }).getParams(); 50 | 51 | for (var key in defaultParams) { 52 | expect(params[key]).not.to.be.equal(undefined); 53 | } 54 | }); 55 | 56 | it('should clamp the parameters', function () { 57 | var params = glitch({ 58 | amount: 300, 59 | iterations: -23, 60 | quality: 1023.2, 61 | seed: 0, 62 | }).getParams(); 63 | 64 | for (var key in defaultParams) { 65 | expect(params[key]).to.be.within(0, 100); 66 | } 67 | }); 68 | }); 69 | 70 | it('should not have a fromBuffer method', function () { 71 | expect(glitch().fromBuffer).to.be.equal(undefined); 72 | }); 73 | 74 | describe('Asynchronous Methods', function () { 75 | it('should have a fromImage method', function () { 76 | expect(glitch().fromImage).to.be.a('function'); 77 | }); 78 | 79 | describe('#fromImage()', function () { 80 | it('should accept a loaded image as parameter', function (done) { 81 | // loadImage( done, function ( img ) { 82 | loadImage().then(img => { 83 | var failed = false; 84 | 85 | glitch() 86 | .fromImage(img) 87 | .toDataURL() 88 | .then( 89 | function (res) { 90 | return false; 91 | }, 92 | function (err) { 93 | return true; 94 | } 95 | ) 96 | .then(failed => { 97 | expect(failed).to.be.false; 98 | done(); 99 | }, done); 100 | }, done); 101 | }); 102 | 103 | it('should throw an error if an image was not loaded', function (done) { 104 | var img = new Image(); 105 | var failed = false; 106 | 107 | glitch() 108 | .fromImage(img) 109 | .toDataURL() 110 | .then( 111 | function (res) { 112 | return false; 113 | check(); 114 | }, 115 | function (err) { 116 | return true; 117 | check(); 118 | } 119 | ) 120 | .then(failed => { 121 | expect(failed).to.be.true; 122 | done(); 123 | }, done); 124 | }); 125 | }); 126 | 127 | // fromImageData 128 | 129 | it('should have a fromImageData method', function () { 130 | expect(glitch().fromImageData).to.be.a('function'); 131 | }); 132 | 133 | describe('#fromImageData()', function () { 134 | it('should be able to handle an imageData object', function (done) { 135 | loadImage().then(img => { 136 | var imageData = imageToImageData(img); 137 | 138 | var failed = false; 139 | 140 | glitch() 141 | .fromImageData(imageData) 142 | .toDataURL() 143 | .then( 144 | function (res) { 145 | failed = false; 146 | check(); 147 | }, 148 | function (err) { 149 | failed = true; 150 | check(err); 151 | } 152 | ); 153 | 154 | function check(err) { 155 | expect(failed).to.be.false; 156 | done(err); 157 | } 158 | }, done); 159 | }); 160 | 161 | it('should throw an error when provided with corrupt imageData object', function (done) { 162 | var failed = false; 163 | glitch() 164 | .fromImageData({ width: 0, data: 'LOL', height: 10 }) 165 | .toDataURL() 166 | .then( 167 | function (res) { 168 | failed = false; 169 | check(); 170 | }, 171 | function (err) { 172 | failed = true; 173 | check(); 174 | } 175 | ); 176 | 177 | function check() { 178 | expect(failed).to.be.true; 179 | done(); 180 | } 181 | }); 182 | 183 | it('should throw an error when provided with no imageData object', function (done) { 184 | var failed = false; 185 | glitch() 186 | .fromImageData() 187 | .toDataURL() 188 | .then( 189 | function (res) { 190 | failed = false; 191 | check(); 192 | }, 193 | function (err) { 194 | failed = true; 195 | check(); 196 | } 197 | ); 198 | 199 | function check() { 200 | expect(failed).to.be.true; 201 | done(); 202 | } 203 | }); 204 | }); 205 | 206 | // toDataURL 207 | 208 | it('should have a toDataURL method', function () { 209 | expect(glitch().fromImage().toDataURL).to.be.a('function'); 210 | }); 211 | 212 | describe('#toDataURL()', function () { 213 | it('should return a dataURL', function (done) { 214 | loadImage() 215 | .then(img => glitch().fromImage(img).toDataURL()) 216 | .then(function (url) { 217 | expect(url).to.be.a('string'); 218 | expect(url.length).to.be.above(21); 219 | expect( 220 | url.indexOf('data:image/jpeg;base64') 221 | ).to.be.equal(0); 222 | done(); 223 | }, done); 224 | }); 225 | }); 226 | 227 | // toImageData 228 | 229 | it('should have a toImageData method', function () { 230 | expect(glitch().fromImage().toImageData).to.be.a('function'); 231 | }); 232 | 233 | describe('#toImageData()', function () { 234 | it('should return an imageData object', function (done) { 235 | loadImage().then(img => { 236 | glitch() 237 | .fromImage(img) 238 | .toImageData() 239 | .then(function (imageData) { 240 | expect(typeof imageData).to.be.equal('object'); 241 | expect(typeof imageData.width).to.be.equal( 242 | 'number' 243 | ); 244 | expect(typeof imageData.height).to.be.equal( 245 | 'number' 246 | ); 247 | expect(typeof imageData.data).not.to.be.equal( 248 | undefined 249 | ); 250 | expect(imageData.data.length).to.be.above(0); 251 | 252 | done(); 253 | }, done); 254 | }, done); 255 | }); 256 | }); 257 | 258 | // toImage 259 | 260 | it('should have a toImage method', function () { 261 | expect(glitch().fromImage().toImage).to.be.a('function'); 262 | }); 263 | 264 | describe('#toImage()', function () { 265 | it('should return an image element', function (done) { 266 | this.timeout(5000); 267 | 268 | loadImage() 269 | .then(img => glitch().fromImage(img).toImage()) 270 | .then(function (img) { 271 | expect(img instanceof HTMLImageElement).to.be.equal( 272 | true 273 | ); 274 | expect(typeof img.naturalWidth).to.be.equal('number'); 275 | expect(typeof img.naturalHeight).to.be.equal('number'); 276 | 277 | done(); 278 | }, done); 279 | }); 280 | }); 281 | }); 282 | }); 283 | 284 | function loadImage() { 285 | return new Promise((done, fail) => { 286 | var img = new Image(); 287 | 288 | img.onload = function () { 289 | done(img); 290 | }; 291 | 292 | img.onerror = err => { 293 | fail(err); 294 | }; 295 | 296 | img.src = imagePath; 297 | }); 298 | } 299 | 300 | function imageToImageData(img) { 301 | var canvas = document.createElement('canvas'); 302 | canvas.width = img.naturalWidth; 303 | canvas.height = img.naturalHeight; 304 | 305 | var ctx = canvas.getContext('2d'); 306 | ctx.drawImage(img, 0, 0); 307 | 308 | return ctx.getImageData(0, 0, img.naturalWidth, img.naturalHeight); 309 | } 310 | -------------------------------------------------------------------------------- /test/test-browser.min.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | glitch-canvas browser tests 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /test/test-node.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, done*/ 2 | var fs = require('fs'); 3 | var assert = require('assert'); 4 | var stream = require('stream'); 5 | var { createCanvas, Image, PNGStream, JPEGStream } = require('canvas'); 6 | var expect = require('expect.js'); 7 | 8 | // var Image = Canvas.Image; 9 | 10 | var glitch = require('../dist/glitch-canvas-node.js'); 11 | 12 | var imagePath = '../examples/img/lincoln.jpg'; 13 | 14 | var defaultParams = { 15 | amount: 35, 16 | iterations: 20, 17 | quality: 30, 18 | seed: 25, 19 | }; 20 | 21 | describe('node tests for glitch-canvas', function () { 22 | it('should add a global function named "glitch"', function () { 23 | expect(glitch).to.be.a('function'); 24 | }); 25 | 26 | it('should return an object', function () { 27 | expect(glitch()).to.be.an('object'); 28 | }); 29 | 30 | it('should return all input methods available in the browser', function () { 31 | var g = glitch(); 32 | expect(g.fromBuffer).to.be.a('function'); 33 | expect(g.fromImageData).to.be.a('function'); 34 | expect(g.fromStream).to.be.a('function'); 35 | }); 36 | 37 | it('should have a getParams method', function () { 38 | expect(glitch().getParams).to.be.a('function'); 39 | }); 40 | 41 | // getParams() 42 | 43 | describe('#getParams()', function () { 44 | it('should return an object with all parameters', function () { 45 | var params = glitch().getParams(); 46 | expect(params.amount).to.be.a('number'); 47 | expect(params.iterations).to.be.a('number'); 48 | expect(params.quality).to.be.a('number'); 49 | expect(params.seed).to.be.a('number'); 50 | }); 51 | 52 | it('should set the default no parameters are submitted', function () { 53 | var params = glitch().getParams(); 54 | 55 | for (var key in defaultParams) { 56 | expect(params[key]).to.be(defaultParams[key]); 57 | } 58 | }); 59 | 60 | it('should add missing parameters', function () { 61 | var params = glitch({ amount: 10, blur: 90 }).getParams(); 62 | 63 | for (var key in defaultParams) { 64 | expect(params[key]).not.to.be('undefined'); 65 | } 66 | }); 67 | 68 | it('should clamp the parameters', function () { 69 | var params = glitch({ 70 | amount: 300, 71 | iterations: -23, 72 | quality: 1023.2, 73 | seed: 0, 74 | }).getParams(); 75 | 76 | for (var key in defaultParams) { 77 | expect(params[key]).to.be.within(0, 100); 78 | } 79 | }); 80 | }); 81 | 82 | describe('Asynchronous Methods', function () { 83 | it('should have a fromBuffer method', function () { 84 | expect(glitch().fromBuffer).to.be.a('function'); 85 | }); 86 | 87 | describe('#fromBuffer()', function () { 88 | it('should accept an image buffer as parameter', function (done) { 89 | this.timeout(10000); 90 | 91 | loadImageBuffer(done, function (buffer) { 92 | var failed = false; 93 | 94 | glitch() 95 | .fromBuffer(buffer) 96 | .toDataURL() 97 | .then( 98 | function (res) { 99 | failed = false; 100 | check(); 101 | }, 102 | function (err) { 103 | failed = true; 104 | check(); 105 | } 106 | ); 107 | 108 | function check() { 109 | expect(failed).to.be(false); 110 | done(); 111 | } 112 | }); 113 | }); 114 | 115 | it('should throw an error if no buffer was passed', function (done) { 116 | var buffer = 'lol'; 117 | var failed = false; 118 | 119 | glitch() 120 | .fromBuffer(buffer) 121 | .toDataURL() 122 | .then( 123 | function (res) { 124 | failed = false; 125 | check(); 126 | }, 127 | function (err) { 128 | failed = true; 129 | check(); 130 | } 131 | ); 132 | 133 | function check() { 134 | expect(failed).to.be(true); 135 | done(); 136 | } 137 | }); 138 | }); 139 | 140 | // fromImageData 141 | 142 | it('should have a fromImageData method', function () { 143 | expect(glitch().fromImageData).to.be.a('function'); 144 | }); 145 | 146 | describe('#fromImageData()', function () { 147 | it('should be able to handle an imageData object', function (done) { 148 | this.timeout(20000); 149 | 150 | loadImageBuffer(done, function (buffer) { 151 | var imageData = bufferToImageData(buffer); 152 | 153 | var failed = false; 154 | 155 | glitch() 156 | .fromImageData(imageData) 157 | .toDataURL() 158 | .then( 159 | function (res) { 160 | failed = false; 161 | check(); 162 | }, 163 | function (err) { 164 | failed = true; 165 | check(err); 166 | } 167 | ); 168 | 169 | function check(err) { 170 | expect(failed).to.be(false); 171 | done(err); 172 | } 173 | }); 174 | }); 175 | 176 | it('should throw an error when provided with corrupt imageData object', function (done) { 177 | this.timeout(10000); 178 | 179 | var failed = false; 180 | glitch() 181 | .fromImageData({ width: 0, data: 'LOL', height: 10 }) 182 | .toDataURL() 183 | .then( 184 | function (res) { 185 | failed = false; 186 | check(); 187 | }, 188 | function (err) { 189 | failed = true; 190 | check(); 191 | } 192 | ); 193 | 194 | function check() { 195 | expect(failed).to.be(true); 196 | done(); 197 | } 198 | }); 199 | 200 | it('should throw an error when provided with no imageData object', function (done) { 201 | this.timeout(10000); 202 | 203 | var failed = false; 204 | glitch() 205 | .fromImageData() 206 | .toDataURL() 207 | .then( 208 | function (res) { 209 | failed = false; 210 | check(); 211 | }, 212 | function (err) { 213 | failed = true; 214 | check(); 215 | } 216 | ); 217 | 218 | function check() { 219 | expect(failed).to.be(true); 220 | done(); 221 | } 222 | }); 223 | }); 224 | 225 | // fromStream 226 | 227 | it('should have a fromStream method', function () { 228 | expect(glitch().fromStream).to.be.a('function'); 229 | }); 230 | 231 | describe('#fromStream()', function () { 232 | it('should accept a readStream as input', function (done) { 233 | var readStream = fs.createReadStream( 234 | __dirname + '/' + imagePath 235 | ); 236 | 237 | glitch() 238 | .fromStream(readStream) 239 | .toImageData() 240 | .then(function (imageData) { 241 | expect(imageData).to.be.an('object'); 242 | done(); 243 | }, done); 244 | }); 245 | }); 246 | 247 | // toDataURL 248 | 249 | it('should have a toDataURL method', function () { 250 | expect(glitch().fromBuffer().toDataURL).to.be.a('function'); 251 | }); 252 | 253 | describe('#toDataURL()', function () { 254 | it('should return a dataURL', function (done) { 255 | this.timeout(10000); 256 | 257 | loadImageBuffer(done, function (buffer) { 258 | glitch() 259 | .fromBuffer(buffer) 260 | .toDataURL() 261 | .then(function (url) { 262 | expect(url).to.be.a('string'); 263 | expect(url.length).to.be.greaterThan(21); 264 | expect(url.indexOf('data:image/jpeg;base64')).to.be( 265 | 0 266 | ); 267 | done(); 268 | }, done); 269 | }); 270 | }); 271 | }); 272 | 273 | // toImageData 274 | 275 | it('should have a toImageData method', function () { 276 | expect(glitch().fromBuffer().toImageData).to.be.a('function'); 277 | }); 278 | 279 | describe('#toImageData()', function () { 280 | it('should return an imageData object', function (done) { 281 | this.timeout(10000); 282 | 283 | loadImageBuffer(done, function (buffer) { 284 | glitch() 285 | .fromBuffer(buffer) 286 | .toImageData() 287 | .then(function (imageData) { 288 | expect(imageData).to.be.an('object'); 289 | expect(imageData.width).to.be.a('number'); 290 | expect(imageData.height).to.be.a('number'); 291 | expect(imageData.data).not.to.be('undefined'); 292 | expect(imageData.data.length).to.be.greaterThan(0); 293 | 294 | done(); 295 | }, done); 296 | }); 297 | }); 298 | }); 299 | 300 | // toBuffer 301 | 302 | it('should have a toBuffer method', function () { 303 | expect(glitch().fromBuffer().toBuffer).to.be.a('function'); 304 | }); 305 | 306 | describe('#toBuffer()', function () { 307 | it('should return a buffer', function (done) { 308 | this.timeout(10000); 309 | 310 | loadImageBuffer(done, function (buffer) { 311 | glitch() 312 | .fromBuffer(buffer) 313 | .toBuffer() 314 | .then(function (glitchedBuffer) { 315 | expect(glitchedBuffer).to.be.an('object'); 316 | expect(glitchedBuffer instanceof Buffer).to.be( 317 | true 318 | ); 319 | done(); 320 | }, done); 321 | }); 322 | }); 323 | }); 324 | 325 | // toBuffer 326 | 327 | it('should have a toJPGStream method', function () { 328 | expect(glitch().fromBuffer().toJPGStream).to.be.a('function'); 329 | expect(glitch().fromBuffer().toJPEGStream).to.be.a('function'); 330 | }); 331 | 332 | describe('#toJPGStream()', function () { 333 | it('should return a JPGStream', function (done) { 334 | loadImageBuffer(done, function (buffer) { 335 | glitch() 336 | .fromBuffer(buffer) 337 | .toJPGStream() 338 | .then(function (jpgStream) { 339 | expect(jpgStream instanceof JPEGStream).to.be(true); 340 | done(); 341 | }, done); 342 | }); 343 | }); 344 | 345 | it('should send data via JPGStream', function (done) { 346 | loadImageBuffer(done, function (buffer) { 347 | glitch() 348 | .fromBuffer(buffer) 349 | .toJPGStream() 350 | .then(function (jpgStream) { 351 | var hasSentData = false; 352 | 353 | jpgStream.on('data', function (chunk) { 354 | hasSentData = !!chunk; 355 | }); 356 | 357 | jpgStream.on('end', function () { 358 | expect(hasSentData).to.be(true); 359 | done(); 360 | }); 361 | 362 | jpgStream.on('error', done); 363 | }, done); 364 | }); 365 | }); 366 | }); 367 | 368 | // toPNGStream 369 | 370 | describe('#toPNGStream()', function () { 371 | it('should return valid PNGStream', function (done) { 372 | loadImageBuffer(done, function (buffer) { 373 | glitch() 374 | .fromBuffer(buffer) 375 | .toPNGStream() 376 | .then(function (pngStream) { 377 | expect(pngStream instanceof PNGStream).to.be(true); 378 | done(); 379 | }, done); 380 | }); 381 | }); 382 | 383 | it('should send data via PNGStream', function (done) { 384 | loadImageBuffer(done, function (buffer) { 385 | glitch() 386 | .fromBuffer(buffer) 387 | .toPNGStream() 388 | .then(function (pngStream) { 389 | var hasSentData = false; 390 | 391 | pngStream.on('data', function (chunk) { 392 | hasSentData = !!chunk; 393 | }); 394 | 395 | pngStream.on('end', function () { 396 | expect(hasSentData).to.be(true); 397 | done(); 398 | }); 399 | 400 | pngStream.on('error', done); 401 | }, done); 402 | }); 403 | }); 404 | }); 405 | }); 406 | }); 407 | 408 | function loadImageBuffer(err, callback) { 409 | fs.readFile(__dirname + '/' + imagePath, function (err, buffer) { 410 | if (err) { 411 | done(err); 412 | throw err; 413 | } 414 | 415 | callback(buffer); 416 | }); 417 | } 418 | 419 | function bufferToImageData(buffer) { 420 | var img = new Image(); 421 | img.src = buffer; 422 | 423 | var canvas = createCanvas(img.width, img.height); 424 | var ctx = canvas.getContext('2d'); 425 | ctx.drawImage(img, 0, 0); 426 | 427 | return ctx.getImageData(0, 0, img.width, img.height); 428 | } 429 | -------------------------------------------------------------------------------- /test/tester.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | glitch-canvas tests 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, expect, glitch*/ 2 | describe('glitch-canvas browser tests', function () { 3 | // prepare two canvas elements 4 | var canvas_one = document.getElementById('canvas-one'); 5 | var canvas_two = document.getElementById('canvas-two'); 6 | var ctx_one = canvas_one.getContext('2d'); 7 | var ctx_two = canvas_two.getContext('2d'); 8 | var canvas_width = canvas_one.clientWidth; 9 | var canvas_height = canvas_one.clientHeight; 10 | 11 | // draw white background 12 | ctx_one.fillStyle = '#ffffff'; 13 | ctx_one.fillRect(0, 0, canvas_width, canvas_height); 14 | 15 | //draw some stuff so we have something to glitch 16 | ctx_one.beginPath(); 17 | ctx_one.moveTo(23, 190); 18 | ctx_one.lineTo(230, 80); 19 | ctx_one.lineTo(150, 30); 20 | ctx_one.closePath(); 21 | ctx_one.fillStyle = '#77bbaa'; 22 | ctx_one.fill(); 23 | 24 | ctx_one.fillStyle = '#ff0000'; 25 | ctx_one.beginPath(); 26 | ctx_one.arc(100, 100, 50, 0, Math.PI * 2, true); 27 | ctx_one.closePath(); 28 | ctx_one.fill(); 29 | 30 | ctx_one.fillStyle = '#0066ff'; 31 | ctx_one.fillRect(120, 80, 120, 30); 32 | 33 | function callback(image_data) { 34 | ctx_two.clearRect(0, 0, canvas_width, canvas_height); 35 | ctx_two.putImageData(image_data, 0, 0); 36 | } 37 | 38 | var image_data = ctx_one.getImageData(0, 0, canvas_width, canvas_height); 39 | var parameters = { amount: 10, seed: 45, iterations: 30, quality: 30 }; 40 | 41 | function glitchWithoutImageData() { 42 | glitch(undefined, parameters, callback); 43 | } 44 | function glitchWithEmptyImageData() { 45 | glitch({ width: 0, height: 0, data: [] }, parameters, callback); 46 | } 47 | function glitchWithoutParameters() { 48 | glitch(image_data, undefined, callback); 49 | } 50 | function glitchWithoutCallback() { 51 | glitch(image_data, parameters); 52 | } 53 | function glitchWithEmptyParameters() { 54 | glitch(image_data, {}, callback); 55 | } 56 | function glitchCorrectly() { 57 | glitch(image_data, parameters, callback); 58 | } 59 | 60 | // http://stackoverflow.com/a/15208067/229189 61 | function checkAsync(done, fn) { 62 | try { 63 | fn(); 64 | done(); 65 | } catch (e) { 66 | done(e); 67 | } 68 | } 69 | 70 | it('adds a global glitch function named "glitch"', function () { 71 | expect(typeof glitch).to.equal('function'); 72 | }); 73 | 74 | it('throws an error if no parameters are given', function () { 75 | expect(glitch).to.throw(Error); 76 | }); 77 | 78 | it('throws an error if the image_data argument is not undefined', function () { 79 | expect(glitchWithoutImageData).to.throw(Error); 80 | }); 81 | 82 | it('throws an error if the image_data argument is empty', function () { 83 | expect(glitchWithEmptyImageData).to.throw(Error); 84 | }); 85 | 86 | it('throws an error if the parameters argument is undefined', function () { 87 | expect(glitchWithoutParameters).to.throw(Error); 88 | }); 89 | 90 | it("doesn't throw an error if the parameters argument is empty", function () { 91 | expect(glitchWithEmptyParameters).not.to.throw(Error); 92 | }); 93 | 94 | it('throws an error if the callback argument is not undefined', function () { 95 | expect(glitchWithoutCallback).to.throw(Error); 96 | }); 97 | 98 | it("doesn't throw an error with correct arguments", function () { 99 | expect(glitchCorrectly).not.to.throw(Error); 100 | }); 101 | 102 | it('does pass a valid imageData object to callback function', function (done) { 103 | glitch(image_data, parameters, function (glitched_image_data) { 104 | checkAsync(done, function () { 105 | expect(typeof glitched_image_data).to.equal('object'); 106 | expect(typeof glitched_image_data.width).to.equal('number'); 107 | expect(glitched_image_data.width).to.equal(canvas_width); 108 | expect(typeof glitched_image_data.height).to.equal('number'); 109 | expect(glitched_image_data.height).to.equal(canvas_height); 110 | expect(typeof glitched_image_data.data).to.equal('object'); 111 | expect(typeof glitched_image_data.data.length).to.equal( 112 | 'number' 113 | ); 114 | expect(glitched_image_data.data.length).to.equal( 115 | image_data.data.length 116 | ); 117 | }); 118 | }); 119 | }); 120 | }); 121 | --------------------------------------------------------------------------------