├── .editorconfig ├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── build ├── jsfx.js └── jsfx.min.js ├── exports.ts ├── gulpfile.js ├── package.json ├── src ├── canvas │ └── renderer.ts ├── filter │ ├── blur.ts │ ├── brightness.ts │ ├── colorHalfTone.ts │ ├── contrast.ts │ ├── curves.ts │ ├── denoise.ts │ ├── dotScreen.ts │ ├── filter.ts │ ├── filterInterface.ts │ ├── hue.ts │ ├── iterableFilter.ts │ ├── iterableFilterInterface.ts │ ├── multiply.ts │ ├── noise.ts │ ├── saturation.ts │ ├── sepia.ts │ ├── unsharpMask.ts │ ├── vibrance.ts │ └── vignette.ts ├── renderer.ts ├── rendererInterface.ts ├── source.ts ├── util │ ├── imageDataHelper.ts │ ├── splineInterpolator.ts │ ├── vector2.ts │ └── vector3.ts └── webgl │ ├── renderer.ts │ ├── shader.ts │ └── texture.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hussein Jafferjee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version][npm-badge]][npm-url] 2 | [![Download Status][download-badge]][npm-url] 3 | [![Dependency Status][dep-badge]][dep-url] 4 | 5 | # jsfx 6 | 7 | An image effects library, heavily inspired by https://github.com/evanw/glfx.js. I needed something that could fallback 8 | to a canvas, and additionally, I needed the same effects to render server side. 9 | 10 | This library is currently in heavy development, and not tested. You should probably refrain from using it in production 11 | until it becomes stable. 12 | 13 | Demo: http://jsfx.inssein.com 14 | 15 | # todo 16 | 17 | * Get feedback on usage of Typescript (the way files are separated, lack of Uint8TypedArray in UnsharpMask, etc). 18 | * Figure out how to have certain static variables per WebGL Rendering context. I am currently casting to any, and 19 | assigning a variable. (vertexBuffer and texCoordBuffer in Shader, frameBuffer in Texture, and shaderCache in Renderer) 20 | * Add more filters 21 | * A lot of the filter files have the comments and the webgl shaders copied from glfx.js. I should probably attribute the 22 | single files as well as mentioning it in the credits. 23 | * Add tests 24 | * Test server side rendering with node-canvas, and add documentation 25 | 26 | # future 27 | * Currently, only the Canvas filters take advantage of IterableFilter. Perhaps implement something similar for WebGL 28 | whereby shaders for a combination of filters are generated, and applied once. This is not very important as these 29 | filters are fairly light weight, and the WebGL implementation is already considered much faster (relative to Canvas). 30 | 31 | # credits 32 | * https://github.com/evanw/glfx.js 33 | 34 | # license 35 | 36 | MIT © [Hussein Jafferjee][author] 37 | 38 | [author]: https://github.com/inssein 39 | [npm-url]: https://npmjs.org/package/jsfx 40 | [npm-badge]: https://img.shields.io/npm/v/jsfx.svg?style=flat-square 41 | [dep-url]: https://david-dm.org/inssein/generator-rise 42 | [dep-badge]: https://david-dm.org/inssein/jsfx.svg?style=flat-square 43 | [download-badge]: http://img.shields.io/npm/dm/jsfx.svg?style=flat-square 44 | -------------------------------------------------------------------------------- /build/jsfx.js: -------------------------------------------------------------------------------- 1 | /// 2 | var jsfx; 3 | (function (jsfx) { 4 | var filter; 5 | (function (filter) { 6 | var Filter = (function () { 7 | function Filter(vertexSource, fragmentSource) { 8 | if (vertexSource === void 0) { vertexSource = null; } 9 | if (fragmentSource === void 0) { fragmentSource = null; } 10 | this.vertexSource = vertexSource; 11 | this.fragmentSource = fragmentSource; 12 | this.properties = {}; 13 | } 14 | Filter.prototype.getProperties = function () { 15 | return this.properties; 16 | }; 17 | Filter.prototype.drawCanvas = function (renderer) { 18 | throw new Error("Must be implemented"); 19 | }; 20 | Filter.prototype.drawWebGL = function (renderer) { 21 | var shader = renderer.getShader(this); 22 | var properties = this.getProperties(); 23 | renderer.getTexture().use(); 24 | renderer.getNextTexture().drawTo(function () { 25 | shader.uniforms(properties).drawRect(); 26 | }); 27 | }; 28 | Filter.prototype.getVertexSource = function () { 29 | return this.vertexSource; 30 | }; 31 | Filter.prototype.getFragmentSource = function () { 32 | return this.fragmentSource; 33 | }; 34 | Filter.clamp = function (low, value, high) { 35 | return Math.max(low, Math.min(value, high)); 36 | }; 37 | return Filter; 38 | })(); 39 | filter.Filter = Filter; 40 | })(filter = jsfx.filter || (jsfx.filter = {})); 41 | })(jsfx || (jsfx = {})); 42 | var __extends = (this && this.__extends) || function (d, b) { 43 | for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; 44 | function __() { this.constructor = d; } 45 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 46 | }; 47 | var jsfx; 48 | (function (jsfx) { 49 | var filter; 50 | (function (filter_1) { 51 | var IterableFilter = (function (_super) { 52 | __extends(IterableFilter, _super); 53 | function IterableFilter() { 54 | _super.apply(this, arguments); 55 | } 56 | IterableFilter.prototype.drawCanvas = function (renderer) { 57 | return IterableFilter.drawCanvas([this], renderer); 58 | }; 59 | IterableFilter.prototype.iterateCanvas = function (imageData) { 60 | throw new Error("Must be implemented"); 61 | }; 62 | IterableFilter.drawCanvas = function (filters, renderer) { 63 | var helper; 64 | var imageData = renderer.getImageData(); 65 | for (var i = 0; i < imageData.data.length; i += 4) { 66 | helper = new jsfx.util.ImageDataHelper(imageData, i); 67 | filters.forEach(function (filter) { 68 | filter.iterateCanvas(helper); 69 | }); 70 | helper.save(); 71 | } 72 | }; 73 | return IterableFilter; 74 | })(filter_1.Filter); 75 | filter_1.IterableFilter = IterableFilter; 76 | })(filter = jsfx.filter || (jsfx.filter = {})); 77 | })(jsfx || (jsfx = {})); 78 | var jsfx; 79 | (function (jsfx) { 80 | var canvas; 81 | (function (canvas) { 82 | var Renderer = (function () { 83 | function Renderer() { 84 | this.canvas = jsfx.canvas.Renderer.createCanvas(); 85 | this.ctx = this.canvas.getContext("2d"); 86 | this.source = null; 87 | this.imageData = null; 88 | } 89 | Renderer.prototype.setSource = function (source) { 90 | if (this.source) { 91 | this.cleanUp(); 92 | } 93 | this.source = source; 94 | this.canvas.width = source.width; 95 | this.canvas.height = source.height; 96 | this.ctx.drawImage(source.element, 0, 0, source.width, source.height); 97 | this.imageData = this.ctx.getImageData(0, 0, source.width, source.height); 98 | return this; 99 | }; 100 | Renderer.prototype.getSource = function () { 101 | return this.source; 102 | }; 103 | Renderer.prototype.applyFilter = function (filter) { 104 | filter.drawCanvas(this); 105 | return this; 106 | }; 107 | Renderer.prototype.applyFilters = function (filters) { 108 | var stack = []; 109 | var filter; 110 | for (var i = 0; i < filters.length; i++) { 111 | filter = filters[i]; 112 | if (filter instanceof jsfx.filter.IterableFilter) { 113 | stack.push(filter); 114 | } 115 | else { 116 | if (stack.length > 0) { 117 | this.applyFilterStack(stack); 118 | stack = []; 119 | } 120 | this.applyFilter(filter); 121 | } 122 | } 123 | if (stack.length > 0) { 124 | this.applyFilterStack(stack); 125 | } 126 | return this; 127 | }; 128 | Renderer.prototype.render = function () { 129 | this.ctx.putImageData(this.imageData, 0, 0); 130 | }; 131 | Renderer.prototype.getCanvas = function () { 132 | return this.canvas; 133 | }; 134 | Renderer.prototype.getContext = function () { 135 | return this.ctx; 136 | }; 137 | Renderer.prototype.getImageData = function () { 138 | return this.imageData; 139 | }; 140 | Renderer.prototype.setImageData = function (v) { 141 | this.imageData = v; 142 | }; 143 | Renderer.prototype.applyFilterStack = function (stack) { 144 | jsfx.filter.IterableFilter.drawCanvas(stack, this); 145 | return this; 146 | }; 147 | Renderer.prototype.cleanUp = function () { 148 | this.imageData = null; 149 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 150 | }; 151 | Renderer.createCanvas = function () { 152 | return typeof Buffer !== "undefined" && typeof window === "undefined" ? 153 | new (require("canvas"))(100, 100) : 154 | document.createElement("canvas"); 155 | }; 156 | return Renderer; 157 | })(); 158 | canvas.Renderer = Renderer; 159 | })(canvas = jsfx.canvas || (jsfx.canvas = {})); 160 | })(jsfx || (jsfx = {})); 161 | var jsfx; 162 | (function (jsfx) { 163 | var filter; 164 | (function (filter) { 165 | var Blur = (function (_super) { 166 | __extends(Blur, _super); 167 | function Blur(radius) { 168 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform vec2 delta;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = vec4(0.0);\n float total = 0.0;\n\n /* randomize the lookup values to hide the fixed number of samples */\n //float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0);\n\n vec3 scale = vec3(12.9898, 78.233, 151.7182);\n float offset = fract(sin(dot(gl_FragCoord.xyz + 0.0, scale)) * 43758.5453 + 0.0);\n\n for (float t = -30.0; t <= 30.0; t++) {\n float percent = (t + offset - 0.5) / 30.0;\n float weight = 1.0 - abs(percent);\n vec4 sample = texture2D(texture, texCoord + delta * percent);\n\n /* switch to pre-multiplied alpha to correctly blur transparent images */\n sample.rgb *= sample.a;\n\n color += sample * weight;\n total += weight;\n }\n\n gl_FragColor = color / total;\n\n /* switch back from pre-multiplied alpha */\n gl_FragColor.rgb /= gl_FragColor.a + 0.00001;\n }\n "); 169 | this.properties.radius = radius; 170 | } 171 | Blur.prototype.drawWebGL = function (renderer) { 172 | var shader = renderer.getShader(this); 173 | var firstPass = { delta: [this.properties.radius / renderer.getSource().width, 0] }; 174 | var secondPass = { delta: [0, this.properties.radius / renderer.getSource().height] }; 175 | renderer.getTexture().use(); 176 | renderer.getNextTexture().drawTo(function () { 177 | shader.uniforms(firstPass).drawRect(); 178 | }); 179 | renderer.getTexture().use(); 180 | renderer.getNextTexture().drawTo(function () { 181 | shader.uniforms(secondPass).drawRect(); 182 | }); 183 | }; 184 | Blur.prototype.drawCanvas = function (renderer) { 185 | var imageData = renderer.getImageData(); 186 | var pixels = imageData.data; 187 | var radius = this.properties.radius; 188 | var width = imageData.width; 189 | var height = imageData.height; 190 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, r_out_sum, g_out_sum, b_out_sum, a_out_sum, r_in_sum, g_in_sum, b_in_sum, a_in_sum, pr, pg, pb, pa, rbs; 191 | var div = radius + radius + 1; 192 | var widthMinus1 = width - 1; 193 | var heightMinus1 = height - 1; 194 | var radiusPlus1 = radius + 1; 195 | var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2; 196 | var stackStart = new BlurStack(); 197 | var stack = stackStart; 198 | for (i = 1; i < div; i++) { 199 | stack = stack.next = new BlurStack(); 200 | if (i == radiusPlus1) 201 | var stackEnd = stack; 202 | } 203 | stack.next = stackStart; 204 | var stackIn = null; 205 | var stackOut = null; 206 | yw = yi = 0; 207 | var mul_sum = mul_table[radius]; 208 | var shg_sum = shg_table[radius]; 209 | for (y = 0; y < height; y++) { 210 | r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0; 211 | r_out_sum = radiusPlus1 * (pr = pixels[yi]); 212 | g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); 213 | b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); 214 | a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); 215 | r_sum += sumFactor * pr; 216 | g_sum += sumFactor * pg; 217 | b_sum += sumFactor * pb; 218 | a_sum += sumFactor * pa; 219 | stack = stackStart; 220 | for (i = 0; i < radiusPlus1; i++) { 221 | stack.r = pr; 222 | stack.g = pg; 223 | stack.b = pb; 224 | stack.a = pa; 225 | stack = stack.next; 226 | } 227 | for (i = 1; i < radiusPlus1; i++) { 228 | p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2); 229 | r_sum += (stack.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i); 230 | g_sum += (stack.g = (pg = pixels[p + 1])) * rbs; 231 | b_sum += (stack.b = (pb = pixels[p + 2])) * rbs; 232 | a_sum += (stack.a = (pa = pixels[p + 3])) * rbs; 233 | r_in_sum += pr; 234 | g_in_sum += pg; 235 | b_in_sum += pb; 236 | a_in_sum += pa; 237 | stack = stack.next; 238 | } 239 | stackIn = stackStart; 240 | stackOut = stackEnd; 241 | for (x = 0; x < width; x++) { 242 | pixels[yi + 3] = pa = (a_sum * mul_sum) >> shg_sum; 243 | if (pa != 0) { 244 | pa = 255 / pa; 245 | pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa; 246 | pixels[yi + 1] = ((g_sum * mul_sum) >> shg_sum) * pa; 247 | pixels[yi + 2] = ((b_sum * mul_sum) >> shg_sum) * pa; 248 | } 249 | else { 250 | pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0; 251 | } 252 | r_sum -= r_out_sum; 253 | g_sum -= g_out_sum; 254 | b_sum -= b_out_sum; 255 | a_sum -= a_out_sum; 256 | r_out_sum -= stackIn.r; 257 | g_out_sum -= stackIn.g; 258 | b_out_sum -= stackIn.b; 259 | a_out_sum -= stackIn.a; 260 | p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2; 261 | r_in_sum += (stackIn.r = pixels[p]); 262 | g_in_sum += (stackIn.g = pixels[p + 1]); 263 | b_in_sum += (stackIn.b = pixels[p + 2]); 264 | a_in_sum += (stackIn.a = pixels[p + 3]); 265 | r_sum += r_in_sum; 266 | g_sum += g_in_sum; 267 | b_sum += b_in_sum; 268 | a_sum += a_in_sum; 269 | stackIn = stackIn.next; 270 | r_out_sum += (pr = stackOut.r); 271 | g_out_sum += (pg = stackOut.g); 272 | b_out_sum += (pb = stackOut.b); 273 | a_out_sum += (pa = stackOut.a); 274 | r_in_sum -= pr; 275 | g_in_sum -= pg; 276 | b_in_sum -= pb; 277 | a_in_sum -= pa; 278 | stackOut = stackOut.next; 279 | yi += 4; 280 | } 281 | yw += width; 282 | } 283 | for (x = 0; x < width; x++) { 284 | g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0; 285 | yi = x << 2; 286 | r_out_sum = radiusPlus1 * (pr = pixels[yi]); 287 | g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]); 288 | b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]); 289 | a_out_sum = radiusPlus1 * (pa = pixels[yi + 3]); 290 | r_sum += sumFactor * pr; 291 | g_sum += sumFactor * pg; 292 | b_sum += sumFactor * pb; 293 | a_sum += sumFactor * pa; 294 | stack = stackStart; 295 | for (i = 0; i < radiusPlus1; i++) { 296 | stack.r = pr; 297 | stack.g = pg; 298 | stack.b = pb; 299 | stack.a = pa; 300 | stack = stack.next; 301 | } 302 | yp = width; 303 | for (i = 1; i <= radius; i++) { 304 | yi = (yp + x) << 2; 305 | r_sum += (stack.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i); 306 | g_sum += (stack.g = (pg = pixels[yi + 1])) * rbs; 307 | b_sum += (stack.b = (pb = pixels[yi + 2])) * rbs; 308 | a_sum += (stack.a = (pa = pixels[yi + 3])) * rbs; 309 | r_in_sum += pr; 310 | g_in_sum += pg; 311 | b_in_sum += pb; 312 | a_in_sum += pa; 313 | stack = stack.next; 314 | if (i < heightMinus1) { 315 | yp += width; 316 | } 317 | } 318 | yi = x; 319 | stackIn = stackStart; 320 | stackOut = stackEnd; 321 | for (y = 0; y < height; y++) { 322 | p = yi << 2; 323 | pixels[p + 3] = pa = (a_sum * mul_sum) >> shg_sum; 324 | if (pa > 0) { 325 | pa = 255 / pa; 326 | pixels[p] = ((r_sum * mul_sum) >> shg_sum) * pa; 327 | pixels[p + 1] = ((g_sum * mul_sum) >> shg_sum) * pa; 328 | pixels[p + 2] = ((b_sum * mul_sum) >> shg_sum) * pa; 329 | } 330 | else { 331 | pixels[p] = pixels[p + 1] = pixels[p + 2] = 0; 332 | } 333 | r_sum -= r_out_sum; 334 | g_sum -= g_out_sum; 335 | b_sum -= b_out_sum; 336 | a_sum -= a_out_sum; 337 | r_out_sum -= stackIn.r; 338 | g_out_sum -= stackIn.g; 339 | b_out_sum -= stackIn.b; 340 | a_out_sum -= stackIn.a; 341 | p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2; 342 | r_sum += (r_in_sum += (stackIn.r = pixels[p])); 343 | g_sum += (g_in_sum += (stackIn.g = pixels[p + 1])); 344 | b_sum += (b_in_sum += (stackIn.b = pixels[p + 2])); 345 | a_sum += (a_in_sum += (stackIn.a = pixels[p + 3])); 346 | stackIn = stackIn.next; 347 | r_out_sum += (pr = stackOut.r); 348 | g_out_sum += (pg = stackOut.g); 349 | b_out_sum += (pb = stackOut.b); 350 | a_out_sum += (pa = stackOut.a); 351 | r_in_sum -= pr; 352 | g_in_sum -= pg; 353 | b_in_sum -= pb; 354 | a_in_sum -= pa; 355 | stackOut = stackOut.next; 356 | yi += width; 357 | } 358 | } 359 | }; 360 | return Blur; 361 | })(filter.Filter); 362 | filter.Blur = Blur; 363 | var mul_table = [ 364 | 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 365 | 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 366 | 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 367 | 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 368 | 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 369 | 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 370 | 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 371 | 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 372 | 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 373 | 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 374 | 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 375 | 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 376 | 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 377 | 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 378 | 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, 379 | 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259]; 380 | var shg_table = [ 381 | 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 382 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 383 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 384 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 385 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 386 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 387 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 388 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 389 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 390 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 391 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 392 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 393 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 394 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 395 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 396 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24]; 397 | var BlurStack = (function () { 398 | function BlurStack() { 399 | this.r = 0; 400 | this.g = 0; 401 | this.b = 0; 402 | this.a = 0; 403 | this.next = null; 404 | } 405 | return BlurStack; 406 | })(); 407 | })(filter = jsfx.filter || (jsfx.filter = {})); 408 | })(jsfx || (jsfx = {})); 409 | var jsfx; 410 | (function (jsfx) { 411 | var filter; 412 | (function (filter) { 413 | var Brightness = (function (_super) { 414 | __extends(Brightness, _super); 415 | function Brightness(brightness) { 416 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float brightness;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n color.rgb += brightness;\n\n gl_FragColor = color;\n }\n "); 417 | this.properties.brightness = filter.Filter.clamp(-1, brightness, 1) || 0; 418 | } 419 | Brightness.prototype.iterateCanvas = function (helper) { 420 | var brightness = this.properties.brightness; 421 | helper.r += brightness; 422 | helper.g += brightness; 423 | helper.b += brightness; 424 | }; 425 | return Brightness; 426 | })(filter.IterableFilter); 427 | filter.Brightness = Brightness; 428 | })(filter = jsfx.filter || (jsfx.filter = {})); 429 | })(jsfx || (jsfx = {})); 430 | var jsfx; 431 | (function (jsfx) { 432 | var filter; 433 | (function (filter) { 434 | var ColorHalfTone = (function (_super) { 435 | __extends(ColorHalfTone, _super); 436 | function ColorHalfTone(centerX, centerY, angle, size) { 437 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform vec2 center;\n uniform float angle;\n uniform float scale;\n uniform vec2 texSize;\n varying vec2 texCoord;\n\n float pattern(float angle) {\n float s = sin(angle), c = cos(angle);\n vec2 tex = texCoord * texSize - center;\n vec2 point = vec2(\n c * tex.x - s * tex.y,\n s * tex.x + c * tex.y\n ) * scale;\n\n return (sin(point.x) * sin(point.y)) * 4.0;\n }\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n vec3 cmy = 1.0 - color.rgb;\n float k = min(cmy.x, min(cmy.y, cmy.z));\n cmy = (cmy - k) / (1.0 - k);\n cmy = clamp(cmy * 10.0 - 3.0 + vec3(pattern(angle + 0.26179), pattern(angle + 1.30899), pattern(angle)), 0.0, 1.0);\n k = clamp(k * 10.0 - 5.0 + pattern(angle + 0.78539), 0.0, 1.0);\n gl_FragColor = vec4(1.0 - cmy - k, color.a);\n }\n "); 438 | this.centerX = centerX; 439 | this.centerY = centerY; 440 | this.properties.angle = filter.Filter.clamp(0, angle, Math.PI / 2); 441 | this.properties.scale = Math.PI / size; 442 | } 443 | ColorHalfTone.prototype.drawWebGL = function (renderer) { 444 | var shader = renderer.getShader(this); 445 | var properties = this.getProperties(); 446 | properties.texSize = [renderer.getSource().width, renderer.getSource().width]; 447 | properties.center = [this.centerX, this.centerY]; 448 | renderer.getTexture().use(); 449 | renderer.getNextTexture().drawTo(function () { 450 | shader.uniforms(properties).drawRect(); 451 | }); 452 | }; 453 | ColorHalfTone.pattern = function (angle, x, y, centerX, centerY, scale) { 454 | var s = Math.sin(angle); 455 | var c = Math.cos(angle); 456 | var tX = x - centerX; 457 | var tY = y - centerY; 458 | return (Math.sin((c * tX - s * tY) * scale) * Math.sin((s * tX + c * tY) * scale)) * 4; 459 | }; 460 | ColorHalfTone.prototype.iterateCanvas = function (helper) { 461 | var _this = this; 462 | var angle = this.properties.angle; 463 | var imageData = helper.getImageData(); 464 | var x = (helper.getIndex() / 4) % imageData.width; 465 | var y = Math.floor((helper.getIndex() / 4) / imageData.width); 466 | var pattern = function (angle) { 467 | return ColorHalfTone.pattern(angle, x, y, _this.centerX, _this.centerY, _this.properties.scale); 468 | }; 469 | var r = 1 - helper.r; 470 | var g = 1 - helper.g; 471 | var b = 1 - helper.b; 472 | var k = Math.min(r, Math.min(g, b)); 473 | r = (r - k) / (1 - k); 474 | g = (g - k) / (1 - k); 475 | b = (b - k) / (1 - k); 476 | r = filter.Filter.clamp(0, r * 10 - 3 + pattern(angle + 0.26179), 1); 477 | g = filter.Filter.clamp(0, g * 10 - 3 + pattern(angle + 1.30899), 1); 478 | b = filter.Filter.clamp(0, b * 10 - 3 + pattern(angle), 1); 479 | k = filter.Filter.clamp(0, k * 10 - 5 + pattern(angle + 0.78539), 1); 480 | helper.r = 1 - r - k; 481 | helper.g = 1 - g - k; 482 | helper.b = 1 - b - k; 483 | }; 484 | return ColorHalfTone; 485 | })(filter.IterableFilter); 486 | filter.ColorHalfTone = ColorHalfTone; 487 | })(filter = jsfx.filter || (jsfx.filter = {})); 488 | })(jsfx || (jsfx = {})); 489 | var jsfx; 490 | (function (jsfx) { 491 | var filter; 492 | (function (filter) { 493 | var Contrast = (function (_super) { 494 | __extends(Contrast, _super); 495 | function Contrast(contrast) { 496 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float contrast;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n\n if (contrast > 0.0) {\n color.rgb = (color.rgb - 0.5) / (1.0 - contrast) + 0.5;\n } else {\n color.rgb = (color.rgb - 0.5) * (1.0 + contrast) + 0.5;\n }\n\n gl_FragColor = color;\n }\n "); 497 | this.properties.contrast = filter.Filter.clamp(-1, contrast, 1) || 0; 498 | } 499 | Contrast.prototype.iterateCanvas = function (helper) { 500 | var contrast = this.properties.contrast; 501 | if (contrast > 0) { 502 | helper.r = (helper.r - 0.5) / (1 - contrast) + 0.5; 503 | helper.g = (helper.g - 0.5) / (1 - contrast) + 0.5; 504 | helper.b = (helper.b - 0.5) / (1 - contrast) + 0.5; 505 | } 506 | else { 507 | helper.r = (helper.r - 0.5) * (1 + contrast) + 0.5; 508 | helper.g = (helper.g - 0.5) * (1 + contrast) + 0.5; 509 | helper.b = (helper.b - 0.5) * (1 + contrast) + 0.5; 510 | } 511 | }; 512 | return Contrast; 513 | })(filter.IterableFilter); 514 | filter.Contrast = Contrast; 515 | })(filter = jsfx.filter || (jsfx.filter = {})); 516 | })(jsfx || (jsfx = {})); 517 | var jsfx; 518 | (function (jsfx) { 519 | var filter; 520 | (function (filter) { 521 | var Curves = (function (_super) { 522 | __extends(Curves, _super); 523 | function Curves(red, green, blue) { 524 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform sampler2D map;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n color.r = texture2D(map, vec2(color.r)).r;\n color.g = texture2D(map, vec2(color.g)).g;\n color.b = texture2D(map, vec2(color.b)).b;\n gl_FragColor = color;\n }\n "); 525 | this.red = red; 526 | this.green = green; 527 | this.blue = blue; 528 | red = Curves.splineInterpolate(red); 529 | if (arguments.length == 1) { 530 | green = blue = red; 531 | } 532 | else { 533 | green = Curves.splineInterpolate(green); 534 | blue = Curves.splineInterpolate(blue); 535 | } 536 | this.red = red; 537 | this.green = green; 538 | this.blue = blue; 539 | } 540 | Curves.prototype.drawWebGL = function (renderer) { 541 | var array = []; 542 | for (var i = 0; i < 256; i++) { 543 | array.splice(array.length, 0, this.red[i], this.green[i], this.blue[i], 255); 544 | } 545 | var extraTexture = renderer.createTexture(); 546 | extraTexture.initFromBytes(256, 1, array); 547 | extraTexture.use(1); 548 | var shader = renderer.getShader(this); 549 | shader.textures({ 550 | map: 1 551 | }); 552 | renderer.getTexture().use(); 553 | renderer.getNextTexture().drawTo(function () { 554 | shader.uniforms({}).drawRect(); 555 | }); 556 | }; 557 | Curves.prototype.iterateCanvas = function (helper) { 558 | var i = helper.getIndex(); 559 | helper.r = this.red[helper.r * 255] / 255; 560 | helper.g = this.green[helper.g * 255] / 255; 561 | helper.b = this.blue[helper.b * 255] / 255; 562 | }; 563 | Curves.splineInterpolate = function (points) { 564 | var interpolator = new jsfx.util.SplineInterpolator(points); 565 | var array = []; 566 | for (var i = 0; i < 256; i++) { 567 | array.push(filter.Filter.clamp(0, Math.floor(interpolator.interpolate(i / 255) * 256), 255)); 568 | } 569 | return array; 570 | }; 571 | return Curves; 572 | })(filter.IterableFilter); 573 | filter.Curves = Curves; 574 | })(filter = jsfx.filter || (jsfx.filter = {})); 575 | })(jsfx || (jsfx = {})); 576 | var jsfx; 577 | (function (jsfx) { 578 | var filter; 579 | (function (filter) { 580 | var Denoise = (function (_super) { 581 | __extends(Denoise, _super); 582 | function Denoise(exponent) { 583 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float exponent;\n uniform float strength;\n uniform vec2 texSize;\n varying vec2 texCoord;\n\n void main() {\n vec4 center = texture2D(texture, texCoord);\n vec4 color = vec4(0.0);\n float total = 0.0;\n\n for (float x = -4.0; x <= 4.0; x += 1.0) {\n for (float y = -4.0; y <= 4.0; y += 1.0) {\n vec4 sample = texture2D(texture, texCoord + vec2(x, y) / texSize);\n float weight = 1.0 - abs(dot(sample.rgb - center.rgb, vec3(0.25)));\n weight = pow(weight, exponent);\n color += sample * weight;\n total += weight;\n }\n }\n\n gl_FragColor = color / total;\n }\n "); 584 | this.properties.exponent = exponent; 585 | } 586 | Denoise.prototype.drawWebGL = function (renderer) { 587 | var shader = renderer.getShader(this); 588 | var properties = this.getProperties(); 589 | properties.texSize = [renderer.getSource().width, renderer.getSource().width]; 590 | renderer.getTexture().use(); 591 | renderer.getNextTexture().drawTo(function () { 592 | shader.uniforms(properties).drawRect(); 593 | }); 594 | }; 595 | Denoise.prototype.drawCanvas = function (renderer) { 596 | var exponent = this.properties.exponent; 597 | var imageData = renderer.getImageData(); 598 | var pixels = imageData.data; 599 | var original = new Uint8ClampedArray(imageData.data); 600 | var x, y, dstOff, color, total, cx, cy, scx, scy, srcOff, weight; 601 | for (x = 0; x < imageData.width; x++) { 602 | for (y = 0; y < imageData.height; y++) { 603 | dstOff = (y * imageData.width + x) * 4; 604 | color = [0, 0, 0, 0]; 605 | total = 0; 606 | for (cx = -4; cx <= 4; cx += 1) { 607 | for (cy = -4; cy <= 4; cy += 1) { 608 | scx = Math.min(imageData.width - 1, Math.max(0, x + cx)); 609 | scy = Math.min(imageData.height - 1, Math.max(0, y + cy)); 610 | srcOff = (scx + scy * imageData.width) * 4; 611 | weight = Math.pow(1.0 - Math.abs((original[srcOff] / 255 - original[dstOff] / 255) * 0.25 612 | + (original[srcOff + 1] / 255 - original[dstOff + 1] / 255) * 0.25 613 | + (original[srcOff + 2] / 255 - original[dstOff + 2] / 255) * 0.25), exponent); 614 | color[0] += original[srcOff] / 255 * weight; 615 | color[1] += original[srcOff + 1] / 255 * weight; 616 | color[2] += original[srcOff + 2] / 255 * weight; 617 | color[3] += original[srcOff + 3] / 255 * weight; 618 | total += weight; 619 | } 620 | } 621 | pixels[dstOff] = (color[0] / total) * 255; 622 | pixels[dstOff + 1] = (color[1] / total) * 255; 623 | pixels[dstOff + 2] = (color[2] / total) * 255; 624 | pixels[dstOff + 3] = (color[3] / total) * 255; 625 | } 626 | } 627 | }; 628 | return Denoise; 629 | })(filter.Filter); 630 | filter.Denoise = Denoise; 631 | })(filter = jsfx.filter || (jsfx.filter = {})); 632 | })(jsfx || (jsfx = {})); 633 | var jsfx; 634 | (function (jsfx) { 635 | var filter; 636 | (function (filter) { 637 | var DotScreen = (function (_super) { 638 | __extends(DotScreen, _super); 639 | function DotScreen(centerX, centerY, angle, size) { 640 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform vec2 center;\n uniform float angle;\n uniform float scale;\n uniform vec2 texSize;\n varying vec2 texCoord;\n\n float pattern() { float s = sin(angle), c = cos(angle);\n vec2 tex = texCoord * texSize - center;\n vec2 point = vec2(\n c * tex.x - s * tex.y,\n s * tex.x + c * tex.y\n ) * scale;\n\n return (sin(point.x) * sin(point.y)) * 4.0;\n }\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n float average = (color.r + color.g + color.b) / 3.0;\n gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n }\n "); 641 | this.centerX = centerX; 642 | this.centerY = centerY; 643 | this.properties.angle = filter.Filter.clamp(0, angle, Math.PI / 2); 644 | this.properties.scale = Math.PI / size; 645 | } 646 | DotScreen.prototype.drawWebGL = function (renderer) { 647 | var shader = renderer.getShader(this); 648 | var properties = this.getProperties(); 649 | properties.texSize = [renderer.getSource().width, renderer.getSource().width]; 650 | properties.center = [this.centerX, this.centerY]; 651 | renderer.getTexture().use(); 652 | renderer.getNextTexture().drawTo(function () { 653 | shader.uniforms(properties).drawRect(); 654 | }); 655 | }; 656 | DotScreen.prototype.iterateCanvas = function (helper) { 657 | var imageData = helper.getImageData(); 658 | var x = (helper.getIndex() / 4) % imageData.width; 659 | var y = Math.floor((helper.getIndex() / 4) / imageData.width); 660 | var average = (helper.r + helper.g + helper.b) / 3; 661 | var pattern = filter.ColorHalfTone.pattern(this.properties.angle, x, y, this.centerX, this.centerY, this.properties.scale); 662 | var value = average * 10 - 5 + pattern; 663 | helper.r = value; 664 | helper.g = value; 665 | helper.b = value; 666 | }; 667 | return DotScreen; 668 | })(filter.IterableFilter); 669 | filter.DotScreen = DotScreen; 670 | })(filter = jsfx.filter || (jsfx.filter = {})); 671 | })(jsfx || (jsfx = {})); 672 | var jsfx; 673 | (function (jsfx) { 674 | var filter; 675 | (function (filter) { 676 | var Hue = (function (_super) { 677 | __extends(Hue, _super); 678 | function Hue(hue) { 679 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float hue;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n\n /* hue adjustment, wolfram alpha: RotationTransform[angle, {1, 1, 1}][{x, y, z}] */\n float angle = hue * 3.14159265;\n float s = sin(angle), c = cos(angle);\n vec3 weights = (vec3(2.0 * c, -sqrt(3.0) * s - c, sqrt(3.0) * s - c) + 1.0) / 3.0;\n color.rgb = vec3(\n dot(color.rgb, weights.xyz),\n dot(color.rgb, weights.zxy),\n dot(color.rgb, weights.yzx)\n );\n\n gl_FragColor = color;\n }\n "); 680 | this.properties.hue = filter.Filter.clamp(-1, hue, 1) || 0; 681 | var angle = hue * 3.14159265; 682 | var sin = Math.sin(angle); 683 | var cos = Math.cos(angle); 684 | this.weights = new jsfx.util.Vector3(2 * cos, -Math.sqrt(3.0) * sin - cos, Math.sqrt(3.0) * sin - cos) 685 | .addScalar(1.0) 686 | .divideScalar(3.0); 687 | } 688 | Hue.prototype.iterateCanvas = function (helper) { 689 | var rgb = helper.toVector3(); 690 | helper.r = rgb.dot(this.weights); 691 | helper.g = rgb.dotScalars(this.weights.z, this.weights.x, this.weights.y); 692 | helper.b = rgb.dotScalars(this.weights.y, this.weights.z, this.weights.x); 693 | }; 694 | return Hue; 695 | })(filter.IterableFilter); 696 | filter.Hue = Hue; 697 | })(filter = jsfx.filter || (jsfx.filter = {})); 698 | })(jsfx || (jsfx = {})); 699 | var jsfx; 700 | (function (jsfx) { 701 | var filter; 702 | (function (filter) { 703 | var Multiply = (function (_super) { 704 | __extends(Multiply, _super); 705 | function Multiply(r, g, b) { 706 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float r;\n uniform float g;\n uniform float b;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n color.r *= r;\n color.g *= g;\n color.b *= b;\n\n gl_FragColor = color;\n }\n "); 707 | this.r = r; 708 | this.g = g; 709 | this.b = b; 710 | this.properties.r = filter.Filter.clamp(0, r, 1); 711 | this.properties.g = filter.Filter.clamp(0, g, 1); 712 | this.properties.b = filter.Filter.clamp(0, b, 1); 713 | } 714 | Multiply.prototype.iterateCanvas = function (helper) { 715 | helper.r *= this.properties.r; 716 | helper.g *= this.properties.g; 717 | helper.b *= this.properties.b; 718 | }; 719 | return Multiply; 720 | })(filter.IterableFilter); 721 | filter.Multiply = Multiply; 722 | })(filter = jsfx.filter || (jsfx.filter = {})); 723 | })(jsfx || (jsfx = {})); 724 | var jsfx; 725 | (function (jsfx) { 726 | var filter; 727 | (function (filter) { 728 | var Noise = (function (_super) { 729 | __extends(Noise, _super); 730 | function Noise(amount) { 731 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float amount;\n varying vec2 texCoord;\n\n float rand(vec2 co) {\n return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n }\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n\n float diff = (rand(texCoord) - 0.5) * amount;\n color.r += diff;\n color.g += diff;\n color.b += diff;\n\n gl_FragColor = color;\n }\n "); 732 | this.properties.amount = filter.Filter.clamp(0, amount, 1); 733 | } 734 | Noise.prototype.iterateCanvas = function (helper) { 735 | var amount = this.properties.amount; 736 | var width = helper.getImageData().width; 737 | var x = (helper.getIndex() / 4) % width; 738 | var y = Math.floor((helper.getIndex() / 4) / width); 739 | var v = new jsfx.util.Vector2(x, y); 740 | var diff = (Noise.rand(v) - 0.5) * amount; 741 | helper.r += diff; 742 | helper.g += diff; 743 | helper.b += diff; 744 | }; 745 | Noise.rand = function (v) { 746 | return Noise.fract(Math.sin(v.dotScalars(12.9898, 78.233)) * 43758.5453); 747 | }; 748 | Noise.fract = function (x) { 749 | return x - Math.floor(x); 750 | }; 751 | return Noise; 752 | })(filter.IterableFilter); 753 | filter.Noise = Noise; 754 | })(filter = jsfx.filter || (jsfx.filter = {})); 755 | })(jsfx || (jsfx = {})); 756 | var jsfx; 757 | (function (jsfx) { 758 | var filter; 759 | (function (filter) { 760 | var Saturation = (function (_super) { 761 | __extends(Saturation, _super); 762 | function Saturation(saturation) { 763 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float saturation;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n\n float average = (color.r + color.g + color.b) / 3.0;\n if (saturation > 0.0) {\n color.rgb += (average - color.rgb) * (1.0 - 1.0 / (1.001 - saturation));\n } else {\n color.rgb += (average - color.rgb) * (-saturation);\n }\n\n gl_FragColor = color;\n }\n "); 764 | this.properties.saturation = filter.Filter.clamp(-1, saturation, 1) || 0; 765 | } 766 | Saturation.prototype.iterateCanvas = function (helper) { 767 | var saturation = this.properties.saturation; 768 | var average = (helper.r + helper.g + helper.b) / 3; 769 | if (saturation > 0) { 770 | helper.r += (average - helper.r) * (1 - 1 / (1.001 - saturation)); 771 | helper.g += (average - helper.g) * (1 - 1 / (1.001 - saturation)); 772 | helper.b += (average - helper.b) * (1 - 1 / (1.001 - saturation)); 773 | } 774 | else { 775 | helper.r += (average - helper.r) * (-saturation); 776 | helper.g += (average - helper.g) * (-saturation); 777 | helper.b += (average - helper.b) * (-saturation); 778 | } 779 | }; 780 | return Saturation; 781 | })(filter.IterableFilter); 782 | filter.Saturation = Saturation; 783 | })(filter = jsfx.filter || (jsfx.filter = {})); 784 | })(jsfx || (jsfx = {})); 785 | var jsfx; 786 | (function (jsfx) { 787 | var filter; 788 | (function (filter) { 789 | var Sepia = (function (_super) { 790 | __extends(Sepia, _super); 791 | function Sepia(amount) { 792 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float amount;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n float r = color.r;\n float g = color.g;\n float b = color.b;\n\n color.r = min(1.0, (r * (1.0 - (0.607 * amount))) + (g * (0.769 * amount)) + (b * (0.189 * amount)));\n color.g = min(1.0, (r * 0.349 * amount) + (g * (1.0 - (0.314 * amount))) + (b * 0.168 * amount));\n color.b = min(1.0, (r * 0.272 * amount) + (g * 0.534 * amount) + (b * (1.0 - (0.869 * amount))));\n\n gl_FragColor = color;\n }\n "); 793 | this.properties.amount = filter.Filter.clamp(-1, amount, 1) || 0; 794 | } 795 | Sepia.prototype.iterateCanvas = function (helper) { 796 | var r = helper.r; 797 | var g = helper.g; 798 | var b = helper.b; 799 | var amount = this.properties.amount; 800 | helper.r = Math.min(1.0, (r * (1.0 - (0.607 * amount))) + (g * (0.769 * amount)) + (b * (0.189 * amount))); 801 | helper.g = Math.min(1.0, (r * 0.349 * amount) + (g * (1.0 - (0.314 * amount))) + (b * 0.168 * amount)); 802 | helper.b = Math.min(1.0, (r * 0.272 * amount) + (g * 0.534 * amount) + (b * (1.0 - (0.869 * amount)))); 803 | }; 804 | return Sepia; 805 | })(filter.IterableFilter); 806 | filter.Sepia = Sepia; 807 | })(filter = jsfx.filter || (jsfx.filter = {})); 808 | })(jsfx || (jsfx = {})); 809 | var jsfx; 810 | (function (jsfx) { 811 | var filter; 812 | (function (filter) { 813 | var UnsharpMask = (function (_super) { 814 | __extends(UnsharpMask, _super); 815 | function UnsharpMask(radius, strength) { 816 | _super.call(this, null, "\n uniform sampler2D blurredTexture;\n uniform sampler2D originalTexture;\n uniform float strength;\n uniform float threshold;\n varying vec2 texCoord;\n\n void main() {\n vec4 blurred = texture2D(blurredTexture, texCoord);\n vec4 original = texture2D(originalTexture, texCoord);\n gl_FragColor = mix(blurred, original, 1.0 + strength);\n }\n "); 817 | this.properties.radius = radius; 818 | this.properties.strength = strength; 819 | } 820 | UnsharpMask.prototype.drawWebGL = function (renderer) { 821 | var shader = renderer.getShader(this); 822 | var radius = this.properties.radius; 823 | var strength = this.properties.strength; 824 | var extraTexture = renderer.createTexture(); 825 | renderer.getTexture().use(); 826 | extraTexture.drawTo(renderer.getDefaultShader().drawRect.bind(renderer.getDefaultShader())); 827 | extraTexture.use(1); 828 | var blur = new filter.Blur(radius); 829 | blur.drawWebGL(renderer); 830 | shader.textures({ 831 | originalTexture: 1 832 | }); 833 | renderer.getTexture().use(); 834 | renderer.getNextTexture().drawTo(function () { 835 | shader.uniforms({ strength: strength }).drawRect(); 836 | }); 837 | extraTexture.unuse(1); 838 | }; 839 | UnsharpMask.prototype.drawCanvas = function (renderer) { 840 | var original = new Uint8ClampedArray(renderer.getImageData().data); 841 | var radius = this.properties.radius; 842 | var strength = this.properties.strength + 1; 843 | var blur = new filter.Blur(radius); 844 | blur.drawCanvas(renderer); 845 | var imageData = renderer.getImageData(); 846 | var pixels = imageData.data; 847 | for (var i = 0; i < pixels.length; i += 4) { 848 | pixels[i] = jsfx.util.ImageDataHelper.mix(pixels[i], original[i], strength); 849 | pixels[i + 1] = jsfx.util.ImageDataHelper.mix(pixels[i + 1], original[i + 1], strength); 850 | pixels[i + 2] = jsfx.util.ImageDataHelper.mix(pixels[i + 2], original[i + 2], strength); 851 | } 852 | renderer.setImageData(imageData); 853 | }; 854 | return UnsharpMask; 855 | })(filter.Filter); 856 | filter.UnsharpMask = UnsharpMask; 857 | })(filter = jsfx.filter || (jsfx.filter = {})); 858 | })(jsfx || (jsfx = {})); 859 | var jsfx; 860 | (function (jsfx) { 861 | var filter; 862 | (function (filter) { 863 | var Vibrance = (function (_super) { 864 | __extends(Vibrance, _super); 865 | function Vibrance(amount) { 866 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float amount;\n varying vec2 texCoord;\n void main() {\n vec4 color = texture2D(texture, texCoord);\n float average = (color.r + color.g + color.b) / 3.0;\n float mx = max(color.r, max(color.g, color.b));\n float amt = (mx - average) * (-amount * 3.0);\n color.rgb = mix(color.rgb, vec3(mx), amt);\n gl_FragColor = color;\n }\n "); 867 | this.properties.amount = filter.Filter.clamp(-1, amount, 1); 868 | } 869 | Vibrance.prototype.iterateCanvas = function (helper) { 870 | var amount = this.properties.amount; 871 | var average = (helper.r + helper.g + helper.b) / 3.0; 872 | var mx = Math.max(helper.r, Math.max(helper.g, helper.b)); 873 | helper.mix(mx, mx, mx, (mx - average) * (-amount * 3.0)); 874 | }; 875 | return Vibrance; 876 | })(filter.IterableFilter); 877 | filter.Vibrance = Vibrance; 878 | })(filter = jsfx.filter || (jsfx.filter = {})); 879 | })(jsfx || (jsfx = {})); 880 | var jsfx; 881 | (function (jsfx) { 882 | var filter; 883 | (function (filter) { 884 | var Vignette = (function (_super) { 885 | __extends(Vignette, _super); 886 | function Vignette(size, amount) { 887 | _super.call(this, null, "\n uniform sampler2D texture;\n uniform float size;\n uniform float amount;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n\n float dist = distance(texCoord, vec2(0.5, 0.5));\n color.rgb *= smoothstep(0.8, size * 0.799, dist * (amount + size));\n\n gl_FragColor = color;\n }\n "); 888 | this.properties.size = filter.Filter.clamp(0, size, 1); 889 | this.properties.amount = filter.Filter.clamp(0, amount, 1); 890 | } 891 | Vignette.prototype.iterateCanvas = function (helper) { 892 | var size = this.properties.size; 893 | var amount = this.properties.amount; 894 | var imageData = helper.getImageData(); 895 | var x = (helper.getIndex() / 4) % imageData.width; 896 | var y = Math.floor((helper.getIndex() / 4) / imageData.width); 897 | var distance = Vignette.distance(x / imageData.width, y / imageData.height, 0.5, 0.5); 898 | var amount = Vignette.smoothstep(0.8, size * 0.799, distance * (amount + size)); 899 | helper.r *= amount; 900 | helper.g *= amount; 901 | helper.b *= amount; 902 | }; 903 | Vignette.distance = function (x1, y1, x2, y2) { 904 | return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); 905 | }; 906 | Vignette.smoothstep = function (min, max, value) { 907 | var x = Math.max(0, Math.min(1, (value - min) / (max - min))); 908 | return x * x * (3 - 2 * x); 909 | }; 910 | return Vignette; 911 | })(filter.IterableFilter); 912 | filter.Vignette = Vignette; 913 | })(filter = jsfx.filter || (jsfx.filter = {})); 914 | })(jsfx || (jsfx = {})); 915 | var jsfx; 916 | (function (jsfx) { 917 | var hasWebGL = (function () { 918 | try { 919 | var canvas = document.createElement("canvas"); 920 | return !!(canvas.getContext("webgl") || canvas.getContext("experimental-webgl")); 921 | } 922 | catch (e) { 923 | return false; 924 | } 925 | })(); 926 | function Renderer(type) { 927 | if (!type) { 928 | type = hasWebGL ? "webgl" : "canvas"; 929 | } 930 | if (type === "webgl") { 931 | return new jsfx.webgl.Renderer(); 932 | } 933 | return new jsfx.canvas.Renderer(); 934 | } 935 | jsfx.Renderer = Renderer; 936 | })(jsfx || (jsfx = {})); 937 | var jsfx; 938 | (function (jsfx) { 939 | var Source = (function () { 940 | function Source(element) { 941 | this.element = element; 942 | } 943 | Object.defineProperty(Source.prototype, "width", { 944 | get: function () { 945 | return this.element.width; 946 | }, 947 | enumerable: true, 948 | configurable: true 949 | }); 950 | Object.defineProperty(Source.prototype, "height", { 951 | get: function () { 952 | return this.element.height; 953 | }, 954 | enumerable: true, 955 | configurable: true 956 | }); 957 | return Source; 958 | })(); 959 | jsfx.Source = Source; 960 | })(jsfx || (jsfx = {})); 961 | var jsfx; 962 | (function (jsfx) { 963 | var util; 964 | (function (util) { 965 | var ImageDataHelper = (function () { 966 | function ImageDataHelper(imageData, index) { 967 | this.imageData = imageData; 968 | this.index = index; 969 | this.r = this.imageData.data[index] / 255; 970 | this.g = this.imageData.data[index + 1] / 255; 971 | this.b = this.imageData.data[index + 2] / 255; 972 | this.a = this.imageData.data[index + 3] / 255; 973 | } 974 | ImageDataHelper.prototype.getImageData = function () { 975 | return this.imageData; 976 | }; 977 | ImageDataHelper.prototype.getIndex = function () { 978 | return this.index; 979 | }; 980 | ImageDataHelper.prototype.save = function () { 981 | this.imageData.data[this.index] = this.r * 255; 982 | this.imageData.data[this.index + 1] = this.g * 255; 983 | this.imageData.data[this.index + 2] = this.b * 255; 984 | this.imageData.data[this.index + 3] = this.a * 255; 985 | }; 986 | ImageDataHelper.prototype.toVector3 = function () { 987 | return new jsfx.util.Vector3(this.r, this.g, this.b); 988 | }; 989 | ImageDataHelper.prototype.fromVector3 = function (v) { 990 | this.r = v.x; 991 | this.g = v.y; 992 | this.b = v.z; 993 | }; 994 | ImageDataHelper.prototype.mix = function (r, g, b, a) { 995 | this.r = ImageDataHelper.mix(this.r, r, a); 996 | this.g = ImageDataHelper.mix(this.g, g, a); 997 | this.b = ImageDataHelper.mix(this.b, b, a); 998 | }; 999 | ImageDataHelper.mix = function (x, y, a) { 1000 | return x * (1 - a) + y * a; 1001 | }; 1002 | return ImageDataHelper; 1003 | })(); 1004 | util.ImageDataHelper = ImageDataHelper; 1005 | })(util = jsfx.util || (jsfx.util = {})); 1006 | })(jsfx || (jsfx = {})); 1007 | var jsfx; 1008 | (function (jsfx) { 1009 | var util; 1010 | (function (util) { 1011 | var SplineInterpolator = (function () { 1012 | function SplineInterpolator(points) { 1013 | this.points = points; 1014 | var n = points.length; 1015 | var i; 1016 | this.xa = []; 1017 | this.ya = []; 1018 | this.u = []; 1019 | this.y2 = []; 1020 | points.sort(function (a, b) { 1021 | return a[0] - b[0]; 1022 | }); 1023 | for (i = 0; i < n; i++) { 1024 | this.xa.push(points[i][0]); 1025 | this.ya.push(points[i][1]); 1026 | } 1027 | this.u[0] = 0; 1028 | this.y2[0] = 0; 1029 | for (i = 1; i < n - 1; ++i) { 1030 | var wx = this.xa[i + 1] - this.xa[i - 1]; 1031 | var sig = (this.xa[i] - this.xa[i - 1]) / wx; 1032 | var p = sig * this.y2[i - 1] + 2.0; 1033 | this.y2[i] = (sig - 1.0) / p; 1034 | var ddydx = (this.ya[i + 1] - this.ya[i]) / (this.xa[i + 1] - this.xa[i]) - 1035 | (this.ya[i] - this.ya[i - 1]) / (this.xa[i] - this.xa[i - 1]); 1036 | this.u[i] = (6.0 * ddydx / wx - sig * this.u[i - 1]) / p; 1037 | } 1038 | this.y2[n - 1] = 0; 1039 | for (i = n - 2; i >= 0; --i) { 1040 | this.y2[i] = this.y2[i] * this.y2[i + 1] + this.u[i]; 1041 | } 1042 | } 1043 | SplineInterpolator.prototype.interpolate = function (x) { 1044 | var n = this.ya.length; 1045 | var klo = 0; 1046 | var khi = n - 1; 1047 | while (khi - klo > 1) { 1048 | var k = (khi + klo) >> 1; 1049 | if (this.xa[k] > x) { 1050 | khi = k; 1051 | } 1052 | else { 1053 | klo = k; 1054 | } 1055 | } 1056 | var h = this.xa[khi] - this.xa[klo]; 1057 | var a = (this.xa[khi] - x) / h; 1058 | var b = (x - this.xa[klo]) / h; 1059 | return a * this.ya[klo] + b * this.ya[khi] + 1060 | ((a * a * a - a) * this.y2[klo] + (b * b * b - b) * this.y2[khi]) * (h * h) / 6.0; 1061 | }; 1062 | return SplineInterpolator; 1063 | })(); 1064 | util.SplineInterpolator = SplineInterpolator; 1065 | })(util = jsfx.util || (jsfx.util = {})); 1066 | })(jsfx || (jsfx = {})); 1067 | var jsfx; 1068 | (function (jsfx) { 1069 | var util; 1070 | (function (util) { 1071 | var Vector2 = (function () { 1072 | function Vector2(x, y) { 1073 | this.x = x; 1074 | this.y = y; 1075 | } 1076 | Vector2.prototype.dotScalars = function (x, y) { 1077 | return this.x * x + this.y * y; 1078 | }; 1079 | return Vector2; 1080 | })(); 1081 | util.Vector2 = Vector2; 1082 | })(util = jsfx.util || (jsfx.util = {})); 1083 | })(jsfx || (jsfx = {})); 1084 | var jsfx; 1085 | (function (jsfx) { 1086 | var util; 1087 | (function (util) { 1088 | var Vector3 = (function () { 1089 | function Vector3(x, y, z) { 1090 | this.x = x; 1091 | this.y = y; 1092 | this.z = z; 1093 | } 1094 | Vector3.prototype.addScalar = function (s) { 1095 | this.x += s; 1096 | this.y += s; 1097 | this.z += s; 1098 | return this; 1099 | }; 1100 | Vector3.prototype.multiplyScalar = function (s) { 1101 | this.x *= s; 1102 | this.y *= s; 1103 | this.z *= s; 1104 | return this; 1105 | }; 1106 | Vector3.prototype.divideScalar = function (s) { 1107 | if (s !== 0) { 1108 | var invScalar = 1 / s; 1109 | this.x *= invScalar; 1110 | this.y *= invScalar; 1111 | this.z *= invScalar; 1112 | } 1113 | else { 1114 | this.x = 0; 1115 | this.y = 0; 1116 | this.z = 0; 1117 | } 1118 | return this; 1119 | }; 1120 | Vector3.prototype.length = function () { 1121 | return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); 1122 | }; 1123 | Vector3.prototype.dot = function (v) { 1124 | return this.x * v.x + this.y * v.y + this.z * v.z; 1125 | }; 1126 | Vector3.prototype.dotScalars = function (x, y, z) { 1127 | return this.x * x + this.y * y + this.z * z; 1128 | }; 1129 | return Vector3; 1130 | })(); 1131 | util.Vector3 = Vector3; 1132 | })(util = jsfx.util || (jsfx.util = {})); 1133 | })(jsfx || (jsfx = {})); 1134 | var jsfx; 1135 | (function (jsfx) { 1136 | var webgl; 1137 | (function (webgl) { 1138 | var Renderer = (function () { 1139 | function Renderer() { 1140 | this.canvas = document.createElement("canvas"); 1141 | this.gl = this.canvas.getContext("experimental-webgl", { premultipliedAlpha: false }); 1142 | this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); 1143 | this.source = null; 1144 | this.sourceTexture = null; 1145 | this.textures = null; 1146 | this.currentTexture = 0; 1147 | this.gl.shaderCache = {}; 1148 | } 1149 | Renderer.prototype.setSource = function (source) { 1150 | if (this.source) { 1151 | this.cleanUp(); 1152 | } 1153 | this.source = source; 1154 | this.sourceTexture = webgl.Texture.fromElement(this.gl, source.element); 1155 | this.initialize(); 1156 | this.sourceTexture.use(); 1157 | this.getTexture().drawTo(this.getDefaultShader().drawRect.bind(this.getDefaultShader())); 1158 | return this; 1159 | }; 1160 | Renderer.prototype.getSource = function () { 1161 | return this.source; 1162 | }; 1163 | Renderer.prototype.applyFilter = function (filter) { 1164 | filter.drawWebGL(this); 1165 | return this; 1166 | }; 1167 | Renderer.prototype.applyFilters = function (filters) { 1168 | var _this = this; 1169 | filters.forEach(function (filter) { 1170 | filter.drawWebGL(_this); 1171 | }); 1172 | return this; 1173 | }; 1174 | Renderer.prototype.render = function () { 1175 | this.getTexture().use(); 1176 | this.getFlippedShader().drawRect(); 1177 | }; 1178 | Renderer.prototype.getCanvas = function () { 1179 | return this.canvas; 1180 | }; 1181 | Renderer.prototype.getTexture = function () { 1182 | return this.textures[this.currentTexture % 2]; 1183 | }; 1184 | Renderer.prototype.getNextTexture = function () { 1185 | return this.textures[++this.currentTexture % 2]; 1186 | }; 1187 | Renderer.prototype.createTexture = function () { 1188 | return new webgl.Texture(this.gl, this.source.width, this.source.height, this.gl.RGBA, this.gl.UNSIGNED_BYTE); 1189 | }; 1190 | Renderer.prototype.getShader = function (filter) { 1191 | var cacheKey = filter.getVertexSource() + filter.getFragmentSource(); 1192 | return this.gl.shaderCache.hasOwnProperty(cacheKey) ? 1193 | this.gl.shaderCache[cacheKey] : 1194 | new webgl.Shader(this.gl, filter.getVertexSource(), filter.getFragmentSource()); 1195 | }; 1196 | Renderer.prototype.getDefaultShader = function () { 1197 | if (!this.gl.shaderCache.def) { 1198 | this.gl.shaderCache.def = new webgl.Shader(this.gl); 1199 | } 1200 | return this.gl.shaderCache.def; 1201 | }; 1202 | Renderer.prototype.getFlippedShader = function () { 1203 | if (!this.gl.shaderCache.flipped) { 1204 | this.gl.shaderCache.flipped = new webgl.Shader(this.gl, null, "\n uniform sampler2D texture;\n varying vec2 texCoord;\n\n void main() {\n gl_FragColor = texture2D(texture, vec2(texCoord.x, 1.0 - texCoord.y));\n }\n "); 1205 | } 1206 | return this.gl.shaderCache.flipped; 1207 | }; 1208 | Renderer.prototype.initialize = function () { 1209 | this.canvas.width = this.source.width; 1210 | this.canvas.height = this.source.height; 1211 | var textures = []; 1212 | for (var i = 0; i < 2; i++) { 1213 | textures.push(this.createTexture()); 1214 | } 1215 | this.textures = textures; 1216 | }; 1217 | Renderer.prototype.cleanUp = function () { 1218 | this.sourceTexture.destroy(); 1219 | for (var i = 0; i < 2; i++) { 1220 | this.textures[i].destroy(); 1221 | } 1222 | this.textures = null; 1223 | }; 1224 | return Renderer; 1225 | })(); 1226 | webgl.Renderer = Renderer; 1227 | })(webgl = jsfx.webgl || (jsfx.webgl = {})); 1228 | })(jsfx || (jsfx = {})); 1229 | var jsfx; 1230 | (function (jsfx) { 1231 | var webgl; 1232 | (function (webgl) { 1233 | var Shader = (function () { 1234 | function Shader(gl, vertexSource, fragmentSource) { 1235 | this.gl = gl; 1236 | this.vertexSource = vertexSource || Shader.defaultVertexSource; 1237 | this.fragmentSource = fragmentSource || Shader.defaultFragmentSource; 1238 | this.fragmentSource = "precision highp float;" + this.fragmentSource; 1239 | this.vertexAttribute = null; 1240 | this.texCoordAttribute = null; 1241 | this.program = gl.createProgram(); 1242 | gl.attachShader(this.program, compileSource(gl, gl.VERTEX_SHADER, this.vertexSource)); 1243 | gl.attachShader(this.program, compileSource(gl, gl.FRAGMENT_SHADER, this.fragmentSource)); 1244 | gl.linkProgram(this.program); 1245 | if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { 1246 | throw "link error: " + gl.getProgramInfoLog(this.program); 1247 | } 1248 | } 1249 | Shader.prototype.textures = function (textures) { 1250 | this.gl.useProgram(this.program); 1251 | for (var name in textures) { 1252 | if (!textures.hasOwnProperty(name)) { 1253 | continue; 1254 | } 1255 | this.gl.uniform1i(this.gl.getUniformLocation(this.program, name), textures[name]); 1256 | } 1257 | return this; 1258 | }; 1259 | Shader.prototype.uniforms = function (uniforms) { 1260 | this.gl.useProgram(this.program); 1261 | for (var name in uniforms) { 1262 | if (!uniforms.hasOwnProperty(name)) { 1263 | continue; 1264 | } 1265 | var location = this.gl.getUniformLocation(this.program, name); 1266 | if (location === null) { 1267 | continue; 1268 | } 1269 | var value = uniforms[name]; 1270 | if (isArray(value)) { 1271 | switch (value.length) { 1272 | case 1: 1273 | this.gl.uniform1fv(location, new Float32Array(value)); 1274 | break; 1275 | case 2: 1276 | this.gl.uniform2fv(location, new Float32Array(value)); 1277 | break; 1278 | case 3: 1279 | this.gl.uniform3fv(location, new Float32Array(value)); 1280 | break; 1281 | case 4: 1282 | this.gl.uniform4fv(location, new Float32Array(value)); 1283 | break; 1284 | case 9: 1285 | this.gl.uniformMatrix3fv(location, false, new Float32Array(value)); 1286 | break; 1287 | case 16: 1288 | this.gl.uniformMatrix4fv(location, false, new Float32Array(value)); 1289 | break; 1290 | default: 1291 | throw "dont't know how to load uniform \"" + name + "\" of length " + value.length; 1292 | } 1293 | } 1294 | else if (isNumber(value)) { 1295 | this.gl.uniform1f(location, value); 1296 | } 1297 | else { 1298 | throw "attempted to set uniform \"" + name + "\" to invalid value " + (value || "undefined").toString(); 1299 | } 1300 | } 1301 | return this; 1302 | }; 1303 | Shader.prototype.drawRect = function (left, top, right, bottom) { 1304 | var viewport = this.gl.getParameter(this.gl.VIEWPORT); 1305 | top = top !== undefined ? (top - viewport[1]) / viewport[3] : 0; 1306 | left = left !== undefined ? (left - viewport[0]) / viewport[2] : 0; 1307 | right = right !== undefined ? (right - viewport[0]) / viewport[2] : 1; 1308 | bottom = bottom !== undefined ? (bottom - viewport[1]) / viewport[3] : 1; 1309 | if (!this.gl.vertexBuffer) { 1310 | this.gl.vertexBuffer = this.gl.createBuffer(); 1311 | } 1312 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.vertexBuffer); 1313 | this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([left, top, left, bottom, right, top, right, bottom]), this.gl.STATIC_DRAW); 1314 | if (!this.gl.texCoordBuffer) { 1315 | this.gl.texCoordBuffer = this.gl.createBuffer(); 1316 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.texCoordBuffer); 1317 | this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]), this.gl.STATIC_DRAW); 1318 | } 1319 | if (this.vertexAttribute == null) { 1320 | this.vertexAttribute = this.gl.getAttribLocation(this.program, "vertex"); 1321 | this.gl.enableVertexAttribArray(this.vertexAttribute); 1322 | } 1323 | if (this.texCoordAttribute == null) { 1324 | this.texCoordAttribute = this.gl.getAttribLocation(this.program, "_texCoord"); 1325 | this.gl.enableVertexAttribArray(this.texCoordAttribute); 1326 | } 1327 | this.gl.useProgram(this.program); 1328 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.vertexBuffer); 1329 | this.gl.vertexAttribPointer(this.vertexAttribute, 2, this.gl.FLOAT, false, 0, 0); 1330 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.texCoordBuffer); 1331 | this.gl.vertexAttribPointer(this.texCoordAttribute, 2, this.gl.FLOAT, false, 0, 0); 1332 | this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4); 1333 | }; 1334 | Shader.prototype.destroy = function () { 1335 | this.gl.deleteProgram(this.program); 1336 | this.program = null; 1337 | }; 1338 | Shader.defaultVertexSource = "\nattribute vec2 vertex;\nattribute vec2 _texCoord;\nvarying vec2 texCoord;\n\nvoid main() {\n texCoord = _texCoord;\n gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);\n}"; 1339 | Shader.defaultFragmentSource = "\nuniform sampler2D texture;\nvarying vec2 texCoord;\n\nvoid main() {\n gl_FragColor = texture2D(texture, texCoord);\n}"; 1340 | return Shader; 1341 | })(); 1342 | webgl.Shader = Shader; 1343 | function compileSource(gl, type, source) { 1344 | var shader = gl.createShader(type); 1345 | gl.shaderSource(shader, source); 1346 | gl.compileShader(shader); 1347 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 1348 | throw "compile error: " + gl.getShaderInfoLog(shader); 1349 | } 1350 | return shader; 1351 | } 1352 | function isArray(obj) { 1353 | return Object.prototype.toString.call(obj) === "[object Array]"; 1354 | } 1355 | function isNumber(obj) { 1356 | return Object.prototype.toString.call(obj) === "[object Number]"; 1357 | } 1358 | })(webgl = jsfx.webgl || (jsfx.webgl = {})); 1359 | })(jsfx || (jsfx = {})); 1360 | var jsfx; 1361 | (function (jsfx) { 1362 | var webgl; 1363 | (function (webgl) { 1364 | var Texture = (function () { 1365 | function Texture(gl, width, height, format, type) { 1366 | if (format === void 0) { format = gl.RGBA; } 1367 | if (type === void 0) { type = gl.UNSIGNED_BYTE; } 1368 | this.gl = gl; 1369 | this.width = width; 1370 | this.height = height; 1371 | this.format = format; 1372 | this.type = type; 1373 | this.id = gl.createTexture(); 1374 | this.element = null; 1375 | gl.bindTexture(gl.TEXTURE_2D, this.id); 1376 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 1377 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 1378 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 1379 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 1380 | if (width && height) { 1381 | gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, null); 1382 | } 1383 | } 1384 | Texture.prototype.loadContentsOf = function (element) { 1385 | this.element = element; 1386 | this.width = element.width; 1387 | this.height = element.height; 1388 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.id); 1389 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.format, this.format, this.type, element); 1390 | }; 1391 | Texture.prototype.initFromBytes = function (width, height, data) { 1392 | this.width = width; 1393 | this.height = height; 1394 | this.format = this.gl.RGBA; 1395 | this.type = this.gl.UNSIGNED_BYTE; 1396 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.id); 1397 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0, this.gl.RGBA, this.type, new Uint8Array(data)); 1398 | }; 1399 | Texture.prototype.use = function (unit) { 1400 | this.gl.activeTexture(this.gl.TEXTURE0 + (unit || 0)); 1401 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.id); 1402 | }; 1403 | Texture.prototype.unuse = function (unit) { 1404 | this.gl.activeTexture(this.gl.TEXTURE0 + (unit || 0)); 1405 | this.gl.bindTexture(this.gl.TEXTURE_2D, null); 1406 | }; 1407 | Texture.prototype.drawTo = function (callback) { 1408 | this.gl.frameBuffer = this.gl.frameBuffer || this.gl.createFramebuffer(); 1409 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.gl.frameBuffer); 1410 | this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, this.id, 0); 1411 | if (this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER) !== this.gl.FRAMEBUFFER_COMPLETE) { 1412 | throw new Error("incomplete framebuffer"); 1413 | } 1414 | this.gl.viewport(0, 0, this.width, this.height); 1415 | callback(); 1416 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); 1417 | }; 1418 | Texture.prototype.destroy = function () { 1419 | this.gl.deleteTexture(this.id); 1420 | this.id = null; 1421 | }; 1422 | Texture.fromElement = function (gl, element) { 1423 | var texture = new Texture(gl, 0, 0); 1424 | texture.loadContentsOf(element); 1425 | return texture; 1426 | }; 1427 | return Texture; 1428 | })(); 1429 | webgl.Texture = Texture; 1430 | })(webgl = jsfx.webgl || (jsfx.webgl = {})); 1431 | })(jsfx || (jsfx = {})); 1432 | -------------------------------------------------------------------------------- /build/jsfx.min.js: -------------------------------------------------------------------------------- 1 | var jsfx;!function(t){function e(e){return e||(e=r?"webgl":"canvas"),"webgl"===e?new t.webgl.Renderer:new t.canvas.Renderer}var r=function(){try{var t=document.createElement("canvas");return!(!t.getContext("webgl")&&!t.getContext("experimental-webgl"))}catch(e){return!1}}();t.Renderer=e}(jsfx||(jsfx={}));var jsfx;!function(t){var e=function(){function t(t){this.element=t}return Object.defineProperty(t.prototype,"width",{get:function(){return this.element.width},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"height",{get:function(){return this.element.height},enumerable:!0,configurable:!0}),t}();t.Source=e}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(e){var r=function(){function e(){this.canvas=t.canvas.Renderer.createCanvas(),this.ctx=this.canvas.getContext("2d"),this.source=null,this.imageData=null}return e.prototype.setSource=function(t){return this.source&&this.cleanUp(),this.source=t,this.canvas.width=t.width,this.canvas.height=t.height,this.ctx.drawImage(t.element,0,0,t.width,t.height),this.imageData=this.ctx.getImageData(0,0,t.width,t.height),this},e.prototype.getSource=function(){return this.source},e.prototype.applyFilter=function(t){return t.drawCanvas(this),this},e.prototype.applyFilters=function(e){for(var r,n=[],i=0;i0&&(this.applyFilterStack(n),n=[]),this.applyFilter(r));return n.length>0&&this.applyFilterStack(n),this},e.prototype.render=function(){this.ctx.putImageData(this.imageData,0,0)},e.prototype.getCanvas=function(){return this.canvas},e.prototype.getContext=function(){return this.ctx},e.prototype.getImageData=function(){return this.imageData},e.prototype.setImageData=function(t){this.imageData=t},e.prototype.applyFilterStack=function(e){return t.filter.IterableFilter.drawCanvas(e,this),this},e.prototype.cleanUp=function(){this.imageData=null,this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)},e.createCanvas=function(){return"undefined"!=typeof Buffer&&"undefined"==typeof window?new(require("canvas"))(100,100):document.createElement("canvas")},e}();e.Renderer=r}(e=t.canvas||(t.canvas={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(t){var e=function(){function t(t,e){void 0===t&&(t=null),void 0===e&&(e=null),this.vertexSource=t,this.fragmentSource=e,this.properties={}}return t.prototype.getProperties=function(){return this.properties},t.prototype.drawCanvas=function(t){throw new Error("Must be implemented")},t.prototype.drawWebGL=function(t){var e=t.getShader(this),r=this.getProperties();t.getTexture().use(),t.getNextTexture().drawTo(function(){e.uniforms(r).drawRect()})},t.prototype.getVertexSource=function(){return this.vertexSource},t.prototype.getFragmentSource=function(){return this.fragmentSource},t.clamp=function(t,e,r){return Math.max(t,Math.min(e,r))},t}();t.Filter=e}(e=t.filter||(t.filter={}))}(jsfx||(jsfx={}));var __extends=this&&this.__extends||function(t,e){function r(){this.constructor=t}for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);t.prototype=null===e?Object.create(e):(r.prototype=e.prototype,new r)},jsfx;!function(t){var e;!function(t){var e=function(t){function e(e){t.call(this,null,"\n uniform sampler2D texture;\n uniform vec2 delta;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = vec4(0.0);\n float total = 0.0;\n\n /* randomize the lookup values to hide the fixed number of samples */\n //float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0);\n\n vec3 scale = vec3(12.9898, 78.233, 151.7182);\n float offset = fract(sin(dot(gl_FragCoord.xyz + 0.0, scale)) * 43758.5453 + 0.0);\n\n for (float t = -30.0; t <= 30.0; t++) {\n float percent = (t + offset - 0.5) / 30.0;\n float weight = 1.0 - abs(percent);\n vec4 sample = texture2D(texture, texCoord + delta * percent);\n\n /* switch to pre-multiplied alpha to correctly blur transparent images */\n sample.rgb *= sample.a;\n\n color += sample * weight;\n total += weight;\n }\n\n gl_FragColor = color / total;\n\n /* switch back from pre-multiplied alpha */\n gl_FragColor.rgb /= gl_FragColor.a + 0.00001;\n }\n "),this.properties.radius=e}return __extends(e,t),e.prototype.drawWebGL=function(t){var e=t.getShader(this),r={delta:[this.properties.radius/t.getSource().width,0]},n={delta:[0,this.properties.radius/t.getSource().height]};t.getTexture().use(),t.getNextTexture().drawTo(function(){e.uniforms(r).drawRect()}),t.getTexture().use(),t.getNextTexture().drawTo(function(){e.uniforms(n).drawRect()})},e.prototype.drawCanvas=function(t){var e,o,a,s,l,u,h,c,f,g,p,x,d,m,v,y,b,w,C,T,F,D,_,S,E=t.getImageData(),j=E.data,R=this.properties.radius,A=E.width,I=E.height,M=R+R+1,U=A-1,P=I-1,B=R+1,z=B*(B+1)/2,L=new i,X=L;for(a=1;M>a;a++)if(X=X.next=new i,a==B)var N=X;X.next=L;var G=null,k=null;h=u=0;var O=r[R],V=n[R];for(o=0;I>o;o++){for(y=b=w=C=c=f=g=p=0,x=B*(T=j[u]),d=B*(F=j[u+1]),m=B*(D=j[u+2]),v=B*(_=j[u+3]),c+=z*T,f+=z*F,g+=z*D,p+=z*_,X=L,a=0;B>a;a++)X.r=T,X.g=F,X.b=D,X.a=_,X=X.next;for(a=1;B>a;a++)s=u+((a>U?U:a)<<2),c+=(X.r=T=j[s])*(S=B-a),f+=(X.g=F=j[s+1])*S,g+=(X.b=D=j[s+2])*S,p+=(X.a=_=j[s+3])*S,y+=T,b+=F,w+=D,C+=_,X=X.next;for(G=L,k=N,e=0;A>e;e++)j[u+3]=_=p*O>>V,0!=_?(_=255/_,j[u]=(c*O>>V)*_,j[u+1]=(f*O>>V)*_,j[u+2]=(g*O>>V)*_):j[u]=j[u+1]=j[u+2]=0,c-=x,f-=d,g-=m,p-=v,x-=G.r,d-=G.g,m-=G.b,v-=G.a,s=h+((s=e+R+1)e;e++){for(b=w=C=y=f=g=p=c=0,u=e<<2,x=B*(T=j[u]),d=B*(F=j[u+1]),m=B*(D=j[u+2]),v=B*(_=j[u+3]),c+=z*T,f+=z*F,g+=z*D,p+=z*_,X=L,a=0;B>a;a++)X.r=T,X.g=F,X.b=D,X.a=_,X=X.next;for(l=A,a=1;R>=a;a++)u=l+e<<2,c+=(X.r=T=j[u])*(S=B-a),f+=(X.g=F=j[u+1])*S,g+=(X.b=D=j[u+2])*S,p+=(X.a=_=j[u+3])*S,y+=T,b+=F,w+=D,C+=_,X=X.next,P>a&&(l+=A);for(u=e,G=L,k=N,o=0;I>o;o++)s=u<<2,j[s+3]=_=p*O>>V,_>0?(_=255/_,j[s]=(c*O>>V)*_,j[s+1]=(f*O>>V)*_,j[s+2]=(g*O>>V)*_):j[s]=j[s+1]=j[s+2]=0,c-=x,f-=d,g-=m,p-=v,x-=G.r,d-=G.g,m-=G.b,v-=G.a,s=e+((s=o+B) 0.0) {\n color.rgb = (color.rgb - 0.5) / (1.0 - contrast) + 0.5;\n } else {\n color.rgb = (color.rgb - 0.5) * (1.0 + contrast) + 0.5;\n }\n\n gl_FragColor = color;\n }\n "),this.properties.contrast=t.Filter.clamp(-1,r,1)||0}return __extends(r,e),r.prototype.iterateCanvas=function(t){var e=this.properties.contrast;e>0?(t.r=(t.r-.5)/(1-e)+.5,t.g=(t.g-.5)/(1-e)+.5,t.b=(t.b-.5)/(1-e)+.5):(t.r=(t.r-.5)*(1+e)+.5,t.g=(t.g-.5)*(1+e)+.5,t.b=(t.b-.5)*(1+e)+.5)},r}(t.IterableFilter);t.Contrast=e}(e=t.filter||(t.filter={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(e){var r=function(r){function n(t,e,i){r.call(this,null,"\n uniform sampler2D texture;\n uniform sampler2D map;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n color.r = texture2D(map, vec2(color.r)).r;\n color.g = texture2D(map, vec2(color.g)).g;\n color.b = texture2D(map, vec2(color.b)).b;\n gl_FragColor = color;\n }\n "),this.red=t,this.green=e,this.blue=i,t=n.splineInterpolate(t),1==arguments.length?e=i=t:(e=n.splineInterpolate(e),i=n.splineInterpolate(i)),this.red=t,this.green=e,this.blue=i}return __extends(n,r),n.prototype.drawWebGL=function(t){for(var e=[],r=0;256>r;r++)e.splice(e.length,0,this.red[r],this.green[r],this.blue[r],255);var n=t.createTexture();n.initFromBytes(256,1,e),n.use(1);var i=t.getShader(this);i.textures({map:1}),t.getTexture().use(),t.getNextTexture().drawTo(function(){i.uniforms({}).drawRect()})},n.prototype.iterateCanvas=function(t){t.getIndex();t.r=this.red[255*t.r]/255,t.g=this.green[255*t.g]/255,t.b=this.blue[255*t.b]/255},n.splineInterpolate=function(r){for(var n=new t.util.SplineInterpolator(r),i=[],o=0;256>o;o++)i.push(e.Filter.clamp(0,Math.floor(256*n.interpolate(o/255)),255));return i},n}(e.IterableFilter);e.Curves=r}(e=t.filter||(t.filter={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(t){var e=function(t){function e(e){t.call(this,null,"\n uniform sampler2D texture;\n uniform float exponent;\n uniform float strength;\n uniform vec2 texSize;\n varying vec2 texCoord;\n\n void main() {\n vec4 center = texture2D(texture, texCoord);\n vec4 color = vec4(0.0);\n float total = 0.0;\n\n for (float x = -4.0; x <= 4.0; x += 1.0) {\n for (float y = -4.0; y <= 4.0; y += 1.0) {\n vec4 sample = texture2D(texture, texCoord + vec2(x, y) / texSize);\n float weight = 1.0 - abs(dot(sample.rgb - center.rgb, vec3(0.25)));\n weight = pow(weight, exponent);\n color += sample * weight;\n total += weight;\n }\n }\n\n gl_FragColor = color / total;\n }\n "),this.properties.exponent=e}return __extends(e,t),e.prototype.drawWebGL=function(t){var e=t.getShader(this),r=this.getProperties();r.texSize=[t.getSource().width,t.getSource().width],t.getTexture().use(),t.getNextTexture().drawTo(function(){e.uniforms(r).drawRect()})},e.prototype.drawCanvas=function(t){var e,r,n,i,o,a,s,l,u,h,c,f=this.properties.exponent,g=t.getImageData(),p=g.data,x=new Uint8ClampedArray(g.data);for(e=0;e=a;a+=1)for(s=-4;4>=s;s+=1)l=Math.min(g.width-1,Math.max(0,e+a)),u=Math.min(g.height-1,Math.max(0,r+s)),h=4*(l+u*g.width),c=Math.pow(1-Math.abs(.25*(x[h]/255-x[n]/255)+.25*(x[h+1]/255-x[n+1]/255)+.25*(x[h+2]/255-x[n+2]/255)),f),i[0]+=x[h]/255*c,i[1]+=x[h+1]/255*c,i[2]+=x[h+2]/255*c,i[3]+=x[h+3]/255*c,o+=c;p[n]=i[0]/o*255,p[n+1]=i[1]/o*255,p[n+2]=i[2]/o*255,p[n+3]=i[3]/o*255}},e}(t.Filter);t.Denoise=e}(e=t.filter||(t.filter={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(t){var e=function(e){function r(r,n,i,o){e.call(this,null,"\n uniform sampler2D texture;\n uniform vec2 center;\n uniform float angle;\n uniform float scale;\n uniform vec2 texSize;\n varying vec2 texCoord;\n\n float pattern() { float s = sin(angle), c = cos(angle);\n vec2 tex = texCoord * texSize - center;\n vec2 point = vec2(\n c * tex.x - s * tex.y,\n s * tex.x + c * tex.y\n ) * scale;\n\n return (sin(point.x) * sin(point.y)) * 4.0;\n }\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n float average = (color.r + color.g + color.b) / 3.0;\n gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n }\n "),this.centerX=r,this.centerY=n,this.properties.angle=t.Filter.clamp(0,i,Math.PI/2),this.properties.scale=Math.PI/o}return __extends(r,e),r.prototype.drawWebGL=function(t){var e=t.getShader(this),r=this.getProperties();r.texSize=[t.getSource().width,t.getSource().width],r.center=[this.centerX,this.centerY],t.getTexture().use(),t.getNextTexture().drawTo(function(){e.uniforms(r).drawRect()})},r.prototype.iterateCanvas=function(e){var r=e.getImageData(),n=e.getIndex()/4%r.width,i=Math.floor(e.getIndex()/4/r.width),o=(e.r+e.g+e.b)/3,a=t.ColorHalfTone.pattern(this.properties.angle,n,i,this.centerX,this.centerY,this.properties.scale),s=10*o-5+a;e.r=s,e.g=s,e.b=s},r}(t.IterableFilter);t.DotScreen=e}(e=t.filter||(t.filter={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(e){var r=function(r){function n(n){r.call(this,null,"\n uniform sampler2D texture;\n uniform float hue;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n\n /* hue adjustment, wolfram alpha: RotationTransform[angle, {1, 1, 1}][{x, y, z}] */\n float angle = hue * 3.14159265;\n float s = sin(angle), c = cos(angle);\n vec3 weights = (vec3(2.0 * c, -sqrt(3.0) * s - c, sqrt(3.0) * s - c) + 1.0) / 3.0;\n color.rgb = vec3(\n dot(color.rgb, weights.xyz),\n dot(color.rgb, weights.zxy),\n dot(color.rgb, weights.yzx)\n );\n\n gl_FragColor = color;\n }\n "),this.properties.hue=e.Filter.clamp(-1,n,1)||0;var i=3.14159265*n,o=Math.sin(i),a=Math.cos(i);this.weights=new t.util.Vector3(2*a,-Math.sqrt(3)*o-a,Math.sqrt(3)*o-a).addScalar(1).divideScalar(3)}return __extends(n,r),n.prototype.iterateCanvas=function(t){var e=t.toVector3();t.r=e.dot(this.weights),t.g=e.dotScalars(this.weights.z,this.weights.x,this.weights.y),t.b=e.dotScalars(this.weights.y,this.weights.z,this.weights.x)},n}(e.IterableFilter);e.Hue=r}(e=t.filter||(t.filter={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(e){var r=function(e){function r(){e.apply(this,arguments)}return __extends(r,e),r.prototype.drawCanvas=function(t){return r.drawCanvas([this],t)},r.prototype.iterateCanvas=function(t){throw new Error("Must be implemented")},r.drawCanvas=function(e,r){for(var n,i=r.getImageData(),o=0;o 0.0) {\n color.rgb += (average - color.rgb) * (1.0 - 1.0 / (1.001 - saturation));\n } else {\n color.rgb += (average - color.rgb) * (-saturation);\n }\n\n gl_FragColor = color;\n }\n "),this.properties.saturation=t.Filter.clamp(-1,r,1)||0}return __extends(r,e),r.prototype.iterateCanvas=function(t){var e=this.properties.saturation,r=(t.r+t.g+t.b)/3;e>0?(t.r+=(r-t.r)*(1-1/(1.001-e)),t.g+=(r-t.g)*(1-1/(1.001-e)),t.b+=(r-t.b)*(1-1/(1.001-e))):(t.r+=(r-t.r)*-e,t.g+=(r-t.g)*-e,t.b+=(r-t.b)*-e)},r}(t.IterableFilter);t.Saturation=e}(e=t.filter||(t.filter={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(t){var e=function(e){function r(r){e.call(this,null,"\n uniform sampler2D texture;\n uniform float amount;\n varying vec2 texCoord;\n\n void main() {\n vec4 color = texture2D(texture, texCoord);\n float r = color.r;\n float g = color.g;\n float b = color.b;\n\n color.r = min(1.0, (r * (1.0 - (0.607 * amount))) + (g * (0.769 * amount)) + (b * (0.189 * amount)));\n color.g = min(1.0, (r * 0.349 * amount) + (g * (1.0 - (0.314 * amount))) + (b * 0.168 * amount));\n color.b = min(1.0, (r * 0.272 * amount) + (g * 0.534 * amount) + (b * (1.0 - (0.869 * amount))));\n\n gl_FragColor = color;\n }\n "),this.properties.amount=t.Filter.clamp(-1,r,1)||0}return __extends(r,e),r.prototype.iterateCanvas=function(t){var e=t.r,r=t.g,n=t.b,i=this.properties.amount;t.r=Math.min(1,e*(1-.607*i)+r*(.769*i)+n*(.189*i)),t.g=Math.min(1,.349*e*i+r*(1-.314*i)+.168*n*i),t.b=Math.min(1,.272*e*i+.534*r*i+n*(1-.869*i))},r}(t.IterableFilter);t.Sepia=e}(e=t.filter||(t.filter={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(e){var r=function(r){function n(t,e){r.call(this,null,"\n uniform sampler2D blurredTexture;\n uniform sampler2D originalTexture;\n uniform float strength;\n uniform float threshold;\n varying vec2 texCoord;\n\n void main() {\n vec4 blurred = texture2D(blurredTexture, texCoord);\n vec4 original = texture2D(originalTexture, texCoord);\n gl_FragColor = mix(blurred, original, 1.0 + strength);\n }\n "),this.properties.radius=t,this.properties.strength=e}return __extends(n,r),n.prototype.drawWebGL=function(t){var r=t.getShader(this),n=this.properties.radius,i=this.properties.strength,o=t.createTexture();t.getTexture().use(),o.drawTo(t.getDefaultShader().drawRect.bind(t.getDefaultShader())),o.use(1);var a=new e.Blur(n);a.drawWebGL(t),r.textures({originalTexture:1}),t.getTexture().use(),t.getNextTexture().drawTo(function(){r.uniforms({strength:i}).drawRect()}),o.unuse(1)},n.prototype.drawCanvas=function(r){var n=new Uint8ClampedArray(r.getImageData().data),i=this.properties.radius,o=this.properties.strength+1,a=new e.Blur(i);a.drawCanvas(r);for(var s=r.getImageData(),l=s.data,u=0;ue;e++)this.xa.push(t[e][0]),this.ya.push(t[e][1]);for(this.u[0]=0,this.y2[0]=0,e=1;r-1>e;++e){var n=this.xa[e+1]-this.xa[e-1],i=(this.xa[e]-this.xa[e-1])/n,o=i*this.y2[e-1]+2;this.y2[e]=(i-1)/o;var a=(this.ya[e+1]-this.ya[e])/(this.xa[e+1]-this.xa[e])-(this.ya[e]-this.ya[e-1])/(this.xa[e]-this.xa[e-1]);this.u[e]=(6*a/n-i*this.u[e-1])/o}for(this.y2[r-1]=0,e=r-2;e>=0;--e)this.y2[e]=this.y2[e]*this.y2[e+1]+this.u[e]}return t.prototype.interpolate=function(t){for(var e=this.ya.length,r=0,n=e-1;n-r>1;){var i=n+r>>1;this.xa[i]>t?n=i:r=i}var o=this.xa[n]-this.xa[r],a=(this.xa[n]-t)/o,s=(t-this.xa[r])/o;return a*this.ya[r]+s*this.ya[n]+((a*a*a-a)*this.y2[r]+(s*s*s-s)*this.y2[n])*(o*o)/6},t}();t.SplineInterpolator=e}(e=t.util||(t.util={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(t){var e=function(){function t(t,e){this.x=t,this.y=e}return t.prototype.dotScalars=function(t,e){return this.x*t+this.y*e},t}();t.Vector2=e}(e=t.util||(t.util={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(t){var e=function(){function t(t,e,r){this.x=t,this.y=e,this.z=r}return t.prototype.addScalar=function(t){return this.x+=t,this.y+=t,this.z+=t,this},t.prototype.multiplyScalar=function(t){return this.x*=t,this.y*=t,this.z*=t,this},t.prototype.divideScalar=function(t){if(0!==t){var e=1/t;this.x*=e,this.y*=e,this.z*=e}else this.x=0,this.y=0,this.z=0;return this},t.prototype.length=function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},t.prototype.dot=function(t){return this.x*t.x+this.y*t.y+this.z*t.z},t.prototype.dotScalars=function(t,e,r){return this.x*t+this.y*e+this.z*r},t}();t.Vector3=e}(e=t.util||(t.util={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(e){var r=function(){function e(){this.canvas=document.createElement("canvas"),this.gl=this.canvas.getContext("experimental-webgl",{premultipliedAlpha:!1}),this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL,1),this.source=null,this.sourceTexture=null,this.textures=null,this.currentTexture=0,this.gl.shaderCache={}}return e.prototype.setSource=function(e){return this.source&&this.cleanUp(),this.source=e,this.sourceTexture=t.webgl.Texture.fromElement(this.gl,e.element),this.initialize(),this.sourceTexture.use(),this.getTexture().drawTo(this.getDefaultShader().drawRect.bind(this.getDefaultShader())),this},e.prototype.getSource=function(){return this.source},e.prototype.applyFilter=function(t){return t.drawWebGL(this),this},e.prototype.applyFilters=function(t){var e=this;return t.forEach(function(t){t.drawWebGL(e)}),this},e.prototype.render=function(){this.getTexture().use(),this.getFlippedShader().drawRect()},e.prototype.getCanvas=function(){return this.canvas},e.prototype.getTexture=function(){return this.textures[this.currentTexture%2]},e.prototype.getNextTexture=function(){return this.textures[++this.currentTexture%2]},e.prototype.createTexture=function(){return new t.webgl.Texture(this.gl,this.source.width,this.source.height,this.gl.RGBA,this.gl.UNSIGNED_BYTE)},e.prototype.getShader=function(e){var r=e.getVertexSource()+e.getFragmentSource();return this.gl.shaderCache.hasOwnProperty(r)?this.gl.shaderCache[r]:new t.webgl.Shader(this.gl,e.getVertexSource(),e.getFragmentSource())},e.prototype.getDefaultShader=function(){return this.gl.shaderCache.def||(this.gl.shaderCache.def=new t.webgl.Shader(this.gl)),this.gl.shaderCache.def},e.prototype.getFlippedShader=function(){return this.gl.shaderCache.flipped||(this.gl.shaderCache.flipped=new t.webgl.Shader(this.gl,null,"\n uniform sampler2D texture;\n varying vec2 texCoord;\n\n void main() {\n gl_FragColor = texture2D(texture, vec2(texCoord.x, 1.0 - texCoord.y));\n }\n ")), 2 | this.gl.shaderCache.flipped},e.prototype.initialize=function(){this.canvas.width=this.source.width,this.canvas.height=this.source.height;for(var t=[],e=0;2>e;e++)t.push(this.createTexture());this.textures=t},e.prototype.cleanUp=function(){this.sourceTexture.destroy();for(var t=0;2>t;t++)this.textures[t].destroy();this.textures=null},e}();e.Renderer=r}(e=t.webgl||(t.webgl={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(t){function e(t,e,r){var n=t.createShader(e);if(t.shaderSource(n,r),t.compileShader(n),!t.getShaderParameter(n,t.COMPILE_STATUS))throw"compile error: "+t.getShaderInfoLog(n);return n}function r(t){return"[object Array]"===Object.prototype.toString.call(t)}function n(t){return"[object Number]"===Object.prototype.toString.call(t)}var i=function(){function t(r,n,i){if(this.gl=r,this.vertexSource=n||t.defaultVertexSource,this.fragmentSource=i||t.defaultFragmentSource,this.fragmentSource="precision highp float;"+this.fragmentSource,this.vertexAttribute=null,this.texCoordAttribute=null,this.program=r.createProgram(),r.attachShader(this.program,e(r,r.VERTEX_SHADER,this.vertexSource)),r.attachShader(this.program,e(r,r.FRAGMENT_SHADER,this.fragmentSource)),r.linkProgram(this.program),!r.getProgramParameter(this.program,r.LINK_STATUS))throw"link error: "+r.getProgramInfoLog(this.program)}return t.prototype.textures=function(t){this.gl.useProgram(this.program);for(var e in t)t.hasOwnProperty(e)&&this.gl.uniform1i(this.gl.getUniformLocation(this.program,e),t[e]);return this},t.prototype.uniforms=function(t){this.gl.useProgram(this.program);for(var e in t)if(t.hasOwnProperty(e)){var i=this.gl.getUniformLocation(this.program,e);if(null!==i){var o=t[e];if(r(o))switch(o.length){case 1:this.gl.uniform1fv(i,new Float32Array(o));break;case 2:this.gl.uniform2fv(i,new Float32Array(o));break;case 3:this.gl.uniform3fv(i,new Float32Array(o));break;case 4:this.gl.uniform4fv(i,new Float32Array(o));break;case 9:this.gl.uniformMatrix3fv(i,!1,new Float32Array(o));break;case 16:this.gl.uniformMatrix4fv(i,!1,new Float32Array(o));break;default:throw"dont't know how to load uniform \""+e+'" of length '+o.length}else{if(!n(o))throw'attempted to set uniform "'+e+'" to invalid value '+(o||"undefined").toString();this.gl.uniform1f(i,o)}}}return this},t.prototype.drawRect=function(t,e,r,n){var i,o=this.gl.getParameter(this.gl.VIEWPORT);e=e!==i?(e-o[1])/o[3]:0,t=t!==i?(t-o[0])/o[2]:0,r=r!==i?(r-o[0])/o[2]:1,n=n!==i?(n-o[1])/o[3]:1,this.gl.vertexBuffer||(this.gl.vertexBuffer=this.gl.createBuffer()),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.gl.vertexBuffer),this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array([t,e,t,n,r,e,r,n]),this.gl.STATIC_DRAW),this.gl.texCoordBuffer||(this.gl.texCoordBuffer=this.gl.createBuffer(),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.gl.texCoordBuffer),this.gl.bufferData(this.gl.ARRAY_BUFFER,new Float32Array([0,0,0,1,1,0,1,1]),this.gl.STATIC_DRAW)),null==this.vertexAttribute&&(this.vertexAttribute=this.gl.getAttribLocation(this.program,"vertex"),this.gl.enableVertexAttribArray(this.vertexAttribute)),null==this.texCoordAttribute&&(this.texCoordAttribute=this.gl.getAttribLocation(this.program,"_texCoord"),this.gl.enableVertexAttribArray(this.texCoordAttribute)),this.gl.useProgram(this.program),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.gl.vertexBuffer),this.gl.vertexAttribPointer(this.vertexAttribute,2,this.gl.FLOAT,!1,0,0),this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.gl.texCoordBuffer),this.gl.vertexAttribPointer(this.texCoordAttribute,2,this.gl.FLOAT,!1,0,0),this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4)},t.prototype.destroy=function(){this.gl.deleteProgram(this.program),this.program=null},t.defaultVertexSource="\nattribute vec2 vertex;\nattribute vec2 _texCoord;\nvarying vec2 texCoord;\n\nvoid main() {\n texCoord = _texCoord;\n gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);\n}",t.defaultFragmentSource="\nuniform sampler2D texture;\nvarying vec2 texCoord;\n\nvoid main() {\n gl_FragColor = texture2D(texture, texCoord);\n}",t}();t.Shader=i}(e=t.webgl||(t.webgl={}))}(jsfx||(jsfx={}));var jsfx;!function(t){var e;!function(t){var e=function(){function t(t,e,r,n,i){void 0===n&&(n=t.RGBA),void 0===i&&(i=t.UNSIGNED_BYTE),this.gl=t,this.width=e,this.height=r,this.format=n,this.type=i,this.id=t.createTexture(),this.element=null,t.bindTexture(t.TEXTURE_2D,this.id),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MAG_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_MIN_FILTER,t.LINEAR),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(t.TEXTURE_2D,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),e&&r&&t.texImage2D(t.TEXTURE_2D,0,this.format,e,r,0,this.format,this.type,null)}return t.prototype.loadContentsOf=function(t){this.element=t,this.width=t.width,this.height=t.height,this.gl.bindTexture(this.gl.TEXTURE_2D,this.id),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.format,this.format,this.type,t)},t.prototype.initFromBytes=function(t,e,r){this.width=t,this.height=e,this.format=this.gl.RGBA,this.type=this.gl.UNSIGNED_BYTE,this.gl.bindTexture(this.gl.TEXTURE_2D,this.id),this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGBA,t,e,0,this.gl.RGBA,this.type,new Uint8Array(r))},t.prototype.use=function(t){this.gl.activeTexture(this.gl.TEXTURE0+(t||0)),this.gl.bindTexture(this.gl.TEXTURE_2D,this.id)},t.prototype.unuse=function(t){this.gl.activeTexture(this.gl.TEXTURE0+(t||0)),this.gl.bindTexture(this.gl.TEXTURE_2D,null)},t.prototype.drawTo=function(t){if(this.gl.frameBuffer=this.gl.frameBuffer||this.gl.createFramebuffer(),this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,this.gl.frameBuffer),this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER,this.gl.COLOR_ATTACHMENT0,this.gl.TEXTURE_2D,this.id,0),this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER)!==this.gl.FRAMEBUFFER_COMPLETE)throw new Error("incomplete framebuffer");this.gl.viewport(0,0,this.width,this.height),t(),this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,null)},t.prototype.destroy=function(){this.gl.deleteTexture(this.id),this.id=null},t.fromElement=function(e,r){var n=new t(e,0,0);return n.loadContentsOf(r),n},t}();t.Texture=e}(e=t.webgl||(t.webgl={}))}(jsfx||(jsfx={})),"undefined"!=typeof module&&(module.exports=jsfx); -------------------------------------------------------------------------------- /exports.ts: -------------------------------------------------------------------------------- 1 | declare var module : any; 2 | 3 | if (typeof module !== 'undefined') { 4 | module.exports = jsfx 5 | } 6 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var shell = require('gulp-shell'); 3 | var runseq = require('run-sequence'); 4 | var tslint = require('gulp-tslint'); 5 | var rename = require('gulp-rename'); 6 | var uglify = require('gulp-uglify'); 7 | var tsproject = require('tsproject'); 8 | 9 | var paths = { 10 | tscripts: { 11 | src: ['src/**/*.ts', 'exports.ts'], 12 | dest: 'build' 13 | } 14 | }; 15 | 16 | gulp.task('default', ['lint', 'buildrun']); 17 | 18 | // ** Running ** // 19 | 20 | gulp.task('run', shell.task([ 21 | 'node build/index.js' 22 | ])); 23 | 24 | gulp.task('buildrun', function(cb) { 25 | runseq('build', 'run', cb); 26 | }); 27 | 28 | // ** Watching ** // 29 | 30 | gulp.task('watch', function() { 31 | gulp.watch(paths.tscripts.src, ['compile:typescript']); 32 | }); 33 | 34 | gulp.task('watchrun', function() { 35 | gulp.watch(paths.tscripts.src, runseq('compile:typescript', 'run')); 36 | }); 37 | 38 | // ** Compilation ** // 39 | 40 | gulp.task('build', ['compile:typescript']); 41 | gulp.task('compile:typescript', function() { 42 | tsproject 43 | .src('.') 44 | .pipe(gulp.dest(paths.tscripts.dest)); 45 | 46 | //return gulp 47 | // .src(paths.tscripts.src) 48 | // .pipe(tsc({ 49 | // module: "commonjs", 50 | // out: "jsfx.js", 51 | // target: "ES5" 52 | // })) 53 | // .pipe(gulp.dest(paths.tscripts.dest)) 54 | // .pipe(uglify()) 55 | // .pipe(rename({ extname: '.min.js' })) 56 | // .pipe(gulp.dest(paths.tscripts.dest)); 57 | }); 58 | 59 | // ** Linting ** // 60 | 61 | gulp.task('lint', ['lint:default']); 62 | gulp.task('lint:default', function() { 63 | return gulp.src(paths.tscripts.src) 64 | .pipe(tslint()) 65 | .pipe(tslint.report('prose', { 66 | emitError: false 67 | })); 68 | }); 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsfx", 3 | "version": "0.0.1", 4 | "description": "An image effects library powered by WebGL, with a Javascript fallback.", 5 | "homepage": "http://jsfx.inssein.com", 6 | "author": "Hussein Jafferjee ", 7 | "keywords": [ 8 | "webgl", 9 | "canvas", 10 | "image", 11 | "effects" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/inssein/jsfx.git" 16 | }, 17 | "license": "MIT", 18 | "dependencies": {}, 19 | "devDependencies": { 20 | "gulp": "^3.6.2", 21 | "gulp-rename": "^1.2.2", 22 | "gulp-shell": "^0.4.2", 23 | "gulp-tslint": "^2.0.0", 24 | "gulp-uglify": "^1.2.0", 25 | "tsproject": "^1.0.5", 26 | "run-sequence": "^1.1.1" 27 | }, 28 | "scripts": { 29 | "main": "gulp buildrun", 30 | "test": "gulp build && mocha" 31 | }, 32 | "main": "build/jsfx.js" 33 | } 34 | -------------------------------------------------------------------------------- /src/canvas/renderer.ts: -------------------------------------------------------------------------------- 1 | declare var Buffer : any; 2 | declare var require : any; 3 | 4 | namespace jsfx.canvas { 5 | export class Renderer implements jsfx.RendererInterface { 6 | private canvas : HTMLCanvasElement; 7 | private ctx : CanvasRenderingContext2D; 8 | private source : jsfx.Source; 9 | private imageData : ImageData; 10 | 11 | constructor() { 12 | this.canvas = jsfx.canvas.Renderer.createCanvas(); 13 | this.ctx = this.canvas.getContext("2d"); 14 | this.source = null; 15 | this.imageData = null; 16 | } 17 | 18 | setSource(source : jsfx.Source) : jsfx.RendererInterface { 19 | // first, clean up 20 | if (this.source) { 21 | this.cleanUp(); 22 | } 23 | 24 | // re-set data and start rendering 25 | this.source = source; 26 | this.canvas.width = source.width; 27 | this.canvas.height = source.height; 28 | 29 | // draw the image on to a canvas we can manipulate 30 | this.ctx.drawImage(source.element, 0, 0, source.width, source.height); 31 | 32 | // store the pixels 33 | this.imageData = this.ctx.getImageData(0, 0, source.width, source.height); 34 | 35 | return this; 36 | } 37 | 38 | public getSource() : jsfx.Source { 39 | return this.source; 40 | } 41 | 42 | public applyFilter(filter : jsfx.filter.FilterInterface) : jsfx.RendererInterface { 43 | filter.drawCanvas(this); 44 | 45 | return this; 46 | } 47 | 48 | public applyFilters(filters : jsfx.filter.FilterInterface[]) : jsfx.RendererInterface { 49 | var stack : jsfx.filter.IterableFilterInterface[] = []; 50 | var filter : jsfx.filter.FilterInterface; 51 | 52 | for (var i : number = 0; i < filters.length; i++) { 53 | filter = filters[i]; 54 | 55 | if (filter instanceof jsfx.filter.IterableFilter) { 56 | stack.push( filter); 57 | } else { 58 | // if there if something in the stack, apply that first 59 | if (stack.length > 0) { 60 | this.applyFilterStack(stack); 61 | stack = []; 62 | } 63 | 64 | // apply current filter 65 | this.applyFilter(filter); 66 | } 67 | } 68 | 69 | // if there is still a stack left, apply it 70 | if (stack.length > 0) { 71 | this.applyFilterStack(stack); 72 | } 73 | 74 | return this; 75 | } 76 | 77 | public render() : void { 78 | this.ctx.putImageData(this.imageData, 0, 0); 79 | } 80 | 81 | public getCanvas() : HTMLCanvasElement { 82 | return this.canvas; 83 | } 84 | 85 | public getContext() : CanvasRenderingContext2D { 86 | return this.ctx; 87 | } 88 | 89 | public getImageData() : ImageData { 90 | return this.imageData; 91 | } 92 | 93 | public setImageData(v : ImageData) : void { 94 | this.imageData = v; 95 | } 96 | 97 | private applyFilterStack(stack : jsfx.filter.IterableFilterInterface[]) { 98 | jsfx.filter.IterableFilter.drawCanvas(stack, this); 99 | 100 | return this; 101 | } 102 | 103 | private cleanUp() : void { 104 | this.imageData = null; 105 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); 106 | } 107 | 108 | static createCanvas() : HTMLCanvasElement { 109 | return typeof Buffer !== "undefined" && typeof window === "undefined" ? 110 | new (require("canvas"))(100, 100) : 111 | document.createElement("canvas"); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/filter/blur.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Blur 4 | * @description This is the TriangleBlur from glfx, but for the canvas implementation, we are cheating by 5 | * using StackBlur. The implementations are obviously very different, but the results are very close. 6 | * @param radius The radius of the pyramid convolved with the image. 7 | */ 8 | export class Blur extends Filter { 9 | constructor(radius? : number) { 10 | super(null, ` 11 | uniform sampler2D texture; 12 | uniform vec2 delta; 13 | varying vec2 texCoord; 14 | 15 | void main() { 16 | vec4 color = vec4(0.0); 17 | float total = 0.0; 18 | 19 | /* randomize the lookup values to hide the fixed number of samples */ 20 | //float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0); 21 | 22 | vec3 scale = vec3(12.9898, 78.233, 151.7182); 23 | float offset = fract(sin(dot(gl_FragCoord.xyz + 0.0, scale)) * 43758.5453 + 0.0); 24 | 25 | for (float t = -30.0; t <= 30.0; t++) { 26 | float percent = (t + offset - 0.5) / 30.0; 27 | float weight = 1.0 - abs(percent); 28 | vec4 sample = texture2D(texture, texCoord + delta * percent); 29 | 30 | /* switch to pre-multiplied alpha to correctly blur transparent images */ 31 | sample.rgb *= sample.a; 32 | 33 | color += sample * weight; 34 | total += weight; 35 | } 36 | 37 | gl_FragColor = color / total; 38 | 39 | /* switch back from pre-multiplied alpha */ 40 | gl_FragColor.rgb /= gl_FragColor.a + 0.00001; 41 | } 42 | `); 43 | 44 | // set properties 45 | this.properties.radius = radius; 46 | } 47 | 48 | public drawWebGL(renderer : jsfx.webgl.Renderer) { 49 | var shader = renderer.getShader(this); 50 | var firstPass = {delta: [this.properties.radius / renderer.getSource().width, 0]}; 51 | var secondPass = {delta: [0, this.properties.radius / renderer.getSource().height]}; 52 | 53 | renderer.getTexture().use(); 54 | renderer.getNextTexture().drawTo(function () { 55 | shader.uniforms(firstPass).drawRect(); 56 | }); 57 | 58 | renderer.getTexture().use(); 59 | renderer.getNextTexture().drawTo(function () { 60 | shader.uniforms(secondPass).drawRect(); 61 | }); 62 | } 63 | 64 | public drawCanvas(renderer : jsfx.canvas.Renderer) : void { 65 | var imageData : ImageData = renderer.getImageData(); 66 | var pixels = imageData.data; 67 | var radius = this.properties.radius; 68 | var width = imageData.width; 69 | var height = imageData.height; 70 | 71 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum, 72 | r_out_sum, g_out_sum, b_out_sum, a_out_sum, 73 | r_in_sum, g_in_sum, b_in_sum, a_in_sum, 74 | pr, pg, pb, pa, rbs; 75 | 76 | var div = radius + radius + 1; 77 | var widthMinus1 = width - 1; 78 | var heightMinus1 = height - 1; 79 | var radiusPlus1 = radius + 1; 80 | var sumFactor = radiusPlus1 * ( radiusPlus1 + 1 ) / 2; 81 | 82 | var stackStart = new BlurStack(); 83 | var stack = stackStart; 84 | for (i = 1; i < div; i++) { 85 | stack = stack.next = new BlurStack(); 86 | if (i == radiusPlus1) var stackEnd = stack; 87 | } 88 | stack.next = stackStart; 89 | var stackIn : BlurStack = null; 90 | var stackOut : BlurStack = null; 91 | 92 | yw = yi = 0; 93 | 94 | var mul_sum = mul_table[radius]; 95 | var shg_sum = shg_table[radius]; 96 | 97 | for (y = 0; y < height; y++) { 98 | r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0; 99 | 100 | r_out_sum = radiusPlus1 * ( pr = pixels[yi] ); 101 | g_out_sum = radiusPlus1 * ( pg = pixels[yi + 1] ); 102 | b_out_sum = radiusPlus1 * ( pb = pixels[yi + 2] ); 103 | a_out_sum = radiusPlus1 * ( pa = pixels[yi + 3] ); 104 | 105 | r_sum += sumFactor * pr; 106 | g_sum += sumFactor * pg; 107 | b_sum += sumFactor * pb; 108 | a_sum += sumFactor * pa; 109 | 110 | stack = stackStart; 111 | 112 | for (i = 0; i < radiusPlus1; i++) { 113 | stack.r = pr; 114 | stack.g = pg; 115 | stack.b = pb; 116 | stack.a = pa; 117 | stack = stack.next; 118 | } 119 | 120 | for (i = 1; i < radiusPlus1; i++) { 121 | p = yi + (( widthMinus1 < i ? widthMinus1 : i ) << 2 ); 122 | r_sum += ( stack.r = ( pr = pixels[p])) * ( rbs = radiusPlus1 - i ); 123 | g_sum += ( stack.g = ( pg = pixels[p + 1])) * rbs; 124 | b_sum += ( stack.b = ( pb = pixels[p + 2])) * rbs; 125 | a_sum += ( stack.a = ( pa = pixels[p + 3])) * rbs; 126 | 127 | r_in_sum += pr; 128 | g_in_sum += pg; 129 | b_in_sum += pb; 130 | a_in_sum += pa; 131 | 132 | stack = stack.next; 133 | } 134 | 135 | stackIn = stackStart; 136 | stackOut = stackEnd; 137 | for (x = 0; x < width; x++) { 138 | pixels[yi + 3] = pa = (a_sum * mul_sum) >> shg_sum; 139 | if (pa != 0) { 140 | pa = 255 / pa; 141 | pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa; 142 | pixels[yi + 1] = ((g_sum * mul_sum) >> shg_sum) * pa; 143 | pixels[yi + 2] = ((b_sum * mul_sum) >> shg_sum) * pa; 144 | } else { 145 | pixels[yi] = pixels[yi + 1] = pixels[yi + 2] = 0; 146 | } 147 | 148 | r_sum -= r_out_sum; 149 | g_sum -= g_out_sum; 150 | b_sum -= b_out_sum; 151 | a_sum -= a_out_sum; 152 | 153 | r_out_sum -= stackIn.r; 154 | g_out_sum -= stackIn.g; 155 | b_out_sum -= stackIn.b; 156 | a_out_sum -= stackIn.a; 157 | 158 | p = ( yw + ( ( p = x + radius + 1 ) < widthMinus1 ? p : widthMinus1 ) ) << 2; 159 | 160 | r_in_sum += ( stackIn.r = pixels[p]); 161 | g_in_sum += ( stackIn.g = pixels[p + 1]); 162 | b_in_sum += ( stackIn.b = pixels[p + 2]); 163 | a_in_sum += ( stackIn.a = pixels[p + 3]); 164 | 165 | r_sum += r_in_sum; 166 | g_sum += g_in_sum; 167 | b_sum += b_in_sum; 168 | a_sum += a_in_sum; 169 | 170 | stackIn = stackIn.next; 171 | 172 | r_out_sum += ( pr = stackOut.r ); 173 | g_out_sum += ( pg = stackOut.g ); 174 | b_out_sum += ( pb = stackOut.b ); 175 | a_out_sum += ( pa = stackOut.a ); 176 | 177 | r_in_sum -= pr; 178 | g_in_sum -= pg; 179 | b_in_sum -= pb; 180 | a_in_sum -= pa; 181 | 182 | stackOut = stackOut.next; 183 | 184 | yi += 4; 185 | } 186 | yw += width; 187 | } 188 | 189 | for (x = 0; x < width; x++) { 190 | g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0; 191 | 192 | yi = x << 2; 193 | r_out_sum = radiusPlus1 * ( pr = pixels[yi]); 194 | g_out_sum = radiusPlus1 * ( pg = pixels[yi + 1]); 195 | b_out_sum = radiusPlus1 * ( pb = pixels[yi + 2]); 196 | a_out_sum = radiusPlus1 * ( pa = pixels[yi + 3]); 197 | 198 | r_sum += sumFactor * pr; 199 | g_sum += sumFactor * pg; 200 | b_sum += sumFactor * pb; 201 | a_sum += sumFactor * pa; 202 | 203 | stack = stackStart; 204 | 205 | for (i = 0; i < radiusPlus1; i++) { 206 | stack.r = pr; 207 | stack.g = pg; 208 | stack.b = pb; 209 | stack.a = pa; 210 | stack = stack.next; 211 | } 212 | 213 | yp = width; 214 | 215 | for (i = 1; i <= radius; i++) { 216 | yi = ( yp + x ) << 2; 217 | 218 | r_sum += ( stack.r = ( pr = pixels[yi])) * ( rbs = radiusPlus1 - i ); 219 | g_sum += ( stack.g = ( pg = pixels[yi + 1])) * rbs; 220 | b_sum += ( stack.b = ( pb = pixels[yi + 2])) * rbs; 221 | a_sum += ( stack.a = ( pa = pixels[yi + 3])) * rbs; 222 | 223 | r_in_sum += pr; 224 | g_in_sum += pg; 225 | b_in_sum += pb; 226 | a_in_sum += pa; 227 | 228 | stack = stack.next; 229 | 230 | if (i < heightMinus1) { 231 | yp += width; 232 | } 233 | } 234 | 235 | yi = x; 236 | stackIn = stackStart; 237 | stackOut = stackEnd; 238 | for (y = 0; y < height; y++) { 239 | p = yi << 2; 240 | pixels[p + 3] = pa = (a_sum * mul_sum) >> shg_sum; 241 | if (pa > 0) { 242 | pa = 255 / pa; 243 | pixels[p] = ((r_sum * mul_sum) >> shg_sum ) * pa; 244 | pixels[p + 1] = ((g_sum * mul_sum) >> shg_sum ) * pa; 245 | pixels[p + 2] = ((b_sum * mul_sum) >> shg_sum ) * pa; 246 | } else { 247 | pixels[p] = pixels[p + 1] = pixels[p + 2] = 0; 248 | } 249 | 250 | r_sum -= r_out_sum; 251 | g_sum -= g_out_sum; 252 | b_sum -= b_out_sum; 253 | a_sum -= a_out_sum; 254 | 255 | r_out_sum -= stackIn.r; 256 | g_out_sum -= stackIn.g; 257 | b_out_sum -= stackIn.b; 258 | a_out_sum -= stackIn.a; 259 | 260 | p = ( x + (( ( p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1 ) * width )) << 2; 261 | 262 | r_sum += ( r_in_sum += ( stackIn.r = pixels[p])); 263 | g_sum += ( g_in_sum += ( stackIn.g = pixels[p + 1])); 264 | b_sum += ( b_in_sum += ( stackIn.b = pixels[p + 2])); 265 | a_sum += ( a_in_sum += ( stackIn.a = pixels[p + 3])); 266 | 267 | stackIn = stackIn.next; 268 | 269 | r_out_sum += ( pr = stackOut.r ); 270 | g_out_sum += ( pg = stackOut.g ); 271 | b_out_sum += ( pb = stackOut.b ); 272 | a_out_sum += ( pa = stackOut.a ); 273 | 274 | r_in_sum -= pr; 275 | g_in_sum -= pg; 276 | b_in_sum -= pb; 277 | a_in_sum -= pa; 278 | 279 | stackOut = stackOut.next; 280 | 281 | yi += width; 282 | } 283 | } 284 | } 285 | } 286 | 287 | var mul_table = [ 288 | 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512, 289 | 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512, 290 | 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456, 291 | 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512, 292 | 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328, 293 | 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456, 294 | 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335, 295 | 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512, 296 | 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405, 297 | 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328, 298 | 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271, 299 | 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456, 300 | 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388, 301 | 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335, 302 | 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292, 303 | 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259]; 304 | 305 | 306 | var shg_table = [ 307 | 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 308 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 309 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 310 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21, 311 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 312 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 313 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 314 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 315 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 316 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 317 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 318 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 319 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 320 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 321 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 322 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24]; 323 | 324 | class BlurStack { 325 | public r : number = 0; 326 | public g : number = 0; 327 | public b : number = 0; 328 | public a : number = 0; 329 | public next : BlurStack = null; 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /src/filter/brightness.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Brightness 4 | * @description Provides additive brightness control. 5 | * @param brightness -1 to 1 (-1 is solid black, 0 is no change, and 1 is solid white) 6 | */ 7 | export class Brightness extends IterableFilter { 8 | constructor(brightness? : number) { 9 | super(null, ` 10 | uniform sampler2D texture; 11 | uniform float brightness; 12 | varying vec2 texCoord; 13 | 14 | void main() { 15 | vec4 color = texture2D(texture, texCoord); 16 | color.rgb += brightness; 17 | 18 | gl_FragColor = color; 19 | } 20 | `); 21 | 22 | // set properties 23 | this.properties.brightness = Filter.clamp(-1, brightness, 1) || 0; 24 | } 25 | 26 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 27 | var brightness = this.properties.brightness; 28 | 29 | helper.r += brightness; 30 | helper.g += brightness; 31 | helper.b += brightness; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/filter/colorHalfTone.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Color Halftone 4 | * @description Simulates a CMYK halftone rendering of the image by multiplying pixel values 5 | * with a four rotated 2D sine wave patterns, one each for cyan, magenta, yellow, 6 | * and black. 7 | * @param centerX The x coordinate of the pattern origin. 8 | * @param centerY The y coordinate of the pattern origin. 9 | * @param angle The rotation of the pattern in radians. 10 | * @param size The diameter of a dot in pixels. 11 | */ 12 | export class ColorHalfTone extends IterableFilter { 13 | constructor(protected centerX : number, protected centerY : number, angle : number, size : number) { 14 | super(null, ` 15 | uniform sampler2D texture; 16 | uniform vec2 center; 17 | uniform float angle; 18 | uniform float scale; 19 | uniform vec2 texSize; 20 | varying vec2 texCoord; 21 | 22 | float pattern(float angle) { 23 | float s = sin(angle), c = cos(angle); 24 | vec2 tex = texCoord * texSize - center; 25 | vec2 point = vec2( 26 | c * tex.x - s * tex.y, 27 | s * tex.x + c * tex.y 28 | ) * scale; 29 | 30 | return (sin(point.x) * sin(point.y)) * 4.0; 31 | } 32 | 33 | void main() { 34 | vec4 color = texture2D(texture, texCoord); 35 | vec3 cmy = 1.0 - color.rgb; 36 | float k = min(cmy.x, min(cmy.y, cmy.z)); 37 | cmy = (cmy - k) / (1.0 - k); 38 | cmy = clamp(cmy * 10.0 - 3.0 + vec3(pattern(angle + 0.26179), pattern(angle + 1.30899), pattern(angle)), 0.0, 1.0); 39 | k = clamp(k * 10.0 - 5.0 + pattern(angle + 0.78539), 0.0, 1.0); 40 | gl_FragColor = vec4(1.0 - cmy - k, color.a); 41 | } 42 | `); 43 | 44 | // set properties 45 | this.properties.angle = Filter.clamp(0, angle, Math.PI / 2); 46 | this.properties.scale = Math.PI / size; 47 | } 48 | 49 | public drawWebGL(renderer : jsfx.webgl.Renderer) : void { 50 | var shader = renderer.getShader(this); 51 | var properties = this.getProperties(); 52 | 53 | // add texture size 54 | properties.texSize = [renderer.getSource().width, renderer.getSource().width]; 55 | properties.center = [this.centerX, this.centerY]; 56 | 57 | renderer.getTexture().use(); 58 | renderer.getNextTexture().drawTo(function () { 59 | shader.uniforms(properties).drawRect(); 60 | }); 61 | } 62 | 63 | public static pattern(angle : number, x : number, y : number, centerX : number, centerY : number, scale : number) { 64 | // float s = sin(angle), c = cos(angle); 65 | var s : number = Math.sin(angle); 66 | var c : number = Math.cos(angle); 67 | 68 | // vec2 tex = texCoord * texSize - center; 69 | // texCoord in webgl is between 0 and 1 70 | var tX : number = x - centerX; 71 | var tY : number = y - centerY; 72 | 73 | //vec2 point = vec2( 74 | // c * tex.x - s * tex.y, 75 | // s * tex.x + c * tex.y 76 | // ) * scale; 77 | //return (sin(point.x) * sin(point.y)) * 4.0; 78 | return (Math.sin((c * tX - s * tY) * scale) * Math.sin((s * tX + c * tY) * scale)) * 4; 79 | } 80 | 81 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 82 | var angle = this.properties.angle; 83 | var imageData = helper.getImageData(); 84 | var x = (helper.getIndex() / 4) % imageData.width; 85 | var y = Math.floor((helper.getIndex() / 4) / imageData.width); 86 | var pattern = (angle : number) : number => { 87 | return ColorHalfTone.pattern(angle, x, y, this.centerX, this.centerY, this.properties.scale); 88 | }; 89 | 90 | // vec3 cmy = 1.0 - color.rgb; 91 | var r = 1 - helper.r; 92 | var g = 1 - helper.g; 93 | var b = 1 - helper.b; 94 | 95 | // float k = min(cmy.x, min(cmy.y, cmy.z)); 96 | var k = Math.min(r, Math.min(g, b)); 97 | 98 | // cmy = (cmy - k) / (1.0 - k); 99 | r = (r - k) / (1 - k); 100 | g = (g - k) / (1 - k); 101 | b = (b - k) / (1 - k); 102 | 103 | // cmy = clamp(cmy * 10.0 - 3.0 + vec3(pattern(angle + 0.26179), pattern(angle + 1.30899), pattern(angle)), 0.0, 1.0); 104 | r = Filter.clamp(0, r * 10 - 3 + pattern(angle + 0.26179), 1); 105 | g = Filter.clamp(0, g * 10 - 3 + pattern(angle + 1.30899), 1); 106 | b = Filter.clamp(0, b * 10 - 3 + pattern(angle), 1); 107 | 108 | // k = clamp(k * 10.0 - 5.0 + pattern(angle + 0.78539), 0.0, 1.0); 109 | k = Filter.clamp(0, k * 10 - 5 + pattern(angle + 0.78539), 1); 110 | 111 | // gl_FragColor = vec4(1.0 - cmy - k, color.a); 112 | helper.r = 1 - r - k; 113 | helper.g = 1 - g - k; 114 | helper.b = 1 - b - k; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/filter/contrast.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Contrast 4 | * @description Provides multiplicative contrast control. 5 | * @param contrast -1 to 1 (-1 is solid gray, 0 is no change, and 1 is maximum contrast) 6 | */ 7 | export class Contrast extends IterableFilter { 8 | constructor(contrast? : number) { 9 | super(null, ` 10 | uniform sampler2D texture; 11 | uniform float contrast; 12 | varying vec2 texCoord; 13 | 14 | void main() { 15 | vec4 color = texture2D(texture, texCoord); 16 | 17 | if (contrast > 0.0) { 18 | color.rgb = (color.rgb - 0.5) / (1.0 - contrast) + 0.5; 19 | } else { 20 | color.rgb = (color.rgb - 0.5) * (1.0 + contrast) + 0.5; 21 | } 22 | 23 | gl_FragColor = color; 24 | } 25 | `); 26 | 27 | // set properties 28 | this.properties.contrast = Filter.clamp(-1, contrast, 1) || 0; 29 | } 30 | 31 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 32 | var contrast = this.properties.contrast; 33 | 34 | if (contrast > 0) { 35 | helper.r = (helper.r - 0.5) / (1 - contrast) + 0.5; 36 | helper.g = (helper.g - 0.5) / (1 - contrast) + 0.5; 37 | helper.b = (helper.b - 0.5) / (1 - contrast) + 0.5; 38 | } else { 39 | helper.r = (helper.r - 0.5) * (1 + contrast) + 0.5; 40 | helper.g = (helper.g - 0.5) * (1 + contrast) + 0.5; 41 | helper.b = (helper.b - 0.5) * (1 + contrast) + 0.5; 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/filter/curves.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Curves 4 | * @description A powerful mapping tool that transforms the colors in the image 5 | * by an arbitrary function. The function is interpolated between 6 | * a set of 2D points using splines. The curves filter can take 7 | * either one or three arguments which will apply the mapping to 8 | * either luminance or RGB values, respectively. 9 | * @param red A list of points that define the function for the red channel. 10 | * Each point is a list of two values: the value before the mapping 11 | * and the value after the mapping, both in the range 0 to 1. For 12 | * example, [[0,1], [1,0]] would invert the red channel while 13 | * [[0,0], [1,1]] would leave the red channel unchanged. If green 14 | * and blue are omitted then this argument also applies to the 15 | * green and blue channels. 16 | * @param green (optional) A list of points that define the function for the green 17 | * channel (just like for red). 18 | * @param blue (optional) A list of points that define the function for the blue 19 | * channel (just like for red). 20 | */ 21 | export class Curves extends IterableFilter { 22 | constructor(private red : number[], private green : number[], private blue : number[]) { 23 | super(null, ` 24 | uniform sampler2D texture; 25 | uniform sampler2D map; 26 | varying vec2 texCoord; 27 | 28 | void main() { 29 | vec4 color = texture2D(texture, texCoord); 30 | color.r = texture2D(map, vec2(color.r)).r; 31 | color.g = texture2D(map, vec2(color.g)).g; 32 | color.b = texture2D(map, vec2(color.b)).b; 33 | gl_FragColor = color; 34 | } 35 | `); 36 | 37 | // interpolate 38 | red = Curves.splineInterpolate(red); 39 | 40 | if (arguments.length == 1) { 41 | green = blue = red; 42 | } else { 43 | green = Curves.splineInterpolate(green); 44 | blue = Curves.splineInterpolate(blue); 45 | } 46 | 47 | this.red = red; 48 | this.green = green; 49 | this.blue = blue; 50 | } 51 | 52 | drawWebGL(renderer : jsfx.webgl.Renderer) : void { 53 | // create texture data 54 | var array : any[] = []; 55 | for (var i = 0; i < 256; i++) { 56 | array.splice(array.length, 0, this.red[i], this.green[i], this.blue[i], 255); 57 | } 58 | 59 | // create a new texture 60 | var extraTexture = renderer.createTexture(); 61 | 62 | // set ramp texture data 63 | extraTexture.initFromBytes(256, 1, array); 64 | 65 | // use the texture 66 | extraTexture.use(1); 67 | 68 | // get the shader 69 | var shader = renderer.getShader(this); 70 | 71 | // set shader textures 72 | shader.textures({ 73 | map: 1 74 | }); 75 | 76 | // render 77 | renderer.getTexture().use(); 78 | renderer.getNextTexture().drawTo(function () { 79 | shader.uniforms({}).drawRect(); 80 | }); 81 | } 82 | 83 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 84 | var i : number = helper.getIndex(); 85 | 86 | helper.r = this.red[helper.r * 255] / 255; 87 | helper.g = this.green[helper.g * 255] / 255; 88 | helper.b = this.blue[helper.b * 255] / 255; 89 | } 90 | 91 | static splineInterpolate(points : number[]) { 92 | var interpolator : jsfx.util.SplineInterpolator = new jsfx.util.SplineInterpolator(points); 93 | var array : number[] = []; 94 | 95 | for (var i = 0; i < 256; i++) { 96 | array.push(Filter.clamp(0, Math.floor(interpolator.interpolate(i / 255) * 256), 255)); 97 | } 98 | 99 | return array; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/filter/denoise.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | declare var Uint8ClampedArray : any; 3 | 4 | /** 5 | * @todo While this filter is fast in WebGL, it is terribly slow in Canvas due to the complexity of the 9x9 box filter. 6 | * 7 | * @filter Denoise 8 | * @description Smooths over grainy noise in dark images using an 9x9 box filter 9 | * weighted by color intensity, similar to a bilateral filter. 10 | * @param exponent The exponent of the color intensity difference, should be greater 11 | * than zero. A value of zero just gives an 9x9 box blur and high values 12 | * give the original image, but ideal values are usually around 10-20. 13 | */ 14 | export class Denoise extends Filter { 15 | constructor(exponent : number) { 16 | super(null, ` 17 | uniform sampler2D texture; 18 | uniform float exponent; 19 | uniform float strength; 20 | uniform vec2 texSize; 21 | varying vec2 texCoord; 22 | 23 | void main() { 24 | vec4 center = texture2D(texture, texCoord); 25 | vec4 color = vec4(0.0); 26 | float total = 0.0; 27 | 28 | for (float x = -4.0; x <= 4.0; x += 1.0) { 29 | for (float y = -4.0; y <= 4.0; y += 1.0) { 30 | vec4 sample = texture2D(texture, texCoord + vec2(x, y) / texSize); 31 | float weight = 1.0 - abs(dot(sample.rgb - center.rgb, vec3(0.25))); 32 | weight = pow(weight, exponent); 33 | color += sample * weight; 34 | total += weight; 35 | } 36 | } 37 | 38 | gl_FragColor = color / total; 39 | } 40 | `); 41 | 42 | // set properties 43 | this.properties.exponent = exponent; 44 | } 45 | 46 | public drawWebGL(renderer : jsfx.webgl.Renderer) : void { 47 | var shader = renderer.getShader(this); 48 | var properties = this.getProperties(); 49 | 50 | // add texture size 51 | properties.texSize = [renderer.getSource().width, renderer.getSource().width]; 52 | 53 | renderer.getTexture().use(); 54 | renderer.getNextTexture().drawTo(function () { 55 | shader.uniforms(properties).drawRect(); 56 | }); 57 | } 58 | 59 | public drawCanvas(renderer : jsfx.canvas.Renderer) : void { 60 | var exponent = this.properties.exponent; 61 | var imageData : ImageData = renderer.getImageData(); 62 | var pixels = imageData.data; 63 | var original : number[] = new Uint8ClampedArray(imageData.data); 64 | 65 | // variables 66 | var x:number, y: number, dstOff : number, color : number[], total : number, cx : number, cy : number, scx : number, scy : number, srcOff : number, weight : number; 67 | 68 | for (x = 0; x < imageData.width; x++) { 69 | for (y = 0; y < imageData.height; y++) { 70 | 71 | dstOff = (y * imageData.width + x) * 4; 72 | color = [0, 0, 0, 0]; 73 | total = 0; 74 | 75 | for (cx = -4; cx <= 4; cx += 1) { 76 | for (cy = -4; cy <= 4; cy += 1) { 77 | 78 | scx = Math.min(imageData.width - 1, Math.max(0, x + cx)); 79 | scy = Math.min(imageData.height - 1, Math.max(0, y + cy)); 80 | srcOff = (scx + scy * imageData.width) * 4; 81 | 82 | // calculate the weight 83 | weight = Math.pow( 84 | 1.0 - Math.abs( 85 | (original[srcOff] / 255 - original[dstOff] / 255) * 0.25 86 | + (original[srcOff + 1] / 255 - original[dstOff + 1] / 255) * 0.25 87 | + (original[srcOff + 2] / 255 - original[dstOff + 2] / 255) * 0.25 88 | ), 89 | exponent 90 | ); 91 | 92 | // color += sample * weight 93 | color[0] += original[srcOff] / 255 * weight; 94 | color[1] += original[srcOff + 1] / 255 * weight; 95 | color[2] += original[srcOff + 2] / 255 * weight; 96 | color[3] += original[srcOff + 3] / 255 * weight; 97 | 98 | total += weight; 99 | } 100 | } 101 | 102 | pixels[dstOff] = (color[0] / total) * 255; 103 | pixels[dstOff + 1] = (color[1] / total) * 255; 104 | pixels[dstOff + 2] = (color[2] / total) * 255; 105 | pixels[dstOff + 3] = (color[3] / total) * 255; 106 | } 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/filter/dotScreen.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Color Halftone 4 | * @description Simulates a CMYK halftone rendering of the image by multiplying pixel values 5 | * with a four rotated 2D sine wave patterns, one each for cyan, magenta, yellow, 6 | * and black. 7 | * @param centerX The x coordinate of the pattern origin. 8 | * @param centerY The y coordinate of the pattern origin. 9 | * @param angle The rotation of the pattern in radians. 10 | * @param size The diameter of a dot in pixels. 11 | */ 12 | export class DotScreen extends IterableFilter { 13 | constructor(protected centerX : number, protected centerY : number, angle : number, size : number) { 14 | super(null, ` 15 | uniform sampler2D texture; 16 | uniform vec2 center; 17 | uniform float angle; 18 | uniform float scale; 19 | uniform vec2 texSize; 20 | varying vec2 texCoord; 21 | 22 | float pattern() {\ 23 | float s = sin(angle), c = cos(angle); 24 | vec2 tex = texCoord * texSize - center; 25 | vec2 point = vec2( 26 | c * tex.x - s * tex.y, 27 | s * tex.x + c * tex.y 28 | ) * scale; 29 | 30 | return (sin(point.x) * sin(point.y)) * 4.0; 31 | } 32 | 33 | void main() { 34 | vec4 color = texture2D(texture, texCoord); 35 | float average = (color.r + color.g + color.b) / 3.0; 36 | gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a); 37 | } 38 | `); 39 | 40 | // set properties 41 | this.properties.angle = Filter.clamp(0, angle, Math.PI / 2); 42 | this.properties.scale = Math.PI / size; 43 | } 44 | 45 | public drawWebGL(renderer : jsfx.webgl.Renderer) : void { 46 | var shader = renderer.getShader(this); 47 | var properties = this.getProperties(); 48 | 49 | // add texture size 50 | properties.texSize = [renderer.getSource().width, renderer.getSource().width]; 51 | properties.center = [this.centerX, this.centerY]; 52 | 53 | renderer.getTexture().use(); 54 | renderer.getNextTexture().drawTo(function () { 55 | shader.uniforms(properties).drawRect(); 56 | }); 57 | } 58 | 59 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 60 | var imageData = helper.getImageData(); 61 | var x = (helper.getIndex() / 4) % imageData.width; 62 | var y = Math.floor((helper.getIndex() / 4) / imageData.width); 63 | 64 | // float average = (color.r + color.g + color.b) / 3.0; 65 | var average : number = (helper.r + helper.g + helper.b) / 3; 66 | 67 | // gl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a); 68 | var pattern : number = ColorHalfTone.pattern(this.properties.angle, x, y, this.centerX, this.centerY, this.properties.scale); 69 | var value : number = average * 10 - 5 + pattern; 70 | 71 | helper.r = value; 72 | helper.g = value; 73 | helper.b = value; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/filter/filter.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace jsfx.filter { 4 | export class Filter implements FilterInterface { 5 | protected properties : any = {}; 6 | 7 | constructor(private vertexSource : string = null, private fragmentSource : string = null) { 8 | } 9 | 10 | /** 11 | * Returns all the properties of the shader. Useful for drawWebGl when are are just passing along data 12 | * to the shader. 13 | * 14 | * @returns {{}|*} 15 | */ 16 | public getProperties() : any { 17 | return this.properties; 18 | } 19 | 20 | /** 21 | * The javascript implementation of the filter 22 | * 23 | * @param renderer 24 | */ 25 | public drawCanvas(renderer : jsfx.canvas.Renderer) : void { 26 | throw new Error("Must be implemented"); 27 | } 28 | 29 | /** 30 | * The WebGL implementation of the filter 31 | * 32 | * @param renderer 33 | */ 34 | public drawWebGL(renderer : jsfx.webgl.Renderer) : void { 35 | var shader = renderer.getShader(this); 36 | var properties = this.getProperties(); 37 | 38 | renderer.getTexture().use(); 39 | renderer.getNextTexture().drawTo(function () { 40 | shader.uniforms(properties).drawRect(); 41 | }); 42 | } 43 | 44 | public getVertexSource() : string { 45 | return this.vertexSource; 46 | } 47 | 48 | public getFragmentSource() : string { 49 | return this.fragmentSource; 50 | } 51 | 52 | static clamp(low : number, value : number, high : number) : number { 53 | return Math.max(low, Math.min(value, high)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/filter/filterInterface.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | export interface FilterInterface { 3 | getProperties() : Object; 4 | getVertexSource() : string; 5 | getFragmentSource() : string; 6 | drawCanvas(renderer : jsfx.canvas.Renderer) : void; 7 | drawWebGL(renderer : jsfx.webgl.Renderer) : void; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/filter/hue.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Hue / Saturation 4 | * @description Provides rotational hue control. RGB color space 5 | * can be imagined as a cube where the axes are the red, green, and blue color 6 | * values. Hue changing works by rotating the color vector around the grayscale 7 | * line, which is the straight line from black (0, 0, 0) to white (1, 1, 1). 8 | * @param hue -1 to 1 (-1 is 180 degree rotation in the negative direction, 0 is no change, 9 | * and 1 is 180 degree rotation in the positive direction) 10 | */ 11 | export class Hue extends IterableFilter { 12 | private weights : jsfx.util.Vector3; 13 | 14 | constructor(hue? : number) { 15 | super(null, ` 16 | uniform sampler2D texture; 17 | uniform float hue; 18 | varying vec2 texCoord; 19 | 20 | void main() { 21 | vec4 color = texture2D(texture, texCoord); 22 | 23 | /* hue adjustment, wolfram alpha: RotationTransform[angle, {1, 1, 1}][{x, y, z}] */ 24 | float angle = hue * 3.14159265; 25 | float s = sin(angle), c = cos(angle); 26 | vec3 weights = (vec3(2.0 * c, -sqrt(3.0) * s - c, sqrt(3.0) * s - c) + 1.0) / 3.0; 27 | color.rgb = vec3( 28 | dot(color.rgb, weights.xyz), 29 | dot(color.rgb, weights.zxy), 30 | dot(color.rgb, weights.yzx) 31 | ); 32 | 33 | gl_FragColor = color; 34 | } 35 | `); 36 | 37 | // set properties 38 | this.properties.hue = Filter.clamp(-1, hue, 1) || 0; 39 | 40 | // pre-calculate data for canvas iteration 41 | var angle = hue * 3.14159265; 42 | var sin = Math.sin(angle); 43 | var cos = Math.cos(angle); 44 | this.weights = new jsfx.util.Vector3(2 * cos, -Math.sqrt(3.0) * sin - cos, Math.sqrt(3.0) * sin - cos) 45 | .addScalar(1.0) 46 | .divideScalar(3.0); 47 | } 48 | 49 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 50 | var rgb : jsfx.util.Vector3 = helper.toVector3(); 51 | 52 | helper.r = rgb.dot(this.weights); 53 | helper.g = rgb.dotScalars(this.weights.z, this.weights.x, this.weights.y); 54 | helper.b = rgb.dotScalars(this.weights.y, this.weights.z, this.weights.x); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/filter/iterableFilter.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | export class IterableFilter extends Filter implements IterableFilterInterface { 3 | public drawCanvas(renderer : jsfx.canvas.Renderer) : void { 4 | return IterableFilter.drawCanvas([this], renderer); 5 | } 6 | 7 | public iterateCanvas(imageData : jsfx.util.ImageDataHelper) : void { 8 | throw new Error("Must be implemented"); 9 | } 10 | 11 | static drawCanvas(filters : IterableFilterInterface[], renderer : jsfx.canvas.Renderer) : void { 12 | var helper : jsfx.util.ImageDataHelper; 13 | var imageData : ImageData = renderer.getImageData(); 14 | 15 | for (var i = 0; i < imageData.data.length; i += 4) { 16 | helper = new jsfx.util.ImageDataHelper(imageData, i); 17 | 18 | filters.forEach((filter : IterableFilterInterface) => { 19 | filter.iterateCanvas(helper); 20 | }); 21 | 22 | helper.save(); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/filter/iterableFilterInterface.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | export interface IterableFilterInterface extends FilterInterface { 3 | iterateCanvas(helper : jsfx.util.ImageDataHelper) : void; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/filter/multiply.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Multiply 4 | */ 5 | export class Multiply extends IterableFilter { 6 | constructor(protected r : number, protected g : number, protected b : number) { 7 | super(null, ` 8 | uniform sampler2D texture; 9 | uniform float r; 10 | uniform float g; 11 | uniform float b; 12 | varying vec2 texCoord; 13 | 14 | void main() { 15 | vec4 color = texture2D(texture, texCoord); 16 | color.r *= r; 17 | color.g *= g; 18 | color.b *= b; 19 | 20 | gl_FragColor = color; 21 | } 22 | `); 23 | 24 | // set properties 25 | this.properties.r = Filter.clamp(0, r, 1); 26 | this.properties.g = Filter.clamp(0, g, 1); 27 | this.properties.b = Filter.clamp(0, b, 1); 28 | } 29 | 30 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 31 | helper.r *= this.properties.r; 32 | helper.g *= this.properties.g; 33 | helper.b *= this.properties.b; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/filter/noise.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Noise 4 | * @description Adds black and white noise to the image. 5 | * @param amount 0 to 1 (0 for no effect, 1 for maximum noise) 6 | */ 7 | export class Noise extends IterableFilter { 8 | constructor(amount : number) { 9 | super(null, ` 10 | uniform sampler2D texture; 11 | uniform float amount; 12 | varying vec2 texCoord; 13 | 14 | float rand(vec2 co) { 15 | return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); 16 | } 17 | 18 | void main() { 19 | vec4 color = texture2D(texture, texCoord); 20 | 21 | float diff = (rand(texCoord) - 0.5) * amount; 22 | color.r += diff; 23 | color.g += diff; 24 | color.b += diff; 25 | 26 | gl_FragColor = color; 27 | } 28 | `); 29 | 30 | // set properties 31 | this.properties.amount = Filter.clamp(0, amount, 1); 32 | } 33 | 34 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 35 | var amount = this.properties.amount; 36 | var width = helper.getImageData().width; 37 | var x = (helper.getIndex() / 4) % width; 38 | var y = Math.floor((helper.getIndex() / 4) / width); 39 | var v : jsfx.util.Vector2 = new jsfx.util.Vector2(x, y); 40 | var diff = (Noise.rand(v) - 0.5) * amount; 41 | 42 | helper.r += diff; 43 | helper.g += diff; 44 | helper.b += diff; 45 | } 46 | 47 | private static rand(v : jsfx.util.Vector2) : number { 48 | return Noise.fract(Math.sin(v.dotScalars(12.9898, 78.233)) * 43758.5453); 49 | } 50 | 51 | private static fract(x : number) : number { 52 | return x - Math.floor(x); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/filter/saturation.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Hue / Saturation 4 | * @description Provides multiplicative saturation control. RGB color space 5 | * can be imagined as a cube where the axes are the red, green, and blue color 6 | * values. 7 | * Saturation is implemented by scaling all color channel values either toward 8 | * or away from the average color channel value. 9 | * @param saturation -1 to 1 (-1 is solid gray, 0 is no change, and 1 is maximum contrast) 10 | */ 11 | export class Saturation extends IterableFilter { 12 | constructor(saturation? : number) { 13 | super(null, ` 14 | uniform sampler2D texture; 15 | uniform float saturation; 16 | varying vec2 texCoord; 17 | 18 | void main() { 19 | vec4 color = texture2D(texture, texCoord); 20 | 21 | float average = (color.r + color.g + color.b) / 3.0; 22 | if (saturation > 0.0) { 23 | color.rgb += (average - color.rgb) * (1.0 - 1.0 / (1.001 - saturation)); 24 | } else { 25 | color.rgb += (average - color.rgb) * (-saturation); 26 | } 27 | 28 | gl_FragColor = color; 29 | } 30 | `); 31 | 32 | // set properties 33 | this.properties.saturation = Filter.clamp(-1, saturation, 1) || 0; 34 | } 35 | 36 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 37 | var saturation : number = this.properties.saturation; 38 | var average : number = (helper.r + helper.g + helper.b) / 3; 39 | 40 | if (saturation > 0) { 41 | helper.r += (average - helper.r) * (1 - 1 / (1.001 - saturation)); 42 | helper.g += (average - helper.g) * (1 - 1 / (1.001 - saturation)); 43 | helper.b += (average - helper.b) * (1 - 1 / (1.001 - saturation)); 44 | } else { 45 | helper.r += (average - helper.r) * (-saturation); 46 | helper.g += (average - helper.g) * (-saturation); 47 | helper.b += (average - helper.b) * (-saturation); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/filter/sepia.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Sepia 4 | * @description Gives the image a reddish-brown monochrome tint that imitates an old photograph. 5 | * @param amount 0 to 1 (0 for no effect, 1 for full sepia coloring) 6 | */ 7 | export class Sepia extends IterableFilter { 8 | constructor(amount? : number) { 9 | super(null, ` 10 | uniform sampler2D texture; 11 | uniform float amount; 12 | varying vec2 texCoord; 13 | 14 | void main() { 15 | vec4 color = texture2D(texture, texCoord); 16 | float r = color.r; 17 | float g = color.g; 18 | float b = color.b; 19 | 20 | color.r = min(1.0, (r * (1.0 - (0.607 * amount))) + (g * (0.769 * amount)) + (b * (0.189 * amount))); 21 | color.g = min(1.0, (r * 0.349 * amount) + (g * (1.0 - (0.314 * amount))) + (b * 0.168 * amount)); 22 | color.b = min(1.0, (r * 0.272 * amount) + (g * 0.534 * amount) + (b * (1.0 - (0.869 * amount)))); 23 | 24 | gl_FragColor = color; 25 | } 26 | `); 27 | 28 | // set properties 29 | this.properties.amount = Filter.clamp(-1, amount, 1) || 0; 30 | } 31 | 32 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 33 | var r : number = helper.r; 34 | var g : number = helper.g; 35 | var b : number = helper.b; 36 | var amount : number = this.properties.amount; 37 | 38 | helper.r = Math.min(1.0, (r * (1.0 - (0.607 * amount))) + (g * (0.769 * amount)) + (b * (0.189 * amount))); 39 | helper.g = Math.min(1.0, (r * 0.349 * amount) + (g * (1.0 - (0.314 * amount))) + (b * 0.168 * amount)); 40 | helper.b = Math.min(1.0, (r * 0.272 * amount) + (g * 0.534 * amount) + (b * (1.0 - (0.869 * amount)))); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/filter/unsharpMask.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | declare var Uint8ClampedArray : any; 3 | 4 | /** 5 | * @filter Unsharp Mask 6 | * @description A form of image sharpening that amplifies high-frequencies in the image. It 7 | * is implemented by scaling pixels away from the average of their neighbors. 8 | * @param radius 0 to 180 - The blur radius that calculates the average of the neighboring pixels. 9 | * @param strength A scale factor where 0 is no effect and higher values cause a stronger effect. 10 | * @note Could potentially be converted to an IterableFilter, but we somehow need the original ImageData 11 | */ 12 | export class UnsharpMask extends Filter { 13 | constructor(radius? : number, strength ? : number) { 14 | super(null, ` 15 | uniform sampler2D blurredTexture; 16 | uniform sampler2D originalTexture; 17 | uniform float strength; 18 | uniform float threshold; 19 | varying vec2 texCoord; 20 | 21 | void main() { 22 | vec4 blurred = texture2D(blurredTexture, texCoord); 23 | vec4 original = texture2D(originalTexture, texCoord); 24 | gl_FragColor = mix(blurred, original, 1.0 + strength); 25 | } 26 | `); 27 | 28 | // set properties 29 | this.properties.radius = radius; 30 | this.properties.strength = strength; 31 | } 32 | 33 | drawWebGL(renderer : jsfx.webgl.Renderer) : void { 34 | var shader = renderer.getShader(this); 35 | var radius = this.properties.radius; 36 | var strength = this.properties.strength; 37 | 38 | // create a new texture 39 | var extraTexture = renderer.createTexture(); 40 | 41 | // use a texture and draw to it 42 | renderer.getTexture().use(); 43 | extraTexture.drawTo(renderer.getDefaultShader().drawRect.bind(renderer.getDefaultShader())); 44 | 45 | // blur current texture 46 | extraTexture.use(1); 47 | 48 | // draw the blur 49 | var blur = new Blur(radius); 50 | blur.drawWebGL(renderer); 51 | 52 | // use the stored texture to detect edges 53 | shader.textures({ 54 | originalTexture: 1 55 | }); 56 | 57 | renderer.getTexture().use(); 58 | renderer.getNextTexture().drawTo(function () { 59 | shader.uniforms({strength: strength}).drawRect(); 60 | }); 61 | 62 | extraTexture.unuse(1); 63 | } 64 | 65 | public drawCanvas(renderer : jsfx.canvas.Renderer) : void { 66 | var original : number[] = new Uint8ClampedArray(renderer.getImageData().data); 67 | 68 | // props 69 | var radius = this.properties.radius; 70 | var strength = this.properties.strength + 1; 71 | 72 | // blur image 73 | var blur = new Blur(radius); 74 | blur.drawCanvas(renderer); 75 | 76 | // get processed image data 77 | var imageData : ImageData = renderer.getImageData(); 78 | var pixels : number[] = imageData.data; 79 | 80 | for (var i = 0; i < pixels.length; i += 4) { 81 | pixels[i] = jsfx.util.ImageDataHelper.mix(pixels[i], original[i], strength); 82 | pixels[i + 1] = jsfx.util.ImageDataHelper.mix(pixels[i + 1], original[i + 1], strength); 83 | pixels[i + 2] = jsfx.util.ImageDataHelper.mix(pixels[i + 2], original[i + 2], strength); 84 | } 85 | 86 | renderer.setImageData(imageData); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/filter/vibrance.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Vibrance 4 | * @description Modifies the saturation of desaturated colors, leaving saturated colors unmodified. 5 | * @param amount -1 to 1 (-1 is minimum vibrance, 0 is no change, and 1 is maximum vibrance) 6 | */ 7 | export class Vibrance extends IterableFilter { 8 | constructor(amount : number) { 9 | super(null, ` 10 | uniform sampler2D texture; 11 | uniform float amount; 12 | varying vec2 texCoord; 13 | void main() { 14 | vec4 color = texture2D(texture, texCoord); 15 | float average = (color.r + color.g + color.b) / 3.0; 16 | float mx = max(color.r, max(color.g, color.b)); 17 | float amt = (mx - average) * (-amount * 3.0); 18 | color.rgb = mix(color.rgb, vec3(mx), amt); 19 | gl_FragColor = color; 20 | } 21 | `); 22 | 23 | // set properties 24 | this.properties.amount = Filter.clamp(-1, amount, 1); 25 | } 26 | 27 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 28 | var amount = this.properties.amount; 29 | var average = (helper.r + helper.g + helper.b) / 3.0; 30 | var mx = Math.max(helper.r, Math.max(helper.g, helper.b)); 31 | 32 | helper.mix(mx, mx, mx, (mx - average) * (-amount * 3.0)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/filter/vignette.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.filter { 2 | /** 3 | * @filter Vignette 4 | * @description Adds a simulated lens edge darkening effect. 5 | * @param size 0 to 1 (0 for center of frame, 1 for edge of frame) 6 | * @param amount 0 to 1 (0 for no effect, 1 for maximum lens darkening) 7 | */ 8 | export class Vignette extends IterableFilter { 9 | constructor(size : number, amount : number) { 10 | super(null, ` 11 | uniform sampler2D texture; 12 | uniform float size; 13 | uniform float amount; 14 | varying vec2 texCoord; 15 | 16 | void main() { 17 | vec4 color = texture2D(texture, texCoord); 18 | 19 | float dist = distance(texCoord, vec2(0.5, 0.5)); 20 | color.rgb *= smoothstep(0.8, size * 0.799, dist * (amount + size)); 21 | 22 | gl_FragColor = color; 23 | } 24 | `); 25 | 26 | // set properties 27 | this.properties.size = Filter.clamp(0, size, 1); 28 | this.properties.amount = Filter.clamp(0, amount, 1); 29 | } 30 | 31 | public iterateCanvas(helper : jsfx.util.ImageDataHelper) : void { 32 | var size : number = this.properties.size; 33 | var amount : number = this.properties.amount; 34 | 35 | var imageData : ImageData = helper.getImageData(); 36 | var x = (helper.getIndex() / 4) % imageData.width; 37 | var y = Math.floor((helper.getIndex() / 4) / imageData.width); 38 | 39 | var distance = Vignette.distance(x / imageData.width, y / imageData.height, 0.5, 0.5); 40 | var amount : number = Vignette.smoothstep(0.8, size * 0.799, distance * (amount + size)); 41 | 42 | helper.r *= amount; 43 | helper.g *= amount; 44 | helper.b *= amount; 45 | } 46 | 47 | protected static distance(x1 : number, y1 : number, x2 : number, y2 : number) : number { 48 | return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); 49 | } 50 | 51 | protected static smoothstep(min : number, max : number, value : number) { 52 | var x = Math.max(0, Math.min(1, (value - min) / (max - min))); 53 | return x * x * (3 - 2 * x); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/renderer.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx { 2 | var hasWebGL = (function () { 3 | try { 4 | var canvas = document.createElement("canvas"); 5 | return !!( canvas.getContext("webgl") || canvas.getContext("experimental-webgl")); 6 | } catch (e) { 7 | return false; 8 | } 9 | })(); 10 | 11 | export function Renderer(type ? : string) : jsfx.RendererInterface { 12 | if (!type) { 13 | type = hasWebGL ? "webgl" : "canvas"; 14 | } 15 | 16 | if (type === "webgl") { 17 | return new jsfx.webgl.Renderer(); 18 | } 19 | 20 | return new jsfx.canvas.Renderer(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/rendererInterface.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx { 2 | export interface RendererInterface { 3 | setSource(source : jsfx.Source) : RendererInterface; 4 | getSource() : jsfx.Source; 5 | applyFilter(filter : jsfx.filter.FilterInterface) : RendererInterface; 6 | applyFilters(filters : jsfx.filter.FilterInterface[]) : RendererInterface; 7 | render() : void; 8 | getCanvas() : HTMLCanvasElement; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/source.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx { 2 | export class Source { 3 | constructor(public element : HTMLImageElement) { 4 | } 5 | 6 | public get width() : number { 7 | return this.element.width; 8 | } 9 | 10 | public get height() : number { 11 | return this.element.height; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/util/imageDataHelper.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.util { 2 | export class ImageDataHelper { 3 | public r : number; 4 | public g : number; 5 | public b : number; 6 | public a : number; 7 | 8 | constructor(private imageData : ImageData, private index : number) { 9 | this.r = this.imageData.data[index] / 255; 10 | this.g = this.imageData.data[index + 1] / 255; 11 | this.b = this.imageData.data[index + 2] / 255; 12 | this.a = this.imageData.data[index + 3] / 255; 13 | } 14 | 15 | public getImageData() : ImageData { 16 | return this.imageData; 17 | } 18 | 19 | public getIndex() : number { 20 | return this.index; 21 | } 22 | 23 | public save() : void { 24 | this.imageData.data[this.index] = this.r * 255; 25 | this.imageData.data[this.index + 1] = this.g * 255; 26 | this.imageData.data[this.index + 2] = this.b * 255; 27 | this.imageData.data[this.index + 3] = this.a * 255; 28 | } 29 | 30 | public toVector3() : jsfx.util.Vector3 { 31 | return new jsfx.util.Vector3(this.r, this.g, this.b); 32 | } 33 | 34 | public fromVector3(v : jsfx.util.Vector3) : void { 35 | this.r = v.x; 36 | this.g = v.y; 37 | this.b = v.z; 38 | } 39 | 40 | /** 41 | * mix(x, y, a) = x * (1 - a) + y * a 42 | * 43 | * @param r 44 | * @param g 45 | * @param b 46 | * @param a 47 | */ 48 | public mix(r : number, g : number, b : number, a : number) : void { 49 | this.r = ImageDataHelper.mix(this.r, r, a); 50 | this.g = ImageDataHelper.mix(this.g, g, a); 51 | this.b = ImageDataHelper.mix(this.b, b, a); 52 | } 53 | 54 | public static mix(x : number, y : number, a : number) : number { 55 | return x * (1 - a) + y * a; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/util/splineInterpolator.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.util { 2 | /** 3 | * From SplineInterpolator.cs in the Paint.NET source code 4 | */ 5 | export class SplineInterpolator { 6 | private xa : number[]; 7 | private ya : number[]; 8 | private u : number[]; 9 | private y2 : number[]; 10 | 11 | constructor(public points : number[]) { 12 | var n : number = points.length; 13 | var i : number; 14 | this.xa = []; 15 | this.ya = []; 16 | this.u = []; 17 | this.y2 = []; 18 | 19 | points.sort(function (a : any, b : any) { 20 | return a[0] - b[0]; 21 | }); 22 | 23 | for (i = 0; i < n; i++) { 24 | this.xa.push(points[i][0]); 25 | this.ya.push(points[i][1]); 26 | } 27 | 28 | this.u[0] = 0; 29 | this.y2[0] = 0; 30 | 31 | for (i = 1; i < n - 1; ++i) { 32 | // This is the decomposition loop of the tri-diagonal algorithm. 33 | // y2 and u are used for temporary storage of the decomposed factors. 34 | var wx : number = this.xa[i + 1] - this.xa[i - 1]; 35 | var sig : number = (this.xa[i] - this.xa[i - 1]) / wx; 36 | var p : number = sig * this.y2[i - 1] + 2.0; 37 | 38 | this.y2[i] = (sig - 1.0) / p; 39 | 40 | var ddydx : number = 41 | (this.ya[i + 1] - this.ya[i]) / (this.xa[i + 1] - this.xa[i]) - 42 | (this.ya[i] - this.ya[i - 1]) / (this.xa[i] - this.xa[i - 1]); 43 | 44 | this.u[i] = (6.0 * ddydx / wx - sig * this.u[i - 1]) / p; 45 | } 46 | 47 | this.y2[n - 1] = 0; 48 | 49 | // This is the back-substitution loop of the tri-diagonal algorithm 50 | for (i = n - 2; i >= 0; --i) { 51 | this.y2[i] = this.y2[i] * this.y2[i + 1] + this.u[i]; 52 | } 53 | } 54 | 55 | interpolate(x : number) : number { 56 | var n : number = this.ya.length; 57 | var klo : number = 0; 58 | var khi : number = n - 1; 59 | 60 | // We will find the right place in the table by means of 61 | // bisection. This is optimal if sequential calls to this 62 | // routine are at random values of x. If sequential calls 63 | // are in order, and closely spaced, one would do better 64 | // to store previous values of klo and khi. 65 | while (khi - klo > 1) { 66 | var k = (khi + klo) >> 1; 67 | 68 | if (this.xa[k] > x) { 69 | khi = k; 70 | } else { 71 | klo = k; 72 | } 73 | } 74 | 75 | var h : number = this.xa[khi] - this.xa[klo]; 76 | var a : number = (this.xa[khi] - x) / h; 77 | var b : number = (x - this.xa[klo]) / h; 78 | 79 | // Cubic spline polynomial is now evaluated. 80 | return a * this.ya[klo] + b * this.ya[khi] + 81 | ((a * a * a - a) * this.y2[klo] + (b * b * b - b) * this.y2[khi]) * (h * h) / 6.0; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/util/vector2.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vector2 Utility Class 3 | * -> Taken from https://github.com/mrdoob/three.js/blob/master/src/math/Vector2.js with only the functions we need. 4 | */ 5 | namespace jsfx.util { 6 | export class Vector2 { 7 | constructor(public x : number, public y : number) { 8 | 9 | } 10 | 11 | dotScalars(x : number, y : number) : number { 12 | return this.x * x + this.y * y; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/util/vector3.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vector3 Utility Class 3 | * -> Taken from https://github.com/mrdoob/three.js/blob/master/src/math/Vector3.js with only the functions we need. 4 | */ 5 | namespace jsfx.util { 6 | export class Vector3 { 7 | constructor(public x : number, public y : number, public z : number) { 8 | 9 | } 10 | 11 | public addScalar(s : number) : Vector3 { 12 | this.x += s; 13 | this.y += s; 14 | this.z += s; 15 | 16 | return this; 17 | } 18 | 19 | multiplyScalar(s : number) : Vector3 { 20 | this.x *= s; 21 | this.y *= s; 22 | this.z *= s; 23 | 24 | return this; 25 | } 26 | 27 | divideScalar(s : number) : Vector3 { 28 | if (s !== 0) { 29 | var invScalar = 1 / s; 30 | 31 | this.x *= invScalar; 32 | this.y *= invScalar; 33 | this.z *= invScalar; 34 | } else { 35 | this.x = 0; 36 | this.y = 0; 37 | this.z = 0; 38 | } 39 | 40 | return this; 41 | } 42 | 43 | length() : number { 44 | return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); 45 | } 46 | 47 | dot(v : Vector3) : number { 48 | return this.x * v.x + this.y * v.y + this.z * v.z; 49 | } 50 | 51 | dotScalars(x : number, y : number, z : number) : number { 52 | return this.x * x + this.y * y + this.z * z; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/webgl/renderer.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.webgl { 2 | export class Renderer implements jsfx.RendererInterface { 3 | private canvas : HTMLCanvasElement; 4 | private gl : WebGLRenderingContext; 5 | private source : jsfx.Source; 6 | private sourceTexture : Texture; 7 | private textures : Texture[]; 8 | private currentTexture : number; 9 | 10 | constructor() { 11 | this.canvas = document.createElement("canvas"); 12 | this.gl = this.canvas.getContext("experimental-webgl", {premultipliedAlpha: false}); 13 | this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1); 14 | 15 | // variables to store the source 16 | this.source = null; 17 | this.sourceTexture = null; 18 | 19 | // store the textures and buffers 20 | this.textures = null; 21 | this.currentTexture = 0; 22 | 23 | // initialize a shader cache 24 | (this.gl).shaderCache = {}; 25 | } 26 | 27 | public setSource(source : jsfx.Source) : jsfx.RendererInterface { 28 | // first, clean up 29 | if (this.source) { 30 | this.cleanUp(); 31 | } 32 | 33 | // re-initialize renderer for rendering with new source 34 | this.source = source; 35 | this.sourceTexture = Texture.fromElement(this.gl, source.element); 36 | 37 | // initialize the renderer textures 38 | this.initialize(); 39 | 40 | // draw the source texture onto the first texture 41 | this.sourceTexture.use(); 42 | this.getTexture().drawTo(this.getDefaultShader().drawRect.bind(this.getDefaultShader())); 43 | 44 | return this; 45 | } 46 | 47 | public getSource() : jsfx.Source { 48 | return this.source; 49 | } 50 | 51 | public applyFilter(filter : jsfx.filter.FilterInterface) : jsfx.RendererInterface { 52 | filter.drawWebGL(this); 53 | 54 | return this; 55 | } 56 | 57 | public applyFilters(filters : jsfx.filter.FilterInterface[]) : jsfx.RendererInterface { 58 | filters.forEach((filter : jsfx.filter.FilterInterface) => { 59 | filter.drawWebGL(this); 60 | }); 61 | 62 | return this; 63 | } 64 | 65 | public render() { 66 | this.getTexture().use(); 67 | this.getFlippedShader().drawRect(); 68 | } 69 | 70 | public getCanvas() : HTMLCanvasElement { 71 | return this.canvas; 72 | } 73 | 74 | public getTexture() : Texture { 75 | return this.textures[this.currentTexture % 2]; 76 | } 77 | 78 | public getNextTexture() : Texture { 79 | return this.textures[++this.currentTexture % 2]; 80 | } 81 | 82 | public createTexture() : Texture { 83 | return new Texture(this.gl, this.source.width, this.source.height, this.gl.RGBA, this.gl.UNSIGNED_BYTE); 84 | } 85 | 86 | public getShader(filter : jsfx.filter.FilterInterface) : Shader { 87 | var cacheKey = filter.getVertexSource() + filter.getFragmentSource(); 88 | 89 | return (this.gl).shaderCache.hasOwnProperty(cacheKey) ? 90 | (this.gl).shaderCache[cacheKey] : 91 | new Shader(this.gl, filter.getVertexSource(), filter.getFragmentSource()); 92 | } 93 | 94 | public getDefaultShader() : Shader { 95 | if (!(this.gl).shaderCache.def) { 96 | (this.gl).shaderCache.def = new Shader(this.gl); 97 | } 98 | 99 | return (this.gl).shaderCache.def; 100 | } 101 | 102 | public getFlippedShader() : Shader { 103 | if (!(this.gl).shaderCache.flipped) { 104 | (this.gl).shaderCache.flipped = new Shader(this.gl, null, ` 105 | uniform sampler2D texture; 106 | varying vec2 texCoord; 107 | 108 | void main() { 109 | gl_FragColor = texture2D(texture, vec2(texCoord.x, 1.0 - texCoord.y)); 110 | } 111 | `); 112 | } 113 | 114 | return (this.gl).shaderCache.flipped; 115 | } 116 | 117 | private initialize() : void { 118 | this.canvas.width = this.source.width; 119 | this.canvas.height = this.source.height; 120 | 121 | // initialize the textures 122 | var textures : Texture[] = []; 123 | 124 | for (var i = 0; i < 2; i++) { 125 | textures.push(this.createTexture()); 126 | } 127 | 128 | this.textures = textures; 129 | } 130 | 131 | private cleanUp() : void { 132 | // destroy source texture 133 | this.sourceTexture.destroy(); 134 | 135 | // destroy textures used for filters 136 | for (var i = 0; i < 2; i++) { 137 | this.textures[i].destroy(); 138 | } 139 | 140 | // re-set textures 141 | this.textures = null; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/webgl/shader.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.webgl { 2 | export class Shader { 3 | private static defaultVertexSource : string = ` 4 | attribute vec2 vertex; 5 | attribute vec2 _texCoord; 6 | varying vec2 texCoord; 7 | 8 | void main() { 9 | texCoord = _texCoord; 10 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0); 11 | }`; 12 | 13 | private static defaultFragmentSource : string = ` 14 | uniform sampler2D texture; 15 | varying vec2 texCoord; 16 | 17 | void main() { 18 | gl_FragColor = texture2D(texture, texCoord); 19 | }`; 20 | 21 | private vertexSource : string; 22 | private fragmentSource : string; 23 | private vertexAttribute : any; 24 | private texCoordAttribute : any; 25 | private program : WebGLProgram; 26 | 27 | constructor(private gl : WebGLRenderingContext, vertexSource? : string, fragmentSource? : string) { 28 | // get the shader source 29 | this.vertexSource = vertexSource || Shader.defaultVertexSource; 30 | this.fragmentSource = fragmentSource || Shader.defaultFragmentSource; 31 | 32 | // set precision 33 | this.fragmentSource = "precision highp float;" + this.fragmentSource; 34 | 35 | // init vars 36 | this.vertexAttribute = null; 37 | this.texCoordAttribute = null; 38 | 39 | // create the program 40 | this.program = gl.createProgram(); 41 | 42 | // attach the shaders 43 | gl.attachShader(this.program, compileSource(gl, gl.VERTEX_SHADER, this.vertexSource)); 44 | gl.attachShader(this.program, compileSource(gl, gl.FRAGMENT_SHADER, this.fragmentSource)); 45 | 46 | // link the program and ensure it worked 47 | gl.linkProgram(this.program); 48 | 49 | if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) { 50 | throw "link error: " + gl.getProgramInfoLog(this.program); 51 | } 52 | } 53 | 54 | /** 55 | * textures are uniforms too but for some reason can't be specified by this.gl.uniform1f, 56 | * even though floating point numbers represent the integers 0 through 7 exactly 57 | * 58 | * @param textures 59 | * @returns {Shader} 60 | */ 61 | public textures(textures : any) { 62 | this.gl.useProgram(this.program); 63 | 64 | for (var name in textures) { 65 | if (!textures.hasOwnProperty(name)) { 66 | continue; 67 | } 68 | 69 | this.gl.uniform1i(this.gl.getUniformLocation(this.program, name), textures[name]); 70 | } 71 | 72 | return this; 73 | } 74 | 75 | public uniforms(uniforms : any) { 76 | this.gl.useProgram(this.program); 77 | 78 | for (var name in uniforms) { 79 | if (!uniforms.hasOwnProperty(name)) { 80 | continue; 81 | } 82 | 83 | var location = this.gl.getUniformLocation(this.program, name); 84 | if (location === null) { 85 | // will be null if the uniform isn't used in the shader 86 | continue; 87 | } 88 | 89 | var value : any = uniforms[name]; 90 | 91 | if (isArray(value)) { 92 | switch (value.length) { 93 | case 1: 94 | this.gl.uniform1fv(location, new Float32Array(value)); 95 | break; 96 | case 2: 97 | this.gl.uniform2fv(location, new Float32Array(value)); 98 | break; 99 | case 3: 100 | this.gl.uniform3fv(location, new Float32Array(value)); 101 | break; 102 | case 4: 103 | this.gl.uniform4fv(location, new Float32Array(value)); 104 | break; 105 | case 9: 106 | this.gl.uniformMatrix3fv(location, false, new Float32Array(value)); 107 | break; 108 | case 16: 109 | this.gl.uniformMatrix4fv(location, false, new Float32Array(value)); 110 | break; 111 | default: 112 | throw "dont't know how to load uniform \"" + name + "\" of length " + value.length; 113 | } 114 | } else if (isNumber(value)) { 115 | this.gl.uniform1f(location, value); 116 | } else { 117 | throw "attempted to set uniform \"" + name + "\" to invalid value " + (value || "undefined").toString(); 118 | } 119 | } 120 | 121 | return this; 122 | } 123 | 124 | public drawRect(left? : number, top? : number, right? : number, bottom? : number) { 125 | var viewport = this.gl.getParameter(this.gl.VIEWPORT); 126 | 127 | top = top !== undefined ? (top - viewport[1]) / viewport[3] : 0; 128 | left = left !== undefined ? (left - viewport[0]) / viewport[2] : 0; 129 | right = right !== undefined ? (right - viewport[0]) / viewport[2] : 1; 130 | bottom = bottom !== undefined ? (bottom - viewport[1]) / viewport[3] : 1; 131 | 132 | if (!(this.gl).vertexBuffer) { 133 | (this.gl).vertexBuffer = this.gl.createBuffer(); 134 | } 135 | 136 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, (this.gl).vertexBuffer); 137 | this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([left, top, left, bottom, right, top, right, bottom]), this.gl.STATIC_DRAW); 138 | 139 | if (!(this.gl).texCoordBuffer) { 140 | (this.gl).texCoordBuffer = this.gl.createBuffer(); 141 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, (this.gl).texCoordBuffer); 142 | this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]), this.gl.STATIC_DRAW); 143 | } 144 | 145 | if (this.vertexAttribute == null) { 146 | this.vertexAttribute = this.gl.getAttribLocation(this.program, "vertex"); 147 | this.gl.enableVertexAttribArray(this.vertexAttribute); 148 | } 149 | 150 | if (this.texCoordAttribute == null) { 151 | this.texCoordAttribute = this.gl.getAttribLocation(this.program, "_texCoord"); 152 | this.gl.enableVertexAttribArray(this.texCoordAttribute); 153 | } 154 | 155 | this.gl.useProgram(this.program); 156 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, (this.gl).vertexBuffer); 157 | this.gl.vertexAttribPointer(this.vertexAttribute, 2, this.gl.FLOAT, false, 0, 0); 158 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, (this.gl).texCoordBuffer); 159 | this.gl.vertexAttribPointer(this.texCoordAttribute, 2, this.gl.FLOAT, false, 0, 0); 160 | this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4); 161 | } 162 | 163 | destroy() { 164 | this.gl.deleteProgram(this.program); 165 | this.program = null; 166 | } 167 | } 168 | 169 | function compileSource(gl : any, type : any, source : any) { 170 | var shader = gl.createShader(type); 171 | gl.shaderSource(shader, source); 172 | gl.compileShader(shader); 173 | 174 | if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { 175 | throw "compile error: " + gl.getShaderInfoLog(shader); 176 | } 177 | 178 | return shader; 179 | } 180 | 181 | function isArray(obj : any) { 182 | return Object.prototype.toString.call(obj) === "[object Array]"; 183 | } 184 | 185 | function isNumber(obj : any) { 186 | return Object.prototype.toString.call(obj) === "[object Number]"; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/webgl/texture.ts: -------------------------------------------------------------------------------- 1 | namespace jsfx.webgl { 2 | export class Texture { 3 | private id : WebGLTexture; 4 | private element : HTMLImageElement; 5 | 6 | constructor(private gl : WebGLRenderingContext, private width : number, private height : number, private format : number = gl.RGBA, private type : number = gl.UNSIGNED_BYTE) { 7 | this.id = gl.createTexture(); 8 | this.element = null; 9 | 10 | gl.bindTexture(gl.TEXTURE_2D, this.id); 11 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); 12 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); 13 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); 14 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); 15 | 16 | if (width && height) { 17 | gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, this.type, null); 18 | } 19 | } 20 | 21 | public loadContentsOf(element : HTMLImageElement) : void { 22 | this.element = element; 23 | this.width = element.width; 24 | this.height = element.height; 25 | 26 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.id); 27 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.format, this.format, this.type, element); 28 | } 29 | 30 | public initFromBytes(width : number, height : number, data : any) : void { 31 | this.width = width; 32 | this.height = height; 33 | this.format = this.gl.RGBA; 34 | this.type = this.gl.UNSIGNED_BYTE; 35 | 36 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.id); 37 | this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0, this.gl.RGBA, this.type, new Uint8Array(data)); 38 | } 39 | 40 | public use(unit? : number) { 41 | this.gl.activeTexture(this.gl.TEXTURE0 + (unit || 0)); 42 | this.gl.bindTexture(this.gl.TEXTURE_2D, this.id); 43 | } 44 | 45 | public unuse(unit? : number) : void { 46 | this.gl.activeTexture(this.gl.TEXTURE0 + (unit || 0)); 47 | this.gl.bindTexture(this.gl.TEXTURE_2D, null); 48 | } 49 | 50 | public drawTo(callback : Function) : void { 51 | // create and bind frame buffer 52 | (this.gl).frameBuffer = (this.gl).frameBuffer || this.gl.createFramebuffer(); 53 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, (this.gl).frameBuffer); 54 | this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, this.id, 0); 55 | 56 | // ensure there was no error 57 | if (this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER) !== this.gl.FRAMEBUFFER_COMPLETE) { 58 | throw new Error("incomplete framebuffer"); 59 | } 60 | 61 | // set the viewport 62 | this.gl.viewport(0, 0, this.width, this.height); 63 | 64 | // do the drawing 65 | callback(); 66 | 67 | // stop rendering to this texture 68 | this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); 69 | } 70 | 71 | public destroy() : void { 72 | this.gl.deleteTexture(this.id); 73 | this.id = null; 74 | } 75 | 76 | static fromElement(gl : WebGLRenderingContext, element : HTMLImageElement) : Texture { 77 | var texture = new Texture(gl, 0, 0); 78 | texture.loadContentsOf(element); 79 | 80 | return texture; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": false, 5 | "removeComments": true, 6 | "preserveConstEnums": true, 7 | "out": "jsfx.js", 8 | "target": "ES5" 9 | }, 10 | "files": [ 11 | "src/filter/filterInterface.ts", 12 | "src/filter/filter.ts", 13 | "src/filter/iterableFilterInterface.ts", 14 | "src/filter/iterableFilter.ts", 15 | "src/**/*.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": true, 5 | "eofline": true, 6 | "forin": true, 7 | "indent": [true, "spaces"], 8 | "label-position": true, 9 | "label-undefined": true, 10 | "max-line-length": [true, 140], 11 | "no-arg": true, 12 | "no-bitwise": true, 13 | "no-console": [true, 14 | "debug", 15 | "info", 16 | "time", 17 | "timeEnd", 18 | "trace" 19 | ], 20 | "no-construct": true, 21 | "no-debugger": true, 22 | "no-duplicate-key": true, 23 | "no-duplicate-variable": true, 24 | "no-empty": true, 25 | "no-eval": true, 26 | "no-string-literal": true, 27 | "no-switch-case-fall-through": true, 28 | "no-trailing-comma": true, 29 | "no-trailing-whitespace": true, 30 | "no-unused-expression": true, 31 | "no-unused-variable": false, 32 | "no-unreachable": true, 33 | "no-use-before-declare": true, 34 | "one-line": [true, 35 | "check-open-brace", 36 | "check-catch", 37 | "check-else", 38 | "check-whitespace" 39 | ], 40 | "quotemark": [true, "double"], 41 | "radix": true, 42 | "semicolon": true, 43 | "triple-equals": [true, "allow-null-check"], 44 | "variable-name": false, 45 | "whitespace": [true, 46 | "check-branch", 47 | "check-decl", 48 | "check-operator", 49 | "check-separator", 50 | "check-type" 51 | ] 52 | } 53 | } --------------------------------------------------------------------------------