Rounded corners [ANSWERED]

So - I want a rectangle with rounded corners. Before I write my own nasty version with lines and quarter ellipses and such - is there some more elegant way to do it I’m missing? Or has someone perhaps already done this?

Nasty way is the only way, for now. (Or custom sprites.)

Once the image class is available you could compute them in an image. Though it would be nice to have them available as a primitive shape.

FWIW - after futzing around a bit, a rectangle with a circle on either end is close enough to a rounded rectangle for my purposes (ie. it looks good and can be an extended key).

now to figure out a better/faster way to do the math.

I did this by filling a rectangle and then drawing the edges (one by one) with the line end mode set to ROUND.

That’s a pretty good solution.

I really liked Andrew’s idea. So I wrote a function to draw them:

function roundRect(x, y, w, h, r)
    pushStyle()
    insetPos = vec2(x+r,y+r)
    insetSize = vec2(w-2*r,h-2*r)
    
    rectMode(CORNER)
    rect(insetPos.x,insetPos.y,insetSize.x,insetSize.y)
    
    if r > 0 then
        smooth()
        lineCapMode(ROUND)
        strokeWidth(r*2)
        
        line(insetPos.x, insetPos.y, 
             insetPos.x + insetSize.x, insetPos.y)
        line(insetPos.x, insetPos.y,
             insetPos.x, insetPos.y + insetSize.y)
        line(insetPos.x, insetPos.y + insetSize.y,
             insetPos.x + insetSize.x, insetPos.y + insetSize.y)
        line(insetPos.x + insetSize.x, insetPos.y,
             insetPos.x + insetSize.x, insetPos.y + insetSize.y)            
    end
    popStyle()
end

Here’s a sample use. (Parameter to adjust the corner radius.)

-- Main
function setup()
    parameter("CornerRadius",0,50)
end

function draw()
    -- This sets the background color to black
    background(0, 0, 0)

    -- Stroke and fill must be the same
    stroke(255,0,0)
    fill(255,0,0)
    
    roundRect(0, 0, 100, 100, CornerRadius)
end

Cute! It suffers the “balls!” issue a bit, but that’s me being nitpicky - this is, dare I say, elegant.

It does, we hope to resolve the ‘balls’ issue on the line shader itself.

It suffers the what issue a bit?

Hah. BALLS.

\cleans out ears\

Nope, still not any the wiser. Will it become obvious if I run that code?

Thin smooth() lines with ROUND end caps look like they have balls on either end. It’s due to the way the line shader draws lines (the caps are something like half a pixel bigger than the line itself, or using a different antialiasing width, to be determined).

Ah, now I understand. Thanks.

added to wiki https://bitbucket.org/TwoLivesLeft/codea/wiki/UserContrib

In Codea 1.3 you can retrieve the style information from all style functions by calling without arguments. So I’ve updated this code to correctly use only the fill colour, as a normal rect would.

It could even be updated to respect the current rectMode.

    function roundRect(x, y, w, h, r)
        pushStyle()
        insetPos = vec2(x+r,y+r)
        insetSize = vec2(w-2*r,h-2*r)
        
        rectMode(CORNER)
        rect(insetPos.x,insetPos.y,insetSize.x,insetSize.y)
        
        r,g,b,a = fill()
        stroke(r,g,b,a)

        if r > 0 then
            smooth()
            lineCapMode(ROUND)
            strokeWidth(r*2)
            
            line(insetPos.x, insetPos.y, 
                 insetPos.x + insetSize.x, insetPos.y)
            line(insetPos.x, insetPos.y,
                 insetPos.x, insetPos.y + insetSize.y)
            line(insetPos.x, insetPos.y + insetSize.y,
                 insetPos.x + insetSize.x, insetPos.y + insetSize.y)
            line(insetPos.x + insetSize.x, insetPos.y,
                 insetPos.x + insetSize.x, insetPos.y + insetSize.y)            
        end
        popStyle()
    end

Here’s a sample use. (Parameter to adjust the corner radius.)

    -- Main
    function setup()
        parameter("CornerRadius",0,50)
    end
    
    function draw()
        -- This sets the background color to black
        background(0, 0, 0)
    
        -- Set the fill color
        fill(255,0,0)
        
        roundRect(0, 0, 100, 100, CornerRadius)
    end

Here’s my version of it (I had to rewire clip() for this)

_clip = clip
function clip(x, y, w, h)
    if x ~= nil then
        local m = modelMatrix()
        x = x * m[1] + m[13]
        y = y * m[6] + m[14]
        w = w * m[1]
        h = h * m[6]
        _clip(x, y, w, h)
    else
        _clip()
    end
end

function roundRect(x, y, w, h, r)
    r = r or math.min(w, h) / 8
    pushStyle()
        noSmooth()
        ellipseMode(RADIUS)
        if rectMode() == CORNERS then
            w = w - x
            h = h - y
        elseif rectMode() == CENTER then
            x = x - w / 2
            y = y - h / 2
        elseif rectMode() == RADIUS then
            x = x - w
            y = y - h
            w = w * 2
            h = h * 2
        end
        if r > math.min(w, h) / 2 then
            r = math.min(w, h) / 2
        end
        rectMode(CORNER)
        pushMatrix()
            translate(x, y)
            clip(r, 0, w - r * 2, h)
                rect(0, 0, w, h)
            clip(0, r, w, h - r * 2)
                rect(0, 0, w, h)
            r = r + .25
            if strokeWidth() + .25 > r then
                fill(stroke())
                noStroke()
            end
            clip(0, 0, r + 1, r + 1)
                ellipse(r, r, r + 1)
            clip(w - r - 1, 0, r + 2, r + 1)
                ellipse(w - r, r, r + 1)
            clip(0, h - r - 1, r + 1, r + 2)
                ellipse(r, h - r, r + 1)
            clip(w - r - 1, h - r - 1, r + 2, r + 2)
                ellipse(w - r, h - r, r + 1)
            clip()
        popMatrix()
    popStyle()
end

function draw()
    rectMode(CORNER)
    fill(255, 0, 0, 255)
    stroke(255, 255, 255, 255)
    strokeWidth(20)
    roundRect(10, 10, WIDTH / 2 - 20, HEIGHT / 2 - 20, 100)
    fill(0, 255, 0, 255)
    stroke(255, 127, 0, 255)
    strokeWidth(55)
    rectMode(CORNERS)
    roundRect(WIDTH / 2 + 10, 10, WIDTH - 10, HEIGHT / 2 - 10, 50)
    fill(0, 0, 255, 255)
    noStroke()
    rectMode(CENTER)
    roundRect(WIDTH / 4, HEIGHT * .75, WIDTH / 2 - 20, HEIGHT / 2 - 20, 75)
    strokeWidth(10)
    stroke(127, 0, 127, 255)
    noFill()
    rectMode(RADIUS)
    translate(WIDTH * .75, HEIGHT * .75)
    roundRect(0, 0, WIDTH / 4 - 10, HEIGHT / 4 - 10)
end

Anyone notice that when drawing with an aplha channel, that the line caps draw the alpha channel twice? The “caps” look like they have twice the density inside of the line than outside of the line.

Yep, it looks like a rect with two ellipses drawn on the end, right? I was thinking about rewriting it in a shader to make it smooth, but I got side tracked…

I have a mesh version of rounded rectangles if anyone would like it.

sure!