文章节选自:《ARM Cortex-M0从这里开始》 作者:zhaojun_xf https://bbs.eeworld.com.cn/thread-324656-1-1.html
1概述 数码相框常见的支持格式有BMP\GIF\JPG\TIF等,由于使用LPC1100微处理器实现数码相框,只能是达到原理性的效果,特殊效果是没法实现的。再加上除了BMP格式外,都要涉及到解码问题,而使用微处理器软件解码也是非常缓慢的。所以,本书只讲解BMP格式的显示。 2 BMP结构 BMP是Windows操作系统中的标准图像文件格式,使用非常广泛。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP占用的空间很大。BMP文件的图像深度可选1Bit、4Bit、8Bit、16Bit、24Bit、32Bit等。BMP文件存储数据时,图像的扫描方式是按照从左到右、从下到上的顺序。典型的BMP图像文件由四部组成,如表7-5所示。
下面就以一副24位位图来说明其文件结构。如图7-26所示,为这幅图的属性。
(1)位图文件头 由14字节数据组成,包含文件的标志、文件大小和实际数据偏移量等信息。为了直观显示文件头信息,下面定义一个结构体来表示文件头。 typedef struct tagBITMAPFILEHEADER { uint16 bfType; // 说明文件类型,在windows系统中为BM uint32 bfSize; // 说明文件大小 uint16 bfReserved1; // 保留,设置为0 uint16 bfReserved2; // 保留,设置为0 uint32 bfOffBits; // 说明实际图形数据的偏移量 }BITMAPFILEHEADER; // 14Byte 如图7-27所示,为一副图的文件头信息,已经用黑色线条把14字节的位图文件头标出。同样是使用WinHex软件打开的,需要注意的是,这里采用的是小端格式存储。
文件头说明: bfType:2字节组成,数据为42 4D,是BM的ASCII值,表示此图为位图; bfSize :4字节数据为36 84 03 00,表示文件大小,转换一下为0x00038436 = 230454字节; bfReserved1:2字节组成,数据为00 00,保留; bfReserved2:2字节组成,数据为00 00,保留; bfOffBits: 4字节数据为36 00 00 00,表示图像数据是从0x00000036 = 54开始。 (2)位图信息头 由40字节数据组成,包含图像宽度、高度、数据位数、压缩、分辨率等信息。为了直观显示信息头结构,下面定义一个结构体来表示文件头。 typedef struct tagBITMAPINFOHEADER { uint32 biSize; // 说明BITMAPINFOHEADER结构所需字节数,在windows系 // 统中为28h uint32 biWidth; // 说明图像宽度 uint32 biHeight; // 说明图像高度 uint16 biPlanes; // 为目标设备说明位面数,其值设为1 uint16 biBitCount; // 每个像素的位数,单色位图为1,256色为8,16bit为 // 16,24bit为24 uint32 biCompression; // 压缩说明,BI_RGB:无压缩,BI_RLE8:8位RLE压缩, // BI_RLE4:4位RLE压缩 uint32 biSizeImage; // 说明图像大小,如无压缩,可设为0 uint32 biXPelsPerMerer; // 水平分辨率 uint32 biYPelsPerMerer; // 垂直分辨率 uint32 biClrUsed; // 位图使用的颜色数 uint32 biClrImportant; // 重要颜色数目 }BITMAPINFOHEADER; // 40Byte 如图7-28所示为位图信息头数据。
信息头说明: biSize:4字节数据28 00 00 00,即0x00000028 = 40,表示信息头占用40字节空间; biWidth:4字节数据40 01 00 00,即0x00000140 = 320,表示图像宽320像素; biHeight:4字节数据F0 00 00 00,即0x0000000F = 240,表示图像高240像素; biplanes:2字节数据01 00,即0x0001 = 1,为目标设备说明位面数; biBitCount:2字节数据18 00,即0x0018 = 24,表示为24位位图; biSizeImage:4字节数据00 84 03 00,即0x00038400 = 230400字节,表示实际图像数据大小。 (3)彩色表 包含的元素与位图所具有的颜色数目相同,像素颜色用结构RGBQUAD存储。只有biBitCount等于1、4、8时才有调色板。调色板实际上是一个数组,元素的个数由biBitCount和biClrUsed决定。 typedef struct tagRGBQUAD { uint8 rgbBlue; // 指定蓝色强度 uint8 rgbGreen; // 指定绿色强度 uint8 rgbRed; // 指定红色强度 uint8 rgbReserved; // 保留,设为0 }RGBQUAD; // 4Byte (4)图像数据 图像数据扫描方式如7-29所示,在彩色表后的是图像数据阵列,图像每一扫描行由连续的字节组成,扫描行由底向上存储,阵列中第一字节为左下角像素,最后一字节为右上角像素。
3 BMP显示 本数码相框实现16位和24位位图的显示。 (1)获取文件头信息 显示图像之前先获取文件的一些信息,上面讲过位图的文件头信息一共占用54字节,而这里只读取需要的部分信息数据,如程序清单7-17所示。 /****************************** 程序清单7-17 **********************************/ /******************************************************************************** * FunctionName : Bmp_GetHeadInfo() * Description : 获取BMP头信息 * EntryParameter : buf - 信息 * ReturnValue : None ********************************************************************************/ BMP_HEADER Bmp_GetHeadInfo(uint8 *buf) { BMP_HEADER bmpHead;
bmpHead.bfType = (buf[0]<<8)+buf[1]; // BM bmpHead.bfSize = (buf[5]<<24 +(buf[4]<<16)+(buf[3]<<8)+buf[2]; // 大小 bmpHead.biWidth = (buf[21]<<24)+(buf[20]<<16)+(buf[19]<<8)+buf[18]; // 宽度 bmpHead.biHeight= (buf[25]<<24)+(buf[24]<<16)+(buf[23]<<8)+buf[22]; // 高度 bmpHead.biBitCount = (buf[29] << 8) + buf[28]; // 每个像素的位数 return bmpHead; } (2)1555格式转换 在PC机上创建的16位位图中,用16位表示一个像素,所以两个字节可以表示1个像素。默认情况下16位DIB是555格式,最高位无效。所以要在TFT上显示时,必须先转换成RGB565,否则显示是有问题的。 说明:在RGB555转换RGB565格式时要注意: RGB555格式中,最高位无效:1+5+5+5; RGB565中G的最低位可以不管,也可以根据G的最高位的值来补"1"还是补"0"。 /****************************** 程序清单7-18 **********************************/ /******************************************************************************** * FunctionName : Bmp_555To565() * Description : 颜色数据格式转换,默认情况下16位DIB是555格式 * EntryParameter : RGB555转换到RGB565; dat - 数据 * ReturnValue : None ********************************************************************************/ uint16 Bmp_555To565(uint16 dat) { return (uint16)(((dat & 0x7C00) << 1) | ((dat & 0x03E0) << 1) | (dat & 0x001F)); } (3)24位格式转换 在PC机创建位图时,一般都采用24位真彩色,结构比较简单。用24位表示一个像素,所以三个字节可以表示1个像素。需要注意的是存储顺序一般是BGR,而不是传统的RGB。由于TFT为565格式的16位数据格式,所以要显示24位位图在TFT上也必须转换一下。转换方法和很简单,B和R直接去掉最高3位数据,G去掉最高2位数据,之后组合成为565格式的16位数据即可,如程序清单7-19所示。 /****************************** 程序清单7-19 **********************************/ /******************************************************************************** * FunctionName : Bmp_24To565() * Description : 颜色数据格式转换,把24位转换成RGB565 * EntryParameter : RGB555转换到RGB565; * ReturnValue : None ********************************************************************************/ uint16 Bmp_24To565(uint8 r, uint8 g, uint8 b) { return (((uint16)(r >> 3) << 11) | ((uint16)(g >> 2) << 5) | ((uint16)b >> 3)); } (4)16位位图显示 16位位图显示比较简单,直接从数据缓冲区中读取两个字节数据,先转换成TFT的565格式后,发送给TFT即显示了相应像素点。如程序清单7-20所示。 /****************************** 程序清单7-20 **********************************/ /******************************************************************************** * FunctionName : Bmp_Disp16Bits() * Description : 显示16位位图 * EntryParameter : p - 数据指针 * ReturnValue : None ********************************************************************************/ void Bmp_Disp16Bits(uint8 *pData) { uint16 i;
for (i=0; i<MMC_BUFF_SIZE/2; i++) { TFTWriteData(Bmp_555To565((*(pData+i*2+1) << 8) + *(pData+i*2))); } } (5)24位位图显示 24位位图显示比较复杂一点,这是因为,一般情况下SD卡读取一扇区数据为512字节,而512字节并不能存储完整的RGB信息。在最后一个RGB数据中会少一个数据,一就是说512字节,前面510字节存放了170个像素点的颜色信息,而后面两字节只存放了一个像素点的部分信息,需要从下次读取是信息中获取一字节数据来补齐,这样就给显示带来了不少麻烦。 在读取第1个扇区时会缺少1字节,读取第2个扇区时会缺少2个字节,在读取第3个扇区时刚好构成完整数据。所以需要按照3个扇区为周期进行数据处理。具体代码如程序清单7-21所示。 /****************************** 程序清单7-21 **********************************/ /******************************************************************************** * FunctionName : Bmp_Disp24Bits() * Description : 显示24位位图。每读取512*3个字节数据,完成一次完整数据读取 * EntryParameter : pData - 数据指针 * ReturnValue : None ********************************************************************************/ void Bmp_Disp24Bits(uint8 *pData) { uint16 i = 0;
while (1) { if (0 == Offset24Bits%3) { Blue24Bits = *(pData+(Offset24Bits%MMC_BUFF_SIZE)); // 获取蓝色数据 if (0 == ++Offset24Bits%(512*3)) // 判断是否为一个周期 { Offset24Bits = 0; } if (++i >= MMC_BUFF_SIZE) // 判断是否读完数据 { break; } }
if (1 == Offset24Bits%3) // 获取绿色数据 { Green24Bits = *(pData+(Offset24Bits%MMC_BUFF_SIZE)); if (0 == ++Offset24Bits%(512*3)) { Offset24Bits = 0; } if (++i >= MMC_BUFF_SIZE) { break; } }
if (2 == Offset24Bits%3) { Red24Bits = *(pData+(Offset24Bits%MMC_BUFF_SIZE)); // 获取红色数据 // 三个数据都获取,发生到TFT显示 TFTWriteData(Bmp_24To565(Red24Bits,Green24Bits,Blue24Bits));
if (0 == ++Offset24Bits%(512*3)) { Offset24Bits = 0; } if (++i >= MMC_BUFF_SIZE) { break; } } } } (6)位图读取并显示 位图显示是通过函数参数,传递文件名,通过文件名读取文件数据。读取位图数据时,先读取54字节的头信息,在根据头信息判断是否为位图图像。如果不是位图,直接返回不再读取数据;如果是位图图像,再判断是16位还是24位。并循环读取数据信息,经过数据转换后发送到TFT进行显示。 读取位图文件需要调用FatFs文件系统的API函数,在读取文件时需要注意一下几点: 首先要f_mount函数注册工作区,在文件读取完成后,再调用这个函数来注销工作区; 打开文件需要调用f_open函数,当文件读取完成后,再调用f_close函数关闭文件,所以这两个函数必须成对出现; 在读取文件时,可以同f_read函数返回的结果和次函数的读取结果来判断数据是否读取完成。 /****************************** 程序清单7-22 **********************************/ /******************************************************************************** * FunctionName : BmpDisplay() * Description : bmp图片显示,实现位图的读取和显示 * EntryParameter : None * ReturnValue : None ********************************************************************************/ uint8 BmpDisplay(uint8 *bmpName) { FATFS fs; /*Work area (file system object) for logical drive*/ FIL file; /*file objects */ UINT br; /*File R/W count */ FRESULT res; BMP_HEADER bmpHead; // 获取头信息
f_mount(0, &fs); res = f_open(&file, (const TCHAR *)bmpName, FA_OPEN_EXISTING|FA_READ); if(res != FR_OK) { return res; } else { res = f_read(&file, MMCBuf, 54, &br); // 读取头文件 if(res != FR_OK) { return res; } else { bmpHead = Bmp_GetHeadInfo(MMCBuf); // 获取头信息 if (bmpHead.bfType == BMP) // 判断是否为BMP图像 { // 设置图片显示窗口 TFTSetWindows(0, 0, bmpHead.biHeight, bmpHead.biWidth); TFT_CS_CLR(); TFTWriteIndex(0x0022); TFT_RS_SET(); Offset24Bits = 0; // 偏移清零
while (1) { res = f_read(&file, MMCBuf, MMC_BUFF_SIZE, &br); if ((res != FR_OK) || (br < MMC_BUFF_SIZE))// 判断数据是否读完 { break; }
if (bmpHead.biBitCount == 16) { Bmp_Disp16Bits(MMCBuf); // 显示16位位图 }
if (bmpHead.biBitCount == 24) // 显示24位位图 { Bmp_Disp24Bits(MMCBuf); // 显示24位位图 } } TFT_CS_SET(); } } }
f_close(&file); // 关闭文件,必须和f_open函数成对出现 f_mount(0, 0); return FR_OK; }
|