Histogram in WebGL2
HTML
<script id="hist-vs" type="not-js">
#version 300 es
in float pixelId;
uniform sampler2D u_texture;
uniform vec4 u_colorMult;
void main() {
// based on an id (0, 1, 2, 3 ...) compute the pixel x, y for the source image
ivec2 resolution = textureSize(u_texture, 0);
ivec2 pixel = ivec2(gl_VertexID % resolution.x, gl_VertexID / resolution.x);
// get the pixels but 0 out channels we don't want
vec4 color = texelFetch(u_texture, pixel, 0) * u_colorMult;
// add up all the channels. Since 3 are zeroed out we'll get just one channel
float colorSum = color.r + color.g + color.b + color.a;
// set the position to be over a single pixel in the 256x1 destination texture
gl_Position = vec4((colorSum * 255.0 + 0.5) / 256.0 * 2.0 - 1.0, 0.5, 0, 1);
gl_PointSize = 1.0;
}
</script>
<script id="hist-fs" type="not-js">
#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
outColor = vec4(1);
}
</script>
<script id="max-fs" type="not-js">
#version 300 es
precision mediump float;
uniform sampler2D u_texture;
out vec4 outColor;
void main() {
vec4 maxColor = vec4(0);
// we know the texture is 256x1 so just go over the whole thing
for (int i = 0; i < 256; ++i) {
// get max value of pixel
maxColor = max(maxColor, texelFetch(u_texture, ivec2(i, 0), 0));
}
outColor = maxColor;
}
</script>
<script id="show-vs" type="not-js">
#version 300 es
in vec4 position;
void main() {
gl_Position = position;
}
</script>
<script id="show-fs" type="not-js">
#version 300 es
precision mediump float;
uniform sampler2D u_histTexture;
uniform vec2 u_resolution;
uniform sampler2D u_maxTexture;
out vec4 outColor;
void main() {
// get the max color constants
vec4 maxColor = texture(u_maxTexture, vec2(0));
// compute our current UV position
vec2 uv = gl_FragCoord.xy / u_resolution;
// Get the history for this color
// (note: since u_histTexture is 256x1 uv.y is irrelevant
vec4 hist = texture(u_histTexture, uv);
// scale by...
CSS
img, canvas { border: 1px solid black; margin: 5px; }
JavaScript
"use strict";
var canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 120;
var m4 = twgl.m4;
var gl = canvas.getContext("webgl2");
var ext = gl.getExtension("EXT_color_buffer_float");
if (!ext) {
alert("requires EXT_color_buffer_float");
}
twgl.createTexture(gl, {
//src: "https://i.imgur.com/9Y3sd8S.png",
src: "https://farm1.staticflickr.com/293/18414763798_cb8ebded43_m_d.jpg",
// required link: https://www.flickr.com/photos/greggman/18414763798/in/album-72157653822314919/
min: gl.NEAREST,
mag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
crossOrigin: "",
}, function(err, tex, img) {
log("img");
document.body.appendChild(img);
log("histogram");
document.body.appendChild(canvas);
var quadBufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
var histProgramInfo = twgl.createProgramInfo(gl, ["hist-vs", "hist-fs"]);
var numPixels = img.width * img.height;
// make a 256x1 RGBA floating point texture and attach to a framebuffer
var sumFbi = twgl.createFramebufferInfo(gl, [
{ internalFormat: gl.RGBA32F,
min: gl.NEAREST,
mag: gl.NEAREST,
wrap: gl.CLAMP_TO_EDGE,
},
], 256, 1);
// Render sum of each color
// we're going to render a gl.POINT for each pixel in the source image
// That point will be positioned based on the color of the source image
// we're just going to render vec4(1,1,1,1). This blend function will
// mean each time we render to a specific point that point will get
// incremented by 1.
gl.blendFunc(gl.ONE, gl.ONE);
gl.enable(gl.BLEND);
gl.useProgram(histProgramInfo.program);
twgl.bindFramebufferInfo(gl, sumFbi);
// render each channel separately since we can only position each POINT
// for one channel at a time.
for (var channel = 0; channel < 4; ++channel) {
gl.colorMask(channel === 0, channel === 1, channel === 2, channel === 3);
twgl.setUniforms(histProgramInfo, {
u_texture: tex,
u_colorMult: [
channel === 0 ? 1...