I’ve been thinking again lately about gradient filled boxes / rects in Codea especially with a view to rendering assets at runtime to cut down on space and I came up with the following class.
What it does is generates a single pixel wide (or high) image of a given size (use either width or height in params table) and fills it with a RGB[A] gradient.
As you can see from the demo below - the gradient is defined by a table of stops, where each entry is a table of values, the first is simply a float value from 0 to 1 and represents the position of the stop in the output and the remaining 3 / 4 entries correspond to the RGB / RGBA values at that point. The code interpolates between all the stops in the table to produce the image.
There’s no error checking on the stop position, but the first one should be at 0 and the last one should be at 1 or else it won’t match up to the output image.
The output image can be any size, but the bigger the image the higher the resolution of the gradient and the less banding you see when you scale the output. I’ve included a reasonable example in the class if you don’t specify any params, but ideally you’d pass in your own parameters.
You can then use the image as a sprite (as shown) or as texture for a mesh / shader, it might even be able to generate the lighting table for a toon shader (but I haven’t got that far yet).
-- Gradient
-- A utility class to take a table of color stops (in the range 0-1) and returns an image that can be used as a texture
Gradient = class()
function Gradient:init(params)
params = params or { height=100, stops = {
{0, 255, 0, 0,255},
{0.2, 0,255, 0,128},
{0.4, 0, 0,255,255},
{0.6, 255, 0,255,255},
{0.8, 255,255, 0,192},
{1, 255,255,255,255}}
}
local stops = params.stops or {
{0, 255,0,0},
{1, 0,255,0}
}
local w = params.width or 1
local h = params.height or 1
local img = image(w,h)
local x,xi,y,yi = 1,0,1,0
local len
if params.width then xi,len = 1,w else yi,len = 1,h end
-- Render the texture
local st,en,s1,s2,range
local r,g,b,a,ri,gi,bi,ai
for i=1,#stops-1 do
-- work out the steps / range
s1,s2 = stops[i], stops[i+1]
st,en = s1[1] * len, s2[1] * len
range = en-st
r = s1[2]; ri = (s2[2]-r) / range
g = s1[3]; gi = (s2[3]-g) / range
b = s1[4]; bi = (s2[4]-b) / range
if s1[5] then a = s1[5]; ai = (s2[5]-a) / range
else a,ai = 255,0
end
-- then render it
for j=st,en do
img:set(x,y,r,g,b,a)
x = x + xi; y = y + yi
r = r + ri; g = g + gi; b = b + bi; a = a + ai
end
end
self.img = img
end
function Gradient:getImage() return self.img end
And here is a demo program to show it off
-- Use this function to perform your initial setup
function setup()
g1 = Gradient()
g2 = Gradient({ width=200,
stops = {
{0, 255, 0, 0,255},
{0.5, 0,255, 0,255},
{1, 0, 0,255,255}}})
end
-- This function gets called once every frame
function draw()
background(40, 40, 50)
spriteMode(CORNER)
sprite(g1:getImage(),50,50,50,HEIGHT-100)
sprite(g2:getImage(),150,500,500,50)
end
Note : that if you don’t pass any params in, it’ll use the default one setup in the class, also use either width OR height.
This just gives a simple horizontal / vertical linear gradient, if you need to display it rotated, you could just draw a rect that was big enough and then use clipping to actually display the required area.
My original intention was to use this with a modified ellipse shader to render nice gradient filled rounded rectangles, but if anyone want’s to tackle this then be my guest
Enjoy…