数码相框(十八、bmp数据提取用于图标显示)

注:本人已购买韦东山老师第三期项目视频,内容来源《数码相框项目视频》,只用于学习记录,如有侵权,请联系删除。

1. 数码相框效果图

根据上一节的数码相框需求框架可得出的大致效果图如下图所示:
数码相框(十八、bmp数据提取用于图标显示)_第1张图片

    上图的图标都是经过缩小的图标,实际上这些图标的分辨率都是比较大的,例如方形的图标分辨率为 128*128;但是我们 Jz2440 开发板的 LCD 只有 480 * 272,如果按实际图片的尺寸大小显示,显然是不可取的,所以需要进行 图片缩放 ;另外上图图标的格式为 bmp 格式的图标,要在 LCD 上显示这种格式的图片,就涉及到了 bmp 格式的图片解码

2. BMP 格式图片解码

(1) 关于 BMP 格式图片的数据分析可以查看这个两篇博客:bitmap格式分析、BMP图像数据格式详解。

(2) 这里需要注意的是 LCD 是从左上角开始显示图像的,而 BMP 图片像素数据的存储是从左下角开始的,如下图所示。因此在显示BMP图片时需要从BMP图片的左上角开始读取数据发送给LCD显示:
数码相框(十八、bmp数据提取用于图标显示)_第2张图片
(3) BMP 格式图片解码代码:
① 在 pic_operation.h 中定义像素数据结构体 T_PixelDatas、文件解析结构体 T_PicFileParser,代码如下:

#ifndef _PIC_OPERATION_H
#define _PIC_OPERATION_H

typedef struct PixelDatas{
	int iWidth;
	int iHeight;
	int iBpp;
	int iLineBytes;
	unsigned char *aucPixelDatas;
}T_PixelDatas, *PT_PixelDatas;

typedef struct PicFileParser{
	char *name;
	int (*isSupport)(unsigned char *aFileHead);
	int (*GetPixelDatas)(unsigned char *aFileHead, PT_PixelDatas tPixelDatas);
	int (*FreePixelDatas)(PT_PixelDatas tPixelDatas);
}T_PicFileParser, *PT_PicFileParser;

int PicZoom(PT_PixelDatas ptOriginPic, PT_PixelDatas ptZoomPic);
int PicMerge(int iX, int iY, PT_PixelDatas ptSmallPic, PT_PixelDatas ptBigPic);

#endif /* _PIC_OPERATION_H */

② bmp解码代码如下:bmp.c

#include 
#include 
#include 
#include 

#pragma pack(push)  /* 将当前pack设置压栈保存 */
#pragma pack(1)     /* 必须在结构体定义之前使用 */

/* bmp 文件头部 */ 
typedef struct tagBITMAPFILEHEADER { /* bmfh */
	unsigned short bfType;  /* 由于更快的访问bfType, 此时的bfType被4字节对齐 */
	unsigned long bfSize;
	unsigned short bfReserved1;
	unsigned short bfReserved2;
	unsigned long bfOffBits;
} BITMAPFILEHEADER;

#pragma pack(pop) /* 恢复先前的pack设置 */

/* bmp 位图信息头部 */ 
typedef struct tagBITMAPINFOHEADER { /* bmih */
	unsigned long  biSize;
	unsigned long  biWidth;
	unsigned long  biHeight;
	unsigned short biPlanes;
	unsigned short biBitCount;
	unsigned long  biCompression;
	unsigned long  biSizeImage;
	unsigned long  biXPelsPerMeter;
	unsigned long  biYPelsPerMeter;
	unsigned long  biClrUsed;
	unsigned long  biClrImportant;
} BITMAPINFOHEADER;

/* bmp 文件开头的标记为 0x4D42
 * 以此判断是否支持BMP格式
 */
static int isBMPFormat(unsigned char *aFileHead)
{
	if (aFileHead[0] != 0x42 || aFileHead[1] != 0x4D)
	{
		return 0;
	}
	else
	{
		return -1;
	}
}

