Rounded Rectangle (2nd Attempt)

I have been trying to put together my own shader in the Shader lab for rounded rectangles, and this is what I have come up with so far. It is pretty dense, and needs to be optimized, but it is able to create a rounded rectangle and smooth the inner and outer border lines in a project or the shader lab, but I would like to move some of it over to the vertex shader for bounds calculation, but I am not entirely sure how to. Right now all of the calculation happens in the fragment shader, but is sees a significant FPS drop when multiple instances are run in a program with smoothing and adding the ellipse areas for the corners. If you want to try it out, you can just past it into the shader lab with the default vertex shader as I didn’t change it.

For Bindings, the stroke and fill are vec4 objects all from 0 - 255, the limits if set to one will prevent the specified corner from being rounded and if zero the corners will be rounded. AntiA is the area of Smoothing on either side of a line. I generally use 0.005. StrokeWidth and radius are in ranges 0 - 1, and position assumes rect mode centered, and accepts a vec4 (x,y,w,h).If you set rectmode to 1 however it assumes rectmode corners.

Please let me know where I can improve it.

Here’s a screenshot of the shader and bindings I’m using
http://www.flickr.com/photos/99433588@N04/11677079456/sizes/o/in/photostream/


//
// A basic fragment shader
//

//Default precision qualifier
precision highp float;

//This represents the current texture on the mesh
uniform lowp sampler2D texture;

uniform vec4 position;
uniform vec4 fill;
uniform vec4 stroke;
uniform float StrokeWidth;
uniform float radius;
uniform float rectMode;
uniform float AntiA ;
uniform float LimitRD; 
uniform float LimitLD;
uniform float LimitRU; 
uniform float LimitLU;

//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
//The interpolated texture coordinate for this fragment
varying highp vec2 vTexCoord;

