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
# essential
library(tidyverse)
# magick
library(magick)
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.
| 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. |
file_format_chart.png
# 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 = "")
# 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)
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")
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:
A: width pixelsB: height pixelsC: x offset by pixelsD: y offset by pixelsMethod:
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 imageimage_rotate rotates and increases size of canvas to fit rotated image.image_deskew auto rotate to correct skewed imagesimage_resize resizes using custom filterTypeimage_scale and image_sample resize using simple ratio and pixel sampling algorithm.image_flip and image_flop invert image vertically and horizontallylogo <- 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)
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)
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
Blur, CometLoG, DoG,Sobel, Roberts, Prewitt, Compass, Kirsch, FreiChenlogo <- 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)
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)
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")
The standard base methods [ [[, c() and length() are used to manipulate vectors of images which can then be treated as layers or frames. Method:
image_animate coalesces frames by playing the sequence and converting to gif format.image_morph expands number of frames by interpolating intermediate frames to blend into each other when played as an animation.image_mosaic inlays images to form a single coherent picture.image_montage creates a composite image by combining frames.mage_flatten merges frames as layers into a single frame using a given operator.image_average averages frames into single frame.image_append stack images left-to-right (default) or top-to-bottom.image_apply applies a function to each frame# 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, ...)
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)
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")
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
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.
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")