Introduction
- Images can be converted to an ASCII representation - it’s a hack that’s as old as the hills
- This post’s contributions:
- Tweet-sized ascii image generator in R
- Executable art
- Notes/References
Tweet sized ascii image generator
C=strsplit('851+-. ','')[[1]]
i=jpeg::readJPEG(system.file('img/Rlogo.jpg',package='jpeg'))[c(T,F),,2]
i[]=C[findInterval(i,quantile(i,p=seq(0,1,,length(C))))]
cat(apply(i,1,paste,collapse=''),sep='\n')
-- - -- - ++++++++++++++++++ -- -- - - - -+
- - + -- ++++155555555555555555555555555555551+++ -+- +-- +
- +- - -++15555555555151111111111155555555555555555558851++ -- ++ - -
+ - ++15511111111111111111111111111111111111111155555555555885+++ - - -
-- + +1511111111115555555511111111111111555555555555551111155555555888+ - - - -
- - +1111111++15555555511+++++1155555555588888888888555555555555515555558881+ +-
+111111+++58855551+++++++1155588888888888888888888888888888888885555515558888+ -
-+11111++18855551+++++++11588888888885+++- - -- - -++15888888855555558881 +
+--- -+1111+++5855551+++++++1588888885++ -+ -------------------- -+188888555558881 - --
-+ + +1111+++888551+++++++158888881+ - -+- - --- ----+--+--++ -+58855558888+ +
- -+111+++885551+++++++5888888+ -+115555555555555555555555555555555555551++- +1885558885 +
++111++588551+++++++588888++ +-- - +51++11111111111111111111111111111++++155885+ +-- +55555888 -
+111++88855+++++++588888+- - -- +51+885555555555555555555555555555588888515888+--- +1555888+
+111++88551+++++++58888+ +- + -- +51+88111111111++++++++++++++++11111111158855888-+- +555885
+111++58851+++++++88888+ +- + +-- +51+881111111++1888888888888855551111111111585888 - - -15588+
+111++88551++++++58888 +- + + +51+88111111+188888888888888888855551111111155888++ +--15581
+111+58851++++++18888+ - +51+8811111115888+ --+-+5851185111111155888 ++5588
+111+58851++++++18888+ - +51+8811111115888+---- - +51+88111111115888 --+188
+511158851++++++18888 - +51+88111111+5888+ - +- +51+88111111+15888 -+185
+5511188551++++++8888- +51+8811111115888+--------+-++ 11++88111111+18885- -- -158+
+511158551++++++58881 - +- + +51+8811111115888+ -++11++88111111++5888+-- +- +188-
5551558551++++++5888+ - +-- - +51+88111111155555555555555551++++8811111+++8888+--+ +158+
-+5555555551++++++5888+ -+-- +51+881111111588511++++++++++1888851+++++188888+ -+ +158+ -
+ +1555555555++++++15888+ - ++- +51+881111111111555555555555551+++11158888888+-+- -+ -+188+
- - +5555555551+++++115885+- - - - +51+88111111111+++++++++111111+18888888885+--+--+ -+1585+
-- ++-+555555555511+++1115888+ - - +51+881111111++155588555555111558881+-- - -+---+11188+ -
- - +585555555111+++11155885+-+ ++51+88111111+188888888888855515855555585++ - -+111588+ +
- ++58855555511111+1115555885+++51+88111111158881- ---+5885155588881+15885+1115888+- -- -
+ - +588555555511111111555555551+88111111158881- - -+555+5511155888588858881
- +- +888888555555111111155551+88111111+58881+++++++++5551581111115858881 - - -
- -+ -+188888885555555511551+881111111588885555111111585158111111585888+ -
- -+ -- - -+ -+5888888888855551+88111111158885111115555888551581111111858885-
++5888888851+88111111+588888888888888885851581111111555888+- - --
- -+ ++51+881111111588888551+++ - 1551181111111585888+ + ---
+ - +51+8811111115888+ -- -+- - +851181111111585888+ ----
+- +- - -+51+85+++++++5888+ -+- --- - - +85118++++++++55888+----+
- --+5118588585885888+ --- +8555888888888888881- --
+ ++-+8888888888888888+ -- +8888888888888888885+
What’s executable in R?
- R has a bunch of unary operators that can be strung together and still make valid executable code.
- Some transitions between operators are not allowed
++
before digits, OK. e.g.++1
++
after digits, Bad. e.g.1++
!
before digits, OK. e.g.!!1
!
after digits, Bad. e.g.1!!
- etc
- Going to construct an ascii image just out of valid executable ascii by abusing this!
+++++++++++++++1
## [1] 1
+++++++++-----------1
## [1] -1
++++!!++++!+++++1-----!!!-----3
## [1] 0
Executable ASCII art
- Set up a transition table of operators and digits e.g.
- Can transition from
!
to2
, but not vice versa - Table manually tweaked to get OK looking output.
- Can transition from
- For each pixel in the image:
- Find its quantized value (calculated via
findInterval()
- Based upon the previous character, pick from the allowable characters at the index corresponding to the quantized value
- Repeat until done.
- Find its quantized value (calculated via
- Ensure each line ends in a
+
or-
so the entire output can be evaluated as a single expression. - Ensure the entire output ends in a digit so it’s a valid expression.
#-----------------------------------------------------------------------------
# Transistion Matrix (stored in a compact form)
# - name of list item is the previous character value
# - contents of the list item is the allowable characters to transition to
# These characters are listed in order of visual density
#-----------------------------------------------------------------------------
next_char <- list(
' ' = ' -+',
'0' = '-+|17235469*80&',
'1' = '-+|17235469*80&',
'2' = '-+|17235469*80&',
'3' = '-+|17235469*80&',
'4' = '-+|17235469*80&',
'5' = '-+|17235469*80&',
'6' = '-+|17235469*80&',
'7' = '-+|17235469*80&',
'8' = '-+|17235469*80&',
'9' = '-+|17235469*80&',
'+' = ' -+!10',
'-' = ' -+!10',
'!' = ' -+!10',
'*' = ' -+!10',
'|' = ' -+!10',
'&' = ' -+!10'
) %>% purrr::map(~rev(strsplit(.x, '')[[1]]))
#-----------------------------------------------------------------------------
# What are the lengths of the allowed 'next_char' transitions
#-----------------------------------------------------------------------------
char_lengths <- next_char %>%
purrr::map_int(length) %>%
unique()
#-----------------------------------------------------------------------------
# Set a gamma value. This is a hack so we don't have a linear intensity mapping,
# because the mapping from image value to displayed character is going to
# depend a lot of font, image darkness, users display settings,
# number of distinct characters to choose from, etc
#-----------------------------------------------------------------------------
gamma <- 1.3
asciify <- function(jpeg_filename, gamma) {
#-----------------------------------------------------------------------------
# Create a quantized version of the image for each transition length
# i.e. if there are only 5 possible next characters, then we should pick
# which one based upon an image quantized to 5 levels.
#-----------------------------------------------------------------------------
qimage <- list()
#-----------------------------------------------------------------------------
# Read image
#-----------------------------------------------------------------------------
image <- jpeg::readJPEG(jpeg_filename)
#-----------------------------------------------------------------------------
# If this is an RGB array, so just select the 'G' plane as an approximation
# of image grayscale intensity
# (I'm too lazy to do a proper conversion for this hack)
#-----------------------------------------------------------------------------
if (length(dim(image))==3) {
image <- image[,,2]
}
#-----------------------------------------------------------------------------
# Only keep every second row of the image. Because ascii characters
# are taller than they are wide, we need a lower resolution in the Y direction
# otherwise the image looks too tall/stretched.
#-----------------------------------------------------------------------------
image <- image[c(T,F),]
#-----------------------------------------------------------------------------
# For each possible length of allowed 'next_char' choices, create a quantized
# image
#-----------------------------------------------------------------------------
for (char_length in char_lengths) {
probs <- seq(0, 1, length.out = char_length + 1)
j <- image
j[] <- findInterval(image, quantile(image, probs = probs^gamma), rightmost.closed = TRUE)
qimage[[char_length]] <- j
}
#-----------------------------------------------------------------------------
# Function to select a next_char based upon the prev_char and
# position in the image (row, col)
#-----------------------------------------------------------------------------
select_next_char <- function(row, col, prev_char) {
# What are the allowable next characters?
available_chars <- next_char[[prev_char]]
N <- length(available_chars)
# Which quantised image should be used as the reference?
this_qimage <- qimage[[N]]
# What is the level/value at the current location
level <- this_qimage[row, col]
level <- min(level, N)
available_chars[level]
}
#-----------------------------------------------------------------------------
# Initialise the prev_char before we begin
#-----------------------------------------------------------------------------
prev_char <- '+'
#-----------------------------------------------------------------------------
# Create the output matrix
#-----------------------------------------------------------------------------
ascii <- image
#-----------------------------------------------------------------------------
# Iterate over the entire image choosing a character to represent the pixel
# at each point
#-----------------------------------------------------------------------------
for (row in seq(nrow(image))) {
for (col in seq(ncol(image))) {
if (col == ncol(image)) {
# If we're at the end of a row, must end in a + or -
this_char <- '+'
} else {
# Otherwise choose a character based upon the previous one
this_char <- select_next_char(row, col, prev_char)
}
ascii[row, col] <- this_char
prev_char <- this_char
}
}
#-----------------------------------------------------------------------------
# Ensure the ascii ends in a digit so that it's a valid expression
#-----------------------------------------------------------------------------
ascii[nrow(ascii), ncol(ascii)] <- 0
#-----------------------------------------------------------------------------
# Collapse it all into a image for display
#-----------------------------------------------------------------------------
ascii <- paste(
apply(ascii, 1, paste, collapse=''),
collapse="\n"
)
ascii
}
- Copy and paste this ascii image into your R session to execute it
ascii <- asciify(jpeg_filename = system.file('img/Rlogo.jpg',package='jpeg'), gamma=1.3)
cat(ascii, "\n")
-+++++++++++++-- +
-+!!!!!!!!!!!!!!!!!!!19999999999993277- +
-!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!19999*19327--- +
-!!!!!!!!!++++++++!+++++++++++++++++++++!!!!!!!!!1999*1*1377-- +
-+!!!!!++++++!!1996444433333333354446666666666444453544666999*18*+ +
-!!!!!!++++!1*199645322223354469999*1*1*1*1*1*1*1*1*1*1999964446699*18837- +
-+!!!++++1*199943222222334699*180000000000000000000000088888*1*199964699*1882-- +
-+!++++++1*196632222223356*000000&0842271- -+180888*1999469*1885- +
---- -!++++++18*194327222223680000&0927--- -!188*19966*1803- +
-- -!+++-+1*19652777222398000&037-- -18*1996*1802- +
- -+!++-+18*16327722226800&087- -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!19994271- -1*196*006- +
-++++-!0*16327772229800&027---- -!++++++++++++++++++++++++++++++++++++!!1*192---- -1999808--+
-!+++-18*1437777226800&071- -!++0866666466666666666666666666699*1000636*182111- -1968087+
-!+++-18*142777222*00&02--- -!++0855355554322222222222222233455543346884980*--- -16804+
-+!++-18*142777722*00&07--- -!++085555343225*1*1*1*1*1*1*16444355353534*1980* -1600+
+!++++08943277722980&0--- -!++0855555523*0&0&0&0&0&0&0&008*!!14354555349*0&+- -!1&+
+!+++!0*14277722380&02-- -!++085555553680&+ -1955845555553990&1- -!0+
+!!!+18*14277722480&07-- -!++085555553680&+---- -142*14545553490&0- -!0+
+!!!+!0*14277722480&0-- -!++0855553336*0&+ -!!+185553553390&0- -+0+
-!!!!!1*143277223*0001- -!++085555553680&+------------ -!++0*+!!+!+++10&!- -!0+
-!!!!18*1527772248003-- -!++0855555336*0&+ -!++-0045544522*0&0711- -+08+
-!!!!1*16527722268002-- -!++085555534999*!!!!!!!!!!!!!++-+084535322380&07117- -1&-+
-!!!!1*16422722248003- -!++085555454488633333333333348&084332722480&007--- -1&- +
- -1964999643222223980*- -!++0855555333544999999999969642223549880&0&07171--- -+007- +
- -19966999443222234988971-- -!++0855555433522222222335554323800&0&0&097117117- -!!097- +
-- -199999944532223356*082- -!++085555543223*1*1*1*1444333498004211- -!!007-- +
- +1999666444332335449*1*+-- -!++0855554323*0&0&0&0008*!!144*!!!199*127-- -!+1&02- +
-1*1996664553333554699*162726328855555536*0&+- -1*13996*00*!+!1*0*+!!!!0&071--- +
-1*1*199964443333544669999632885555533480&+- +193*15554980*1800*0&03- +
-18*1*199966445355444466328855555536*0&+--------+1*139*!+!+++!168004-- +
-1888*1*19996644444632885555533680&164444444549*139*!++++!!19*002- +
-!088888*1*1*19632885555553680&1335544699888*136*!++!!!!19*0041- +
-!000088*!++085555343680&08000000000009*136*!+++!!!1*180*-- +
-!++085543353680&08943227-- -1934*!++!+++1*18087-- +
-!++085534533680&+ -1944845445336868002- +
-!++092222222680&+ -1954*++++++++19*00211117- +
-!+!1*1*1*1*1*10&+ -196*1*1*1*1*1*1*0041--- +
-00&0&0&0&0&0&0&03- -0&0&0&0&0&0&0&0&0097- 0
eval(parse(text=ascii))
[1] FALSE
Tweetable Executable ASCII of the Mona Lisa
- This is about 230 ascii characters
- You’ll need to squint to try and make it look like the Mona Lisa
- Copy and paste this ascii image into your R session to execute it
ascii <- asciify("../../static/img/ascii-mona/mona-tiny.jpg", gamma=0.7)
cat(ascii, "\n")
-+++++!+-++++++--+
- +0&1| +
-- +- -08+ +
!+!-!181170&!----+
!14711*099*093944+
+!!!185- -+!- +
!++!1*13|+1649&06+
!+!16808800*1*0&!+
!1869*1&0&00998&1+
!0&1892- -!+!0&0&+
0&00&!10080000&0&+
0&08*10&0&0&0&0&00
eval(parse(text=ascii))
[1] TRUE
Executable ASCII of the Mona Lisa
- Copy and paste this ascii image into your R session to execute it
ascii <- asciify("../../static/img/ascii-mona/mona-small.jpg", gamma=0.7)
cat(ascii, "\n")
+++++++++++!!!!!!!++++++!++!!!!+!!!!!+++++++!++!!!!!!!!+++++++!++++++++++++++++++++-++++
++++-+++++++++++!+++++++++++!!+++!!+++++++++++!+++++++++++++++++++++++!+++++++++++++++++
+--+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++-+--+++-+++++++++++++++++++++++++++++++++--------+++++---+-+++---+
+---------------------------------+++----++!!!+----------------------------------------+
------ -1080000000&009711111|----------------------- +
--- +08*1*188800&0&0&08| +
- +1*195988888800&0&0&07+ +
- +11+ -180000&0&0&- +
- +12+ -18&00&0&08+ +
+- -1091| -+!180&0&0&+ +
++!--- ----- -!1&!---- -++--+!1880&0&0*-- +
!-!+---------- -++--+-----!!1&1964*! +1599*1*1800&0&087|-- +
+++-------------++---++++!!+!1*0&! ----!!-+18800&0086|-------- +
+!!-----------++-+---+!!!+!!1*00&! -18000&0&0*- +
+!++!+-+!!!!!!-------+!!1*199*08&17| -! -!108800&0089377532711721|-----++!!++++++
+!+!+!+-!16644*+---++!!1*19*19800&12| +0*- -!!1800800&008*+---++---+!!!!--++--+!-++!!+
++!!!!!+-+!!+!!+--++!!19999669*0&082737746641249*088800&0&0*!--++----+++!!!+++--+++!!!!+
+-++!!154*16699446445533222259*0&0&097134432249*1888880&0&00972344455532354233334455456+
++!!!!1*!++!!!!!!!196966444556980&00&0511736*1*1888880000&0&152547746644496537222232346+
+--++!!!16696466444553227777349880&00&0&08888888*1*1888000&0&!+--+--1445335545772465366+
+-!169*196964553333222771172249080088800*+!!199996698*0800&0&152546*19*1*!!164664354634+
!19999*1*1996432111111171117754*10088888831723555549*1*19*100844566569955665549*1949456+
!+++++++-+++++!!!!!!--------++!1808*1*14|- -!1*19669980842375335521756625*199454+
!--++++++!!!!!!!!+18*16*19*134988*14| -1*!!+!!!1*0096421|-----+!!+-!18*+!!+
!+++!!!1953355354498*18809*1888*14+ -!194345499*009457|----++-++!1664464+
++++!!1*14644546999999*09680*1861- -!!+++-----+!!165|-- -++++
!!!!!!!19*!++++!+!!!!!!!108*189| -!- -+!!!1*18471| -++++
!!!!!!!!!!!!16522332235*08988*1+ ---+!!!!1*1888080080&16377| -++
!!!--++++++!!!!!!!!++!108*18*1665| -!!19*1*1669*180&00&00&0*1962|-- +
!!!!+!!+++++++++!!!!!1&08809699966643| -!!1*08434349699*1*0&0&0&0&099996471117+
1*145442353354444646900888*!!19999644455444664469888895353665355569*0&0&0&0&09999666511+
199994522227255544468888*08699*1*1644444466669*1800866544*!!+--+!!1*00&0&0&0&196669644*+
1999*1666666666699*088*188899*19*1966669999*1*1800*19669942213249*00880&0&0&0&166995724+
18*19*!!!!!!!!1668088*1*0089*08*1*1*1*1*1*1*188008*189*1635136698888880&0&0&0&166962499+
1*19*----------1&1*1999*180*1088888*088*1*18880&0880880944699988888*180&00&0&0871125454+
+------------+00*!!144669*080088808008*1*0880&0008080&0*199*08*1*19*1800&0&0&0&11112225+
188888*1*18800*19644464499*080880000&1888*1&0&0&0800&088*1888*1669*1880000&0&0&08888*18+
!!!!!!!!19*08*1966646646666*08800000888880&0&0&000&0&00888889666669*100&0000&0&08666666+
16666666*08*1*!!!!!!!!!1*19*18880&008800&0&0&0000&0000888089999999999*0000000&0&0655554+
!!1664490888*1*1*1*1*1*1*1*188800&000&0&0&0&00&0&000&08008*1*1966699*18008000&0&0*!!!!!+
!!!!!1*0008*080*!++!!!!100&00&008008000&0&0&0&0&0&0&080&08*!!!1999*180800&0&0&0&0&15454+
!!!!!!1&00008945347243527494| -180&0&0&0&0&0&0&00000888*1*18*18*1*08000&00&00&15554+
!!!!!!0&000&144254673644697+ +0&00&000000800&088*000008800000&000&0&0&09354+
!!!!!1&0&0&0994595565969*12| -1&00&0&00000&0&008888800080&0&0&0&0&0&1355+
!!!!!0&00&0&09*1*196*1*1*09664571| -!196335421722356*0&0080&0&0&0000&0&08444+
166448&0&0&0&0*18888888888808*165371| +061|-+-!!+!!-++-+!!!19*1&0&00&0&0&0&0&0&0644+
1999*1&0&0&0&0&008000000*+-+- -1*194322461+ -0891+--!!+!++!!!!!199*0000&00&0&0&0&0&0644+
1*1*1*0&0&0&0&0&0&0&00&1299|----!18*!161+-16275*189424*0*199569*1*18000000&0&0&0&0&0946+
1*18&0&0&0&0&00000&0&0&164+!!-++!!18089*1646*147489*1908*1*1*1998088000&0&0&0&0&0&00969+
1*0&0&0&0&088888880&0&0*19*!--+!1*000&0&0000080&000088088808888880800000&0&0&0&0&0&0*19+
188&0&0&00000888000&008&0*1376808888888888000000000&000000080008008&000&0&0&0&0&0&0&19*+
188&0&0&00&0&0&00000000&0&0*18&00000&0&0&0&0&00&00000000000000&000&0&0&0&0&0&0&0&0&08*1+
180000000000&0&0088000088888*000008880000000000&00&0&0&0&0&0&00&0&0&0&0000&0&0&0&0&00*1+
1&0&0&0&0&0&00&0080&0*1*1*1*1000&00&0&0&0&0&0&0&000&0&0&0&0&0&00&0&0&0&000&0&0&0&0&0888+
1&0&0&0&080&0&00&0*199999*1*1800&00&0&00&0&0&0&0&00&0&0&0&0&0&00&0&0&0&0&0&0&0&0&0&0000+
10&0&0&0&000&0&0*1*1*1*1*18880000800&0&0&0&0&0&0&000&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0&0&000
eval(parse(text=ascii))
[1] TRUE
Extension
Ideas:
- Could probably use error diffusion for a slightly better image given the limited palette
- Add some randomness to the next character selection and then generate
multiple images until you have an executable ascii image which evaluates
into the answer you want.
- e.g. an ascii image of the greek letter pi which evaluates to
3.14
- Note: I’m either going to need a lot of computing power, or a smart approach. Genetic/evolutionary algos?
- e.g. an ascii image of the greek letter pi which evaluates to