Skip to content

A toy R wrapper for 'rust-skia'

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE
MIT
LICENSE.md
Notifications You must be signed in to change notification settings

paithiov909/skiagd

Repository files navigation

skiagd

rust-skia-version R-CMD-check DeepWiki

skiagd is an experimental drawing library for R that wraps rust-skia (the Rust crate skia_safe, a binding for Skia).

Installation

Due to the limitation of the upstream prebuilt binaries, it is available only for Linux and macOS. To build from source, it requires freetype, fontconfig, and the Rust toolchains.

remotes::install_github("paithiov909/skiagd")

Examples

Overview

skiagd takes a pipeline‑oriented philosophy similar to ggplot2 but directs it at pure graphics rather than data visualisation. In a skiagd pipeline, there are three main steps to draw an image:

  1. Create a canvas with a background colour. The call canvas() returns a new picture object that represents the drawing state.
  2. Compose shapes by piping calls to add_*() functions. Each add_*() call takes the current picture, adds shapes to it, and returns a new picture. The picture object always remains a recipe of drawing commands, no pixels are produced at this stage.
  3. Render the picture. To convert a picture into pixels you can call…
    1. draw_img() to draw to the current graphics device
    2. as_nativeraster() to obtain a nativeRaster
    3. as_png() to obtain a PNG as a raw vector

Despite its name, skiagd presents itself as a toy R wrapper for rust-skia and does not behave like a graphics device. The package is meant to draw images independently of R’s graphics device system.

Painting Attributes

Skia exposes a rich set of painting attributes that control how shapes appear when drawn. In skiagd, you can specify these attributes via the paint() function and pass the resulting object to the props argument of add_*(). Unspecified attributes inherit defaults, many of which follow the current graphics device’s settings.

The following example illustrates a basic drawing pipeline.

library(skiagd)

rad <- \(deg) deg * (pi / 180)

# Canvas size in pixels (from the current graphics device)
cv_size <- dev_size()

# Generate coordinates for a rose curve
rose <-
  dplyr::tibble(
    i = seq_len(360),
    r = 120 * abs(sin(rad(4 * i)))
  ) |>
  dplyr::reframe(
    x = r * cos(rad(360 * i / 360)) + cv_size[1] / 2,
    y = r * sin(rad(360 * i / 360)) + cv_size[2] / 2,
    z = 1
  )

canvas("violetred") |>
  add_point(
    as.matrix(rose),
    props = paint(
      color = "white",
      width = 3,
      point_mode = PointMode$Polygon,
    )
  ) |>
  draw_img()

Here we first generate coordinates for a simple rose curve and then draw it with a single call to add_point(). We supply the points as a matrix and specify painting attributes via paint(). Here we set the colour to "white", the line width to 3 pixels and use PointMode$Polygon to connect successive points. And finally, we call draw_img() to render the picture on the current graphics device.

Showcase

The following example, inspired by this blog post, demonstrates how skiagd can be used to create a more practical artwork.

cv_size <- dev_size()
cv_size
#> [1] 768 576

n_frames <- 720
n_circles <- 50
radius <- runif(n_circles, min = .25, max = 2) |> sort()
trans <- matrix(c(60, 0, cv_size[1] / 2, 0, 60, cv_size[2] / 2, 0, 0, 1), ncol = 3)

circle <- \(amp, freq, phase) {
  amp * 1i^(freq * seq(0, 600, length.out = n_circles) + phase)
}

dir <- tempdir()
imgs <- purrr::imap_chr(seq(0, 4 * pi, length.out = n_frames + 1)[-1], \(a, i) {
  # Compute a stack of circles with changing amplitudes and phases
  l <- sin(pi * (2 * a - .5)) + 1
  z <- circle(pi / 6, -pi, 0) +
    circle(l, ceiling(a), -9 * cos(a) + 1) +
    circle(l / 2 - 1, ceiling((-a + (7 / 2)) %% 7) - 7, -7 * cos(a) + 1)

  hue <- (a + (Re(z / pi))) %% 1
  colours <- grDevices::hsv(hue, .66, .75, alpha = 1)

  # Build one frame
  png <- canvas("#04010F") |>
    add_circle(
      cbind(Re(z), Im(z), 1) %*% trans,
      radius = log(max(cv_size), exp(.5)) * radius,
      color = col2rgba(colours),
      props = paint(
        style = Style$Fill,
        blend_mode = BlendMode$Plus,
      )
    ) |>
    as_png()

  fp <- file.path(dir, sprintf("%04d.png", i))
  writeBin(png, fp)
  fp
})

The animation consists of multiple rotating circles whose radii and phases change over time. You don’t need to understand the details of the trigonometry here, the key takeaway is that you can compute complex coordinates in R, pipe them into add_circle(), and then assemble the frames into a GIF using gifski.

# Combine frames into a GIF (30 fps)
gifski::gifski(
  imgs,
  "mystery-circles.gif",
  width  = cv_size[1],
  height = cv_size[2],
  delay  = 1 / 30
)

The frames in the GIF look like this:

Further Readings

License

MIT License.

About

A toy R wrapper for 'rust-skia'

Topics

Resources

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE
MIT
LICENSE.md

Stars

Watchers

Forks