Instead, I plan to keep interpolation in the RGB space (at least for now), but I want to improve and provide more options than the 5th order polynomial easing function that is currently implemented. I finally have a working cubic interpolation function (it was simpler than I thought!) which I wanted to showcase below. The real trick for using cubic interpolation for colors though is that you want to prevent overshoot outside of the range [0-1] and you want to preserve monotonicity where possible. With the help of Wikipedia (Cubic Hermite splines and Monotone cubic interpolation), I finally got that working as well.
I have not yet updated the gradient library, but wanted to share the quick and dirty script I put together that compares a variety of interpolation methods. Compared in this script are the following interpolation curves:
- Linear Interpolation
- Cosine Interpolation
- Parameterized 5th Order Polynomial Interpolation (select acceleration values from 0 to 30)
- Cubic Hermite Spline (Monotonic)
- Cubic Hermite Spline
Again, the 5th order polynomial is currently what the gradient library uses. You can see where it isn't as smooth when values are not oscillating, since the slope at every gradient stop is 0, causing the curve to have to level out to 0 when it could just pass through the point. This is the main problem that cubic interpolation solves.
Here is interpolation over the same points using cubic hermite splines (one with tangents estimated using finite difference and the other with tangents adjusted for preserving monotonicity):
The "Cubic Monotone Splines" are what I'm most excited about implementing, because I think they will look the smoothest and most natural. Hopefully this method won't have too big of an impact on the gradient drawing speed.
Code: Select all
DEF GET_TANGENTS_FINITE_DIFFERENCE (x(), p())
GET DIM x XSIZE n
GET DIM p XSIZE np
IF n <> np THEN RETURN -1
DIM m(n)
FOR k = 0 TO n-1
IF k = 0 THEN
m(k) = (p(k+1) - p(k)) / (x(k+1) - x(k))
ELSE ! IF k = n-1 THEN
m(k) = (p(k) - p(k-1)) / (x(k) - x(k-1))
ELSE
mL = (p(k) - p(k-1)) / (x(k) - x(k-1))
mR = (p(k+1) - p(k)) / (x(k+1) - x(k))
m(k) = (mL + mR)/2
END IF ! END IF
NEXT k
END DEF
DEF GET_TANGENTS_MONOTONIC (x(), p())
GET DIM x XSIZE n
GET DIM p XSIZE np
IF n <> np THEN RETURN -1
DIM m(n)
' Start with average of secant line slopes
GET_TANGENTS_FINITE_DIFFERENCE(x, p)
COPY1(GET_TANGENTS_FINITE_DIFFERENCE.m, m)
' Adjust tangents to preserve curve mononocity
FOR k = 1 TO n-2
mL = (p(k) - p(k-1)) / (x(k) - x(k-1))
mR = (p(k+1) - p(k)) / (x(k+1) - x(k))
IF SIGN(mL) <> SIGN(mR) THEN m(k) = 0
NEXT k
FOR k = 0 TO n-2
mR = (p(k+1) - p(k)) / (x(k+1) - x(k))
IF mR = 0 THEN
m(k) = 0
m(k+1) = 0
ELSE
ak = m(k)/mR
bk = m(k+1)/mR
IF ak < 0 OR bk < 0 THEN
m(k) = 0
ELSE ! IF (ak*ak + bk*bk) > 9 THEN
' Prevent overshoot
tk = 3/(SQRT(ak*ak + bk*bk))
m(k) = tk*ak*mR
m(k+1) = tk*bk*mR
END IF ! END IF
END IF
NEXT k
END DEF
DEF CUBIC_HERMITE_SPLINE (t, x1, x2, p1, p2, m1, m2)
dx = x2 -x1
t2 = t*t
t3 = t2*t
' Hermite basis functions
h00 = 2*t3 - 3*t2 + 1
h10 = t3 - 2*t2 + t
h01 = -2*t3 + 3*t2
h11 = t3 - t2
RETURN h00*p1 + h10*dx*m1 + h01*p2 + h11*dx*m2
END DEF
'==============================================================
.pi = 2*ACOS(0)
DEF LERP (t, a, b) = a + t*(b-a)
DEF COSERP (t, a, b) = LERP((1 - COS(.pi*t))/2, a, b)
DEF EASE_POLY5_A (t, a) = t*t*(t*(t*(t*(6-a)+(5*a-30)/2)+2*(5-a))+a/2)
DEF COPY1 (from(), to())
GET DIM from XSIZE n
GET DIM to XSIZE m
IF n <> m THEN RETURN -1
FOR i = 0 TO n-1
to(i) = from(i)
NEXT i
END DEF
'==============================================================
GRAPHICS CLEAR 1,1,1
SET ORIENTATION PORTRAIT
GET SCREEN SIZE scrW, scrH
xMin = scrW*0.1
xMax = scrW*0.9
yMin = xMin
yMax = xMax
sz = xMax - xMin
n = 9
DIM xk(n), pk(n), mk(n)
xk(0) = sz*0.05
pk(0) = sz*0.2
xk(1) = sz*0.19
pk(1) = sz*0.5
xk(2) = sz*0.31
pk(2) = sz*0.5
xk(3) = sz*0.5
pk(3) = sz*1
xk(4) = sz*0.6
pk(4) = sz*0.9
xk(5) = sz*0.65
pk(5) = sz*0.4
xk(6) = sz*0.7
pk(6) = sz*0.1
xk(7) = sz*0.8
pk(7) = sz*0.5
xk(8) = sz*0.95
pk(8) = sz*0.3
GET_TANGENTS_FINITE_DIFFERENCE(xk, pk)
COPY1(GET_TANGENTS_FINITE_DIFFERENCE.m, mk)
mk(0) = 0
mk(n-1) = 0
DRAW COLOR 0, 0, 0
fs = FONT_SIZE()
SPRITE "LERP" BEGIN scrW,scrH
FILL COLOR .5, .5, 1
FOR k = 0 TO n-1
x = xMin + xk(k)
y = yMin + sz - pk(k)
FILL CIRCLE x,y SIZE 3
NEXT k
FILL COLOR 0,0,0
FOR x = 0 TO sz STEP 0.25
IF x < xk(0) THEN
y = pk(0)
ELSE ! IF x > xk(n-1) THEN
y = pk(n-1)
ELSE
FOR k = 0 TO n-2
IF x < xk(k+1) THEN BREAK k
NEXT k
t = (x - xk(k)) / (xk(k+1) - xk(k))
y = LERP(t, pk(k), pk(k+1))
END IF ! END IF
y = sz - y
FILL CIRCLE xMin+x, yMin+y SIZE 0.5
NEXT x
DRAW TEXT "Linear Interpolation" AT xMin, yMax
SPRITE END
SPRITE "COSERP" BEGIN scrW,scrH
FILL COLOR .5, .5, 1
FOR k = 0 TO n-1
x = xMin + xk(k)
y = yMin + sz - pk(k)
FILL CIRCLE x,y SIZE 3
NEXT k
FILL COLOR 0,0,0
FOR x = 0 TO sz STEP 0.25
IF x < xk(0) THEN
y = pk(0)
ELSE ! IF x > xk(n-1) THEN
y = pk(n-1)
ELSE
FOR k = 0 TO n-2
IF x < xk(k+1) THEN BREAK k
NEXT k
t = (x - xk(k)) / (xk(k+1) - xk(k))
y = COSERP(t, pk(k), pk(k+1))
END IF ! END IF
y = sz - y
FILL CIRCLE xMin+x, yMin+y SIZE 0.5
NEXT x
DRAW TEXT "Cosine Interpolation" AT xMin, yMax
SPRITE END
ea = 0
SPRITE "POLY5_0" BEGIN scrW,scrH
FILL COLOR .5, .5, 1
FOR k = 0 TO n-1
x = xMin + xk(k)
y = yMin + sz - pk(k)
FILL CIRCLE x,y SIZE 3
NEXT k
FILL COLOR 0,0,0
FOR x = 0 TO sz STEP 0.25
IF x < xk(0) THEN
y = pk(0)
ELSE ! IF x > xk(n-1) THEN
y = pk(n-1)
ELSE
FOR k = 0 TO n-2
IF x < xk(k+1) THEN BREAK k
NEXT k
t = (x - xk(k)) / (xk(k+1) - xk(k))
t = EASE_POLY5_A(t, ea)
y = LERP(t, pk(k), pk(k+1))
END IF ! END IF
y = sz - y
FILL CIRCLE xMin+x, yMin+y SIZE 0.5
NEXT x
DRAW TEXT "5th Order Polynomial" AT xMin, yMax
DRAW TEXT "(ea="&ea&")" AT xMin, yMax+ fs*1.2
SPRITE END
ea = 6
SPRITE "POLY5_6" BEGIN scrW,scrH
FILL COLOR .5, .5, 1
FOR k = 0 TO n-1
x = xMin + xk(k)
y = yMin + sz - pk(k)
FILL CIRCLE x,y SIZE 3
NEXT k
FILL COLOR 0,0,0
FOR x = 0 TO sz STEP 0.25
IF x < xk(0) THEN
y = pk(0)
ELSE ! IF x > xk(n-1) THEN
y = pk(n-1)
ELSE
FOR k = 0 TO n-2
IF x < xk(k+1) THEN BREAK k
NEXT k
t = (x - xk(k)) / (xk(k+1) - xk(k))
t = EASE_POLY5_A(t, ea)
y = LERP(t, pk(k), pk(k+1))
END IF ! END IF
y = sz - y
FILL CIRCLE xMin+x, yMin+y SIZE 0.5
NEXT x
DRAW TEXT "5th Order Polynomial" AT xMin, yMax
DRAW TEXT "(ea="&ea&")" AT xMin, yMax+ fs*1.2
SPRITE END
ea = 14
SPRITE "POLY5_14" BEGIN scrW,scrH
FILL COLOR .5, .5, 1
FOR k = 0 TO n-1
x = xMin + xk(k)
y = yMin + sz - pk(k)
FILL CIRCLE x,y SIZE 3
NEXT k
FILL COLOR 0,0,0
FOR x = 0 TO sz STEP 0.25
IF x < xk(0) THEN
y = pk(0)
ELSE ! IF x > xk(n-1) THEN
y = pk(n-1)
ELSE
FOR k = 0 TO n-2
IF x < xk(k+1) THEN BREAK k
NEXT k
t = (x - xk(k)) / (xk(k+1) - xk(k))
t = EASE_POLY5_A(t, ea)
y = LERP(t, pk(k), pk(k+1))
END IF ! END IF
y = sz - y
FILL CIRCLE xMin+x, yMin+y SIZE 0.5
NEXT x
DRAW TEXT "5th Order Polynomial" AT xMin, yMax
DRAW TEXT "(ea="&ea&")" AT xMin, yMax+ fs*1.2
SPRITE END
ea = 24
SPRITE "POLY5_24" BEGIN scrW,scrH
FILL COLOR .5, .5, 1
FOR k = 0 TO n-1
x = xMin + xk(k)
y = yMin + sz - pk(k)
FILL CIRCLE x,y SIZE 3
NEXT k
FILL COLOR 0,0,0
FOR x = 0 TO sz STEP 0.25
IF x < xk(0) THEN
y = pk(0)
ELSE ! IF x > xk(n-1) THEN
y = pk(n-1)
ELSE
FOR k = 0 TO n-2
IF x < xk(k+1) THEN BREAK k
NEXT k
t = (x - xk(k)) / (xk(k+1) - xk(k))
t = EASE_POLY5_A(t, ea)
y = LERP(t, pk(k), pk(k+1))
END IF ! END IF
y = sz - y
FILL CIRCLE xMin+x, yMin+y SIZE 0.5
NEXT x
DRAW TEXT "5th Order Polynomial" AT xMin, yMax
DRAW TEXT "(ea="&ea&")" AT xMin, yMax+ fs*1.2
SPRITE END
ea = 30
SPRITE "POLY5_30" BEGIN scrW,scrH
FILL COLOR .5, .5, 1
FOR k = 0 TO n-1
x = xMin + xk(k)
y = yMin + sz - pk(k)
FILL CIRCLE x,y SIZE 3
NEXT k
FILL COLOR 0,0,0
FOR x = 0 TO sz STEP 0.25
IF x < xk(0) THEN
y = pk(0)
ELSE ! IF x > xk(n-1) THEN
y = pk(n-1)
ELSE
FOR k = 0 TO n-2
IF x < xk(k+1) THEN BREAK k
NEXT k
t = (x - xk(k)) / (xk(k+1) - xk(k))
t = EASE_POLY5_A(t, ea)
y = LERP(t, pk(k), pk(k+1))
END IF ! END IF
y = sz - y
FILL CIRCLE xMin+x, yMin+y SIZE 0.5
NEXT x
DRAW TEXT "5th Order Polynomial" AT xMin, yMax
DRAW TEXT "(ea="&ea&")" AT xMin, yMax+ fs*1.2
SPRITE END
SPRITE "CUBIC_HERMITE" BEGIN scrW,scrH
FILL COLOR .5, .5, 1
FOR k = 0 TO n-1
x = xMin + xk(k)
y = yMin + sz - pk(k)
FILL CIRCLE x,y SIZE 3
NEXT k
FILL COLOR 0,0,0
FOR x = 0 TO sz STEP 0.25
IF x < xk(0) THEN
y = pk(0)
ELSE ! IF x > xk(n-1) THEN
y = pk(n-1)
ELSE
FOR k = 0 TO n-2
IF x < xk(k+1) THEN BREAK k
NEXT k
t = (x - xk(k)) / (xk(k+1) - xk(k))
y = CUBIC_HERMITE_SPLINE(t, xk(k), xk(k+1), pk(k), pk(k+1), mk(k), mk(k+1))
END IF ! END IF
y = sz - y
FILL CIRCLE xMin+x, yMin+y SIZE 0.5
NEXT x
DRAW TEXT "Cubic Hermite Splines" AT xMin, yMax
SPRITE END
SPRITE "CUBIC_MONOTONIC" BEGIN scrW,scrH
FILL COLOR .5, .5, 1
FOR k = 0 TO n-1
x = xMin + xk(k)
y = yMin + sz - pk(k)
FILL CIRCLE x,y SIZE 3
NEXT k
GET_TANGENTS_MONOTONIC(xk, pk)
COPY1(GET_TANGENTS_MONOTONIC.m, mk)
mk(0) = 0
mk(n-1) = 0
FILL COLOR 0,0,0
FOR x = 0 TO sz STEP 0.25
IF x < xk(0) THEN
y = pk(0)
ELSE ! IF x > xk(n-1) THEN
y = pk(n-1)
ELSE
FOR k = 0 TO n-2
IF x < xk(k+1) THEN BREAK k
NEXT k
t = (x - xk(k)) / (xk(k+1) - xk(k))
y = CUBIC_HERMITE_SPLINE(t, xk(k), xk(k+1), pk(k), pk(k+1), mk(k), mk(k+1))
END IF ! END IF
y = sz - y
FILL CIRCLE xMin+x, yMin+y SIZE 0.5
NEXT x
DRAW TEXT "Cubic Monotone Splines" AT xMin, yMax
SPRITE END
num_methods = 9
DIM n$(num_methods)
n$(0) = "LERP"
n$(1) = "COSERP"
n$(2) = "POLY5_0"
n$(3) = "POLY5_6"
n$(4) = "POLY5_14"
n$(5) = "POLY5_24"
n$(6) = "POLY5_30"
n$(7) = "CUBIC_MONOTONIC"
n$(8) = "CUBIC_HERMITE"
GRAPHICS
DRAW COLOR 0.5, 0.5, 0.5
DRAW RECT xMin,yMin TO xMax,yMax
DRAW FONT SIZE fs*0.6
DRAW TEXT "Tap to cycle interpolation methods" AT xMin, yMax*1.5
DRAW TEXT "Use two touches to exit" AT xMin, yMax*1.5 + fs*1.2
index = 0
SPRITE n$(index) SHOW
WHILE TOUCH_X(1) = -1
IF NOT sameTouch AND TOUCH_X(0) > -1 THEN
sameTouch = 1
prevIndex = index
index = (index + 1) % (num_methods)
SPRITE n$(index) SHOW
SPRITE n$(prevIndex) HIDE
ELSE ! IF TOUCH_X(0) = -1 THEN
sameTouch = 0
END IF ! END IF
SLOWDOWN
END WHILE