void main()
{

    //Sample the texture at the interpolated coordinate
    lowp vec4 col = texture2D( texture, vTexCoord ) * vColor;
    vec4 Color = vec4(0,0,0,0);
    
    vec2 Center = vec2 (0.5,0.5);
    vec4 fill = vec4(fill.r/255.,fill.g/255.,fill.b/255.,fill.a/255.);
    vec4 stroke = vec4(stroke.r/255.,stroke.g/255.,stroke.b/255.,stroke.a/255.);
    float W = position.z/2.; float H = position.w/2.;
    
    float Radius; float dist = min(W,H); // Keeps Radius within rect bounds
     if (radius <= dist) Radius = radius; else if (radius > dist) Radius = dist;
     float radius = Radius;

    if (rectMode == 1.) // rectMode CORNERS
     W,H = (position.z - position.x)/2., (position.w - position.y)/.2; 
    
    // ---------------
    
    
    float L = Center.x - W; float R = Center.x + W; 
    float U = Center.y + H; float D = Center.y - H;
    
    // Draws Shape Line Border
    if (vTexCoord.x >= L - AntiA/2. && vTexCoord.x <= R + AntiA/2.
     && vTexCoord.y  >= D - AntiA/2. && vTexCoord.y <= U + AntiA/2.) { 
   
     // Interior Fill Drawing
     if (vTexCoord.x >= L + StrokeWidth - AntiA/2. 
      && vTexCoord.x <= R - StrokeWidth + AntiA/2.
      && vTexCoord.y >= D + StrokeWidth - AntiA/2. 
      && vTexCoord.y <= U - StrokeWidth + AntiA/2.){
 
      float DistA = min(distance(vec2(vTexCoord.x, U - StrokeWidth + AntiA/2.),vTexCoord), 
       distance(vec2(vTexCoord.x,D + StrokeWidth - AntiA/2.),vTexCoord));
      float DistB = min(distance(vec2(L + StrokeWidth - AntiA/2.,vTexCoord.y),vTexCoord), 
       distance(vec2(R - StrokeWidth + AntiA/2.,vTexCoord.y),vTexCoord));
      float Dist = min(DistA,DistB);
    
      if (Dist <= AntiA) { float distOut = AntiA - Dist; 
       if (StrokeWidth > 0.) Color = mix(fill,stroke,(distOut)/AntiA); 
       else Color = mix(fill,vec4(0,0,0,0),(distOut)/AntiA); } 
      else Color = fill; }
    
    else if (vTexCoord.x <= L + AntiA/2. || vTexCoord.x >= R - AntiA/2.){
     if (vTexCoord.y < U - AntiA/2. && vTexCoord.y > D + AntiA/2. ) {
      float Dist = min(distance(vec2(L + AntiA/2.,vTexCoord.y),vTexCoord), 
       distance(vec2(R - AntiA/2.,vTexCoord.y),vTexCoord));
      if (Dist <= AntiA) Color = mix(vec4(0,0,0,0),stroke,(AntiA - Dist)/AntiA); }
     else if (vTexCoord.y > U - AntiA/2. || vTexCoord.y < D + AntiA/2.){
    
      vec2 LD = vec2(Center.x - W ,Center.y - H) + vec2(AntiA/2.,AntiA/2.);
      vec2 RD = vec2(Center.x + W ,Center.y - H) - vec2(AntiA/2.,-AntiA/2.);
      vec2 LU = vec2(Center.x - W ,Center.y + H) + vec2(AntiA/2.,-AntiA/2.);
      vec2 RU = vec2(Center.x + W ,Center.y + H) - vec2(AntiA/2.,AntiA/2.);
    
      //Draws Rect Corners
      float DistA = min(distance(LU,vTexCoord), distance(RU,vTexCoord));
      float DistB = min(distance(LD,vTexCoord), distance(RD,vTexCoord));
      float Dist = min(DistA,DistB);
      Color = mix(vec4(0,0,0,0),stroke,(AntiA - Dist)/AntiA); }  }
        
    else if (vTexCoord.y <= D + AntiA/2. || vTexCoord.y >= U - AntiA/2.){
     if (vTexCoord.x < R - AntiA/2. && vTexCoord.x > L - AntiA/2.) {
      float Dist = min(distance(vec2(vTexCoord.x, U - AntiA/2.),vTexCoord),
      distance(vec2(vTexCoord.x, D + AntiA/2.),vTexCoord));
      if (Dist <= AntiA) Color = mix(vec4(0,0,0,0),stroke,(AntiA - Dist)/AntiA); }}
    else Color = stroke;  }
    

   if (Radius > 0.) { // Rounds Rectangle Sides

    // Corner Circle Coordinates //
    vec2 LD = vec2(Center.x - W ,Center.y - H) + vec2(Radius,Radius);
    vec2 RD = vec2(Center.x + W ,Center.y - H) - vec2(Radius,-Radius);
    vec2 LU = vec2(Center.x - W ,Center.y + H) + vec2(Radius,-Radius);
    vec2 RU = vec2(Center.x + W ,Center.y + H) - vec2(Radius,Radius);
    
   //Rectangle Interior Pixel Check
   bool Area = vTexCoord.y < Center.y + H - StrokeWidth
    && vTexCoord.y > Center.y - H + StrokeWidth && vTexCoord.x > Center.x - W + StrokeWidth 
    && vTexCoord.x < Center.x + W - StrokeWidth;

   // Inside Drawn Rectangle Fill // --- ---- --- -- ---
   if (vTexCoord.x < LD.x && vTexCoord.y < LD.y && LimitLD == 0.
   || vTexCoord.x > RD.x && vTexCoord.y < RD.y && LimitRD == 0.
   || vTexCoord.x < LU.x && vTexCoord.y > LU.y && LimitLU == 0.
   || vTexCoord.x > RU.x && vTexCoord.y > RU.y && LimitRU == 0.
   || Area && vTexCoord.x < LU.x && vTexCoord.y > LU.y && LimitLU == 0. &&
    distance(LU,vTexCoord) <= Radius - StrokeWidth + AntiA/2.
   || Area && vTexCoord.x > RU.x && vTexCoord.y > RU.y && LimitRU == 0. &&
    distance(RU,vTexCoord) <= Radius - StrokeWidth + AntiA/2.
   || Area && vTexCoord.x < LD.x && vTexCoord.y < LD.y && LimitLD == 0. &&
    distance(LD,vTexCoord) <= Radius - StrokeWidth + AntiA/2.
   || Area && vTexCoord.x > RD.x && vTexCoord.y < RD.y && LimitRD == 0. &&
    distance(RD,vTexCoord) <= Radius - StrokeWidth + AntiA/2.) {

    float DistU = min(distance(LU,vTexCoord),distance(RU,vTexCoord));
    float DistD = min(distance(LD,vTexCoord),distance(RD,vTexCoord));
    float Dist = min(DistU,DistD); 

    float distIn = abs(radius - StrokeWidth - AntiA/2. - Dist) ; 
    float distOut = radius + AntiA/2. - Dist; 
    
    if (Dist <= Radius - StrokeWidth + AntiA/2. && Dist >= Radius - StrokeWidth - AntiA/2.) 
     {if (StrokeWidth > 0.) Color = mix(fill,stroke,((distIn)/AntiA));
      else Color = mix(fill,vec4(0,0,0,0),((distIn)/AntiA));}
    
    else if (Dist <= Radius + AntiA/2. && Dist > Radius - StrokeWidth + AntiA/2.) 
     if (Dist < Radius - AntiA/2.) Color = stroke;
     else Color = mix(vec4(0,0,0,0),stroke,(distOut / AntiA)); 
    else if (Dist > Radius + AntiA/2.) Color = vec4(0,0,0,0);  };
    
 };
    
    //Set the output color to the texture color
    gl_FragColor = Color;
}

