vs2019实现24位真彩BMP文件顺时针旋转90°和180°

暑假期间,导师布置了一个将24位BMP图像的翻转90°和180°的小任务,经过经典面向百度编程,学习了一些bmp格式的知识,主要参考以下几篇文章。

知乎一位大佬的详解:C++实现将24位真彩BMP文件顺时针旋转90度,补足字节部分是精华

bmp24位位图格式总结:BMP24位位图格式总结

主体代码部分还是知乎大佬那篇文章,我自己在vs2019上运行时debug了一遍,添加了旋转函数rotation1,使其能够旋转180°(这部分搞清楚bmp图像的存储格式,其实是可以旋转任意较好的角度,找到新指针和旧指针对应的像素点位置即可).

一、BMP文件的组成

一个BMP文件由四部分构成:文件头、信息头、颜色表和图像信息区。文件头和信息头储存一些关于图像的信息,颜色表用来生成调色板,图像信息区则存储像素信息。这些信息依次存储在BMP文件中,要读取他们时我们只需依次将其读取即可。而在信息头中,我们主要用到的就是

biWidth(位图的宽度,以像素为单位);

biHeight(位图的高度,以像素为单位);

biBitCount(每个像素所需位数),biBitCount是多少这个图就是多少位图,我们处理的24位图的biBitCount就等于24。

其他的信息在图片旋转操作中不会改变,我们也不去管他们,只用依次将其从原文件中读取,再依次存入新文件就好了。

而在24位真彩图中,没有颜色表信息,可以不加,但是如果对灰度图像进行处理,就需要添加颜色表信息,且灰度值用rgb三通道数据计算。

二、BMP文件存储格式

BMP文件在存储像素信息时,和平常jpeg之类的格式不同,不是从左上到右下存储,它可以认为是以一个一维数组存储,从图片的左下角开始,先填满最下面一行,再填倒数第二行,一直填到最上面一行。同时,每一行存储像素信息的字节数必须是4的倍数,不够就填0补上剩下的。

例如:宽度为957个像素,24位图每个像素占3个字节,一行所需的字节数应该是 957x3=2871 个字节,但是实际上一行的字节为2872个(4的倍数),多出来的这一个字节就填上0。

三、关键函数

若未补上0,可能导致旋转出错,为了处理这个问题,编写一个字节补足函数

int getDiff(Info & info)
{
    int DataSizePerline = (info.biWidth * info.biBitCount+31) / 8;
    DataSizePerline -= DataSizePerline % 4;
    return DataSizePerline - info.biWidth * info.biBitCount / 8;
}

然后读取像素信息:

DATA *imgdata=new DATA[size];
int diff = getDiff(srcInfo);

//读取原图片像素信息
for (int i=0;i<h;i++){
    fread((char*)imgdata + i * w * 3, 3, w, p);
    fseek(p, diff, SEEK_CUR);
}

其中用到了(char*)来转换指针类型,这样可以将指针移动特定的字节数(char*指针+1则移动一个字节)。读取完一行的像素之后,用fseek()函数来跳过这一行补零的那些字节,再给imgdata指针加上一行的“有内容字节数”,继续读下一行。

旋转函数:

(1)旋转90°函数

void rotation(const DATA* src){
    int newH = w;
    int newW = h;//图片旋转90度之后宽度、高度互换
    int newSize = newH * newW;

    FILE *p;
    p=fopen("dest.bmp","wb");
    Header newHead = srcHead;
    Info newInfo = srcInfo;


    //修改旋转后图片的尺寸、宽度和高度
    newHead.bfSize = (DWORD)(newHead.bfSize);
    newInfo.biHeight = (DWORD)newH;
    newInfo.biWidth = (DWORD)newW;
    int newdiff = getDiff(newInfo);
    newInfo.biSizeImage = (DWORD)((newInfo.biWidth * 3 + newdiff) * newInfo.biHeight);

    //将种类、文件头、信息头写入新bmp文件
    fwrite(&bfType,1,sizeof(WORD),p);
    fwrite(&newHead,1,sizeof(Header),p);
    fwrite(&newInfo,1,sizeof(Info),p);
}

旋转90°时,长宽互换

用指针代替新像素数组,把原数组像素信息转移到新数组中

for (int i=0; i<newH; i++){
    for (int j=0; j<newW; j++){
        *(target + i * newW + j) = *(src + j * w + newH - i - 1)
    }
}

原数组中(i,j)位置的像素换到了(j,newH-i-1)位置

现在将target所存储的像素信息写入dest.bmp文件中:

for (int i=0; i<newH; i++){
    fwrite((char*)target + i * newW * 3, 3, newW, p);//写入“有内容”的字节
    fseek(p, newdiff, SEEK_CUR);//跳过补零的字节
}

(2)旋转180°函数

旋转180°时,长宽不变

int newH = h;
    int newW = w;//图片旋转180度之后宽高不变
    int newSize = newH * newW;

用指针代替新像素数组,把原数组像素信息转移到新数组中

    for (int i = 0; i < newH; i++) {
        for (int j = 0; j < newW; j++) {
            *(target + i * newW + j) = *(src + (h-i-1) * w +w-j-1);
        }
    }

原数组中(i,j)位置的像素换到了(newW-j-1,newH-i-1)位置

将target所存储的像素信息写入dest.bmp文件中:

for (int i = 0; i < newH; i++) {
        fwrite((char*)target + i * newW * 3, 3, newW, p);
        fseek(p, newdiff, SEEK_CUR);
    }

