C#读写BMP文件

1.BMP文件格式

        BMP文件格式组成部分:bmp文件头(14个字节) + 位图信息头(40个字节) + 调色板(由颜色索引数决定) + 位图数据(由图像尺寸决定)

位图文件头BITMAPFILEHEADER

位图信息头BITMAPINFOHEADER

调色板Palette(可选)

实际的位图数据ImageDate

        第一部分为位图文件头BITMAPFILEHEADER,是一个结构,其定义如下:

typedef struct tagBITMAPFILEHEADER {
    WORD  bfType;
    DWORD bfSize;
    WORD  bfReserved1;
    WORD  bfReserved2;
    DWORD bfOffBits;
} BITMAPFILEHEADER;

        这个结构的长度是固定的,为14个字节(WORD为无符号16位整数,DWORD为无符号32位整数),各个域的说明如下:

bfType

指定文件类型,必须是0x424D,即字符串“BM”,也就是说所有.bmp文件的头两个字节都是“BM”。

bfSize

指定文件大小,包括这14个字节。

bfReserved1

保留字,不用考虑

bfReserved2

保留字,不用考虑

bfOffBits

为从文件头到实际的位图数据的偏移字节数,前三个部分的长度之和。

        第二部分为位图信息头BITMAPINFOHEADER,也是一个结构,其定义如下:

typedef struct tagBITMAPINFOHEADER{
    DWORD  biSize;
    LONG   biWidth;
    LONG   biHeight;
    WORD   biPlanes;
    WORD   biBitCount
    DWORD  biCompression;
    DWORD  biSizeImage;
    LONG   biXPelsPerMeter;
    LONG   biYPelsPerMeter;
    DWORD  biClrUsed;
    DWORD  biClrImportant;
} BITMAPINFOHEADER;

        这个结构的长度是固定的,为40个字节(LONG为32位整数),各个域的说明如下:

biSize

指定这个结构的长度,为40。

biWidth

指定图象的宽度,单位是像素。

biHeight

指定图象的高度,单位是像素。

biPlanes

必须是1,不用考虑。

biBitCount

指定表示颜色时要用到的位数,常用的值为1(黑白二色图), 4(16色图), 8(256色), 24(真彩色图), 32(带透明通道的真彩色图)

biCompression

指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量)。一般只使用第一种不压缩的情况,即biCompression为BI_RGB的情况。

biSizeImage

指定实际的位图数据占用的字节数,其实也可以从以下的公式中计算出来:

biSizeImage=biWidth’ × biHeight

要注意的是:上述公式中的biWidth’必须是4的整倍数(所以不是biWidth,而是biWidth’,表示大于或等于biWidth的,最接近4的整倍数。举个例子,如果biWidth=240,则biWidth’=240;如果biWidth=241,biWidth’=244)。

如果biCompression为BI_RGB,则该项可能为零

biXPelsPerMeter

指定目标设备的水平分辨率,单位是每米的像素个数。

biYPelsPerMeter

指定目标设备的垂直分辨率,单位同上。

biClrUsed

指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为2的biBitCount次方。

biClrImportant

指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。

        第三部分为调色板Palette,当然,这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。

        调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有 2的biBitCount次方 个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:

typedef struct tagRGBQUAD {
    BYTE    rgbBlue;     //该颜色的蓝色分量
    BYTE    rgbGreen;    //该颜色的绿色分量
    BYTE    rgbRed;      //该颜色的红色分量
    BYTE    rgbReserved; //保留值
} RGBQUAD;

        第四部分就是实际的图像数据了。对于用到调色板的位图,图像数据就是该像素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。

要注意两点:

        (1) 每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。

        (2) 一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图像最下面一行的左边第一个像素,然后是左边第二个像素……接下来是倒数第二行左边第一个像素,左边第二个像素……依次类推 ,最后得到的是最上面一行的最右一个象素。

2.C#读写BMP代码

using System;
using System.IO;

namespace Demo_CSharp
{
    class BmpUtil
    {
        /**
         * @author   mickey
         * @category 读取BMP图像
         * @param    strFileName - [in]BMP文件路径
         *           ucImgBuf    - [out]图象数据
         *           iWidth      - [out]图像宽度
         *           iHeight     - [out]图像高度
         *           iChannels   - [out]图像通道数
         * @return    0   - 成功
         *           其他  - 失败
         * @notes    暂时只支持灰度BMP文件格式
         * */
        public static int ReadBMP(String strFileName, byte[] ucImgBuf, int[] iWidth, int[] iHeight, int[] iChannels)
        {
            byte[] bBmp = ReadData(strFileName);
            if (bBmp == null)
                return -1;
            return Bmp2Raw(bBmp, ucImgBuf, iWidth, iHeight, iChannels);
        }

        /**
         * @author   mickey
         * @category 保存BMP图像
         * @param    strFileName - [in]BMP文件路径
         *           ucImgBuf    - [in]]图象数据
         *           iWidth      - [in]图像宽度
         *           iHeight     - [in]图像高度
         *           iChannels   - [in]图像通道数
         * @return    0   - 成功
         *           其他  - 失败
         * @notes    暂时只支持灰度BMP文件格式
         * */
        public static int SaveBMP(String strFileName, byte[] ucImgBuf, int iWidth, int iHeight, int iChannels)
        {
            int iNewWidth = (iChannels * iWidth + 3) / 4 * 4;
            byte[] bBmp = new byte[iNewWidth * iHeight + 1078];
            int[] iBmpLen = new int[1];
            Raw2Bmp(bBmp, iBmpLen, ucImgBuf, iWidth, iHeight, iChannels);
            return SaveData(strFileName, bBmp, iBmpLen[0]);
        }

        public static int JUnsigned(int x)
        {
            if (x >= 0)
                return x;
            else
                return (x + 256);
        }

        /**
         * @author   mickey
         * @category BMP格式图像数据 -> 图像裸数据
         * @param    pBmp       - [in]BMP格式图像数据
         * 			 pRaw       - [out]图像裸数据
         * 			 iWidth     - [out]图像宽度
         * 			 iHeight	- [out]图像高度
         * 			 iChannels  - [out]图像通道数
         * @return     0    - 成功
         *            其他   - 失败
         * */
        public static int Bmp2Raw(byte[] pBmp, byte[] pRaw, int[] iWidth, int[] iHeight, int[] iChannels)
        {
            int i, X, Y, channels;
            byte[] head = new byte[1078];
            Array.Copy(pBmp, 0, head, 0, 54);
            if (head[0] != 0x42 || head[1] != 0x4D)
                return -1;
            int head18 = (int)head[18];
            int head19 = (int)(head[19] << 8);
            int head20 = (int)(head[20] << 8);
            int head21 = (int)(head[21] << 8);

            int head22 = (int)head[22];
            int head23 = (int)(head[23] << 8);
            int head24 = (int)(head[24] << 8);
            int head25 = (int)(head[25] << 8);
            X = JUnsigned(head18) + JUnsigned(head19) + JUnsigned(head20) + JUnsigned(head21);
            Y = JUnsigned(head22) + JUnsigned(head23) + JUnsigned(head24) + JUnsigned(head25);

            int head28 = (int)(head[28]);
            int head29 = (int)(head[29] << 8);
            channels = JUnsigned(head28) + JUnsigned(head29);
            channels = channels / 8;
            //System.out.println("channels="+channels);
            iWidth[0] = X;
            iHeight[0] = Y;
            iChannels[0] = channels;
            if (channels == 4)
                iChannels[0] = 3;
            int iNewWidth = (channels * X + 3) / 4 * 4;
            if (channels == 1)
            {
                for (i = 0; i < Y; i++)
                {
                    Array.Copy(pBmp, 1078 + (Y - 1 - i) * iNewWidth, pRaw, i * X, X);
                }
            }
            else if (channels == 3)
            {
                for (i = 0; i < Y; i++)
                {
                    Array.Copy(pBmp, 54 + (Y - 1 - i) * iNewWidth, pRaw, i * 3 * X, 3 * X);
                }
            }
            else if (channels == 4)
            {
                for (i = 0; i < Y; i++)
                {
                    for (int j = 0; j < X; j++)
                    {
                        pRaw[i * 3 * X + 3 * j] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j];
                        pRaw[i * 3 * X + 3 * j + 1] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j + 1];
                        pRaw[i * 3 * X + 3 * j + 2] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j + 2];
                    }
                }
            }
            else
            {
                return -2;
            }
            return 0;
        }

        /**
         * @author   mickey
         * @category 读取文件数据到byte数组
         * @param    filepath - [in]文件路径
         * @return   byte数组
         * */
        public static byte[] ReadData(string fileName)
        {
            using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
            {
                try
                {
                    byte[] buffer = new byte[fs.Length];
                    fs.Read(buffer, 0, (int)fs.Length);
                    return buffer;
                }
                catch (Exception ex)
                {
                    return null;
                }
            }
        }

        /**
         * @author   mickey
         * @category 图像裸数据-> BMP格式图像数据
         * @param    pBmp    - [out]BMP格式图像数据
         *           iBMPLen - [out]BMP格式图像数据长度
         * 			 pRaw    - [in]图像裸数据
         * 			 iImgX   - [in]图像宽度
         * 			 iImgY	 - [in]图像高度
         * 			 iChannels   - [in]图像通道数
         * @return     0    - 成功
         *            其他   - 失败
         * */
        public static int Raw2Bmp(byte[] pBmp, int[] iBMPLen, byte[] pRaw, int X, int Y, int iChannels)
        {
            int num;
            int i, j;
            byte[] head = new byte[1078];

            byte[] temp = { 0x42, 0x4d, // file header
                0x00, 0x00, 0x00, 0x00, // file size***
                0x00, 0x00, // reserved
                0x00, 0x00,// reserved
                0x36, 0x4, 0x00, 0x00,// head byte***
                0x28, 0x00, 0x00, 0x00,// struct size
                0x00, 0x00, 0x00, 0x00,// map width***
                0x00, 0x00, 0x00, 0x00,// map height***
                0x01, 0x00,// must be 1
                0x08, 0x00,// color count***
                0x00, 0x00, 0x00, 0x00, // compression
                0x00, 0x00, 0x00, 0x00,// data size***
                0x00, 0x00, 0x00, 0x00, // dpix
                0x00, 0x00, 0x00, 0x00, // dpiy
                0x00, 0x00, 0x00, 0x00,// color used
                0x00, 0x00, 0x00, 0x00,// color important
            };
            Array.Copy(temp, 0, head, 0, temp.Length);

            int iNewWidth = (iChannels * X + 3) / 4 * 4;
            int iHeadLen = 54;

            if (iChannels == 1)
            {
                iHeadLen = 1078;
            }
            int iFileSize = iHeadLen + iNewWidth * Y;
            num = iFileSize;
            head[2] = (byte)(num & 0xFF);
            num = num >> 8;
            head[3] = (byte)(num & 0xFF);
            num = num >> 8;
            head[4] = (byte)(num & 0xFF);
            num = num >> 8;
            head[5] = (byte)(num & 0xFF);

            num = iHeadLen;
            head[10] = (byte)(num & 0xFF);
            num = num >> 8;
            head[11] = (byte)(num & 0xFF);
            num = num >> 8;
            head[12] = (byte)(num & 0xFF);
            num = num >> 8;
            head[13] = (byte)(num & 0xFF);

            // 确定图象宽度数值
            num = X;
            head[18] = (byte)(num & 0xFF);
            num = num >> 8;
            head[19] = (byte)(num & 0xFF);
            num = num >> 8;
            head[20] = (byte)(num & 0xFF);
            num = num >> 8;
            head[21] = (byte)(num & 0xFF);
            // 确定图象高度数值
            num = Y;
            head[22] = (byte)(num & 0xFF);
            num = num >> 8;
            head[23] = (byte)(num & 0xFF);
            num = num >> 8;
            head[24] = (byte)(num & 0xFF);
            num = num >> 8;
            head[25] = (byte)(num & 0xFF);
            //BitCount
            num = iChannels * 8;
            head[28] = (byte)(num & 0xFF);
            num = num >> 8;
            head[29] = (byte)(num & 0xFF);
            //Data size
            num = Y * iNewWidth;
            head[34] = (byte)(num & 0xFF);
            num = num >> 8;
            head[35] = (byte)(num & 0xFF);
            num = num >> 8;
            head[36] = (byte)(num & 0xFF);
            num = num >> 8;
            head[37] = (byte)(num & 0xFF);

            // 确定调色板数值
            j = 0;
            for (i = 54; i < 1078; i = i + 4)
            {
                head[i] = head[i + 1] = head[i + 2] = (byte)j;
                head[i + 3] = 0;
                j++;
            }
            // 写入文件头
            Array.Copy(head, 0, pBmp, 0, iHeadLen);
            // 写入图象数据
            for (i = 0; i < Y; i++)
            {
                Array.Copy(pRaw, i * iChannels * X, pBmp,
                        iHeadLen + (Y - 1 - i) * iNewWidth, iChannels * X);
            }
            //输出文件大小
            iBMPLen[0] = iFileSize;
            return 0;
        }

       /**
        * @author   mickey
        * @category 保存数据为文件
        * @param    filepath - [in]文件路径
        * 			 buffer   - [in]数据缓存
        * 			 size     - [in]数据长度
        * @return     0      - 成功
        *            其他     - 失败
        * */
        public static int SaveData(string fileName, byte[] buffer, int size)
        {
            FileStream fs = new FileStream(fileName, FileMode.Create);
            fs.Write(buffer, 0, size);
            fs.Dispose();
            return 0;
        }
    }
}

3. C#测试工程源代码

编译器版本:VS2015

https://download.csdn.net/download/mickey2007/89017624

以下是一个简单的示例代码,可以实现将一个BMP图片转换为马赛克: ```c #include <stdio.h> #include <stdint.h> #define MOSAIC_SIZE 8 int main(int argc, char *argv[]) { if (argc != 3) { printf("Usage: %s input.bmp output.bmp\n", argv[0]); return 1; } FILE *input_file = fopen(argv[1], "rb"); if (!input_file) { printf("Failed to open input file: %s\n", argv[1]); return 1; } FILE *output_file = fopen(argv[2], "wb"); if (!output_file) { printf("Failed to open output file: %s\n", argv[2]); fclose(input_file); return 1; } // 读取BMP文件头 uint8_t header[54]; if (fread(header, 1, sizeof(header), input_file) != sizeof(header)) { printf("Failed to read BMP header\n"); fclose(input_file); fclose(output_file); return 1; } // 验证BMP文件格式 if (header[0] != 'B' || header[1] != 'M') { printf("Invalid BMP format\n"); fclose(input_file); fclose(output_file); return 1; } // 读取BMP图片数据 uint32_t width = *(uint32_t *)(header + 18); uint32_t height = *(uint32_t *)(header + 22); uint16_t bpp = *(uint16_t *)(header + 28) / 8; uint8_t *image_data = (uint8_t *)malloc(width * height * bpp); if (!image_data) { printf("Failed to allocate memory\n"); fclose(input_file); fclose(output_file); return 1; } if (fread(image_data, 1, width * height * bpp, input_file) != width * height * bpp) { printf("Failed to read BMP image data\n"); free(image_data); fclose(input_file); fclose(output_file); return 1; } // 将BMP图片数据转换为马赛克 uint32_t x, y; uint8_t *src, *dst; uint32_t mosaic_width = width / MOSAIC_SIZE; uint32_t mosaic_height = height / MOSAIC_SIZE; for (y = 0; y < mosaic_height; y++) { for (x = 0; x < mosaic_width; x++) { uint32_t i, j; uint32_t r = 0, g = 0, b = 0; for (j = 0; j < MOSAIC_SIZE; j++) { for (i = 0; i < MOSAIC_SIZE; i++) { src = image_data + ((y * MOSAIC_SIZE + j) * width + x * MOSAIC_SIZE + i) * bpp; r += src[2]; g += src[1]; b += src[0]; } } dst = image_data + ((y * MOSAIC_SIZE) * width + x * MOSAIC_SIZE) * bpp; dst[2] = r / (MOSAIC_SIZE * MOSAIC_SIZE); dst[1] = g / (MOSAIC_SIZE * MOSAIC_SIZE); dst[0] = b / (MOSAIC_SIZE * MOSAIC_SIZE); for (j = 0; j < MOSAIC_SIZE; j++) { for (i = 0; i < MOSAIC_SIZE; i++) { if (i == 0 && j == 0) { continue; } dst = image_data + ((y * MOSAIC_SIZE + j) * width + x * MOSAIC_SIZE + i) * bpp; dst[2] = dst[-bpp * MOSAIC_SIZE + 2]; dst[1] = dst[-bpp * MOSAIC_SIZE + 1]; dst[0] = dst[-bpp * MOSAIC_SIZE + 0]; } } } } // BMP文件头 if (fwrite(header, 1, sizeof(header), output_file) != sizeof(header)) { printf("Failed to write BMP header\n"); free(image_data); fclose(input_file); fclose(output_file); return 1; } // 入马赛克数据 if (fwrite(image_data, 1, width * height * bpp, output_file) != width * height * bpp) { printf("Failed to write BMP image data\n"); free(image_data); fclose(input_file); fclose(output_file); return 1; } free(image_data); fclose(input_file); fclose(output_file); return 0; } ``` 该程序使用标准C库函数实现了将一个BMP图片转换为马赛克的功能。具体实现思路是将图片按照一定的分辨率划分为若干个小块,然后对每个小块取平均RGB值,最终将小块的颜色统一设置为该平均值。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值