If you look at XFC you’ll find an implementation of rounded rect based on mesh.

@Jmv38 Thanks, I’ll check it out.

@Beckett2000 - if you are doing that much work in the shader just for a rounded rectangle, you are limiting what you can do with the rest of your program.

Rounded rectangles come up regularly in the forum, try a little search (I’m not trying to suggest Jmv38’s code isn’t suitable, just that it’s interesting to see a variety of approaches)

@Beckett2000 I’ve been looking for a good rounded rect shader for a while, with the other implementations I’ve seen on the forum they use circles and lines with a rect in the middle or just lines with a round cap and a rectangle, but this doesn’t allow for alpha and the circles often don’t align with the lines and it looks out of place.

@Luatee: Same here, I’d like to have a decent round rect shader too.

Ok, since it seems to be missing, here is an explicit example of how i make rounded rects.
I use a mesh, not a shader, because then the work is done only once. So It is faster to create the round rect image, then spite it where needed. This is just one of the many possible ways to do it. If it can help someone.
image


--# RoundRect

function roundRect(w,h,radius,rectColor)
    
    local r = radius or 16
    if r > h/2 then r = h/2 end
    if r > w/2 then r = w/2 end
    r = math.floor(r)
    local c = rectColor or color(255)
    local a = r -- half size of background template 
    local b = r/2 -- quater size
    local wc, hc = r,r
    local xLeft, yBot  = -r +b, -r+b
    local xRight, yTop = -xLeft, -yBot
    local i 

    -- background is meshed into a 3x3 matrix to keep detail fidelity
    local m = mesh()
    
    -- bottom line
    i = m:addRect(xLeft,yBot,a,a)
    m:setRectTex(i,0,0,0.5,0.5)
    i = m:addRect(0,yBot,wc,a)
    m:setRectTex(i,0.5,0,0,0.5)
    i = m:addRect(xRight,yBot,a,a)
    m:setRectTex(i,0.5,0,0.5,0.5)
    
    -- middle line
    i = m:addRect(xLeft,0,a,hc)
    m:setRectTex(i,0, 0.5, 0.5, 0)
    i = m:addRect(0,0,wc,hc)
    m:setRectTex(i,0.5, 0.5 ,0 ,0)
    i = m:addRect(xRight,0,a,hc)
    m:setRectTex(i,0.5, 0.5, 0.5, 0)
    
    -- top line
    i = m:addRect(xLeft,yTop,a,a)
    m:setRectTex(i,0, 0.5, 0.5, 0.5)
    i = m:addRect(0,yTop,wc,a)
    m:setRectTex(i,0.5, 0.5 ,0 ,0.5)
    i = m:addRect(xRight,yTop,a,a)
    m:setRectTex(i,0.5, 0.5, 0.5, 0.5)
    
    local icon = image(a*2,a*2)
    setContext(icon)
        background(0, 0, 0, 0)
        fill(c)
        noStroke()
        ellipse(a,a,r*2)
    setContext()
    m.texture = icon


    local img = image(w,h)
    local wc, hc = w-2*a, h-2*a
    local xLeft, yBot  = -w/2 +b, -h/2+b
    local xRight, yTop = -xLeft, -yBot
    local i 
    

    -- mesh is resized to final image size
    -- bottom line
    i=1
    m:setRect(i,xLeft,yBot,a,a)
    i=i+1
    m:setRect(i,0,yBot,wc,a)
    i=i+1
    m:setRect(i,xRight,yBot,a,a)
    
    -- middle line
    i=i+1
    m:setRect(i,xLeft,0,a,hc)
    i=i+1
    m:setRect(i,0,0,wc,hc)
    i=i+1
    m:setRect(i,xRight,0,a,hc)

    -- top line
    i=i+1
    m:setRect(i,xLeft,yTop,a,a)
    i=i+1
    m:setRect(i,0,yTop,wc,a)
    i=i+1
    m:setRect(i,xRight,yTop,a,a)

    setContext(img)
        pushStyle() pushMatrix()
        resetStyle() resetMatrix()
        translate(w/2,h/2)
        m:draw()
        popStyle() popMatrix()
    setContext()
    
    return img

