一、简要介绍思路:
BMP文件简述:BMP文件由文件头、信息头、颜色信息、图形数据四部分组成。(此处由于是24位BMP不考虑颜色信息),则BMP文件里只剩下文件头、信息头、图形数据。
读取BMP时读取顺序则为:1.读取一个文件头 2.读取一个信息头 3.读取图形数据
预先定义的数据类型:BYTE(8位即一个字节)、WORD(16位即两个字节)、DWORD(32位即四个字节)
typedef unsigned char BYTE;//一字节
typedef unsigned short WORD;//两字节
typedef unsigned long DWORD;//四字节
BMP文件的文件头数据结构如下:
//BMP图片的文件头(一共14个字节)
typedef struct HEAD
{
WORD bfType; /* 文件类型*/
DWORD bfSize; /* 文件大小*/
WORD bfReserved1; /* 保留位*/
WORD bfReserved2; /* 保留位*/
DWORD bfOffBits; /* 数据偏移位置*/
}HEAD;
BMP文件的信息头数据结构如下:
//BMP图片信息头(40字节)
typedef struct INFOHEAD
{
DWORD biSize; /* 此结构大小 */
DWORD biWidth; /* 图像宽度 */
DWORD biHeight; /* 图像高度 */
WORD biPlanes; /* 调色板数量 */
WORD biBitCount; /* 每个象素对应的位数,*/
DWORD biCompression; /* 压缩 */
DWORD biSizeImage; /* 图像大小 */
DWORD biXPelsPerMeter;/* 横向分辨率 */
DWORD biYPelsPerMeter;/* 纵向分辨率 */
DWORD biClrUsed; /* 颜色使用数 */
DWORD biClrImportant; /* 重要颜色数 */
}INFOHEAD;
思路:
1.首先读取原BMP图像数据:读取一个文件头 、读取一个信息头 、读取图形像素数据(要注意BMP文件图形像素数据里面,每行的数据都是4字节的整数倍,不是4字节整数倍的要补零字节,因此在读取、写入图形像素数据时要注意这部分零字节)。
2.进行BMP转为YUV文件时,只需运用YUV、BMP转换公式即可:
Y= 0.299*R+0.587*G+0.114*B
U= -0.168*R-0.332*G+0.5*G+128
V= 0.5*R-0.419*G-0.081*B+128
因为是要转为YUV(4:2:0),所以在当把所有的BMP像素点的B、G、R值转为Y、U、V的值后要将U、V的值进行4:1采样(本文采用了取平均值),Y则不需要变。
注意事项:(1)BMP的图形像素信息存储顺序为从左向右,从下向上(主要注意这里),因此在进行BMP转YUV时,先进行BMP图形像素信息的上下对称。
(2)BMP每个像素点的存储是按着B、G、R的顺序(不是R、G、B)。
3.进行180度翻转时,文件头、信息头的值都未改变,只要改变图形像素数据即可。
4.进行90度翻转时,文件头、信息头的部分值改变了,要注意重新计算这些值,之后再改变图形像素数据即4.可。
二、完整代码:(BMP旋转、BMP转YUV420可以单独运行)
1.header.h:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
typedef unsigned char BYTE;//一字节
typedef unsigned short WORD;//两字节
typedef unsigned long DWORD;//四字节
//BMP图片的文件头(一共14个字节)
typedef struct HEAD
{
//WORD bfType; /* 文件类型*/
DWORD bfSize; /* 文件大小*/
WORD bfReserved1; /* 保留位*/
WORD bfReserved2; /* 保留位*/
DWORD bfOffBits; /* 数据偏移位置*/
}HEAD; // 这里的bfType需要单独取
//BMP图片信息头(40字节)
typedef struct INFOHEAD
{
DWORD biSize; /* 此结构大小 */
DWORD biWidth; /* 图像宽度 */
DWORD biHeight; /* 图像高度 */
WORD biPlanes; /* 调色板数量 */
WORD biBitCount; /* 每个象素对应的位数,*/
DWORD biCompression; /* 压缩 */
DWORD biSizeImage; /* 图像大小 */
DWORD biXPelsPerMeter;/* 横向分辨率 */
DWORD biYPelsPerMeter;/* 纵向分辨率 */
DWORD biClrUsed; /* 颜色使用数 */
DWORD biClrImportant; /* 重要颜色数 */
}INFOHEAD;
void turn180(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead, int off);
void turn90(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead);
void rgb2yuv(int width, int heigth, unsigned char* rgbbuf, unsigned char* ybuf, unsigned char* ubuf, unsigned char* vbuf);
2.Image_rollover.cpp:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include"header.h"
FILE* FP, * FP_yuv;
HEAD* filehead = (HEAD*)malloc(sizeof(HEAD));
INFOHEAD* infohead = (INFOHEAD*)malloc(sizeof(INFOHEAD));
WORD bfType; // 文件类型
BYTE* store; // 存储一个字节信息
int i, j, k;
BYTE* image_array, * ybuf, * ubuf, * vbuf, * rgbbuf;
BYTE zero_fill = 0;
FILE* FP1;
int off, width, height;
void shan(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead);
int main()
{
if ((FP = fopen("1.bmp", "rb")) == NULL)
{
printf("cann't find the file!");
return 0;
}
fread(&bfType, sizeof(WORD), 1, FP); //单独读取bfType
fread(filehead, sizeof(HEAD), 1, FP); //读入文件头
fread(infohead, sizeof(INFOHEAD), 1, FP); //读入信息头
/*
printf("文件头大小:%d\n", sizeof(HEAD));
printf("信息头大小:%d\n", sizeof(INFOHEAD));
*/
int a = 0;
if (bfType != 0X4d42)
{
printf("不是BMP图片"); //bfType为0X4d42时说明读取的时BMP图片
return 0;
}
width = infohead->biWidth;
height = infohead->biHeight;
image_array = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth * 3);//存储图片的像素数据
store = (BYTE*)malloc(sizeof(BYTE));
ybuf = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth);
ubuf = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth / 4);
vbuf = (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth / 4);
rgbbuf= (BYTE*)malloc(sizeof(BYTE) * infohead->biHeight * infohead->biWidth * 3);
off = (infohead->biWidth * 3) % 4;
if (off != 0)
{
off - 4 - off; //BMP图片每行的像素信息都要用零补成4个字节的整数倍,off即为补的
//零字节的个数
}
/*因为图像存储时每行为了补成4字节的整数倍补了零字节,所以一个字节一个字节地读取*/
for (i = 0;i < infohead->biHeight;i++) //图形像素数据存储到image_array数组中
{
for (j = 0;j < infohead->biWidth;j++)
{
fread(store, sizeof(BYTE), 1, FP);
image_array[i * infohead->biWidth * 3 + j * 3] = *store;
fread(store, sizeof(BYTE), 1, FP);
image_array[i * infohead->biWidth * 3 + j * 3 + 1] = *store;
fread(store, sizeof(BYTE), 1, FP);
image_array[i * infohead->biWidth * 3 + j * 3 + 2] = *store;
}
for (k = 0;k < off;k++)
{
fread(store, sizeof(BYTE), 1, FP); //取每行末尾补的零字节但是不存
}
}
fclose(FP);
/*读取的数据恢复图像*/
/*
FP1 = fopen("2.bmp", "wb");
fwrite(&bfType, sizeof(WORD), 1, FP1);
fwrite(filehead, sizeof(HEAD), 1, FP1);
fwrite(infohead, sizeof(INFOHEAD), 1, FP1);
for (i = 0;i < infohead->biHeight;i++) //原始数据存储到数组里面
{
for (j = 0;j < infohead->biWidth;j++)
{
fwrite(&image_array[i * infohead->biWidth * 3 + j * 3], sizeof(BYTE), 1, FP1);
fwrite(&image_array[i * infohead->biWidth * 3 + j * 3+1 ], sizeof(BYTE), 1, FP1);
fwrite(&image_array[i * infohead->biWidth * 3 + j * 3+2], sizeof(BYTE), 1, FP1);
}
for (k = 0;k < off;k++)
{
fwrite(&zero_fill, sizeof(BYTE), 1, FP1);
}
}
*/
/*
上下颠倒
for (i = 0;i < infohead->biHeight;i++) //原始数据存储到数组里面
{
for (j = 0;j < infohead->biWidth;j++)
{
fwrite(&image_array[(infohead->biHeight-i-1) * infohead->biWidth * 3 + j * 3], sizeof(BYTE), 1, FP1);
fwrite(&image_array[(infohead->biHeight - i - 1) * infohead->biWidth * 3 + j * 3 + 1], sizeof(BYTE), 1, FP1);
fwrite(&image_array[(infohead->biHeight - i - 1) * infohead->biWidth * 3 + j * 3 + 2], sizeof(BYTE), 1, FP1);
}
for (k = 0;k < off;k++)
{
fwrite(&zero_fill, sizeof(BYTE), 1, FP1);
}
}
fclose(FP1);
*/
/*rgbbuf为bmp上下翻转之后的,这样才是正常的顺序,因为bmp是从左向右,从下往上存数据的*/
for (i = 0;i < height;i++)
for (j = 0;j < width;j++)
{
rgbbuf[j * 3 + (height - 1 - i) * width * 3] = image_array[j * 3 + width * 3 * i];
rgbbuf[j * 3 + (height - 1 - i) * width * 3+1] = image_array[j * 3 + width * 3 * i+1];
rgbbuf[j * 3 + (height - 1 - i) * width * 3+2] = image_array[j * 3 + width * 3 * i+2];
}
/*bmp文件通过公式转为yuv文件*/
rgb2yuv(width, height, rgbbuf, ybuf, ubuf, vbuf); //调用转换函数
FP_yuv = fopen("3.yuv", "wb");
fwrite(ybuf, 1, sizeof(unsigned char) * width * height, FP_yuv);
fwrite(ubuf, 1, sizeof(unsigned char) * width * height/4, FP_yuv);
fwrite(vbuf, 1, sizeof(unsigned char)* width* height/4, FP_yuv);
printf("%d*%d的bmp文件转成yuv文件完成\n", height, width);
fclose(FP_yuv);
/*选择进行bmp的翻转操作*/
printf("请选择想对BMP图片的操作:输入1为旋转180度,输入2位旋转90度:\n");
int select;
scanf("%d", &select);
if (select == 1)
{
turn180(bfType, image_array, filehead, infohead,off);
}
else if (select == 2)
{
turn90(bfType, image_array, filehead, infohead);
}
else
{
printf("选择出错了");
}
}
3. rgb2yuv.cpp:
#include "stdlib.h"
#include"header.h"
void rgb2yuv(int width, int heigth, unsigned char* rgbbuf, unsigned char* ybuf, unsigned char* ubuf, unsigned char* vbuf)
{
int i, j;
unsigned char* sub_ubuf, * sub_vbuf;//未丢弃的uv量
sub_ubuf = (unsigned char*)malloc(sizeof(unsigned char) * width * heigth);
sub_vbuf= (unsigned char*)malloc(sizeof(unsigned char) * width * heigth);
/*通过YUV和RGB的转换公式来计算每个像素Y、U、V的值,注意RGB里面每个像素点是按着B、G、R(不是R、G、B)的顺序存的*/
for(i=0;i<heigth;i++)
for (j = 0;j < width;j++)
{
ybuf[i * width + j] = 0.299 * rgbbuf[i * width * 3 + j * 3+2] + 0.587 * rgbbuf[i * width * 3 + j * 3 + 1] + 0.114 * rgbbuf[i * width * 3 + j * 3 ];
if (ybuf[i * width + j] > 235)
ybuf[i * width + j] = 235;
else if (ybuf[i * width + j] < 16)
ybuf[i * width + j] = 16;
sub_ubuf[i * width + j] = -0.168 * rgbbuf[i * width * 3 + j * 3+2] - 0.332 * rgbbuf[i * width * 3 + j * 3 + 1] + 0.5 * rgbbuf[i * width * 3 + j * 3 ]+128;
sub_vbuf[i * width + j] = 0.5 * rgbbuf[i * width * 3 + j * 3+2] - 0.419 * rgbbuf[i * width * 3 + j * 3 + 1] - 0.081*rgbbuf[i * width * 3 + j * 3 ]+128;
}
/*将sub_ubuf sub_vbuf减少到1/4(4:2:0)*/
for(i=0;i<heigth/2;i++)
for (j = 0;j < width/2;j++)
{
ubuf[i * width / 2 + j] = (sub_ubuf[i * 2 * width + j * 2] + sub_ubuf[i * 2 * width + j * 2 + 1] + sub_ubuf[i * 2 * width + j * 2 + width] + sub_ubuf[i * 2 * width + j * 2 + width + 1]) / 4;
if (ubuf[i * width / 2 + j] > 235)
ubuf[i * width / 2 + j] = 235;
else if (ubuf[i * width / 2 + j] < 16)
ubuf[i * width / 2 + j] = 16;
vbuf[i * width / 2 + j]= (sub_vbuf[i * 2 * width + j * 2] + sub_vbuf[i * 2 * width + j * 2 + 1] + sub_vbuf[i * 2 * width + j * 2 + width] + sub_vbuf[i * 2 * width + j * 2 + width + 1]) / 4;
if (vbuf[i * width / 2 + j] > 235)
vbuf[i * width / 2 + j] = 235;
else if (vbuf[i * width / 2 + j] < 16)
vbuf[i * width / 2 + j] = 16;
}
}
4.turn90.cpp:
#include"header.h"
#include<stdlib.h>
#include<stdio.h>
void turn90(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead)
{
FILE* FP = fopen("1.bmp", "wb");
int i, j, k;
int off_new;
BYTE zero_fill = 0;
DWORD width, heigth;
width = infohead->biWidth;
heigth = infohead->biHeight; //原图的宽高
off_new = infohead->biHeight * 3 % 4;
if (off_new != 0)
{
off_new = 4 - off_new; //每行需要补零的字节数
}
//翻转90度时BMP文件头、信息头的部分值有所改变,要重新计算
filehead->bfSize = sizeof(WORD) + sizeof(HEAD) + sizeof(INFOHEAD) + infohead->biHeight * infohead->biWidth * 3 + off_new * infohead->biWidth;
DWORD swsw;
/*原图像的宽和高要互换*/
swsw = infohead->biHeight;
infohead->biHeight = infohead->biWidth;
infohead->biWidth = swsw;
infohead->biSizeImage = filehead->bfSize - (sizeof(WORD) + sizeof(HEAD) + sizeof(INFOHEAD));
fwrite(&bfType, sizeof(WORD), 1, FP);
fwrite(filehead, sizeof(HEAD), 1, FP);
fwrite(infohead, sizeof(INFOHEAD), 1, FP);
/*写入翻转的BMP像素信息*/
for (i = 0;i < width;i++)
{
for (j = heigth - 1;j >= 0;j--)
{
fwrite(&image_array[j * width * 3 + i * 3], sizeof(BYTE), 1, FP);
fwrite(&image_array[j * width * 3 + i * 3 + 1], sizeof(BYTE), 1, FP);
fwrite(&image_array[j * width * 3 + i * 3 + 2], sizeof(BYTE), 1, FP);
}
for (k = 0;k < off_new;k++)
{
fwrite(&zero_fill, sizeof(BYTE), 1, FP); //每行为了成为4字节的整数倍要补的零字节
}
}
fclose(FP);
}
5.turn180cpp:
#include"header.h"
#include<stdlib.h>
#include<stdio.h>
void turn180(WORD bfType, BYTE* image_array, HEAD* filehead, INFOHEAD* infohead, int off)
{
FILE* FP = fopen("1.bmp", "wb");
BYTE zero_fill = 0;
int i, j, k;
/*因为是翻转180度,图片的长宽都没变,文件头、信息头的值都未改变,则直接写入原图像的文件头和信息头*/
fwrite(&bfType, sizeof(WORD), 1, FP);
fwrite(filehead, sizeof(HEAD), 1, FP);
fwrite(infohead, sizeof(INFOHEAD), 1, FP);
for (i = infohead->biHeight - 1;i >= 0;i--)
{
for (j = infohead->biWidth - 1;j >= 0;j--)
{
fwrite(&image_array[i * infohead->biWidth * 3 + j * 3], sizeof(BYTE), 1, FP);
fwrite(&image_array[i * infohead->biWidth * 3 + j * 3 + 1], sizeof(BYTE), 1, FP);
fwrite(&image_array[i * infohead->biWidth * 3 + j * 3 + 2], sizeof(BYTE), 1, FP);
}
if (off != 0)
{
for (k = 0;k < off;k++)
{
fwrite(&zero_fill, sizeof(BYTE), 1, FP); //每行为了成为4字节的整数倍补的零字节
}
}
}
fclose(FP);
}