C/C++读写BMP文件

BMP文件格式

  读写BMP格式的图片需要首先了解BMP图片的存储格式。可以参考维基百科上的介绍。
  BMP文件主要有文件头(File Header)、信息头(DIB Header)、调色板(Color Table)和像素阵列(Pixel Array)组成。大部分情况下我们需要用的就是每个像素的数据。
  可以从图中观察到,文件头中的File Offset to PixelArray可以直接得知Pixel Array的位置。信息头中有图像的宽、高和位深度的信息。像素阵列中的数据是一行一行组织的,每行的长度都是4字节的整数倍,如果一行的像素大小不是4字节的整数倍,还会再后面加Padding。
在这里插入图片描述

BMP读写

  了解了BMP文件的格式后,就可以据此编写相应的读写函数了。我准备写两个函数分别负责读和写。如下面的头文件所示:

/**
 * @file bmpRw.h
 * @author Jiandong Qiu (1335521934@qq.com)
 * @brief 
 * @version 0.1
 * @date 2022-10-07
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#ifndef _BMPRW_H_
#define _BMPRW_H_

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief read bmp file, read data array into pData
 * user should allocate space outsize of function
 * 
 * @param fileName bmp file name
 * @param pData pre-allocated data pointer
 * @return int return 0 for OK
 */
int readBmp(char *fileName, void *pData);

/**
 * @brief write bmp file, user should prepare dstInfo and dstHead before
 * call this function, this function only support dedicated type of bmp file.
 * 
 * @param fileName bmp file name
 * @param pData bmp data
 * @return int return 0 for OK
 */
int writeBmp(char *fileName, void *pData);

#ifdef __cplusplus
}
#endif

#endif

  读BMP文件的函数可以通过指定图片名,将图片中的像素数据读到一个指针指向的地址空间中。用户需要确保这个地址空间足够大。
  写BMP文件的函数同样需要由用户指定文件名,然后将一个指针指向的一块连续的数据写到BMP文件中,考虑到实际使用中图片的分辨率一般不会发生变化,所以这个写BMP文件的函数只支持特定的分辨率大小,文件信息头是由固定的常量给出的。根据实际使用情况需要对源文件中的文件信息头dstInfo进行修改。
  下面是源文件的实现:

/**
 * @file bmpRw.c
 * @author Jiandong Qiu (1335521934@qq.com)
 * @brief 
 * @version 0.1
 * @date 2022-10-07
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#include "bmpRw.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

typedef struct __attribute__((packed)) BITMAPFILEHEADER  
{   
    uint16_t bfType;   
    uint32_t bfSize;   
    uint16_t bfReserved1;   
    uint16_t bfReserved2;   
    uint32_t bfOffBits;   
}BITMAPFILEHEADER;   
 
typedef struct __attribute__((packed)) BITMAPINFOHEADER  
{   
    uint32_t biSize;   
    uint32_t biWidth;   
    uint32_t biHeight;   
    uint16_t biPlanes;   
    uint16_t biBitCount;   
    uint32_t biCompression;   
    uint32_t biSizeImage;   
    uint32_t biXPelsPerMeter;   
    uint32_t biYPelsPerMeter;   
    uint32_t biClrUsed;   
    uint32_t biClrImportant;   
}BITMAPINFOHEADER;

const BITMAPFILEHEADER dstHead = {
    .bfType = 19778,
    .bfSize = 766136,
    .bfReserved1 = 0,
    .bfReserved2 = 0,
    .bfOffBits = 54};

const BITMAPINFOHEADER dstInfo = {
    .biSize = 40,
    .biWidth = 639,
    .biHeight = 399,
    .biPlanes = 1,
    .biBitCount = 24,
    .biCompression = 0,
    .biSizeImage = 766082,
    .biXPelsPerMeter = 3779,
    .biYPelsPerMeter = 3779,
    .biClrUsed = 0,
    .biClrImportant = 0};

/**
 * @brief read bmp file, read data array into pData
 * user should allocate space outsize of function
 * 
 * @param fileName bmp file name
 * @param pData pre-allocated data pointer
 * @return int return 0 for OK
 */
int readBmp(char *fileName, void *pData)
{
    if(!fileName || !pData){
        return -1;
    }
    int i;
    BITMAPFILEHEADER head;
    BITMAPINFOHEADER info;
    size_t nElemSize;
    size_t nPaddingSize;

    FILE *fp = fopen(fileName, "rb");
    if (fp == NULL){
        return -1;
    }

    fread(&head, sizeof(BITMAPFILEHEADER), 1, fp);
    fread(&info, sizeof(BITMAPINFOHEADER), 1, fp);

    nElemSize = info.biBitCount / 8;
    nPaddingSize = ((info.biWidth * nElemSize + 3) & (size_t)-4) - info.biWidth * nElemSize;

    // move fp to data array
    fseek(fp, head.bfOffBits, SEEK_SET);
    for (i = info.biHeight - 1; i >= 0; --i){
        fread(pData + i * info.biWidth * nElemSize, nElemSize, info.biWidth, fp);
        // skip padding
        fseek(fp, nPaddingSize, SEEK_CUR);
    }

    fclose(fp);
    return 0;
}

/**
 * @brief write bmp file, user should prepare dstInfo and dstHead before
 * call this function, this function only support dedicated type of bmp file.
 * 
 * @param fileName bmp file name
 * @param pData bmp data
 * @return int return 0 for OK
 */
int writeBmp(char *fileName, void *pData)
{
    if (!fileName || !pData) {
        return -1;
    }

    int i;
    size_t nElemSize;
    size_t nPaddingSize;
    FILE *fp = fopen(fileName, "wb");
    if (fp == NULL) {
        return -1;
    }

    fwrite(&dstHead, 1, sizeof(BITMAPFILEHEADER), fp);
    fwrite(&dstInfo, 1, sizeof(BITMAPINFOHEADER), fp);

    nElemSize = dstInfo.biBitCount / 8;
    nPaddingSize = ((dstInfo.biWidth * nElemSize + 3) & (size_t)-4) - dstInfo.biWidth * nElemSize;
    uint8_t *pPadding = (uint8_t *)malloc(sizeof(uint8_t) * nPaddingSize);
    if(!pPadding)
        return -1;
    memset(pPadding, 0, sizeof(uint8_t) * nPaddingSize);

    for (i = dstInfo.biHeight - 1; i >= 0; --i) {
        fwrite(pData + i * dstInfo.biWidth * nElemSize, nElemSize, dstInfo.biWidth, fp);
        // add padding
        fwrite(pPadding, 1, nPaddingSize, fp);
    }

    fclose(fp);

    free(pPadding);
    pPadding = NULL;

    return 0;
}

  需要注意的地方:

  • 结构体类型定义需要带上packed属性,这样结构体中的字段才能和真正的BMP文件存储格式对应上;
  • 读写每行像素时,习惯上的第一行,实际上是存在文件中的最后一行,所以在进行读写操作时,循环变量i是从大到小变化;
  • 每行像素有可能会有padding,所以在读写的时候也需要对padding进行处理。
  • 写BMP文件时,要确认文件信息头是否正确。在不知道文件信息头应该是什么样的情况时,可以先读一张同样大小的图片,然后把信息头保存下来。

BMP读写测试

/**
 * @file main.c
 * @author Jiandong Qiu (1335521934@qq.com)
 * @brief 
 * @version 0.1
 * @date 2022-10-07
 * 
 * @copyright Copyright (c) 2022
 * 
 */

#include "bmpRw.h"
#include <stdint.h>
#include <stdlib.h>

typedef uint8_t Pixel[3];

#define WIDTH (639)
#define HEIGHT (399)

