From 424f4ae5730fb8ff2f2bef15e7be2dad394a283e Mon Sep 17 00:00:00 2001 From: Florian Boesch Date: Thu, 14 Mar 2013 15:19:52 +0000 Subject: [PATCH] added an option to use a gradient texture and turn of intensity to alpha --- deep-sea-gradient.png | Bin 0 -> 3403 bytes example.html | 4 +- readme.md | 8 ++++ skyline-gradient.png | Bin 0 -> 3452 bytes webgl-heatmap.coffee | 83 ++++++++++++++++++++++++++++++++---------- webgl-heatmap.js | 50 +++++++++++++++++++++++-- 6 files changed, 121 insertions(+), 24 deletions(-) create mode 100644 deep-sea-gradient.png create mode 100644 skyline-gradient.png diff --git a/deep-sea-gradient.png b/deep-sea-gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..dfb8f3fce597496af0f4ebf3f9c0d1d2117a46d5 GIT binary patch literal 3403 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 hZf69X window.onload = function(){ var canvas = document.getElementsByTagName('canvas')[0]; - var heatmap = createWebGLHeatmap({canvas: canvas}); + var heatmap = createWebGLHeatmap({canvas: canvas, intensityToAlpha:true}); + //var heatmap = createWebGLHeatmap({canvas: canvas, intensityToAlpha:true, gradientTexture:'deep-sea-gradient.png'}); + //var heatmap = createWebGLHeatmap({canvas: canvas, intensityToAlpha:false, gradientTexture:'skyline-gradient.png'}); //var heatmap = createWebGLHeatmap({width: 500, height: 500}); // statically sized heatmap // just feeding some mouse coords diff --git a/readme.md b/readme.md index a80b9bc..3307ed2 100644 --- a/readme.md +++ b/readme.md @@ -28,6 +28,14 @@ catch(error){ } ``` +creation arguments + + * canvas: the canvas you wish to draw on + * width: explicit width + * height: explicit height + * intensityToAlpha: defaults to true + * gradientTexture: texture used instead of color calculation, can be path or an image + Add a data point. * x and y relative to the canvas in pixels diff --git a/skyline-gradient.png b/skyline-gradient.png new file mode 100644 index 0000000000000000000000000000000000000000..9e046dadc7b8705034081ff66adc212fc43ac4e2 GIT binary patch literal 3452 zcmV-?4TJKDP)0o{~i8G{6YRf{vv)6AMh9XA-;ew@g{ zdiwpWmvR?u>l8sy0`G-{L;~?2a+~$sz6n6xOX91!JLdaw`B(vz3(n^w9$qH!=cJ(( z>!zDw7~{q4dcLVRy!F+`J%0P&J+vc%Vw@iqgq@-US?eAAEcZmZW0pJIqNY?xJp*qANK1W}~IrvbY6YqTbPko(ypE?JhqXXOv4si|+ z^&!svlsG(&078Z4JrIJ*PvscfuvHPRzCoNzA`=Ts62m6wO#ljfyZ{k#$NQG38`g?P zHGHZv0r3;e>0(SjuOdq8d%{X3*ATorAt5p`jAJVi`ISPm}sI%HY0$ieWKu zM&QLpC{iHYx|xc|u)UzCA*Mm($5@9g4twyKs^d!iZUPAFleOovxoVjV2Z%FSImo;) zQmM*f3NTb%(@s>m6_G5)DQo*Mv0P~#6BC5@behz^2kg|X+>%xANHRJ%v5N@iR-`%vc- z@Y0K5J>l(_@iN_%oGDuB0}Ng;LPhq6!I27D44-L^Z4oL@9c@~7l!0{8+p#xhC~re9 z(&J!vtK>wK;7eBgMqE@Nw4p0g5&ba-6YKADoatQa*-GIz>_gaf)4zOl=l&07Qq3nV*JvstX#h7Hk| zqLyFY5}!M12fl|e(6vt|H^X5wQpIOEPoFy9c$X(@g*b^}!ZfaE{d2S4Qib~+sS zw^g4OLUfBHHF?bJn{PExBvGvVEq9km!}NH^Bi%Cw-P9bJOL3~oM4rT#bef0Mfw;-7 zr}QVmp_SaCQZ)=-X?0pgxl=E-MrcG$eT57+I*3h*YQM{R#vr#zKMj(cc149!`FRFE zQyUOBGbfg}#`()wzOy%KrW}%E%gVW6&5r>s62mopO-h}fJaMD z-NcAOrt8wK$6b$L5LLwsP?2@GR)Yr~ z>>!k^059%c38plgY;RqeUQfxP4O41>mJ0G9us4{g7Y(}Pfb?}{V_-xX3qV-}wrON& zWX*d$yyBxwYes=MH7&ic@oJDvu}4EyscW>uoBIDSh=kCv@rDhA#4M#Ec2k+bjirctv;Zo<&rSjMCO zI)znLXJElliBj@1JCk|G5lv(>PoR?_3!hC6=rxW?i%VVpz)JrI_oPP?@4tC2N)3-uORcs*>5V9jY#m z^rls5WKpfas!n?AK+c1jf}6$`9Y2}KVFLT2H{~nL>UIx705u%t#gTPA!tt^*as4Br z)MJB`Hlv6}xcb*)*o6|-_@fKR+*-8F(U{W}rJkdd zSy^(an$#L~43|rkIaZ)PmFTM|wav!JKd4MyalX zlMI@A@BKw*S$y(SE*?!gU8^?eiqFe*jB6A_ucJYD9U@0-Om5Ul8-une@2pN`N>RGB z8`j+9a{x%KeCpNu@-Yy z?VjN60jvlfdean0yT)oIvj;P*UDa+bn*h@6qduk|N4uqFVC2*J(RqK<*PJg{3eAR; z0jWkqrsjhtO`YgeX9T7@%XwvRRWP*da@Uc@A99H?)< z0NBmIDljz$T<6r$8{9u9Wckkm&u`2E&7oq>AgbSNgwSY%C!%sZ-=zVOd?6fKt$v|9 z?@NchVv}H#{5Fi+Vx;I3M0M=WWcmZXQjJvr`$+8Q%{LFK%2h@jku_sTg$nJNo1c%) z#q*?$+tu$Hg1VFD7a_ZntNBDzQyxI zm{qnHbkb>kJK z66npPp*io_sLI}YaVKd~enpRh;M6s3M6-q*jEl<052eT0_>eY=!-(GGkV+(lmBCwv zV0qb?80(}XacZW;fH;7zR2vM|@G>yg8s3mTh6Jq_nmG^L=E1f4AG1_dD=8Y#ZWARM z^E~99hj1j4d_2^Cg*SKVZ9+bNVBv*|cBgBnKo(!8dcwQLJp1=3J&(r6FEcoX`Ff;| eCj0g@4F3i;vYelP3ls+c0000 + @width = data.width + @height = data.height + + @gl.texImage2D @target, 0, @channels, @channels, @type, data + return @ + linear: -> @gl.texParameteri @target, @gl.TEXTURE_MAG_FILTER, @gl.LINEAR @gl.texParameteri @target, @gl.TEXTURE_MIN_FILTER, @gl.LINEAR @@ -368,7 +375,7 @@ class Heights @pointCount += 1 class WebGLHeatmap - constructor: ({@canvas, @width, @height}={}) -> + constructor: ({@canvas, @width, @height, intensityToAlpha, gradientTexture}={}) -> @canvas = document.createElement('canvas') unless @canvas try @gl = @canvas.getContext('experimental-webgl', depth:false, antialias:false) @@ -385,22 +392,30 @@ class WebGLHeatmap @gl.enableVertexAttribArray 0 @gl.blendFunc @gl.ONE, @gl.ONE - @shader = new Shader @gl, - vertex: vertexShaderBlit - fragment: fragmentShaderBlit + ''' - float linstep(float low, float high, float value){ - return clamp((value-low)/(high-low), 0.0, 1.0); - } + if gradientTexture + textureGradient = @gradientTexture = new Texture(@gl, channels:'rgba').bind(0).setSize(2, 2).linear().clampToEdge() + if typeof gradientTexture == 'string' + image = new Image() + image.onload = -> + textureGradient.bind().upload(image) + image.src = gradientTexture + else + if gradientTexture.width > 0 and gradientTexture.height > 0 + textureGradient.upload(gradientTexture) + else + gradientTexture.onload = -> + textureGradient.upload(gradientTexture) - float fade(float low, float high, float value){ - float mid = (low+high)*0.5; - float range = (high-low)*0.5; - float x = 1.0 - clamp(abs(mid-value)/range, 0.0, 1.0); - return smoothstep(0.0, 1.0, x); + getColorFun = ''' + uniform sampler2D gradientTexture; + vec3 getColor(float intensity){ + return texture2D(gradientTexture, vec2(intensity, 0.0)).rgb; } - void main(){ - float intensity = smoothstep(0.0, 1.0, texture2D(source, texcoord).r); - + ''' + else + textureGradient = null + getColorFun = ''' + vec3 getColor(float intensity){ vec3 blue = vec3(0.0, 0.0, 1.0); vec3 cyan = vec3(0.0, 1.0, 1.0); vec3 green = vec3(0.0, 1.0, 0.0); @@ -414,12 +429,40 @@ class WebGLHeatmap fade(0.5, 1.0, intensity)*yellow + smoothstep(0.75, 1.0, intensity)*red ); - - gl_FragColor = vec4(color*intensity, intensity); - //gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0); + return color; } ''' + intensityToAlpha ?= true + + if intensityToAlpha + output = 'vec4(color*intensity, intensity)' + else + output = 'vec4(color, 1.0)' + + @shader = new Shader @gl, + vertex: vertexShaderBlit + fragment: fragmentShaderBlit + """ + float linstep(float low, float high, float value){ + return clamp((value-low)/(high-low), 0.0, 1.0); + } + + float fade(float low, float high, float value){ + float mid = (low+high)*0.5; + float range = (high-low)*0.5; + float x = 1.0 - clamp(abs(mid-value)/range, 0.0, 1.0); + return smoothstep(0.0, 1.0, x); + } + + #{getColorFun} + + void main(){ + float intensity = smoothstep(0.0, 1.0, texture2D(source, texcoord).r); + vec3 color = getColor(intensity); + gl_FragColor = #{output}; + } + """ + @width ?= @canvas.offsetWidth or 2 @height ?= @canvas.offsetHeight or 2 @canvas.width = @width @@ -458,7 +501,9 @@ class WebGLHeatmap @gl.bindBuffer @gl.ARRAY_BUFFER, @quad @gl.vertexAttribPointer(0, 4, @gl.FLOAT, false, 0, 0) @heights.nodeFront.bind(0) - @shader.use().int('source', 0) + if @gradientTexture + @gradientTexture.bind(1) + @shader.use().int('source', 0).int('gradientTexture', 1) @gl.drawArrays @gl.TRIANGLES, 0, 6 update: -> diff --git a/webgl-heatmap.js b/webgl-heatmap.js index 180f1db..3971c82 100644 --- a/webgl-heatmap.js +++ b/webgl-heatmap.js @@ -208,6 +208,13 @@ return this; }; + Texture.prototype.upload = function(data) { + this.width = data.width; + this.height = data.height; + this.gl.texImage2D(this.target, 0, this.channels, this.channels, this.type, data); + return this; + }; + Texture.prototype.linear = function() { this.gl.texParameteri(this.target, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); this.gl.texParameteri(this.target, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); @@ -432,8 +439,8 @@ WebGLHeatmap = (function() { function WebGLHeatmap(_arg) { - var quad, _ref, _ref1, _ref2; - _ref = _arg != null ? _arg : {}, this.canvas = _ref.canvas, this.width = _ref.width, this.height = _ref.height; + var getColorFun, gradientTexture, image, intensityToAlpha, output, quad, textureGradient, _ref, _ref1, _ref2; + _ref = _arg != null ? _arg : {}, this.canvas = _ref.canvas, this.width = _ref.width, this.height = _ref.height, intensityToAlpha = _ref.intensityToAlpha, gradientTexture = _ref.gradientTexture; if (!this.canvas) { this.canvas = document.createElement('canvas'); } @@ -459,9 +466,41 @@ } this.gl.enableVertexAttribArray(0); this.gl.blendFunc(this.gl.ONE, this.gl.ONE); + if (gradientTexture) { + textureGradient = this.gradientTexture = new Texture(this.gl, { + channels: 'rgba' + }).bind(0).setSize(2, 2).linear().clampToEdge(); + if (typeof gradientTexture === 'string') { + image = new Image(); + image.onload = function() { + return textureGradient.bind().upload(image); + }; + image.src = gradientTexture; + } else { + if (gradientTexture.width > 0 && gradientTexture.height > 0) { + textureGradient.upload(gradientTexture); + } else { + gradientTexture.onload = function() { + return textureGradient.upload(gradientTexture); + }; + } + } + getColorFun = 'uniform sampler2D gradientTexture;\nvec3 getColor(float intensity){\n return texture2D(gradientTexture, vec2(intensity, 0.0)).rgb;\n}'; + } else { + textureGradient = null; + getColorFun = 'vec3 getColor(float intensity){\n vec3 blue = vec3(0.0, 0.0, 1.0);\n vec3 cyan = vec3(0.0, 1.0, 1.0);\n vec3 green = vec3(0.0, 1.0, 0.0);\n vec3 yellow = vec3(1.0, 1.0, 0.0);\n vec3 red = vec3(1.0, 0.0, 0.0);\n\n vec3 color = (\n fade(-0.25, 0.25, intensity)*blue +\n fade(0.0, 0.5, intensity)*cyan +\n fade(0.25, 0.75, intensity)*green +\n fade(0.5, 1.0, intensity)*yellow +\n smoothstep(0.75, 1.0, intensity)*red\n );\n return color;\n}'; + } + if (intensityToAlpha == null) { + intensityToAlpha = true; + } + if (intensityToAlpha) { + output = 'vec4(color*intensity, intensity)'; + } else { + output = 'vec4(color, 1.0)'; + } this.shader = new Shader(this.gl, { vertex: vertexShaderBlit, - fragment: fragmentShaderBlit + 'float linstep(float low, float high, float value){\n return clamp((value-low)/(high-low), 0.0, 1.0);\n}\n\nfloat fade(float low, float high, float value){\n float mid = (low+high)*0.5;\n float range = (high-low)*0.5;\n float x = 1.0 - clamp(abs(mid-value)/range, 0.0, 1.0);\n return smoothstep(0.0, 1.0, x);\n}\nvoid main(){\n float intensity = smoothstep(0.0, 1.0, texture2D(source, texcoord).r);\n\n vec3 blue = vec3(0.0, 0.0, 1.0);\n vec3 cyan = vec3(0.0, 1.0, 1.0);\n vec3 green = vec3(0.0, 1.0, 0.0);\n vec3 yellow = vec3(1.0, 1.0, 0.0);\n vec3 red = vec3(1.0, 0.0, 0.0);\n\n vec3 color = (\n fade(-0.25, 0.25, intensity)*blue +\n fade(0.0, 0.5, intensity)*cyan +\n fade(0.25, 0.75, intensity)*green +\n fade(0.5, 1.0, intensity)*yellow +\n smoothstep(0.75, 1.0, intensity)*red\n );\n\n gl_FragColor = vec4(color*intensity, intensity);\n //gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);\n}' + fragment: fragmentShaderBlit + ("float linstep(float low, float high, float value){\n return clamp((value-low)/(high-low), 0.0, 1.0);\n}\n\nfloat fade(float low, float high, float value){\n float mid = (low+high)*0.5;\n float range = (high-low)*0.5;\n float x = 1.0 - clamp(abs(mid-value)/range, 0.0, 1.0);\n return smoothstep(0.0, 1.0, x);\n}\n\n" + getColorFun + "\n\nvoid main(){\n float intensity = smoothstep(0.0, 1.0, texture2D(source, texcoord).r);\n vec3 color = getColor(intensity);\n gl_FragColor = " + output + ";\n}") }); if ((_ref1 = this.width) == null) { this.width = this.canvas.offsetWidth || 2; @@ -498,7 +537,10 @@ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quad); this.gl.vertexAttribPointer(0, 4, this.gl.FLOAT, false, 0, 0); this.heights.nodeFront.bind(0); - this.shader.use().int('source', 0); + if (this.gradientTexture) { + this.gradientTexture.bind(1); + } + this.shader.use().int('source', 0).int('gradientTexture', 1); return this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); };