DRAWING PORTRAITS WITH TEXT
PUBLISHED BY GIORA SIMCHONI
Recently I’ve seen some interesting posts showing how to make ASCII art in R (see here and here). Why boundary ourselves to ASCII, I thought. Lincoln’s portrait could live drawn alongside the Gettysberg Address instead of commas together with semicolons. And Trump’s portrait actually deserves his tweetshttp://giorasimchoni.com/2017/07/09/2017-07-09-read-my-face/
DRAWING PORTRAITS WITH TEXT
PUBLISHED BY GIORA SIMCHONI
Now let’s instruct the Gettysberg Address from here using the
rvest
package:library(rvest) text <- read_html("http://avalon.law.yale.edu/19th_century/gettyb.asp") %>% html_nodes("p") %>% html_text() text
## [1] "\"Fourscore together with 7 years agone our fathers brought forth on this continent a novel nation, conceived inwards liberty together with dedicated to the proffer that all men are created equal. Now nosotros are engaged inwards a bang-up civil war, testing whether that land or whatever land together with then conceived together with and then dedicated tin long endure. We are met on a bang-up battlefield of that war. We direct keep come upward to dedicate a component of that patch every bit a in conclusion resting-place for those who hither gave their lives that that land mightiness live. It is altogether plumbing fixtures together with proper that nosotros should practise this. But inwards a larger sense, nosotros cannot dedicate, nosotros cannot consecrate, nosotros cannot hallow this ground. The brave men, living together with dead who struggled hither direct keep consecrated it far inwards a higher house our pitiful ability to add together or detract. The public volition piffling complaint nor long cry back what nosotros state here, but it tin never forget what they did here. It is for us the living rather to live dedicated hither to the unfinished piece of work which they who fought hither direct keep hence far together with then nobly advanced. It is rather for us to live hither dedicated to the bang-up line of piece of work remaining earlier us--that from these honored dead nosotros accept increased devotion to that drive for which they gave the in conclusion total mensurate of devotion--that nosotros hither highly resolve that these dead shall non direct keep died inwards vain, that this land nether God shall direct keep a novel nascence of freedom, together with that authorities of the people, past times the people, for the people shall non perish from the earth.\" "
Now let’s convert Lincoln’s icon into a 500 x 700 (transposed) matrix. It is right away inwards grayscale mode, together with then at that topographic point is exclusively a unmarried color channel, alongside values randing from 0 to 1:
imgGSMat <- img %>% as.matrix() %>% t() dim(imgGSMat)
## [1] 500 700
summary(c(imgGSMat))
## Min. 1st Qu. Median Mean 3rd Qu. Max. ## 0.03137 0.09804 0.60000 0.46649 0.80784 0.99608
Let’s suppose the portion we’ll impress the text in, is where the pixels value is lower (= darker) than a sure enough threshold, state 0.5, together with let’s plot this portion to meet it makes sense:
plot(as.cimg(imgGSMat > 0.5))
Don’t worry close the icon existence rotated, this volition piece of work out eventually. For right away I simply wanted to brand sure enough the “dark” portion alongside our chosen threshold looks OK, together with it appears so.
To fill upward the nighttime portion alongside the Gettysberg Address let’s split upward the text into characters. Then, we’ll loop over the entire matrix together with text, together with when we’re inwards the “dark” region, we’ll plot the electrical flow text character. We’ll work the
grid
package for plotting.library(grid) text <- str_split(text, "")[[1]] grid.newpage() counter <- 0 for (i inwards seq(1, nrow(imgGSMat), 13)) { for (j inwards seq(1, ncol(imgGSMat), 5)) { if (imgGSMat[i, j] < 0.5) { counter <- ifelse(counter < length(text), counter + 1, 1) grid.text(text[counter], x = j / ncol(imgGSMat), y = 1 - i / nrow(imgGSMat), gp = gpar(fontsize = 10), simply = "left") } } }
This is a expert start, but a failure nonetheless. H5N1 few things to notice:
- We’re non actually looping through the entire matrix of pixels. Since the font size is currently 10, we’re looping through rows (image height) alongside the
i
variable inwards steps of xiii pixels. - We’re looping through columns (image width) alongside the
j
variable in fixed steps of five - which is clearly a problem, since there’s a variablity inwards the characters width. - Once the text is over nosotros start from the beginning.
- The
grid.text
function does non desire ouri
andj
as pixels, but every bit fractions from the image’s superlative together with width respectively.
So the principal number hither is the supposition of fixed width for all letters. Let’s alter that alongside distinguishing betwixt “fat” together with “skinny” letters (the residue volition live “regular”):
fatChars <- c(LETTERS[-which(LETTERS == "I")], "m", "w", "@") skinnyChars <- c("l", "I", "i", "t", "'", "f") grid.newpage() counter <- 0 for (i in seq(1, nrow(imgGSMat), 13)) { for (j in seq(1, ncol(imgGSMat), 10)) { if (imgGSMat[i, j] < 0.5) { counter <- ifelse(counter < length(text), counter + 1, 1) beforeLastChar <- ifelse(counter > 2, lastChar, " ") lastChar <- ifelse(counter > 1, char, " ") char <- text[counter] grid.text(char, x = j/ncol(imgGSMat) + 0.004 * (lastChar %in% fatChars) - 0.003 * (lastChar %in% skinnyChars) + 0.003 * (beforeLastChar %in% fatChars) - 0.002 * (beforeLastChar %in% skinnyChars), y = 1 - i / nrow(imgGSMat), gp = gpar(fontsize = 10), simply = "left") } } }
Looks much improve although the “algorithm” is somewhat dirty.
Let’s set it inwards a role anyway, making the threshold, font size together with another parameters configurable:
drawImageWithText <- function(img, text, thresh, fontSize = 10, fileName = "myfile.png", resize = TRUE, saveToDisk = FALSE) { text <- paste(text, collapse = " ") text <- str_replace_all(text, "\n+", " ") text <- str_replace_all(text, " +", " ") text <- str_split(text, "")[[1]] if (resize) img <- resize(img, 700, 500) imgGSMat <- img %>% grayscale %>% as.matrix %>% t() fatChars <- c(LETTERS[-which(LETTERS == "I")], "m", "w", "@") skinnyChars <- c("l", "I", "i", "t", "'", "f") if (saveToDisk) png(fileName, width(img), height(img)) grid.newpage() counter <- 0 for (i in seq(1, nrow(imgGSMat) - fontSize, fontSize + floor(fontSize / 3))) { for (j in seq(1, ncol(imgGSMat) - fontSize, fontSize)) { if (imgGSMat[i, j] < thresh) { counter <- ifelse(counter < length(text), counter + 1, 1) beforeLastChar <- ifelse(counter > 2, lastChar, " ") lastChar <- ifelse(counter > 1, char, " ") char <- text[counter] grid.text(char, x = 0.01 + j/ncol(imgGSMat) + 0.004 * (lastChar %in% fatChars) - 0.003 * (lastChar %in% skinnyChars) + 0.003 * (beforeLastChar %in% fatChars) - 0.002 * (beforeLastChar %in% skinnyChars), y = 1 - i / nrow(imgGSMat) - 0.01, gp = gpar(fontsize = fontSize), simply = "left") } } } if (saveToDisk) dev.off() }
Martin Luther King Jr./I Have H5N1 Dream
Let’s examine our role on Martin Luther King Jr. together with his iconic speech communication taken from here:
img <- load.image(" /mlkj.jpg") text <- read_lines(" /ihaveadream.txt") drawImageWithText(img, text, thresh = 0.3, fontSize = 5)
Free, at last.
Marylin Monroe/Her Wikipedia Article
img <- load.image(" /marylin.jpg") text <- read_html("https://en.wikipedia.org/wiki/Marilyn_Monroe") %>% html_nodes("p") %>% html_text() %>% str_replace_all(., "\\[[0-9]+\\]", "") drawImageWithText(img, text, thresh = 0.5, fontSize = 8)
Adele/Rolling In The Deep
img <- load.image(" /adele.jpg") text <- read_lines(" /rollinginthedeep.txt") drawImageWithText(img, text, thresh = 0.5, fontSize = 10)
Hadley Wickham/The dplyr code
img <- load.image(" /hadley.jpg") text <- read_html("https://github.com/tidyverse/dplyr/blob/master/src/mutate.cpp") %>% html_nodes("div") %>% .[[47]] %>% html_text() drawImageWithText(img, text, thresh = 0.75, fontSize = 5)
This instance alongside Hadley is a fleck different, because hither the original icon isn’t dark together with white. It has colors, together with nosotros could work some color, together with then let’s alter the role a bit:
drawImageWithText <- function(img, text, thresh, color = FALSE, fontSize = 10, fileName = "myfile.png", resize = TRUE, saveToDisk = FALSE) { if (color) { if (spectrum(img) == 1) { warning("Image is inwards grayscale mode, setting color to FALSE.") color = FALSE } } text <- paste(text, collapse = " ") text <- str_replace_all(text, "\n+", " ") text <- str_replace_all(text, " +", " ") text <- str_split(text, "")[[1]] if (resize) img <- resize(img, 700, 500) imgMat <- img %>% as.array() %>% adrop(3) %>% aperm(c(2, 1, 3)) imgGSMat <- img %>% grayscale %>% as.matrix %>% t() fatChars <- c(LETTERS[-which(LETTERS == "I")], "m", "w", "@") skinnyChars <- c("l", "I", "i", "t", "'", "f") if (saveToDisk) png(fileName, width(img), height(img)) grid.newpage() counter <- 0 for (i in seq(1, nrow(imgGSMat) - fontSize, fontSize + 1)) { for (j in seq(1, ncol(imgGSMat) - fontSize, fontSize)) { if (imgGSMat[i, j] < thresh) { counter <- ifelse(counter < length(text), counter + 1, 1) beforeLastChar <- ifelse(counter > 2, lastChar, " ") lastChar <- ifelse(counter > 1, char, " ") char <- text[counter] grid.text(char, x = 0.01 + j/ncol(imgGSMat) + 0.004 * (lastChar %in% fatChars) - 0.003 * (lastChar %in% skinnyChars) + 0.003 * (beforeLastChar %in% fatChars) - 0.002 * (beforeLastChar %in% skinnyChars), y = 1 - i / nrow(imgGSMat) - 0.01, gp = gpar(fontsize = fontSize, col = ifelse(!color, "black", rgb(imgMat[i, j, 1], imgMat[i, j, 2], imgMat[i, j, 3]))), simply = "left") } } } if (saveToDisk) suppressMessages(dev.off()) } drawImageWithText(img, text, thresh = 0.9, color = TRUE, fontSize = 5)
Nice! Sorry, Hadley.
Trump/His Tweets
Let’s write a wrapper to a greater extent than or less our
drawImageWithText
function that volition automatically download a Twitter user’s icon together with tweets - together with work those as img
and text
. We’ll work the wonderful rtweet
package for this:library(rtweet) drawImageWithTextFromTwitter <- function(username, thresh, ...) { text <- get_timeline(username, n = 200) %>% select(text) %>% unlist() %>% discard(str_detect(., "^RT")) %>% str_replace(., "(http|https)[^([:blank:]|\"|<|&|#\n\r)]+", "") %>% str_extract_all(., "[a-zA-Z0-9[:punct:]]+") %>% unlist %>% paste(., collapse = " ") img <- load.image(lookup_users(username)$profile_image_url) drawImageWithText(img, text, thresh, ...) }
Let’s practise Trump:
drawImageWithTextFromTwitter("realDonaldTrump", 0.55, fontSize = 8)
Tyra Banks/Her Tweets
drawImageWithTextFromTwitter("tyrabanks", 0.9, color = TRUE, fontSize = 5)
Congratulations. You’re yet inwards the running towards becoming America’s Next Top Model.
That’s It
Next mensuration is, obviously, making T-shirts. Enjoy!