Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

LINQ to ASCII Art

4.81/5 (33 votes)
2 Jul 2013CPOL3 min read 106.9K   854  
Convert image to ASCII art using LINQ technology.

Introduction

I saw many articles explaining how to generate ASCII Art from image, but I didn't see any article that achieves the same stuff in LINQy style. With that, I 'll explain how to use LINQ technology to compose a couple of functions to achieve our goal.

Background

ASCII Art in a nutshell refers to text-based visual art. So we can create cool arts using ASCII codes from simple things such ^_^ :) to little advance things such as:

For more information, I advice you to check out this link.

As we know, Language Integrated Query (LINQ) is a powerful technology for composing, querying, transformation different data source, all of that inside your code.

I think of ASCII Art as composing and transforming a couple of functions to generate the beauty stuff.

Using the Code

The LINQ to ASCII Art is very straight forward. In this section, I'll explain every function in detail.

Get Pixels

This main purpose of this function is to get the actual pixels for a specified image, so it accepts a bitmap, iterates through all its pixels horizontally and vertically using the width and the height of that image, and uses the GetPixel function to get a pixel with specific coordinates. Then, the return value is the actual pixels as IEnumerable(Of Color) to make it easy to play with those pixels using LINQ later on. Its code looks like this:

VB.NET
Public Function GetPixels(bmp As Bitmap) As IEnumerable(Of Color)
        Return From x In Enumerable.Range(0, bmp.Width - 1)
               From y In Enumerable.Range(0, bmp.Height - 1)
               Select bmp.GetPixel(y, x)
End Function 

Gray Scale Pixels

This main purpose of this function is to convert the actual pixels to gray scale, so it accepts the bunch of pixels, iterates them and applies the following formula:

Gray Scale = 0.3 * Red + 0.59 * Green + 0.11 * Blue 

For more information about gray scale, please check out this link.

Then the return value is the grayed scale pixels as IEnumerable(Of Color) to make it easy to play with those pixels using LINQ later on. Its code looks like this:

VB.NET
Public Function GrayScalePixles_
(pixels As IEnumerable(Of Color)) As IEnumerable(Of Color)
        Return From p In pixels
               Let g = 0.3 * p.R + 0.59 * p.G + 0.11 * p.B
               Select p = Color.FromArgb(g, g, g)
End Function

Get Characters

This main purpose of this function is to get the character corresponding to the value of gray scale pixel. By the way, I want to thank someone whose name I don't remember:) for his experiment to get the underneath characters in the code, but you can use any suitable set. So the idea behind that is check the brightness of the grayed color which is value of anyone of the RGB for that pixel. For example, if the value is 230 means the pixel goes to white, if it's less than 50 means the pixel goes to black, so the idea is simple from lightness to darkness. Then return value is the character (ASCIIs) they should print out, they 're IEnumerable(Of Char) to make it easy to play with those characters using LINQ later on. It's code looks like this:

VB.NET
Public Function GetChars_
(pixels As IEnumerable(Of Color)) As IEnumerable(Of Char)
        Return From p In pixels
        Select If(p.R >= 230, " "c, If(p.R >= 200, _
        "."c, If(p.R >= 180, "*"c, If(p.R >= 160, _
        ":"c, If(p.R >= 130, "o"c, If(p.R >= 100, _
        "&"c, If(p.R >= 70, "8"c, _
        If(p.R >= 50, "#"c, "@"c))))))))

End Function

The last thing is composing them using LINQ as follows:

VB.NET
Dim img As Image = Image.FromFile("Hisham.jpg")
Dim bmp As New Bitmap(img, 150, 150)
Dim parts = GetChars(GrayScalePixles(GetPixels(bmp))).Chunk(bmp.Width _
  - 1).Select(Function(part) String.Join("", part))
Dim data = String.Join(vbCrLf, parts)
File.WriteAllText("test.txt", data)

Last I want to show you that we can go further a little bit and compose them all in a single giant LINQ query as follows:

VB.NET
Dim data = String.Join(vbCrLf, (From x In Enumerable.Range(0, bmp.Width - 1)
     From y In Enumerable.Range(0, bmp.Height - 1)
     Let p = bmp.GetPixel(y, x)
     Let g = 0.3 * p.R + 0.59 * p.G + 0.11 * p.B
     Let v = Color.FromArgb(g, g, g)
     Select If(v.R >= 230, " ", If(v.R >= 200, ".", _
       If(v.R >= 180, "*", If(v.R >= 160, ":", _
       If(v.R >= 130, "o", If(v.R >= 100, "&", _
       If(v.R >= 70, "8", If(v.R >= 50, "#", _
       "@"))))))))).Chunk(bmp.Width - 1).Select(Function(part) _
               String.Join("", part)))

Finally, I want to mention that some of you guys said the last query is expensive and takes much time. Of course I agree with you because there is a lot of processing for many many pixels, anyway we can improve the performance using AsParallel() method, also we can use new async modifier to eliminate the thread blocking.

Points of Interest

Amazingly, we have seen how we can generate ASCII art in LINQy style, which is something beautiful :) and open mind for many further things such image processing. We saw how we GrayScale an image with simple LINQ query, and we can do more than that, inverting, flipping, ... etc.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)