Use of this document

This is a study note for using \(magick\) package to modernize and simplify high-quality image processing. For more details on the study material see https://cran.r-project.org/web/packages/magick/vignettes/intro.html.

Acknowledgement: Special thank Qi He, my colleague for the idea of Pickles in the final example

Prerequisites

# essential
library(tidyverse)
# magick
library(magick)

1. Graphic foramat

The 95VISUAL's blog provides a great comparision between various graphic formats. Within the scope of this blog, the following three graphic formats are discussed.

Summary of Comparision between Graphic foramat
Criterion Portable Network Graphics (png) Scalable Vector Graphics (svg) Portable Document File (pdf)
Image quality Since it is a raster image format, so it made up of a fixed number of pixels that form a complete image. The image cannot be enlarged without distortion occurring An SVG image can be compressed or stretched without loss of image quality. A vector image remains crisp and clear at any resolution or size It supports both raster and vector image format.
Web pages compatibility Yes, the PNG format is suitable for web images like logos that you want to include transparency and fading effects Yes, but not support for transparency Yes, but not support for transparency
Storage size Because of the size of a PNG file, this format is not recommended for photos as JPG is unless file size is not an issue. SVG image element files are smaller than if the image were present in a raster format, but if an object in the image contains many small elements, the size of the file can grow very fast the file sizes are usually smaller than if you saved a document in its native format including its graphic files.
A cross-check table for comparision from the 95VISUAL is attached at below.
file_format_chart.png

file_format_chart.png

2. Image I/O

2.1 Input

# not running
image_read(path, density = NULL, depth = NULL, strip = FALSE)
image_read_svg(path, width = NULL, height = NULL)
image_read_pdf(path, pages = NULL, density = 300, password = "")

2.2 Output

# not running
image_write(image, path = NULL, format = NULL, quality = NULL,
  depth = NULL, density = NULL, comment = NULL, flatten = FALSE)
# not running
image_convert(image, format = NULL, type = NULL, colorspace = NULL,
  depth = NULL, antialias = NULL)

2.3 Example

tiger <- image_read_svg('http://jeroen.github.io/images/tiger.svg', width = 400)
image_write(tiger, path = "tiger.png", format = "png")
# Convert to png
tiger_png <- image_convert(tiger, "png")

3. Transformations

3.1 Geometry parameter

Several of the transformation functions take an geometry parameter which requires a special syntax of the form AxB+C+D where each element is optional. Some examples:

  • image_crop(image, “100x150+50”): crop out width:100px and height:150px starting +50px from the left
    • A: width pixels
    • B: height pixels
    • C: x offset by pixels
    • D: y offset by pixels
  • image_scale(image, “200”): resize proportionally to width: 200px
  • image_scale(image, “x200”): resize proportionally to height: 200px
  • image_fill(image, “blue”, “+100+200”): flood fill with blue starting at the point at x:100, y:200
  • image_border(frink, “red”, “20x10”): adds a border of 20px left+right and 10px top+bottom

3.2 Cut and edit

Method:

  • image_trim removes edges that are the background color from the image.
  • image_chop removes vertical or horizontal subregion of image.
  • image_crop cuts out a subregion of original image
  • image_rotate rotates and increases size of canvas to fit rotated image.
  • image_deskew auto rotate to correct skewed images
  • image_resize resizes using custom filterType
  • image_scale and image_sample resize using simple ratio and pixel sampling algorithm.
  • image_flip and image_flop invert image vertically and horizontally
logo <- image_read("logo:")
logo <- image_scale(logo, "400")
logo.1 <- image_trim(logo)
logo.2 <- image_chop(logo, "100x20")
logo.3 <- image_rotate(logo, 45)

logo.4 <- image_crop(logo, "400x400+200+200")
logo.5 <- image_flip(logo)
logo.6 <- image_flop(logo)
if(magick_config()$version > "6.8.6")
  logo.7 <- image_orient(logo)

par(mar = rep(0, 4))
par(mfrow = c(2, 4))
plot(logo)
plot(logo.1)
plot(logo.2)
plot(logo.3)
plot(logo.4)
plot(logo.5)
plot(logo.6)
plot(logo.7)

# Small image
rose <- image_convert(image_read("rose:"), "png")
# Resize to 400 width or height:
rose.1 <- image_resize(rose, "400x")
rose.2 <- image_resize(rose, "x400")
# Resize keeping ratio
rose.3 <- image_resize(rose, "400x400")
# Resize, force size losing ratio
rose.4 <- image_resize(rose, "400x400!")
# Different filters
rose.5 <- image_resize(rose, "400x", filter = "Triangle")
rose.6 <- image_resize(rose, "400x", filter = "Point")
# simple pixel resize
rose.7 <- image_scale(rose, "400x")
rose.8 <- image_sample(rose, "400x")

