I’m trying to iteratively mix random colors but over time they all become green.
The provided code makes two sets of random-colored squares, and mixes the sets at every tap.
This should result in a constant array of random colors, but instead everything quickly turns green.
(Some code explanation follows the code)
--the big-three Codea functions
function setup()
--create sets of squares of random colors
createTwoSetsOfRandomSquares()
end
function draw()
background(64, 64, 110)
--draw all the squares in each set
for _, square in ipairs(upperSquares) do
square:draw()
end
for _, square in ipairs(lowerSquares) do
square:draw()
end
end
function touched(touch)
if touch.state == ENDED then
-- Shuffle the lower squares
for i = #lowerSquares, 2, -1 do
local j = math.random(i)
lowerSquares[i], lowerSquares[j] = lowerSquares[j], lowerSquares[i]
end
-- mix each upper color with a corresponding lower color and vice versa
for i, upperSquare in ipairs(upperSquares) do
local lowerSquare = lowerSquares[i]
local color1 = randomizeHueBetween(upperSquare.color, lowerSquare.color)
local color2 = randomizeHueBetween(upperSquare.color, lowerSquare.color)
upperSquare.color = color1
lowerSquare.color = color2
end
end
end
--the color functions
--random RGB color
function randomColor()
return color(math.random(255), math.random(255), math.random(255))
end
--conversion from RGB to HSB
function colorToHSB(aColor)
local r, g, b = aColor.r / 255, aColor.g / 255, aColor.b / 255
local max, min = math.max(r, g, b), math.min(r, g, b)
local delta = max - min
local hue = 0
if delta ~= 0 then
if max == r then
hue = ((g - b) / delta) % 6
elseif max == g then
hue = ((b - r) / delta) + 2
else
hue = ((r - g) / delta) + 4
end
hue = hue * 60
if hue < 0 then hue = hue + 360 end
end
local saturation = (max == 0) and 0 or (delta / max)
local brightness = max
return vec3(hue, saturation, brightness)
end
--conversion from HSB to RGB
function hsbToColor(h, s, b)
h = h % 360
s = math.max(0, math.min(1, s))
b = math.max(0, math.min(1, b))
local i = math.floor(h / 60)
local f = (h / 60) - i
local p = b * (1 - s)
local q = b * (1 - (s * f))
local t = b * (1 - (s * (1 - f)))
local r1, g1, b1
if i == 0 then
r1, g1, b1 = b, t, p
elseif i == 1 then
r1, g1, b1 = q, b, p
elseif i == 2 then
r1, g1, b1 = p, b, t
elseif i == 3 then
r1, g1, b1 = p, q, b
elseif i == 4 then
r1, g1, b1 = t, p, b
else
r1, g1, b1 = b, p, q
end
return color(math.floor(r1 * 255), math.floor(g1 * 255), math.floor(b1 * 255), 255)
end
--randomizer between two colors--only randomizes hue
--keeps saturation and brightness of color1
function randomizeHueBetween(color1, color2)
-- Convert RGB to HSB
local hsb1 = colorToHSB(color1)
local hsb2 = colorToHSB(color2)
-- Mix hues with wrapping
local hueDiff = math.abs(hsb2.x - hsb1.x)
if hueDiff > 180 then
hueDiff = 360 - hueDiff
end
local newHue = (math.random() * hueDiff + math.min(hsb1.x, hsb2.x)) % 360
-- Convert back to RGB
return hsbToColor(newHue, hsb1.y, hsb1.z)
end
--the class for color squares
Square = class()
function Square:init(x, y, size, color)
self.x = x
self.y = y
self.size = size
self.color = color
end
function Square:draw()
fill(self.color)
rect(self.x, self.y, self.size, self.size)
end
--the function to set up the square sets
function createTwoSetsOfRandomSquares()
--function for setting up squares once dimensions are calculated
function initializeSquares(numRows, numCols, squareSize, yOffset, margin)
local squares = {}
local totalWidth = numCols * squareSize
local startX = (WIDTH - totalWidth) / 2 -- Centering the block of squares
for row = 1, numRows do
for col = 1, numCols do
local x = startX + (col - 1) * squareSize
local y = yOffset + (row - 1) * squareSize
table.insert(squares, Square(x, y, squareSize, randomColor()))
end
end
return squares
end
--calculate areas to draw squares in
local margin = WIDTH * 0.05
local availableWidth = WIDTH - 2 * margin
local availableHeight = (HEIGHT / 2) - margin
local numSquares = 200
local numRows = math.ceil(math.sqrt(numSquares * availableHeight / availableWidth))
local numCols = math.ceil(numSquares / numRows)
--calculate square size that fits in those areas
local squareSize = math.min(availableWidth / numCols, availableHeight / numRows)
--use intialization function to make squares using those margins
upperSquares = initializeSquares(numRows, numCols, squareSize, HEIGHT / 2 + margin / 2, margin)
lowerSquares = initializeSquares(numRows, numCols, squareSize, margin / 2, margin)
end
I think the commenting makes most things clear except for this: why mix colors using HSB not RBG?
RGB has a fatal flaw in color-mixing:over time all colors become gray. I believe this is a known problem.
So it has to be HSB. For simplicity, in this code I am only randomizing the H part. The results are consistent when S and B are also randomized, but there’s no need for that here.
It seems like the problem has to be in the colorToHSB
and hsbToColor
functions, but I can’t find it. Help! Anyone?