[Solved] Composite layers?

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.
Post Reply
User avatar
pgimeno
Party member
Posts: 3550
Joined: Sun Oct 18, 2015 2:58 pm

[Solved] Composite layers?

Post by pgimeno »

I'm trying to find information of how to implement a composite layer function. By composite layer function I mean a function whose input is two color/alpha component values and its output is a color/alpha component value. Something very similar to love.graphics.setBlendMode but with my own function (which is not one of these, otherwise I would not be asking of course!)

I figure it can't be done directly (that is, implement a new blend mode for setBlendMode). But I've found this: http://distrustsimplicity.net/articles/ ... g-in-glsl/ which looks like a step in the right direction.

So I imagine I need to use setShader. I'm new to using shaders, but the AWESOME shader basics tutorial in the love2d blog has been enlightening. Is there a way to retrieve the pixels already drawn? Or do I need to paint my lower layer to a canvas and pass the canvas through shader:send()? And in that case, how would I handle the coordinates so that only the area of the canvas corresponding to the drawn rectangle is used for compositing?

EDIT: Got it now. Here's the resulting .love file I've been using for experimenting.
Attachments
composite.love
Composite layers test using shaders (updated, with UI)
(185.24 KiB) Downloaded 82 times
Last edited by pgimeno on Fri Nov 13, 2015 4:00 am, edited 4 times in total.
User avatar
pgimeno
Party member
Posts: 3550
Joined: Sun Oct 18, 2015 2:58 pm

Re: Composite layers?

Post by pgimeno »

So I finally got to implementing it in the only way I figured out. I realized that I could use the screen position parameter for the lower layer coordinates that I needed.

However, this way feels a bit convoluted. Is there a better way than this? Currently it involves four steps:

- draw everything to a canvas rather than to the screen,
- pass the canvas to the shader,
- draw the canvas to the screen,
- draw the overlaid layer using the shader.

Code: Select all

local canvas, shader, img1, img2

function love.load(args)
  shader = love.graphics.newShader[[

extern Image canvas;

// Composite function
vec4 composite(vec4 lower, vec4 upper)
{
    return vec4(
        // "Overlay": lerp between multiply and screen modes, based on lower
        mix(lower.x*upper.x, 1.-(1.-lower.x)*(1.-upper.x), lower.x),
        mix(lower.y*upper.y, 1.-(1.-lower.y)*(1.-upper.y), lower.y),
        mix(lower.z*upper.z, 1.-(1.-lower.z)*(1.-upper.z), lower.z),
        upper.w
    );
}

vec4 effect(vec4 clr, Image img, vec2 imgpos, vec2 scrpos)
{
    vec2 cpos;
    cpos.x = scrpos.x/love_ScreenSize.x;
    cpos.y = 1-scrpos.y/love_ScreenSize.y;

    // Composite mode.
    return composite(Texel(canvas, cpos), Texel(img, imgpos)*clr);
}

]]

  image1 = love.graphics.newImage("image1.jpg")
  image2 = love.graphics.newImage("image2.jpg")

  canvas = love.graphics.newCanvas(800, 600)
end

local imgx, imgy

function love.update(dt)
  -- update imgx, imgy
end


function love.draw()
  -- Draw the bottom layer to the canvas. This could be more complicated than
  -- just drawing a static image; that's just an example.
  canvas:clear()
  love.graphics.setCanvas(canvas)
  love.graphics.draw(image1, 0, 0)
  love.graphics.setCanvas()

  -- Send the canvas (bottom layer) to the shader
  shader:send("canvas", canvas)

  -- Actually draw the canvas
  love.graphics.draw(canvas, 0, 0)

  -- Draw the top layer with shader
  love.graphics.setShader(shader)
  love.graphics.draw(image2, imgx, imgy)
  love.graphics.setShader()
end
I wonder if that can be made simpler. Skipping the 'draw to a canvas' part would be ideal. Am I stuck with doing it this way?

.love file:
(edit: uploaded to the OP now)
Last edited by pgimeno on Fri Nov 13, 2015 3:35 am, edited 2 times in total.
User avatar
Evine
Citizen
Posts: 72
Joined: Wed May 28, 2014 11:46 pm

Re: Composite layers?

Post by Evine »

I've done something similar to this myself. And it seems completely impossible to access what you are drawing to. (I have no idea why. Could anyone explain why this limitation exist?)

So I did the same as you. Drawing to a canvas and sending the canvas to the shader then drawing a blendmode image. What I've done different is that I draw the image to back on the canvas. I don't know if that is a good idea.

So I've implemented: Screen, multiply, and overlay. All with proper opacity alpha blend (love2d blend mode screen and multiply does not correctly alpha blend)

