Gradient Line - trouble optimizing code

I’m creating a class that draws a gradient line on the screen:

GradientLine = class()

--this is a gradient line, drawn horizontally from 0,0 to length,0
--the class that uses this needs to handle rotation/translation

function GradientLine:init( length, sc, ec )
    -- you can accept and set parameters here
    self.length = length
    --print( "GL:init :", sc, ec )
    self.sc = {}
    self.ec = {}
    self.sc.r = sc.r
    self.sc.g = sc.g
    self.sc.b = sc.b
    self.sc.a = sc.a
    self.ec.r = ec.r
    self.ec.g = ec.g
    self.ec.b = ec.b
    self.ec.a = ec.a
    
    --print( "GradientLine:init", length, self.sc.r, self.sc.g, self.sc.b, self.sc.a, self.ec.r, self.ec.g, self.ec.b, self.ec.a )
end

function GradientLine:draw()
    --count number of pixels.
    --we're basically gonna do a line() for each pixel, and interpolate the color for each pixel 
    
    --figure out step size for color change
    local rStep = (self.ec.r - self.sc.r) / self.length
    local gStep = (self.ec.g - self.sc.g) / self.length
    local bStep = (self.ec.b - self.sc.b) / self.length
    local aStep = (self.ec.a - self.sc.a) / self.length
    
    local r = self.sc.r
    local g = self.sc.g
    local b = self.sc.b
    local a = self.sc.a
    
    local x = 0
    
    --print( "GradientLine:draw()" )
    strokeWidth( 1 )
    stroke( r, g, b, a )
    line( 0, 0, 1, 0 )
    for i=0, self.length do
        
        
        r = r + rStep
        g = g + gStep
        b = b + bStep
        a = a + aStep
        x = x + 1
        strokeWidth( 1 )
        stroke( r, g, b, a )
        line( x, 0, x+1, 0 )
        --print( r, g, b, a )
    end
end

Is there any way to optimize this? as it stands, it’s going to call line() ‘length’ number of times. if the line is 800px long, then it’s gonna call it 800 times per frame. It works well, btw. it’s just slow when i have more than 3 instances of the class on the screen.

--main

function setup()
    sc = { r=255, g=0, b=0, a=255 }
    ec = { r=0, g=255, b=0, a=255 }
  gl = GradientLine( 500, sc, ec )
end

function draw()
  pushMatrix()
  translate( 50, 50 )
  gl:draw()
  popMatrix()
end

@matkatmusic:
There are definitely ways to optimize this. Some tips:

  • Use a mesh instead of a series of line()s to create the gradient.

  • Use color() instead of tables to represent your rgba quadruples.

  • When you write something like:

    self.sc.r = sc.r
    self.sc.g = sc.g
    self.sc.b = sc.b
    self.sc.a = sc.a

the Lua interpreter has to repeatedly evaluate self.sc. You can often noticeably improve performance by replacing the above with

    local self_sc = self_sc
    self_sc.r = sc.r
    self_sc.g = sc.g
    self_sc.b = sc.b
    self_sc.a = sc.a

(This also, e.g., applies to your xStep computations.)

  • Something like
        r = r + rStep
        g = g + gStep
        b = b + bStep
        a = a + aStep

is actually a vector operation. So you could potentially benefit from representing (r, g, b, a) as a vec4 (unfortunately, I don’t think it works with color; but double-check me on that):

    -- Outside the loop:
    local rgba, rgba_step = vec4(r, g, b, a), vec4(rStep, gStep, bStep, aStep)
    -- ... Inside the loop:
        rgba = rgba + rgba_step
        stroke(rgba.r, rgba.g, rgba.b, rgba.a)
        -- ... etc.

(All code untested.)

I’m having a bit of trouble getting the meshes to work properly.

GradientLine = class()

--this is a gradient line, drawn horizontally from 0,0 to length,0
--the class that uses this needs to handle rotation/translation

function GradientLine:init( length, v4_sc, v4_ec )
    -- you can accept and set parameters here
    self.length = length
    --print( "GL:init :", sc, ec )
    self.sc = v4_sc
    self.ec = v4_ec
    self.mesh = mesh()
    print( "GL:init length:", length )
    self.mesh:resize( length )
    --init vertices and colors here for now
    local verts = {}
    local colors = {}
    local x = 0 
    local self_sc = self.sc
    local self_ec = self.ec
    local m = self.mesh
    local rgbaStep = vec4(  (self_ec.r - self_sc.r ) / length,
                            (self_ec.g - self_sc.g ) / length,
                            (self_ec.b - self_sc.b ) / length,
                            (self_ec.a - self_sc.a ) / length )
    print( "colors:", self.sc, rgbaStep )
    for i=1, length do --init verts and colors
        verts[i] = vec2( x, 0 )
        x = x + 1
        colors[i] = color( self_sc.r, self_sc.g, self_sc.b, self_sc.a )
        self_sc = self_sc + rgbaStep
    end
    self.mesh.vertices = verts
    --assign colors to verts
    for i=1, length do 
        m:color(i, colors[i] )
    end 