par(mar = rep(0, 4))
par(mfrow = c(3, 3))
plot(rose)
plot(rose.1)
plot(rose.2)
plot(rose.3)
plot(rose.4)
plot(rose.5)
plot(rose.6)
plot(rose.7)
plot(rose.8)

3.3 Effect

logo <- image_read("logo:")
logo.1 <- image_despeckle(logo)
logo.2 <- image_reducenoise(logo)
logo.3 <- image_noise(logo)
logo.4 <- image_blur(logo, 10, 10)
logo.5 <- image_charcoal(logo)
logo.6 <- image_oilpaint(logo, radius = 3)
logo.7 <- image_emboss(logo)
logo.8 <- image_implode(logo)
logo.9 <- image_negate(logo)

par(mar = rep(0, 4))
par(mfrow = c(2, 5))
plot(logo)
plot(logo.1)
plot(logo.2)
plot(logo.3)
plot(logo.4)
plot(logo.5)
plot(logo.6)
plot(logo.7)
plot(logo.8)
plot(logo.9)

3.4 Kernel convolution

The image_convolve() function applies a kernel over the image. Kernel convolution means that each pixel value is recalculated using the weighted neighborhood sum defined in the kernel matrix.

use any of the standard kernels

  • Blurring Images (Low-Pass Filtering)
    • Blur, Comet
  • Edge Detection Convolutions (High-Pass Filtering)
    • LoG, DoG,
  • Directional Convolutions (Slopes, Compass Filtering)
    • Sobel, Roberts, Prewitt, Compass, Kirsch, FreiChen
logo <- image_read("logo:")
img <- image_resize(logo, "300x300")
img.1 <- img %>% image_convolve('Sobel') %>% image_negate()
img.2 <- img %>% image_convolve('DoG:0,0,2') %>% image_negate()

par(mar = rep(0, 4))
par(mfrow = c(1, 2))
plot(img.1)
plot(img.2)

3.5 Edge

Best results are obtained by finding edges with image_canny() and then performing Hough-line detection on the edge image.

# not running
image_edge(image, radius = 1)
image_canny(image, geometry = "0x1+10%+30%")
image_hough_draw(image, geometry = NULL, color = "red",
  bg = "transparent", size = 3, overlay = FALSE)
image_hough_txt(image, geometry = NULL, format = c("mvg", "svg"))
if(magick_config()$version > "6.8.9"){
shape <- demo_image("shape_rectangle.gif")
rectangle <- image_canny(shape)
rectangle.1 <- rectangle %>% image_hough_draw('5x5+20')
#rectangle.2 <- rectangle %>% image_hough_txt(format = 'svg') %>% cat()
}
par(mar = rep(0, 4))
par(mfrow = c(1, 2))
plot(rectangle)
plot(rectangle.1)

3.6 Text Annotation

Find more font style form the Google Fonts. Fonts that are supported on most platforms include:

  • sans, mono, serif, Times, Helvetica, Trebuchet, Georgia, Palatinoor Comic Sans.
library(sysfonts)
font <- "Roboto"
font_info_google(font)
frink <- image_read("https://jeroen.github.io/images/frink.png")
# Fonts may require ImageMagick has fontconfig
image_annotate(frink, "The quick brown fox", font = font, size = 30,degrees = 60, location = "+50+100")

4. Image Vectors

