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. |
# 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
, Comet
LoG
, DoG
,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)
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
, Palatino
or 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")