Question: Efficiently clearing images

Hello,
In my program, I’m using setContext to draw into an image, but here’s the problem: When I do that without setting the image variable to a blank image, it retains the previous image. Here’s the other problem: I need to keep the background alpha, so I can’t just draw an opaque rectangle behind it.
This image I’m using is the size of the screen, so when I set it to a blank image (“image(WIDTH,HEIGHT)”), it slows down the performance by about 3 times. I’ve tried to make a template blank image and set it to the image I want to clear every frame, but that doesn’t do anything.
Help would be great.
Thanks!

Maybe a shader will help. What exactly are you doing with the image and the alpha - it isn’t clear to me.

@Ignattz - Here’s my code I made so far:

displayMode(FULLSCREEN)

function setup()
    parameter.watch("1/DeltaTime")
    fixtures = {}
    local gSize = vec2(WIDTH,HEIGHT/2)
    local gScale = 5
    local funnleSize = 5
    funnle1 = physics.body(CHAIN,false,vec2(-gSize.x,gSize.y),
                                vec2(-funnleSize,-gSize.y/3),
                                vec2(-funnleSize,-gSize.y)
                                )
    local funnle2 = physics.body(CHAIN,false,vec2(gSize.x,gSize.y),
                                vec2(funnleSize,-gSize.y/3),
                                vec2(funnleSize,-gSize.y)
                                )
    local split = physics.body(CHAIN,false,vec2(-WIDTH/2,-HEIGHT/8),
                                vec2(0,HEIGHT/80),
                                vec2(WIDTH/2,-HEIGHT/8)
                                )
    funnle1.x = WIDTH / 2
    funnle1.y = HEIGHT - HEIGHT / 4
    funnle1.type = STATIC
    funnle2.x = WIDTH / 2
    funnle2.y = HEIGHT - HEIGHT / 4
    funnle2.type = STATIC
    split.x = WIDTH / 2
    split.y = HEIGHT/8
    split.type = STATIC
    split.restitution = .3
    table.insert(fixtures,funnle1)
    table.insert(fixtures,funnle2)
    table.insert(fixtures,split)

    blankImage = image(WIDTH,HEIGHT)
    blur = 30
    water = {}
    waterimage = softImage(color(0, 155, 255),5,blur)
    allWater = blankImage

    --[[waterMesh = mesh()
    waterMesh.texture = waterimage]]--
    
    mDisplay = mesh()
    mDisplay:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    mDisplay.shader = shader("Documents:Alpha Threshold")
    mDisplay.shader.smoothness = 0
    mDisplay.shader.threshold = .1
    mDisplay.shader.unpremultiply = 1
end


function draw()
    background(0, 0, 0, 255)
    physics.gravity(Gravity/10)
    smooth()

    allWater = image(WIDTH,HEIGHT)
    print(allWater.data)
    setContext(allWater)
    noStroke()         
    smooth()
    for i,v in ipairs(water) do
        sprite(waterimage,v.body.x,v.body.y,blur,blur)
        --waterMesh:addRect(v.body.x,v.body.y,blur*extraSpace,blur*extraSpace)
        if v.body.x <= 0 or v.body.x >= WIDTH or v.body.y <= 0 or v.body.y >= HEIGHT then
            v.body:destroy()
            table.remove(water,i)
        end
    end

    --waterMesh:draw()
    --waterMesh:clear()
    setContext()
    
    mDisplay.texture = allWater
    mDisplay:draw()

    strokeWidth(3)
    for i,v in ipairs(fixtures) do
        pushMatrix()
        translate(v.x,v.y)
        local points = v.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
        popMatrix()
    end
    popMatrix()
end

function touched(t)
    addWater(10,t.x,t.y)
end

function softImage(f,r,s)
    simg = image(r,r)
    setContext(simg)
    pushStyle()
    noStroke()
    fill(f)
    ellipse(simg.width/2,simg.height/2,simg.width)
    popStyle()
    setContext()
    limg = image(s,s)
    setContext(simg)
    pushStyle()
    smooth()
    sprite(simg,0,0,s,s)
    popStyle()
    setContext()
    return simg
end

function addWater(amnt,x,y)
    local posRand = 20
    local vRand = 50
    for i = 1,amnt do
        local b = physics.body(CIRCLE,2)
        b.x = x + math.random(-posRand,posRand)
        b.y = y + math.random(-posRand,posRand)
        b.linearVelocity = vec2(math.random(-vRand,vRand),math.random(-vRand,vRand))
        b.restitution = .1
        table.insert(water,{body = b})
    end
end

