Vertical lines of color fall softly, resembling the delicate, shimmering appearance of light rain.

This piece was inspired by Marius Watz’s Blocker series. The code draws semi-transparent rectangles with colors and positions determined by Perlin noise. The final color palette is a blend from separate noise patterns.

library(ambient)
library(viridis)

# create a blank canvas
width <- 2000
height <- 1000
img1 <- matrix(0, nrow = height, ncol = width)
img2 <- matrix(0, nrow = height, ncol = width)

# generate perlin noise
noise <- noise_perlin(c(height, width), frequency = 0.05)

# function to draw a random vertical rectangle with a specified color map
draw_rectangle <- function(img1, img2, noise) {
    
    # generate random position, width, and height
    x <- sample(1:width, 1)
    w <- sample(1:10, 1)
    
    y <- round(sample(1:height, 1) + rnorm(1, sd=5)) + 1
    h <- sample(1:150, 1)
    
    x0 <- max(1, x-w)
    x1 <- min(x+w, width)    
    y0 <- max(1, y-h)
    y1 <- min(y+h, height)
    
    # get the average noise value within the rectangle
    noise_value <- mean(noise[y0:y1, x0:x1])
    
    # map the noise value to a color index
    color_index <- round(noise_value * 255) + 200
    
    # calculate the opacity based on the area (smaller rectangles have higher opacity)
    if (runif(1) > 0.1)
        opacity <- 1 - exp(-(h + w) / 100)
    else
        opacity <- exp(-(h + w) / 1000)
    
    # create a semi-transparent rectangle with the corresponding color
    rect <- opacity * matrix(color_index, nrow=y1-y0+1, ncol=x1-x0+1)
    
    # randomly select the color map for the rectangle
    if (runif(1) < 0.5) {
        img1[y0:y1, x0:x1] <- (1 - opacity) * img1[y0:y1, x0:x1] + opacity * rect
    } else {
        img2[y0:y1, x0:x1] <- (1 - opacity) * img2[y0:y1, x0:x1] + opacity * rect
    }
    
    return(list(img1, img2))
}

# generate the images by drawing multiple rectangles
set.seed(123)
for (i in 1:750) {
    result <- draw_rectangle(img1, img2, noise)
    img1 <- result[[1]]
    img2 <- result[[2]]
}

for (palettes in list(c(turbo, mako), c(viridis, inferno), c(mako, viridis))) {

    # apply the first color palette to img1
    img_color1 <- col2rgb(palettes[[1]](256)[round(img1) + 1]) / 255
    dim(img_color1) <- c(3, dim(img1))
    
    # apply the second color palette to img2
    img_color2 <- col2rgb(palettes[[2]](256)[round(img2) + 1]) / 255
    dim(img_color2) <- c(3, dim(img2))
    
    # combine the two color-mapped images
    blend_weight <- 0.5
    img_blended <- pmin(blend_weight * img_color1 + (1 - blend_weight) * img_color2, 1.0)
    img_blended <- round(img_blended/0.2)*0.2
    
    # display the combined image
    par(mar = c(0, 0, 0, 0), oma=c(0, 0, 0, 0))
    plot(0, 0, type = "n", xlim = c(0, 1), ylim = c(0, 1), xaxt = "n", yaxt = "n", ann=F, axes=F)
    rasterImage(aperm(img_blended, perm=c(2, 3, 1)), 0, 0, 1, 1)    
}