Histogram in WebGL

by Gregg Tavares

HTML

<script id="hist-vs" type="not-js">
attribute float pixelId;

uniform vec2 u_resolution;
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
  vec2 pixel = vec2(mod(pixelId, u_resolution.x), floor(pixelId / u_resolution.x));

  // compute corresponding uv center of that pixel
  vec2 uv = (pixel + 0.5) / u_resolution;

  // get the pixels but 0 out channels we don't want
  vec4 color = texture2D(u_texture, uv) * 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">
precision mediump float;

void main() {
  gl_FragColor = vec4(1);
}
</script>
<script id="max-fs" type="not-js">
precision mediump float;

uniform sampler2D u_texture;

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) {
    // compute centers of pixels
    vec2 uv = vec2((float(i) + 0.5) / 256.0, 0.5);

    // get max value of pixel
    maxColor = max(maxColor, texture2D(u_texture, uv));
  }

  gl_FragColor = maxColor;
}
</script>
<script id="show-vs" type="not-js">
attribute vec4 position;
void main() {
  gl_Position = position;
}
</script>
<script id="show-fs" type="not-js">
precision mediump float;

uniform sampler2D u_histTexture;
uniform vec2 u_resolution;
uniform sampler2D u_maxTexture;

void main() {
  // get the max color constants
  vec4 maxColor = texture2D(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 =...

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("webgl");
var ext = gl.getExtension("OES_texture_float");
if (!ext) {
   alert("requires OES_texture_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 pixelIds = [];
  var numIds = img.width * img.height;

  // Just fill a buffer with an incrementing count. If we wanted to make this
  // generic we'd re-use this buffer and just make it bigger if we were
  // processing a bigger image
  for (var i = 0; i < numIds; ++i) {
    pixelIds.push(i);
  }
  var pixelIdBufferInfo = twgl.createBufferInfoFromArrays(gl, {
    pixelId: { size: 1, data: new Float32Array(pixelIds), },
  });

  // make a 256x1 RGBA floating point texture and attach to a framebuffer
  var sumFbi = twgl.createFramebufferInfo(gl, [
    { type: gl.FLOAT,
      min: gl.NEAREST,
      mag: gl.NEAREST,
      wrap: gl.CLAMP_TO_EDGE,
    },
  ], 256, 1);
  if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
    alert("can't render to floating point texture");
  }

  // 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...