Unpredictable paintings

Danielle Navarro

First I was an academic…

Then I was a data scientist…

Now I am a pharmacometrician…

Academic work looked like this

Data science work was like this

Pharmacometric work is like this

But this is a talk about art

An art talk is a different kind of thing

Art is personal






Art is political

Art is emotional

Art is theft

Art is theft

Art from data

Not traditionally considered “generative” art, but it’s where the story begins for a lot of us who work in the data trades

Making art from data

mpg
# A tibble: 234 × 11
   manufacturer model  displ  year   cyl trans drv     cty   hwy fl   
   <chr>        <chr>  <dbl> <int> <int> <chr> <chr> <int> <int> <chr>
 1 audi         a4       1.8  1999     4 auto… f        18    29 p    
 2 audi         a4       1.8  1999     4 manu… f        21    29 p    
 3 audi         a4       2    2008     4 manu… f        20    31 p    
 4 audi         a4       2    2008     4 auto… f        21    30 p    
 5 audi         a4       2.8  1999     6 auto… f        16    26 p    
 6 audi         a4       2.8  1999     6 manu… f        18    26 p    
 7 audi         a4       3.1  2008     6 auto… f        18    27 p    
 8 audi         a4 qu…   1.8  1999     4 manu… 4        18    26 p    
 9 audi         a4 qu…   1.8  1999     4 auto… 4        16    25 p    
10 audi         a4 qu…   2    2008     4 manu… 4        20    28 p    
# ℹ 224 more rows
# ℹ 1 more variable: class <chr>

Making art from data

ggplot(
  data = mpg, 
  mapping = aes(
    x = displ, 
    y = hwy
  )
) + 
  geom_point() + 
  theme_bw() + 
  labs(
    x = "Engine displacement",
    y = "Highway mileage"
  )

Making art from data

A plot

A plot with… alterations?

Art?

Making art from data

blog.djnavarro.net/crayola-crayon-colours

Making art from data

blog.djnavarro.net/crayola-crayon-colours

crayola |> 
  mutate(
    color = fct_reorder(color, hue2)
  ) |> 
  ggplot(aes(
    x = year,
    group = color,
    fill = color
  )) +
  scale_fill_identity() +
  geom_bar(show.legend = FALSE) 

Art from noise

The prototypical generative art workflow

Step 1: Generate random data…

set.seed(1)
n <- 1000
dat <- tibble(
  x0 = runif(n),
  y0 = runif(n),
  x1 = x0 + runif(n, min = -.2, max = .2),
  y1 = y0 + runif(n, min = -.2, max = .2),
  shade = runif(n), 
  size = runif(n)
)

Step 1: Generate random data…

dat
# A tibble: 1,000 × 6
       x0     y0     x1    y1  shade   size
    <dbl>  <dbl>  <dbl> <dbl>  <dbl>  <dbl>
 1 0.266  0.531  0.414  0.652 0.188  0.0355
 2 0.372  0.685  0.559  0.677 0.505  0.356 
 3 0.573  0.383  0.720  0.254 0.0273 0.249 
 4 0.908  0.955  0.883  0.914 0.496  0.879 
 5 0.202  0.118  0.0785 0.244 0.947  0.318 
 6 0.898  0.0391 0.731  0.216 0.381  0.321 
 7 0.945  0.505  0.978  0.374 0.698  0.848 
 8 0.661  0.578  0.489  0.520 0.689  0.381 
 9 0.629  0.839  0.640  0.921 0.478  0.275 
10 0.0618 0.654  0.0507 0.838 0.273  0.918 
# ℹ 990 more rows

Step 2: Plot the random data…

pic <- dat |> 
  ggplot(aes(
    x = x0,
    y = y0,
    xend = x1,
    yend = y1,
    colour = shade,
    size = size
  )) +
  geom_segment(
    show.legend = FALSE
  ) +
  theme_void()

pic

Step 3: Play around…

pic + 
  scale_size(
    range = c(0, 1)
  )

Step 3: Play around…

pic + 
  scale_size(
    range = c(0, 1)
  ) + 
  scale_color_distiller()

