clipping

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
Amatereasu
Prole
Posts: 9
Joined: Wed May 10, 2023 6:30 am

clipping

Post by Amatereasu »

hello! i have a UI element here split into 3 parts, a background, the liquid edge, and the border, and i want to mask the red part so that it doesnt go over the edge, and stays confined to the actual image
2024-10-11_08-38.png
2024-10-11_08-38.png (38.08 KiB) Viewed 8234 times
i tried a few methods for this with some help, but none of them worked
my code currently looks like this

Code: Select all

  drawHealth = function(self, health, delta)
    local healthregion = health/100*327

    self.health.liquid.position = (self.health.liquid.position + (75 * delta)) % 64
    love.graphics.setCanvas({self.health.bg.canvas, stencil = true})
      love.graphics.clear(0,0,0,0)
      love.graphics.stencil(function()
          love.graphics.setColor(1,1,1,1)
          love.graphics.draw(self.health.bg.texture, 0,0)
      end, "replace", 1)
      love.graphics.setStencilTest("greater", 0)
      love.graphics.setColor(1,0,0,1)
      love.graphics.rectangle("fill", 0, 0, 64, healthregion)
      love.graphics.setColor(1,1,1,1)
      love.graphics.draw(self.health.liquid.texture, -80 + self.health.liquid.position, healthregion - 15)
      love.graphics.setStencilTest()
    love.graphics.setCanvas()
    love.graphics.draw(self.health.bg.canvas, 34, 34)

    love.graphics.draw(self.health.gold.texture, 4,32)
  end,
im trying to use a stencil for this but it literally doesnt work lol
i also tried to use a shader, but im horrible at shaders and so i didnt really understand what i was doing or what to do
can anyone help?
User avatar
steVeRoll
Party member
Posts: 140
Joined: Sun Feb 14, 2016 1:13 pm

Re: clipping

Post by steVeRoll »

Stencils, by default, consider the entire area of an image as part of the stencil - they don't cut out the transparent parts automatically.

For that you'll need a basic shader. The wiki page for love.graphics.stencil has an example under "Using an Image as a stencil mask".
User avatar
Amatereasu
Prole
Posts: 9
Joined: Wed May 10, 2023 6:30 am

Re: clipping

Post by Amatereasu »

steVeRoll wrote: Fri Oct 11, 2024 5:41 pm Stencils, by default, consider the entire area of an image as part of the stencil - they don't cut out the transparent parts automatically.

For that you'll need a basic shader. The wiki page for love.graphics.stencil has an example under "Using an Image as a stencil mask".
hi steve c:
it worked! thank you!

Code: Select all

      love.graphics.stencil(function()
        love.graphics.setShader(shader.mask)
        love.graphics.setColor(1,1,1,1)
        love.graphics.draw(self.health.bg.texture, 0,0)
        love.graphics.setShader()
      end, "replace", 1)

Code: Select all

vec4 effect(vec4 color, Image texture, vec2 texturecoords, vec2 screencoords)
{
  if (Texel(texture, texturecoords).a == 0.0)
  {
    discard;
  }
  return Texel(texture, texturecoords) * color;
}
RNavega
Party member
Posts: 385
Joined: Sun Aug 16, 2020 1:28 pm

Re: clipping

Post by RNavega »

It's definitely not simpler or less code than using a stencil, but out of interest I tried doing it with another image as a mask, in a shader.

imageMaskPreview.gif
imageMaskPreview.gif (166.9 KiB) Viewed 7531 times

So this doesn't use a stencil. It loads another image as a uniform in the shader then samples it with a transformed UV coordinate that places that mask image where the user wants it on screen, and then you can use the sampled color(s) in whatever way you want, like as this additional alpha channel. I think this effect is called "matte" or "alpha over" in editing software.

main.lua below, but it needs the two images that are inside the attached .love:

Code: Select all

-- Example of using a secondary image as a transparency mask.
-- AKA the Alpha Over / Matte effect.
-- RN 2024.

