Skip to content

ASCII Art

This homework assignment aims to practice applications of higher-order functions for processing lists of elements. You are supposed to implement a function generating an ASCII art from input images following the specification. Examples of such a transformation are depicted in the figures below.


 ;;;::::,,,,...       
 o;;;;::::,,,....     
 ooo;;;;:::,,,,....   
 xoooo;;;::::,,,....  
 xxoooo;;;;:::,,,,... 
 xxxxoooo;;;::::,,,,. 
 %%xxxxooo;;;;::::,,, 
 %%%%xxxoooo;;;;:::,, 
 ##%%%xxxxoooo;;;:::: 
 ###%%%%xxxoooo;;;;:: 
 @####%%%xxxxoooo;;;: 
 @@@###%%%%xxxxooo;;; 

Example of a gradient image (left) and its transformation (right).

::::::;;;;;;;;;;;;;;;;;oooooooxxxxx%%%%%%%%%%############%%%%%%%xxxxxxxxxxooooo;;;;;;:::::,,,,,,. 
::::;;;;;;;;;;;;;;;;;oooooooxxx%%%%###############%%###%%%%%%%%#%%%%%%xxxxxxoooooo;;;;;:::,,,,,,. 
::;;;;;;;;;;;;;;;;;;oooooxxx%%######%%%%#######%%%%%%%xxxx%x%%%%%%%%%%%%%xxxxxxooooo;;;;::::,,,,, 
:;;;;;;;;;;;;;;;;;ooooxxx%%%#######%%%###############%%%%%%xoxooooxxxxx%%%%%xxxxxoooooo;;::::,,,, 
;;;;;;;;;;;;;;;;;oooxx%%%##################################%xxxxxo;:::;oxxxxx%%%xxxxxooo;;;;::::, 
;;;;;;;;;;;;;;;ooox%%%###################################%%%%%xxxoxoooxo;::;ooxx%%xxxxoooo;;;:::: 
;;;;;;;;;;;;;ooox%%%%%%###################%###############%#%xxxxxxoxooo:;;:,,,;oxx%%xxxoooo;;;:: 
;;;;;;;;;;;ooox%%%%%%%%###%%####%%%%%%%%%%%###%%%%%%%%%%%%##%%%%%%xoooooxooo;;;;o;ooxxxxxooooo;;: 
;;;;;;;;;;oox%%%%%%%%%%##%%%%%%%x%%%%############%%%%xxoooxx%%%%%xxxxx%xxxxxxxo::,.,:;xxxxxooo;;; 
;;;;;;;;ooox%%xx%%%%%%%%%%%%xxxx%%###%#############%%%%xo;;;;;ooxx%%xxx%x%xxoooo;:,,:;oxxxxoooo;; 
;;;;;;;ooxxxxxxxxxx%%%%%%xxxxx%###%%%%%%%%%%%%%######%%%xo;:,,,,::;ox%%xxxxxxxxooo;:,,,:;oxxooo;; 
;;;;;;ooooxxxxxxx%%%%%%xxxxxx%##%%%%%%xxx%xx%x%%%%%%%%%#%xo;,... .,,;oxxxxxxooo;ooxo;oo;;ooooo;;: 
::::;;;;ooooxxxx%%%%%xxxxxx%###%%xxxx%xooxxxxxx%xxx%%%%%%%%x;.      .,,;oxxxxxxxoo;;;xooooo;;;;;; 
::::::;;;ooxxox%%%%%xxxxox%##%xxxxxxxxxxxx%%x%%%xxxxxx%%%%xx::         ,:;oxxoxoxxxxox%xo:;;;;;;; 
,,,,:::;;ooxx%%%%%%xxxxoox%#xxxxxxooxxx%#######%%%%%xoxx%%;,. .         .,:ooxooooo;::;;;;:::;::: 
,,,,:::;ox%%#%%%%%xxxxoox%#%xooxxo;ox%%@@@@@@@@@#%%%%oox;.  .            ..,;ooooo;;;;::::::;::;: 
.,,,::;o%%%%%%%%%xxxxxoox#%xo;oo;;ox%@@@@@@@@@@@@@#%%x::     ,,,           .,:;oo;;;;;;;;:,,,,,,, 
..,,,;%%%%%%%%%%xxxxxooo%%xoo;xo;;x%@@@@@@@@@@@@@@@%x..,     :xo,           .,:;;;;::::::::::,,,, 
...,:x%%%%%%%%%%xxxxooox%%xooooo;;x#@@@@@@@@@@@@@@@#x;.:;,:;;x%%;.            .,:;:::,,,,,,:::::: 
 ..,oo%%x%%%%%%%xxxxoooo%%xo;;;;:ox#@@@@@@@@@@@@@@@#%xxoxooxox%%o.             .,:,,,,,,,,,,,,,,, 
   ,:;oxx%%%%%%xxxxooooox%%;::::,:o%@@@@@@@@@@@@@@@##xoxx%x%x%%%x.             .....  ........... 
   .::;o:o%%%%%%xxxooo;ox%xo::::::o%@@@@@@@@@@@@@@#%%xooxxxxox%%o.              ..      ......... 
   ..,:oo;x%%%%%xxxxooo;o%%o::,:,,;o%@@@@@@@@@@@@##%xxooxxxx%%%%:               ..        ....... 
   ...,:ox%%%%%%%xxxxoo;oxxx:::;:,:ox%#@@@@@@@@@##%%oooxxxxx%%%x.              ...             .. 
........,,:o%%%%%%%xxxoo;o%%o;,,;::;oox%%######%%xxoo;oxx%xx%%%,              ...                 
,...,.....,,:;oxxxxxxxxooox%%;;;:;::;;ooxxxxx%%xooooxoxxxxx%%%:              ....                 
::,,,,,,,,,,,:::;;oooxxoooox%%xo;::;:::;;;ooooooo;ooxoxx%%%%%:.            ....                   
:::::::::::,,::::;;:::;;oooox%%xo;:;;;;oo%oooooooox%ooxox%%x:.          ......                    
::;;;;;;;;;::::::::::::::::;;ox%xx::;;;xxxxxxxxxoooooxx%%x;,.         .......                     
::;;;;;;;;;;;;;;;;;;:::::::::::;oxxxooooxxxxxx%xxox%%%xx;,.       ..........                      
::;;;;ooooooooooo;;;;;;;::::::::::::;xx%%%%xox%x%%%%xo;:,......,,........ .  ..                   
,::;;o;ooooooooooooooo;;;;;:::::::::,,,,:;;oooooxoo;;:,,,,,,..,,,,,,,,,...,. .,                   
,:::;;;;oooooooooxxoooooo;;;;;;::::,,,::,,,,,,,,,,,,:,:::,:,:,,,,,,....  . ..  ,                  
,,:::;;;ooooxxxxooxxxxooooooo;;;;:::::::::,,,,,,:,,,,,:,,,,:,,,,:...,.   ....                     
,,,:::;;;;oooxxxxxxxxxxxooooooo;;;;;;;:::::,,:,,:::,::,,,::,::,,,,,.....     .                    
.,,:::;;;;;oooxxxxxxooxxxxxoooooo;;o;;;;:;;::::::::,:::::,::,,,::,,,.,..       .                  
..,,:::;;;;;oooooxxxooooxxxxxxooooooo;;;;;;;;;:;;;::::::::,,,,..,,..   ... ..                     
...,,:::::;;;;;ooooooooooooooxxooooooo;;;;;;;;;;;;;:::::::,,,,,,,........ . .                     
...,,,,:::;;;;;;ooooooooooooooooxxoooooo;;;;;;;;;::::::;:,,,,,,,,.......                          

If you are very ambitious you can even render video with your solution. Thanks a lot for this @Jiří Svítil!

Specification

The implementation has to be done in the programming language racket. In order to work with images, it has to import the library 2htdp/image.

Important:

All your code is required to be in a single file called hw1.rkt. The file should behave as a module providing two functions img->mat and ascii-art. Thus your file should start with the following lines:

scheme
#lang racket

(require 2htdp/image)

(provide img->mat ascii-art)

To test your implementation you need to work with images. The images can be either loaded from a file for example as follows:

scheme
(define img (bitmap "grad.png"))

or can be created by the function provided by the library 2htdp/image. For instance

scheme
(define img (circle 20 "solid" "blue"))

defines a blue disc of radius 20. For further details see: https://docs.racket-lang.org/teachpack/2htdpimage.html

Functions to be implemented

The first function (img->mat img) is a helper function transforming color images into matrices of intensities. To do that you need a couple of functions. First, the function (image-width img) returns the width of the image in pixels.

Second, the function (image->color-list img) transforms the image into a list of pixels colors, reading from left to right, top to bottom. For instance,

scheme
(define img (triangle 5 "solid" "violet"))

> (image->color-list img)
(#(struct:color 255 255 255 0)
 #(struct:color 255 255 255 1)
 #(struct:color 238 130 238 149)
 #(struct:color 255 128 255 2)
 #(struct:color 255 255 255 0)
 ...
 )

The colors should be converted into a grayscale intensity by the following function:

scheme
(define (RGB->grayscale color)
  (+ (* 0.3 (color-red color))
     (* 0.59 (color-green color))
     (* 0.11 (color-blue color))))

Now applying the function RGB->grayscale to the list of colors, we get a list of intesities:

scheme
> (map RGB->grayscale (image->color-list img))
(255.0 255.0 174.28 180.07 255.0 255.0 174.69 174.28 174.1 255.0 
171.46 174.28 174.28 174.28 172.69 174.28 174.28 174.28 174.28 173.69)

The function img->mat should return a matrix of intensities. So your task is to take the above list of intensities and transform it into a matrix, i.e., a list of rows where each row represents a horizontal line of pixels in the image. As the width of the above triangle image is 5, the output should look like

scheme
> (img->mat img)
((255.0 255.0 174.28 180.07 255.0) 
 (255.0 174.69 174.28 174.1 255.0) 
 (171.46 174.28 174.28 174.28 172.69) 
 (174.28 174.28 174.28 174.28 173.69))

As a second function, you are supposed to implement the function

scheme
(ascii-art width height chars)

returning a function of a single argument img. The returned function takes an image img and transforms it into a string approximating the input image. Thus the function is going to be called e.g. as follows:

scheme
(define chars " .,:;ox%#@")

((ascii-art 5 8 chars) (bitmap "grad.png"))

chars argument is a string of characters we want to use to approximate intervals of intensities. Let be the length of chars. An intensity should be represented by a character whose index in chars is computed by the index formula:

The notation denotes the maximum integer number below , which can be computed in racket by the function floor. Thus for the highest intensity , we have , and for the lowest intensity , we have . The reason there is the inner floor function is that might be slightly larger than due to rounding errors.

The function returned by ascii-art should split the matrix of intensities into blocks. The size of blocks is given by the arguments width and height The separation of the matrix into blocks is depicted below. In case the width (resp. height) of the matrix is not divisible by width (resp. height) the incomplete blocks have to be removed from the matrix as is illustrated by the red area:

Once the matrix is separated into blocks, intensities in each block have to be averaged. The average intensities are then transformed into the corresponding characters by the index formula, i.e., applying the function list-ref to chars and the index computed by the index formula. Finally, the resulting matrix of characters is transformed into a string composed of the characters in each row, followed by the newline character "\n".

Examples

Consider the following simple image composed of four gray rectangles of different intensities. The function (make-color r g b) creates a color object with respective RGB components. The function above places its image arguments on top of each other and analogously beside next to each other.

scheme
(define example 
    (above
        (beside (rectangle 2 1 "solid" (make-color 0 0 0))
                (rectangle 3 1 "solid" (make-color 75 75 75)))
        (beside (rectangle 2 3 "solid" (make-color 180 180 180))
                (rectangle 3 3 "solid" (make-color 225 225 225)))))

Evaluating the function img->mat on this image results in

scheme
((0 0 75.0 75.0 75.0) 
 (180.0 180.0 225.0 225.0 225.0) 
 (180.0 180.0 225.0 225.0 225.0) 
 (180.0 180.0 225.0 225.0 225.0))

After splitting into blocks of size 2x2 (the last column is omitted as the matrix width is 5) and computing average intensities we obtain

scheme
((90.0 150.0) (180.0 225.0))

Assuming that our characters approximating intensities are

scheme
(define chars " .,:;ox%#@")

the above intensities are represented by the following characters

scheme
((#\x #\;) 
 (#\, #\.))

After transforming the above matrix of characters into a string, the function returned by ascii-art produces the following output:

scheme
((ascii-art 2 2 chars) example) => "x;\n,.\n"

To see the result better, you can use the function display as follows:

scheme
> (display ((ascii-art 2 2 chars) example))
x;
,.