Coloring SVGs in R

Jun 17, 2022


Beyond spatial transcriptomics analysis, coding is a useful skill for lots of other fun things too! So just for fun, in this blog post, I use R to recolor an SVG to make some funny colored hot dog dogs.


Getting Started

Let’s first read in an SVG image into R using svgparser. For the SVG image we’re using, I simplified an SVG image by catalyststuff just so that we can have fewer paths to deal with (more on paths later).

hotdogdog.svg


svgparser parses the SVG elements into grid graphical objects, aka “grobs”. So we can use grid to display the original SVG image as a grob.

library(grid)
library(svgparser)

## read in file
file <- 'hotdogdog.svg'
img_grob <- svgparser::read_svg(file)

## show original
grid::grid.newpage()
grid::grid.draw(img_grob)

What is an SVG?

SVGs, or Scalable Vector Graphics, are an XML-based markup language for describing 2D vector graphics. Vector graphics is the set of mechanisms for creating visual images directly from geometric shapes.

If we look at the SVG image using a text editor, we can see that it is just a series of geometric paths defined by sets of 2D points with colors set by hexadecimal fills.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   version="1.1"
   baseProfile="full"
   xmlns="http://www.w3.org/2000/svg"
   width="5333.3335"
   height="5333.3335"
   viewBox="0 0 5333.3335 5333.3335"
   >
   <path
   d="M 0,5333.3333 H 5333.3332 V 1e-4 H 0 Z"
   style="fill:#4db7ff;"
   id="path14" /><path
   d="m 1766.2,3819.6667 c -71.5467,4.1466 -121.4534,7.24 -121.4534,7.24 -25.28,103.3866 -13.7866,298.68 165.4134,399.76 119.6933,67.52 275.1866,44.7866 383.7866,-17.4267 -264.7733,94.8667 -530.4,-136.0267 -427.7466,-389.5733"
   style="fill:#ffffff;"
   id="path18" /><path

We can see these paths read in as individual grobs in R as well.

## get elements
ls <- grid::grid.ls()
print(head(ls))
names <- ls$name[grepl('pathgrob', ls$name)]
print(head(names))
[1] "GRID.pathgrob.4257" "GRID.pathgrob.4260" "GRID.pathgrob.4263" "GRID.pathgrob.4266" "GRID.pathgrob.4270"
[6] "GRID.pathgrob.4273"
## check one
name <- names[1]
ngrob <- grid.get(gPath(name))
print(names(ngrob))
print(names(ngrob$gp))
print(ngrob$gp$fill )
 [1] "x"              "y"              "id"             "id.lengths"     "pathId"         "pathId.lengths" "rule"       
 [8] "name"           "gp"             "vp"            
 [1] "col"        "fill"       "alpha"      "lwd"        "lineend"    "linejoin"   "linemitre"  "fontsize"   "cex"     
[10] "fontface"   "fontfamily" "font"      
[1] "#4DB7FFFF"

Recoloring our SVG

To recolor our SVG, we should be able to just change the path fill colors to something new. So let’s first grab the current fill colors used by these paths.

## grab original fill colors
fills <- sapply(names, function(name) {
  ngrob <- grid.get(gPath(name))
  ngrob$gp$fill 
})
ncols <- length(unique(fills))

table(fills)
fills
#000000FF #4D4D9DFF #4DB7FFFF #CA7B34FF #CA9368FF #CC721DFF #CCB97DFF #CCCCE3FF #ED1821FF #FC63A2FF #FC9A24FF #FDB866FF 
       38         2         1         1         2         1         1         1         1        11         9         1 
#FFCE00FF #FFFFFFFF 
        1        13 

We can see some paths share the same color. For example, there are 38 paths that are black aka #000000FF. In our recoloring, we will keep this pattern for now. So if two paths previously shared the same color, in the recoloring, they will still share the same color. We will sample from a random palette of 20 rainbow colors for our new colors. I will maintain the black outlines though so the paths previously colored black will remain black just for aesthetic purposes.

## recolor
set.seed(100)
rand_colors <- sample(rainbow(20),ncols)
new_colors <- as.factor(fills)
levels(new_colors) = rand_colors
new_colors <- as.character(new_colors)
new_colors[fills == "#000000FF"] <- "#000000FF" ## maintain black outline
names(new_colors) <- names(fills)
table(new_colors)
new_colors
#000000FF   #001AFF   #0066FF   #00B3FF   #00FF19   #00FFFF   #3300FF   #33FF00   #7F00FF   #80FF00   #CC00FF   #FF0099 
       38         1         9         2        13         1        11         1         1         2         1         1 
  #FF4D00   #FFE500 
        1         1 

Now we can recolor our paths by setting their fills to their new random colors and then redrawing them!

## draw with new colors
sapply(names, function(name) {
  ngrob <- grid::grid.get(gPath(name))
  ngrob$gp$fill <- new_colors[name]
  grid::grid.draw(ngrob)
})

Try it out for yourself!

  • Try just using any random colors rather than having paths that previously used the same colors continue to share colors.
  • Can you identify the paths corresponding to the mustard swirl? What we if also randomly toggle these paths on and off?
  • Find your own SVG image and try it out for yourself