6

I love playing around with ASCII art and, as you may know, there are tools to generate ASCII art from images, where the ASCII consists of those characters which will result in the best image, as this first picture shows:

enter image description here

With the eye, you need ASCII characters with a lot of white (e.g. dots, commas, hyphens) to generate the brighter areas and characters with a lot of black (e.g. M, $) to generate darker areas.

Now, what I want to achieve is being able to control the characters which make up the image. An example which is halfway there is the following image, where the story forms the shape of a raven.

enter image description here

However, this raven is only flat, with no depth like the eye has. I would like to combine the examples with the eye and the raven.

Would it be possible to achieve the same kind of effect in \LaTeX either by using different shades of grey, or by using different sizes, for each individual character? So, for brighter areas, use a light grey or small black character, and for darker areas, use black and large characters.

Does this make sense?

Is it possible to achieve in an automatic way with \LaTeX?

  • Welcome to TeX.SX. Please show us the code for your raven. I guess, this would be a good point to start for people who want to help. – LaRiFaRi Jun 6 '14 at 8:04
  • @LaRiFaRi Thanks. I didn't code the raven using Latex, I just googled and found the image. But I think you can achieve that quite easily with the shapepar package. – rigor Jun 6 '14 at 8:40
  • 2
    @rigor The idea is that you do the easy bits then when you get stuck post a working test document and ask about the detail that you are stuck on. – David Carlisle Jun 6 '14 at 9:03
  • @DavidCarlisle So you are saying shapepar the best way forward then? If I knew that, then I obviously wouldn't phrase the question in the way that I did. I'm not trying to be lazy, I just need some pointers in the right direction. (And when I said 'you' to LaRiFaRi, I wasn't refering to him/her specifically but more a general 'you' (which could include me). – rigor Jun 6 '14 at 9:11
  • @rigor No I mean we need a lot more hints from you about the actual problem, in particular the only difficulty in this is mapping the input to the output so we need to know the intended input, for example you could make the eye picture by using picture mode to position each character at a specified coordinate, so it's just a matter of specifying which character goes at which coordinate... – David Carlisle Jun 6 '14 at 9:30
3

This answer requires lualatex and imagemagick or a similar program. You must convert your color pictures to .ppm and the greyscale ones to .pgm, which are very easy to work with. You can use these commands to keep the aspect ratio:

convert -compress none -resize ___OutputWidth___ ___InputFile___ ___output___.ppm
convert -compress none -resize ___OutputWidth___ ___InputFile___ ___output___.pgm

Warning: it's a long process and it will take a lot of time with large images.

Here's the code:

asciiart.tex

\documentclass{article}
\usepackage{luacode}
\usepackage{xcolor}
\usepackage{graphicx}

\directlua {require "asciiart"}
\newcommand\bwascii[1]{\directlua{bwascii("#1")}}
\newcommand\colorascii[1]{\directlua{colorascii("#1")}}
\newcommand\bwframe[1]{\vspace*{\fill}\bwascii{#1}\vspace*{\fill}\newpage}
\newcommand\colorframe[1]{\vspace*{\fill}\colorascii{#1}\vspace*{\fill}\newpage}

\begin{document}
\ttfamily\frenchspacing
\newlength{\correctem}\settowidth{\correctem}{M}%to set the size of the minipage
\newlength{\correctex}\settowidth{\correctex}{x}%to set the line height
\pagestyle{empty}\centering

\colorframe{lenna_128x128.ppm}
\colorframe{lisa_150x224.ppm}
\colorframe{knuth_192x227.ppm}
\bwframe{einstein_150x206.pgm}
\end{document}

asciiart.lua

colorascii = function ( picture )
  -- read a picture in .ppm format
  local file = io.open(picture, "r")
  if file==nil then
    tex.sprint("file not found")
    return
  end
  local arr = {}
  for line in file:lines() do
    if line:sub(1,1)~="#" then--if line starts with # don't insert it
      table.insert(arr,line);
    end
  end
  file:close()
  if arr[1]~="P3" then
    tex.sprint("i don't like this file")
    --TODO: implement moar ways to detect if the file is corrupt
    return
  end
  local sizes = {}
  for i in string.gmatch(arr[2],"%d+") do
    table.insert(sizes,i)
  end
  local xsize = tonumber(sizes[1])
  local ysize = tonumber(sizes[2])
  table.remove(arr,1)--remove "P3"
  table.remove(arr,1)--remove sizes
  table.remove(arr,1)--remove maxval and assume no value is bigger than 255
  colors = ""
  for k,v in pairs(arr) do
    colors = colors..v.." "
  end
  colors = string.gsub(colors, " +", " ")
  --now all our picture is in a single string
  rgb = {}
  for i in string.gmatch(colors,"%d+ %d+ %d+") do
    temp = {}
    for j in string.gmatch(i, "%d+") do
      table.insert(temp,j)
    end
    table.insert(rgb, temp)
  end
  tex.sprint("\\noindent\\resizebox{\\textwidth}{!}{")
  tex.sprint("\\noindent\\begin{minipage}{"..xsize.."\\correctem}\\setlength\\baselineskip{1\\correctex}\\setlength\\lineskip{0pt}\\setlength\\prevdepth{0pt}")
  for i = 1,#rgb do
    tex.sprint("\\definecolor{mycolor}{RGB}{"..rgb[i][1]..","..rgb[i][2]..","..rgb[i][3].."}\\textcolor{mycolor}x\\hspace{0pt}")
  end
  tex.sprint("\\end{minipage}}")
end

valchar = function (val)--takes an integer from 0 to 255 and returns a character
  val = tonumber(val)
  valuetable = {"\\$","B","Q","Y","v","~","."," "}--return darker characters for darker values
  return valuetable[math.floor(val/32)+1]
end

bwascii = function ( picture )
  local file = io.open(picture, "r")
  if file==nil then
    tex.sprint("file not found")
    return
  end
  local arr = {}
  for line in file:lines() do
    if line:sub(1,1)~="#" then
      table.insert(arr,line);
    end
  end
  file:close()
  if arr[1]~="P2" then
    tex.sprint("i don't like this file")
    return
  end
  local sizes = {}
  for i in string.gmatch(arr[2],"%d+") do
    table.insert(sizes,i)
  end
  local xsize = tonumber(sizes[1])
  local ysize = tonumber(sizes[2])
  table.remove(arr,1)
  table.remove(arr,1)
  table.remove(arr,1)
  greys = ""
  for k,v in pairs(arr) do
    greys = greys..v.." "
  end
  greys = string.gsub(greys, " +", " ")
  value = {}
  for i in string.gmatch(greys,"%d+") do
    table.insert(value, i)
  end
  tex.sprint("\\noindent\\resizebox{\\textwidth}{!}{")
  tex.sprint("\\noindent\\begin{minipage}{"..xsize.."\\correctem}\\setlength\\baselineskip{1\\correctex}\\setlength\\lineskip{0pt}\\setlength\\prevdepth{0pt}\\leavevmode")
  for i = 1,#value do
    tex.sprint("\\smash{"..valchar(value[i]).."}\\hspace{0pt}")
  end
  tex.sprint("\\end{minipage}}")
end

Sample output (download the pdf to zoom in):

lenna lisa dek einstein

Warning: with large images it may take way longer than what you expect.

These are the original pictures: lenna, lisa, knuth, einstein.

Converted to .ppm/.pgm in case you don't want to install imagemagick: lenna, lisa, knuth, einstein.

While this may not be exactly what you wanted (the raven, the change the font size and thickness...), it's a start. As far as I know there are no other ways to generate ascii art with latex.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.