int main()
{
    Pixel *pData = (Pixel *)malloc(sizeof(Pixel) * WIDTH * HEIGHT);
    if(!pData)
        return -1;
    readBmp("Test.bmp", pData);
    writeBmp("Out.bmp", pData);

    return 0;
}

  最后在windows和Ubuntu下分别对639x399的RGB BMP图片进行读写测试。程序能够读取图片数据并正确写回。

Windows MinGW gcc
在这里插入图片描述
Ubuntu 环境信息
在这里插入图片描述
测试结果:
在这里插入图片描述

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
数字图像获取处理及实践应用源代码\ImageProcessing\cdib.cpp ................................\...............\cdib.h ................................\...............\ChildFrm.cpp ................................\...............\ChildFrm.h ................................\...............\ColorTable.h ................................\...............\Default.SUP ................................\...............\DIBPrcs.cpp ................................\...............\DibShow.cpp ................................\...............\DlgAftReg.cpp ................................\...............\DlgAftReg.h ................................\...............\DlgArith.cpp ................................\...............\DlgBitPlane.cpp ................................\...............\DlgCoding.h ................................\...............\DlgCodingHuffman.cpp ................................\...............\DlgEhnLinTrans.cpp ................................\...............\DlgEhnLinTrans.h ................................\...............\DlgEnhColor.cpp ................................\...............\DlgEnhColor.h ................................\...............\DlgHistShow.cpp ................................\...............\DlgHistShow.h ................................\...............\DlgHistShow1.cpp ................................\...............\DlgHistShow1.h ................................\...............\DlgHuffman.cpp ................................\...............\DlgMedian.cpp ................................\...............\DlgMedian.h ................................\...............\DlgRecMatch.cpp ................................\...............\DlgRecMatch.h ................................\...............\DlgReg.cpp ................................\...............\DlgReg.h ................................\...............\DlgShannon.cpp ................................\...............\DlgSmooth.cpp ................................\...............\DlgSmooth.h ................................\...............\DWT.CPP ................................\...............\Enhance.cpp ................................\...............\FreTrans.cpp ................................\...............\GlobalApi.h ................................\...............\ImageAnalysis.cpp ................................\...............\ImageCoding.cpp ................................\...............\ImageProcessing.aps ................................\...............\ImageProcessing.clw ................................\...............\ImageProcessing.cpp ................................\...............\ImageProcessing.dsp ................................\...............\ImageProcessing.dsw ................................\...............\ImageProcessing.h ................................\...............\ImageProcessing.ncb ................................\...............\ImageProcessing.opt ................................\...............\ImageProcessing.plg ................................\...............\ImageProcessing.rc ................................\...............\ImageProcessingDoc.cpp ................................\...............\ImageProcessingDoc.h ................................\...............\ImageProcessingView.cpp ................................\...............\ImageProcessingView.h ................................\...............\ImageView.cpp ................................\...............\MainFrm.cpp ................................\...............\MainFrm.h ................................\...............\Motion.cpp ................................\...............\ReadMe.txt ................................\...............\Recog.asp ................................\...............\Recog.cpp ................................\...............\res\ImageProcessing.ico ................................\...............\...\ImageProcessing.rc2 ................................\...............\...\ImageProcessingDoc.ico ................................\...............\...\Toolbar.bmp ................................\...............\resource.h ................................\...............\restore.cpp ................................\...............\SegApi.cpp ................................\...............\StdAfx.cpp ................................\...............\StdAfx.h ................................\lena.bmp ................................\ImageProcessing\Debug ................................\...............\Release ................................\...............\res ................................\ImageProcessing
以下是利用C++实现BMP文件读写代码: ```c++ #include <iostream> #include <fstream> using namespace std; #pragma pack(push, 1) typedef struct BMPFileHeader { char bfType[2]; int bfSize; short bfReserved1; short bfReserved2; int bfOffBits; } BMPFileHeader; typedef struct BMPInfoHeader { int biSize; int biWidth; int biHeight; short biPlanes; short biBitCount; int biCompression; int biSizeImage; int biXPelsPerMeter; int biYPelsPerMeter; int biClrUsed; int biClrImportant; } BMPInfoHeader; #pragma pack(pop) void readBMP(string filename, unsigned char* &imgData, int &width, int &height, int &channels) { BMPFileHeader fileHeader; BMPInfoHeader infoHeader; int padding; FILE* fp = fopen(filename.c_str(), "rb"); if(!fp) { cout << "Failed to open file: " << filename << endl; return; } fread(&fileHeader, sizeof(BMPFileHeader), 1, fp); fread(&infoHeader, sizeof(BMPInfoHeader), 1, fp); width = infoHeader.biWidth; height = infoHeader.biHeight; channels = infoHeader.biBitCount / 8; padding = (4 - (width * channels) % 4) % 4; imgData = new unsigned char[width * height * channels]; fseek(fp, fileHeader.bfOffBits, SEEK_SET); for(int i = 0; i < height; i++) { fread(imgData + i * width * channels, channels, width, fp); fseek(fp, padding, SEEK_CUR); } fclose(fp); } void writeBMP(string filename, unsigned char* imgData, int width, int height, int channels) { BMPFileHeader fileHeader; BMPInfoHeader infoHeader; int padding; fileHeader.bfType[0] = 'B'; fileHeader.bfType[1] = 'M'; fileHeader.bfSize = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + (width * channels + (4 - (width * channels) % 4) % 4) * height; fileHeader.bfReserved1 = 0; fileHeader.bfReserved2 = 0; fileHeader.bfOffBits = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader); infoHeader.biSize = sizeof(BMPInfoHeader); infoHeader.biWidth = width; infoHeader.biHeight = height; infoHeader.biPlanes = 1; infoHeader.biBitCount = channels * 8; infoHeader.biCompression = 0; infoHeader.biSizeImage = (width * channels + (4 - (width * channels) % 4) % 4) * height; infoHeader.biXPelsPerMeter = 0; infoHeader.biYPelsPerMeter = 0; infoHeader.biClrUsed = 0; infoHeader.biClrImportant = 0; padding = (4 - (width * channels) % 4) % 4; FILE* fp = fopen(filename.c_str(), "wb"); if(!fp) { cout << "Failed to open file: " << filename << endl; return; } fwrite(&fileHeader, sizeof(BMPFileHeader), 1, fp); fwrite(&infoHeader, sizeof(BMPInfoHeader), 1, fp); for(int i = 0; i < height; i++) { fwrite(imgData + i * width * channels, channels, width, fp); for(int j = 0; j < padding; j++) { fputc(0, fp); } } fclose(fp); } int main() { unsigned char* imgData; int width, height, channels; readBMP("input.bmp", imgData, width, height, channels); // 对图像进行处理 writeBMP("output.bmp", imgData, width, height, channels); delete[] imgData; return 0; } ``` 其中,readBMP函数用于读取BMP文件,writeBMP函数用于写入BMP文件。在读取BMP文件时,需要注意文件头和信息头的结构体定义,并且需要考虑对齐和补齐的问题。在写入BMP文件时,需要根据读取到的图像数据的宽度、高度和通道数等信息来计算文件头和信息头中的数据,并且需要考虑对齐和补齐的问题。 以下是读取BMP文件并显示结果的示例代码: ```c++ #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; int main() { Mat img = imread("input.bmp"); if(img.empty()) { cout << "Failed to read image" << endl; return -1; } namedWindow("Image", WINDOW_NORMAL); imshow("Image", img); waitKey(0); return 0; } ``` 利用OpenCV库中的imread函数可以读取BMP文件,并利用namedWindow和imshow函数可以创建窗口并显示图像。需要注意的是,在使用imshow函数时需要调用waitKey函数等待按键事件,否则窗口会立即关闭。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小裘HUST

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值