在 C# 和 WPF 中解码 Stardent AVS 位图图像 (.avs, .x) 文件
一篇面向中级程序员的文章,介绍如何解码 Stardent Corp. 的 AVS 位图图像格式。
引言和背景
自从几年前开始编程以来,我一直致力于创建一个图像查看器。虽然我最初使用 C 和 C++,但我已经转向使用 C# 编程,并且对在该语言中缺少读取图像格式的信息感到沮丧。在学习了更多关于编程的知识并致力于一个单独的项目来读取 vCard 后,我回到了创建图像查看器的工作中,并且这次比我之前的尝试取得了更大的成功。
在这里,我向你展示我自己实现的 Stardent Corp. AVS 位图图像查看器,完全用 C# 编写,没有第三方库,并且我希望以一种简单、易于理解的方式编写。
Stardent 是一家 90 年代初期的美国公司,发布了一款名为 Application Visualisation Software 或 AVS 的产品。我不知道这个软件是做什么的(这个名字听起来有点像营销术语),但我确实设法在 http://paulbourke.net/dataformats/avs_x/ 找到了该格式的简要描述。它基本上是一种原始图像格式,关于数据的唯一信息是高度和宽度,以及作为 32 位 ARGB 的实际数据。
Using the Code
首先,我们需要找到尺寸。它们以小端顺序存储为每个 4 字节,因此我们需要读取它们。为了保持良好的实践,我设置了一个结构体,AVS_X_Header
struct AVS_X_Header
{
public Int32 Width;
public Int32 Height;
};
下一部分是一个简单地将数据读入程序的函数。BinaryEndian
类可以在源代码的 tools\BinaryEndian.cs 下找到。它反转了我读取的字节顺序,实际上是从 bytes.com 的这个讨论中获取的: http://bytes.com/topic/c-sharp/answers/454822-binarywriter-reader-big-endian. 它只是在您要求时反转字节的顺序。
AVS_X_Header AVSxHeader(string fileName)
{
FileStream file = new FileStream(
fileName,
FileMode.Open,
FileAccess.Read);
BinaryReader reader = new BinaryReader(file);
tools.BinaryEndian r = new tools.BinaryEndian();
AVS_X_Header Header = new AVS_X_Header();
reader.BaseStream.Seek(0, SeekOrigin.Begin);
Header.Width = r.combine(
reader,
4,
tools.BinaryEndian.ByteOrder.LittleEndian);
Header.Height = r.combine(
reader,
4,
tools.BinaryEndian.ByteOrder.LittleEndian);
return Header;
}
下一步:读取数据。没有压缩,数据以 ARGB 格式存储,每个通道一个字节。我们简单地创建四个字节数组 - 每个通道一个 - 并读取每个像素的数据。
void readData(
string fname,
int width,
int height,
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;
//skip height and width (in32 = 4 bytes)
//width * height is total number of pixels
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++)
{
//For each pixel, there are 4 bytes
//Remember ReadByte() advances the position in the BaseStream
//by a byte.
pixelDataA[i] = r.ReadByte();//read a byte (a)
pixelDataR[i] = r.ReadByte();//read another byte (r)
pixelDataG[i] = r.ReadByte();//read yet another byte (g)
pixelDataB[i] = r.ReadByte();//read a final byte (b)
}
}
}
我们的最后一步是从它创建一个位图。在这里我使用了一个不安全的代码块,如果你不喜欢不安全的代码,你可以随时使用 SetPixel
或提出像素数据的 IntPtr
。我很快发现 Microsoft 的位图不做 ARGB,尽管具有 Format32ARGB
格式。相反,格式是 BGRA(如果有人知道为什么这些位图是这样工作的,请分享)。
System.Windows.Media.Imaging.BitmapImage bmpFromBinaryPBM(
int width,
int height,
byte[] pixelsA,
byte[] pixelsR,
byte[] pixelsG,
byte[] pixelsB)
{
int stride = ((width * ((1 + 7) / 8)) + 4 -
((width * ((1 + 7) / 8)) % 4));
System.Drawing.Bitmap B = new System.Drawing.Bitmap(
width,
height,
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
unsafe
{
System.Drawing.Imaging.BitmapData bmd = B.LockBits(
new System.Drawing.Rectangle(0, 0, B.Width, B.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite,
B.PixelFormat);
int i = 0;
for (int y = 0; y < bmd.Height; y++)
{
//'Y' is the number widths that we have crossed and row is a
//pointer to the place in memory where the data for a row is
//found. 'x' is an integer that is used to index the pointer
//effectively being added to the address (pointed to by row).
//The memory at this address is then written with our pixel
//values.
byte* row = (byte*)bmd.Scan0 + (y * bmd.Stride);
for (int x = 0; x < (bmd.Width*4); x+=4, i++;)
{
row[x + 3] = pixelsA[i];//Alpha (Transparency Channel)
row[x + 2] = pixelsR[i];//Red Channel
row[x + 1] = pixelsG[i];//Green channel
row[x] = pixelsB[i];//Blue channel
}
//Remember that there are four bytes per pixel, so we set
//each of those bytes in one loop.
}
B.UnlockBits(bmd);
}
MemoryStream ms = new MemoryStream();
B.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
ms.Position = 0;
System.Windows.Media.Imaging.BitmapImage bi =
new System.Windows.Media.Imaging.BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
return bi;
}
最后,它全部从类中的一个主函数调用。这会将 BitmapImage
从我们的位图类返回到调用它的任何地方。将它指定为 WPF Image
元素的源,您应该会发现图像显示在您的应用程序中。如果您使用的是 Windows Forms,那么您所需要做的就是将我们解锁的 Bitmap
转换为 Image
,并将其指定为 PictureBox
的 Image
。
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);
}
哦,要调用所有这些
avs_x avs_x = new avs_x();
image1.Source = avs_x.readAVS_X_File(openDialog.FileName);
好吧,我希望这对一些人有所帮助。如果一切顺利,我打算在此处发布更多关于图像解码的文章,所以任何建设性的反馈都很好。如果有任何问题,请随时发表评论。
关注点
这个项目没有任何特别的兴趣点或烦恼点,但我认为很高兴看到像这样的古老格式在 C# 中被使用。
历史
- 2011 年 12 月 19 日(16:08 GMT):第一个版本。