7

I have some code that converts an image into an ascii art image. Currently it outputs it as a .txt file, but the file can have several hundred thousand characters. How can I convert the file to an image such as a .png file?

Currently it builds a character vector based on the pixel density, and then writes the vector into a .txt image.

Andrew Sides
  • 71
  • 1
  • 1
  • 3
  • To clarify, the .txt file is ascii art and you want an image (like a screenshot) of the text? – nathancahill Apr 20 '15 at 23:57
  • Yes, exactly. I want to output a png that looks like the .txt file. – Andrew Sides Apr 21 '15 at 00:21
  • 1
    I posted an answer based on something I made in the past. In the future, I recommend you put more effort into creating a [Minimal, Complete and Verifiable Example](http://stackoverflow.com/help/mcve). I think you will get a warmer reception to your questions and more answers since it shows you have put some effort into the problem. In this case if you don't have any idea about the code, at least you should provide a sample input and describe as best you can the output that you want. – KobeJohn Apr 21 '15 at 14:53

3 Answers3

20

If I understand correctly, you want an image that looks as if someone took a screenshot of the ascii art as it would look in a giant unlimited text editor.

I have done something similar to generate text programmatically with PILLOW. Here is an example modified from this code of mine. Hopefully this code will help you and others avoid the fiddling I had to do to figure out how to make things look reasonable.

Here is an example result made from the code below.

enter image description here

The code is a straightforward modification of the linked library to work with a text file instead of a string. You should be able to paste this into a console or a file and run it directly.

from math import ceil

from PIL import (
    Image,
    ImageFont,
    ImageDraw,
)

PIL_GRAYSCALE = 'L'
PIL_WIDTH_INDEX = 0
PIL_HEIGHT_INDEX = 1
COMMON_MONO_FONT_FILENAMES = [
    'DejaVuSansMono.ttf',  # Linux
    'Consolas Mono.ttf',   # MacOS, I think
    'Consola.ttf',         # Windows, I think
]


def main():
    image = textfile_to_image('content.txt')
    image.show()
    image.save('content.png')


def textfile_to_image(textfile_path):
    """Convert text file to a grayscale image.

    arguments:
    textfile_path - the content of this file will be converted to an image
    font_path - path to a font file (for example impact.ttf)
    """
    # parse the file into lines stripped of whitespace on the right side
    with open(textfile_path) as f:
        lines = tuple(line.rstrip() for line in f.readlines())

    # choose a font (you can see more detail in the linked library on github)
    font = None
    large_font = 20  # get better resolution with larger size
    for font_filename in COMMON_MONO_FONT_FILENAMES:
        try:
            font = ImageFont.truetype(font_filename, size=large_font)
            print(f'Using font "{font_filename}".')
            break
        except IOError:
            print(f'Could not load font "{font_filename}".')
    if font is None:
        font = ImageFont.load_default()
        print('Using default font.')

    # make a sufficiently sized background image based on the combination of font and lines
    font_points_to_pixels = lambda pt: round(pt * 96.0 / 72)
    margin_pixels = 20

    # height of the background image
    tallest_line = max(lines, key=lambda line: font.getsize(line)[PIL_HEIGHT_INDEX])
    max_line_height = font_points_to_pixels(font.getsize(tallest_line)[PIL_HEIGHT_INDEX])
    realistic_line_height = max_line_height * 0.8  # apparently it measures a lot of space above visible content
    image_height = int(ceil(realistic_line_height * len(lines) + 2 * margin_pixels))

    # width of the background image
    widest_line = max(lines, key=lambda s: font.getsize(s)[PIL_WIDTH_INDEX])
    max_line_width = font_points_to_pixels(font.getsize(widest_line)[PIL_WIDTH_INDEX])
    image_width = int(ceil(max_line_width + (2 * margin_pixels)))

    # draw the background
    background_color = 255  # white
    image = Image.new(PIL_GRAYSCALE, (image_width, image_height), color=background_color)
    draw = ImageDraw.Draw(image)

    # draw each line of text
    font_color = 0  # black
    horizontal_position = margin_pixels
    for i, line in enumerate(lines):
        vertical_position = int(round(margin_pixels + (i * realistic_line_height)))
        draw.text((horizontal_position, vertical_position), line, fill=font_color, font=font)

    return image


if __name__ == '__main__':
    main()

By the way, all that code should not be stuffed in one function, but I think it makes it easier to follow for example code.

KobeJohn
  • 7,390
  • 6
  • 41
  • 62
  • thank you a lot, it's working great! how to make text wrap with this code? – Mhmd Admn Jul 29 '21 at 12:45
  • 1
    Sorry not sure what you mean. If it's a simple change, can you explain more detail? If it's a major change, you might want to ask a new question and reference this one. – KobeJohn Jul 30 '21 at 13:13
  • Thank you, I meant: paste the text in like columns not just one tall column. I think I did it, I finished 95% of the code. I wanted to make the image square so I did some math, math.sqrt(N_lines)...., here's the code https://pastebin.com/jPWy9J0e, it's not perfect, do you know why the text isn't split evenly on all columns? here's the output https://imgur.com/a/OOvnrF7 – Mhmd Admn Jul 30 '21 at 13:43
  • I see. That is quite a different problem so I recommend you start a new question if you still need help. Even if you already solved it, last time I checked it is allowed to post a question and answer it yourself. That might help someone. – KobeJohn Aug 01 '21 at 01:37
1

Have a look at Pillow (documentation here)

From the above linked documentation:

Here’s a simple example:

import ImageFont, ImageDraw

draw = ImageDraw.Draw(image)

# use a bitmap font
font = ImageFont.load("arial.pil")

draw.text((10, 10), "hello", font=font)

# use a truetype font
font = ImageFont.truetype("arial.ttf", 15)

draw.text((10, 25), "world", font=font)
Raniz
  • 10,882
  • 1
  • 32
  • 64
  • I haven't been using PIL for a while so wasn't aware about it's status nor that Pillow has suceeded it - I've updated the answer to reflect that. – Raniz Apr 21 '15 at 00:56
  • 2
    The question was "How can I convert the file to an image such as a .png file?" and I think that pointing towards a library that provides the means to do so is an appropriate answer. – Raniz Apr 21 '15 at 00:58
  • If you can show a piece of working code using Pillow (or any other library) that reads in ASCII art and spits out a PNG, I'll definitely upvote your answer. – MattDMo Apr 21 '15 at 01:01
0

I liked that the ImageFont-ImageDraw solution above, because it was much shorter. But it did not work as I tried it. Therefore I figured something out which produced the desired output.

In the following example code 1st a white image is generated. Afterwards the test is written on the image:

from PIL import Image, ImageFont, ImageDraw
img = Image.new('RGB', (200, 50), color = (255,255,255))
fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 30)
ImageDraw.Draw(img).text((0,0), "hello world", font=fnt, fill=(0,0,0))
img
Thomas R
  • 1,067
  • 11
  • 17