Share via


.Net: Braille Code

(Try using the table below to decipher this!)

Download

You can download a more elaborate version of this code (the project version) at the following link.

Introduction

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.

Braille to Binary Table

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

Example Code (Visual Basic)

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

Example Code (C Sharp)

*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;
  }
 }
}

Summary

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.

See also

Please check out my other TechNet Wiki articles!