io.stdout:setvbuf('no')


local smileImage
local smileDimensions

local maskImage
local maskDimensions
local maskUvTransform

local shader


-- See the default LÖVE pixel shader code in here:
-- https://love2d.org/wiki/love.graphics.newShader#Pixel_Shader_Function
local SHADER_SOURCE = [[
uniform mat4 mask_uv_tf;
uniform Image mask_image;

vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords)
{
    // Transform the original image UV coordinates in order to "place" the mask
    // where it needs to go.
    // Make it into a 'vec4' so it can be multiplied by the 4x4 matrix.
    vec4 temp_coords = vec4(texture_coords, 0.0, 1.0);
    vec2 mask_coords = (mask_uv_tf * temp_coords).xy;

    // Get the mask value (depends on the image format, could be the red
    // channel or the alpha etc).
    float mask_value = Texel(mask_image, mask_coords).a;

    // Sample the image being drawn.
    vec4 texture_color = Texel(tex, texture_coords);
    texture_color.a = texture_color.a * (1.0 - mask_value);

    // Output.
    return texture_color * color;
}
]]


function love.load()
    love.window.setMode(128, 256)
    love.graphics.setBackgroundColor(0.2, 0.4, 0.7)

    smileImage = love.graphics.newImage('smile.png')
    smileDimensions = {smileImage:getDimensions()}

    maskImage = love.graphics.newImage('mask.png')
    -- Clip the image outside its boundary (https://love2d.org/wiki/Texture:setWrap).
    maskImage:setWrap('clampzero', 'clampzero')
    maskDimensions = {maskImage:getDimensions()}
    maskUvTransform = love.math.newTransform()

    shader = love.graphics.newShader(SHADER_SOURCE)
    -- Only need to send this once as it won't change during the program.
    shader:send('mask_image', maskImage)
end


function love.draw()
    -- Draw some background stuff.
    love.graphics.setColor(0.1, 0.3, 0.4)
    love.graphics.circle('fill', 0, 30, 100)
    love.graphics.circle('fill', 128, 226, 100)
    love.graphics.setColor(1.0, 1.0, 1.0)
    -- Draw the shader-masked image.
    local mx, my = love.mouse.getPosition()
    love.graphics.setShader(shader)
    -- This mask UV transform changes per frame, so it always need to be sent.
    shader:send('mask_uv_tf', maskUvTransform)
    love.graphics.draw(smileImage,
                       mx - smileDimensions[1] / 2.0,
                       my - smileDimensions[2] / 2.0)
    -- Disable the active shader at the end of the draw function to avoid surprises later.
    love.graphics.setShader()
end


function updateMaskTransform(mx, my)
    -- The desired pixel coordinates of the mask on screen.
    local maskDrawX = 0
    local maskDrawY = 0
    -- The pixel coordinates of the image on screen (same as used in love.draw()).
    local smileDrawX = mx - smileDimensions[1] / 2.0
    local smileDrawY = my - smileDimensions[2] / 2.0
    -- How big the drawn image is in relation to the mask image being used.
    local ratioW = smileDimensions[1] / maskDimensions[1]
    local ratioH = smileDimensions[2] / maskDimensions[2]
    -- Make a transform that translates the difference in position of the images,
    -- and scales by the size ratio between the drawn image and its mask.
    maskUvTransform:setTransformation((smileDrawX - maskDrawX) / maskDimensions[1],
                                      (smileDrawY - maskDrawY) / maskDimensions[2],
                                      0,
                                      ratioW,
                                      ratioH)
end


function love.mousemoved(mx, my)
    -- Could also be done from within love.update().
    updateMaskTransform(mx, my)
end


function love.keypressed(key)
    if key == 'escape' then
        love.event.quit()
    end
end
Attachments
AlphaOverDemo.love
(9.23 KiB) Downloaded 58 times
Post Reply

Who is online

Users browsing this forum: No registered users and 8 guests