Fibonacci Slideshow: an experiment with recursion

Another simple piece of code that is related to my experiments with the golden angle and ratio, here and here. (Update) An example of its output is below:

--
-- Fibonacci Slideshow 
--

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)
function setup()
    maxN = 14
    noSmooth()
    rectMode(CORNERS)
    myRect = Oblong()
    sTime = ElapsedTime
    local w, h = 21 + 3 + 2 + 8 + 55, 34 + 5 + 3 + 13
    zoom = math.min(WIDTH * 0.95 / w, HEIGHT * 0.95 / h)
    cx = (WIDTH - w * zoom) / 2 + (21 + 3) * zoom
    cy = (HEIGHT - h * zoom) / 2 + (34 + 5) * zoom
end

function draw()
    if ElapsedTime - sTime > 1 and myRect.box.n < maxN then
        myRect = Oblong(myRect)
        sTime = ElapsedTime
    end
    background(0)
    myRect:draw(zoom, vec2(cx, cy), - ElapsedTime * 30)
end

-- Hue ([0, 1)) to RGB; (S = 1, V = 1)
function H2RGB(h)
    local r, g, b = 0, 0, 0
    local i = h * 6
    local x = (1 - math.abs(i % 2 - 1))
    if i < 1 then r, g = 1, x
    elseif i < 2 then r, g = x, 1
    elseif i < 3 then g, b = 1, x
    elseif i < 4 then g, b = x, 1
    elseif i < 5 then r, b = x, 1
    else r, b = 1, x end
    return color(255 * r, 255 * g, 255 * b)
end

Box = class()

function Box:init(size, orient, origin)
    self.size = size
    self.orient = orient
    self.origin = origin
end

function Box:draw(zoom, centre, rotation)
    local orients = {vec2(0, 1), vec2(-1, 0), vec2(0, -1), vec2(1, 0)}
    local origin = self.origin * zoom
    local orient = self.orient
    local size = self.size * zoom
    local nextOrient = orient % 4 + 1
    local opp = origin + (orients[orient] - orients[nextOrient]) * size
    pushMatrix()
    resetMatrix()
    translate(centre.x, centre.y)
    rotate(rotation)
    noFill()
    strokeWidth(math.max(3, zoom / 20))
    stroke(H2RGB(self.n / maxN))
    rect(origin.x, origin.y, opp.x, opp.y)
    stroke(255)
    strokeWidth(3)
    local p0 = opp - orients[orient] * size
    for i = 1, 20 do
        local a = math.pi / 2 * i / 20
        p1 = opp - (orients[orient] * math.cos(a) 
            - orients[nextOrient] * math.sin(a)) * size
        line(p0.x, p0.y, p1.x, p1.y)
        p0 = p1
    end
    popMatrix()
end

Oblong = class()
    
function Oblong:init(oblong)
    local orients = {vec2(0, 1), vec2(-1, 0), vec2(0, -1), vec2(1, 0)}
    self.oblong = oblong
    if not oblong then
        self.box = Box(1, 1, vec2())
        self.box.n = 1
        self.orient = self.box.orient
        self.origin = self.box.origin
        self.size = vec2(self.box.size, self.box.size)
    else
        local orient = oblong.orient % 4 + 1
        local size = oblong.size
        local origin = oblong.origin
        local minSize = size.x
        local maxSize = size.y
        local nextOrigin = origin
            - orients[orient] * (minSize + maxSize)
        self.box = Box(maxSize, orient, nextOrigin)
        self.box.n = oblong.box.n + 1
        self.orient = orient
        self.origin = nextOrigin
        self.size = vec2(maxSize, minSize + maxSize)   
    end
end

function Oblong:draw(zoom, centre, rotation)
    self.box:draw(zoom, centre, rotation)
    if self.oblong then
        self.oblong:draw(zoom, centre, rotation)
    end
end