The standard base methods [ [[, c() and length() are used to manipulate vectors of images which can then be treated as layers or frames. Method:

# not running
image_animate(image, fps = 10, loop = 0, dispose = c("background", "previous", "none"))
image_morph(image, frames = 8)
image_mosaic(image, operator = NULL)
image_montage(image)
image_flatten(image, operator = NULL)
image_average(image)
image_append(image, stack = FALSE)
image_apply(image, FUN, ...)

4.1 Combining

bigdata <- image_read('https://jeroen.github.io/images/bigdata.jpg')
frink <- image_read("https://jeroen.github.io/images/frink.png")
logo <- image_read("https://jeroen.github.io/images/Rlogo.png")
img <- c(bigdata, logo, frink)
img <- image_scale(img, "300x300")
image_info(img)
## # A tibble: 3 x 7
##   format width height colorspace matte filesize density
##   <chr>  <int>  <int> <chr>      <lgl>    <int> <chr>  
## 1 JPEG     300    225 sRGB       FALSE        0 72x72  
## 2 PNG      300    232 sRGB       TRUE         0 72x72  
## 3 PNG      148    300 sRGB       TRUE         0 72x72
img.1 <- image_append(image_scale(img, "100"), stack = TRUE)
img.2 <- image_append(image_scale(img, "x200"))
bigdatafrink <- image_scale(image_rotate(image_background(frink, "none"), 300), "x200")
img.3 <- image_composite(image_scale(bigdata, "x400"), bigdatafrink, offset = "+180+100")

par(mar = rep(0, 4))
par(mfrow = c(1, 3))
plot(img.1)
plot(img.2)
plot(img.3)

4.2 Animation

banana <- image_read("https://jeroen.github.io/images/banana.gif")
banana <- image_scale(banana, "150")
image_info(banana)
## # A tibble: 8 x 7
##   format width height colorspace matte filesize density
##   <chr>  <int>  <int> <chr>      <lgl>    <int> <chr>  
## 1 GIF      150    148 sRGB       TRUE         0 72x72  
## 2 GIF      150    148 sRGB       TRUE         0 72x72  
## 3 GIF      150    148 sRGB       TRUE         0 72x72  
## 4 GIF      150    148 sRGB       TRUE         0 72x72  
## 5 GIF      150    148 sRGB       TRUE         0 72x72  
## 6 GIF      150    148 sRGB       TRUE         0 72x72  
## 7 GIF      150    148 sRGB       TRUE         0 72x72  
## 8 GIF      150    148 sRGB       TRUE         0 72x72
image_animate(banana, fps = 10)

manual <- image_read_pdf('https://cloud.r-project.org/web/packages/magick/magick.pdf', density = 72)
image_animate(image_scale(manual, "200x200"), fps = 1, dispose = "previous")

4.3 Drawing and Graphics

library(gapminder)
library(ggplot2)
img <- image_graph(600, 340, res = 96)
datalist <- split(gapminder, gapminder$year)
out <- lapply(datalist, function(data){
  p <- ggplot(data, aes(gdpPercap, lifeExp, size = pop, color = continent)) +
    scale_size("population", limits = range(gapminder$pop)) + geom_point() + ylim(20, 90) + 
    scale_x_log10(limits = range(gapminder$gdpPercap)) + ggtitle(data$year) + theme_classic()
  print(p)
})
dev.off()
## png 
##   2
animation <- image_animate(img, fps = 2)
print(animation)
## # A tibble: 12 x 7
##    format width height colorspace matte filesize density
##    <chr>  <int>  <int> <chr>      <lgl>    <int> <chr>  
##  1 gif      600    340 sRGB       TRUE         0 72x72  
##  2 gif      600    340 sRGB       TRUE         0 72x72  
##  3 gif      600    340 sRGB       TRUE         0 72x72  
##  4 gif      600    340 sRGB       TRUE         0 72x72  
##  5 gif      600    340 sRGB       TRUE         0 72x72  
##  6 gif      600    340 sRGB       TRUE         0 72x72  
##  7 gif      600    340 sRGB       TRUE         0 72x72  
##  8 gif      600    340 sRGB       TRUE         0 72x72  
##  9 gif      600    340 sRGB       TRUE         0 72x72  
## 10 gif      600    340 sRGB       TRUE         0 72x72  
## 11 gif      600    340 sRGB       TRUE         0 72x72  
## 12 gif      600    340 sRGB       TRUE         0 72x72

5. OCR text extraction

img <- image_read("http://jeroen.github.io/images/testocr.png")
plot(img)

# Extract text
cat(image_ocr(img))
## First use of Tesseract: copying language data...
## This is a lot of 12 point text to test the
## ocr code and see if it works on all types
## of file format.
## 
## The quick brown dog jumped over the
## lazy fox. The quick brown dog jumped
## over the lazy fox. The quick brown dog
## jumped over the lazy fox. The quick
## brown dog jumped over the lazy fox.

6. Example

library(magick)
library(hexSticker)
pickle<-image_read("https://vignette.wikia.nocookie.net/rickandmorty/images/1/19/Pickle_rick_transparent.png")
cats<-image_read("https://ih0.redbubble.net/image.378606813.7322/raf,750x1000,075,t,101010:01c5ca27c6.jpg")
cats <- image_convert(cats, "png")

sticker(image_blank(518, 600, color="black"),
        package="tmp", 
        h_fill = "black", 
        p_color= "black",
        h_color = "#87B13F")
hexground <- image_read("tmp.png"); hexground

w <- image_info(hexground)$width
h <- image_info(hexground)$height

hexground <- image_flatten(c(image_blank(w,h, color="red"),hexground)); hexground

hexground <- hexground %>% 
  image_transparent("black") %>% 
  image_transparent("#87B13F"); hexground

cates <- cats %>% 
  image_scale("518x") %>% 
  image_crop("518x600+0+0");cates

catsground <- image_flatten(c(cates,hexground)) %>%
  image_transparent("red"); catsground

pickle <- pickle %>% 
  image_scale("300x") %>% 
  image_fill("black","+1+1") %>% 
  image_transparent("black");pickle

hexpickle <- image_composite(catsground,pickle,
                             offset = paste0("+",(518-300)/2,"+",(600-397)/2)); hexpickle

sticker(hexpickle, package = "Pickle",
        p_size = 30, p_y = 0.6, p_color = "grey40",
        s_x = 1, s_y = 1, s_width = 2, s_height = 2,
        h_fill = "gray95", h_color = "aquamarine4", h_size = 1.8)
image_read("Pickle.png")