65.9K
CodeProject 正在变化。 阅读更多。
Home

在 C# 和 WPF 中解码 Stardent AVS 位图图像 (.avs, .x) 文件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2011 年 12 月 19 日

CPOL

3分钟阅读

viewsIcon

16455

downloadIcon

474

一篇面向中级程序员的文章,介绍如何解码 Stardent Corp. 的 AVS 位图图像格式。

引言和背景

自从几年前开始编程以来,我一直致力于创建一个图像查看器。虽然我最初使用 C 和 C++,但我已经转向使用 C# 编程,并且对在该语言中缺少读取图像格式的信息感到沮丧。在学习了更多关于编程的知识并致力于一个单独的项目来读取 vCard 后,我回到了创建图像查看器的工作中,并且这次比我之前的尝试取得了更大的成功。

在这里,我向你展示我自己实现的 Stardent Corp. AVS 位图图像查看器,完全用 C# 编写,没有第三方库,并且我希望以一种简单、易于理解的方式编写。

main.png

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,并将其指定为 PictureBoxImage

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):第一个版本。
© . All rights reserved.