使用C++实现多张BMP图片转换为YUV动画----附加淡入淡出转场(逐渐变明变暗),及垂直滑像转场(逐行渐变)
一、BMP图像简介
1、BMP图像是什么?
BMP是英文Bitmap(位图)的简写,它是Windows操作系统中的标准图像文件格式,能够被多种Windows应用程序所支持。随着Windows操作系统的流行与丰富的Windows应用程序的开发,BMP位图格式理所当然地被广泛应用。这种格式的特点是包含的图像信息较丰富,几乎不进行压缩,但由此导致了它与生俱来的缺点–占用磁盘空间过大。所以,目前BMP在单机上比较流行。BMP位图文件默认的文件扩展名是BMP或者bmp(有时它也会以.DIB或.RLE作扩展名)。
2、BMP图像文件结构
位图文件可看成由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、彩色表(color table)和定义位图的字节(位图数据,即图像数据,Data Bits 或Data Body)阵列,它具有如下所示的形式。
1)图象文件头
0000h 文件标识 2 bytes 两字节的内容用来识别位图的类型:
‘BM’ :Windows 3.1x,95,NT,…
‘BA’ :OS/2 Bitmap Array
‘CI’ :OS/2 Color Icon
‘CP’ :OS/2 Color Pointer
‘IC’ :OS/2 Icon
‘PT’ :OS/2 Pointer
注:因为OS/2系统并没有被普及开,所以在编程时,你只需判断第一个标识“BM”就行。
0002h File Size 1 dword 用字节表示的整个文件的大小
0006h Reserved 1 dword 保留,必须设置为0
000Ah Bitmap Data Offset 1 dword 从文件开始到位图数据开始之间的数据(bitmap data)之间的偏移量
2)图象信息头
000Eh Bitmap Header Size 1 dword位图信息头(Bitmap Info Header)的长度,用来描述位图的颜色、压缩方法等。下面的长度表示:
28h - Windows 3.1x,95,NT,…
0Ch - OS/2 1.x
F0h - OS/2 2.x
注:在Windows95、98、2000等操作系统中,位图信息头的长度并不一定是28h,因为微软已经制定出了新的BMP文件格式,其中的信息头结构变化比较大,长度加长。所以最好不要直接使用常数28h,而是应该从具体的文件中读取这个值。这样才能确保程序的兼容性。
0012h Width 1 dword位图的宽度,以象素为单位
0016h Height 1 dword位图的高度,以象素为单位
001Ah Planes 1 word位图的位面数(注:该值将总是1)
001Ch Bits Per Pixel 1 word 每个象素的位数
1 - 单色位图(实际上可有两种颜色,缺省情况下是黑色和白色。你可以自己定义这两种颜色)
4 - 16 色位图
8 - 256 色位图
16 - 16bit 高彩色位图
24 - 24bit真彩色位图
32 - 32bit 增强型真彩色位图
001Eh Compression 1 dword 压缩说明:
0 - 不压缩 (使用BI_RGB表示)
1 - RLE 8-使用8位RLE压缩方式(用BI_RLE8表示)
2 - RLE 4-使用4位RLE压缩方式(用BI_RLE4表示)
3 - Bitfields-位域存放方式(用BI_BITFIELDS表示)
0022h Bitmap Data Size 1 dword 用字节数表示的位图数据的大小。该数必须是4的倍数
0026h HResolution 1 dword 用象素/米表示的水平分辨率
002Ah VResolution 1 dword 用象素/米表示的垂直分辨率
002Eh Colors 1 dword位图使用的颜色数。如8-比特/象素表示为100h或者 256.
0032h Important Colors 1 dword 指定重要的颜色数。当该域的值等于颜色数时(或者等于0时),表示所有颜色都一样重要
3)调色板数据
根据BMP版本的不同而不同 Palette N * 4 byte 调色板规范。对于调色板中的每个表项,这4个字节用下述方法来描述RGB的值:1字节用于蓝色分量
1字节用于绿色分量
1字节用于红色分量
1字节用于填充符(设置为0)
4)图象数据
根据BMP版本及调色板尺寸的不同而不同 Bitmap Data xxx bytes 该域的大小取决于压缩方法及图像的尺寸和图像的位深度,它包含所有的位图数据字节,这些数据可能是彩色调色板的索引号,也可能是实际的RGB值,这将根据图像信息头中的位深度值来决定。
二、利用命令参数实现读取文件的简单化安全化
(与本人之前所写的博客内容相同,详解可看“使用C++实现YUV格式图像与RGB格式图像之间相互转换”)
通过选择“项目”-“yuv2rgb属性”进入yuv2rgb属性页
在属性页中选择“配置属性”-“调试”进入调试器界面
在命令参数中按顺序依次输入值(文件名称、参数等)并以“ ”(空格)隔开,这些值会依次排放在argv[1]-argv[n]之中,需要调用的时候直接赋值即可:
yuvFileName = argv[1];
rgbFileName = argv[2];
frameWidth = atoi(argv[3]);
frameHeight = atoi(argv[4]);
并在工作目录选择文件所在位置即可并通过已下代码即可使文件读取更加安全,文件路径不会再在代码中展现:
yuvFile = fopen(yuvFileName, "rb");
rgbFile = fopen(rgbFileName, "wb");
三、使用C++实现多张BMP转换为YUV动画
本次的BMP图像采用宽度1024像素和高度为576像素,位深度为24位真彩色,不涉及调色板。
1、BMP文件读入
因为本次实验采用的图像不涉及调色板所以只需读取三部份文件----文件头、信息头及图像数据。因此,打开BMP文件后,先根据FileHeader判断图像属性,然后仅需从InfoHeader中读取出宽和高,开出相应的缓存区即可。下面为代码实现:
BITMAPFILEHEADER File_header;//文件头
BITMAPINFOHEADER Info_header;//信息头
FILE* bmp;
int width,height;//图像宽高信息
bmp = fopen(bmpFileName, "rb");
fread(&File_header,sizeof(BITMAPFILEHEADER),1,bmp);//图区文件头
fread(&Info_header,sizeof(BITMAPINFOHEADER),1,bmp);//读取信息头
width = (int)Info_header.biWidth;//获取宽度信息
height = (int)Info_header.biHeight;//获取高度信息
//开辟空间
yBuf = (u_int8_t*)malloc(width * height);
uBuf = (u_int8_t*)malloc((width * height) /4);
vBuf = (u_int8_t*)malloc((width * height) 4);
fread(rgbBuf, 1, width * height * 3, bmp);//读取图像数据
2、BMP(RGB)转YUV
因为本次实验从BMP文件中读出的图像数据部分为RGB格式,所以可以直接引用本人以往博客点击此处进入,中的RGB2YUV函数进行转化,在这里不再做讲解。
3、多张BMP图片转换为YUV动画
在上面,介绍了如何将一张BMP图片转换为YUV图片,接下来就要说明如何将多张BMP图片转换为YUV动画。
要将多张BMP图片转换为YUV动画在大体上只需两部分:一是将多张BMP图片依次输入,二是将每张BMP图片序列分别输入30次,这样得到的就是一个YUV动画。
其实,要用代码实现这个转换非常简单,只需将上面介绍的步骤添加几个简单的“for”循环嵌套即可:
首先是外层的大循环,是为了实现第一部分(将多张BMP图片依次输入)因为这里采用4张图片,所以z=4;
其次便是内层循环,是为了实现第二部分(将每张BMP图片序列分别输入30次):
for(int z = 1; z < 4; z++)//第一部分
{
bmpFileName = argv[z];
bmp = fopen(bmpFileName, "rb");
for(i=0;i<30;i++)//第二部分
{
fwrite(yBuf, 1, width * height, yuvFile);
fwrite(uBuf, 1, (width * height) / 4, yuvFile);
fwrite(vBuf, 1, (width * height) / 4, yuvFile);
}
}
整体代码会在讲完转场的实现后一并给出
四、淡入淡出转场的实现(逐渐变明变暗)
淡入淡出的意思就是逐渐变明变暗,所以只需要将Y的值增大减小即可达成,在这里经过实验发现依次乘(除)1.08效果最好。
1、淡入(变明)
因为是变亮,所以需将初始较暗的图片存入新的缓存中,因为这里是10张图依次变量所以只需要将原来的亮度Y除以1.08的10次方(2.16)即可,再将暗图片缓存依次成1.08即可恢复成原图,并实现渐变明亮的效果。以下是代码实现:
for (i = 0; i < width*height; i++)//获取初始较暗缓存
{
yBuf_high[i] = yBuf[i]/2.16;
}
for(int x=0;x<10;x++)//依次亮度提升
{
for (int j = 0; j < width*height; j++)
{
yBuf_high[j] = yBuf_high[j]*1.08;
}
for (int m = 0; m < width*height; m++)
{
if (yBuf_high[m] < 16) yBuf_high[m] = 16;
if (yBuf_high[m] > 235) yBuf_high[m] = 235;
}
fwrite(yBuf_high, 1, width * height, yuvFile);
fwrite(uBuf, 1, (width * height) / 4, yuvFile);
fwrite(vBuf, 1, (width * height) / 4, yuvFile);
}
2、淡出(变暗)
与变明的思想相同,只需先将原图的亮度Y存入新的缓存当中,再将亮度Y依次除以1.08即可,以下为代码实现:
for (i = 0; i < width*height; i++)//获取初始缓存
{
yBuf_low[i] = yBuf[i];
}
for(int x=0;x<10;x++)//依次变暗
{
for (int j = 0; j < width*height; j++)
{
yBuf_low[j] = yBuf_low[j]/1.08;
}
for (int m = 0; m < width*height; m++)
{
if (yBuf_low[m] < 16) yBuf_low[m] = 16;
if (yBuf_low[m] > 235) yBuf_low[m] = 235;
}
fwrite(yBuf_low, 1, width * height, yuvFile);
fwrite(uBuf, 1, (width * height) / 4, yuvFile);
fwrite(vBuf, 1, (width * height) / 4, yuvFile);
}
3、原始亮度图片
只需在淡入和淡出之间插入10张原始图片即可实现一个附带淡入淡出转场的动画,以下是插入10张原始图片的代码:
for(i=0;i<10;i++)
{
fwrite(yBuf, 1, width * height, yuvFile);
fwrite(uBuf, 1, (width * height) / 4, yuvFile);
fwrite(vBuf, 1, (width * height) / 4, yuvFile);
}
完整代码在垂直滑像转场讲解完之后给出
4、效果演示
这里只选择了一张图片进行演示,多张图片演示在垂直滑像转场中有演示,因为CSDN只支持上传GIF图片所以效果不是很好。
五、垂直滑像转场的实现(逐行渐变)
1、代码实现
垂直滑像转场的实现分为两步,第一步将第一张图像全部存入y、u、v缓存中,第二步将后一张图片依次存入y、u、v缓存中即可实现,以下是代码实现:
//换行
for(int b=0;b<width*height;b++)//将第一张图片y存入缓存
{
yBuf_huanhang[b] = yBuf[b];
}
for(int b=0;b<width*height/4;b++)//将第一张图片u、v存入缓存
{
uBuf_huanhang[b] = uBuf[b];
vBuf_huanhang[b] = vBuf[b];
}
for(int a=30;a>0;a--)//依次将后一张图片y、u、v存入缓存中
{
for(int b=0;b<width*height/a;b++)
{
yBuf_huanhang[b] = yBuf_2[b];
}
for(int b=0;b<width*height/a/4;b++)
{
uBuf_huanhang[b] = uBuf_2[b];
vBuf_huanhang[b] = vBuf_2[b];
}
fwrite(yBuf_huanhang, 1, width * height, yuvFile);
fwrite(uBuf_huanhang, 1, (width * height) / 4, yuvFile);
fwrite(vBuf_huanhang, 1, (width * height) / 4, yuvFile);
}
2、效果演示
这里因为CSDN只支持上传GIF图片所以效果不是很好,且以淡入与垂直滑向两者结合做演示
六、整体代码演示
这里将三个转场功能做了注释,使用时可以根据要使用的转场进行取消注释
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "rgb2yuv.h"
#include <windows.h>
#define u_int8_t unsigned __int8
#define u_int unsigned __int32
#define u_int32_t unsigned __int32
#define FALSE false
#define TRUE true
/*
* rgb2yuv
* required arg1 should be the input RAW RGB24 file
* required arg2 should be the output RAW YUV12 file
*/
int main(int argc, char** argv)
{
BITMAPFILEHEADER File_header;
BITMAPINFOHEADER Info_header;
BITMAPFILEHEADER File_header_2;
BITMAPINFOHEADER Info_header_2;
bool flip = FALSE;
char* yuvFileName = NULL;
FILE* yuvFile = NULL;
yuvFileName = argv[5];//argv6是yuv的名字
yuvFile = fopen(yuvFileName, "wb");
for(int z = 1; z < 4; z++)
{
u_int32_t videoFramesWritten = 0;
int i;
u_int8_t* yBuf = NULL;
u_int8_t* uBuf = NULL;
u_int8_t* vBuf = NULL;
u_int8_t* rgbBuf = NULL;
u_int8_t* rgbBuf_2 = NULL;
u_int8_t* yBuf_2 = NULL;
u_int8_t* uBuf_2 = NULL;
u_int8_t* vBuf_2 = NULL;
u_int8_t* yBuf_low = NULL;
u_int8_t* yBuf_high = NULL;
u_int8_t* yBuf_huanhang = NULL;
u_int8_t* uBuf_huanhang = NULL;
u_int8_t* vBuf_huanhang = NULL;
u_int8_t* yBuf_huanhang_2 = NULL;
u_int8_t* uBuf_huanhang_2 = NULL;
u_int8_t* vBuf_huanhang_2 = NULL;
int width,height;//图像宽高信息
int width_2,height_2;
char* bmpFileName = NULL;
char* bmpFileName_2 = NULL;
FILE* bmp;
FILE* bmp_2;
bmpFileName = argv[z];
bmpFileName_2 = argv[z+1];
bmp = fopen(bmpFileName, "rb");
bmp_2 = fopen(bmpFileName_2, "rb");
if (bmp == NULL)
{
printf("cannot find bmp file\n");
exit(1);
}
else
{
printf("The output bmp file is %s\n", bmpFileName_2);
}
if (bmp_2 == NULL)
{
printf("cannot find bmp file\n");
exit(1);
}
else
{
printf("The output bmp file is %s\n", bmpFileName_2);
}
if(fread(&File_header,sizeof(BITMAPFILEHEADER),1,bmp)!=1)
{
printf("reader file header error!");
exit(0);
}
fread(&Info_header,sizeof(BITMAPINFOHEADER),1,bmp);
width = (int)Info_header.biWidth;//获取宽度信息
height = (int)Info_header.biHeight;//获取高度信息
if(fread(&File_header_2,sizeof(BITMAPFILEHEADER),1,bmp_2)!=1)
{
printf("reader file header error!");
exit(0);
}
fread(&Info_header_2,sizeof(BITMAPINFOHEADER),1,bmp_2);
width_2 = (int)Info_header_2.biWidth;
height_2 = (int)Info_header_2.biHeight;
//开辟空间
yBuf = (u_int8_t*)malloc(width * height);
uBuf = (u_int8_t*)malloc((width * height) / 4);
vBuf = (u_int8_t*)malloc((width * height) / 4);
yBuf_2 = (u_int8_t*)malloc(width * height);
uBuf_2 = (u_int8_t*)malloc((width * height) / 4);
vBuf_2 = (u_int8_t*)malloc((width * height) / 4);
uBuf_huanhang = (u_int8_t*)malloc((width * height) / 4);
vBuf_huanhang = (u_int8_t*)malloc((width * height) / 4);
uBuf_huanhang_2 = (u_int8_t*)malloc((width * height) / 4);
vBuf_huanhang_2 = (u_int8_t*)malloc((width * height) / 4);
yBuf_low = (u_int8_t*)malloc(width * height);
yBuf_high = (u_int8_t*)malloc(width * height);
yBuf_huanhang = (u_int8_t*)malloc(width * height);
yBuf_huanhang_2 = (u_int8_t*)malloc(width * height);
rgbBuf = (u_int8_t*)malloc(width * height * 3);
rgbBuf_2 = (u_int8_t*)malloc(width * height * 3);
fread(rgbBuf, 1, width * height * 3, bmp);
fread(rgbBuf_2, 1, width * height * 3, bmp_2);
if(RGB2YUV(width, height, rgbBuf, yBuf, uBuf, vBuf, flip))
{
printf("error");
return 0;
}
if(RGB2YUV(width_2, height_2, rgbBuf_2, yBuf_2, uBuf_2, vBuf_2, flip))
{
printf("error");
return 0;
}
for (i = 0; i < width*height; i++)
{
if (yBuf[i] < 16) yBuf[i] = 16;
if (yBuf[i] > 235) yBuf[i] = 235;
if (yBuf_2[i] < 16) yBuf_2[i] = 16;
if (yBuf_2[i] > 235) yBuf_2[i] = 235;
}
for (i = 0; i < width*height/4; i++)
{
if (uBuf[i] < 16) uBuf[i] = 16;
if (uBuf[i] > 240) uBuf[i] = 240;
if (vBuf[i] < 16) vBuf[i] = 16;
if (vBuf[i] > 240) vBuf[i] = 240;
if (uBuf_2[i] < 16) uBuf_2[i] = 16;
if (uBuf_2[i] > 240) uBuf_2[i] = 240;
if (vBuf_2[i] < 16) vBuf_2[i] = 16;
if (vBuf_2[i] > 240) vBuf_2[i] = 240;
}
变亮
//for (i = 0; i < width*height; i++)//变亮
//{
// yBuf_high[i] = yBuf[i]/2.16;
//}
//for(int x=0;x<10;x++)
//{
// for (int j = 0; j < width*height; j++)//变亮
// {
// yBuf_high[j] = yBuf_high[j]*1.08;
// }
// for (int m = 0; m < width*height; m++)
// {
// if (yBuf_high[m] < 16) yBuf_high[m] = 16;
// if (yBuf_high[m] > 235) yBuf_high[m] = 235;
// }
//
// fwrite(yBuf_high, 1, width * height, yuvFile);
// fwrite(uBuf, 1, (width * height) / 4, yuvFile);
// fwrite(vBuf, 1, (width * height) / 4, yuvFile);
//}
//不变
for(i=0;i<30;i++)
{
fwrite(yBuf, 1, width * height, yuvFile);
fwrite(uBuf, 1, (width * height) / 4, yuvFile);
fwrite(vBuf, 1, (width * height) / 4, yuvFile);
}
// //换行
//for(int b=0;b<width*height;b++)//将第一张图片y存入缓存
// {
// yBuf_huanhang[b] = yBuf[b];
//
// }
// for(int b=0;b<width*height/4;b++)//将第一张图片u、v存入缓存
//{
// uBuf_huanhang[b] = uBuf[b];
// vBuf_huanhang[b] = vBuf[b];
//
// }
// for(int a=30;a>0;a--)//依次将后一张图片y、u、v存入缓存中
// {
// for(int b=0;b<width*height/a;b++)
// {
// yBuf_huanhang[b] = yBuf_2[b];
//
//
// }
// for(int b=0;b<width*height/a/4;b++)
// {
// uBuf_huanhang[b] = uBuf_2[b];
// vBuf_huanhang[b] = vBuf_2[b];
// }
// fwrite(yBuf_huanhang, 1, width * height, yuvFile);
// fwrite(uBuf_huanhang, 1, (width * height) / 4, yuvFile);
// fwrite(vBuf_huanhang, 1, (width * height) / 4, yuvFile);
//}
变暗
// for (i = 0; i < width*height; i++)// //变暗
//{
// yBuf_low[i] = yBuf[i];
//}
//for(int x=0;x<10;x++)
//{
// for (int j = 0; j < width*height; j++)// //变暗
//{
// yBuf_low[j] = yBuf_low[j]/1.08;
//}
// for (int m = 0; m < width*height; m++)
//{
// if (yBuf_low[m] < 16) yBuf_low[m] = 16;
// if (yBuf_low[m] > 235) yBuf_low[m] = 235;
//}
//fwrite(yBuf_low, 1, width * height, yuvFile);
//fwrite(uBuf, 1, (width * height) / 4, yuvFile);
//fwrite(vBuf, 1, (width * height) / 4, yuvFile);
//}
}
return(0);
}
七、归纳总结
通过此次实验熟悉了两种图像格式之间的转换,以及对转场的实现,并且熟悉巩固了C++语言的使用,为后续进一步学习数据压缩打下了基础。