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:
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:
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:
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:
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:
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.