/* 从BMP图片获取一行数据 */
static int ConvertOneLineFrmBMP(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)
			{
				dwRed   = dwRed >> 3;
				dwGreen = dwGreen >> 2;
				dwBlue  = dwBlue >> 3;
				dwColor = (dwRed << 11) | (dwGreen << 5) | dwBlue;
				*pwDstDatas16bpp = dwColor;
				pwDstDatas16bpp++;
			}
		}
	}
	return 0;
}


/* 获取BMP图片像素数据 
 * ptPixelDatas->iBpp 是输入的参数,它决定从BMP得到的数据要转换为该格式
 */
static int GetPixelDatasFrmBMP(unsigned char *aFileHead, PT_PixelDatas ptPixelDatas)
{
	BITMAPFILEHEADER *ptBITMAPFILEHEADER;
	BITMAPINFOHEADER *ptBITMAPINFOHEADER;

	int iWidth;
	int iHeight;
	int iBMPBpp;
	int y;

	unsigned char *pucSrc;
	unsigned char *pucDest;
	int iLineWidthAlign;
	int iLineWidthReal;
	
	ptBITMAPFILEHEADER = (BITMAPFILEHEADER *)aFileHead;
	ptBITMAPINFOHEADER = (BITMAPINFOHEADER *)(aFileHead + sizeof(BITMAPFILEHEADER));

	iWidth  = ptBITMAPINFOHEADER->biWidth;
	iHeight = ptBITMAPINFOHEADER->biHeight;
	iBMPBpp = ptBITMAPINFOHEADER->biBitCount;

	if (iBMPBpp != 24)
	{
		return -1;
	}
	
	ptPixelDatas->iWidth  = iWidth;
	ptPixelDatas->iHeight = iHeight;
	ptPixelDatas->aucPixelDatas = malloc(iWidth * iHeight * ptPixelDatas->iBpp / 8);
	ptPixelDatas->iLineBytes = iWidth * ptPixelDatas->iBpp / 8;
	if (ptPixelDatas->aucPixelDatas == NULL)
	{
		return -1;
	}

	iLineWidthReal = iWidth * iBMPBpp / 8;
	iLineWidthAlign = (iLineWidthReal + 3) & ~0x03; /* 4字节对齐 */
	
	pucSrc = aFileHead +ptBITMAPFILEHEADER->bfOffBits;
	pucSrc = pucSrc + (iHeight - 1) * iLineWidthAlign; /* 把pucSrc指向图片数据的最后一行 */

	pucDest = ptPixelDatas->aucPixelDatas;
	
	for (y = 0; y < iHeight; y++)
	{
		ConvertOneLineFrmBMP(iWidth, iBMPBpp, ptPixelDatas->iBpp, pucSrc, pucDest);
		pucSrc -= iLineWidthAlign;
		pucDest += ptPixelDatas->iLineBytes;
	}

	return 0;
}

static int FreePixelDatasForBMP(PT_PixelDatas ptPixelDatas)
{
	free(ptPixelDatas->aucPixelDatas);
	return 0;
}

T_PicFileParser g_tBMPParser = {
	.name           = "bmp",
	.isSupport      = isBMPFormat,
	.GetPixelDatas  = GetPixelDatasFrmBMP,
	.FreePixelDatas = FreePixelDatasForBMP,
};

3. BMP 格式图片缩放

(1) 关于图像的缩放算法,可以参考博客:图像缩放算法。
(2) 在图像缩放算法这博客中,有写到图片的缩放原理如下图所示:根据原图和缩放后图片的比例相同的原理,通过缩放后图片的 x、y 坐标求出原图的Sx、Sy坐标,然后取出原图(Sx,Sy)坐标的颜色值赋值给缩放图片的(x,y)坐标。
数码相框(十八、bmp数据提取用于图标显示)_第3张图片
由此可以得出基本的代码框架如下图所示:
数码相框(十八、bmp数据提取用于图标显示)_第4张图片
由于图片的像素是一行一行的读取的,在上图的 for 的第二层循环中,由于 y 的值是不变的,所有为了节省计算,优化如下:
数码相框(十八、bmp数据提取用于图标显示)_第5张图片
另外,对与图片的每一列,x 的值是相等的,我们可以事先算出每一列 x 的值,存放在一个 table 里面,这样就不用每执行一次第二层for循环都重新计算 Sx 的值,从而节省计算时间,优化如下:
数码相框(十八、bmp数据提取用于图标显示)_第6张图片
(3) 图片缩放的代码如下:zoom.c

