Monday, July 16, 2012

Optical Art with R

Last week, in a post entitled Bridget Riley exhibition in London, the author Markus Gesmann wrote an R script reproducing one of Riley's famous art pieces: Movement in Squares.

This reminded me of my own first "brush" with Op art. It was in art class years ago, our professor (Madame Vitré) had asked that we recreate an interesting piece. The concept in itself was easy: pick two points and draw a number of lines through each of them. Where they intersect, the two sets of lines create a multitude of polygons that can be filled in black or white alternatively, for quite a dramatic result.

As you can imagine, it was tedious work on paper. Years later, I'd get a thrill at doing it in just a couple minutes using Paint on Windows. Well, here I am again in 2012, now a grown-up programmer:

optical.art <- function(P, N, colors = brewer.pal(P, "Greys")) {
## This function creates an Op art figure as can be found on
## http://r-de-jeu.blogspot.com/
##
## Inputs:
## - P: (integer) number of starting points
## - N: (integer) number of semi-lines starting from each point.
## For best results, N should be a multiple of P
## - colors: vector of colors for filling polygons.
## For best results, you should use P colors
require(sp)
require(rgeos)
require(RColorBrewer)
# plot area
plot(c(0, 1), c(0, 1), "n", axes=FALSE, xlab="", ylab="")
create.polygon <- function(x, y) {
pol <- Polygon(cbind(c(x, head(x, 1)), c(y, head(y, 1))))
set <- Polygons(list(pol), "pol")
spP <- SpatialPolygons(list(set))
}
plot.area <- create.polygon(x = c(0,1,1,0), y = c(0,0,1,1))
polygons <- vector("list", length = P)
for (p in 1:P) {
# randomly select the coordinates of a point and the
# angles of N semi-lines starting from that point
center.x <- runif(1)
center.y <- runif(1)
angles <- 2*pi/N*(0:(N-1) + 2*pi*runif(1))
# the semi-lines and frame will define N polygons
polygons[[p]] <- vector("list", length = N)
for (i in 1:N) {
i1 <- i
i2 <- ifelse((i + 1) > N, 1, i + 1)
a1 <- angles[i1]
a2 <- angles[i2]
polygons[[p]][[i]] <-
create.polygon(x = center.x + c(0, 2*cos(a1), 2*cos(a2), 0),
y = center.y + c(0, 2*sin(a1), 2*sin(a2), 0))
}
}
all.comb <- expand.grid(data.frame(replicate(P, 1:N)))
for (k in 1:nrow(all.comb)) {
# compute the intersection of the polygons
poly <- plot.area
for (i in 1:P) {
poly <- gIntersection(poly, polygons[[i]][[all.comb[k,i]]])
if (gIsEmpty(poly)) break
}
if (gIsEmpty(poly)) next
# plot the polygon and fill it with the appropriate color
coords <- poly@polygons[[1]]@Polygons[[1]]@coords
polygon(coords[, 1], coords[, 2], border = NA,
col = colors[1 + (sum(all.comb[k,]) %% length(colors))])
}
}
view raw optical.art.R hosted with ❤ by GitHub


The example above was run using P = 2, N = 30, and colors = c("black", "white"). And here is a nice series using three points and colors from the brewer palette:
P = 3, N = 12,
colors = brewer.pal(3, "Reds")
P = 3, N = 15,
colors = brewer.pal(3, "Purples")
P = 3, N = 15,
colors = brewer.pal(3, "Blues")
P = 3, N = 9,
colors = brewer.pal(3, "Greens")

Madame Vitré would be proud. (or horrified?)

4 comments:

  1. Cool post, and a great reminder that sp* packages, functions, and classes can be used for all kind of fun this. Reminds me of some stuff I posted years ago:

    http://casoilresource.lawr.ucdavis.edu/drupal/node/878

    http://casoilresource.lawr.ucdavis.edu/drupal/node/838

    ReplyDelete
  2. You Rtist. Love your post!!!

    ReplyDelete
  3. Very nice. Makes me wonder how mind-bending it would be to do something similar for a self-intersecting closed curve (maybe parameterised as some kind of 2D spline with random knots)

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete