数码相框的准备——显示并缩放bmp图片
一、写在前面
后续的文章将会在电子书的基础上实现一个数码相框。在讲解框架之前,先讲解如何在LCD上显示bmp图片
并支持缩放。另外,后面在讲完一个知识点后便贴出源码实现,加深理解。
对于在LCD显示bmp位图,分三步:
1)解析BMP文件,获得位图数据
2)缩放图片
3)合并图片
下面按这三步依序讲解。
二、BMP图片格式解析与源码实现
1)理解BMP图片格式
对于理解BMP,只需了解它的组成 和 两个结构体 就足以编程了。
1.1) 位图文件三部分组成,如下图
文件信息头:用来描述位图文件有关信息,如位图水平/垂直分辨率、位图文件名字、大小等
位图信息头:用来描述位图有关信息,如位图bpp,位图宽度、高度等
RGB颜色阵列:注意:Windows下,RGB颜色阵列存储的格式其实BGR
1.2)位图的两个结构体(只注释编程用到的)
typedef struct tagBITMAPFILEHEADER { /* bmfh */
unsigned short bfType; // 文件的类型,该值必需是0x4D42,也就是字符'BM'
unsigned long bfSize; // 该位图文件的大小,用字节为单位
unsigned short bfReserved1; // 保留,必须设置为0
unsigned short bfReserved2; // 保留,必须设置为0
unsigned long bfOffBits; // 文件头开始到实际的图象数据之间的字节的偏移量
}__attribute__((packed)) BITMAPFILEHEADER;;
typedef struct tagBITMAPINFOHEADER { /* bmih */
unsigned long biSize; // 说明BITMAPINFOHEADER结构所需要的字数。
unsigned long biWidth; // 说明图象的宽度,以象素为单位
unsigned long biHeight; // 说明图象的高度,以象素为单位
unsigned short biPlanes;
unsigned short biBitCount; // 说明比特数/象素,其值为1、4、8、16、24、或32
unsigned long biCompression;
unsigned long biSizeImage;
unsigned long biXPelsPerMeter;
unsigned long biYPelsPerMeter;
unsigned long biClrUsed;
unsigned long biClrImportant;
}__attribute__((packed)) BITMAPINFOHEADER;
备注:__attribute__((packed)) 用于结构体对齐,这适用于较老的编译器。
对于新的编译器,可用
#pragma pack(push) /* 将当前pack设置压栈保存 */
#pragma pack(1) /* 必须在结构体定义之前使用 */
/* ... */
#pragma pack(pop) /* 恢复先前的pack设置 */
详解可参考:https://www.cnblogs.com/CZM-/p/5398720.html
说明:以上解析来源自:https://blog.51cto.com/redwolf/229096 (推荐阅读)
2)编程解析BMP的注意点
a. Windows下,RGB颜色阵列存储的格式其实BGR
b. 位图数据从左下角开始存放,而LCD显示从左上角开始。写入FB和绘制位图要注意这一点。
c.下面源码实现的位图的bpp是24位,因LCD的bpp可能是16/24/32位,在显示一行前需转换
3)源码实现
static int CovertOneLine(int iWidth, int iSrcBpp, int iDstBpp, unsigned char *pudSrcDatas, unsigned char *pudDstDatas)
{
unsigned int dwRed;
unsigned int dwGreen;
unsigned int dwBlue;
unsigned int dwColor;
unsigned short *pwDstDatas16bpp = (unsigned short *)pudDstDatas;
unsigned int *pwDstDatas32bpp = (unsigned int *)pudDstDatas;
int i;
int pos = 0;
if(iSrcBpp != 24)
{
return -1;
}
if(iDstBpp == 24)
{
memcpy(pudDstDatas, pudSrcDatas, iWidth*3);
}
else
{
for(i = 0; i < iWidth; ++i)
{
dwBlue = pudSrcDatas[pos++];
dwGreen = pudSrcDatas[pos++];
dwRed = pudSrcDatas[pos++];
if(iDstBpp == 32)
{
dwColor = (dwRed << 16) | (dwGreen << 8) | dwBlue;
*pwDstDatas32bpp = dwColor;
pwDstDatas32bpp++;
}
else if(iDstBpp == 16)
{
/* 565 */
dwRed = dwRed >> 3;
dwGreen = dwGreen >> 2;
dwBlue = dwBlue >> 3;
dwColor = (dwRed << 11) | (dwGreen << 5) | (dwBlue);
*pwDstDatas16bpp = dwColor;
pwDstDatas16bpp++;
}
}
}
}
static int GetBmpPixelDatasFrmBMP(unsigned char *aFileHead, PT_PixelDatas ptPixelDatas)
{
/* 解析BMP文件,获得位图首地址 */
BITMAPFILEHEADER *p_tBitmapHeader;
BITMAPINFOHEADER *p_tBitmapInfoHeader;
int iWidth;
int iHeight;
int iBMPBpp;
int y;
unsigned char *pucSrc;
unsigned char *pucDest;
int iLineWidthAlign;
int iLineWidthReal;
p_tBitmapHeader = (BITMAPFILEHEADER *)aFileHead;
p_tBitmapInfoHeader = (BITMAPINFOHEADER *)(aFileHead + sizeof(BITMAPFILEHEADER));
iWidth = p_tBitmapInfoHeader->biWidth;
iHeight = p_tBitmapInfoHeader->biHeight;
iBMPBpp = p_tBitmapInfoHeader->biBitCount;
if(iBMPBpp != 24)
{
DBG_PRINTF("iBMPBpp = %d\n", iBMPBpp);
DBG_PRINTF("sizeof(BITMAPFILEHEADER) = %d\n", sizeof(BITMAPFILEHEADER));
return -1;
}
ptPixelDatas->iWidth = iWidth;
ptPixelDatas->iHeight = iHeight;
ptPixelDatas->aucPixelDatas = malloc(iWidth * iHeight * ptPixelDatas->iBpp / 8);
ptPixelDatas->iLineBytes = (iWidth * ptPixelDatas->iBpp / 8);
if(NULL == ptPixelDatas->aucPixelDatas)
{
return -1;
}
iLineWidthReal = iWidth * iBMPBpp / 8;
iLineWidthAlign = (iLineWidthReal + 3) & ~0x3;
pucSrc = aFileHead + p_tBitmapHeader->bfOffBits;
pucSrc = pucSrc + (iHeight -1) * iLineWidthAlign;
pucDest = ptPixelDatas->aucPixelDatas;
/* 将位图映射到LCD的FrameBuffer */
for(y = 0; y < iHeight; ++y)
{
CovertOneLine(iWidth, iBMPBpp, ptPixelDatas->iBpp, pucSrc, pucDest);
pucSrc -= iLineWidthAlign;
pucDest += ptPixelDatas->iLineBytes;
}
return 0;
}
static void FreeBmpPixelDatas(PT_PixelDatas ptBmpPixelData)
{
free(ptBmpPixelData->aucPixelDatas);
}
三、图像缩放算法(近邻取样插值)与源码实现
1)近邻取样插值的缩放原理
根据前后缩放比列不变,即 Dx / DW = Sx / SW,。可算出
缩放后每个像素点的坐标位置,然后在把原图的RGB写入缩放后对应坐标位置即可。
2)源码实现
#include <pic_operation.h>
#include <string.h>
#include <stdlib.h>
int PicZoom(PT_PixelDatas ptOriginPic, PT_PixelDatas ptZoomPic)
{
unsigned long x;
unsigned long y;
unsigned long srcy;
unsigned long dst_width = ptZoomPic->iWidth;
unsigned long* SrcX_Table = malloc(sizeof(unsigned long) * dst_width);
unsigned char *pucDest;
unsigned char *pucSrc;
unsigned long dwPixelBytes = ptOriginPic->iBpp/8;
if(ptOriginPic->iBpp != ptZoomPic->iBpp)
{
return -1;
}
for (x =0; x < dst_width; ++x)//生成表 SrcX_Table,记录一行中每个像素缩放后的坐标位置
{ // 因为每行的每个像素缩放比例是一定的,可提前算出,加速
SrcX_Table[x] = (x * ptOriginPic->iWidth/ ptZoomPic->iWidth);
}
for (y = 0; y < ptZoomPic->iHeight; ++y) // 处理一列
{
srcy = (y * ptOriginPic->iHeight / ptZoomPic->iHeight);
pucDest = ptZoomPic->aucPixelDatas + y * ptZoomPic->iLineBytes;
pucSrc = ptOriginPic->aucPixelDatas + srcy * ptOriginPic->iLineBytes;
for (x=0;x<dst_width;++x) // 处理一行,逐像素处理
{
memcpy(pucDest+x*dwPixelBytes, pucSrc+SrcX_Table[x]*dwPixelBytes, dwPixelBytes);
}
}
free(SrcX_Table);
return 0;
}
四、图片合并与源码实现
1)图片合并解释,看下图(有个错字“钟”-->中)
2)源码实现
int PicMerge(int iX, int iY, PT_PixelDatas ptSmallPic, PT_PixelDatas ptBigPic)
{
int i;
unsigned char *pucSrc;
unsigned char *pucDst;
if ((ptSmallPic->iWidth > ptBigPic->iWidth) ||
(ptSmallPic->iHeight > ptBigPic->iHeight) ||
(ptSmallPic->iBpp != ptBigPic->iBpp))
{
return -1;
}
pucSrc = ptSmallPic->aucPixelDatas;
pucDst = ptBigPic->aucPixelDatas + iY * ptBigPic->iLineBytes + iX * ptBigPic->iBpp / 8;
for(i = 0; i < ptSmallPic->iHeight; ++i)
{
memcpy(pucDst, pucSrc, ptSmallPic->iLineBytes);
pucSrc += ptSmallPic->iLineBytes;
pucDst += ptBigPic->iLineBytes;
}
return 0;
}
五、mian.c 与 实验效果
main.c 测试源码如下:
/* 省略一堆头文件 */
/* ./show_file [-s Size] [-f freetype_font_file] [-h HZK] <text_file> */
int main(int argc, char ** argv)
{
int iFdBmp;
int iRet;
unsigned char *pucBMPmem;
struct stat tBMPstat;
PT_DispOpr ptDispOpr;
extern T_PicFileParser g_tBmpFileParser;
T_PixelDatas tPixelDatas;
T_PixelDatas tPixelDatasSmall;
T_PixelDatas tPixelDatasFB;
if (argc != 2)
{
printf("%s <bmp_file>\n", argv[0]);
return -1;
}
DebugInit();
InitDebugChanel();
DisplayInit();
ptDispOpr = GetDispOpr("fb");
ptDispOpr->DeviceInit();
ptDispOpr->CleanScreen(0);
/* 打开BMP文件 */
iFdBmp = open(argv[1], O_RDWR);
if (iFdBmp == -1)
{
DBG_PRINTF("can't open %s\n", argv[1]);
}
fstat(iFdBmp, &tBMPstat);
pucBMPmem = (unsigned char *)mmap(NULL , tBMPstat.st_size, PROT_READ | PROT_WRITE,
MAP_SHARED, iFdBmp, 0);
if (pucBMPmem == (unsigned char *)-1)
{
DBG_PRINTF("mmap error!\n");
return -1;
}
/* 提取BMP文件的RGB数据, 缩放, 在LCD上显示出来 */
iRet = g_tBmpFileParser.isSupport(pucBMPmem);
if(iRet == 0)
{
DBG_PRINTF("%s is not bmp file\n", argv[1]);
return -1;
}
tPixelDatas.iBpp = ptDispOpr->iBpp;
iRet = g_tBmpFileParser.GetPixelDatas(pucBMPmem, &tPixelDatas);
if (iRet)
{
DBG_PRINTF("GetPixelDatas error!\n");
return -1;
}
tPixelDatasFB.iWidth = ptDispOpr->iXres;
tPixelDatasFB.iHeight = ptDispOpr->iYres;
tPixelDatasFB.iBpp = ptDispOpr->iBpp;
tPixelDatasFB.iLineBytes = ptDispOpr->iXres * ptDispOpr->iBpp / 8;
tPixelDatasFB.aucPixelDatas = ptDispOpr->pucDispMem;
PicMerge(0, 0, &tPixelDatas, &tPixelDatasFB);
tPixelDatasSmall.iWidth = tPixelDatas.iWidth/2;
tPixelDatasSmall.iHeight = tPixelDatas.iHeight/2;
tPixelDatasSmall.iBpp = tPixelDatas.iBpp;
tPixelDatasSmall.iLineBytes = tPixelDatasSmall.iWidth * tPixelDatasSmall.iBpp / 8;
tPixelDatasSmall.aucPixelDatas = malloc(tPixelDatasSmall.iLineBytes * tPixelDatasSmall.iHeight);
PicZoom(&tPixelDatas, &tPixelDatasSmall);
PicMerge(128, 128, &tPixelDatasSmall, &tPixelDatasFB);
/* 记得释放 */
g_tBmpFileParser.FreePixelDatas(&tPixelDatas);
munmap(pucBMPmem, tBMPstat.st_size);
return 0;
}
实验效果图如下: