基于STM32的BMP图片解码

基于STM32的BMP图片解码_第1张图片

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
*********************************************************************************************************
*/
#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
*
* 
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                           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)在调试过程中遇到最难的并不是协议上的,我遇到一个问题。

原图:
基于STM32的BMP图片解码_第2张图片

显示:

将读取的数据与原始图片的数据作对比,发现在某一行数据之后,数据均发生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!

你可能感兴趣的:(图像解码)