#include 
#include 
#include 

int PicZoom(PT_PixelDatas ptOriginPic, PT_PixelDatas ptZoomPic)
{
	unsigned long dwDstWidth = ptZoomPic->iWidth;
	unsigned long *pdwSrcXTable = malloc(sizeof(unsigned long) * dwDstWidth);
	unsigned long x;
	unsigned long y;
	unsigned long dwSrcY;
	unsigned char *pucDest;
	unsigned char *pucSrc;
	unsigned long dwPixelBytes = ptOriginPic->iBpp / 8;

	if (ptOriginPic->iBpp != ptZoomPic->iBpp)
	{
		free(pdwSrcXTable);
		return -1;
	}

	for (x = 0; x < dwDstWidth; x++)
	{
		pdwSrcXTable[x] = x * ptOriginPic->iWidth / ptZoomPic->iWidth;
	}


	for (y = 0; y < ptZoomPic->iHeight; y++)
	{
		dwSrcY  = y *  ptOriginPic->iHeight / ptZoomPic->iHeight;
		pucDest = ptZoomPic->aucPixelDatas + y * ptZoomPic->iLineBytes;
		pucSrc  = ptOriginPic->aucPixelDatas + dwSrcY * ptOriginPic->iLineBytes;
		for (x = 0; x < dwDstWidth; x++)
		{
			/* 原图坐标:SrcX_Table[x], dwSrcY
			 * 缩放坐标:x, y
			 */
			memcpy(pucDest + x * dwPixelBytes , pucSrc + pdwSrcXTable[x] * dwPixelBytes, dwPixelBytes);
		}
	}
	
	free(pdwSrcXTable);
	return 0;
}

图片经过缩小后得到一个小的数组,如何把这个小的数组放到大的 framebuffer里呢?代码如下:merge.c

#include 
#include 

/* 把小图片合并到大图片的(iX,iY)坐标中
 * (iX,iY)坐标是小图片合并到大图的坐标
 */
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;
}

4. LCD 显示bmp格式图片

在 main.c 中,显示一幅原图和原图长宽缩小1/2的图片,代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


/* ./digitpic  */
int main(int argc, char **argv)
{
	int iFdBmp;
	int iRet;
	PT_DispOpr ptDispOpr;
	unsigned char *pucBMPmem;
	struct stat tBMPStat;
	extern T_PicFileParser g_tBMPParser;
	T_PixelDatas tPixelDatas;
	T_PixelDatas tPixelDatasSmall;
	T_PixelDatas tPixelDatasFB;
	
	if (argc != 2)
	{
		printf("%s \n", argv[0]);
		return -1;
	}
	
	DebugInit();
	InitDebugChannel();
	
	DisplayInit();

	ptDispOpr = GetDispOpr("fb");
	ptDispOpr->DeviceInit();
	ptDispOpr->CleanScreen(0x00);

	/* 打开BMP文件 */
	iFdBmp = open(argv[1], O_RDWR);
	if (iFdBmp == -1)
	{
		DBG_PRINTF("can't open %s!\n", argv[1]);
		return -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_tBMPParser.isSupport(pucBMPmem);
	if (iRet == 0)
	{
		DBG_PRINTF("%s is not bmp file!\n", argv[1]);
		return -1;			
	}

	tPixelDatas.iBpp = ptDispOpr->iBpp;
	iRet = g_tBMPParser.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);

	/* 缩小到原来的1/4 */
	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);

	free(tPixelDatasSmall.aucPixelDatas);
	return 0;	
}

5. 测试

把编译好程序拷贝到开发板,运行:./digitpic browse_mode.bmp,运行结果如下图所示:
数码相框(十八、bmp数据提取用于图标显示)_第7张图片

你可能感兴趣的:(linux,bmp图片解码,LCD显示)