您的位置:首页 > 其它

基于STM32的BMP图片解码

2016-08-26 11:09 423 查看


1. 硬件描述

单片机:STM32F407VET6

TFT-LCD控制器:RA8875

SD卡:金士顿4GB

2. 第三方模块

文件系统:FATFS R0.11

3. BMP图片基础知识

BMP(全称Bitmap)是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大。BMP文件的图像深度可选lbit、4bit、8bit、24bit及32Bit。BMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。由于BMP文件格式是Windows环境中交换与图有关的数据的一种标准,因此在Windows环境中运行的图形图像软件都支持BMP图像格式。

4. 结构分析

BMP虽然是图片格式,但底层仍然是二进制文件。若要将二进制文件解析成图片,需要明确每一个二进制位代表什么含义!

#pragma pack (1)

/**
* 位图 = 文件头 + 信息头 + 调色板(可选) + 数据体
*/

/* 位图文件头 */
typedef struct tagBITMAP_FILE_HEADER{
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
}BITMAP_FILE_HEADER;

/* 位图信息头 */
typedef struct tagBITMAP_INFO_HEADER{
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
}BITMAP_INFO_HEADER;

typedef struct tagRGBQUAD{
uint8_t rgbBlue;
uint8_t rgbGreen;
uint8_t rgbRed;
uint8_t rgbReserved;
}RGBQUAD;

#pragma pack ()


1、文件头的结构定义了该图片的框架信息,占14个字节:

(1)bfType:指定文件类型,必须是 0x424D,即字符串”BM”,也就是说.bmp文件的关键字都是“BM”。

(2)bfSize:指定文件大小。

(3)bfOffBits:实际数据占文件头的偏移量。

2、信息头的结构定义了该图片的具体信息,占40个字节:

(1)biWidth:指定图像宽度,单位:像素

(2)biHeight:指定图像高度,单位:像素

(3)biBitCount:指定颜色位数,常用的值为1(灰度图),4(16色图),8(256色图),24(真彩色图),32(真彩色图,增加ALPHA通道)

3、调色板

对于8位图及以下才有调色板部分,对于24位、32位图像调色板位置直接就是数据部分。

调色板的目的,就是把一张图片所有用得到的颜色依次编号,每种颜色都有一个唯一的编号。在数据区域,只显示一种颜色在调色板区域的索引值。

以8位图为例,原来一个像素需要3个字节(RGB分量各占一个字节),现在只需要1个字节即可,大大节省了存储空间。

为什么对于24位图及以上,不使用调色板了呢?以24位图、240*320分辨率的图片为例:

调色板占空间:(2^24) * 4字节

图片占空间:240 * 320 * 3 字节

相比于不使用调色板,不但没有减小图像体积,反而多了一块巨大的调色板!

4、源码

bmp.h

/*
*********************************************************************************************************
* @file     bmp.h
* @author   SY
* @version  V1.0.0
* @date     2016-8-26 09:40:47
* @IDE      V5.18.0.0
* @Chip     STM32F407VE
* @brief    BMP图片头文件
*********************************************************************************************************
* @attention
*
*
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                           Define to prevent recursive inclusion
************************************
4000
*********************************************************************
*/
#ifndef __BMP_H
#define __BMP_H

/*
*********************************************************************************************************
*                                           Exported Includes
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                           Exported define
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                           Exported types
*********************************************************************************************************
*/
typedef enum
{
eErrTypeBMP_NONE = 0,
eErrTypeBMP_OPEN_FILE,
eErrTypeBMP_FILE_HEAD,
eErrTypeBMP_MALLOC,
eErrTypeBMP_NOT_SUPPORT,
}BMP_ERR_TypeDef;

/*
*********************************************************************************************************
*                                           Exported constants
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                           Exported macro
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                           Exported variables
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                           Exported functions
*********************************************************************************************************
*/
BMP_ERR_TypeDef LCD_ShowBMP( uint16_t x, uint16_t y, const char *bmpPathPtr );

#endif
/************************ (C) COPYRIGHT STMicroelectronics **********END OF FILE*************************/


bmp.c

