This blog has relocated to https://coolbutuseless.github.ioand associated packages are now hosted at https://github.com/coolbutuseless.

29 April 2018

mikefc

Introduction

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 ! to 2, but not vice versa
    • Table manually tweaked to get OK looking output.
  • 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.
  • 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?