0&&(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 | }
--------------------------------------------------------------------------------