Introduction and Background
Since beginning programming a couple of years ago, I have always made it my intention to create an image viewer. Although I started out in C and C++,
I have moved onto programming in C# and was frustrated by the lack of information on reading image formats in that language. After learning more about programming
and working on a separate project to read vCards, I have come back to creating an image viewer and this time with far more success than my earlier attempts.
Here, I am showing you my own implementation of a Stardent Corp. AVS Bitmap Image viewer, written solely in C# without third-party libraries, and in what
I hope is a simple, fairly easy-to-follow way.
Stardent was an American corporation back in the early 90s which released a product called Application Visualisation Software or AVS.
I have no idea what this software did (the name sounds a bit like marketing-speak to me), but I did manage to find a brief description of the format
at http://paulbourke.net/dataformats/avs_x/. It's basically a raw image format, with the only information
about the data being the height and width and the actual data as 32-bit ARGB.
Using the Code
To begin, we need to find the dimensions. They are stored as 4-bytes each in Little-Endian order, so we need to read those in.
To keep with good practice, I've set up a struct,
public Int32 Width;
public Int32 Height;
The next part is a function that simply reads this data into the program. The
BinaryEndian class can be found in the source under tools\BinaryEndian.cs.
It reverses the order of the bytes that I read in and is actually taken from this discussion
at bytes.com: http://bytes.com/topic/c-sharp/answers/454822-binarywriter-reader-big-endian.
It simply reverses the order of the bytes if you ask it to.
AVS_X_Header AVSxHeader(string fileName)
FileStream file = new FileStream(
BinaryReader reader = new BinaryReader(file);
tools.BinaryEndian r = new tools.BinaryEndian();
AVS_X_Header Header = new AVS_X_Header();
Header.Width = r.combine(
Header.Height = r.combine(
Next up: reading the data. There is no compression and the data is stored as ARGB in that order with one byte per channel. We simply create four byte arrays - one for each
channel - and read in the data for each pixel.
out byte pixelDataA,
out byte pixelDataR,
out byte pixelDataG,
out byte pixelDataB)
FileStream file = new FileStream(fname, FileMode.Open, FileAccess.Read);
using (BinaryReader r = new BinaryReader(file))
r.BaseStream.Position = 8;
pixelDataA = new byte[width * height];
pixelDataR = new byte[width * height];
pixelDataG = new byte[width * height];
pixelDataB = new byte[width * height];
for (int i = 0; i < (height * width); i++)
pixelDataA[i] = r.ReadByte(); pixelDataR[i] = r.ReadByte(); pixelDataG[i] = r.ReadByte(); pixelDataB[i] = r.ReadByte(); }
Our last step is to create a bitmap from it. Here I'm using an unsafe code block, you could always use
SetPixel or come up with an
IntPtr of pixel data
if you don't like unsafe code. I quickly found that Microsoft's bitmaps don't do ARGB, despite having a format of
Format32ARGB. Instead, the format is BGRA (if someone knows
why these bitmaps work like this, please share).
int stride = ((width * ((1 + 7) / 8)) + 4 -
((width * ((1 + 7) / 8)) % 4));
System.Drawing.Bitmap B = new System.Drawing.Bitmap(
System.Drawing.Imaging.BitmapData bmd = B.LockBits(
new System.Drawing.Rectangle(0, 0, B.Width, B.Height),
int i = 0;
for (int y = 0; y < bmd.Height; y++)
byte* row = (byte*)bmd.Scan0 + (y * bmd.Stride);
for (int x = 0; x < (bmd.Width*4); x+=4, i++;)
row[x + 3] = pixelsA[i]; row[x + 2] = pixelsR[i]; row[x + 1] = pixelsG[i]; row[x] = pixelsB[i]; }
MemoryStream ms = new MemoryStream();
ms.Position = 0;
System.Windows.Media.Imaging.BitmapImage bi =
bi.StreamSource = ms;
Finally, it is all called from one main function in the class. This returns the
BitmapImage from our bitmap class back to wherever it is being called from.
Specify this as the source of a WPF
Image element and you should find that the image displays in your application. If you're using Windows Forms, then all you need
to do is cast the
Bitmap we unlocked as an
Image and specify that as the
Image of a
public System.Windows.Media.Imaging.BitmapImage readAVS_X_File(string fname)
AVS_X_Header AvsXH = AVSxHeader(fname);
byte A, R, G, B;
readData(fname, AvsXH.Width, AvsXH.Height, out A, out R, out G, out B);
return bmpFromBinaryPBM(AvsXH.Width, AvsXH.Height, A, R, G, B);
Oh, and to call all of this:
avs_x avs_x = new avs_x();
image1.Source = avs_x.readAVS_X_File(openDialog.FileName);
Well, I hope this has been some help to a few people. If all goes well, I intend to put a few more articles on image decoding on here, so any constructive
feedback is good. If there are any questions, don't hesitate to leave a comment.
Points of Interest
There weren't any particular points of interest or annoyance with this project, but I think it's nice to see an archaic format like this being worked with in C#.
- 19 Dec. 2011 (16:08 GMT): First version.