Artal, A .PSD loader: https://github.com/EvineDev/Artal
User avatar
pgimeno
Party member
Posts: 3550
Joined: Sun Oct 18, 2015 2:58 pm

Re: Composite layers?

Post by pgimeno »

Thanks for the video and for the reply.
Evine wrote:So I did the same as you. Drawing to a canvas and sending the canvas to the shader then drawing a blendmode image. What I've done different is that I draw the image to back on the canvas. I don't know if that is a good idea.
That was the next thing I was going to try. I feel uneasy about modifying the same canvas I am reading from, but I guess it must be prepared to work.

It's also more powerful, in that you can stack as many layers as you want. With the method I outlined, I would not be able to add another layer on top of the last. That was why I wanted to try writing back to the canvas. I wonder if a double-buffer scheme would help. I remember having seen a recommendation about using a double buffer in the OpenGL docs, but I can't remember enough details to know if it applies to this case.

Lots of formulas to try now! http://www.w3.org/TR/compositing-1/#blending
User avatar
Evine
Citizen
Posts: 72
Joined: Wed May 28, 2014 11:46 pm

Re: [Solved] Composite layers?

Post by Evine »

I think your formula to overlay blending might be wrong. I've attached my code so you could take a look at that. :awesome: (I did some pretty stupid things in there though. Like placing where the image is drawn inside the shader. But the formulas for blending is correct.)

Code: Select all

--[[
The MIT License (MIT)

Copyright (c) 2015 Daniel Rasmussen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]]

blendMode = {}
blendMode.common = [[
// This file is concatenated in front of other shaderfiles to give them access to generic functions.

vec4 loadImage(Image loadImage , vec2 texture_coords , vec2 pos, vec2 size )
{
	return Texel(loadImage, vec2(
		texture_coords.x * love_ScreenSize.x/size.x - pos.x/size.x ,
		texture_coords.y * love_ScreenSize.y/size.y - pos.y/size.y));
}

vec4 alphaBlend(vec4 SOURCE_C , vec4 DRAW_C , vec4 result)
{
	result = (SOURCE_C*(1-DRAW_C.a)+result*DRAW_C.a) ; // alpha blend
	result.a = (SOURCE_C.a*(1-DRAW_C.a)+DRAW_C.a) ;
	return result;
}


//Avoid drawing outside the DRAW_C texture
vec4 clipOutline(vec2 texture_coords, vec2 pos, vec2 size, vec4 SOURCE_C , vec4 result)
{
	if( texture_coords.x * love_ScreenSize.x < pos.x ||
	texture_coords.y * love_ScreenSize.y < pos.y ||
	texture_coords.x * love_ScreenSize.x > pos.x + size.x ||
	texture_coords.y * love_ScreenSize.y > pos.y + size.y )
	{
		return SOURCE_C;
	}
	else
	{
		return result;
	}
	
} 
]]
blendMode.overlay = love.graphics.newShader(blendMode.common..[[
extern Image drawingImage; //This is image being added
extern vec2 pos; //Position on screen
extern vec2 size; //Size of image Being added.
vec4 result ; //result

vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords )
{
	vec4 DRAW_C = loadImage( drawingImage ,  texture_coords ,  pos,  size );
	vec4 SOURCE_C = Texel(texture, texture_coords );

	for ( int i = 0; i < 3; i++ )
	{
		result[i] = (SOURCE_C[i] < 0.5) ?
			(2*SOURCE_C[i] * DRAW_C[i]) : //Multi blend
			(1-2*(1-SOURCE_C[i]) * (1-DRAW_C[i])) ; //Screen blend
	}
		
	//result = (SOURCE_C*(1-DRAW_C.a)+result); // premultiplied

	result = alphaBlend(SOURCE_C , DRAW_C , result);
	result = clipOutline(texture_coords, pos, size, SOURCE_C, result);


	return result;
}
]])

blendMode.multiply = love.graphics.newShader(blendMode.common..[[
extern Image drawingImage; //This is image being added
extern vec2 pos; //Position on screen
extern vec2 size; //Size of image.
vec4 result ; //result

vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords )
{

	vec4 DRAW_C = loadImage( drawingImage ,  texture_coords ,  pos,  size );
	vec4 SOURCE_C = Texel(texture, texture_coords );

	result = (SOURCE_C * DRAW_C) ; //Multi blend

	//result = (SOURCE_C*(1-DRAW_C.a)+result); // premultiplied

	result = alphaBlend(SOURCE_C , DRAW_C , result);
	result = clipOutline(texture_coords, pos, size, SOURCE_C, result);

	return result;
}

]])