end


--# Main
-- round rect by JMV38

function setup()
    
    r1 = roundRect(150,100,30,   color(255, 189, 0, 255))
    r2 = roundRect(200,100,10,   color(255, 0, 13, 255))
    r3 = roundRect(300,100,5,   color(34, 0, 255, 255))
    r4 = roundRect(200,100,60,   color(0, 255, 13, 255))
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    translate(200,200)
    sprite(r1)
    translate(300,0)
    sprite(r2)
    translate(-300,300)
    sprite(r3)
    translate(300,0)
    sprite(r4)
end

In XFC the version is more complex, because i want to use a hand-drawn image for the icon, to make any kind of border, shadow, shape etc… possible

I’m sure I’ve posted this somewhere before. Here’s my rounded rectangle mesh. It’s an honest mesh (ie the mesh is the rounded rectangle, not a rectangle with a rounded texture). You can also turn off the rounding on some corners, and specify the radius of the rounding. It caches the mesh for speed.

--[[
This is an auxilliary function for drawing a rectangle with possibly
curved cornders.  The first four parameters specify the rectangle.
The fifth is the radius of the corner rounding.  The optional sixth is
a way for specifying which corners should be rounded by passing a
number between 0 and 15.  The first bit corresponds to the lower-left
corner and it procedes clockwise from there.
--]]

local __RRects = {}