```

Thanks @mpilgrem. Bye the way, I like your process for the H2RGB function. Yet, I’ve only seen you convert the Hue when S and V remain equal to one. Do you have a function (or can you give me some guidance) to convert HSV to RGB when H, S and V are all different values?

Hello @Ric_Esrey. My experiments with an ideal ‘spectrum of attractive colours’ function are not yet complete. H2RGB(h) is overweight on deep blues and bright greens and underweight on purples, light blues, yellows and oranges.

I’ve not tested it myself but, following the Wikipedia article on HSV, how about:


--
-- HSV to RGB
--
-- Assumes that h is [0, 1), not [0, 360)
function HSV2RGB(h, s, v)
    if not h then return color(0) end
    local r, g, b = 0, 0, 0
    local c = s * v
    local i = h * 6
    local x = c * (1 - math.abs(i % 2 - 1))
    if i < 1 then r, g = c, x
    elseif i < 2 then r, g = x, c
    elseif i < 3 then g, b = c, x
    elseif i < 4 then g, b = x, c
    elseif i < 5 then r, b = x, c
    else r, b = c, x end
    local m = v - c
    r, g, b = r + m, g + m, b + m
    return color(255 * r, 255 * g, 255 * b)
end

Thanks @mpilgrem. I’ll have to try out your conversion routine. I’m also experimenting with my version of a 'spectrum of attractive colors." What I really want to do is to make color mixing more natural. For instance primary colors should be red, yellow, and blue. When I mix red and blue, the result should be purple; red and yellow should make orange; yellow and blue should make green. Secondary colors should be purple, orange, and green. Mixing any color (for instance Red) with its complement (for instance Green) should make grey. So far, no drawing program I’ve used (not even Photoshop) mixes colors naturally. As a traditionally trained artist, I have to use a combination of Normal, Multiply, and Overlay layers in order to recreate art in the digital environment. Having to remember which type of layer to use is unnatural and slows down the creative process.

So, I’m experimenting with a way to simulate the mixing of real colors. Here are the two aspects of color mixing that I’m trying to replicate:

  1. There is a difference between emitted color and reflected color. Emitted color (i.e. coming from the sun, a light bulb, or a computer screen) uses equal mixtures of the primaries–Red, Green, and Blue–to make the color White. An absence of these colors makes Black. Reflected color (i.e. from a sofa material or from the pigments in paint) uses equal measures of its primaries–red, yellow, and blue–to make the color Black. An absence of these colors defaults to the color of the canvas (usually white). This works because, for instance, the fabric on a red sofa is not really red (represented as RGB(255, 0, 0)). It is a material that absorbs the green and blue frequencies and reflects the red (really it is RGB(0, 255, 255)). So, to properly mix the colors, we should be able to take the complement of each of two colors; then add them together; then take the complement of the result to get a proper looking mixed color.

  2. The above procedure works fairly well in that when you mix any color with White (which is RGB(255, 255, 255)) the result is that hue with a lighter tint. When you mix any color with Black (which is RGB(0, 0, 0)) the result is that hue tinted darker. Unfortunately, it doesn’t allow other realistic color mixing ( i.e. yellow + blue = green). That’s because our eyes percieve the Reds, the Greens, and the Blues differently. For instance, a normal person percieves green spectrum colors the best. Only about 75% of the energy in the red frequencies is seen and only about 25% for the blue frequencies. So, we have too many red ‘bins’ in our RGB model and way too many blue ‘bins.’ This makes Cyan the complement of Red when we expect Green to be the Red’s complement.

The code below uses a combination of changing the emitted colors into reflected colors before mixing and also changes the number of bins for each color so that colors are mixed more naturally. I’m still playing with it because it’s not exactly right yet. For instance, when I mix Black (RGB(0, 0, 0)) with Black (RGB(0, 0, 0)) I actually get a dark green instead of just Black. But, try it and see what you think. Note: The first circle is Color 1, the second circle is Color 2, and the third circle is the result of mixing the two colors. Colors 1 and 2 can be adjusted using the iparameter bars. Also, I’m NOT a programmer–I’m an artist. Therefore, my code will surely make the elite coders cringe. I apologize. It’s the best I can do. Due to length, the code is in the next post.

-- COLORS
-- Use this function to perform your initial setup
function setup()
    iparameter("Startarr1_Red", 0, 255, 0)
    iparameter("Startarr1_Green", 0, 255, 0)
    iparameter("Startarr1_Blue", 0, 255, 0)
    iparameter("Startarr2_Red", 0, 255, 0)
    iparameter("Startarr2_Green", 0, 255, 200)
    iparameter("Startarr2_Blue", 0, 255, 75)
    arr1 = {r, g, b}
    arr2 = {r, g, b}

end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
  background(40, 40, 50)
    Startarr1 = {r=Startarr1_Red, g=Startarr1_Green, b=Startarr1_Blue}
    arr1["r"] = Startarr1_Red * 0.8
    arr1["g"] = Startarr1_Green
    arr1["b"] = Startarr1_Blue * 0.25
    Startarr2 = {r=Startarr2_Red, g=Startarr2_Green, b=Startarr2_Blue}
    arr2["r"] = Startarr2_Red * 0.8
    arr2["g"] = Startarr2_Green
    arr2["b"] = Startarr2_Blue * 0.25
    Startarr = {r=(Startarr1_Red+Startarr2_Red)/2, g=(Startarr1_Green+Startarr2_Green)/2, b=(Startarr1_Blue+Startarr2_Blue)/2}
    pushMatrix()
    translate(WIDTH/2-100, HEIGHT/2+300)
    font("Georgia")
    fill(255)
    fontSize(20)
    textWrapWidth(300)
    local startcolor = string.format("start color1 = %d, %d, %d",arr1["r"], arr1["g"], arr1["b"])
    text(startcolor)
    translate(0, -50)
    local startcolor2 = string.format("start color2 = %d, %d, %d",arr2["r"], arr2["g"], arr2["b"])
    text(startcolor2)
    popMatrix()
    
    arr3 = {r=0, g=0, b=0}
   
    arr3 = addColors(arr1, arr2)
    pushMatrix()
    translate(WIDTH/2-100, HEIGHT/2)
    local screen= string.format("Screen Intense = %d, %d, %d",arr3["r"], arr3["g"], arr3["b"])
    text(screen)
    popMatrix()
      
  
    fill(arr3["r"], arr3["g"], arr3["b"])
    ellipse(382, 200, 100)
    
    strokeWidth(5)
    
    fill(Startarr1_Red, Startarr1_Green, Startarr1_Blue)
     ellipse(100, 200, 100)
    
    fill(Startarr2_Red, Startarr2_Green, Startarr2_Blue)
        ellipse(249, 200, 100)
    -- Do your drawing here

end

function addColors(col1, col2)
    pushMatrix()
    font("Georgia")
    fill(255)
    fontSize(20)
    textWrapWidth(300)
    
    translate(WIDTH/2, HEIGHT/2+200)
--Complement the colors
    col1 = complement(col1)
    local complement1 = string.format("Complement1 = %d, %d, %d",col1["r"], col1["g"], col1["b"])
    text(complement1)
    col2 = complement(col2)
    translate(0, -20)
    local complement2 = string.format("Complement2 = %d, %d, %d",col2["r"], col2["g"], col2["b"])
    text(complement2)
 
--Make the intensities equal   
    translate(0, -20)
    local col1= equalIntensity(col1)
    local EqualIntense = string.format("IntensEqual1= %d, %d, %d",col1["r"], col1["g"], col1["b"])
    text(EqualIntense)
    local col2 = equalIntensity(col2)
    translate(0, -20)
    local EqualIntense = string.format("IntensEqual2= %d, %d, %d",col2["r"], col2["g"], col2["b"])
    text(EqualIntense)
--Add the colors together
    
        arr3["r"] = (col1["r"] + col2["r"]) / 2
        arr3["g"] = (col1["g"] + col2["g"]) / 2
        arr3["b"] =  (col1["b"]+ arr2["b"]) / 2

    translate(-100, -20)
    local combined = string.format("Combined color= %d, %d, %d",arr3["r"], arr3["g"], arr3["b"])
    text(combined)

--Complement the Result
    arr3= complement(arr3)
    translate(0, -50)
    local final = string.format("Complement color = %d, %d, %d",arr3["r"], arr3["g"], arr3["b"])
    text(final)
    popMatrix()
    return(screenIntensity(arr3))
end

function equalIntensity(arr)
    
    
    --Ths clamps the minimum value of the output color. Division gives negative values.
    --Top values are 255.  The commented lines below were just samples I tried.
   -- arr["r"] = (arr["r"]+(arr["r"]*(3/4)))/2

    -- arr["g"] = (arr["g"]+(arr["g"]*(.2)))/2
    

   -- arr["b"] = (arr["b"]+(arr["b"]/(1/4)))/2
   -- arr["r"] = (arr["r"]+(arr["r"]*(3/4)))/2
   -- arr["g"] = (arr["g"]+(arr["g"]*(1)))/2
    
   -- arr["b"] = (arr["b"]+(arr["b"]*(1/4)))/2
    
    return(arr)
end

--This function clamps the maximum values.  Division goes above 255.
--Bottom value is 0 unless constant is added. Then min is the constant.

function screenIntensity(arr)
  
    arr["r"] = ((Startarr["r"]+(arr["r"]/(.75)))/2)+10
    arr["g"] = ((Startarr["g"]+(arr["g"]/(.6)))/2)+20
    arr["b"] = ((Startarr["b"]+(arr["b"]/(1/4)))/2)
    return(arr)

end

function complement(arr)
    arr["r"] = 255 - arr["r"]
    arr["g"] = 255 - arr["g"]
    arr["b"] = 255 - arr["b"]
    return (arr)
end