/*
*********************************************************************************************************
* @file bmp.c
* @author SY
* @version V1.0.0
* @date 2016-8-26 09:40:47
* @IDE V5.18.0.0
* @Chip STM32F407VE
* @brief BMP图片源文件
*********************************************************************************************************
* @attention
*
*
*********************************************************************************
14d6b
************************
*/

/*
*********************************************************************************************************
* Private Includes
*********************************************************************************************************
*/
#include "bsp.h"
#include "bmp.h"

/*
*********************************************************************************************************
* Private define
*********************************************************************************************************
*/

/*
*********************************************************************************************************
* Private typedef
*********************************************************************************************************
*/
#pragma pack (1) /** * 位图 = 文件头 + 信息头 + 调色板(可选) + 数据体 */ /* 位图文件头 */ typedef struct tagBITMAP_FILE_HEADER{ uint16_t bfType; uint32_t bfSize; uint16_t bfReserved1; uint16_t bfReserved2; uint32_t bfOffBits; }BITMAP_FILE_HEADER; /* 位图信息头 */ typedef struct tagBITMAP_INFO_HEADER{ uint32_t biSize; uint32_t biWidth; uint32_t biHeight; uint16_t biPlanes; uint16_t biBitCount; uint32_t biCompression; uint32_t biSizeImage; uint32_t biXPelsPerMeter; uint32_t biYPelsPerMeter; uint32_t biClrUsed; uint32_t biClrImportant; }BITMAP_INFO_HEADER; typedef struct tagRGBQUAD{ uint8_t rgbBlue; uint8_t rgbGreen; uint8_t rgbRed; uint8_t rgbReserved; }RGBQUAD; #pragma pack ()

/*
*********************************************************************************************************
* Private constants
*********************************************************************************************************
*/

/*
*********************************************************************************************************
* Private macro
*********************************************************************************************************
*/

/*
*********************************************************************************************************
* Private variables
*********************************************************************************************************
*/
extern uint16_t file_buff[];

/*
*********************************************************************************************************
* Private function prototypes
*********************************************************************************************************
*/

/*
*********************************************************************************************************
* Private functions
*********************************************************************************************************
*/
/*
*********************************************************************************************************
* Function Name : LCD_ShowBMP
* Description : 显示BMP图片(支持8、24、32位格式)
* Input : None
* Output : None
* Return : None
*********************************************************************************************************
*/
BMP_ERR_TypeDef LCD_ShowBMP( uint16_t x, uint16_t y, const char *bmpPathPtr )
{
FRESULT fresult;
FIL file_obj;
uint32_t br;

/* 打开文件 */
fresult = f_open(&file_obj,bmpPathPtr,FA_OPEN_EXISTING | FA_READ);
if (fresult != FR_OK)
{
return eErrTypeBMP_OPEN_FILE;
}

/* 打开文件头 */
BITMAP_FILE_HEADER fileHead;
fresult = f_read(&file_obj,&fileHead,sizeof(BITMAP_FILE_HEADER),&br);
if (fresult != FR_OK)
{
f_close(&file_obj);
return eErrTypeBMP_OPEN_FILE;
}

/* 验证"BM" */
if (fileHead.bfType != 0x4D42)
{
f_close(&file_obj);
return eErrTypeBMP_FILE_HEAD;
}

/* 打开信息头 */
BITMAP_INFO_HEADER infoHead;
fresult = f_read(&file_obj,&infoHead,sizeof(BITMAP_INFO_HEADER),&br);
if (fresult != FR_OK)
{
f_close(&file_obj);
return eErrTypeBMP_OPEN_FILE;
}

/* 行必须是4的倍数 */
const uint32_t MULTIPLE = 4;
uint32_t originBiWidth = infoHead.biWidth;
uint8_t rowRemain = infoHead.biWidth % MULTIPLE;
if (rowRemain != 0)
{
uint32_t div = infoHead.biWidth / MULTIPLE;
infoHead.biWidth = (div + 1) * MULTIPLE;
}

uint32_t virtualWidth = 0;
switch ( infoHead.biBitCount )
{
case 8:
virtualWidth = infoHead.biWidth;
break;
case 24:
virtualWidth = 3 * infoHead.biWidth;
break;
case 32:
virtualWidth = 4 * infoHead.biWidth;
break;
default:
f_close(&file_obj);

return eErrTypeBMP_NOT_SUPPORT;
}

/* 行、列合法性 */
if ((infoHead.biHeight==0) || (infoHead.biWidth==0))
{
f_close(&file_obj);
return eErrTypeBMP_NONE;
}

/* 是否存在调色板 */
bool isIncludePalette = true;
if (fileHead.bfOffBits == sizeof(BITMAP_FILE_HEADER) + sizeof(BITMAP_INFO_HEADER))
{
isIncludePalette = false;
}

/* 为调色板申请内存 */
RGBQUAD *palettePtr = NULL;
uint32_t paletteNums = 0;
if (isIncludePalette == true)
{
paletteNums = fileHead.bfOffBits - \
sizeof(BITMAP_FILE_HEADER) - sizeof(BITMAP_INFO_HEADER);

palettePtr = (RGBQUAD *)calloc(1,paletteNums);
if (palettePtr == NULL)
{
f_close(&file_obj);
return eErrTypeBMP_MALLOC;
}
}

/* 读取调色板数据 */
if (isIncludePalette == true)
{
fresult = f_read(&file_obj,palettePtr,paletteNums,&br);
if (fresult != FR_OK)
{
f_close(&file_obj);
if (isIncludePalette == true)
{
free(palettePtr);
}
return eErrTypeBMP_OPEN_FILE;
}
}

/*
* 显示图像数据
* 注:
* 1、BMP文件数据顺序为: 从左到右,从下向上。而液晶屏加载显示顺序为:从左到右,从上向下。需要重新定位!
* 2、无论是否含有调色板,数据区的起始地址(fileHead.bfOffBits)都不是4的倍数。
* 3、由于底层使用DMA读取数据,必须保证 f_read() 函数起始地址是4的倍数。
*/
const uint8_t OPPSET_POS = 2;
const uint16_t BMP_BUFF_SIZE = 4000;

uint8_t *bmpBuffPtr = (uint8_t *)&file_buff;
uint32_t maxBuffSize = BMP_BUFF_SIZE - OPPSET_POS; //由于后面会多读取 OPPSET_POS 个字节,因此需要留有余量
uint32_t maxReadLenth = maxBuffSize;
if (maxBuffSize % virtualWidth != 0)
{
uint32_t div = maxBuffSize / virtualWidth;
maxReadLenth = div * virtualWidth;
}
uint32_t maxColumnNums = maxReadLenth / virtualWidth;
uint32_t remainColumnNums = infoHead.biHeight;
uint16_t start_x = x;
uint16_t start_y = y + infoHead.biHeight;

/* 从该地址开始读取数据,保证读取的起始地址是4的倍数 */
uint32_t offset = fileHead.bfOffBits - OPPSET_POS;
while (1)
{
f_lseek(&file_obj,offset);
fresult = f_read(&file_obj,bmpBuffPtr,maxReadLenth+OPPSET_POS,&br);
br -= OPPSET_POS;
offset += br;

if (fresult != FR_OK)
{
f_close(&file_obj);
if (isIncludePalette == true)
{
free(palettePtr);
}
return eErrTypeBMP_OPEN_FILE;
}

if (br == 0)
{
break;
}

memmove(bmpBuffPtr,bmpBuffPtr+OPPSET_POS,maxReadLenth);

/* 设置像素窗口 */
if (remainColumnNums >= maxColumnNums)
{
remainColumnNums -= maxColumnNums;
start_x = x;
start_y -= maxColumnNums;
}
else
{
remainColumnNums = 0;
start_x = x;
start_y = y;
}

Active_Window(start_x,start_x+originBiWidth-1,start_y,start_y+maxColumnNums-1);
XY_Coordinate(start_x,start_y);
WriteCommand(0x02);

uint16_t pixel = 0;
RGBQUAD tempQuad;
switch (infoHead.biBitCount)
{
case 8:
{
uint32_t increase = 0;
uint32_t initValue = br - virtualWidth;

for (int32_t i=initValue; i>=0; ++i)
{
tempQuad = *(RGBQUAD *)(palettePtr + *(uint8_t *)(bmpBuffPtr + i));
pixel = RGB(tempQuad.rgbRed, tempQuad.rgbGreen, tempQuad.rgbBlue);
*(__IO uint16_t *)(LCD_DATA_ADD) = pixel;

if (++increase >= originBiWidth)
{
increase = 0;

initValue -= virtualWidth;
i = initValue - 1;
}
}
break;
}
case 24:
{
uint32_t increase = 0;
uint32_t initValue = br - virtualWidth;

for (int32_t i=initValue; i>=0; i+=3)
{
tempQuad.rgbBlue = *(uint8_t *)(bmpBuffPtr+i+0);
tempQuad.rgbGreen = *(uint8_t *)(bmpBuffPtr+i+1);
tempQuad.rgbRed = *(uint8_t *)(bmpBuffPtr+i+2);
tempQuad.rgbReserved = 0x00;

pixel = RGB(tempQuad.rgbRed, tempQuad.rgbGreen, tempQuad.rgbBlue);
*(__IO uint16_t *)(LCD_DATA_ADD) = pixel;

if (++increase >= originBiWidth)
{
increase = 0;

initValue -= virtualWidth;
i = initValue - 3;
}
}
break;
}
case 32:
{
uint32_t increase = 0;
uint32_t initValue = br - virtualWidth;

for (int32_t i=initValue; i>=0; i+=4)
{
tempQuad.rgbBlue = *(uint8_t *)(bmpBuffPtr+i+0);
tempQuad.rgbGreen = *(uint8_t *)(bmpBuffPtr+i+1);
tempQuad.rgbRed = *(uint8_t *)(bmpBuffPtr+i+2);
tempQuad.rgbReserved = *(uint8_t *)(bmpBuffPtr+i+3);

pixel = RGB(tempQuad.rgbRed, tempQuad.rgbGreen, tempQuad.rgbBlue);
*(__IO uint16_t *)(LCD_DATA_ADD) = pixel;

if (++increase >= originBiWidth)
{
increase = 0;

initValue -= virtualWidth;
i = initValue - 4;
}
}
break;
}
default:
{
break;
}
}
}

/* 关闭文件 */
f_close(&file_obj);
if (isIncludePalette == true)
{
free(palettePtr);
}

return eErrTypeBMP_NONE;
}

