Bootstrap

C#加速Bitmap存图

如果希望大幅提高图像保存速度,特别是在处理非常大的图像时,可以尝试以下更直接、更高效的方法:

1. 避免使用 Bitmap 类的 Save 方法
Bitmap.Save 方法的速度受限于 GDI+ 库的操作,尤其是对于非常大的图像,它可能会经历内存分配、像素格式转换等多重开销。我们可以通过直接操作图像数据流来绕过这些问题。

2. 直接将图像数据写入文件(原始像素数据)
对于大型图像,您可以直接将图像的像素数据转换为 BMP 格式的二进制流,并快速写入文件。BMP 格式是一个非常简单的无压缩格式,因此您可以手动构造 BMP 文件头和像素数据流。

下面是一个使用 原始图像数据流 写入RGB-BMP 文件的例子:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        // 创建一个较大的 Bitmap 对象(例如 5000x5000)
        Bitmap bitmap = new Bitmap(5000, 5000);
        
        // 在这里填充图像,模拟数据
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.Clear(Color.Blue);  // 蓝色背景
        }

        // 保存为 BMP 格式
        SaveBitmapAsBmpFast(bitmap, "output.bmp");
    }

    static void SaveBitmapAsBmpFast(Bitmap bitmap, string filePath)
    {
        // 锁定位图数据
        Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
        BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat);

        // 创建文件流
        using (FileStream fs = new FileStream(filePath, FileMode.Create))
        {
            // 计算图像文件大小
            int rowSize = bitmapData.Stride;
            int dataSize = rowSize * bitmap.Height;

            // BMP 文件头
            byte[] fileHeader = new byte[14] {
                0x42, 0x4D,             // 'BM'标识符
                0, 0, 0, 0,             // 文件大小(稍后设置)
                0, 0,                   // 保留字段
                0, 0,                   // 保留字段
                0x36, 0x00, 0x00, 0x00  // 位图数据的偏移量
            };
            fs.Write(fileHeader, 0, fileHeader.Length);

            // 信息头
            byte[] infoHeader = new byte[40] {
                0x28, 0x00, 0x00, 0x00,  // 信息头大小
                (byte)(bitmap.Width & 0xFF), (byte)((bitmap.Width >> 8) & 0xFF), 0, 0, // 图像宽度
                (byte)(bitmap.Height & 0xFF), (byte)((bitmap.Height >> 8) & 0xFF), 0, 0, // 图像高度
                0x01, 0x00,              // 颜色平面
                0x18, 0x00,              // 每像素位数(24位,RGB)
                0x00, 0x00, 0x00, 0x00,  // 压缩方法
                0x00, 0x00, 0x00, 0x00,  // 图像大小(0表示未压缩)
                0x13, 0x0B, 0x00, 0x00,  // 水平分辨率(假设为72 DPI)
                0x13, 0x0B, 0x00, 0x00,  // 垂直分辨率
                0x00, 0x00, 0x00, 0x00,  // 调色板数量(0表示不使用调色板)
                0x00, 0x00, 0x00, 0x00   // 重要颜色数量
            };
            fs.Write(infoHeader, 0, infoHeader.Length);

            // 写入像素数据
            byte[] buffer = new byte[dataSize];
            Marshal.Copy(bitmapData.Scan0, buffer, 0, buffer.Length);
            fs.Write(buffer, 0, buffer.Length);
        }

        // 解锁位图数据
        bitmap.UnlockBits(bitmapData);

        Console.WriteLine("Image saved as BMP (fast).");
    }
}

下面是一个使用 原始图像数据流 写入Gray-BMP 文件的例子:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;

class Program
{
    static void Main()
    {
        // 创建一个灰度图像的 Bitmap 对象(例如 5000x5000)
        Bitmap bitmap = new Bitmap(5000, 5000, PixelFormat.Format8bppIndexed);

        // 创建一个调色板,包含256个灰度颜色
        ColorPalette palette = bitmap.Palette;
        for (int i = 0; i < 256; i++)
        {
            palette.Entries[i] = Color.FromArgb(i, i, i);  // 设置灰度颜色
        }
        bitmap.Palette = palette;

        // 填充灰度图像
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            for (int x = 0; x < bitmap.Width; x++)
            {
                for (int y = 0; y < bitmap.Height; y++)
                {
                    // 创建一个灰度值(在此示例中,使用随机值)
                    int grayValue = (x + y) % 256;  // 这里使用简单的算法产生灰度值,实际应用中可以按需要设置
                    bitmap.SetPixel(x, y, Color.FromArgb(grayValue, grayValue, grayValue));
                }
            }
        }

        // 保存为 BMP 格式
        SaveBitmapAsBmpFast(bitmap, "output_gray_8bpp.bmp");
    }

    static void SaveBitmapAsBmpFast(Bitmap bitmap, string filePath)
    {
        // 锁定位图数据
        Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
        BitmapData bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat);

        // 创建文件流
        using (FileStream fs = new FileStream(filePath, FileMode.Create))
        {
            // 计算图像文件大小
            int rowSize = bitmapData.Stride;
            int dataSize = rowSize * bitmap.Height;

            // BMP 文件头
            byte[] fileHeader = new byte[14] {
                0x42, 0x4D,             // 'BM'标识符
                0, 0, 0, 0,             // 文件大小(稍后设置)
                0, 0,                   // 保留字段
                0, 0,                   // 保留字段
                0x36, 0x00, 0x00, 0x00  // 位图数据的偏移量
            };
            fs.Write(fileHeader, 0, fileHeader.Length);

            // 信息头
            byte[] infoHeader = new byte[40] {
                0x28, 0x00, 0x00, 0x00,  // 信息头大小
                (byte)(bitmap.Width & 0xFF), (byte)((bitmap.Width >> 8) & 0xFF), 0, 0, // 图像宽度
                (byte)(bitmap.Height & 0xFF), (byte)((bitmap.Height >> 8) & 0xFF), 0, 0, // 图像高度
                0x01, 0x00,              // 颜色平面
                0x08, 0x00,              // 每像素位数(8位灰度图)
                0x00, 0x00, 0x00, 0x00,  // 压缩方法
                0x00, 0x00, 0x00, 0x00,  // 图像大小(0表示未压缩)
                0x13, 0x0B, 0x00, 0x00,  // 水平分辨率(假设为72 DPI)
                0x13, 0x0B, 0x00, 0x00,  // 垂直分辨率
                0x00, 0x00, 0x00, 0x00,  // 调色板数量(0表示不使用调色板)
                0x00, 0x00, 0x00, 0x00   // 重要颜色数量
            };
            fs.Write(infoHeader, 0, infoHeader.Length);

            // 调色板(256个灰度颜色)
            byte[] colorTable = new byte[1024];  // 256个条目,每个条目4字节(ARGB)
            for (int i = 0; i < 256; i++)
            {
                colorTable[i * 4] = (byte)i;        // R
                colorTable[i * 4 + 1] = (byte)i;    // G
                colorTable[i * 4 + 2] = (byte)i;    // B
                colorTable[i * 4 + 3] = 0x00;       // A (透明度)
            }
            fs.Write(colorTable, 0, colorTable.Length);

            // 写入像素数据
            byte[] buffer = new byte[dataSize];
            Marshal.Copy(bitmapData.Scan0, buffer, 0, buffer.Length);
            fs.Write(buffer, 0, buffer.Length);
        }

        // 解锁位图数据
        bitmap.UnlockBits(bitmapData);

        Console.WriteLine("Grayscale image (8bpp) saved as BMP (fast).");
    }
}

;