Anyone have a shader to put an outline around an image?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
Jasoco
Inner party member
Posts: 3622
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Anyone have a shader to put an outline around an image?

Post by Jasoco » Mon May 28, 2018 11:23 pm

I’m looking for a shaded that can put an outline around the pixels in an image preferably with options to set the color and thickness if possible. Has anyone made one? I know they can be useful.

User avatar
pgimeno
Party member
Posts: 1340
Joined: Sun Oct 18, 2015 2:58 pm

Re: Anyone have a shader to put an outline around an image?

Post by pgimeno » Tue May 29, 2018 12:41 am

Thrust II Reloaded - GifLoad for Löve - GSpöt GUI - My NotABug.org repositories - portland (mobile orientation)
The MS-Github repositories I had have been closed after the acquisition announcement and will be removed in the near future.

var77
Prole
Posts: 25
Joined: Wed May 02, 2018 1:57 pm

Re: Anyone have a shader to put an outline around an image?

Post by var77 » Tue May 29, 2018 3:56 pm


User avatar
Jasoco
Inner party member
Posts: 3622
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: Anyone have a shader to put an outline around an image?

Post by Jasoco » Wed May 30, 2018 1:48 am

Neither of those are working right at all.

The qwooke one is completely broken. Or I'm not understanding how to send the information to it.

And the blog one doesn't draw the way it should. It seems to draw the outline inside the image edge instead of around the outside of the image edge which is what I need.

grump
Party member
Posts: 430
Joined: Sat Jul 22, 2017 7:43 pm

Re: Anyone have a shader to put an outline around an image?

Post by grump » Wed May 30, 2018 6:23 am

The best way to do high-quality outlines is by applying a distance transform to the image, then use the resulting signed distance field to render the outline. It's an expensive operation that is not easy to do on a GPU. It's not suitable for realtime generated images and takes a lot of memory, because you need to precalculate the distance fields for each image.

Here's another less expensive technique I used before to put outlines around fonts. It samples the image multiple times and uses two passes to create an outlined image; one pass to render the outline, the second pass renders the image on top of it. It's still expensive and I haven't used this for realtime stuff, only to pre-bake outlines into textures. It may or may not be sufficiently fast for your application.

You can set varying thickness (size), color and smoothness of the outline. Lower smoothness gives crisp outlines, higher smoothness creates a glow-like effect.

Code: Select all

local gfx = love.graphics

local shader = gfx.newShader([[
	extern vec2 pixelsize;
	extern float size = 1;
	extern float smoothness = 1;

	vec4 effect(vec4 color, Image texture, vec2 uv, vec2 fc) {
		float a = 0;
		for(float y = -size; y <= size; ++y) {
			for(float x = -size; x <= size; ++x) {
				a += Texel(texture, uv + vec2(x * pixelsize.x, y * pixelsize.y)).a;
			}
		}
		a = color.a * min(1, a / (2 * size * smoothness + 1));

		return vec4(color.rgb, a);
	}
]])

-- the image
local canvas = gfx.newCanvas(128, 128)
canvas:renderTo(function()
	gfx.setColor(1, 0, 0)
	gfx.setLineWidth(20)
	gfx.circle('line', 64, 64, 32)
end)

function love.draw()
	gfx.setBlendMode('alpha')

	-- pass 1: render outline
	gfx.setColor(1, 1, 1) -- outline color
	shader:send('pixelsize', { 1 / canvas:getWidth(), 1 / canvas:getHeight() })
	shader:send('size', 4)
	gfx.setShader(shader)
	gfx.draw(canvas)

	-- pass 2: render image
	gfx.setBlendMode('alpha', 'premultiplied')
	gfx.setShader()
	gfx.setColor(1, 1, 1)
	gfx.draw(canvas)
end
Image

Edit: formatting
Edit2: there was a bug in the alpha calculation
Last edited by grump on Wed May 30, 2018 7:31 am, edited 3 times in total.

KayleMaster
Party member
Posts: 190
Joined: Mon Aug 29, 2016 8:51 am

Re: Anyone have a shader to put an outline around an image?

Post by KayleMaster » Wed May 30, 2018 6:40 am

A very dirty trick is to draw two copies of the sprite behind it, one smaller for the inner glow and one larger for the outer glow.
Just do setColor to whatever you want before hand.The thickness is basically the scale.
I don't know if this will work in all scenarios tho.