/************************ (C) COPYRIGHT STMicroelectronics **********END OF FILE*************************/


要点:

(1)BMP的文件顺序为:从左到右,从下到上。而液晶屏显示顺序为:从左到右,从上到下。因此,代码中需要调换BMP图像显示顺序。

(2)由于内存中BMP缓存只有4000字节,对于较大尺寸的图片,需要分段加载。

(3)BMP协议规定,行长度必须为4的倍数,如果不足,则填充0!

(4)在调试过程中遇到最难的并不是协议上的,我遇到一个问题。

原图:



显示:



将读取的数据与原始图片的数据作对比,发现在某一行数据之后,数据均发生2个字节的偏移!这是分段加载的结果,如果我将图片缓存区加大,一次性将数据全部读取出来,图片显示正常,因此我推测可能是文件系统的问题。

顿时灵光一现!特么我不是遇到过类似问题吗!

将数据写入SD卡正确,从SD卡读取到内存发现数据发生偏移,后来发现是因为SD卡读写数据是使用DMA传输数据,DMA要求内存起始地址必须4字节对齐!

计算数据起始地址,文件头(14) + 信息头(40) + 调色板(8位数据,1024) + 数据体(若干),14 + 40 + 1024 = 1078,

1078 / 4 = 269.5,不能被4整除,也就是说可能问题就是出在这里,这样使用f_read()读取的数据,第一次读取是正确的,在之后的某个地方只要一读取就偏移2个字节。结论:使用 f_read() 必须保证4字节对齐!

因此,每次分段读取数据时,使用 f_lseek() 向前偏移2个字节,保证4字节对齐,这样显示就OK啦!

看一下显示效果,Perfect!



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  STM32 BMP 图像解码