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

C# RGB to Palette Based 8-bit Greyscale Bitmap Class

4.75/5 (20 votes)
5 Apr 2010CPOL4 min read 1   3.7K  
A class for converting RGB image into 8-bit Grayscale bitmap

Introduction

As far as I know, images cannot be converted into 8-bits per pixel grayscale bitmap images with GDI+. That's why I started working on this class. With this class, images can be converted into a palette based 8bpp grayscale image. The color palette contains 256 shades of gray.

Background

To create a bitmap file, one has to create 4 different parts of the file. Bitmap file header, DIB header, color palette and raw bitmap data.

Bitmap file header contains:

  • Magic Number (2 bytes) at 0x0000 offset
  • File Size (4 bytes) at 0x0002 offset
  • Two 2 bytes long data, the values of which depend on the application that creates the file, at offsets 0x0006 and 0x0008
  • Offset at which raw bitmap data can be found (4 bytes) at 0x000A

DIB header (Windows V3) contains:

  • Size of the DIB header (4 bytes) at 0x000E. Its value is 40, i.e. the size of the DIB header is 40 bytes.
  • Width of the image in pixel (4 bytes) at 0x0012
  • Height of the image in pixel (4 bytes) at 0x0016
  • The number of color planes being used (2 bytes) at 0x001A. Its value must be set to 1.
  • Number of bits per pixel (2 bytes) at 0x001C. For the case of this particular project, its value is set to 8.
  • Compression method being used (4 bytes) at 0x001E. In this case, the value is set to 0 which corresponds to no compression method being used.
  • Size of the raw bitmap data (4 bytes) at 0x0022. More on this later.
  • Horizontal and vertical resolutions of the image (pixels per meter). Each of these are 4 bytes and can be found at 0x0026 and 0x002A respectively. Their values are not really important.
  • Number of colors in color palette (4 bytes) at 0x002E. In this particular project, 256 color (shades of gray) is used. Therefore its value is set to 256.
  • Number of important colors used (4 bytes) at 0x0032. In this project, its value is set to 0 which corresponds to every color is important.

Color palette:

In this case, color palette containing 256 shades of gray is created [RGB(i,i,i) where i ranges from 0 to 255]. At the end of each shade, one byte padding has to be added.

Raw bitmap data:

In raw bitmap data, pixels are mapped from left to right and from bottom to up. The left most pixel at the bottom of the bitmap image is stored first in raw bitmap data. At the end of each row of pixel necessary padding is added for 4 byte alignment, i.e the width of the bitmap file is divisible by 4 then no padding is added, if the remainder is 1, 2 or 3 then 3 byte, 2 byte or 1 byte padding is added respectively to maintain 4 byte alignment.

For calculating the length of raw bitmap data, one has to calculate the number of byte padding required after each row. Here is how to calculate the size of raw bitmap data:

raw data size (in bytes) = bitmap width* bitmap height*bytes per pixel+bitmap height* byte of padding at the end of each row

The RGB to Grayscale is converted by using the following formula:

Shade of gray = (int) (0.3 * R + 0.59 * G + 0.11 * B)

Say, after evaluating a pixel, the value of the shade of gray is found 100. It corresponds to the 101st color in the color palette, which should be RGB(100,100,100).

Code Explained

It's a static class containing two public methods:

C#
bool CreateGrayBitmapFile(Image Image, string Path) 

and

C#
byte[] CreateGrayBitmapArray(Image Image)

The former saves the grayscale image to the specified path and returns true if it successfully does so and the latter returns a byte array of the bitmap file, which can later be converted into an image by using ImageConverter class (included with the source) created by Rajan Tawate.

The static class has 4 arrays for 4 parts of the BMP file.

C#
static byte[] BMP_File_Header = new byte[14];
static byte[] DIB_header = new byte[40];
static byte[] Color_palette = new byte[1024]; //a palette containing 256 colors
static byte[] Bitmap_Data = null;

The internal method void create_parts(Image img) populates the 4 above mentioned arrays. The array Bitmap_Data is populated by using an internal method byte[] ConvertToGrayscale(Image Source) which will be discussed later.

C#
static void create_parts(Image img)
{
    //Create Bitmap Data
    Bitmap_Data = ConvertToGrayscale(img);
    //Create Bitmap File Header (populate BMP_File_Header array)
    Copy_to_Index(BMP_File_Header, new byte[] 
		{ (byte)'B', (byte)'M' }, 0); //magic number
    Copy_to_Index(BMP_File_Header, BitConverter.GetBytes(BMP_File_Header.Length
                    + DIB_header.Length + Color_palette.Length 
			+ Bitmap_Data.Length), 2); //file size
    Copy_to_Index(BMP_File_Header, new byte[] { (byte)'M', (byte)'C', (byte)'A', 
    (byte)'T' }, 6); 	//reserved for application 
			//generating the bitmap file (not important)
    Copy_to_Index(BMP_File_Header, BitConverter.GetBytes(BMP_File_Header.Length
                    + DIB_header.Length + 
			Color_palette.Length), 10); //bitmap raw data offset
    //Create DIB Header (populate DIB_header array)
    Copy_to_Index(DIB_header, BitConverter.GetBytes
			(DIB_header.Length), 0); //DIB header length
    Copy_to_Index(DIB_header, BitConverter.GetBytes
			(((Bitmap)img).Width), 4); //image width
    Copy_to_Index(DIB_header, BitConverter.GetBytes
			(((Bitmap)img).Height), 8); //image height
    Copy_to_Index(DIB_header, new byte[] { (byte)1, 
		(byte)0 }, 12); //color planes. N.B. Must be set to 1
    Copy_to_Index(DIB_header, new byte[] { (byte)8, 
			(byte)0 }, 14); //bits per pixel
    Copy_to_Index(DIB_header, 
	BitConverter.GetBytes(0), 16); //compression method N.B. BI_RGB = 0
    Copy_to_Index(DIB_header, 
	BitConverter.GetBytes(Bitmap_Data.Length), 20); //length of raw bitmap data
    Copy_to_Index(DIB_header, 
	BitConverter.GetBytes(1000), 24); //horizontal resolution N.B. not important
    Copy_to_Index(DIB_header, 
	BitConverter.GetBytes(1000), 28); //vertical resolution N.B. not important
    Copy_to_Index(DIB_header, 
	BitConverter.GetBytes(256), 32); //number of colors in the palette
    Copy_to_Index(DIB_header, 
	BitConverter.GetBytes(0), 36); //number of important colors used N.B. 
					//0 = all colors are important
    //Create Color palett
    Color_palette = create_palette();
}