function RoundedRectangle(x,y,w,h,s,c,a)
    c = c or 0
    w = w or 0
    h = h or 0
    if w < 0 then
        x = x + w
        w = -w
    end
    if h < 0 then
        y = y + h
        h = -h
    end
    w = math.max(w,2*s)
    h = math.max(h,2*s)
    a = a or 0
    pushMatrix()
    translate(x,y)
    rotate(a)
    local label = table.concat({w,h,s,c},",")
    if __RRects[label] then
        __RRects[label]:setColors(fill())
        __RRects[label]:draw()
    else
    local rr = mesh()
    local v = {}
    local ce = vec2(w/2,h/2)
    local n = 4
    local o,dx,dy
    for j = 1,4 do
        dx = -1 + 2*(j%2)
        dy = -1 + 2*(math.floor(j/2)%2)
        o = ce + vec2(dx * (w/2 - s), dy * (h/2 - s))
        if math.floor(c/2^(j-1))%2 == 0 then
    for i = 1,n do
        table.insert(v,o)
        table.insert(v,o + vec2(dx * s * math.cos((i-1) * math.pi/(2*n)), dy * s * math.sin((i-1) * math.pi/(2*n))))
        table.insert(v,o + vec2(dx * s * math.cos(i * math.pi/(2*n)), dy * s * math.sin(i * math.pi/(2*n))))
    end
    else
        table.insert(v,o)
        table.insert(v,o + vec2(dx * s,0))
        table.insert(v,o + vec2(dx * s,dy * s))
        table.insert(v,o)
        table.insert(v,o + vec2(0,dy * s))
        table.insert(v,o + vec2(dx * s,dy * s))
    end
    end
    rr.vertices = v
    rr:addRect(ce.x,ce.y,w,h-2*s)
    rr:addRect(ce.x,ce.y + (h-s)/2,w-2*s,s)
    rr:addRect(ce.x,ce.y - (h-s)/2,w-2*s,s)
    rr:setColors(fill())
    rr:draw()
    __RRects[label] = rr
    end
    popMatrix()
end

@Andrew_Stacey lol! Do you mean my mesh is not honest? :)) :)) :))

@Jmv38 No! It’s not honest! When you actually draw your rounded rectangle then you are drawing a sprite. So you’re drawing a proper rectangle with a texture which happens to be rounded. This means that you have some transparent pixels which aren’t needed. That’s what I mean about your mesh not being honest.

@Andrew_Stacey this is correct. Now i can see better how dis-honest my code is. And i dont even feel ashamed! >:)
(just to make sure i am correctly understood here : :wink: )

@Andrew_Stacey With the use of the mesh that is actually rounded, is there a save on processing from texturing a rectangular mesh? I have run your code and found that it seems to perform significantly faster in iterations than my original, but one thing with yours is that it does not have a line border by default, which was one thing I was trying to incorporate for the shader I created. I could see how it might be added, but it appears from playing around with it for a little that your mesh is 3 rectangles and 4 semi circles for the corners. Is there any way that the aforementioned border may be added to it without adding more vertices to the mesh?

This is the menu system that I’m working on and using the shaders to render buttons for. The FPS is really bad in it because of the recording and that it’s on an ipad mini 1, but on average, the program will maintain 60 FPS, dropping to around 50 if a lot of things are running or the rounded part of the rectangles is turned on.

Menu Sys 1 from Beckett Dunning on Vimeo.

@Becket2000 adding a stroke to one of those meshes would be quite costly in a shader or to create the stroke with vertices in a mesh, I haven’t had a play with this shader yet but it has stroke on it which is what I needed most!

@Luatee The stroke I just added with coordinates and determining the location of vTexCoord, but the drawback of mine is that turning on radius to round the corners actually currently almost doubles amount of computation per pixel for the fragment shader,so there is a slowdown if you are trying to render multiple at a time. This is not an issue I believe on newer devices like the 5s, because it has more raw power (and I believe more GPU cores, though I’m not certain.) I have only tested it on my ipad mini 1 and 5s, though I’m pretty sure it would be the same or faster on one of the newer ipad airs or minis. I however do most of my coding on the mini one, and want it to work well on all devices, so I am looking to improve on it significantly. I am looking at doing more with the vertices however as was shown in the examples above, because that would most likely be a better option than doing it
entirely on the fragment shader side.

