Problem with tweening color

Am I trying to do something incorrect?
Also noticed the color() selector doesn’t occur on the “tween(2, tweentest, {fillColor = color(0, 233, 233, 255)})” line.

-- tween test

-- Use this function to perform your initial setup
function setup()
    
    tweentest = {x = 100,y = 100, fillColor = color(206, 33, 33, 255), red = 206, green =33, blue = 33, alpha = 255}
    
    -- uncommenting the following line causes an error.
    --tween(2, tweentest, {fillColor = color(0, 233, 233, 255)})
    
    tween(2, tweentest, {red = 0, green = 233, blue = 255, alpha = 255},{loop = tween.loop.pingpong})
    tween(2,tweentest,{x = 600, y =300},{easing = tween.easing.linear, loop = tween.loop.pingpong})
       
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(19, 19, 187, 255)
    stroke(255)
    
    fill(tweentest.fillColor)

    -- the following fill works
    --fill(tweentest.red,tweentest.green,tweentest.blue,tweentest.alpha)
    -- This sets the line thickness
    strokeWidth(5)
    
    ellipse(tweentest.x,tweentest.y,100)
    -- Do your drawing here 
end

The reason this doesn’t work is because the color type does not provide arithmetic operators. So for example:

myColor1 = color(0,0,100)
myColor2 = color(100,100,100)
myColor3 = myColor1 + myColor2

Does not work.

This is not that obvious and perhaps we should look at adding arithmetic operators to color.

For the moment, simply change your fillColor to a vec4 type instead, and then set the fill as follows:

fill( tweentest.fillColor.x, tweentest.fillColor.y, tweentest.fillColor.z )

Ok I understand. Color tweening is shown in the box example for tween in the reference. So that’s why I was trying it out.

.@Reyals123 sorry about that, that’s a mistake on our part. We’ll add arithmetic operators to color to allow tween to work with it.

I can work around that easily enough I just thought I was screwing something up and just wasn’t seeing the problem.

The Tween library was originally designed to work on table properties (specifically numbers), an alternative is to do the following:

object = {fillColor = color(255,255,255,255)}
tween(2, object.fillColor, {r = 255, g = 0, b = 255})
...
fill(object.fillColor)

Thanks John, the reference under tween needs to have your code snippet as the example for color.

Hi! I’m just flagging this up again, as I’ve been trying to tween colours, couldn’t get it to work (getting weird results) and found this post. Wondering if this is till the problem?

@pjholden Try this.

supportedOrientations(LANDSCAPE_ANY)
displayMode(FULLSCREEN)

function setup()
    rectMode(CENTER)
    fontSize(50)
    c={r=0,g=255,b=0}    
end

function draw()
    background(0)
    fill(c.r,c.g,c.b)
    text("Tap screen to start the color change",WIDTH/2,HEIGHT-100)
    rect(WIDTH/2,HEIGHT/2,200,200)
end

function touched(t)
    if t.state==BEGAN then 
        t2=tween(5,c,{r=255,g=0},{loop=tween.loop.pingpong})
    end
end

Cheers dave, i’d Already worked around it (more or less doing what you’ve suggested) just wondering if, as Simeon said upthread (in 2013!) a fix ever came up for it (I’m assuming it never did)

@pjholden What problems are you seeing? I just tried the following and saw what I expected to see (a rectangle fading in to purple).

function setup()
    t = { colour = color(40,40,50)}
    tween(5,t, {colour = color(255,0,255)})
end

function draw()
    background(40,40,50)
    fill(t.colour)
    rect(WIDTH/2,HEIGHT/2,100)
end

@LoopSpace It doesn’t work, it just looked like it did. Red should go from 40 to 255, green from 40 to 0, and blue from 50 to 255. Run the code below and watch the r,g,b values.

This doesn’t work.

function setup()
    parameter.watch("t.colour.r")
    parameter.watch("t.colour.g")
    parameter.watch("t.colour.b")
    
    t = { colour = color(40,40,50)}
    tween(5,t, {colour = color(255,0,255)})
end

function draw()
    background(40,40,50)
    fill(t.colour)
    rect(WIDTH/2,HEIGHT/2,100)
end

This does work.


function setup()
    parameter.watch("t.r")
    parameter.watch("t.g")
    parameter.watch("t.b")
    
    t = { r=40,g=40,b=50}
    tween(5,t, {r=255,g=0,b=255})
end

function draw()
    background(40,40,50)
    fill(t.r,t.g,t.b)
    rect(WIDTH/2,HEIGHT/2,100)
end

I usually notice the alpha values, they go particularly screwy.

As it is, I ended up seperati g out RGBA values in to their components, so I could tween them and corners of a rectangle as part of a single tween (mostly so I could hold the tween’s ID in a single variable which I then reset to zero after it’s finished tweeting, so I can easily check if the thing is animating or not)

self.animating = tween( .7, self, 
        { focusX = self.panels[self.currentPanel].x, 
          focusY = self.panels[self.currentPanel].y,
        focusW = self.panels[self.currentPanel].w,
         focusH = self.panels[self.currentPanel].h,
        focusZoom = zoom,
    focusBGR = self.panels[self.currentPanel].bg.r,
    focusBGG = self.panels[self.currentPanel].bg.g,
    focusBGB = self.panels[self.currentPanel].bg.b,
     focusBGA = self.panels[self.currentPanel].bg.a
     },
        tween.easing.In,
        function()
        self.animating = false
        end
    )

Interesting. It appears that the arithmetic operators on colours are clipped to the 0-255 range. That shouldn’t be a problem as tweening is affine, except that the implementation of tweening converts the natural affine relationship into a linear one and that takes colours out of the 0-255 range temporarily.

The proper fix would be to change tween.lua in the following ways:

local function easeWithTween(self, subject, target, initial)
  local t,b,c,d

  for k,v in pairs(target) do
    if type(v)=='table' then
      easeWithTween(self, subject[k], v, initial[k])
    else
      t,b,c,d = self.running, initial[k], v, self.time -- third argument changed from v - initial[k]
      
      if self.loop then
        t = self.loop(t,d)
      end

      subject[k] = self.easing(t,b,c,d)
    end
  end
end

and then the easing functions need to be changed, eg for the linear one:

local function linear(t, b, c, d) return c * (t / d) + (1 - t / d) * b end

If the second colour is greater than the first, (ie all components of the target colour are bigger than or equal to the corresponding components of the initial colour) then a simple solution is to pass an easing function with correct bracketting. Using my earlier code:

tween(5,t, {colour = color(255,0,255)}, function(t,b,c,d) return (t/d)*c + b end)

Lastly, an option is to modify the arithmetic operators on the color userdata to remove the clipping:

local mt = getmetatable(color())

mt.__add = function(c1,c2)
  return color(c1.r+c2.r, c1.g+c2.g, c1.b+c2.b, c1.a+c2.a)
end

mt.__sub = function(c1,c2)
  return color(c1.r-c2.r, c1.g-c2.g, c1.b-c2.b, c1.a-c2.a)
end

mt.__mul = function(c1,c2)
  if type(c2) == "number" then
    c1,c2 = c2,c1
  end
  return color(c1*c2.r, c1*c2.g, c1*c2.b, c1*c2.a)
end

mt.__div = function(c1,c2)
  return color(c1.r/c2, c1.g/c2, c1.b/c2, c1.a/c2)
end

This has the advantage that it can be used with Codea as-is.
Though of course, this might break other things that rely on colours having their values clipped.