8

I have to make a program that reads in a file from the command line and covert it to ASCII art. I am using PPM format and here is a link to the project.

Here is what I have so far:

import sys

def main(filename):
    image = open(filename)
    #reads through the first three lines
    color = image.readline().splitlines()
    size_width, size_height = image.readline().split()
    max_color = image.readline().splitlines()

    #reads the body of the file
    pixels = image.read().split()
    red = 0
    green = 0
    blue = 0
    r_g_b_value = []
    #pulls out the values of each tuple and coverts it to its grayscale value 
    for i in pixels:
        if i !=  "\n" or " ":
            if len(i) == 3:
                red = int(i[0]) * .3
                green = int(i[1]) * .59
                blue = int(i[2]) * .11
            elif len(i) == 2:
                red == int(i[0]) * .3
                green == int(i[1]) *.59
                blue == 0
            elif len(i) == 1:
                red == int(i[0]) * .3
                green == 0
                blue == 0

            r_g_b_value = [red + green + blue]

            character = []
        for j in len(r_g_b_value):
            if int(j) <= 16:
                character = " "
            elif int(j) > 16 and int(j) <= 32:
                character = "."
            elif int(j) > 32 and int(j) <= 48:
                character = ","
            elif int(j) > 48 and int(j) <= 64:
                charcter = ":"
            elif int(j) > 64 and int(j) <= 80:
                character = ";"
            elif int(j) > 80 and int(j) <= 96:
                character = "+"
            elif int(j) > 96 and int(j) <= 112:
                character = "="
            elif int(j) > 112 and int(j) <= 128:
                character = "o"
            elif int(j) > 128 and int(j) <= 144:
                character = "a"
            elif int(j) > 144 and int(j) <= 160:
                character = "e"
            elif int(j) > 160 and int(j) <= 176:
                character = "0"
            elif int(j) > 176 and int(j) <= 192:
                character = "$"
            elif int(j) > 192 and int(j) <= 208:
                character = "@"
            elif int(j) > 208 and int(j) <= 224:
                character = "A"
            elif int(j) > 224 and int(j) <= 240:
                character = "#"
            else:
                character = "M"

            grayscale = character
            print(grayscale)

main(sys.argv[1])

I an getting an error that says 'int' object is not iterable, is there is an easy way to fix this and how would someone recommend printing this out while preserving the image.

And the last thing I am unsure about is how to preserve the width and height of the picture.

Any help would be greatly appreciated.

4
  • I am lost here and it seems that when I print the r_g_b_value it is printing it one value per line. Commented Sep 12, 2011 at 2:27
  • I am thinking if I do for j in range(r_g_b__values) and then do a series of if/else statements of int(j), setting the values to those in the list of available characters, would this work and what would be the best way to save the characters. Would it be a new list? Commented Sep 12, 2011 at 2:35
  • If you're still getting the error, the full traceback would be helpful. Commented Sep 12, 2011 at 3:24
  • I got rid of the error, but do you have any idea on printing out this character in while keeping the image Commented Sep 12, 2011 at 3:57

3 Answers 3

9

You can use image-to-ansi.py for the conversion.

First, download image-to-ansi.py:

wget https://gist.githubusercontent.com/klange/1687427/raw/image-to-ansi.py

Convert it to Python 3 syntax:

2to3 -w image-to-ansi.py

Install Pillow, an image library:

pip3 install pillow

Save this script as ppmimage.py:

# Parses a PPM file and loads it into image-to-ansi.py
import re, itertools, sys
from PIL import Image

image_to_ansi = __import__("image-to-ansi") # __import__ because of minuses in filename. From https://gist.github.com/1687427

if __name__ == '__main__':
    im = Image.open('/dev/stdin')
    for y in range(im.size[1]):
        for x in range(im.size[0]):
            p = im.getpixel((x,y))
            h = "%2x%2x%2x" % (p[0],p[1],p[2])
            short, rgb = image_to_ansi.rgb2short(h)
            sys.stdout.write("\033[48;5;%sm " % short)
        sys.stdout.write("\033[0m\n")
    sys.stdout.write("\n")

You can test the script like this (this assumes you have netpbm and imagemagick installed):

convert -resize $(($COLUMNS*2))x$(($LINES*2)) logo: pnm:- | pnmtoplainpnm | python3 ppmimage.py

On my machine, it looks like this:

ImageMagick logo shown in Xterm

Sign up to request clarification or add additional context in comments.

Comments

4

Here you have your code modified and working.
It is not optimal, it does not take into account the presence of more or less comments in the header and there is not exception handling but it is a start.

import sys
import numpy as np

RGBS = range(16, 255, 16)
CHARS = [' ', '.', ',', ':', ';', '+', '=', 'o',
         'a', 'e', '0', '$', '@', 'A', '#']
FACTORS = [.3, .59, .11]

def main(filename):
    image = open(filename)
    #reads header lines
    color = image.readline()
    _ = image.readline()
    size_width, size_height = image.readline().split()
    max_color = image.readline()

    size_width = int(size_width)
    max_color = int(max_color)

    #reads the body of the file
    data = [int(p) for p in image.read().split()]
    #converts to array and reshape
    data = np.array(data)
    pixels = data.reshape((len(data)/3, 3))
    #calculate rgb value per pixel
    rgbs = pixels * FACTORS
    sum_rgbs = rgbs.sum(axis=1)
    rgb_values = [item * 255 / max_color for item in sum_rgbs]

    grayscales = []
    #pulls out the value of each pixel and coverts it to its grayscale value 
    for indx, rgb_val in enumerate(rgb_values):
        #if max width, new line
        if (indx % size_width) == 0 : grayscales.append('\n')    

        for achar, rgb in zip(CHARS, RGBS):
            if rgb_val <= rgb:
                character = achar
                break
            else:
                character = 'M'

        grayscales.append(character)

    print ''.join(grayscales)

main('test.ppm')

These are the ppm figure and the ASCII Art result

enter image description here

And the micro ppm file I used for the example:

P3
# test.ppm
4 4
15
 0  0  0    0  0  0    0  0  0   15  0 15
 0  0  0    0 15  7    0  0  0    0  0  0
 0  0  0    0  0  0    0 15  7    0  0  0
15  0 15    0  0  0    0  0  0    0  0  0

2 Comments

This solution provided in this answer provides no color support. See my answer for how to sacrifice support for the craptastic Windows console and gain color support.
@JanusTroelsen Thanks but note the OP is asking about ASCII art, not ANSI art
0

I wrote one of these in C# a while ago and I calculated the character to use with this formula:

index_into_array = (int)(r_g_b_value * (chars_array_length / (255.0)));

As for the width and height, you could average every two lines of vertical pixels to halve the height.

Edit 1: in response to comment: The basic idea is that it scales your RGB value from 0 to 255 to 0 to the length of the array and uses that as the index.

Edit 2: Updated to correct that I was ignoring your grayscale normalization.

4 Comments

I am still not very good at coding, so I am not sure I fully understand, I set the list of available to chars_array_length and then use the function that you posted and then do I print that value to get the character or do I need to do something else. Thank you again for your help
@asmith Updated answer to explain further.
Once again I apologize for my lack of skills but I get this error File "other.py", line 40, in <module> main(sys.argv[1]) File "other.py", line 36, in main current_character = character_choices[int((r_g_b_value * (len(character_choices) /(255.0*3))))] TypeError: can't multiply sequence by non-int of type 'float'
Yeah I don't know python, you'll have to convert the algorithm I posted to python code yourself or update your/ask another question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.