Step 3: Play around…

pic + 
  scale_size(
    range = c(0, 1)
  ) + 
  scale_color_distiller() +
  scale_y_continuous(
    limits = c(0, .5), 
    oob = scales::oob_keep
  ) +
  coord_polar(clip = "off")

Step 4: Pretend it was planned

But… where does this jump happen?

Smoke and mirrors

A large part of generative art is trickery.

Spatial noise generators

You can generate Perlin noise easily in R

This piece is just a fancy version of that

Spatial noise generators

Ultimately, this is just rectangular data

You can manipulate it like any other data

Data wrangling tools as paint brushes

blank_canvas |> mutate(
  lf_noise = gen_simplex(x, y, frequency = 1, seed = 1234),
  mf_noise = gen_simplex(x, y, frequency = 20, seed = 1234),
  hf_noise = gen_simplex(x, y, frequency = 99, seed = 1234),
  paint = lf_noise + mf_noise + hf_noise
)

Adding a sense of flow

Flow fields are a generative art cliché, but a fun one

Generative artists love fields

Perlin noise as surface

Slope of the surface

Curl of the surface

A little grid of flowing particles

A fancier grid of flowing particles

Flow fields to make hearts bleed

Flow fields to make hearts bleed

Using brute force to make soft shapes

Distort polygons randomly…

Overlay semi-transparent copies…

And you get ominous clouds

Or smudged hexagons

How to make a machine draw wonky hearts

Making blobs from circles

tibble(
  angle = seq(0, 2*pi, length.out = n),
  x_base = cos(angle),
  y_base = sin(angle),
  radius = fracture(
    x = x_base, 
    y = y_base, 
    freq_init = freq_init,
    noise = gen_perlin, 
    fractal = fbm, 
    octaves = octaves
  ) |> normalise_radius(r_min, r_max),
  x = radius * x_base,
  y = radius * y_base
)

Making “hand-drawn” hearts

heart_x <- function(angle) {
  x <- (16 * sin(angle) ^ 3) / 17
  return(x - mean(x))
}

heart_y <- function(angle) {
  y <- (13 * cos(angle) - 
  5 * cos(2 * angle) - 
  2 * cos(3 * angle) -
  cos(4 * angle)) / 17
  return(y - mean(y))
}

A grid of wonky hearts

Using geospatial tools to create alien worlds

Rayshading tools for landscapes

Using rayshader as intended

circle_shadow <- ray_shade(
  heightmap = circle_array,
  sunaltitude = 15, 
  sunangle = 135,
  zscale = .01,
  multicore = TRUE
)

plot_map(circle_shadow, rotate = 270)

Using rayshader as intended

circle_scape <- circle_array |> 
  height_shade() |>
  add_shadow(
    shadowmap = circle_shadow,
    max_darken = .1
  )

plot_map(circle_scape, rotate = 270)

Shading impossible landscapes

Shading impossible landscapes

Shading impossible landscapes

Iterated function systems

Repeatedly transform a line…

Et voilà!

It’s just like drawing an owl

Hm, let’s try that again, shall we?

A little more about how the “rest of the owl” happens

A little more about how the “rest of the owl” happens

fractal_art(ridged, gen_simplex, seed = 2, octaves = 1)
fractal_art(ridged, gen_simplex, seed = 2, octaves = 2)
fractal_art(ridged, gen_simplex, seed = 2, octaves = 20)

The “unboxing” example I use in generative art workshops

The emotion in the art

Nowhere is the emotion more obvious than looking at the variation in what you can do with a few minor edits to an iterative function system

Anger

Gentleness

Style

Disguising the generative process

Sometimes the artistry is in the deception

Art from Voronoi tesselation

A random Voronoi tesselation

With some nicer colours

And a maximum radius

Art from Voronoi tesselation

Voronoise

Sadists Kiss

The deception revealed…

The art is the artist

And so we come full circle. Generative art is not different to other art forms. The craft is the craft. And the artist is the art.

The artist is sometimes obvious

Sometimes she is not

But she is always there.

Thank you ❤️