├── .gitignore ├── .jshintrc ├── .sublime-grunt.cache ├── Gruntfile.js ├── README.md ├── bower.json ├── demo ├── big_buck_bunny.mp4 ├── big_buck_bunny.ogv ├── big_buck_bunny.webm ├── index.html ├── ny_view.jpg ├── prism.css ├── prism.js └── styles.css ├── dist ├── jquery.tocanvas.js └── jquery.tocanvas.min.js ├── package.json └── src └── jquery.tocanvas.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /docs 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "expr": true, 7 | "immed": true, 8 | "noarg": true, 9 | "onevar": true, 10 | "quotmark": "double", 11 | "unused": true, 12 | "node": true 13 | } 14 | -------------------------------------------------------------------------------- /.sublime-grunt.cache: -------------------------------------------------------------------------------- 1 | {"F:\\php\\jquery-tocanvas\\Gruntfile.js":{"sha1":"7b7ae792703690de18ccd8a112c70b9935a2d8e5","tasks":{"concat":{"name":"concat","info":"Concatenate files.","meta":{"info":"\"grunt-contrib-concat\" local Npm module","filepath":"F:\\php\\jquery-tocanvas\\node_modules\\grunt-contrib-concat\\tasks\\concat.js"},"multi":true,"targets":["dist"]},"jshint":{"name":"jshint","info":"Validate files with JSHint.","meta":{"info":"\"grunt-contrib-jshint\" local Npm module","filepath":"F:\\php\\jquery-tocanvas\\node_modules\\grunt-contrib-jshint\\tasks\\jshint.js"},"multi":true},"uglify":{"name":"uglify","info":"Minify files with UglifyJS.","meta":{"info":"\"grunt-contrib-uglify\" local Npm module","filepath":"F:\\php\\jquery-tocanvas\\node_modules\\grunt-contrib-uglify\\tasks\\uglify.js"},"multi":true,"targets":["my_target"]},"watch":{"name":"watch","info":"Run predefined tasks whenever watched files change.","meta":{"info":"\"grunt-contrib-watch\" local Npm module","filepath":"F:\\php\\jquery-tocanvas\\node_modules\\grunt-contrib-watch\\tasks\\watch.js"},"targets":["tasks"]},"build":{"name":"build","info":"Alias for \"concat\", \"uglify\" tasks.","meta":{"info":"Gruntfile","filepath":"F:\\php\\jquery-tocanvas\\Gruntfile.js"}},"default":{"name":"default","info":"Alias for \"jshint\", \"build\" tasks.","meta":{"info":"Gruntfile","filepath":"F:\\php\\jquery-tocanvas\\Gruntfile.js"}},"travis":{"name":"travis","info":"Alias for \"default\" task.","meta":{"info":"Gruntfile","filepath":"F:\\php\\jquery-tocanvas\\Gruntfile.js"}}}}} -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | 5 | // Import package manifest 6 | pkg: grunt.file.readJSON("package.json"), 7 | 8 | // Banner definitions 9 | meta: { 10 | banner: "/*\n" + 11 | " * <%= pkg.title || pkg.name %> - v<%= pkg.version %>\n" + 12 | " * <%= pkg.description %>\n" + 13 | " * <%= pkg.homepage %>\n" + 14 | " *\n" + 15 | " * Made by <%= pkg.author.name %>\n" + 16 | " * Under <%= pkg.license %> License\n" + 17 | " */\n" 18 | }, 19 | 20 | // Concat definitions 21 | concat: { 22 | dist: { 23 | options: { 24 | banner: "<%= meta.banner %>" 25 | }, 26 | src: ["src/jquery.tocanvas.js"], 27 | dest: "dist/jquery.tocanvas.js" 28 | }, 29 | readme: { 30 | src: ["docs/banner.md", "docs/src/jquery.tocanvas.md"], 31 | dest: "README.md" 32 | } 33 | }, 34 | 35 | // Lint definitions 36 | jshint: { 37 | files: ["src/jquery.tocanvas.js"], 38 | options: { 39 | jshintrc: ".jshintrc" 40 | } 41 | }, 42 | 43 | // Minify definitions 44 | uglify: { 45 | my_target: { 46 | src: ["dist/jquery.tocanvas.js"], 47 | dest: "dist/jquery.tocanvas.min.js" 48 | }, 49 | options: { 50 | banner: "<%= meta.banner %>" 51 | } 52 | }, 53 | 54 | // watch for changes to source 55 | // Better than calling grunt a million times 56 | // (call 'grunt watch') 57 | watch: { 58 | files: ['src/*'], 59 | tasks: ['default'] 60 | }, 61 | 62 | jsdox: { 63 | generate: { 64 | options: { 65 | contentsEnabled: false, 66 | }, 67 | 68 | src: ['src/jquery.tocanvas.js'], 69 | dest: 'docs' 70 | }, 71 | } 72 | }); 73 | 74 | grunt.loadNpmTasks("grunt-contrib-concat"); 75 | grunt.loadNpmTasks("grunt-contrib-jshint"); 76 | grunt.loadNpmTasks("grunt-contrib-uglify"); 77 | grunt.loadNpmTasks("grunt-contrib-watch"); 78 | grunt.loadNpmTasks('grunt-jsdox'); 79 | 80 | grunt.registerTask("build", ["concat:dist", "uglify"]); 81 | grunt.registerTask("travis", ["default"]); 82 | grunt.registerTask("doc", ["jsdox:generate", "concat:readme"]); 83 | grunt.registerTask("default", ["jshint", "build", "doc"]); 84 | 85 | }; 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery toCanvas 2 | This plugin allow you to add effects to images and html5 videotags easily. It supports different types of processed effects and overlay 3 | 4 | [Check out a demo here](http://www.seedoubleyou.nl/demos/tocanvas) 5 | 6 | > documentention is a work in progress 7 | 8 | # Global 9 | 10 | 11 | 12 | 13 | 14 | * * * 15 | 16 | ### render() 17 | 18 | Render all effects, overlays, filters, etc. Called automatically by the plugin, but you can use this for custom functionality 19 | 20 | **Returns**: `obj`, this 21 | 22 | 23 | ### draw() 24 | 25 | Draw the contents of this.element to canvas en reset internal pixel data Called by the plugin#render method, but you can use this for custom functionality 26 | 27 | **Returns**: `obj`, this 28 | 29 | 30 | ### putImageData() 31 | 32 | draw the current pixel-data to the canvas and read the new pixel information from result Called by the process#processcallback method, but you can use this for custom functionality 33 | 34 | **Returns**: `obj`, this 35 | 36 | 37 | ### process(callback) 38 | 39 | Process a callback for each pixel. We loop over all pixels and call the callback for each. This function is used by all processed effects 40 | 41 | **Parameters** 42 | 43 | **callback**: `function`, The callback must return an array [r, g, b, a] 44 | 45 | **Returns**: `obj`, this 46 | 47 | 48 | ### grayscale(options) 49 | 50 | Convert pixels to a pure gray value 51 | 52 | **Parameters** 53 | 54 | **options**: `obj`, Convert pixels to a pure gray value 55 | 56 | **Returns**: `array`, r, g, b, a 57 | 58 | 59 | ### sepia(options) 60 | 61 | Convert pixels to sepia color 62 | 63 | **Parameters** 64 | 65 | **options**: `obj`, Convert pixels to sepia color 66 | 67 | **Returns**: `array`, r, g, b, a 68 | 69 | 70 | ### invert(options) 71 | 72 | Invert r, g and by by subtracting original values from 255 73 | 74 | **Parameters** 75 | 76 | **options**: `obj`, Invert r, g and by by subtracting original values from 255 77 | 78 | **Returns**: `array`, r, g, b, a 79 | 80 | 81 | ### edges(options) 82 | 83 | Find edges 84 | 85 | **Parameters** 86 | 87 | **options**: `obj`, Find edges 88 | 89 | **Returns**: `array`, r, g, b, a 90 | 91 | 92 | ### noise(options) 93 | 94 | Add randomized noise 95 | 96 | **Parameters** 97 | 98 | **options**: `obj`, Add randomized noise 99 | 100 | **Returns**: `array`, r, g, b, a 101 | 102 | 103 | ### pixelate(options) 104 | 105 | [pixelate description] 106 | 107 | **Parameters** 108 | 109 | **options**: `obj`, [pixelate description] 110 | 111 | **Returns**: `array`, r, g, b, a 112 | 113 | 114 | ### blur(options) 115 | 116 | **Parameters** 117 | 118 | **options**: `obj` 119 | 120 | **Returns**: `array`, r, g, b, a 121 | 122 | 123 | ### boxBlur(options) 124 | 125 | **Parameters** 126 | 127 | **options**: `obj` 128 | 129 | **Returns**: `array`, r, g, b, a 130 | 131 | 132 | ### gaussianBlur(options) 133 | 134 | **Parameters** 135 | 136 | **options**: `obj` 137 | 138 | **Returns**: `array`, r, g, b, a 139 | 140 | 141 | ### sharpen(options) 142 | 143 | **Parameters** 144 | 145 | **options**: `obj` 146 | 147 | **Returns**: `array`, r, g, b, a 148 | 149 | 150 | ### emboss(options) 151 | 152 | **Parameters** 153 | 154 | **options**: `obj` 155 | 156 | **Returns**: `array`, r, g, b, a 157 | 158 | 159 | ### laplace(options) 160 | 161 | **Parameters** 162 | 163 | **options**: `obj` 164 | 165 | **Returns**: `array`, r, g, b, a 166 | 167 | 168 | ### sobel(options) 169 | 170 | **Parameters** 171 | 172 | **options**: `obj` 173 | 174 | **Returns**: `array`, r, g, b, a 175 | 176 | 177 | ### sobelVertical(options) 178 | 179 | **Parameters** 180 | 181 | **options**: `obj` 182 | 183 | **Returns**: `array`, r, g, b, a 184 | 185 | 186 | ### sobelHorizontal(options) 187 | 188 | **Parameters** 189 | 190 | **options**: `obj` 191 | 192 | **Returns**: `array`, r, g, b, a 193 | 194 | 195 | ### convolutionFilter(options) 196 | 197 | **Parameters** 198 | 199 | **options**: `obj` 200 | 201 | **Returns**: `array`, r, g, b, a 202 | 203 | 204 | ### vignette(options) 205 | 206 | Add a vignette 207 | 208 | **Parameters** 209 | 210 | **options**: `obj`, Add a vignette 211 | 212 | **Returns**: `obj`, this 213 | 214 | 215 | ### threshold(options) 216 | 217 | **Parameters** 218 | 219 | **options**: `obj` 220 | 221 | **Returns**: `array`, r, g, b, a 222 | 223 | 224 | ### hue(options) 225 | 226 | **Parameters** 227 | 228 | **options**: `obj` 229 | 230 | **Returns**: `array`, r, g, b, a 231 | 232 | 233 | ### saturation(options) 234 | 235 | **Parameters** 236 | 237 | **options**: `obj` 238 | 239 | **Returns**: `array`, r, g, b, a 240 | 241 | 242 | ### brightness(options) 243 | 244 | **Parameters** 245 | 246 | **options**: `obj` 247 | 248 | **Returns**: `array`, r, g, b, a 249 | 250 | 251 | ### lightness(options) 252 | 253 | **Parameters** 254 | 255 | **options**: `obj` 256 | 257 | **Returns**: `array`, r, g, b, a 258 | 259 | 260 | ### contrast(options) 261 | 262 | **Parameters** 263 | 264 | **options**: `obj` 265 | 266 | **Returns**: `array`, r, g, b, a 267 | 268 | 269 | ### gamma(options) 270 | 271 | **Parameters** 272 | 273 | **options**: `obj` 274 | 275 | **Returns**: `array`, r, g, b, a 276 | 277 | 278 | ### rgb2hsl(r, g, b) 279 | 280 | Convert an RGB tuplet to HSL 281 | 282 | **Parameters** 283 | 284 | **r**: `int`, Red component [0-255] 285 | 286 | **g**: `int`, Green component [0-255] 287 | 288 | **b**: `int`, Blue component [0-255] 289 | 290 | **Returns**: `obj`, HSL (object {h, s, l}) 291 | 292 | 293 | ### hsl2rgb(h, s, l) 294 | 295 | Convert an HSL tuplet to RGB 296 | 297 | **Parameters** 298 | 299 | **h**: `int`, Hue component [0-1] 300 | 301 | **s**: `int`, Saturation component [0-1] 302 | 303 | **l**: `int`, Lightness component [0-1] 304 | 305 | **Returns**: `obj`, RGB (object {r, g, b}) 306 | 307 | 308 | ### hue2rgb(p, q, t) 309 | 310 | Convert a hue to an RGB component 311 | 312 | **Parameters** 313 | 314 | **p**: `int`, Convert a hue to an RGB component 315 | 316 | **q**: `int`, Convert a hue to an RGB component 317 | 318 | **t**: `int`, Convert a hue to an RGB component 319 | 320 | **Returns**: `int`, p 321 | 322 | 323 | ### gaussian(x, mu, sigma) 324 | 325 | 1D Gaussian function 326 | 327 | **Parameters** 328 | 329 | **x**: `float`, 1D Gaussian function 330 | 331 | **mu**: `float`, 1D Gaussian function 332 | 333 | **sigma**: `float`, 1D Gaussian function 334 | 335 | **Returns**: `float`, gaussian 336 | 337 | 338 | 339 | * * * 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-tocanvas", 3 | "version": "0.1.0", 4 | "homepage": "http://seedoubleyou.nl", 5 | "authors": [ 6 | "Cees-Willem Hofstede " 7 | ], 8 | "description": "A jquery plugin to overlay/append any img or html5 video with a canvas to add effects to it", 9 | "main": "src/jquery.tocanvas.js", 10 | "keywords": [ 11 | "jquery", 12 | "plugin", 13 | "tocanvas", 14 | "jquery-plugin", 15 | "jquery-tocanvas" 16 | ], 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /demo/big_buck_bunny.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeeDoubleYou/jquery-tocanvas/00dc19a48dbf527b174c690abcf7e1aabda0d8b7/demo/big_buck_bunny.mp4 -------------------------------------------------------------------------------- /demo/big_buck_bunny.ogv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeeDoubleYou/jquery-tocanvas/00dc19a48dbf527b174c690abcf7e1aabda0d8b7/demo/big_buck_bunny.ogv -------------------------------------------------------------------------------- /demo/big_buck_bunny.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeeDoubleYou/jquery-tocanvas/00dc19a48dbf527b174c690abcf7e1aabda0d8b7/demo/big_buck_bunny.webm -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jQuery toCanvas Plugin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Wait for it...

13 | 14 |
15 |
16 |

jQuery toCanvas

17 | 18 |

19 | This plugin allows you to add live effects to images and videos! It features effects and overlays such as: 20 |

21 | 44 |

Each effect may have it's own specific settings which at some point will all be documented. There are some general options as well.

45 |
$(".toCanvas").toCanvas({
 46 | framerate: 0, // updates per seconds (use this for video)
 47 | hoverOpacity: 1, // opacity of canvas on hover
 48 | opacity: 1, // opacity of canvas
 49 | overlay: true, // draw canvas on top of image/video
 50 | process: {}, // sets the effects to be used
 51 | sharedOptions: { // object with options that are re-used for all processed effects
 52 |   opacity:   1, // opacity of the effect
 53 |   xPctStart: 0, // % on x-axis to start
 54 |   xPctEnd: 100, // % on x-axis to end
 55 |   yPctStart: 0, // % on y-axis to start
 56 |   yPctEnd: 100, // % on y-axis to end
 57 | }
 58 | });
59 |

60 | Setting it up is easy! Simply download the project from the github page, include it on the page and use it like the examples below. 61 |

62 | 63 |

64 | Feature-requests are more than welcome. Pull requests are even better! We may build the next online image editor together. 65 |

66 | 67 | 68 |
69 | 70 |
71 |

Original, no toCanvas

72 |

73 | A picture I took from my hotel in New-York in the summer of 2011 74 |

75 | 76 |
77 | 78 | 79 | 80 |
81 |

Grayscale

82 |

83 | Simple grayscale, single effect. Hover over the image to see the original. 84 |

85 |
$(".toCanvas-gray").toCanvas({
 86 |     hoverOpacity: 0,
 87 |     process: [
 88 |         { grayscale: { } }
 89 |     ]
 90 | });
91 | 92 |
93 | 94 | 95 | 96 |
97 |

Hue, Saturation and Lightness

98 |

99 | You can change the HSL. 100 |

101 |

102 | $(".toCanvas-hsl").toCanvas({
103 | 	hoverOpacity: 0,
104 | 	process: [
105 | 		{ hue: { value: 50, colorize: true } },
106 | 		{ saturation: { value: -50 } },
107 | 		{ lightness:  { value: +10 } }
108 | 	]
109 | });
110 | 111 |
112 | 113 | 114 | 115 |
116 |

Blur

117 |

118 | Blur effect using 3x3 neighborhood 119 |

120 |
121 | 0.1  0.1  0.1
122 | 0.1  0.2  0.1
123 | 0.1  0.1  0.1
124 | 				
125 |

126 | $(".toCanvas-gauss").toCanvas({
127 | 	hoverOpacity: 0,
128 | 	process: [
129 | 			{ blur }
130 | 		]
131 | 	});
132 | 133 |
134 | 135 | 136 | 137 |
138 |

Box Blur

139 |

140 | Simple Box Blur effect 141 |

142 |

143 | $(".toCanvas-gauss").toCanvas({
144 | 	hoverOpacity: 0,
145 | 	process: [
146 | 		{ boxBlur: { radius: 10} }
147 | 	]
148 | });
149 | 150 |
151 | 152 | 153 | 154 |
155 |

Gaussian Blur

156 |

157 | You can use convolution filters as well. Some come with toCanvas built in, like a gaussian filter. 158 |

159 |

160 | $(".toCanvas-gauss").toCanvas({
161 | 	hoverOpacity: 0,
162 | 	process: [
163 | 		{ gaussianBlur: { radius: 10} }
164 | 	]
165 | });
166 | 167 |
168 | 169 | 170 | 171 |
172 |

Custom convolution filter

173 |

174 | Custom convolution filters are supported. The example below shows an example of a motion blur filter 175 |

176 |

177 | $(".toCanvas-move").toCanvas({
178 |   hoverOpacity: 0,
179 |   process: [
180 | 	{ convolutionFilter: { filter: [
181 | 	  [1/9, 0, 0, 0, 0, 0, 0, 0, 0],
182 | 	  [0, 1/9, 0, 0, 0, 0, 0, 0, 0],
183 | 	  [0, 0, 1/9, 0, 0, 0, 0, 0, 0],
184 | 	  [0, 0, 0, 1/9, 0, 0, 0, 0, 0],
185 | 	  [0, 0, 0, 0, 1/9, 0, 0, 0, 0],
186 | 	  [0, 0, 0, 0, 0, 1/9, 0, 0, 0],
187 | 	  [0, 0, 0, 0, 0, 0, 1/9, 0, 0],
188 | 	  [0, 0, 0, 0, 0, 0, 0, 1/9, 0],
189 | 	  [0, 0, 0, 0, 0, 0, 0, 0, 1/9]
190 | 	] } }
191 |   ]
192 | });
193 | 194 |
195 | 196 | 197 | 198 |
199 |

Combined effects

200 |

201 | You can use combine and even repeat effects. In the example below we make the bottom 30% gray, threshold a bar, add some gaussian blur and a sharpen. Pro tip: don't do this for videos. 202 |

203 |

204 | $(".toCanvas-combined").toCanvas({
205 | hoverOpacity: 0,
206 | process: [
207 | 	{ grayscale: {
208 | 		yPctStart: 70,
209 | 	} },
210 | 	{ threshold: {
211 | 		yPctStart: 70,
212 | 		yPctEnd: 80,
213 | 		opacity: 0.9
214 | 	} },
215 | 	{ gaussianBlur: {
216 | 		radius: 10,
217 | 		xPctStart: 15,
218 | 		xPctEnd: 50,
219 | 	} },
220 | 	{ sharpen: {
221 | 		xPctStart: 70,
222 | 		xPctEnd: 90,
223 | 	} },
224 | ]
225 | });
226 | 227 |
228 | 229 | 230 | 231 |
232 |

Video!

233 |

234 | toCanvas works with html5 video. In the example below the canvas it not overlayed and shows an inverted version of the video below the actual video. 235 |

236 |

237 | $('.toCanvas-video').toCanvas({
238 | 	framerate: 10,
239 | 	overlay: false,
240 | 	process: [
241 | 		{ invert: {} },
242 | 	]
243 | });
244 | 245 | 250 |
251 |
252 | 253 | 254 | 255 |
256 |

Playground

257 |

258 | this is a very simple "playground" to show you how you can use toCanvas to build your own online photoshop. Not really though. 259 |

260 |

261 | var playgroundOptions = {
262 | 	hoverOpacity: 0,
263 | 	process: [
264 | 		{ threshold: {
265 | 			threshold: $( "#pg-threshold" ).val()
266 | 		} }
267 | 	],
268 | 	sharedOptions: {
269 | 		opacity: $( "#pg-opacity" ).val()
270 | 	}
271 | };
272 | 
273 | $playground = $(".toCanvas-playground").toCanvas(playgroundOptions);
274 | 
275 | $('#pg-threshold-slider').slider({
276 | 	value: $( "#pg-threshold" ).val(),
277 | 	min:   0,
278 | 	max:   255,
279 | 	slide: function( event, ui ) {
280 | 		$( "#pg-threshold" ).val( ui.value );
281 | 		playgroundOptions.process[0].threshold.threshold = ui.value;
282 | 		$playground.render();
283 | 	}
284 | });
285 | 
286 | $('#pg-opacity-slider').slider({
287 | 	value: $( "#pg-opacity" ).val(),
288 | 	min:   0,
289 | 	max:   1,
290 | 	step:  0.01,
291 | 	slide: function( event, ui ) {
292 | 		$( "#pg-opacity" ).val( ui.value );
293 | 		playgroundOptions.sharedOptions.opacity = ui.value;
294 | 		$playground.render();
295 | 	}
296 | });
297 | 298 |
299 |

300 | 301 | 302 |

303 |
304 | 305 |

306 | 307 | 308 |

