32-bit BMP转PNG之C#版

.Net库中默认的Bitmap类是不支持读取32-bit BMP的, 注意是不支持直接读, 本身是可以处理ARGB结构的. 既然这样那就自己一个个字节读吧, 反正BMP又不需要解码一个一个像素挨个读取便是.

不过读取了每个像素的各个分量值后如果挨个setPixel()的效率是不可忍受的. 且不说双层循环本来效率就很差, setPixel()本来就不快. 还好微软自己也觉得这速度太不靠谱, 留下了用指针直接操作的方法.

主要代码如下:

using (FileStream fs = new FileStream(file, FileMode.Open))
{
    //判断像素深度, 仅对32-bit BMP操作
    fs.Position = 0x1C;
    byte[] byteDepth = new byte[4];
    fs.Read(byteDepth, 0, 4);
    int depth = BitConverter.ToInt32(byteDepth, 0);
    if (depth != 32)
    {
        Console.Write("Not 32-bit bmp file.n");
        continue;
    }
    else
    {
        DateTime dt = DateTime.Now;
    
        //Pixel数据区开始位置
        fs.Position = 0xA;
        byte[] bytePixelArrayStart = new byte[4];
        fs.Read(bytePixelArrayStart, 0, 4);
        int posPixelArrayStart = BitConverter.ToInt32(bytePixelArrayStart, 0);
    
        //图片宽度
        fs.Position = 0x12;
        byte[] byteWidth = new byte[4];
        fs.Read(byteWidth, 0, 4);
        int width = BitConverter.ToInt32(byteWidth, 0);
    
        //图片高度
        fs.Position = 0x16;
        byte[] byteHeight = new byte[4];
        fs.Read(byteHeight, 0, 4);
        int height = BitConverter.ToInt32(byteHeight, 0);

        fs.Position = posPixelArrayStart;
        //建立一个与原图等大的Bitmap对象, 设置像素格式为32-bitARGB
        using (Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb))
        {
            //获取该Bitmap对象的BitmapData数据
            BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bmp.PixelFormat);
            //获取数据开始的指针
            IntPtr ptr = bmpData.Scan0;
            //计算数据区的长度
            int imgLen = Math.Abs(bmpData.Stride) * bmp.Height;
            byte[] pixels = new byte[imgLen];
            
            //逐个Byte读取值并写入对应像素的各个通道
            for (int counter = 0; counter < pixels.Length; counter++)
            {
                byte[] color = new byte[1];
                fs.Read(color, 0, 1);
                pixels[counter] = color[0];
            }
            
            //直接将新的Pixel数据复制入BitmapData对象, 覆盖原有数据
            System.Runtime.InteropServices.Marshal.Copy(pixels, 0, ptr, imgLen);
            bmp.UnlockBits(bmpData);
    
            //这样生成的图时y方向上下颠倒的, 需要做一次翻转
            //这样的翻转不会导致图片质量的损失
            bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
    
            //转换成PNG格式并写入新文件
            string pngFile = string.Format(@"{0}{1}.png", Path.GetDirectoryName(file), Path.GetFileNameWithoutExtension(file));
            bmp.Save(pngFile, ImageFormat.Png);
    
            Console.Write("Used: {0}ms.n", (DateTime.Now - dt).TotalMilliseconds);
        }
    }
}

测试表明使用指针直接操作Pixel数据后速度有显著提升.

另有个小工具叫AlphaConv也可以实现32-bit BMP和PNG互转, Delphi写的, 调的libpng, 效率应该不错, 可惜只有图形界面.

有空来试试直接用C调libpng来实现一下, 看能比.Net快多少><.

Advertisement

One thought on “32-bit BMP转PNG之C#版

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s