end

function GradientLine:draw()
    --figure out step size for colo
    --print( "mesh:", m, m.vertices )
    self.mesh:draw()
end

What am I missing?

here is an alternative. the top bar is version 2.

function setup()
    g = GradientLine( 300,color(255, 0, 0, 255),color(53, 255, 0, 255))
    g2 = GradientLine2( 300,color(255, 0, 0, 255),color(53, 255, 0, 255))
end
function draw()
    translate(200,200)
    g:draw()
    translate(0,10)
    g2:draw()
end

GradientLine2 = class()
function GradientLine2:init( length, sc, ec )
    self.length = length
    local img = image(3,1)
    img:set(1,1,sc) 
    img:set(2,1,color( (sc.r+ec.r)/2 , (sc.g+ec.g)/2 , (sc.b+ec.b)/2, (sc.a+ec.a)/2 ))
    img:set(3,1,ec) 
    local img2 = image(length,1)
    setContext(img2)
        spriteMode(CORNER)
        scale(length/3)
        sprite(img,0,0)
    setContext()
    self.img = img2
end
function GradientLine2:draw()
    spriteMode(CORNER)
    sprite(self.img,0,0)
end

GradientLine = class()

--this is a gradient line, drawn horizontally from 0,0 to length,0
--the class that uses this needs to handle rotation/translation

function GradientLine:init( length, sc, ec )
    -- you can accept and set parameters here
    self.length = length
    --print( "GL:init :", sc, ec )
    self.sc = {}
    self.ec = {}
    self.sc.r = sc.r
    self.sc.g = sc.g
    self.sc.b = sc.b
    self.sc.a = sc.a
    self.ec.r = ec.r
    self.ec.g = ec.g
    self.ec.b = ec.b
    self.ec.a = ec.a

    --print( "GradientLine:init", length, self.sc.r, self.sc.g, self.sc.b, self.sc.a, self.ec.r, self.ec.g, self.ec.b, self.ec.a )
end

function GradientLine:draw()
    --count number of pixels.
    --we're basically gonna do a line() for each pixel, and interpolate the color for each pixel 

    --figure out step size for color change
    local rStep = (self.ec.r - self.sc.r) / self.length
    local gStep = (self.ec.g - self.sc.g) / self.length
    local bStep = (self.ec.b - self.sc.b) / self.length
    local aStep = (self.ec.a - self.sc.a) / self.length

    local r = self.sc.r
    local g = self.sc.g
    local b = self.sc.b
    local a = self.sc.a

    local x = 0

    --print( "GradientLine:draw()" )
    strokeWidth( 1 )
    stroke( r, g, b, a )
    line( 0, 0, 1, 0 )
    for i=0, self.length do


        r = r + rStep
        g = g + gStep
        b = b + bStep
        a = a + aStep
        x = x + 1
        strokeWidth( 1 )
        stroke( r, g, b, a )
        line( x, 0, x+1, 0 )
        --print( r, g, b, a )
    end
end

@matkatmusic: Meshes already interpolate their vertex colors (via the GPU). So you don’t need to implement that yourself. Here is a bit of code that implements your idea, but note that since you’re drawing for y == 0, it won’t be very visible unless you translate it a bit like @Jvm38 does.

GradientLine = class {}

function GradientLine:init(len, startColor, endColor)
    local m = mesh()
    local v = m:buffer("position")
    local c = m:buffer("color")
    local halfWidth = 0.5
    v:resize(6)
    c:resize(6)
    v[1] = vec3(0, -halfWidth, 0)
    v[2] = vec3(0, halfWidth, 0)
    v[3] = vec3(len, halfWidth, 0)
    v[4] = vec3(0, -halfWidth, 0)
    v[5] = vec3(len, -halfWidth, 0)
    v[6] = vec3(len, halfWidth, 0)
    
    c[1] = startColor
    c[2] = startColor
    c[4] = startColor
    c[3] = endColor
    c[5] = endColor
    c[6] = endColor
    self.gradientMesh = m
end

function GradientLine:draw()
    self.gradientMesh:draw()
end

function draw()
    pushMatrix()
    translate(200, 200)
    GradientLine(800, color(200, 0, 0), color(0, 100, 200)):draw()
    popMatrix()
end

If you want a thicker line, increase halfWidth (could be made a parameter).