The internal method:

C#
bool Copy_to_Index(byte[] destination, byte[] source, int index)

copies the contents of source array to the destination array at the index of the destination array. It is used to copy a smaller array to a larger array.

C#
static bool Copy_to_Index(byte[] destination, byte[] source, int index)
{
    try
    {
        for (int i = 0; i < source.Length; i++)
        {
            destination[i + index] = source[i];
        }
        return true;
    }
    catch
    {
        return false;
    }
}

byte[] ConvertToGrayscale(Image Source)method converts the RGB pixels into shades of gray and it maps the gray pixels left to right, bottom to top.

C#
static byte[] ConvertToGrayscale(Image Source)
{
    Bitmap source = (Bitmap)Source;
    int padding = (source.Width % 4) != 0 ? 4 - 
	(source.Width % 4) : 0; //determine padding needed for bitmap file
    byte[] bytes = new byte[source.Width * source.Height + padding * 
	source.Height]; //create array to contain bitmap data with padding
    for (int y = 0; y < source.Height; y++)
    {
        for (int x = 0; x < source.Width; x++)
        {
            Color c = source.GetPixel(x, y);
            int g = Convert.ToInt32(0.3 * c.R + 0.59 * 
		c.G + 0.11 * c.B); //grayscale shade corresponding to rgb
            bytes[(source.Height - 1 - y) * source.Width + 
		(source.Height - 1 - y) * padding + x] = (byte)g;
        }
        //add the padding
        for (int i = 0; i < padding; i++)
        {
            bytes[(source.Height - y) * source.Width + 
		(source.Height - 1 - y) * padding + i] = (byte)0;
        }
    }
    return bytes;
}

The color palette is created with the following code:

C#
static byte[] create_palette()
{
    byte[] color_palette = new byte[1024];
    for (int i = 0; i < 256; i++)
    {
        color_palette[i * 4 + 0] = (byte)(i); //blue
        color_palette[i * 4 + 1] = (byte)(i); //green
        color_palette[i * 4 + 2] = (byte)(i); //red
        color_palette[i * 4 + 3] = (byte)0; //padding
    }
    return color_palette;
}

Finally the code for the public methods:

C#
//creates a grayscale bitmap file of Image specified by Path
static public bool CreateGrayBitmapFile(Image Image, string Path)
{
    try
    {
        create_parts(Image);
        //Write to file
        FileStream oFileStream;
        oFileStream = new FileStream(Path, System.IO.FileMode.OpenOrCreate);
        oFileStream.Write(BMP_File_Header, 0, BMP_File_Header.Length);
        oFileStream.Write(DIB_header, 0, DIB_header.Length);
        oFileStream.Write(Color_palette, 0, Color_palette.Length);
        oFileStream.Write(Bitmap_Data, 0, Bitmap_Data.Length);
        oFileStream.Close();
        return true;
    }
    catch
    {
        return false;
    }
}
//returns a byte array of a grey scale bitmap image
static public byte[] CreateGrayBitmapArray(Image Image)
{
    try
    {
        create_parts(Image);
        //Create the array
        byte[] bitmap_array = new byte[BMP_File_Header.Length + DIB_header.Length
                               + Color_palette.Length + Bitmap_Data.Length];
        Copy_to_Index(bitmap_array, BMP_File_Header, 0);
        Copy_to_Index(bitmap_array, DIB_header, BMP_File_Header.Length);
        Copy_to_Index(bitmap_array, Color_palette, 
		BMP_File_Header.Length + DIB_header.Length);
        Copy_to_Index(bitmap_array, Bitmap_Data, 
		BMP_File_Header.Length + DIB_header.Length + Color_palette.Length);
         return bitmap_array;
    }
    catch
    {
        return new byte[1]; //return a null single byte array if fails
    }
}

Using the Code

Here's how to use the code:

C#
static void Main(string[] args)
{
    Console.WriteLine("Working...");
    string path = Environment.CurrentDirectory + @"\new.jpg";
    Image img;
    try { img = Image.FromFile(path); }
    catch (Exception ex)
    {
        Console.Write("An Error Occurred.\nError message: " + ex.ToString());
        Console.ReadKey();
        return;
    }
    //saves as bmp file format
    GrayBMP_File.CreateGrayBitmapFile(img, Environment.CurrentDirectory + @"\gray1.bmp");
    
    //uses the image converter class created by Rajan Tawate 
    //and then saved into formats other than bmp
    byte[] gray = GrayBMP_File.CreateGrayBitmapArray(img);
    Image img2 = ImageConverter.byteArrayToImage(gray);
    img2.Save(@"grey2.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);

    Console.WriteLine("Done.");
    //Console.ReadKey();
}

License

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