GDI+
FAQ: Converting an RGB image to 1 bit-per-pixel
monochrome.
One bit per pixel images are essentially
an indexed format image having two entries in the colour palette. Unlike a
GIF or other 256 colour indexed format which uses a single byte to access
one of up-to 256 colours in the palette, the one bit per pixel format uses
only a single bit to select colour A or colour B from the palette.
Often used in Fax formats, one bit per pixel images
are very compact with one byte of information representing eight
pixels.
Very often, a more complex RGB image can be used for
the basic raw-image that is used to define the 1 bpp array but this
operation in GDI+ and .NET systems is a little more complex than one would
hope because you cannot simply get the Graphics object for an indexed
image and draw on it. In order to do this conversion, you must manipulate
the individual bits of the image by locking it's byte array and selecting
the correct bit for the desired pixel.
The LockBits method can be used to obtain a BitmapData object which contains the address of the bitmap in memory, the
dimensions and particularly, the stride of the image. All images are
stored in an array that fills memory to a four-byte boundary so the stride
is used to calculate where each successive line begins in the array. A
specific line may be addressed by the formula LineAddress=stride*linenumber and NOT LineAddress =
pixeldepth*imagewidth*linenumber as many people assume.
For our single bit per pixel image, the pixel
indexing is further complicated by the need to select a specific bit in a
byte that contains up to 8 pixels (it may have less than 8 pixels because
it may be on the end of a scan-line). To perform this selection, a bit
mask is used and rotated into position so that the correct bit may be set
or reset as desired.
The simple application shown in listing 1 enables
you to load any image into a PictureBox control situated on the left of
the form. Once loaded, the image is scanned pixel by pixel to check the
brightness of the colours contained in the image. If a pixel is darker
than 50% brightness, a black pixel is set in the corresponding pixel of a
black and white, single bit per pixel image stored in the rightmost
PictureBox on the form. Between the two PictureBox controls, a Splitter
enables you to see more or less of the original image for comparison with
the converted one.
Note in particular how the single bit per pixel
image is created in the pictureBox1_Click handler and how the
SetIndexedPixel method is used to access the individual pixel bits in the
monochrome image.
To compile this source you will need to allow the
use of unsafe code blocks in the compiler or on the command
line. Listing 1:
using
System;
using
System.Drawing;
using
System.Drawing.Drawing2D;
using
System.Drawing.Imaging;
using
System.Collections;
using
System.ComponentModel;
using
System.Windows.Forms;
using
System.Data;
namespace
_1bitmap
{
/// <summary>
/// Summary
description for Form1.
/// </summary>
public class
Form1 : System.Windows.Forms.Form
{
/// <summary>
/// Required
designer variable.
/// </summary>
private System.ComponentModel.Container
components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after
InitializeComponent call
//
SetStyle(ControlStyles.ResizeRedraw,true);
}
/// <summary>
/// Clean up any
resources being used.
/// </summary>
protected override void
Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region
Windows Form Designer generated code
/// <summary>
/// Required method
for Designer support - do not modify
/// the contents of
this method with the code editor.
/// </summary>
private void
InitializeComponent()
{
this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.splitter1 = new System.Windows.Forms.Splitter();
this.pictureBox2 = new System.Windows.Forms.PictureBox();
this.SuspendLayout();
//
// pictureBox1
//
this.pictureBox1.BackColor =
System.Drawing.Color.White;
this.pictureBox1.Dock =
System.Windows.Forms.DockStyle.Left;
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(100, 397);
this.pictureBox1.TabIndex = 0;
this.pictureBox1.TabStop = false;
this.pictureBox1.Click += new System.EventHandler(this.pictureBox1_Click);
//
// splitter1
//
this.splitter1.Location = new System.Drawing.Point(100, 0);
this.splitter1.Name = "splitter1";
this.splitter1.Size = new System.Drawing.Size(3, 397);
this.splitter1.TabIndex = 1;
this.splitter1.TabStop = false;
//
// pictureBox2
//
this.pictureBox2.BackColor =
System.Drawing.Color.FromArgb(((System.Byte)(128)), ((System.Byte)(255)),
((System.Byte)(255)));
this.pictureBox2.Dock =
System.Windows.Forms.DockStyle.Fill;
this.pictureBox2.Location = new System.Drawing.Point(103, 0);
this.pictureBox2.Name = "pictureBox2";
this.pictureBox2.Size = new System.Drawing.Size(473, 397);
this.pictureBox2.TabIndex = 2;
this.pictureBox2.TabStop = false;
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(576, 397);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.pictureBox2,
this.splitter1,
this.pictureBox1});
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// The main entry
point for the application.
/// </summary>
[STAThread]
static void
Main()
{
Application.Run(new Form1());
}
protected unsafe void
SetIndexedPixel(int x,int y,BitmapData bmd, bool pixel)
{
byte* p=(byte*)bmd.Scan0.ToPointer();
int index=y*bmd.Stride+(x>>3);
byte mask=(byte)(0x80>>(x&0x7));
if(pixel)
p[index]|=mask;
else
p[index]&=(byte)(mask^0xff);
}
private System.Windows.Forms.PictureBox
pictureBox1;
private System.Windows.Forms.Splitter
splitter1;
private System.Windows.Forms.PictureBox
pictureBox2;
Bitmap bm;
private void
Form1_Load(object sender,
System.EventArgs e)
{
}
private void
pictureBox1_Click(object sender,
System.EventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter="Image
files|*.bmp;*.gif;*.jpg";
if(dlg.ShowDialog()==DialogResult.OK)
{
Bitmap img =
(Bitmap)Image.FromFile(dlg.FileName);
this.pictureBox1.Image=img;
bm=new Bitmap(this.pictureBox1.Image.Width,this.pictureBox1.Image.Height,PixelFormat.Format1bppIndexed);
BitmapData
bmd=bm.LockBits(new Rectangle(0,
0,
bm.Width, bm.Height), ImageLockMode.ReadWrite,
PixelFormat.Format1bppIndexed );
for(int
y=0;y<img.Height;y++)
{
for(int
x=0;x<img.Width;x++)
{
if(img.GetPixel(x,y).GetBrightness()>0.5f)
this.SetIndexedPixel(x,y,bmd,true);
}
}
bm.UnlockBits(bmd);
this.pictureBox2.Image=bm;
}
}
}
}
The image shown in Figure 1 shows this application at work.
Back to the GDI+
FAQ
|