I would like to implement an image filter called a Mode Filter in GLSL for use in a WebGL application. A mode filter computes the mode (most frequent) value from the surrounding pixels and if there is a single value that is most frequent, sets the pixel colour to the mode, otherwise leaves it unchanged. The surrounding pixels is determined by the kernel size, typically being 9 or 25 pixels centred on the pixel in question.
I am a real beginner with shaders, GLSL and the intricacies of achieving performance and my current implementation seems to be particularly bad. I'm looking for some help finding a more efficient approach.
I put my current shader into shader toy at https://www.shadertoy.com/view/wsVSWw, I get under 10 fps! Note you need to select something for iChannel0 to see the effect.
Edit
I converted a counting sort algorithm to GLSL and grabbed a loop from a median filter, result is 30fps now, new version at https://www.shadertoy.com/view/tsVSDm
In my particular case, I'm working with a grayscale photo so I've only bothered with the red channel.
Here is shader as implemented for webgl, it has minor differences from the shadertoy version (for instance texture2D() instead of texture()):
precision mediump float;
varying vec2 v_coord;
uniform sampler2D u_texture;
uniform vec2 imageResolution;
int histogram[256];
void main() {
vec2 pos = vec2(v_coord.x, 1.0 - v_coord.y);
vec4 outColor = texture2D(u_texture, pos);
gl_FragColor = outColor;
/* the mode filter does not apply to semi-transparent pixels
for performance reasons, we would need to mix pixels based
on alpha to get the effective color for the mode calculation
then remix to get the correct color with the current pixel's
alpha. This is both hairy and would add processing time
*/
if (outColor.a < 1.0) {
return;
}
vec2 onePixel = vec2(1) / imageResolution;
for (int i = 0; i<256; i++) {
histogram[i] = 0;
}
int maxValue = 0;
int offset = int(float(${kernelSize})/2.0);
for (int yy = 0; yy < ${kernelSize}; yy ++) {
for (int xx = 0; xx < ${kernelSize}; xx ++) {
vec2 samplePos = pos + vec2(yy - offset, xx - offset) * onePixel;
vec4 color = texture2D(u_texture, samplePos);
if (color.a == 1.0) {
int red = int(color.r * 255.0);
for (int i = 0; i<256; i++) {
if (i == red) {
histogram[i]++;
maxValue = int(max(float(maxValue), float(histogram[i])));
}
}
}
}
}
/* the number of values that have the maximum mode value */
int numModes = 0;
/* the colour (index) of the maximum node value */
int modeValue = 0;
for (int i = 0; i<256; i++) {
if (histogram[i] == maxValue) {
numModes++;
modeValue = i;
}
}
if (numModes == 1) {
outColor.r = float(modeValue) / 255.0;
}
outColor.g = outColor.r;
outColor.b = outColor.r;
gl_FragColor = outColor;
}