blendMode.screen = love.graphics.newShader(blendMode.common..[[

extern Image drawingImage; //This is image being added
extern vec2 pos; //Position on screen
extern vec2 size; //Size of image.
vec4 result ; //result

vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords )
{

	vec4 DRAW_C = loadImage( drawingImage ,  texture_coords ,  pos,  size );
	vec4 SOURCE_C = Texel(texture, texture_coords );

	result = (1-(1-SOURCE_C) * (1-DRAW_C)) ; //Screen blend

	//result = (SOURCE_C*(1-DRAW_C.a)+result); // premultiplied

	result = alphaBlend(SOURCE_C , DRAW_C , result);
	result = clipOutline(texture_coords, pos, size, SOURCE_C, result);

	return result;
}
]])





blendMode.canvas = love.graphics.newCanvas(1920,1080)

local bufferImage = bufferImage or love.graphics.newImage("beautifulbox.png")
local blendInImage = blendInImage or love.graphics.newImage("beautifulbox2.png")

function drawBlend(image,mode,x,y)
	x = x or 0
	y = y or 0
	if mode == "overlay" or mode == "multiply" or mode == "screen" then
		love.graphics.setShader(blendMode[mode])
		blendMode[mode]:send("drawingImage",image)
		blendMode[mode]:send("pos",{x,y})
		blendMode[mode]:send("size",{image:getWidth(),image:getHeight()})
	else
		assert(false, "Invalid blend mode, \""..tostring(mode).."\"\nValid blend modes == \"screen\" , \"multiply\" , \"overlay\"")
	end
	love.graphics.draw(blendMode.canvas)
	love.graphics.setShader()
end


function love.draw()
	love.graphics.setCanvas(blendMode.canvas)
	blendMode.canvas:clear()
	love.graphics.setColor(255,255,255)
	love.graphics.draw(bufferImage,0,0)
	if love.timer.getTime() % 4 < 1 then
		drawBlend(blendInImage,"multiply",0,0)
		love.graphics.print("multiply")
	elseif love.timer.getTime() % 4 < 2 then
		drawBlend(blendInImage,"screen",0,0)
		love.graphics.setColor(0,0,0)
		love.graphics.print("screen")
	elseif love.timer.getTime() % 4 < 3 then
		love.graphics.setColor(0,0,0)
		drawBlend(blendInImage,"overlay",0,0)
		love.graphics.print("overlay")
	else
		love.graphics.draw(blendInImage)
		love.graphics.setColor(255,255,255)
		love.graphics.print("alpha")
	end
	love.graphics.setCanvas()
	love.graphics.setColor(255,255,255)
	love.graphics.draw(blendMode.canvas)
end
Attachments
blendMode.love
(41.5 KiB) Downloaded 80 times
Artal, A .PSD loader: https://github.com/EvineDev/Artal
User avatar
pgimeno
Party member
Posts: 3550
Joined: Sun Oct 18, 2015 2:58 pm

Re: [Solved] Composite layers?

Post by pgimeno »

Don'tcha love feature creep... :oops: ;)
Evine wrote:I think your formula to overlay blending might be wrong.
There are several formulas called "overlay". I used the one GIMP used until 2.8. I think it's been recently changed in GIMP to match SVG's.

I like GIMP's better because it's more continuous and softer. Compare the blend graphs. I love it for colorizing, which is what I may be using this for in future.

(edit: uploaded to the OP now)
Last edited by pgimeno on Fri Nov 13, 2015 3:34 am, edited 1 time in total.
User avatar
Evine
Citizen
Posts: 72
Joined: Wed May 28, 2014 11:46 pm

Re: [Solved] Composite layers?

Post by Evine »

Ah cool. I took a look over your code and it looked really good, though you should send the canvas to the shader immediately after you set the shader. Currently the blendmode is being set a frame late. You can easily see this if you insert a love.timer.sleep(0.5)
Artal, A .PSD loader: https://github.com/EvineDev/Artal
User avatar
pgimeno
Party member
Posts: 3550
Joined: Sun Oct 18, 2015 2:58 pm

Re: [Solved] Composite layers?

Post by pgimeno »

Oh, good catch! I reorganized the order somewhat, trying to implement your suggestion of drawing the top layer to the canvas rather than separately, but got distracted adding features and forgot to finish that part.

And, well, sorry for the mess, I realize it's a lot of ad-hoc code for the controls rather than making functions or modules to deal with them... it was too tiny a project for me to care about that :)

Updated now and uploaded to the OP. Thanks a lot!
Post Reply

Who is online

Users browsing this forum: No registered users and 189 guests