309 |
310 |
311 |
312 | 313 | 314 | 315 | 316 | 317 | 462 | 463 | 464 | -------------------------------------------------------------------------------- /demo/ny_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SeeDoubleYou/jquery-tocanvas/00dc19a48dbf527b174c690abcf7e1aabda0d8b7/demo/ny_view.jpg -------------------------------------------------------------------------------- /demo/prism.css: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript */ 2 | /** 3 | * okaidia theme for JavaScript, CSS and HTML 4 | * Loosely based on Monokai textmate theme by http://www.monokai.nl/ 5 | * @author ocodia 6 | */ 7 | 8 | code[class*="language-"], 9 | pre[class*="language-"] { 10 | color: #f8f8f2; 11 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 12 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 13 | direction: ltr; 14 | text-align: left; 15 | white-space: pre; 16 | word-spacing: normal; 17 | word-break: normal; 18 | line-height: 1.5; 19 | 20 | -moz-tab-size: 4; 21 | -o-tab-size: 4; 22 | tab-size: 4; 23 | 24 | -webkit-hyphens: none; 25 | -moz-hyphens: none; 26 | -ms-hyphens: none; 27 | hyphens: none; 28 | } 29 | 30 | /* Code blocks */ 31 | pre[class*="language-"] { 32 | padding: 1em; 33 | margin: .5em 0; 34 | overflow: auto; 35 | border-radius: 0.3em; 36 | } 37 | 38 | :not(pre) > code[class*="language-"], 39 | pre[class*="language-"] { 40 | background: #272822; 41 | } 42 | 43 | /* Inline code */ 44 | :not(pre) > code[class*="language-"] { 45 | padding: .1em; 46 | border-radius: .3em; 47 | } 48 | 49 | .token.comment, 50 | .token.prolog, 51 | .token.doctype, 52 | .token.cdata { 53 | color: slategray; 54 | } 55 | 56 | .token.punctuation { 57 | color: #f8f8f2; 58 | } 59 | 60 | .namespace { 61 | opacity: .7; 62 | } 63 | 64 | .token.property, 65 | .token.tag, 66 | .token.constant, 67 | .token.symbol, 68 | .token.deleted { 69 | color: #f92672; 70 | } 71 | 72 | .token.boolean, 73 | .token.number { 74 | color: #ae81ff; 75 | } 76 | 77 | .token.selector, 78 | .token.attr-name, 79 | .token.string, 80 | .token.char, 81 | .token.builtin, 82 | .token.inserted { 83 | color: #a6e22e; 84 | } 85 | 86 | .token.operator, 87 | .token.entity, 88 | .token.url, 89 | .language-css .token.string, 90 | .style .token.string, 91 | .token.variable { 92 | color: #f8f8f2; 93 | } 94 | 95 | .token.atrule, 96 | .token.attr-value, 97 | .token.function { 98 | color: #e6db74; 99 | } 100 | 101 | .token.keyword { 102 | color: #66d9ef; 103 | } 104 | 105 | .token.regex, 106 | .token.important { 107 | color: #fd971f; 108 | } 109 | 110 | .token.important, 111 | .token.bold { 112 | font-weight: bold; 113 | } 114 | .token.italic { 115 | font-style: italic; 116 | } 117 | 118 | .token.entity { 119 | cursor: help; 120 | } 121 | 122 | -------------------------------------------------------------------------------- /demo/prism.js: -------------------------------------------------------------------------------- 1 | /* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+css+clike+javascript */ 2 | self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{};var Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var s="";for(var o in i.attributes)s+=o+'="'+(i.attributes[o]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+s+">"+i.content+""},!self.document)return self.addEventListener?(self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code;self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),self.close()},!1),self.Prism):self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);; 3 | Prism.languages.markup={comment://,prolog:/<\?.+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?[\w:-]+\s*(?:\s+[\w:-]+(?:=(?:("|')(\\?[\w\W])*?\1|[^\s'">=]+))?\s*)*\/?>/i,inside:{tag:{pattern:/^<\/?[\w:-]+/i,inside:{punctuation:/^<\/?/,namespace:/^[\w-]+?:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/=|>|"/}},punctuation:/\/?>/,"attr-name":{pattern:/[\w:-]+/,inside:{namespace:/^[\w-]+?:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(t){"entity"===t.type&&(t.attributes.title=t.content.replace(/&/,"&"))});; 4 | Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{punctuation:/[;:]/}},url:/url\((?:(["'])(\\\n|\\?.)*?\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*(?=\s*\{)/,string:/("|')(\\\n|\\?.)*?\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,punctuation:/[\{\};:]/,"function":/[-a-z0-9]+(?=\()/i},Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/[\w\W]*?<\/style>/i,inside:{tag:{pattern:/|<\/style>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css},alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag));; 5 | Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.+/,lookbehind:!0}],string:/("|')(\\\n|\\?.)*?\1/,"class-name":{pattern:/((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":{pattern:/[a-z0-9_]+\(/i,inside:{punctuation:/\(/}},number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|&{1,2}|\|?\||\?|\*|\/|~|\^|%/,ignore:/&(lt|gt|amp);/i,punctuation:/[{}[\];(),.:]/};; 6 | Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|function|get|if|implements|import|in|instanceof|interface|let|new|null|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|-?Infinity)\b/,"function":/(?!\d)[a-z0-9_$]+(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/[\w\W]*?<\/script>/i,inside:{tag:{pattern:/|<\/script>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript},alias:"language-javascript"}});; 7 | -------------------------------------------------------------------------------- /demo/styles.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | height: 100%; 7 | padding: 0; 8 | margin: 0; 9 | } 10 | 11 | body { 12 | background: #eee; 13 | color: #333; 14 | font-family: 'Raleway', sans-serif; 15 | text-align: center; 16 | } 17 | 18 | a { 19 | color: #333; 20 | transition: 500ms all; 21 | } 22 | a:hover { 23 | text-decoration: none; 24 | text-shadow: 0px 1px 2px #666; 25 | } 26 | 27 | h1 { 28 | font-size: 3em; 29 | text-shadow: 0px 2px 2px #333; 30 | } 31 | 32 | h2 { 33 | margin-bottom: 0.5em; 34 | text-shadow: 0px 1px 2px #333; 35 | } 36 | 37 | p { 38 | margin: 0 auto 0.5em; 39 | max-width: 400px; 40 | } 41 | 42 | pre[class*="language-"] { 43 | max-width: 400px; 44 | margin: 1em auto; 45 | } 46 | 47 | canvas { 48 | transition: all 0.8s; 49 | } 50 | 51 | ul { 52 | max-width: 400px; 53 | text-align: left; 54 | display: block; 55 | margin: 1em auto; 56 | padding-left: 2em; 57 | } 58 | li { 59 | list-style-position: inside; 60 | } 61 | 62 | video { 63 | display: block; 64 | margin: 0 auto; 65 | } 66 | 67 | #loader { 68 | background-color: #000; 69 | bottom: 0; 70 | display: none; 71 | color: #fff; 72 | font-size: 3em; 73 | height: 100%; 74 | left: 0; 75 | opacity: 0.8; 76 | padding: 25% 0 0 0; 77 | position: fixed; 78 | right: 0; 79 | text-align: center; 80 | top: 0; 81 | z-index: 99; 82 | } 83 | #loader p { 84 | } 85 | 86 | #forkframe { 87 | margin: 1em 0 0 0; 88 | } 89 | 90 | .wrapper { 91 | } 92 | 93 | .intro {} 94 | 95 | .example { 96 | margin: 1em auto; 97 | padding: 1em; 98 | position: relative; 99 | text-align: center; 100 | width: 100%; 101 | } 102 | .example:nth-child(even) { 103 | background: #ddd; 104 | box-shadow: 0px 0px 10px #ccc inset; 105 | } 106 | .example:nth-child(odd) { 107 | background: #eee; 108 | } 109 | 110 | .totop { 111 | background: #ccc; 112 | border-radius: 50%; 113 | display: block; 114 | font-weight: bold; 115 | height: 1.5em; 116 | line-height: 2em; 117 | margin: 0 auto 0.5em; 118 | padding-left: 1px; 119 | text-decoration: none; 120 | width: 1.5em; 121 | } 122 | .totop:hover { 123 | box-shadow: 0px 3px 4px #333; 124 | } 125 | 126 | .playground { 127 | margin: 1em auto; 128 | max-width: 400px; 129 | } 130 | .playground p { 131 | text-align: left; 132 | } 133 | .playground input[type='text'] { 134 | background: transparent; 135 | border: none; 136 | font-weight: bold; 137 | } 138 | .playground .slider { 139 | margin-bottom: 1em; 140 | } -------------------------------------------------------------------------------- /dist/jquery.tocanvas.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery-tocanvas - v0.2.0 3 | * A jquery plugin to overlay any img with a canvas to add effects to it 4 | * http://seedoubleyou.nl 5 | * 6 | * Made by Cees-Willem Hofstede 7 | * Under MIT License 8 | */ 9 | // the semi-colon before function invocation is a safety net against concatenated 10 | // scripts and/or other plugins which may not be closed properly. 11 | ;(function ( $, window, document, undefined ) { 12 | 13 | "use strict"; 14 | 15 | // undefined is used here as the undefined global variable in ECMAScript 3 is 16 | // mutable (ie. it can be changed by someone else). undefined isn't really being 17 | // passed in so we can ensure the value of it is truly undefined. In ES5, undefined 18 | // can no longer be modified. 19 | 20 | // window and document are passed through as local variable rather than global 21 | // as this (slightly) quickens the resolution process and can be more efficiently 22 | // minified (especially when both are regularly referenced in your plugin). 23 | 24 | // Create the defaults once 25 | var pluginName = "toCanvas", 26 | defaults = { 27 | continuous: false, 28 | framerate: 0, 29 | hoverOpacity: 1, 30 | opacity: 1, 31 | overlay: true, 32 | process: {}, 33 | sharedOptions: {}, 34 | zIndex: 10 35 | } 36 | ; 37 | 38 | // The actual plugin constructor 39 | function Plugin ( element, options ) { 40 | this.element = element; 41 | this.$element = $(this.element); 42 | this.$canvas = $(""); 43 | this.canvas = this.$canvas.get(0); 44 | this.wrapper = $("
"); 45 | this.$wrapper = $(this.wrapper); 46 | this.renderCount = 0; 47 | 48 | // jQuery has an extend method which merges the contents of two or 49 | // more objects, storing the result in the first object. The first object 50 | // is generally empty as we don't want to alter the default options for 51 | // future instances of the plugin 52 | this.settings = $.extend( {}, defaults, options ); 53 | this._defaults = defaults; 54 | this._name = pluginName; 55 | return this._init(); 56 | } 57 | 58 | // Avoid Plugin.prototype conflicts 59 | $.extend(Plugin.prototype, { 60 | 61 | /** 62 | * Called when plugin is created. Initial setup. 63 | * 64 | * @return {obj} this 65 | */ 66 | _init: function () { 67 | var tc = this; // tc stands for ToCanvas and is used as a context var throughout the plugins methods 68 | 69 | // if the element is an image, create an image object with the same source. 70 | // Wait for the image object to load so that we know its dimensions 71 | // then perform setup and render the canvas 72 | if(tc.$element.is("img")) { 73 | tc.imageObj = new Image(); 74 | tc.imageObj.onload = function() { 75 | tc._setup(); 76 | tc.render(); 77 | }; 78 | tc.imageObj.src = this.$element.attr("src"); 79 | } 80 | 81 | // Not an image? Video then (html5)? If so, awesome, let's use the video element as the image object 82 | // Then run the setup, and make sure the canvas is render when the video starts to play 83 | else if(tc.$element.is("video")) { 84 | tc.imageObj = tc.element; 85 | tc._setup(); 86 | tc.$element.on("play", function() { 87 | tc.render(); 88 | }); 89 | //tc.$element.trigger("play"); 90 | } 91 | 92 | // Not an image and not an video? Out of luck you are! 93 | else { 94 | return false; //incorrect source 95 | } 96 | 97 | // return this to allow chaining 98 | return tc; 99 | }, 100 | 101 | /** 102 | * Called when the object (img or video) is loaded. Do setup based on 103 | * object properties 104 | * 105 | * @return {obj} this 106 | */ 107 | _setup: function() { 108 | var tc = this; 109 | tc.w = tc.$element.width(); 110 | tc.h = tc.$element.height(); 111 | 112 | // add a wrapper so we can position the canvas on top of the image (when set as overlay) 113 | tc.$wrapper 114 | .addClass("tc_wrapper") 115 | .css({ 116 | display: "inline-block", 117 | height: tc.h, 118 | position: "relative", 119 | width: tc.w 120 | }) 121 | ; 122 | 123 | // create an abject for the canvas css based on the overlay setting 124 | var canvasCSS = tc.settings.overlay ? { 125 | left: 0, 126 | opacity: tc.settings.opacity, 127 | position: "absolute", 128 | top: 0, 129 | zIndex: tc.settings.zIndex 130 | } : { 131 | opacity: tc.settings.opacity, 132 | }; 133 | 134 | // create the canvas itself 135 | tc.$canvas 136 | .addClass("tc_canvas") // @todo: maybe nice to have this as a setting? 137 | .css(canvasCSS) 138 | 139 | // update opacity on hover 140 | .hover(function() { 141 | $(this).css({ 142 | opacity: tc.settings.hoverOpacity 143 | }); 144 | }, function() { 145 | $(this).css({ 146 | opacity: tc.settings.opacity 147 | }); 148 | }) 149 | 150 | .attr({ 151 | // width and height need to be set as attributes 152 | // so that the coordinate system is set correctly 153 | height: tc.h, 154 | width: tc.w 155 | }) 156 | ; 157 | 158 | if(tc.settings.overlay) { 159 | tc.$element.wrap(tc.$wrapper); 160 | tc.$element.before(tc.$canvas); 161 | } else { 162 | tc.$element.after(tc.$canvas); 163 | } 164 | 165 | tc.context = this.canvas.getContext("2d"); 166 | 167 | return tc; 168 | }, 169 | 170 | /** 171 | * Render all effects, overlays, filters, etc. 172 | * Called automatically by the plugin, but you can use this for custom functionality 173 | * 174 | * @return {obj} this 175 | */ 176 | render: function() { 177 | var tc = this; 178 | 179 | if(tc.imageObj.paused || tc.imageObj.ended) { 180 | return false; 181 | } 182 | 183 | tc.draw(); 184 | 185 | $.each(tc.settings.process, function(index, modifierObj) { 186 | var modifier = Object.keys(modifierObj)[0]; 187 | var options = modifierObj[modifier]; 188 | tc[modifier](options); 189 | }); 190 | 191 | tc.renderCount++; 192 | 193 | if(tc.settings.framerate > 0) { 194 | window.setTimeout(function() { 195 | tc.render(); 196 | }, 1000/tc.settings.framerate); 197 | } 198 | 199 | return tc; 200 | }, 201 | 202 | /** 203 | * Draw the contents of this.element to canvas en reset internal pixel data 204 | * Called by the {@link plugin#render} method, but you can use this for custom functionality 205 | * 206 | * @return {obj} this 207 | */ 208 | draw: function() { 209 | this.context.drawImage(this.imageObj, 0, 0, this.w, this.h); // draw image to canvas 210 | this.imgdIn = this.context.getImageData(0, 0, this.w, this.h); // get data from 2D context 211 | this.pixelsIn = this.imgdIn.data; 212 | this.nrPixels = this.pixelsIn.length; 213 | 214 | // we make a copy so that effects use 'in' during calculation 215 | // and 'out' when writing data 216 | this.imgdOut = this.context.getImageData(0, 0, this.w, this.h); // get data from 2D context 217 | this.pixelsOut = this.imgdOut.data; 218 | return this; 219 | }, 220 | 221 | /** 222 | * draw the current pixel-data to the canvas and read the new pixel information from result 223 | * Called by the {@link process#processcallback} method, but you can use this for custom functionality 224 | * 225 | * @return {obj} this 226 | */ 227 | putImageData: function() { 228 | this.context.putImageData(this.imgdOut, 0, 0); 229 | this.imgdIn = this.context.getImageData(0, 0, this.w, this.h); // get data from 2D context 230 | this.pixelsIn = this.imgdIn.data; 231 | this.nrPixels = this.pixelsIn.length; 232 | this.imgdOut = this.context.getImageData(0, 0, this.w, this.h); // get data from 2D context 233 | this.pixelsOut = this.imgdOut.data; 234 | 235 | return this; 236 | }, 237 | 238 | /** 239 | * Process a callback for each pixel. 240 | * We loop over all pixels and call the callback for each. 241 | * This function is used by all processed effects 242 | * 243 | * @param {function} callback The callback must return an array [r, g, b, a] 244 | * @return {obj} this 245 | */ 246 | process: function(callback, options) { 247 | options = $.extend({}, { 248 | opacity: 1, 249 | xPctStart: 0, 250 | xPctEnd: 100, 251 | yPctStart: 0, 252 | yPctEnd: 100, 253 | }, this.settings.sharedOptions, options); 254 | 255 | // calculate begin and end pixels from percentage settings 256 | var xStart = Math.round(options.xPctStart * (this.w/100)); 257 | var xEnd = Math.round(options.xPctEnd * (this.w/100)); 258 | var yStart = Math.round(options.yPctStart * (this.h/100)); 259 | var yEnd = Math.round(options.yPctEnd * (this.h/100)); 260 | 261 | // loop over x-axis from start to end 262 | for (var x = xStart; x < xEnd; x++) { 263 | // loop over y-axis from start to end 264 | for (var y = yStart; y < yEnd; y++) { 265 | var i = 4*(y*this.w + x); 266 | var r = this.pixelsIn[i], 267 | g = this.pixelsIn[i+1], 268 | b = this.pixelsIn[i+2], 269 | a = this.pixelsIn[i+3] 270 | ; 271 | // get processed pixel object from callback 272 | var processed = callback(r, g, b, a, x, y, i); 273 | 274 | // set pixel values from prosessed data 275 | this.pixelsOut[i] = processed[0]; 276 | this.pixelsOut[i+1] = processed[1]; 277 | this.pixelsOut[i+2] = processed[2]; 278 | this.pixelsOut[i+3] = options.opacity * processed[3]; 279 | } 280 | } 281 | 282 | this.putImageData(); 283 | 284 | return this; 285 | }, 286 | 287 | /** 288 | * -------------------------------------------------------------------------------- 289 | * EFFECTS 290 | * -------------------------------------------------------------------------------- 291 | */ 292 | 293 | /** 294 | * Convert pixels to a pure gray value 295 | * 296 | * @param {obj} options 297 | * @return {array} r, g, b, a 298 | */ 299 | grayscale: function(options) { 300 | return this.process(function(r, g, b, a) { 301 | var grayscale = r * 0.3 + g * 0.59 + b * 0.11; 302 | return [ 303 | grayscale, 304 | grayscale, 305 | grayscale, 306 | a 307 | ]; 308 | }, options); 309 | }, 310 | 311 | /** 312 | * Convert pixels to sepia color 313 | * 314 | * @param {obj} options 315 | * @return {array} r, g, b, a 316 | */ 317 | sepia: function(options) { 318 | return this.process(function(r, g, b, a) { 319 | return [ 320 | (r * 0.393)+(g * 0.769)+(b * 0.189), 321 | (r * 0.349)+(g * 0.686)+(b * 0.168), 322 | (r * 0.272)+(g * 0.534)+(b * 0.131), 323 | a 324 | ]; 325 | }, options); 326 | }, 327 | 328 | /** 329 | * Invert r, g and by by subtracting original values from 255 330 | * 331 | * @param {obj} options 332 | * @return {array} r, g, b, a 333 | */ 334 | invert: function(options) { 335 | return this.process(function(r, g, b, a) { 336 | return [ 337 | 255 - r, 338 | 255 - g, 339 | 255 - b, 340 | a 341 | ]; 342 | }, options); 343 | }, 344 | 345 | /** 346 | * Find edges 347 | * 348 | * @param {obj} options 349 | * @return {array} r, g, b, a 350 | */ 351 | edges: function(options) { 352 | var tc = this; 353 | var rowShift = tc.w*4; 354 | return tc.process(function(r, g, b, a, x, y, i) { 355 | r = 127 + 2*r - tc.pixels[i + 4] - tc.pixels[i + rowShift]; 356 | g = 127 + 2*g - tc.pixels[i + 5] - tc.pixels[i+1 + rowShift]; 357 | b = 127 + 2*b - tc.pixels[i + 6] - tc.pixels[i+2 + rowShift]; 358 | 359 | return [r, g, b, a]; 360 | }, options); 361 | }, 362 | 363 | /** 364 | * Add randomized noise 365 | * 366 | * @param {obj} options 367 | * @return {array} r, g, b, a 368 | */ 369 | noise: function(options) { 370 | options = $.extend({}, { 371 | amount: 10, 372 | }, options); 373 | return this.process(function(r, g, b, a) { 374 | return [ 375 | r + (0.5 - Math.random()) * options.amount, 376 | g + (0.5 - Math.random()) * options.amount, 377 | b + (0.5 - Math.random()) * options.amount, 378 | a 379 | ]; 380 | }, options); 381 | }, 382 | 383 | /** 384 | * [pixelate description] 385 | * @param {obj} options 386 | * @return {array} r, g, b, a 387 | */ 388 | pixelate: function(options) { 389 | var tc = this; 390 | 391 | options = $.extend({}, { 392 | blockSize: 100, 393 | }, options); 394 | 395 | blockSize = options.blockSize; 396 | blockSize = 1/blockSize; 397 | 398 | var w = tc.w * blockSize, 399 | h = tc.h * blockSize; 400 | 401 | tc.context.mozImageSmoothingEnabled = false; 402 | tc.context.webkitImageSmoothingEnabled = false; 403 | tc.context.imageSmoothingEnabled = false; 404 | 405 | tc.context.drawImage(tc.canvas, 0, 0, w, h); 406 | 407 | // TODO cannot draw from canvas? 408 | tc.context.drawImage(tc.canvas, 0, 0, w, h, 0, 0, this.w, this.h); 409 | 410 | tc.imgdIn = tc.context.getImageData(0, 0, tc.w, tc.h); // get data from 2D context 411 | tc.pixelsIn = tc.imgdIn.data; 412 | tc.nrPixels = tc.pixelsIn.length; 413 | 414 | // make sure other effect get the updated data 415 | tc.imgdOut = tc.context.getImageData(0, 0, tc.w, tc.h); 416 | tc.pixelsOut = tc.imgdOut.data; 417 | 418 | tc.putImageData(); 419 | 420 | return tc; 421 | }, 422 | 423 | /** 424 | * -------------------------------------------------------------------------------- 425 | * CONVULUTION FILTERS 426 | * -------------------------------------------------------------------------------- 427 | */ 428 | 429 | /** 430 | * @param {obj} options 431 | * @return {array} r, g, b, a 432 | */ 433 | blur: function(options) { 434 | options = $.extend({}, { 435 | filter: [ 436 | [0.1, 0.1, 0.1], 437 | [0.1, 0.2, 0.1], 438 | [0.1, 0.1, 0.1] 439 | ] 440 | }, options); 441 | 442 | return this.convolutionFilter(options); 443 | }, 444 | 445 | /** 446 | * @param {obj} options 447 | * @return {array} r, g, b, a 448 | */ 449 | boxBlur: function(options) { 450 | options = $.extend({}, { 451 | radius: 3 452 | }, options); 453 | 454 | var radius = options.radius; 455 | var len = radius * radius; 456 | var val = 1 / len; 457 | var filter = []; 458 | 459 | for(var r = 0; r < radius; r++) { 460 | var row = []; 461 | for(var c = 0; c < radius; c++) { 462 | row.push(val); 463 | } 464 | filter.push(row); 465 | } 466 | 467 | options = $.extend({}, { 468 | filter: filter 469 | }, options); 470 | 471 | return this.convolutionFilter(options); 472 | }, 473 | 474 | /** 475 | * @param {obj} options 476 | * @return {array} r, g, b, a 477 | */ 478 | gaussianBlur: function(options) { 479 | options = $.extend({}, { 480 | radius: 3 481 | }, options); 482 | 483 | var radius = options.radius; 484 | var size = 2*radius+1; 485 | var sigma = radius/2; 486 | var sum = 0.0; // For accumulating the filter values 487 | var filter = []; 488 | for (var x = 0; x < size; ++x) { 489 | var row = []; 490 | for (var y = 0; y < size; ++y) { 491 | var col = this.gaussian(x, radius, sigma) * this.gaussian(y, radius, sigma); 492 | row.push(col); 493 | sum += col; 494 | } 495 | filter.push(row); 496 | } 497 | 498 | // Normalize the filter 499 | for (var x2 = 0; x2 < size; ++x2) { 500 | for (var y2 = 0; y2 < size; ++y2) { 501 | filter[x2][y2] /= sum; 502 | } 503 | } 504 | 505 | options = $.extend({}, { 506 | filter: filter 507 | }, options); 508 | 509 | return this.convolutionFilter(options); 510 | }, 511 | 512 | /** 513 | * @param {obj} options 514 | * @return {array} r, g, b, a 515 | */ 516 | sharpen: function(options) { 517 | return this.convolutionFilter($.extend({}, { 518 | filter: [ 519 | [ 0, -1, 0], 520 | [-1, 5, -1], 521 | [ 0, -1, 0] 522 | ] 523 | }, options)); 524 | }, 525 | 526 | /** 527 | * @param {obj} options 528 | * @return {array} r, g, b, a 529 | */ 530 | emboss: function(options) { 531 | options = $.extend({}, { 532 | offset: 127 533 | }, options); 534 | 535 | return this.convolutionFilter($.extend({}, { 536 | filter: [ 537 | [2, 0, 0], 538 | [0, -1, 0], 539 | [0, 0, -1] 540 | ] 541 | }, options)); 542 | }, 543 | 544 | /** 545 | * @param {obj} options 546 | * @return {array} r, g, b, a 547 | */ 548 | laplace: function(options) { 549 | return this.convolutionFilter($.extend({}, { 550 | filter: [ 551 | [0, 1, 0], 552 | [1, -4, 1], 553 | [0, 1, 0] 554 | ] 555 | }, options)); 556 | }, 557 | 558 | /** 559 | * @param {obj} options 560 | * @return {array} r, g, b, a 561 | */ 562 | sobel: function(options) { 563 | this.sobelVertical(options); 564 | this.putImageData(); 565 | return this.sobelHorizontal(options); 566 | }, 567 | 568 | /** 569 | * @param {obj} options 570 | * @return {array} r, g, b, a 571 | */ 572 | sobelVertical: function(options) { 573 | return this.convolutionFilter($.extend({}, { 574 | filter: [ 575 | [-1, 0, 1], 576 | [-2, 0, 2], 577 | [-1, 0, 1] 578 | ] 579 | }, options)); 580 | }, 581 | 582 | /** 583 | * @param {obj} options 584 | * @return {array} r, g, b, a 585 | */ 586 | sobelHorizontal: function(options) { 587 | return this.convolutionFilter($.extend({}, { 588 | filter: [ 589 | [-1, -2, -1], 590 | [ 0, 0, 0], 591 | [ 1, 2, 1] 592 | ] 593 | }, options)); 594 | }, 595 | 596 | /** 597 | * @param {obj} options 598 | * @return {array} r, g, b, a 599 | */ 600 | convolutionFilter: function(options) { 601 | options = $.extend({}, { 602 | updateR: true, 603 | updateG: true, 604 | updateB: true, 605 | offset: 0, 606 | filter: [1] 607 | }, options); 608 | 609 | var filter = options.filter; 610 | 611 | var tc = this; 612 | 613 | var rows = filter.length; // odd 614 | var cols = filter[0].length; // odd 615 | 616 | var rm = Math.floor(rows/2); // center of row (current pixel) 617 | var cm = Math.floor(cols/2); // center of column (current pixel) 618 | 619 | return tc.process(function(r, g, b, a, x, y, i) { 620 | 621 | var nR = options.updateR ? 0 : tc.pixelsIn[i ], 622 | nG = options.updateG ? 0 : tc.pixelsIn[i+1], 623 | nB = options.updateB ? 0 : tc.pixelsIn[i+2] 624 | ; 625 | 626 | for(var row = 0; row < rows; row++) { 627 | var rd = Math.abs(row-rm); 628 | var ri = (row < rm ? -rd : (row > rm ? +rd : 0)); 629 | 630 | var nY = Math.max(Math.min(y+ri, tc.h-1), 0); 631 | 632 | for(var col = 0; col < cols; col++) { 633 | var cd = Math.abs(col-cm); 634 | var ci = (col < cm ? -cd : (col > cm ? +cd : 0)); 635 | 636 | var nX = Math.max(Math.min(x+ci, tc.w-1), 0); 637 | var nI = 4*(nY * tc.w + nX); 638 | 639 | if(options.updateR) { nR += (filter[row][col] * tc.pixelsIn[nI ]); } 640 | if(options.updateG) { nG += (filter[row][col] * tc.pixelsIn[nI+1]); } 641 | if(options.updateB) { nB += (filter[row][col] * tc.pixelsIn[nI+2]); } 642 | } 643 | } 644 | return [options.offset + nR, options.offset + nG, options.offset + nB, a]; 645 | }, options); 646 | }, 647 | 648 | /** 649 | * -------------------------------------------------------------------------------- 650 | * OVERLAYS 651 | * -------------------------------------------------------------------------------- 652 | */ 653 | 654 | /** 655 | * Add a vignette 656 | * 657 | * @param {obj} options 658 | * @return {obj} this 659 | */ 660 | vignette: function(options) { 661 | options = $.extend( {}, { 662 | size: 0.5, 663 | opacity: 1 664 | }, options ); 665 | 666 | var outerRadius = Math.sqrt( Math.pow(this.w / 2, 2) + Math.pow(this.h / 2, 2) ); 667 | var gradient = this.context.createRadialGradient(this.w/2, this.h/2, 0, this.w/2, this.h/2, outerRadius); 668 | 669 | // write current data to image so we can overlay the vignette 670 | this.context.putImageData(this.imgdOut, 0, 0); 671 | this.context.globalCompositeOperation = "source-over"; 672 | gradient.addColorStop(0, "rgba(0,0,0,0)"); 673 | gradient.addColorStop(options.size, "rgba(0,0,0,0)"); 674 | gradient.addColorStop(1, "rgba(0,0,0,"+ options.opacity +")"); 675 | this.context.fillStyle = gradient; 676 | this.context.fillRect(0, 0, this.w, this.h); 677 | 678 | // make sure other effect get the updated data 679 | this.imgdOut = this.context.getImageData(0, 0, this.w, this.h); 680 | this.pixelsOut = this.imgdOut.data; 681 | 682 | return this; 683 | }, 684 | 685 | 686 | /** 687 | * -------------------------------------------------------------------------------- 688 | * ADJUSTMENTS 689 | * -------------------------------------------------------------------------------- 690 | */ 691 | 692 | /** 693 | * @param {obj} options 694 | * @return {array} r, g, b, a 695 | */ 696 | threshold: function(options) { 697 | options = $.extend({}, { 698 | threshold: 127, 699 | }, options); 700 | 701 | return this.process(function(r, g, b, a) { 702 | var v = (0.2126*r + 0.7152*g + 0.0722*b >= options.threshold) ? 255 : 0; 703 | return [ 704 | v, 705 | v, 706 | v, 707 | a 708 | ]; 709 | }, options); 710 | }, 711 | 712 | /** 713 | * @param {obj} options 714 | * @return {array} r, g, b, a 715 | */ 716 | hue: function(options) { 717 | return this.hslUpdate("h", options); 718 | }, 719 | 720 | /** 721 | * @param {obj} options 722 | * @return {array} r, g, b, a 723 | */ 724 | saturation: function(options) { 725 | return this.hslUpdate("s", options); 726 | }, 727 | 728 | /** 729 | * @param {obj} options 730 | * @return {array} r, g, b, a 731 | */ 732 | brightness: function(options) { 733 | return this.lightness(options); 734 | }, 735 | 736 | /** 737 | * @param {obj} options 738 | * @return {array} r, g, b, a 739 | */ 740 | lightness: function(options) { 741 | return this.hslUpdate("l", options); 742 | }, 743 | 744 | hslUpdate: function(axis, options) { 745 | var tc = this; 746 | options = $.extend( {}, { 747 | value: 0, 748 | colorize: false 749 | }, options ); 750 | 751 | if (options.value === 0 && options.colorize !== true) { 752 | return false; 753 | } 754 | 755 | var max = axis === "h" ? 360 : 100; 756 | 757 | return this.process(function(r, g, b, a) { 758 | var hsl = tc.rgb2hsl(r, g, b); 759 | 760 | if(options.colorize) { 761 | // colorize means, set the value to exaclty the value 762 | hsl[axis] = Math.min(Math.max(options.value/max, 0), 1); 763 | } else { 764 | // add the value to the current value 765 | if(axis === "h") { 766 | hsl[axis] = (hsl[axis]*max + options.value) % max; 767 | if(hsl[axis] < 0) { 768 | hsl[axis] = max - hsl[axis]; 769 | } 770 | hsl[axis] /= max; 771 | } else { 772 | hsl[axis] = Math.min(Math.max(hsl[axis] + options.value/max, 0), 1); 773 | } 774 | } 775 | 776 | var rgb = tc.hsl2rgb(hsl.h, hsl.s, hsl.l); 777 | 778 | return [ 779 | rgb.r, 780 | rgb.g, 781 | rgb.b, 782 | a 783 | ]; 784 | }, options); 785 | }, 786 | 787 | /** 788 | * @param {obj} options 789 | * @return {array} r, g, b, a 790 | */ 791 | contrast: function(options) { 792 | options = $.extend({}, { 793 | value: 10, 794 | }, options); 795 | 796 | var level = Math.pow((options.value + 100) / 100, 2); 797 | return this.process(function(r, g, b, a) { 798 | return [ 799 | ((r / 255 - 0.5) * level + 0.5) * 255, 800 | ((g / 255 - 0.5) * level + 0.5) * 255, 801 | ((b / 255 - 0.5) * level + 0.5) * 255, 802 | a 803 | ]; 804 | }, options); 805 | }, 806 | 807 | /** 808 | * @param {obj} options 809 | * @return {array} r, g, b, a 810 | */ 811 | gamma: function(options) { 812 | options = $.extend({}, { 813 | value: 1.5, 814 | }, options); 815 | 816 | return this.process(function(r, g, b, a) { 817 | return [ 818 | r * options.value, 819 | g * options.value, 820 | b * options.value, 821 | a 822 | ]; 823 | }, options); 824 | }, 825 | 826 | 827 | /** 828 | * -------------------------------------------------------------------------------- 829 | * HELPERS 830 | * -------------------------------------------------------------------------------- 831 | */ 832 | 833 | /** 834 | * Convert an RGB tuplet to HSL 835 | * @param {int} r Red component [0-255] 836 | * @param {int} g Green component [0-255] 837 | * @param {int} b Blue component [0-255] 838 | * @return {obj} HSL (object {h, s, l}) 839 | */ 840 | rgb2hsl: function(r, g, b) { 841 | r /= 255; 842 | g /= 255; 843 | b /= 255; 844 | var max = Math.max(r, g, b); 845 | var min = Math.min(r, g, b); 846 | var h, s, l = (max + min) / 2; 847 | 848 | if (max === min) { 849 | h = s = 0; 850 | } else { 851 | var d = max - min; 852 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 853 | switch (max) { 854 | case r: 855 | h = (g - b) / d + (g < b ? 6 : 0); 856 | break; 857 | 858 | case g: 859 | h = (b - r) / d + 2; 860 | break; 861 | 862 | case b: 863 | h = (r - g) / d + 4; 864 | break; 865 | } 866 | } 867 | h /= 6; 868 | return { 869 | h: h, 870 | s: s, 871 | l: l 872 | }; 873 | }, 874 | 875 | /** 876 | * Convert an HSL tuplet to RGB 877 | * @param {int} h Hue component [0-1] 878 | * @param {int} s Saturation component [0-1] 879 | * @param {int} l Lightness component [0-1] 880 | * @return {obj} RGB (object {r, g, b}) 881 | */ 882 | hsl2rgb: function(h, s, l) { 883 | var r, g, b; 884 | if (s === 0) { 885 | r = g = b = l; //gray value 886 | } else { 887 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 888 | var p = 2 * l - q; 889 | r = this.hue2rgb(p, q, h + 1 / 3); 890 | g = this.hue2rgb(p, q, h); 891 | b = this.hue2rgb(p, q, h - 1 / 3); 892 | } 893 | return { 894 | r: r * 255, 895 | g: g * 255, 896 | b: b * 255 897 | }; 898 | }, 899 | 900 | /** 901 | * Convert a hue to an RGB component 902 | * @param {int} p 903 | * @param {int} q 904 | * @param {int} t 905 | * @return {int} p 906 | */ 907 | hue2rgb: function(p, q, t) { 908 | if (t < 0) { t += 1; } 909 | if (t > 1) { t -= 1; } 910 | if (t < 1 / 6) { return p + (q - p) * 6 * t; } 911 | if (t < 1 / 2) { return q; } 912 | if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6; } 913 | return p; 914 | }, 915 | 916 | /** 917 | * 1D Gaussian function 918 | * @param {float} x 919 | * @param {float} mu 920 | * @param {float} sigma 921 | * @return {float} gaussian 922 | */ 923 | gaussian: function(x, mu, sigma) { 924 | return Math.exp( -(((x-mu)/(sigma))*((x-mu)/(sigma)))/2.0 ); 925 | }, 926 | }); 927 | 928 | // A really lightweight plugin wrapper around the constructor, 929 | // preventing against multiple instantiations 930 | $.fn[ pluginName ] = function ( options ) { 931 | var plugin; 932 | var canvas2DSupported = !!window.CanvasRenderingContext2D; 933 | if(!canvas2DSupported) { 934 | return false; 935 | } 936 | 937 | this.each(function() { 938 | plugin = $.data(this, "plugin_" + pluginName); 939 | if (!plugin) { 940 | plugin = new Plugin(this, options); 941 | $.data(this, "plugin_" + pluginName, plugin); 942 | } 943 | }); 944 | return plugin; 945 | }; 946 | 947 | })( jQuery, window, document ); 948 | -------------------------------------------------------------------------------- /dist/jquery.tocanvas.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery-tocanvas - v0.2.0 3 | * A jquery plugin to overlay any img with a canvas to add effects to it 4 | * http://seedoubleyou.nl 5 | * 6 | * Made by Cees-Willem Hofstede 7 | * Under MIT License 8 | */ 9 | !function(a,b){"use strict";function c(b,c){return this.element=b,this.$element=a(this.element),this.$canvas=a(""),this.canvas=this.$canvas.get(0),this.wrapper=a("
"),this.$wrapper=a(this.wrapper),this.renderCount=0,this.settings=a.extend({},e,c),this._defaults=e,this._name=d,this._init()}var d="toCanvas",e={continuous:!1,framerate:0,hoverOpacity:1,opacity:1,overlay:!0,process:{},sharedOptions:{},zIndex:10};a.extend(c.prototype,{_init:function(){var a=this;if(a.$element.is("img"))a.imageObj=new Image,a.imageObj.onload=function(){a._setup(),a.render()},a.imageObj.src=this.$element.attr("src");else{if(!a.$element.is("video"))return!1;a.imageObj=a.element,a._setup(),a.$element.on("play",function(){a.render()})}return a},_setup:function(){var b=this;b.w=b.$element.width(),b.h=b.$element.height(),b.$wrapper.addClass("tc_wrapper").css({display:"inline-block",height:b.h,position:"relative",width:b.w});var c=b.settings.overlay?{left:0,opacity:b.settings.opacity,position:"absolute",top:0,zIndex:b.settings.zIndex}:{opacity:b.settings.opacity};return b.$canvas.addClass("tc_canvas").css(c).hover(function(){a(this).css({opacity:b.settings.hoverOpacity})},function(){a(this).css({opacity:b.settings.opacity})}).attr({height:b.h,width:b.w}),b.settings.overlay?(b.$element.wrap(b.$wrapper),b.$element.before(b.$canvas)):b.$element.after(b.$canvas),b.context=this.canvas.getContext("2d"),b},render:function(){var c=this;return c.imageObj.paused||c.imageObj.ended?!1:(c.draw(),a.each(c.settings.process,function(a,b){var d=Object.keys(b)[0],e=b[d];c[d](e)}),c.renderCount++,c.settings.framerate>0&&b.setTimeout(function(){c.render()},1e3/c.settings.framerate),c)},draw:function(){return this.context.drawImage(this.imageObj,0,0,this.w,this.h),this.imgdIn=this.context.getImageData(0,0,this.w,this.h),this.pixelsIn=this.imgdIn.data,this.nrPixels=this.pixelsIn.length,this.imgdOut=this.context.getImageData(0,0,this.w,this.h),this.pixelsOut=this.imgdOut.data,this},putImageData:function(){return this.context.putImageData(this.imgdOut,0,0),this.imgdIn=this.context.getImageData(0,0,this.w,this.h),this.pixelsIn=this.imgdIn.data,this.nrPixels=this.pixelsIn.length,this.imgdOut=this.context.getImageData(0,0,this.w,this.h),this.pixelsOut=this.imgdOut.data,this},process:function(b,c){c=a.extend({},{opacity:1,xPctStart:0,xPctEnd:100,yPctStart:0,yPctEnd:100},this.settings.sharedOptions,c);for(var d=Math.round(c.xPctStart*(this.w/100)),e=Math.round(c.xPctEnd*(this.w/100)),f=Math.round(c.yPctStart*(this.h/100)),g=Math.round(c.yPctEnd*(this.h/100)),h=d;e>h;h++)for(var i=f;g>i;i++){var j=4*(i*this.w+h),k=this.pixelsIn[j],l=this.pixelsIn[j+1],m=this.pixelsIn[j+2],n=this.pixelsIn[j+3],o=b(k,l,m,n,h,i,j);this.pixelsOut[j]=o[0],this.pixelsOut[j+1]=o[1],this.pixelsOut[j+2]=o[2],this.pixelsOut[j+3]=c.opacity*o[3]}return this.putImageData(),this},grayscale:function(a){return this.process(function(a,b,c,d){var e=.3*a+.59*b+.11*c;return[e,e,e,d]},a)},sepia:function(a){return this.process(function(a,b,c,d){return[.393*a+.769*b+.189*c,.349*a+.686*b+.168*c,.272*a+.534*b+.131*c,d]},a)},invert:function(a){return this.process(function(a,b,c,d){return[255-a,255-b,255-c,d]},a)},edges:function(a){var b=this,c=4*b.w;return b.process(function(a,d,e,f,g,h,i){return a=127+2*a-b.pixels[i+4]-b.pixels[i+c],d=127+2*d-b.pixels[i+5]-b.pixels[i+1+c],e=127+2*e-b.pixels[i+6]-b.pixels[i+2+c],[a,d,e,f]},a)},noise:function(b){return b=a.extend({},{amount:10},b),this.process(function(a,c,d,e){return[a+(.5-Math.random())*b.amount,c+(.5-Math.random())*b.amount,d+(.5-Math.random())*b.amount,e]},b)},pixelate:function(b){var c=this;b=a.extend({},{blockSize:100},b),blockSize=b.blockSize,blockSize=1/blockSize;var d=c.w*blockSize,e=c.h*blockSize;return c.context.mozImageSmoothingEnabled=!1,c.context.webkitImageSmoothingEnabled=!1,c.context.imageSmoothingEnabled=!1,c.context.drawImage(c.canvas,0,0,d,e),c.context.drawImage(c.canvas,0,0,d,e,0,0,this.w,this.h),c.imgdIn=c.context.getImageData(0,0,c.w,c.h),c.pixelsIn=c.imgdIn.data,c.nrPixels=c.pixelsIn.length,c.imgdOut=c.context.getImageData(0,0,c.w,c.h),c.pixelsOut=c.imgdOut.data,c.putImageData(),c},blur:function(b){return b=a.extend({},{filter:[[.1,.1,.1],[.1,.2,.1],[.1,.1,.1]]},b),this.convolutionFilter(b)},boxBlur:function(b){b=a.extend({},{radius:3},b);for(var c=b.radius,d=c*c,e=1/d,f=[],g=0;c>g;g++){for(var h=[],i=0;c>i;i++)h.push(e);f.push(h)}return b=a.extend({},{filter:f},b),this.convolutionFilter(b)},gaussianBlur:function(b){b=a.extend({},{radius:3},b);for(var c=b.radius,d=2*c+1,e=c/2,f=0,g=[],h=0;d>h;++h){for(var i=[],j=0;d>j;++j){var k=this.gaussian(h,c,e)*this.gaussian(j,c,e);i.push(k),f+=k}g.push(i)}for(var l=0;d>l;++l)for(var m=0;d>m;++m)g[l][m]/=f;return b=a.extend({},{filter:g},b),this.convolutionFilter(b)},sharpen:function(b){return this.convolutionFilter(a.extend({},{filter:[[0,-1,0],[-1,5,-1],[0,-1,0]]},b))},emboss:function(b){return b=a.extend({},{offset:127},b),this.convolutionFilter(a.extend({},{filter:[[2,0,0],[0,-1,0],[0,0,-1]]},b))},laplace:function(b){return this.convolutionFilter(a.extend({},{filter:[[0,1,0],[1,-4,1],[0,1,0]]},b))},sobel:function(a){return this.sobelVertical(a),this.putImageData(),this.sobelHorizontal(a)},sobelVertical:function(b){return this.convolutionFilter(a.extend({},{filter:[[-1,0,1],[-2,0,2],[-1,0,1]]},b))},sobelHorizontal:function(b){return this.convolutionFilter(a.extend({},{filter:[[-1,-2,-1],[0,0,0],[1,2,1]]},b))},convolutionFilter:function(b){b=a.extend({},{updateR:!0,updateG:!0,updateB:!0,offset:0,filter:[1]},b);var c=b.filter,d=this,e=c.length,f=c[0].length,g=Math.floor(e/2),h=Math.floor(f/2);return d.process(function(a,i,j,k,l,m,n){for(var o=b.updateR?0:d.pixelsIn[n],p=b.updateG?0:d.pixelsIn[n+1],q=b.updateB?0:d.pixelsIn[n+2],r=0;e>r;r++)for(var s=Math.abs(r-g),t=g>r?-s:r>g?+s:0,u=Math.max(Math.min(m+t,d.h-1),0),v=0;f>v;v++){var w=Math.abs(v-h),x=h>v?-w:v>h?+w:0,y=Math.max(Math.min(l+x,d.w-1),0),z=4*(u*d.w+y);b.updateR&&(o+=c[r][v]*d.pixelsIn[z]),b.updateG&&(p+=c[r][v]*d.pixelsIn[z+1]),b.updateB&&(q+=c[r][v]*d.pixelsIn[z+2])}return[b.offset+o,b.offset+p,b.offset+q,k]},b)},vignette:function(b){b=a.extend({},{size:.5,opacity:1},b);var c=Math.sqrt(Math.pow(this.w/2,2)+Math.pow(this.h/2,2)),d=this.context.createRadialGradient(this.w/2,this.h/2,0,this.w/2,this.h/2,c);return this.context.putImageData(this.imgdOut,0,0),this.context.globalCompositeOperation="source-over",d.addColorStop(0,"rgba(0,0,0,0)"),d.addColorStop(b.size,"rgba(0,0,0,0)"),d.addColorStop(1,"rgba(0,0,0,"+b.opacity+")"),this.context.fillStyle=d,this.context.fillRect(0,0,this.w,this.h),this.imgdOut=this.context.getImageData(0,0,this.w,this.h),this.pixelsOut=this.imgdOut.data,this},threshold:function(b){return b=a.extend({},{threshold:127},b),this.process(function(a,c,d,e){var f=.2126*a+.7152*c+.0722*d>=b.threshold?255:0;return[f,f,f,e]},b)},hue:function(a){return this.hslUpdate("h",a)},saturation:function(a){return this.hslUpdate("s",a)},brightness:function(a){return this.lightness(a)},lightness:function(a){return this.hslUpdate("l",a)},hslUpdate:function(b,c){var d=this;if(c=a.extend({},{value:0,colorize:!1},c),0===c.value&&c.colorize!==!0)return!1;var e="h"===b?360:100;return this.process(function(a,f,g,h){var i=d.rgb2hsl(a,f,g);c.colorize?i[b]=Math.min(Math.max(c.value/e,0),1):"h"===b?(i[b]=(i[b]*e+c.value)%e,i[b]<0&&(i[b]=e-i[b]),i[b]/=e):i[b]=Math.min(Math.max(i[b]+c.value/e,0),1);var j=d.hsl2rgb(i.h,i.s,i.l);return[j.r,j.g,j.b,h]},c)},contrast:function(b){b=a.extend({},{value:10},b);var c=Math.pow((b.value+100)/100,2);return this.process(function(a,b,d,e){return[255*((a/255-.5)*c+.5),255*((b/255-.5)*c+.5),255*((d/255-.5)*c+.5),e]},b)},gamma:function(b){return b=a.extend({},{value:1.5},b),this.process(function(a,c,d,e){return[a*b.value,c*b.value,d*b.value,e]},b)},rgb2hsl:function(a,b,c){a/=255,b/=255,c/=255;var d,e,f=Math.max(a,b,c),g=Math.min(a,b,c),h=(f+g)/2;if(f===g)d=e=0;else{var i=f-g;switch(e=h>.5?i/(2-f-g):i/(f+g),f){case a:d=(b-c)/i+(c>b?6:0);break;case b:d=(c-a)/i+2;break;case c:d=(a-b)/i+4}}return d/=6,{h:d,s:e,l:h}},hsl2rgb:function(a,b,c){var d,e,f;if(0===b)d=e=f=c;else{var g=.5>c?c*(1+b):c+b-c*b,h=2*c-g;d=this.hue2rgb(h,g,a+1/3),e=this.hue2rgb(h,g,a),f=this.hue2rgb(h,g,a-1/3)}return{r:255*d,g:255*e,b:255*f}},hue2rgb:function(a,b,c){return 0>c&&(c+=1),c>1&&(c-=1),1/6>c?a+6*(b-a)*c:.5>c?b:2/3>c?a+(b-a)*(2/3-c)*6:a},gaussian:function(a,b,c){return Math.exp(-((a-b)/c*((a-b)/c))/2)}}),a.fn[d]=function(e){var f,g=!!b.CanvasRenderingContext2D;return g?(this.each(function(){f=a.data(this,"plugin_"+d),f||(f=new c(this,e),a.data(this,"plugin_"+d,f))}),f):!1}}(jQuery,window,document); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-tocanvas", 3 | "version": "0.2.0", 4 | "description": "A jquery plugin to overlay any img with a canvas to add effects to it", 5 | "keywords": [ 6 | "jquery-plugin", 7 | "jquery", 8 | "plugins" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/SeeDoubleYou/jquery-tocanvas" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/SeeDoubleYou/jquery-tocanvas/issues" 16 | }, 17 | "author": { 18 | "name": "Cees-Willem Hofstede", 19 | "email": "ceeswillem@gmail.com", 20 | "url": "https://github.com/SeeDoubleYou" 21 | }, 22 | "homepage": "http://seedoubleyou.nl", 23 | "contributors": [ 24 | { 25 | "name": "Cees-Willem Hofstede", 26 | "email": "ceeswillem@gmail.com", 27 | "url": "https://github.com/SeeDoubleYou" 28 | } 29 | ], 30 | "license": "MIT", 31 | "devDependencies": { 32 | "bower": "^1.3.12", 33 | "grunt": "^0.4.5", 34 | "grunt-cli": "^0.1.13", 35 | "grunt-contrib-concat": "^0.5.1", 36 | "grunt-contrib-jshint": "^0.11.1", 37 | "grunt-contrib-uglify": "^0.8.0", 38 | "grunt-contrib-watch": "^0.6.1", 39 | "grunt-jsdoc": "^0.5.8", 40 | "grunt-jsdox": "^0.1.7", 41 | "travis": "^0.1.1" 42 | }, 43 | "scripts": { 44 | "test": "grunt travis --verbose" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/jquery.tocanvas.js: -------------------------------------------------------------------------------- 1 | // the semi-colon before function invocation is a safety net against concatenated 2 | // scripts and/or other plugins which may not be closed properly. 3 | ;(function ( $, window, document, undefined ) { 4 | 5 | "use strict"; 6 | 7 | // undefined is used here as the undefined global variable in ECMAScript 3 is 8 | // mutable (ie. it can be changed by someone else). undefined isn't really being 9 | // passed in so we can ensure the value of it is truly undefined. In ES5, undefined 10 | // can no longer be modified. 11 | 12 | // window and document are passed through as local variable rather than global 13 | // as this (slightly) quickens the resolution process and can be more efficiently 14 | // minified (especially when both are regularly referenced in your plugin). 15 | 16 | // Create the defaults once 17 | var pluginName = "toCanvas", 18 | defaults = { 19 | continuous: false, 20 | framerate: 0, 21 | hoverOpacity: 1, 22 | opacity: 1, 23 | overlay: true, 24 | process: {}, 25 | sharedOptions: {}, 26 | zIndex: 10 27 | } 28 | ; 29 | 30 | // The actual plugin constructor 31 | function Plugin ( element, options ) { 32 | this.element = element; 33 | this.$element = $(this.element); 34 | this.$canvas = $(""); 35 | this.canvas = this.$canvas.get(0); 36 | this.wrapper = $("
"); 37 | this.$wrapper = $(this.wrapper); 38 | this.renderCount = 0; 39 | 40 | // jQuery has an extend method which merges the contents of two or 41 | // more objects, storing the result in the first object. The first object 42 | // is generally empty as we don't want to alter the default options for 43 | // future instances of the plugin 44 | this.settings = $.extend( {}, defaults, options ); 45 | this._defaults = defaults; 46 | this._name = pluginName; 47 | return this._init(); 48 | } 49 | 50 | // Avoid Plugin.prototype conflicts 51 | $.extend(Plugin.prototype, { 52 | 53 | /** 54 | * Called when plugin is created. Initial setup. 55 | * 56 | * @return {obj} this 57 | */ 58 | _init: function () { 59 | var tc = this; // tc stands for ToCanvas and is used as a context var throughout the plugins methods 60 | 61 | // if the element is an image, create an image object with the same source. 62 | // Wait for the image object to load so that we know its dimensions 63 | // then perform setup and render the canvas 64 | if(tc.$element.is("img")) { 65 | tc.imageObj = new Image(); 66 | tc.imageObj.onload = function() { 67 | tc._setup(); 68 | tc.render(); 69 | }; 70 | tc.imageObj.src = this.$element.attr("src"); 71 | } 72 | 73 | // Not an image? Video then (html5)? If so, awesome, let's use the video element as the image object 74 | // Then run the setup, and make sure the canvas is render when the video starts to play 75 | else if(tc.$element.is("video")) { 76 | tc.imageObj = tc.element; 77 | tc._setup(); 78 | tc.$element.on("play", function() { 79 | tc.render(); 80 | }); 81 | //tc.$element.trigger("play"); 82 | } 83 | 84 | // Not an image and not an video? Out of luck you are! 85 | else { 86 | return false; //incorrect source 87 | } 88 | 89 | // return this to allow chaining 90 | return tc; 91 | }, 92 | 93 | /** 94 | * Called when the object (img or video) is loaded. Do setup based on 95 | * object properties 96 | * 97 | * @return {obj} this 98 | */ 99 | _setup: function() { 100 | var tc = this; 101 | tc.w = tc.$element.width(); 102 | tc.h = tc.$element.height(); 103 | 104 | // add a wrapper so we can position the canvas on top of the image (when set as overlay) 105 | tc.$wrapper 106 | .addClass("tc_wrapper") 107 | .css({ 108 | display: "inline-block", 109 | height: tc.h, 110 | position: "relative", 111 | width: tc.w 112 | }) 113 | ; 114 | 115 | // create an abject for the canvas css based on the overlay setting 116 | var canvasCSS = tc.settings.overlay ? { 117 | left: 0, 118 | opacity: tc.settings.opacity, 119 | position: "absolute", 120 | top: 0, 121 | zIndex: tc.settings.zIndex 122 | } : { 123 | opacity: tc.settings.opacity, 124 | }; 125 | 126 | // create the canvas itself 127 | tc.$canvas 128 | .addClass("tc_canvas") // @todo: maybe nice to have this as a setting? 129 | .css(canvasCSS) 130 | 131 | // update opacity on hover 132 | .hover(function() { 133 | $(this).css({ 134 | opacity: tc.settings.hoverOpacity 135 | }); 136 | }, function() { 137 | $(this).css({ 138 | opacity: tc.settings.opacity 139 | }); 140 | }) 141 | 142 | .attr({ 143 | // width and height need to be set as attributes 144 | // so that the coordinate system is set correctly 145 | height: tc.h, 146 | width: tc.w 147 | }) 148 | ; 149 | 150 | if(tc.settings.overlay) { 151 | tc.$element.wrap(tc.$wrapper); 152 | tc.$element.before(tc.$canvas); 153 | } else { 154 | tc.$element.after(tc.$canvas); 155 | } 156 | 157 | tc.context = this.canvas.getContext("2d"); 158 | 159 | return tc; 160 | }, 161 | 162 | /** 163 | * Render all effects, overlays, filters, etc. 164 | * Called automatically by the plugin, but you can use this for custom functionality 165 | * 166 | * @return {obj} this 167 | */ 168 | render: function() { 169 | var tc = this; 170 | 171 | if(tc.imageObj.paused || tc.imageObj.ended) { 172 | return false; 173 | } 174 | 175 | tc.draw(); 176 | 177 | $.each(tc.settings.process, function(index, modifierObj) { 178 | var modifier = Object.keys(modifierObj)[0]; 179 | var options = modifierObj[modifier]; 180 | tc[modifier](options); 181 | }); 182 | 183 | tc.renderCount++; 184 | 185 | if(tc.settings.framerate > 0) { 186 | window.setTimeout(function() { 187 | tc.render(); 188 | }, 1000/tc.settings.framerate); 189 | } 190 | 191 | return tc; 192 | }, 193 | 194 | /** 195 | * Draw the contents of this.element to canvas en reset internal pixel data 196 | * Called by the {@link plugin#render} method, but you can use this for custom functionality 197 | * 198 | * @return {obj} this 199 | */ 200 | draw: function() { 201 | this.context.drawImage(this.imageObj, 0, 0, this.w, this.h); // draw image to canvas 202 | this.imgdIn = this.context.getImageData(0, 0, this.w, this.h); // get data from 2D context 203 | this.pixelsIn = this.imgdIn.data; 204 | this.nrPixels = this.pixelsIn.length; 205 | 206 | // we make a copy so that effects use 'in' during calculation 207 | // and 'out' when writing data 208 | this.imgdOut = this.context.getImageData(0, 0, this.w, this.h); // get data from 2D context 209 | this.pixelsOut = this.imgdOut.data; 210 | return this; 211 | }, 212 | 213 | /** 214 | * draw the current pixel-data to the canvas and read the new pixel information from result 215 | * Called by the {@link process#processcallback} method, but you can use this for custom functionality 216 | * 217 | * @return {obj} this 218 | */ 219 | putImageData: function() { 220 | this.context.putImageData(this.imgdOut, 0, 0); 221 | this.imgdIn = this.context.getImageData(0, 0, this.w, this.h); // get data from 2D context 222 | this.pixelsIn = this.imgdIn.data; 223 | this.nrPixels = this.pixelsIn.length; 224 | this.imgdOut = this.context.getImageData(0, 0, this.w, this.h); // get data from 2D context 225 | this.pixelsOut = this.imgdOut.data; 226 | 227 | return this; 228 | }, 229 | 230 | /** 231 | * Process a callback for each pixel. 232 | * We loop over all pixels and call the callback for each. 233 | * This function is used by all processed effects 234 | * 235 | * @param {function} callback The callback must return an array [r, g, b, a] 236 | * @return {obj} this 237 | */ 238 | process: function(callback, options) { 239 | options = $.extend({}, { 240 | opacity: 1, 241 | xPctStart: 0, 242 | xPctEnd: 100, 243 | yPctStart: 0, 244 | yPctEnd: 100, 245 | }, this.settings.sharedOptions, options); 246 | 247 | // calculate begin and end pixels from percentage settings 248 | var xStart = Math.round(options.xPctStart * (this.w/100)); 249 | var xEnd = Math.round(options.xPctEnd * (this.w/100)); 250 | var yStart = Math.round(options.yPctStart * (this.h/100)); 251 | var yEnd = Math.round(options.yPctEnd * (this.h/100)); 252 | 253 | // loop over x-axis from start to end 254 | for (var x = xStart; x < xEnd; x++) { 255 | // loop over y-axis from start to end 256 | for (var y = yStart; y < yEnd; y++) { 257 | var i = 4*(y*this.w + x); 258 | var r = this.pixelsIn[i], 259 | g = this.pixelsIn[i+1], 260 | b = this.pixelsIn[i+2], 261 | a = this.pixelsIn[i+3] 262 | ; 263 | // get processed pixel object from callback 264 | var processed = callback(r, g, b, a, x, y, i); 265 | 266 | // set pixel values from prosessed data 267 | this.pixelsOut[i] = processed[0]; 268 | this.pixelsOut[i+1] = processed[1]; 269 | this.pixelsOut[i+2] = processed[2]; 270 | this.pixelsOut[i+3] = options.opacity * processed[3]; 271 | } 272 | } 273 | 274 | this.putImageData(); 275 | 276 | return this; 277 | }, 278 | 279 | /** 280 | * -------------------------------------------------------------------------------- 281 | * EFFECTS 282 | * -------------------------------------------------------------------------------- 283 | */ 284 | 285 | /** 286 | * Convert pixels to a pure gray value 287 | * 288 | * @param {obj} options 289 | * @return {array} r, g, b, a 290 | */ 291 | grayscale: function(options) { 292 | return this.process(function(r, g, b, a) { 293 | var grayscale = r * 0.3 + g * 0.59 + b * 0.11; 294 | return [ 295 | grayscale, 296 | grayscale, 297 | grayscale, 298 | a 299 | ]; 300 | }, options); 301 | }, 302 | 303 | /** 304 | * Convert pixels to sepia color 305 | * 306 | * @param {obj} options 307 | * @return {array} r, g, b, a 308 | */ 309 | sepia: function(options) { 310 | return this.process(function(r, g, b, a) { 311 | return [ 312 | (r * 0.393)+(g * 0.769)+(b * 0.189), 313 | (r * 0.349)+(g * 0.686)+(b * 0.168), 314 | (r * 0.272)+(g * 0.534)+(b * 0.131), 315 | a 316 | ]; 317 | }, options); 318 | }, 319 | 320 | /** 321 | * Invert r, g and by by subtracting original values from 255 322 | * 323 | * @param {obj} options 324 | * @return {array} r, g, b, a 325 | */ 326 | invert: function(options) { 327 | return this.process(function(r, g, b, a) { 328 | return [ 329 | 255 - r, 330 | 255 - g, 331 | 255 - b, 332 | a 333 | ]; 334 | }, options); 335 | }, 336 | 337 | /** 338 | * Find edges 339 | * 340 | * @param {obj} options 341 | * @return {array} r, g, b, a 342 | */ 343 | edges: function(options) { 344 | var tc = this; 345 | var rowShift = tc.w*4; 346 | return tc.process(function(r, g, b, a, x, y, i) { 347 | r = 127 + 2*r - tc.pixels[i + 4] - tc.pixels[i + rowShift]; 348 | g = 127 + 2*g - tc.pixels[i + 5] - tc.pixels[i+1 + rowShift]; 349 | b = 127 + 2*b - tc.pixels[i + 6] - tc.pixels[i+2 + rowShift]; 350 | 351 | return [r, g, b, a]; 352 | }, options); 353 | }, 354 | 355 | /** 356 | * Add randomized noise 357 | * 358 | * @param {obj} options 359 | * @return {array} r, g, b, a 360 | */ 361 | noise: function(options) { 362 | options = $.extend({}, { 363 | amount: 10, 364 | }, options); 365 | return this.process(function(r, g, b, a) { 366 | return [ 367 | r + (0.5 - Math.random()) * options.amount, 368 | g + (0.5 - Math.random()) * options.amount, 369 | b + (0.5 - Math.random()) * options.amount, 370 | a 371 | ]; 372 | }, options); 373 | }, 374 | 375 | /** 376 | * [pixelate description] 377 | * @param {obj} options 378 | * @return {array} r, g, b, a 379 | */ 380 | pixelate: function(options) { 381 | var tc = this; 382 | 383 | options = $.extend({}, { 384 | blockSize: 100, 385 | }, options); 386 | 387 | blockSize = options.blockSize; 388 | blockSize = 1/blockSize; 389 | 390 | var w = tc.w * blockSize, 391 | h = tc.h * blockSize; 392 | 393 | tc.context.mozImageSmoothingEnabled = false; 394 | tc.context.webkitImageSmoothingEnabled = false; 395 | tc.context.imageSmoothingEnabled = false; 396 | 397 | tc.context.drawImage(tc.canvas, 0, 0, w, h); 398 | 399 | // TODO cannot draw from canvas? 400 | tc.context.drawImage(tc.canvas, 0, 0, w, h, 0, 0, this.w, this.h); 401 | 402 | tc.imgdIn = tc.context.getImageData(0, 0, tc.w, tc.h); // get data from 2D context 403 | tc.pixelsIn = tc.imgdIn.data; 404 | tc.nrPixels = tc.pixelsIn.length; 405 | 406 | // make sure other effect get the updated data 407 | tc.imgdOut = tc.context.getImageData(0, 0, tc.w, tc.h); 408 | tc.pixelsOut = tc.imgdOut.data; 409 | 410 | tc.putImageData(); 411 | 412 | return tc; 413 | }, 414 | 415 | /** 416 | * -------------------------------------------------------------------------------- 417 | * CONVULUTION FILTERS 418 | * -------------------------------------------------------------------------------- 419 | */ 420 | 421 | /** 422 | * @param {obj} options 423 | * @return {array} r, g, b, a 424 | */ 425 | blur: function(options) { 426 | options = $.extend({}, { 427 | filter: [ 428 | [0.1, 0.1, 0.1], 429 | [0.1, 0.2, 0.1], 430 | [0.1, 0.1, 0.1] 431 | ] 432 | }, options); 433 | 434 | return this.convolutionFilter(options); 435 | }, 436 | 437 | /** 438 | * @param {obj} options 439 | * @return {array} r, g, b, a 440 | */ 441 | boxBlur: function(options) { 442 | options = $.extend({}, { 443 | radius: 3 444 | }, options); 445 | 446 | var radius = options.radius; 447 | var len = radius * radius; 448 | var val = 1 / len; 449 | var filter = []; 450 | 451 | for(var r = 0; r < radius; r++) { 452 | var row = []; 453 | for(var c = 0; c < radius; c++) { 454 | row.push(val); 455 | } 456 | filter.push(row); 457 | } 458 | 459 | options = $.extend({}, { 460 | filter: filter 461 | }, options); 462 | 463 | return this.convolutionFilter(options); 464 | }, 465 | 466 | /** 467 | * @param {obj} options 468 | * @return {array} r, g, b, a 469 | */ 470 | gaussianBlur: function(options) { 471 | options = $.extend({}, { 472 | radius: 3 473 | }, options); 474 | 475 | var radius = options.radius; 476 | var size = 2*radius+1; 477 | var sigma = radius/2; 478 | var sum = 0.0; // For accumulating the filter values 479 | var filter = []; 480 | for (var x = 0; x < size; ++x) { 481 | var row = []; 482 | for (var y = 0; y < size; ++y) { 483 | var col = this.gaussian(x, radius, sigma) * this.gaussian(y, radius, sigma); 484 | row.push(col); 485 | sum += col; 486 | } 487 | filter.push(row); 488 | } 489 | 490 | // Normalize the filter 491 | for (var x2 = 0; x2 < size; ++x2) { 492 | for (var y2 = 0; y2 < size; ++y2) { 493 | filter[x2][y2] /= sum; 494 | } 495 | } 496 | 497 | options = $.extend({}, { 498 | filter: filter 499 | }, options); 500 | 501 | return this.convolutionFilter(options); 502 | }, 503 | 504 | /** 505 | * @param {obj} options 506 | * @return {array} r, g, b, a 507 | */ 508 | sharpen: function(options) { 509 | return this.convolutionFilter($.extend({}, { 510 | filter: [ 511 | [ 0, -1, 0], 512 | [-1, 5, -1], 513 | [ 0, -1, 0] 514 | ] 515 | }, options)); 516 | }, 517 | 518 | /** 519 | * @param {obj} options 520 | * @return {array} r, g, b, a 521 | */ 522 | emboss: function(options) { 523 | options = $.extend({}, { 524 | offset: 127 525 | }, options); 526 | 527 | return this.convolutionFilter($.extend({}, { 528 | filter: [ 529 | [2, 0, 0], 530 | [0, -1, 0], 531 | [0, 0, -1] 532 | ] 533 | }, options)); 534 | }, 535 | 536 | /** 537 | * @param {obj} options 538 | * @return {array} r, g, b, a 539 | */ 540 | laplace: function(options) { 541 | return this.convolutionFilter($.extend({}, { 542 | filter: [ 543 | [0, 1, 0], 544 | [1, -4, 1], 545 | [0, 1, 0] 546 | ] 547 | }, options)); 548 | }, 549 | 550 | /** 551 | * @param {obj} options 552 | * @return {array} r, g, b, a 553 | */ 554 | sobel: function(options) { 555 | this.sobelVertical(options); 556 | this.putImageData(); 557 | return this.sobelHorizontal(options); 558 | }, 559 | 560 | /** 561 | * @param {obj} options 562 | * @return {array} r, g, b, a 563 | */ 564 | sobelVertical: function(options) { 565 | return this.convolutionFilter($.extend({}, { 566 | filter: [ 567 | [-1, 0, 1], 568 | [-2, 0, 2], 569 | [-1, 0, 1] 570 | ] 571 | }, options)); 572 | }, 573 | 574 | /** 575 | * @param {obj} options 576 | * @return {array} r, g, b, a 577 | */ 578 | sobelHorizontal: function(options) { 579 | return this.convolutionFilter($.extend({}, { 580 | filter: [ 581 | [-1, -2, -1], 582 | [ 0, 0, 0], 583 | [ 1, 2, 1] 584 | ] 585 | }, options)); 586 | }, 587 | 588 | /** 589 | * @param {obj} options 590 | * @return {array} r, g, b, a 591 | */ 592 | convolutionFilter: function(options) { 593 | options = $.extend({}, { 594 | updateR: true, 595 | updateG: true, 596 | updateB: true, 597 | offset: 0, 598 | filter: [1] 599 | }, options); 600 | 601 | var filter = options.filter; 602 | 603 | var tc = this; 604 | 605 | var rows = filter.length; // odd 606 | var cols = filter[0].length; // odd 607 | 608 | var rm = Math.floor(rows/2); // center of row (current pixel) 609 | var cm = Math.floor(cols/2); // center of column (current pixel) 610 | 611 | return tc.process(function(r, g, b, a, x, y, i) { 612 | 613 | var nR = options.updateR ? 0 : tc.pixelsIn[i ], 614 | nG = options.updateG ? 0 : tc.pixelsIn[i+1], 615 | nB = options.updateB ? 0 : tc.pixelsIn[i+2] 616 | ; 617 | 618 | for(var row = 0; row < rows; row++) { 619 | var rd = Math.abs(row-rm); 620 | var ri = (row < rm ? -rd : (row > rm ? +rd : 0)); 621 | 622 | var nY = Math.max(Math.min(y+ri, tc.h-1), 0); 623 | 624 | for(var col = 0; col < cols; col++) { 625 | var cd = Math.abs(col-cm); 626 | var ci = (col < cm ? -cd : (col > cm ? +cd : 0)); 627 | 628 | var nX = Math.max(Math.min(x+ci, tc.w-1), 0); 629 | var nI = 4*(nY * tc.w + nX); 630 | 631 | if(options.updateR) { nR += (filter[row][col] * tc.pixelsIn[nI ]); } 632 | if(options.updateG) { nG += (filter[row][col] * tc.pixelsIn[nI+1]); } 633 | if(options.updateB) { nB += (filter[row][col] * tc.pixelsIn[nI+2]); } 634 | } 635 | } 636 | return [options.offset + nR, options.offset + nG, options.offset + nB, a]; 637 | }, options); 638 | }, 639 | 640 | /** 641 | * -------------------------------------------------------------------------------- 642 | * OVERLAYS 643 | * -------------------------------------------------------------------------------- 644 | */ 645 | 646 | /** 647 | * Add a vignette 648 | * 649 | * @param {obj} options 650 | * @return {obj} this 651 | */ 652 | vignette: function(options) { 653 | options = $.extend( {}, { 654 | size: 0.5, 655 | opacity: 1 656 | }, options ); 657 | 658 | var outerRadius = Math.sqrt( Math.pow(this.w / 2, 2) + Math.pow(this.h / 2, 2) ); 659 | var gradient = this.context.createRadialGradient(this.w/2, this.h/2, 0, this.w/2, this.h/2, outerRadius); 660 | 661 | // write current data to image so we can overlay the vignette 662 | this.context.putImageData(this.imgdOut, 0, 0); 663 | this.context.globalCompositeOperation = "source-over"; 664 | gradient.addColorStop(0, "rgba(0,0,0,0)"); 665 | gradient.addColorStop(options.size, "rgba(0,0,0,0)"); 666 | gradient.addColorStop(1, "rgba(0,0,0,"+ options.opacity +")"); 667 | this.context.fillStyle = gradient; 668 | this.context.fillRect(0, 0, this.w, this.h); 669 | 670 | // make sure other effect get the updated data 671 | this.imgdOut = this.context.getImageData(0, 0, this.w, this.h); 672 | this.pixelsOut = this.imgdOut.data; 673 | 674 | return this; 675 | }, 676 | 677 | 678 | /** 679 | * -------------------------------------------------------------------------------- 680 | * ADJUSTMENTS 681 | * -------------------------------------------------------------------------------- 682 | */ 683 | 684 | /** 685 | * @param {obj} options 686 | * @return {array} r, g, b, a 687 | */ 688 | threshold: function(options) { 689 | options = $.extend({}, { 690 | threshold: 127, 691 | }, options); 692 | 693 | return this.process(function(r, g, b, a) { 694 | var v = (0.2126*r + 0.7152*g + 0.0722*b >= options.threshold) ? 255 : 0; 695 | return [ 696 | v, 697 | v, 698 | v, 699 | a 700 | ]; 701 | }, options); 702 | }, 703 | 704 | /** 705 | * @param {obj} options 706 | * @return {array} r, g, b, a 707 | */ 708 | hue: function(options) { 709 | return this.hslUpdate("h", options); 710 | }, 711 | 712 | /** 713 | * @param {obj} options 714 | * @return {array} r, g, b, a 715 | */ 716 | saturation: function(options) { 717 | return this.hslUpdate("s", options); 718 | }, 719 | 720 | /** 721 | * @param {obj} options 722 | * @return {array} r, g, b, a 723 | */ 724 | brightness: function(options) { 725 | return this.lightness(options); 726 | }, 727 | 728 | /** 729 | * @param {obj} options 730 | * @return {array} r, g, b, a 731 | */ 732 | lightness: function(options) { 733 | return this.hslUpdate("l", options); 734 | }, 735 | 736 | hslUpdate: function(axis, options) { 737 | var tc = this; 738 | options = $.extend( {}, { 739 | value: 0, 740 | colorize: false 741 | }, options ); 742 | 743 | if (options.value === 0 && options.colorize !== true) { 744 | return false; 745 | } 746 | 747 | var max = axis === "h" ? 360 : 100; 748 | 749 | return this.process(function(r, g, b, a) { 750 | var hsl = tc.rgb2hsl(r, g, b); 751 | 752 | if(options.colorize) { 753 | // colorize means, set the value to exaclty the value 754 | hsl[axis] = Math.min(Math.max(options.value/max, 0), 1); 755 | } else { 756 | // add the value to the current value 757 | if(axis === "h") { 758 | hsl[axis] = (hsl[axis]*max + options.value) % max; 759 | if(hsl[axis] < 0) { 760 | hsl[axis] = max - hsl[axis]; 761 | } 762 | hsl[axis] /= max; 763 | } else { 764 | hsl[axis] = Math.min(Math.max(hsl[axis] + options.value/max, 0), 1); 765 | } 766 | } 767 | 768 | var rgb = tc.hsl2rgb(hsl.h, hsl.s, hsl.l); 769 | 770 | return [ 771 | rgb.r, 772 | rgb.g, 773 | rgb.b, 774 | a 775 | ]; 776 | }, options); 777 | }, 778 | 779 | /** 780 | * @param {obj} options 781 | * @return {array} r, g, b, a 782 | */ 783 | contrast: function(options) { 784 | options = $.extend({}, { 785 | value: 10, 786 | }, options); 787 | 788 | var level = Math.pow((options.value + 100) / 100, 2); 789 | return this.process(function(r, g, b, a) { 790 | return [ 791 | ((r / 255 - 0.5) * level + 0.5) * 255, 792 | ((g / 255 - 0.5) * level + 0.5) * 255, 793 | ((b / 255 - 0.5) * level + 0.5) * 255, 794 | a 795 | ]; 796 | }, options); 797 | }, 798 | 799 | /** 800 | * @param {obj} options 801 | * @return {array} r, g, b, a 802 | */ 803 | gamma: function(options) { 804 | options = $.extend({}, { 805 | value: 1.5, 806 | }, options); 807 | 808 | return this.process(function(r, g, b, a) { 809 | return [ 810 | r * options.value, 811 | g * options.value, 812 | b * options.value, 813 | a 814 | ]; 815 | }, options); 816 | }, 817 | 818 | 819 | /** 820 | * -------------------------------------------------------------------------------- 821 | * HELPERS 822 | * -------------------------------------------------------------------------------- 823 | */ 824 | 825 | /** 826 | * Convert an RGB tuplet to HSL 827 | * @param {int} r Red component [0-255] 828 | * @param {int} g Green component [0-255] 829 | * @param {int} b Blue component [0-255] 830 | * @return {obj} HSL (object {h, s, l}) 831 | */ 832 | rgb2hsl: function(r, g, b) { 833 | r /= 255; 834 | g /= 255; 835 | b /= 255; 836 | var max = Math.max(r, g, b); 837 | var min = Math.min(r, g, b); 838 | var h, s, l = (max + min) / 2; 839 | 840 | if (max === min) { 841 | h = s = 0; 842 | } else { 843 | var d = max - min; 844 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 845 | switch (max) { 846 | case r: 847 | h = (g - b) / d + (g < b ? 6 : 0); 848 | break; 849 | 850 | case g: 851 | h = (b - r) / d + 2; 852 | break; 853 | 854 | case b: 855 | h = (r - g) / d + 4; 856 | break; 857 | } 858 | } 859 | h /= 6; 860 | return { 861 | h: h, 862 | s: s, 863 | l: l 864 | }; 865 | }, 866 | 867 | /** 868 | * Convert an HSL tuplet to RGB 869 | * @param {int} h Hue component [0-1] 870 | * @param {int} s Saturation component [0-1] 871 | * @param {int} l Lightness component [0-1] 872 | * @return {obj} RGB (object {r, g, b}) 873 | */ 874 | hsl2rgb: function(h, s, l) { 875 | var r, g, b; 876 | if (s === 0) { 877 | r = g = b = l; //gray value 878 | } else { 879 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 880 | var p = 2 * l - q; 881 | r = this.hue2rgb(p, q, h + 1 / 3); 882 | g = this.hue2rgb(p, q, h); 883 | b = this.hue2rgb(p, q, h - 1 / 3); 884 | } 885 | return { 886 | r: r * 255, 887 | g: g * 255, 888 | b: b * 255 889 | }; 890 | }, 891 | 892 | /** 893 | * Convert a hue to an RGB component 894 | * @param {int} p 895 | * @param {int} q 896 | * @param {int} t 897 | * @return {int} p 898 | */ 899 | hue2rgb: function(p, q, t) { 900 | if (t < 0) { t += 1; } 901 | if (t > 1) { t -= 1; } 902 | if (t < 1 / 6) { return p + (q - p) * 6 * t; } 903 | if (t < 1 / 2) { return q; } 904 | if (t < 2 / 3) { return p + (q - p) * (2 / 3 - t) * 6; } 905 | return p; 906 | }, 907 | 908 | /** 909 | * 1D Gaussian function 910 | * @param {float} x 911 | * @param {float} mu 912 | * @param {float} sigma 913 | * @return {float} gaussian 914 | */ 915 | gaussian: function(x, mu, sigma) { 916 | return Math.exp( -(((x-mu)/(sigma))*((x-mu)/(sigma)))/2.0 ); 917 | }, 918 | }); 919 | 920 | // A really lightweight plugin wrapper around the constructor, 921 | // preventing against multiple instantiations 922 | $.fn[ pluginName ] = function ( options ) { 923 | var plugin; 924 | var canvas2DSupported = !!window.CanvasRenderingContext2D; 925 | if(!canvas2DSupported) { 926 | return false; 927 | } 928 | 929 | this.each(function() { 930 | plugin = $.data(this, "plugin_" + pluginName); 931 | if (!plugin) { 932 | plugin = new Plugin(this, options); 933 | $.data(this, "plugin_" + pluginName, plugin); 934 | } 935 | }); 936 | return plugin; 937 | }; 938 | 939 | })( jQuery, window, document ); 940 | --------------------------------------------------------------------------------