Getting Started with Odiffr

Introduction

Odiffr provides R bindings to Odiff, a blazing-fast pixel-by-pixel image comparison tool. It’s designed for:

System Requirements

Odiffr requires the Odiff binary to be installed on your system:

# npm (cross-platform, recommended)
npm install -g odiff-bin

# Or download binaries from GitHub releases
# https://github.com/dmtrKovalenko/odiff/releases

If you cannot install Odiff system-wide, use odiffr_update() after installing the package to download a binary to your user cache.

Installation

# From CRAN (when available)
install.packages("odiffr")

# Development version
pak::pak("BenWolst/odiffr")

Basic Usage

library(odiffr)

Check Configuration

# Verify Odiff is available
odiff_available()
#> [1] TRUE

# View configuration details
odiff_info()
#> odiffr configuration
#> --------------------
#> OS:       darwin 
#> Arch:     arm64 
#> Path:     /opt/homebrew/lib/node_modules/odiff-bin/raw_binaries/odiff-macos-arm64 
#> Version:  <unknown> 
#> Source:   system

Compare Images

The main function is compare_images(), which returns a tibble (or data.frame):

result <- compare_images("baseline.png", "current.png")
result
#> # A tibble: 1 × 7
#>   match reason     diff_count diff_percentage diff_output img1         img2
#>   <lgl> <chr>           <int>           <dbl> <chr>       <chr>        <chr>
#> 1 FALSE pixel-diff       1234            2.45 NA          baseline.png current.png

Generate Diff Images

# Specify output path
result <- compare_images("baseline.png", "current.png",
                         diff_output = "diff.png")

# Or use TRUE for auto-generated temp file
result <- compare_images("baseline.png", "current.png",
                         diff_output = TRUE)
result$diff_output
#> [1] "/tmp/RtmpXXXXXX/file12345.png"

Advanced Options

Threshold

The threshold parameter (0-1) controls color sensitivity. Lower values are more precise:

# Very strict comparison
result <- compare_images("img1.png", "img2.png", threshold = 0.01)

# More lenient (ignore minor color variations)
result <- compare_images("img1.png", "img2.png", threshold = 0.2)

Antialiasing

Ignore antialiased pixels that often differ between renders:

result <- compare_images("img1.png", "img2.png", antialiasing = TRUE)

Ignore Regions

Exclude specific areas from comparison (useful for timestamps, dynamic content):

result <- compare_images("img1.png", "img2.png",
  ignore_regions = list(
    ignore_region(x1 = 0, y1 = 0, x2 = 200, y2 = 50),    # Header
    ignore_region(x1 = 0, y1 = 900, x2 = 1920, y2 = 1080) # Footer
  )
)

Batch Processing

Compare multiple image pairs efficiently:

pairs <- data.frame(
  img1 = c("baseline/page1.png", "baseline/page2.png", "baseline/page3.png"),
  img2 = c("current/page1.png", "current/page2.png", "current/page3.png")
)

results <- compare_images_batch(pairs, diff_dir = "diffs/")

# View failures
results[!results$match, ]

Working with magick

Odiffr integrates with the magick package for preprocessing:

library(magick)

# Read and preprocess images
img1 <- image_read("baseline.png") |>
  image_resize("800x600") |>
  image_convert(colorspace = "sRGB")

img2 <- image_read("current.png") |>
  image_resize("800x600") |>
  image_convert(colorspace = "sRGB")

# Compare directly
result <- compare_images(img1, img2)

Low-Level API

For full control, use odiff_run():

result <- odiff_run(
  img1 = "baseline.png",
  img2 = "current.png",
  diff_output = "diff.png",
  threshold = 0.1,
  antialiasing = TRUE,
  fail_on_layout = TRUE,
  diff_mask = FALSE,
  diff_overlay = 0.5,
  diff_color = "#FF00FF",
  diff_lines = TRUE,
  reduce_ram = FALSE,
  ignore_regions = list(ignore_region(10, 10, 100, 50)),
  timeout = 60
)

# Detailed result
result$match
result$reason
result$diff_count
result$diff_percentage
result$diff_lines
result$exit_code
result$duration

Binary Management

Update Binary

Download the latest Odiff binary to your user cache:

# Latest version
odiffr_update()

# Specific version
odiffr_update(version = "v4.1.2")

Custom Binary Path

Use a specific binary (useful for validated environments):

options(odiffr.path = "/validated/bin/odiff-4.1.2")

Cache Management

# View cache location
odiffr_cache_path()
#> [1] "/Users/benwolstenholme/Library/Caches/org.R-project.R/R/odiffr"
# Clear cached binaries
odiffr_clear_cache()

Visual Regression Testing

Example integration with testthat:

library(testthat)
library(odiffr)

test_that("dashboard renders correctly", {
  # Generate current screenshot
  webshot2::webshot("http://localhost:3838/dashboard", "current.png")

  # Compare to baseline
  result <- compare_images(
    "baselines/dashboard.png",
    "current.png",
    diff_output = "diffs/dashboard_diff.png",
    threshold = 0.1,
    antialiasing = TRUE
  )

  expect_true(result$match,
    info = sprintf("%.2f%% pixels differ", result$diff_percentage))
})

For Validated Environments

Odiffr is designed for validated pharmaceutical/clinical research:

  1. Pinnable: Lock to a specific validated binary with options(odiffr.path = ...)
  2. Auditable: Use odiff_version() to document binary version for audit trails
  3. Base R core: Zero external runtime dependencies for core functions
# Pin to a specific validated binary
options(odiffr.path = "/validated/bin/odiff-4.1.2")

# Document version for validation
info <- odiff_info()
sprintf("Using odiff %s from %s", info$version, info$source)