Either way any of the other solutions posted here will be much better. (like grump's)

User avatar
pgimeno
Party member
Posts: 1340
Joined: Sun Oct 18, 2015 2:58 pm

Re: Anyone have a shader to put an outline around an image?

Post by pgimeno » Wed May 30, 2018 7:47 am

Jasoco wrote:
Wed May 30, 2018 1:48 am
And the blog one doesn't draw the way it should. It seems to draw the outline inside the image edge instead of around the outside of the image edge which is what I need.
Simply inverting the sign works for me:

outline.glsl

Code: Select all

vec4 resultCol;
extern vec2 stepSize;

vec4 effect( vec4 col, Image texture, vec2 texturePos, vec2 screenPos )
{
	// get color of pixels:
	number alpha = -4.0*texture2D( texture, texturePos ).a;
	alpha += texture2D( texture, texturePos + vec2( stepSize.x, 0.0f ) ).a;
	alpha += texture2D( texture, texturePos + vec2( -stepSize.x, 0.0f ) ).a;
	alpha += texture2D( texture, texturePos + vec2( 0.0f, stepSize.y ) ).a;
	alpha += texture2D( texture, texturePos + vec2( 0.0f, -stepSize.y ) ).a;

	// calculate resulting color
	resultCol = vec4( 0.4f, 1.0f, 0.1f, alpha );
	// return color for current pixel
	return resultCol;
}
You can see the outline of the next quad, though, so that's something to watch out for.
Thrust II Reloaded - GifLoad for Löve - GSpöt GUI - My NotABug.org repositories - portland (mobile orientation)
The MS-Github repositories I had have been closed after the acquisition announcement and will be removed in the near future.

bobbyjones
Party member
Posts: 693
Joined: Sat Apr 26, 2014 7:46 pm

Re: Anyone have a shader to put an outline around an image?

Post by bobbyjones » Wed May 30, 2018 7:51 am

grump wrote:
Wed May 30, 2018 6:23 am
The best way to do high-quality outlines is by applying a distance transform to the image, then use the resulting signed distance field to render the outline. It's an expensive operation that is not easy to do on a GPU. It's not suitable for realtime generated images and takes a lot of memory, because you need to precalculate the distance fields for each image.

Here's another less expensive technique I used before to put outlines around fonts. It samples the image multiple times and uses two passes to create an outlined image; one pass to render the outline, the second pass renders the image on top of it. It's still expensive and I haven't used this for realtime stuff, only to pre-bake outlines into textures. It may or may not be sufficiently fast for your application.

You can set varying thickness (size), color and smoothness of the outline. Lower smoothness gives crisp outlines, higher smoothness creates a glow-like effect.

Code: Select all

local gfx = love.graphics

local shader = gfx.newShader([[
	extern vec2 pixelsize;
	extern float size = 1;
	extern float smoothness = 1;

	vec4 effect(vec4 color, Image texture, vec2 uv, vec2 fc) {
		float a = 0;
		for(float y = -size; y <= size; ++y) {
			for(float x = -size; x <= size; ++x) {
				a += Texel(texture, uv + vec2(x * pixelsize.x, y * pixelsize.y)).a;
			}
		}
		a = color.a * min(1, a / (2 * size * smoothness + 1));

		return vec4(color.rgb, a);
	}
]])

-- the image
local canvas = gfx.newCanvas(128, 128)
canvas:renderTo(function()
	gfx.setColor(1, 0, 0)
	gfx.setLineWidth(20)
	gfx.circle('line', 64, 64, 32)
end)

function love.draw()
	gfx.setBlendMode('alpha')

	-- pass 1: render outline
	gfx.setColor(1, 1, 1) -- outline color
	shader:send('pixelsize', { 1 / canvas:getWidth(), 1 / canvas:getHeight() })
	shader:send('size', 4)
	gfx.setShader(shader)
	gfx.draw(canvas)

	-- pass 2: render image
	gfx.setBlendMode('alpha', 'premultiplied')
	gfx.setShader()
	gfx.setColor(1, 1, 1)
	gfx.draw(canvas)
end
Image

Edit: formatting
Edit2: there was a bug in the alpha calculation
When I saw this post the first thing I did was ask the people on IRC if SDF will be faster than the way already described here lol. The only reason I did not recommend it is because no one has created a way to generate a SDF in love yet. So I was going to write one first before submitting my answer.

grump
Party member
Posts: 430
Joined: Sat Jul 22, 2017 7:43 pm

Re: Anyone have a shader to put an outline around an image?

Post by grump » Wed May 30, 2018 8:16 am

bobbyjones wrote:
Wed May 30, 2018 7:51 am
no one has created a way to generate a SDF in love yet
I implemented this for FÖNTGen, but I it doesn't work with LÖVE 11 yet. It's a pure Lua implementation that runs on a single core and while it's fast enough for the things I use it for, it's too slow to be used in realtime on a large scale.

User avatar
Jasoco
Inner party member
Posts: 3622
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: Anyone have a shader to put an outline around an image?

Post by Jasoco » Wed May 30, 2018 7:00 pm

pgimeno wrote:
Wed May 30, 2018 7:47 am
Jasoco wrote:
Wed May 30, 2018 1:48 am
And the blog one doesn't draw the way it should. It seems to draw the outline inside the image edge instead of around the outside of the image edge which is what I need.
Simply inverting the sign works for me:

outline.glsl

Code: Select all

vec4 resultCol;
extern vec2 stepSize;

vec4 effect( vec4 col, Image texture, vec2 texturePos, vec2 screenPos )
{
	// get color of pixels:
	number alpha = -4.0*texture2D( texture, texturePos ).a;
	alpha += texture2D( texture, texturePos + vec2( stepSize.x, 0.0f ) ).a;
	alpha += texture2D( texture, texturePos + vec2( -stepSize.x, 0.0f ) ).a;
	alpha += texture2D( texture, texturePos + vec2( 0.0f, stepSize.y ) ).a;
	alpha += texture2D( texture, texturePos + vec2( 0.0f, -stepSize.y ) ).a;

	// calculate resulting color
	resultCol = vec4( 0.4f, 1.0f, 0.1f, alpha );
	// return color for current pixel
	return resultCol;
}
You can see the outline of the next quad, though, so that's something to watch out for.
See that's a problem I'd not want to deal with. Also using this shader doesn't work right either. I'm sending a table with two values to it and no matter what I send it doesn't render right.

bobbyjones wrote:
Wed May 30, 2018 7:51 am
grump wrote:
Wed May 30, 2018 6:23 am
The best way to do high-quality outlines is by applying a distance transform to the image, then use the resulting signed distance field to render the outline. It's an expensive operation that is not easy to do on a GPU. It's not suitable for realtime generated images and takes a lot of memory, because you need to precalculate the distance fields for each image.

Here's another less expensive technique I used before to put outlines around fonts. It samples the image multiple times and uses two passes to create an outlined image; one pass to render the outline, the second pass renders the image on top of it. It's still expensive and I haven't used this for realtime stuff, only to pre-bake outlines into textures. It may or may not be sufficiently fast for your application.

You can set varying thickness (size), color and smoothness of the outline. Lower smoothness gives crisp outlines, higher smoothness creates a glow-like effect.

Code: Select all

local gfx = love.graphics

local shader = gfx.newShader([[
	extern vec2 pixelsize;
	extern float size = 1;
	extern float smoothness = 1;

	vec4 effect(vec4 color, Image texture, vec2 uv, vec2 fc) {
		float a = 0;
		for(float y = -size; y <= size; ++y) {
			for(float x = -size; x <= size; ++x) {
				a += Texel(texture, uv + vec2(x * pixelsize.x, y * pixelsize.y)).a;
			}
		}
		a = color.a * min(1, a / (2 * size * smoothness + 1));

		return vec4(color.rgb, a);
	}
]])

-- the image
local canvas = gfx.newCanvas(128, 128)
canvas:renderTo(function()
	gfx.setColor(1, 0, 0)
	gfx.setLineWidth(20)
	gfx.circle('line', 64, 64, 32)
end)

function love.draw()
	gfx.setBlendMode('alpha')

	-- pass 1: render outline
	gfx.setColor(1, 1, 1) -- outline color
	shader:send('pixelsize', { 1 / canvas:getWidth(), 1 / canvas:getHeight() })
	shader:send('size', 4)
	gfx.setShader(shader)
	gfx.draw(canvas)

	-- pass 2: render image
	gfx.setBlendMode('alpha', 'premultiplied')
	gfx.setShader()
	gfx.setColor(1, 1, 1)
	gfx.draw(canvas)
end
Image

Edit: formatting
Edit2: there was a bug in the alpha calculation
When I saw this post the first thing I did was ask the people on IRC if SDF will be faster than the way already described here lol. The only reason I did not recommend it is because no one has created a way to generate a SDF in love yet. So I was going to write one first before submitting my answer.
This one works perfectly though, but it also seems to require padding on the image to work right which would require a lot more effort on my part to redo all my image files. I hadn't planned on doing that. But it's a start. Even if I have to re-render all my images onto canvases with the padding automatically.

Post Reply

Who is online

Users browsing this forum: No registered users and 12 guests