Function to blur to any image

@yojimbo2000 made a super-awesome Gaussian blur for his Soda tools.

I wanted it!

So I turned it into a stand-alone function that takes any image as a parameter and returns a blurred version of that image.

In case such a thing is of use to anyone else, here’s the code:


--# Main
--blurFunction
--adapted by UberGoober from Yojimbo2000 from http://xissburg.com/faster-gaussian-blur-in-glsl/ and http://www.sunsetlakesoftware.com/2013/10/21/optimizing-gaussian-blurs-mobile-gpu

function setup()
    preBlur = imageResizedToScreen(readImage("Cargo Bot:Game Area"))
    preBlur = imageWithGaussian2PassBlur(preBlur)
end

function imageResizedToScreen(imageToResize)
    local screenSizedImage = image(WIDTH,HEIGHT)
    setContext(screenSizedImage)
    sprite(imageToResize,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    setContext()
    return screenSizedImage
end

function draw()
    sprite(preBlur, WIDTH/2,HEIGHT/2, WIDTH,HEIGHT)
end
--# imageWithGaussian2PassBlur
function imageWithGaussian2PassBlur(imageToBlur)
    --aspect ratio for blurring with:
    local largestSide = math.max(imageToBlur.width,imageToBlur.height)
    local aspect = vec2(largestSide/imageToBlur.width, largestSide/imageToBlur.height) --should be inverse ratio?
    --dimensions for fullSized and downsampled images
    local downsampleAmount = 0.5 -- going down to 0.25 actually looks pretty good, but, weirdly, slower than 0.5
    local fullDimensions = vec2(imageToBlur.width,imageToBlur.height)
    local downsampleDimensions = vec2(imageToBlur.width*downsampleAmount,imageToBlur.height*downsampleAmount)
    --images
    local blurImages = {}
    blurImages.fullSized = imageToBlur
    blurImages.downsampled = image(downsampleDimensions.x,downsampleDimensions.y)
    setContext(blurImages.downsampled)
    sprite(imageToBlur,downsampleDimensions.x/2,downsampleDimensions.y/2,downsampleDimensions.x,downsampleDimensions.y)
    setContext()
    --meshes
    local blurMeshes = {}
    blurMeshes.horizontal = mesh()
    blurMeshes.vertical = mesh()
    --horizontal mesh settings
    blurMeshes.horizontal.texture = blurImages.fullSized
    blurMeshes.horizontal:addRect(downsampleDimensions.x/2,downsampleDimensions.y/2,
        downsampleDimensions.x,downsampleDimensions.y) --fullSized image uses downsampled rect
    blurMeshes.horizontal.shader = shader(gaussianShader.vert[1],gaussianShader.frag)
    blurMeshes.horizontal.shader.am = aspect
    --vertical mesh settings
    blurMeshes.vertical.texture = blurImages.downsampled
    blurMeshes.vertical:addRect(fullDimensions.x/2,fullDimensions.y/2,
        fullDimensions.x,fullDimensions.y) --downsampled image uses fullSized rect
    blurMeshes.vertical.shader = shader(gaussianShader.vert[2],gaussianShader.frag)
    blurMeshes.vertical.shader.am = aspect
    --draw the blurred horizontal mesh to the vertical mesh texture
    setContext(blurMeshes.vertical.texture)
    blurMeshes.horizontal:draw() --pass one
    setContext()
    --draw the double-blurred vertical mesh to a new image
    local renderTarget = image(imageToBlur.width,imageToBlur.height)
    setContext(renderTarget)
    blurMeshes.vertical:draw() --pass two
    setContext()
    --send back the blurred image
    return renderTarget
end

gaussianShader = {
vert = { -- horizontal pass vertex shader
[[
uniform mat4 modelViewProjection;
uniform vec2 am; // ammount of blur, inverse aspect ratio (so that oblong shapes still produce round blur)
attribute vec4 position;
attribute vec2 texCoord;
 
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
 
void main()
{
    gl_Position = modelViewProjection * position;
    vTexCoord = texCoord;
    v_blurTexCoords[ 0] = vTexCoord + vec2(-0.028 * am.x, 0.0);
    v_blurTexCoords[ 1] = vTexCoord + vec2(-0.024 * am.x, 0.0);
    v_blurTexCoords[ 2] = vTexCoord + vec2(-0.020 * am.x, 0.0);
    v_blurTexCoords[ 3] = vTexCoord + vec2(-0.016 * am.x, 0.0);
    v_blurTexCoords[ 4] = vTexCoord + vec2(-0.012 * am.x, 0.0);
    v_blurTexCoords[ 5] = vTexCoord + vec2(-0.008 * am.x, 0.0);
    v_blurTexCoords[ 6] = vTexCoord + vec2(-0.004 * am.x, 0.0);
    v_blurTexCoords[ 7] = vTexCoord + vec2( 0.004 * am.x, 0.0);
    v_blurTexCoords[ 8] = vTexCoord + vec2( 0.008 * am.x, 0.0);
    v_blurTexCoords[ 9] = vTexCoord + vec2( 0.012 * am.x, 0.0);
    v_blurTexCoords[10] = vTexCoord + vec2( 0.016 * am.x, 0.0);
    v_blurTexCoords[11] = vTexCoord + vec2( 0.020 * am.x, 0.0);
    v_blurTexCoords[12] = vTexCoord + vec2( 0.024 * am.x, 0.0);
    v_blurTexCoords[13] = vTexCoord + vec2( 0.028 * am.x, 0.0);
}]],
-- vertical pass vertex shader
 [[
uniform mat4 modelViewProjection;
uniform vec2 am; // ammount of blur
attribute vec4 position;
attribute vec2 texCoord;
 
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
 
void main()
{
    gl_Position = modelViewProjection * position;
    vTexCoord = texCoord;
    v_blurTexCoords[ 0] = vTexCoord + vec2(0.0, -0.028 * am.y);
    v_blurTexCoords[ 1] = vTexCoord + vec2(0.0, -0.024 * am.y);
    v_blurTexCoords[ 2] = vTexCoord + vec2(0.0, -0.020 * am.y);
    v_blurTexCoords[ 3] = vTexCoord + vec2(0.0, -0.016 * am.y);
    v_blurTexCoords[ 4] = vTexCoord + vec2(0.0, -0.012 * am.y);
    v_blurTexCoords[ 5] = vTexCoord + vec2(0.0, -0.008 * am.y);
    v_blurTexCoords[ 6] = vTexCoord + vec2(0.0, -0.004 * am.y);
    v_blurTexCoords[ 7] = vTexCoord + vec2(0.0,  0.004 * am.y);
    v_blurTexCoords[ 8] = vTexCoord + vec2(0.0,  0.008 * am.y);
    v_blurTexCoords[ 9] = vTexCoord + vec2(0.0,  0.012 * am.y);
    v_blurTexCoords[10] = vTexCoord + vec2(0.0,  0.016 * am.y);
    v_blurTexCoords[11] = vTexCoord + vec2(0.0,  0.020 * am.y);
    v_blurTexCoords[12] = vTexCoord + vec2(0.0,  0.024 * am.y);
    v_blurTexCoords[13] = vTexCoord + vec2(0.0,  0.028 * am.y);
}]]},
--fragment shader
frag = [[precision mediump float;
 
uniform lowp sampler2D texture;
 
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
 
void main()
{
    gl_FragColor = vec4(0.0);
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 0])*0.0044299121055113265;
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 1])*0.00895781211794;
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 2])*0.0215963866053;
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 3])*0.0443683338718;
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 4])*0.0776744219933;
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 5])*0.115876621105;
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 6])*0.147308056121;
    gl_FragColor += texture2D(texture, vTexCoord         )*0.159576912161;
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 7])*0.147308056121;
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 8])*0.115876621105;
    gl_FragColor += texture2D(texture, v_blurTexCoords[ 9])*0.0776744219933;
    gl_FragColor += texture2D(texture, v_blurTexCoords[10])*0.0443683338718;
    gl_FragColor += texture2D(texture, v_blurTexCoords[11])*0.0215963866053;
    gl_FragColor += texture2D(texture, v_blurTexCoords[12])*0.00895781211794;
    gl_FragColor += texture2D(texture, v_blurTexCoords[13])*0.0044299121055113265;
}]]
}

Nice background!

Thanks @UberGoober ,

you can see the difference with sprite without blur


function draw()    
    sprite(preBlur, WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    sprite("Cargo Bot:Game Area",0,HEIGHT/2,WIDTH/1.1,HEIGHT)
end

@hpsoft nice!

Good work! It’s really nice to see code being recycled and re-worked.

One thing I noticed (this is probably an issue in my original), when removing the width and height from the Sprite command in draw, to see it at its regular resolution, is that the top of the image is sometimes clipped (I tried it with the rocket from planet cute). If you’re just using it for full screen output, you wouldn’t notice it. I guess it’s because the blur actually makes the image larger. Maybe making the output image a bit larger than the input would fix this.

@yojimbo2000 the choices seem to be a) return an image of different dimensions than the one passed in, or b) return an image with the same dimensions but with the content shrunk to fit them.

If the drawn size is set dynamically, it comes to the same thing. If not, my question is how (and should) the user get notified that their image has been altered? Using this function without being aware of that could lead to all sorts of hair-pulling during debugging, so I think they definitely should be made aware of it somehow. Is a comment in the code enough? I’d prefer a notification that’s unavoidable, so that the user is made aware of it just by using the code. Maybe in the function name itself?