Ahh right, once I’ve finished my shader I’ll have a look, looks very interesting. I wish the TLL team included one along with their ellipse shader! it would have saved about a year of not having proper roundedRects!

Round Rectangle 2nd Attempt,

The last time I was working on this shader, while it was able to create rounded rectangles, it was rather inefficient and slow when acutally applying the ellipses to the corners for rounding. In this updated version I completely rewrote the vertex and fragment shaders so that there are less conditionals and no pixels are processed more than once or checked when not needed for the fragment shading. Also the variables to set are now (Smooth,StrokeWidth,Radius,Position,Fill, and Stroke).

fill/stroke - Standard vec4 objects which are normalized by the vertex shader and passed to the fragment.

Smooth,StrokeWidth, and Radius - Normalized floating point vars

Position vec2 object (0-1,0-1) (Width,Height)

I currently removed the ability to control which corners are rounded, but I am going to add that back soon and am working towards further optimizing the fragment shader.

Vertex Shader:


//
// A basic vertex shader
//

//This is the current model * view * projection matrix
// Codea sets it automatically
uniform mat4 modelViewProjection;

//This is the current mesh vertex position, color and tex coord
// Set automatically
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

//This is an output variable that will be passed to the fragment shader
varying lowp vec4 vColor;
varying highp vec2 TexV;

uniform vec4 fill;
uniform vec4 stroke;

varying lowp vec4 Fill;
varying lowp vec4 Stroke;

void main()
{
    //Pass the mesh color to the fragment shader
    vColor = color;
    TexV = texCoord;
    
    // Passes Normalized Stroke and Fill to Fragment
    Fill = vec4(fill.r/255.,fill.g/255.,fill.b/255.,fill.a/255.);
    Stroke = vec4(stroke.r/255.,stroke.g/255.,stroke.b/255.,stroke.a/255.);

    //Multiply the vertex position by our combined transform
    gl_Position = modelViewProjection * position;
}


Fragment Shader:


//
// A basic fragment shader
//

//Default precision qualifier
precision highp float;

uniform vec2 position;
uniform float Smooth;
uniform float StrokeWidth;
uniform float radius;

//The interpolated vertex color for this fragment
varying lowp vec4 vColor;
varying lowp vec4 Stroke;
varying lowp vec4 Fill;

//The interpolated texture coordinate for this fragment
varying highp vec2 TexV;

void main()