```

Thanks!

I couldnt find a way around this hurdle, i tried to set my image to a screen size image of 0 alpha pixels every way i could imagine but they didnt work, i just stuck with image(WIDTH,HEIGHT)

@Luatee - how about this

Forget about drawing a fullsize image on each draw, that will always be slow. Instead, add your water drops to a mesh (with the shader attached, of course), and on each draw, just update their x,y position, add and remove them, etc. Then just draw the mesh.

This avoids drawing the image twice (once to a hidden image and then to the screen).

PS you forgot to include the shader code, but I found it in another post because I recognised the code.

@Ignatz - The issue is that if I add the droplets to a mesh as rectangles, then the shader will only apply to each drop individually. Then you won’t get the water effect. If I could access the data aspect of the mesh, that’d be great, but I can’t. It returns nil.

@Zoyt - ok, then how about this

Instead of drawing a full screen image, you draw it exactly the size you need, by first looping through the drops and calculating the min and max x and y values. Then you draw to that size image. Below is my draw routine that does this. The drops don’t stay in the funnel for some reason, but it does seem faster

function draw()
    background(0, 0, 0, 255)
    physics.gravity(Gravity/10)
    smooth()
    --get min and max values
    local minX,minY,maxX,maxY=9999,9999,0,0
    for i,v in ipairs(water) do
        if v.body.x>maxX then maxX=v.body.x end
        if v.body.x<minX then minX=v.body.x end
        if v.body.y>maxY then maxY=v.body.y end
        if v.body.y<minY then minY=v.body.y end
    end
    if minX>0 then
        --min and max values are at centre of drops, allow space for width of drop
        minX,minY=minX-blur/2,minY-blur/2
        maxX,maxY=maxX+blur/2,maxY+blur/2
        --draw exact size of image required
        local w,h=maxX-minX,maxY-minY
        allWater = image(w,h)
        setContext(allWater)
        noStroke()         
        smooth()
        for i,v in ipairs(water) do
            --waterMesh:addRect(v.body.x,v.body.y,blur*extraSpace,blur*extraSpace)
            if v.body.x <= 0 or v.body.x >= WIDTH or v.body.y <= 0 or v.body.y >= HEIGHT then
                v.body:destroy()
                table.remove(water,i)
            else
               --adjust position by minX, minY
                sprite(waterimage,v.body.x-minX,v.body.y-minY,blur,blur)
            end
        end
        setContext()
        --adjust rectangle to minX, minY
        mDisplay:setRect(1,minX-w/2,minY-h/2,w,h)
        mDisplay.texture = allWater
        mDisplay:draw()
    end
 
    strokeWidth(3)
    for i,v in ipairs(fixtures) do
        pushMatrix()
        translate(v.x,v.y)
        local points = v.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
        popMatrix()
    end
    popMatrix()
end

@Ignatz, that works well except for one thing. Change the following:

mDisplay:setRect(1,minX-w/2,minY-h/2,w,h)

To

mDisplay:setRect(1,minX+w/2,minY+h/2,w,h)

Now the water stays where it should.

Edit: something else is a bit funky too. When the first water drop goes off screen, it clears the whole image. Math.min and math.max functions will fix this, also removing the if statement with minx > 0.

function draw()
    background(0, 0, 0, 255)
    physics.gravity(Gravity/10)
    smooth()
    --get min and max values
    local minX,minY,maxX,maxY=9999,9999,0,0
    for i,v in ipairs(water) do
        if v.body.x>maxX then maxX=v.body.x end
        if v.body.x<minX then minX=v.body.x end
        if v.body.y>maxY then maxY=v.body.y end
        if v.body.y<minY then minY=v.body.y end
    end
    --min and max values are at centre of drops, allow space for width of drop
    minX,minY=math.max(minX-blur/2,0),math.max(minY-blur/2,0)
    maxX,maxY=math.min(maxX+blur/2,WIDTH),math.min(maxY+blur/2,HEIGHT)
    --draw exact size of image required
    local w,h=maxX-minX,maxY-minY
    allWater = image(w,h)
    setContext(allWater)
    noStroke()
    for i,v in ipairs(water) do
        --waterMesh:addRect(v.body.x,v.body.y,blur*extraSpace,blur*extraSpace)
        if v.body.x <= 0 or v.body.x >= WIDTH or v.body.y <= 0 or v.body.y >= HEIGHT then
            v.body:destroy()
            table.remove(water,i)
        else
            --adjust position by minX, minY
            sprite(waterimage,v.body.x-minX,v.body.y-minY,blur,blur)
        end
    end
    setContext()
    --adjust rectangle to minX, minY
    mDisplay:setRect(1,minX+w/2,minY+h/2,w,h)
    mDisplay.texture = allWater
    mDisplay:draw()

    strokeWidth(3)
    for i,v in ipairs(fixtures) do
        pushMatrix()
        translate(v.x,v.y)
        local points = v.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
        popMatrix()
    end
end

To answer the original question, use background. This forces everything to the specified colour including alpha. So background(0,0,0,0) makes the image transparent black.

Thanks guys. You’ve been super helpful. @Andrew_Stacey’s answer was perfect.
Thanks!

@Andrew_Stacey I tried using background(0,0) with setContext before but it didn’t work, yours seems to work though

Unless you provide all four values, Codea assumes alpha is 255

Aha, that would be it, even though it comes up with grey,alpha it must ignore the alpha

@Luatee sounds like a bug. Thanks for the find.