最后释放内存。

四、完整代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)

//位图文件头定义;
struct  Header {
    DWORD bfSize;//文件大小
    WORD bfReserved1;//保留字
    WORD bfReserved2;//保留字
    DWORD bfOffBits;//从文件头到实际位图数据的偏移字节数
};

//位图信息头定义
struct Info {
    DWORD biSize;//信息头大小
    DWORD biWidth;//图像宽度
    DWORD biHeight;//图像高度
    WORD biPlanes;//位平面数,必须为1
    WORD biBitCount;//每像素位数
    DWORD  biCompression; //压缩类型
    DWORD  biSizeImage; //压缩图像大小字节数
    DWORD  biXPelsPerMeter; //水平分辨率
    DWORD  biYPelsPerMeter; //垂直分辨率
    DWORD  biClrUsed; //位图实际用到的色彩数
    DWORD  biClrImportant; //本位图中重要的色彩数
};

//像素信息
struct DATA
{
    BYTE blue;
    BYTE green;
    BYTE red;
};

WORD bfType;//文件类型
Header srcHead;//原文件文件头
Info srcInfo;//原文件信息头
int h, w, size1;//原图像的高度、宽度和尺寸

int getDiff(Info& info)
{
    int DataSizePerline = (info.biWidth * info.biBitCount + 31) / 8;
    DataSizePerline -= DataSizePerline % 4;
    return DataSizePerline - info.biWidth * info.biBitCount / 8;
}

//旋转90°函数
void rotation(const DATA* src) {
    int newH = w;
    int newW = h;//图片旋转90度之后宽度、高度互换
    int newSize = newH * newW;

    FILE* p;
    p = fopen("dest.bmp", "wb");
    Header newHead = srcHead;
    Info newInfo = srcInfo;


    //修改旋转后图片的尺寸、宽度和高度
    newHead.bfSize = (DWORD)(newHead.bfSize);
    newInfo.biHeight = (DWORD)newH;
    newInfo.biWidth = (DWORD)newW;
    int newdiff = getDiff(newInfo);
    newInfo.biSizeImage = (DWORD)((newInfo.biWidth * 3 + newdiff) * newInfo.biHeight);

    //将种类、文件头、信息头写入新bmp文件
    fwrite(&bfType, 1, sizeof(WORD), p);
    fwrite(&newHead, 1, sizeof(Header), p);
    fwrite(&newInfo, 1, sizeof(Info), p);

    DATA* target = new DATA[newSize];
    for (int i = 0; i < newH; i++) {
        for (int j = 0; j < newW; j++) {
            *(target + i * newW + j) = *(src + j * w + newH - i - 1);
        }
    }

    for (int i = 0; i < newH; i++) {
        fwrite((char*)target + i * newW * 3, 3, newW, p);
        fseek(p, newdiff, SEEK_CUR);
    }

    fclose(p);
    delete[]target;
}
//旋转180°函数
void rotation1(const DATA* src) {
    int newH = h;
    int newW = w;//图片旋转180度之后宽高不变
    int newSize = newH * newW;

    FILE* p;
    p = fopen("dest.bmp", "wb");
    Header newHead = srcHead;
    Info newInfo = srcInfo;


    //修改旋转后图片的尺寸、宽度和高度
    newHead.bfSize = (DWORD)(newHead.bfSize);
    newInfo.biHeight = (DWORD)newH;
    newInfo.biWidth = (DWORD)newW;
    int newdiff = getDiff(newInfo);
    newInfo.biSizeImage = (DWORD)((newInfo.biWidth * 3 + newdiff) * newInfo.biHeight);

    //将种类、文件头、信息头写入新bmp文件
    fwrite(&bfType, 1, sizeof(WORD), p);
    fwrite(&newHead, 1, sizeof(Header), p);
    fwrite(&newInfo, 1, sizeof(Info), p);

    DATA* target = new DATA[newSize];
    for (int i = 0; i < newH; i++) {
        for (int j = 0; j < newW; j++) {
            *(target + i * newW + j) = *(src + (h-i-1) * w +w-j-1);
        }
    }

    for (int i = 0; i < newH; i++) {
        fwrite((char*)target + i * newW * 3, 3, newW, p);
        fseek(p, newdiff, SEEK_CUR);
    }
    fclose(p);
    delete[]target;
}

int main() {
    FILE* p;
    p = fopen("bmp_24.bmp", "rb");
    if (p != NULL) {
        //先读取文件类型
        fread(&bfType, 1, sizeof(WORD), p);
        //读取bmp文件的文件头和信息头
        fread(&srcHead, 1, sizeof(Header), p);
        fread(&srcInfo, 1, sizeof(Info), p);
        h = srcInfo.biHeight;
        w = srcInfo.biWidth;
        size1 = w * h;

        DATA* imgdata = new DATA[size1];
        int diff = getDiff(srcInfo);

        //读取原图片像素信息
        for (int i = 0; i < h; i++) {
            fread((char*)imgdata + i * w * 3, 3, w, p);
            fseek(p, diff, SEEK_CUR);
        }
        fclose(p);
        rotation1(imgdata);
        delete[]imgdata;
    }
    else {
        cout << "无法打开文件" << endl;
    }
    return 0;
}

五、一些debug过程

(1)报错errorC4996:添加代码

#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)

(2)定义不明确

知乎大佬那篇文章有自己定义一个size,在vs运行时,会占用命名空间,所以我把size改成size1

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值