{
    float SM = Smooth * 0.5; vec4 Color = vec4(0,0,0,0);
    vec4 Dims = vec4(0.5 - position.x * 0.5, 0.5 + position.x * 0.5, 
     0.5 + position.y * 0.5, 0.5 - position.y * 0.5);
    
    float L = Dims.x; float R = Dims.y; float U = Dims.z; float D = Dims.w;
    float Radius = abs(radius); float dist = min(position.x * 0.5,position.y * 0.5);
    if (Radius > dist) Radius = dist;
    float Max = max(StrokeWidth,Radius);
    
    // Sets Bounds for Drawing
    if (TexV.x >= L - SM && TexV.x <= R + SM && TexV.y <= U + SM && TexV.y >= D - SM) {
    
       // Rounds Rectangle Corners
       if ((TexV.x < L + Radius || TexV.x > R - Radius) && 
        (TexV.y < D + Radius || TexV.y > U - Radius)){ 
    
        float Dist = min(min(distance(vec2(L + Radius,D + Radius),TexV),
         distance(vec2(R - Radius,D + Radius),TexV)),
         min(distance(vec2(L + Radius,U - Radius),TexV),
         distance(vec2(R - Radius,U - Radius),TexV))); 
        
        if (Dist <= Radius - StrokeWidth + SM && Dist >= Radius - StrokeWidth - SM)
         {if (StrokeWidth > 0.) // Fill Smoothing
         Color = mix(Fill,Stroke,(abs(Radius - StrokeWidth - SM - Dist))/Smooth);
         else Color = mix(Fill,vec4(0,0,0,0),((abs(Radius - StrokeWidth - SM - Dist))/Smooth));}
        
        else if (Dist <= Radius + SM && Dist >= Radius - StrokeWidth + SM) 
         if (Dist < Radius - SM) Color = Stroke; //Stroke Smiothing
         else Color = mix(vec4(0,0,0,0),Stroke,(Radius + SM - Dist)/Smooth); 
        
        else if (Dist < Radius + SM) Color = Fill;
        else if (Dist > Radius + SM) Color = vec4(0,0,0,0); }
        
       else { // Draws Rectangle Sides/Interior
    
        float Dist = min( // Distance From Pixel to Closest Side
         min(distance(vec2(TexV.x, U + SM),TexV),distance(vec2(TexV.x,D - SM),TexV)), 
         min(distance(vec2(L - SM,TexV.y),TexV),distance(vec2(R + SM,TexV.y),TexV))); 
        
        if (Dist <= Smooth + StrokeWidth && Dist >= StrokeWidth && StrokeWidth > 0.) 
         Color = mix(Fill,Stroke,(Smooth - Dist + StrokeWidth)/Smooth); 
        else if (Dist + StrokeWidth <= Smooth) 
         Color = mix(Fill,vec4(0,0,0,0),(Smooth - Dist + StrokeWidth)/Smooth);
        else if (Dist <= Smooth && StrokeWidth > 0.) 
         Color = mix(Stroke,vec4(0,0,0,0),(Smooth - Dist)/Smooth);
        else if ((TexV.y <= D + StrokeWidth || TexV.y >= U - StrokeWidth) ||
         (TexV.x <= L + StrokeWidth || TexV.x >= R - StrokeWidth)) Color = Stroke;   
        else Color = Fill; }} 

    else discard;
    
    //Set the output color to the texture color
    gl_FragColor = Color;
    
}

When I’m drawing rounded rects, they are most often based on a texture. So I use something like this to make a 9-patch.

UI.BorderImage = class(UI.Rect)
function UI.BorderImage:init(ps, ...)
    UI.Item.init(self, ps, ...)
    self.m = mesh()
    self.m.texture = readImage(ps.image)
    local iw, ih = spriteSize(self.m.texture)
    local b = ps.border
    if type(b) == "number" then b = {b,b,b,b} end
    local bl,br,bt,bb = unpack(b)
    local mw,mh = iw-bl-br, ih-bt-bb    
    local ri = 0
    function slice(x,y,w,h, tx,ty,tw,th)
        if w == 0 or h == 0 then return end
        self.m:addRect(x+w/2, y+h/2, w, h)
        ri = ri + 1        
        self.m:setRectTex(ri,tx/iw,ty/ih,tw/iw,th/ih)
    end
    
    slice(0,0,bl,bb, 0,0,bl,bb)
    slice(bl,0,self.w-bl-br,bb, bl,0,mw,bb)
    slice(self.w-br,0,br,bb, bl+mw,0,br,bb)
    slice(0,bb,bl,self.h-bt-bb, 0,bb,bl,mh)
    slice(bl,bb,self.w-bl-br,self.h-bt-bb, bl,bb,mw,mh)
    slice(self.w-br,bb,br,self.h-bt-bb, bl+mw,bb,br,mh)
    slice(0,self.h-bt,bl,bt, 0,bb+mh,bl,bt)
    slice(bl,self.h-bt,self.w-bl-br,bt, bl,bb+mh,mw,bt)
    slice(self.w-br,self.h-bt,br,bt, bl+mw,bb+mh,br,bt)      
    self.m:setColors(self.ps.color or color(255,255,255,255))
end

A bit out of context, but image dividing the image into 9 areas where the corner areas match the size of the rounded corner in the image. Well tell me if anyone wants a full working example instead of code ripped out of my UI library. :slight_smile: