Generating ASCII art of Image in golang.

Kushal chapagain || 9/16/2024

In this blog, we will create ASCII art from an image. The basic flow for generating ASCII art from an image is as follows:

    1.Load the image which you what to convert into ASCII art.

    2.Convert the image into gray scale so that each pixel has a single color value rather than three values for RGB.

    3.Iterate through every pixel of image and get he position and color value of each pixel.

    4.Map each pixel's color value (ranging from 0 to 255) to a corresponding ASCII character. Darker pixels should be assigned darker characters, while lighter pixels should be assigned lighter characters. For example, you can use a string like $@B%#*+=-,., where the characters are ordered by brightness. Ensure that the string is arranged in ascending or descending order of darkness to accurately match the pixel values.

    5.Then there you have it, the series of ASCII characters (string) which Approximately (almost correctly) represent your image. Now you can create the another image from that string or text file or html file whatever you want.

    In this blog, we'll dive into the details and write some Go code to generate ASCII art from an image. I know, you know little bit of GO and know how to setup go project, so I am not going into that detail. We don't install any packages and just use golang Image standard library for 2D image manipulation.

Load the image that you want to convert into ASCII art.

func LoadImage() image.Image { file, err := os.Open("output/me.png") if err != nil { fmt.Printf("error while opening file %v\n", err) } defer file.Close() img, err := png.Decode(file) if err != nil { fmt.Printf("error while decoding image %v\n", err) } return img }

This function will load the me.png image from output directory of root of the project and decode that file in image file, In this snippet It only decode png images but you can decode jpg or jpej by simply calling jpg.Decode() instead of png, But in this blog we only do png . why? I know you will do it.

After image is loaded we have option to rezise it or not ,I recommend to resize image into standard size which will give consistent result ,I guess that what you need.

During resizing if we have image with large resolution then it will reduce the dimensions and if the size is smaller than the standard size then it will simply enlarge it.

We pass the loaded image into resize function and in return we will the resized image.

func ResizeImage(img image.Image, width int) image.Image { bounds := img.Bounds() height := (bounds.Dy() * width) / bounds.Dx() newImage := image.NewRGBA(image.Rect(0, 0, width, height)) draw.CatmullRom.Scale(newImage, newImage.Bounds(), img, bounds, draw.Over, nil) return newImage }

In this function we pass the width as parameter and calculate the height so that he height of the new image is proportional to the width of image.

After resizing now we need to convert the image into the gray scale image , which makes easier to get the pixel color value , because now we have to deal with one color value range from 0-255 instead three color values in RGB.

To convert the image into grayscale image we first find the dimensions of image and create a new blank image with same size and loop over the every pixel of RGB image, convert color of every pixel from RGB image to equivalent gray scale color and place that in exact same place as of RGB image on new image.

func ConvGrayScale(img image.Image) image.Image { bound := img.Bounds() grayImage := image.NewRGBA(bound) for i := bound.Min.X; i < bound.Max.X; i++ { for j := bound.Min.Y; j < bound.Max.Y; j++ { oldPixel := img.At(i, j) color := color.GrayModel.Convert(oldPixel) grayImage.Set(i, j, color) } } return grayImage }

After getting gray scale Image now we have one thing to do , loop over every pixel of gray scale image and assign the ASCII character value according to the color value of image.

func MapAscii(img image.Image) []string { asciiChar := "$@B%#*+=,....." bound := img.Bounds() height, width := bound.Max.Y, bound.Max.X result := make([]string, height) for y := bound.Min.Y; y < height; y++ { line := "" for x := bound.Min.X; x < width; x++ { pixelValue := color.GrayModel.Convert(img.At(x, y)).(color.Gray) pixel := pixelValue.Y asciiIndex := int(pixel) * (len(asciiChar) - 1) / 255 line += string(asciiChar[asciiIndex]) } result[y] = line } return result }

The Return value of this function is array of string where each string denote the one horizontal line which consist the series of characters of that line.(you can adjust the ASCII string by adding spaces on end of string or using different set of string)

Now to view this, we are going to convert that into html file so that the ascii image can be rendered properly whith line brak. to convert the ascii image into the html format we loop over the result (array of string ) and print each line with line break <br/> tag which which correctly format our html code.

func AsciiToHTML(ascii []string) { HtmlFile, err := os.Create("output/format.html") if err != nil { fmt.Println("error while creatig html file") } for lin, lines := range ascii { htmlString := `<!DOCTYPE html> <html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=0.8"> <title>AsciiImage</title> </head> <body> <code> <span class="ascii" style="color: black; background: white; display:inline-block; white-space:pre; letter-spacing:0; line-height:0.9; font-family:'Consolas','BitstreamVeraSansMono','CourierNew',Courier,monospace; font-size:10px; border-width:1px; border-style:solid; border-color:lightgray;">` if lin == 0 { _, err := HtmlFile.WriteString(htmlString) if err != nil { fmt.Println("error while start writing into html file") } } for _, char := range lines { _, err := HtmlFile.WriteString(fmt.Sprintf("<span>%v</span>", string(char))) if err != nil { fmt.Println("error while writing into html file") } } _, err := HtmlFile.WriteString("<br>") if err != nil { fmt.Println("error while writing into html file") } if lin == len(ascii)-1 { _, err := HtmlFile.WriteString("</code></body></html>") if err != nil { fmt.Println("error while end writing into html file") } } } }

To correctly format html file we add head and meta tag at the beginning of first line and closing tags on last line. Now this should give you the correct formatted html code.

This is it for this article but if you want to convert the array of strings into another actual image, use golang.org/x/image

The full implementation of this package to generate image is implemented in this repo.

The live preview of this implementation can be found here

Thank you for reading, For any queries or suggestion or anything in general contact me