library(magick)
library(here)
library(gifski)I use a visual hindsight bias demonstration in class where students watch a blurry image gradually come into focus and try to identify who it is. After the reveal, I ask when they first recognized the person. Most students are convinced they knew way earlier than they actually did—which is a nice setup for talking about hindsight bias and why it matters for things like eyewitness memory and legal decision-making.
I wanted a quick way to generate these stimuli in R rather than fiddling around in Photoshop, so I put together a couple of functions using the magick package. Most of this is adapted from a Stack Overflow answer I found when I was trying to figure out how to make the GIF.
The Packages
Nothing fancy here—magick does the image processing, here keeps the file paths sane, and gifski compiles the frames into a GIF.
The Functions
A series of stills
This first function takes an image and creates a series of progressively less blurry versions. The blur starts at maximum and decreases linearly, so the first image is almost unrecognizable and the last one is the original.
create_blurred <- function(image_path, output_dir, prefix = "blurred", num_images = 6) {
original <- image_read(image_path)
max_blur <- 99.9999
for (i in 1:num_images) {
blur_amount <- max_blur * (1 - (i - 1) / (num_images - 1))
blurred <- image_blur(original, radius = blur_amount, sigma = blur_amount / 2)
image_write(blurred, path = file.path(output_dir, sprintf("%s_%d.png", prefix, i)))
}
}An animated GIF
If you want to compile everything into a GIF for presentations, this version handles both steps. I use more frames here (30 instead of 6) so the animation feels smoother.
create_blurred_and_gif <- function(image_path, output_dir, prefix = "blurred",
num_images = 30, width = 800, height = 600, delay = 0.5) {
original <- image_read(image_path)
max_blur <- 99.9999
png_paths <- vector("character", num_images)
for (i in 1:num_images) {
blur_amount <- max_blur * (1 - (i - 1) / (num_images - 1))
blurred <- image_blur(original, radius = blur_amount, sigma = blur_amount / 2)
png_path <- file.path(output_dir, sprintf("%s_%02d.png", prefix, i))
png_paths[i] <- png_path
image_write(blurred, path = png_path)
}
gif_path <- file.path(output_dir, sprintf("%s_images.gif", prefix))
gifski(png_files = png_paths, gif_file = gif_path, width = width, height = height, delay = delay)
return(gif_path)
}Generating the Stimuli
Set eval: true the first time you render to create the images, then set it back to eval: false so they don’t get regenerated on every render.
output_dir <- here("posts", "hindsight-visual")
# Meryl Streep - 6 stills for the grid, 30 frames for the GIF
create_blurred(
image_path = here("posts", "hindsight-visual", "meryl_streep.png"),
output_dir = output_dir,
prefix = "meryl",
num_images = 6
)
create_blurred_and_gif(
image_path = here("posts", "hindsight-visual", "meryl_streep.png"),
output_dir = output_dir,
prefix = "meryl",
num_images = 30,
width = 800,
height = 600,
delay = 0.5
)
# Jeff Probst - slower reveal, smaller dimensions
create_blurred(
image_path = here("posts", "hindsight-visual", "jeff_probst.png"),
output_dir = output_dir,
prefix = "jeff",
num_images = 6
)
create_blurred_and_gif(
image_path = here("posts", "hindsight-visual", "jeff_probst.png"),
output_dir = output_dir,
prefix = "jeff",
num_images = 40,
width = 600,
height = 450,
delay = 0.7
)What You Get
Meryl Streep
Here’s the progression as a series of stills:






And the animated version:

Jeff Probst
Same idea, but with a slower reveal:







How I Use This
I can run the visual hindsight bias demonstration using the embedded GIF and create new ones to keep up with the kids as my pop cultural references age.