Stylo Doodles

Candles

Details

Author:

Alex Carney

Github:

alcarney

Dimensions:

1080 x 1080

Stylo Version:

0.9.0
Show Source Show Notes Show Both

Source

import numpy as np
import numpy.random as npr
from math import pi

from stylo.domain import get_real_domain
from stylo.domain.transform import translate, rotate

from stylo.color import FillColor
from stylo.color.colormap import ColorMap
from stylo.shape import Shape, Circle, Ellipse
from stylo.image import LayeredImage
from stylo.math import lerp
darkred = FillColor("aa0000")
red = FillColor("dd0000")
orange = FillColor("FFA500")
white = FillColor("ffffff")
class Halo(ColorMap):
    
    def __init__(self, rate, r=0, g=0, b=0, dy=0.8, dx=0):
        self.rate = rate
        self.r = r
        self.g = g
        self.b = b
        self.dy = dy
        self.dx = dx
            
    def gradient(self, x, y):
        r = np.sqrt(x*x + y*y)
        
        b = (1 - r / self.rate) * self.b // 1
        g = (1 - r / self.rate) * self.g // 1
        red = (1 - r / self.rate) * self.r // 1
        
        return np.dstack([red, g, b])
        
    
    def _paint(self, shape, image_data):
        
        w, h, d = image_data.shape
        domain = get_real_domain(w, h)
        
        x, y = domain['xy'](w, h)
        colors = self.gradient(x - self.dx, y - self.dy)
        image_data[shape] = colors[shape]
        
        return image_data
    
class Shine(ColorMap):
        
    def __init__(self, rate, r=0, g=0, b=0, dy=0.8, dx=0):
        self.rate = rate
        self.r = r
        self.g = g
        self.b = b
        self.dy = dy
        self.dx = dx
            
    def gradient(self, x, y):
        r = np.sqrt(x*x + y*y)
        
        b = (1 - r / self.rate*0.5) * self.b // 1
        g = (1 - r / self.rate) * self.g // 1
        red = (1 - r / self.rate) * self.r // 1
        
        return np.dstack([red, g, b])
        
    
    def _paint(self, shape, image_data):
        
        w, h, d = image_data.shape
        domain = get_real_domain(w, h)
        
        x, y = domain['xy'](w, h)
        colors = self.gradient(x - self.dx, y - self.dy)
        image_data[shape] = colors[shape]
        
        return image_data
    

class Fill(Shape):
    def draw(self):
        def fill(x):
            return True
        
        return fill
    
halo = Halo(rate=2.5, g=255)
flame_yellow = Halo(rate=1, r=255, g=255, dy=0.65)
flame_yellow2 = Halo(rate=1, r=255, g=255, dy=0.27, dx=0.37)
flame_yellow3 = Halo(rate=1, r=255, g=255, dy=-0.42, dx=0.47)

bauble_blue = Shine(rate=0.6, r=64, g=64, b=255, dx=0.15, dy=-0.3)

fill = Fill()
class Candle(Shape):
    
    def __init__(self, cutout=True, scale=1):
        self.cutout = cutout
        self.scale = scale
    
    def draw(self):
        
        curve = Circle(x=0, y=0, r=0.45, fill=True)
        
        def candle(x, y):
            x = x / self.scale
            y = y / self.scale
            
            sides = np.logical_and(np.abs(x) < 0.1, np.abs(y) < 0.5)
            base = np.logical_and(sides, curve(x=x, y=y))
            
            if not self.cutout:
                return base
            
            cutout = np.logical_not(curve(x=x, y=y-0.875))            
            return np.logical_and(cutout, base)
            
        return candle
class Flame(Shape):
    
    def __init__(self, scale=1, angle=-pi/12):
        self.scale = scale
        self.angle = angle
    
    def draw(self):
        
        base = Ellipse(r=0.1, a=1, b=2,fill=True)
        
        def flame(x, y):
            
            x = x / self.scale
            y = y / self.scale
            
            rx = np.cos(self.angle)*x - np.sin(self.angle)*y
            ry = np.sin(self.angle)*x + np.cos(self.angle)*y
            
            fire = base(x=rx, y=ry)
            cutout = rx*4 > 0.2*np.sin(ry*pi*4)
            
            return np.logical_and(fire, cutout)
            
        return flame
candle_bg = Candle(cutout=False, scale=1.5)
candle = Candle(scale=1.5)

candle2_bg = Candle(cutout=False, scale=1.25) >> translate(-0.35, -0.2)
candle2 = Candle(scale=1.25) >> translate(-0.35, -0.2)

candle3_bg = Candle(cutout=False, scale=1) >> translate(0.35, -0.3)
candle3 = Candle(scale=1) >> translate(0.35, -0.3)

flame = Flame() >> translate(-0.05, 0.85)
flame2 = Flame(scale=0.75, angle=pi/12) >> translate(0.37, 0.27)
flame3 = Flame(scale=0.8, angle=-pi/6) >> translate(-0.42, 0.47)

bauble = Circle(x=0.3, y=-0.6, r=0.3, fill=True)
image = LayeredImage(background="004400", scale=2.3)
image.add_layer(fill, halo)

image.add_layer(candle_bg, darkred)
image.add_layer(candle, red)
image.add_layer(flame, flame_yellow)

image.add_layer(candle2_bg, darkred)
image.add_layer(candle2, red)
image.add_layer(flame3, flame_yellow3)

image.add_layer(candle3_bg, darkred)
image.add_layer(candle3, red)
image.add_layer(flame2, flame_yellow2)

image.add_layer(bauble, bauble_blue)

npr.seed(256)

rs = lerp(0.03, 0.28)
ts = lerp(-pi, pi)

for s, t in npr.rand(100, 2):
    r, theta = rs(s), ts(t)
    x, y = r*np.cos(theta), r*np.sin(theta)
    
    dot = Circle(x=(0.3 + x), y=(y - 0.6), fill=True, r=0.005)
    image.add_layer(dot, white)