.Net: Braille Code
(Try using the table below to decipher this!)
You can download a more elaborate version of this code (the project version) at the following link.
Not too long ago (while reading Code by Charles Petzold, which I recommend), I became particularly interested in Braille code. I decided it was time for me to write a little application in Visual Basic, which I later ported over to C# for this article. The book goes on to describe how in Braille Code each symbol is represented by 6 binary values. The layout of a Braille symbol is as follows:
Knowing that each one of the 6 cells is going to be either darkened or invisible, you can deduce two possible values for a cell: Darkened-invisible, meaning either high-low, on-off, or 1-zero. This means that each cell is binary in nature. Since we know that there are 6 cells, and in the picture above each cell has a number corresponding to that cells' position, we can also easily represent a Braille symbol using a binary string of ones and zeros.
Below is a picture that shows how the cell positions in a binary symbol relate to the bit positions in a binary string.
The following is a table that shows the binary representation for each Braille symbol. *Please note that this table does not cover shorthand Braille code.
A | 100000 | I | 010100 | Q | 111110 | Y | 101111 | 7 | 011011 | ) | 011111 | \ | 110011 | @ | 000100 |
B | 110000 | J | 010110 | R | 111010 | Z | 101011 | 8 | 011001 | * | 100001 | [ | 010101 | ^ | 000110 |
C | 100100 | K | 101000 | S | 011100 | 1 | 010000 | 9 | 001010 | < | 110001 | / | 001100 | _ | 000111 |
D | 100110 | L | 111000 | T | 011110 | 2 | 011000 | 0 | 001011 | % | 100101 | + | 001101 | " | 000010 |
E | 100010 | M | 101100 | U | 101001 | 3 | 010010 | & | 111101 | ? | 100111 | # | 001111 | . | 000101 |
F | 110100 | N | 101110 | V | 111001 | 4 | 010011 | = | 111111 | : | 100011 | > | 001110 | ; | 000011 |
G | 110110 | O | 101010 | W | 010111 | 5 | 010001 | ( | 111011 | $ | 110101 | ' | 001000 | , | 000001 |
H | 110010 | P | 111100 | X | 101101 | 6 | 011010 | ! | 011101 | ] | 110111 | - | 001001 | 000000 |
Knowing all of this, we can create a simple program that will draw these symbols using GDI.
In order to keep things less complicated, I have extracted the main function (ToBraille) from my example, and I will explain that via in-code comments. At the end of this article, you will find a link that leads you to the entire project that I created.
Option Strict On
Option Explicit On
Option Infer Off
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'make sure our layout doesn't tile or stretch
Me.BackgroundImageLayout = ImageLayout.None
'get a braille image for our string
Dim TurnThisStringIntoBraille As String = "hello world"
Me.BackgroundImage = ToBraille(TurnThisStringIntoBraille, 10, Me.BackColor, Color.Black, True, Me)
End Sub
Private Shared Function ToBraille(ByVal text As String, ByVal fontSize As Integer, ByVal backColor As Color, ByVal foreColor As Color, ByVal drawBlanks As Boolean, parent As Control) As Bitmap
'Create an array for holding all of our carriage return delimited lines
Dim lines() As String = {}
'This will calculate the size of each cell in the braille symbol, subtracting 2 because of each surrounding pixel
Dim dotSize As Integer = fontSize - 2
'This will calculate the space in between braille symbols, which should be the same size as a dot, therefore
'we only take half(one half for each symbol to add up to a whole)
Dim pad As Integer = dotSize \ 2
'This calculates the width of the braille symbol
Dim characterWidth As Integer = fontSize * 2 + (pad * 2)
'this calculates the height of the braille symbol
Dim characterHeight As Integer = fontSize * 3 + (pad * 2)
'These colors are later passed as symbol carriage return data
Dim crColor As Color = Color.FromArgb(255, 255, 255, 0)
Dim lfColor As Color = Color.FromArgb(255, 0, 255, 255)
Dim unknownColor As Color = Color.FromArgb(255, 255, 0, 255)
'We attempt to get all of the lines in the text
Try
lines = text.Split(CChar(vbCr))
Catch ex As Exception
'if an error occurs, this means there is nothing to translate
'to Braille, so we return a single pixeled bitmap
Dim bm As New Bitmap(1, 1)
Dim g As Graphics = Graphics.FromImage(bm)
g.Clear(parent.BackColor)
Return bm
End Try
'This calculates the width of the final bitmap
Dim maxSize As New Size(0, lines.Count * characterHeight)
For Each line As String In lines
If (line.Length * characterWidth) > maxSize.Width Then maxSize.Width = (line.Length * characterWidth)
Next
'This list of images will be a list of bitmaps containing each braille symbol needed to construct
'the final bitmap
Dim images As New List(Of Image)
'This will be the bitmap that is returned by the function
Dim finalBM As Bitmap
Try
'We attempt to create a new bitmap
finalBM = New Bitmap(maxSize.Width, maxSize.Height)
Catch ex As Exception
'If an error occurs, we know that translating
'to braille is not possible
Dim bm As New Bitmap(1, 1)
Dim g As Graphics = Graphics.FromImage(bm)
g.Clear(parent.BackColor)
'Therefore, we just return a one pixeled bitmap
Return bm
End Try
'Get the graphics object from the final bitmap
Dim finalG As Graphics = Graphics.FromImage(finalBM)
'Set to no anti-aliasing, because all of the anti-aliasing
'will be performed within the creation of the individual
'braille symbols
finalG.SmoothingMode = Drawing2D.SmoothingMode.None
'Clear final bitmap
finalG.Clear(backColor)
'If the text is blank, return the blank bitmap... Final check
If text = "" Then Return finalBM
'Iterate each character in the string of text
For Each s As String In text
'Create a variable for holding our binary string
Dim keyCode As String = String.Empty
'Create a bitmap for each braille character(We calculated the size earlier)
Dim bm As New Bitmap(characterWidth, characterHeight)
'Get the graphics object from the current symbol's bitmap
Dim g As Graphics = Graphics.FromImage(bm)
'For each iteration, create an x and a y object
'This cooridinate will be modified
'for drawing each cell in the braille symbol(total of 6 cells)
Dim x As Integer = 0
Dim y As Integer = -fontSize 'This starts at -fontsize because it will be
'incremented before it is used the first time, therefore when it increments the first time, it will be exactly zero
'We do all of our anti-aliasing when we draw each symbol
g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
'Clear our symbol's bitmap
g.Clear(backColor)
'Get a binary string/braille keycode for the current character
Select Case s.ToLower
Case "a" : keyCode = "100000"
Case "b" : keyCode = "110000"
Case "c" : keyCode = "100100"
Case "d" : keyCode = "100110"
Case "e" : keyCode = "100010"
Case "f" : keyCode = "110100"
Case "g" : keyCode = "110110"
Case "h" : keyCode = "110010"
Case "i" : keyCode = "010100"
Case "j" : keyCode = "010110"
Case "k" : keyCode = "101000"
Case "l" : keyCode = "111000"
Case "m" : keyCode = "101100"
Case "n" : keyCode = "101110"
Case "o" : keyCode = "101010"
Case "p" : keyCode = "111100"
Case "q" : keyCode = "111110"
Case "r" : keyCode = "111010"
Case "s" : keyCode = "011100"
Case "t" : keyCode = "011110"
Case "u" : keyCode = "101001"
Case "v" : keyCode = "111001"
Case "w" : keyCode = "010111"
Case "x" : keyCode = "101101"
Case "y" : keyCode = "101111"
Case "z" : keyCode = "101011"
Case "1" : keyCode = "010000"
Case "2" : keyCode = "011000"
Case "3" : keyCode = "010010"
Case "4" : keyCode = "010011"
Case "5" : keyCode = "010001"
Case "6" : keyCode = "011010"
Case "7" : keyCode = "011011"
Case "8" : keyCode = "011001"
Case "9" : keyCode = "001010"
Case "0" : keyCode = "001011"
Case "&" : keyCode = "111101"
Case "=" : keyCode = "111111"
Case "(" : keyCode = "111011"
Case "!" : keyCode = "011101"
Case ")" : keyCode = "011111"
Case "*" : keyCode = "100001"
Case "<" : keyCode = "110001"
Case "%" : keyCode = "100101"
Case "?" : keyCode = "100111"
Case ":" : keyCode = "100011"
Case "$" : keyCode = "110101"
Case "]" : keyCode = "110111"
Case "\" : keyCode = "110011"
Case "[" : keyCode = "010101"
Case "/" : keyCode = "001100"
Case "+" : keyCode = "001101"
Case "#" : keyCode = "001111"
Case ">" : keyCode = "001110"
Case "'" : keyCode = "001000"
Case "-" : keyCode = "001001"
Case "@" : keyCode = "000100"
Case "^" : keyCode = "000110"
Case "_" : keyCode = "000111"
Case """" : keyCode = "000010"
Case "." : keyCode = "000101"
Case ";" : keyCode = "000011"
Case "," : keyCode = "000001"
Case " "
'If it is a space, we just add a blank bitmap to our list of images
g.SmoothingMode = Drawing2D.SmoothingMode.None
g.Clear(backColor)
images.Add(bm)
'Skip to the next iteration
Continue For
Case vbCr
'If it is a carriage return, we
'set the first pixel to our special carriage return color
'Later we will check that first pixel....
g.SmoothingMode = Drawing2D.SmoothingMode.None
g.Clear(Color.White)
bm.SetPixel(0, 0, crColor)
images.Add(bm)
'Skip to the next iteration
Continue For
Case vbLf
'If it is a line feed, we
'set the first pixel to our special line feed color
'Later we will check that first pixel....
g.SmoothingMode = Drawing2D.SmoothingMode.None
g.Clear(Color.White)
bm.SetPixel(0, 0, lfColor)
images.Add(bm)
'Skip to the next iteration
Continue For
Case Else
'If it is an unknown character, we
'set the first pixel to our special unknown color
'Later we will check that first pixel....
g.SmoothingMode = Drawing2D.SmoothingMode.None
g.Clear(Color.White)
bm.SetPixel(0, 0, unknownColor)
images.Add(bm)
'Skip to the next iteration
Continue For
End Select
'If we made it to this point, we were able to successfully translate the
'character to a braille keycode/binary string
'We will now iterate through each bit in that binary string
'Meaning a total of 6 iterations(one per braille cell)
For Each s2 As String In keyCode.ToString
'We increment y(up and down) by the fontsize(cell size)
'On the first iteration, this will bring y to a value of 0, before we use it
y += fontSize
'when the first column of cells is drawn,
'y will reset, and x will increment
'We draw in this direction because
'of the layout of the braille symbol's cells
If y > (fontSize * 2) Then
y = 0 : x += fontSize
End If
'Determine an action based
'on the current bit's value
If s2 = "1" Then
'We will fill an ellipse if the bit is ON
g.FillEllipse(New SolidBrush(foreColor), _
New Rectangle(New Point(x + pad, y + pad), _
New Size(dotSize, dotSize)))
Else
'We will either draw a hollow circle if the bit is OFF,
'or we will no draw anything at all, based on if the drawBlanks option is on or off.
If drawBlanks Then
g.DrawEllipse(New Pen(New SolidBrush(foreColor)), New Rectangle(New Point(x + pad, y + pad), New Size(dotSize, dotSize)))
End If
End If
Next
'Now that we have finished drawing our braille symbol, we will add it to our list of images
images.Add(bm)
Next
'The left and top variables will be a coordinate system for drawing each of our pre-drawn braille symbols
'to our final bitmap
'again, we start left negative, because left will increment by the width
'of an image before it accessed, therefore the first increment will bring it to zero
Dim left As Integer = -images(0).Width
Dim top As Integer = 0
'iterate for each image in the list of images
For Each Image As Bitmap In images
'get the color of the first pixel in each braille symbol
'looking for special information about that symbol that we made earlier
Dim color As Color = Image.GetPixel(0, 0)
Select Case color
Case crColor
'If the color indicates that the image is a carriage return,
'we increment our coordinate system
left = -Image.Width : top += Image.Height
'we then skip to the next iteration without drawing the carriage return to
'our bitmap
Continue For
Case lfColor
'Since usually a carriage return is accompanied by a line feed,
'this means we already incremented our coordinate system,
'therefore we do not increment our coordinate system, and
'instead we just skip to the next iteration without drawing the line feed to our final bitmap
Continue For
Case unknownColor
'we skip this symbol all together, because we don't know what it is.
' MsgBox("unknown!")
Continue For
Case Else
End Select
'if iteration makes it to this step, this means that the symbol is a valid character symbol
'so we increment our coordinate system
left += Image.Width
If (left + Image.Width) > maxSize.Width Then
left = 0 : top += Image.Height
End If
'we now create a new point object using our coordinate system
Dim location As New Point(left, top)
'now we draw the symbol's image to the final bitmap
finalG.DrawImage(Image, location)
Next
'once all symbols are drawn to the final bitmap, we return the final bitmap
Return finalBM
End Function
End Class
*Please see Visual Basic example for comments
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
this.BackgroundImageLayout = ImageLayout.None;
this.BackgroundImage = ToBraille("hello World", 10, Color.Black, Color.White, true, this);
}
private static Bitmap ToBraille(string text, int fontSize, Color backColor, Color foreColor, bool drawBlanks, Control parent)
{
string[] lines = {
};
int dotSize = fontSize - 2;
int pad = dotSize / 2;
int characterWidth = (fontSize * 2) + (pad * 2);
int characterHeight = (fontSize * 3) + (pad * 2);
Color crColor = Color.FromArgb(255, 255, 255, 0);
Color lfColor = Color.FromArgb(255, 0, 255, 255);
Color unknownColor = Color.FromArgb(255, 255, 0, 255);
try
{
lines = text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
}
catch
{
Bitmap bm = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(bm);
g.Clear(parent.BackColor);
return bm;
}
Size maxSize = new Size(0, lines.Count() * characterHeight);
foreach (string line in lines)
{
if ((line.Length * characterWidth) > maxSize.Width)
maxSize.Width = (line.Length * characterWidth);
}
List<Image> images = new List<Image>();
Bitmap finalBM = null;
try
{
finalBM = new Bitmap(maxSize.Width, maxSize.Height);
}
catch
{
Bitmap bm = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(bm);
g.Clear(parent.BackColor);
return bm;
}
Graphics finalG = Graphics.FromImage(finalBM);
finalG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
finalG.Clear(backColor);
if (string.IsNullOrEmpty(text))
return finalBM;
foreach (char s in text.ToLower())
{
string keyCode = string.Empty;
Bitmap bm = new Bitmap(characterWidth, characterHeight);
Graphics g = Graphics.FromImage(bm);
int x = 0;
int y = -fontSize;
string s3 = s.ToString();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.Clear(backColor);
switch (s3)
{
case "a":
keyCode = "100000";
break;
case "b":
keyCode = "110000";
break;
case "c":
keyCode = "100100";
break;
case "d":
keyCode = "100110";
break;
case "e":
keyCode = "100010";
break;
case "f":
keyCode = "110100";
break;
case "g":
keyCode = "110110";
break;
case "h":
keyCode = "110010";
break;
case "i":
keyCode = "010100";
break;
case "j":
keyCode = "010110";
break;
case "k":
keyCode = "101000";
break;
case "l":
keyCode = "111000";
break;
case "m":
keyCode = "101100";
break;
case "n":
keyCode = "101110";
break;
case "o":
keyCode = "101010";
break;
case "p":
keyCode = "111100";
break;
case "q":
keyCode = "111110";
break;
case "r":
keyCode = "111010";
break;
case "s":
keyCode = "011100";
break;
case "t":
keyCode = "011110";
break;
case "u":
keyCode = "101001";
break;
case "v":
keyCode = "111001";
break;
case "w":
keyCode = "010111";
break;
case "x":
keyCode = "101101";
break;
case "y":
keyCode = "101111";
break;
case "z":
keyCode = "101011";
break;
case "1":
keyCode = "010000";
break;
case "2":
keyCode = "011000";
break;
case "3":
keyCode = "010010";
break;
case "4":
keyCode = "010011";
break;
case "5":
keyCode = "010001";
break;
case "6":
keyCode = "011010";
break;
case "7":
keyCode = "011011";
break;
case "8":
keyCode = "011001";
break;
case "9":
keyCode = "001010";
break;
case "0":
keyCode = "001011";
break;
case "&":
keyCode = "111101";
break;
case "=":
keyCode = "111111";
break;
case "(":
keyCode = "111011";
break;
case "!":
keyCode = "011101";
break;
case ")":
keyCode = "011111";
break;
case "*":
keyCode = "100001";
break;
case "<":
keyCode = "110001";
break;
case "%":
keyCode = "100101";
break;
case "?":
keyCode = "100111";
break;
case ":":
keyCode = "100011";
break;
case "$":
keyCode = "110101";
break;
case "]":
keyCode = "110111";
break;
case "\\":
keyCode = "110011";
break;
case "[":
keyCode = "010101";
break;
case "/":
keyCode = "001100";
break;
case "+":
keyCode = "001101";
break;
case "#":
keyCode = "001111";
break;
case ">":
keyCode = "001110";
break;
case "'":
keyCode = "001000";
break;
case "-":
keyCode = "001001";
break;
case "@":
keyCode = "000100";
break;
case "^":
keyCode = "000110";
break;
case "_":
keyCode = "000111";
break;
case "\"":
keyCode = "000010";
break;
case ".":
keyCode = "000101";
break;
case ";":
keyCode = "000011";
break;
case ",":
keyCode = "000001";
break;
case " ":
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
g.Clear(backColor);
images.Add(bm);
continue;
//'\r' is carriage return.
//'\n' is new line.
case "\r":
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
g.Clear(Color.White);
bm.SetPixel(0, 0, crColor);
images.Add(bm);
continue;
case "\n":
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
g.Clear(Color.White);
bm.SetPixel(0, 0, lfColor);
images.Add(bm);
continue;
default:
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
g.Clear(Color.White);
bm.SetPixel(0, 0, unknownColor);
images.Add(bm);
continue;
}
foreach (char s2 in keyCode.ToCharArray())
{
y += fontSize;
if (y > (fontSize * 2))
{
y = 0;
x += fontSize;
}
if (s2 == "1".ToCharArray()[0])
{
g.FillEllipse(new SolidBrush(foreColor), new Rectangle(new Point(x + pad, y + pad), new Size(dotSize, dotSize)));
}
else
{
if (drawBlanks)
{
g.DrawEllipse(new Pen(new SolidBrush(foreColor)), new Rectangle(new Point(x + pad, y + pad), new Size(dotSize, dotSize)));
}
}
}
images.Add(bm);
}
int left = -images[0].Width;
int top = 0;
foreach (Bitmap Image in images)
{
Color color = Image.GetPixel(0, 0);
if (color == crColor)
{
left = -Image.Width;
top += Image.Height;
continue;
}
else if (color == lfColor)
{
continue;
}
else if (color == unknownColor)
{
// MsgBox("unknown!")
continue;
}
left += Image.Width;
if ((left + Image.Width) > maxSize.Width)
{
left = 0;
top += Image.Height;
}
Point location = new Point(left, top);
finalG.DrawImage(Image, location);
}
return finalBM;
}
}
}
Braille code can be represented as binary code and translated to a symbol using GDI.
At declaration, taking extra consideration into selecting a numeric datatype may be beneficial, and help to reduce unnecessary memory consumption.
You can download a more elaborate version of this code (the project version) at the following link.
Please check out my other TechNet Wiki articles!