首页 > 技术文章 > 用LCD显示BMP(位图)

ding-ding-light 2020-12-21 23:37 原文

基本概念

 BMP是英文Bitmap的缩写,由称作像素(图片元素)的单个点组成的,每个像素点由三个字节(用char型定义)组成,按照蓝绿红排列。这些点可以进行不同的排列和染色以构成图样。如下图所示,当读取图片信息时,文件指针由左下角开始增长。如下图所示,BMP图片包含了14个字节的文件头信息,和40和字节的BMP图片信息,读取BMP数据的时候注意主要跳过。

 下图为LCD显示屏,它的数据从上角开始增长的,而且是由四个字节(用int型定义)组成一个像素点,而且原色排列也与BMP排序不同,编程时注意。

 下图为BMP转换为LCD显示的过程。

使用LCD显示位图

 该程序的文件结构如下:

 需要注意的是,BMP图片大小应该与LCD分辨率一致,不然将会错位。
main.c

#include "main.h"

int main(int argc, char const *argv[])
{
    lcd_info  lcd; 

    BITMAPFILEHEADER file_head; //存放文件头的结构体
    BITMAPINFOHEADER bmp_info;  //存放bmp数据的结构体

    int bmp_size;
    int x;
    int y;
    int color;

    char bmp_buf[LCD_W*LCD_H*3]; //存放bmp数据

    lcd = init_lcd(LCD_PATH);
    
    int fd_bmp = init_bmp(BMP_PATH);

    file_head =  read_file_head(fd_bmp, file_head);   //获得文件头,并且移动文件指针
    bmp_info  =  read_file_bmp_info(fd_bmp, bmp_info); //获得bmp图片信息,并且将文件指针移动到了bmp图片的数据部分

    int ret = read(fd_bmp, &bmp_buf, LCD_W*LCD_H*3); //获得文件数据,并且将用char类型来存放

    if (-1 == ret)
    {
        printf("read bmp_data msg: %s\n", strerror(errno));
    }
    printf("read bmp_data %d\n", ret);

    for (y = 0; y < LCD_H; y++)
    {
        for ( x = 0; x < LCD_W; x++)
        {
         /* 将BMP的3个元素组成一个能在LCD上正确显示的像素 */
         color = bmp_buf[(x+y*800)*3+0] << 0 |            
                 bmp_buf[(x+y*800)*3+1] << 8 |
                 bmp_buf[(x+y*800)*3+2] << 16;
         draw_point(lcd.p_lcd, color, x, LCD_H - 1 - y); //每获得一个像素就根据坐标去打印它,注意纵轴方向需要倒着打印,因为LCD与位图的显示与存放的方式不同
        }
    }
    return 0;
}

lcd.c

#include "lcd.h"
#include "main.h"

lcd_info init_lcd(const char *path)
{
    lcd_info lcd = {
        .fd_lcd = -1,
        .p_lcd  = NULL
    };
   
     lcd.fd_lcd = open(LCD_PATH , O_RDWR);
    if (-1 ==  lcd.fd_lcd)
    {
        printf("open  lcd.fd_lcd msg: %s\n", strerror(errno));
        return lcd;
    }
    lcd.p_lcd = mmap(NULL, LCD_SIZE,  PROT_WRITE | PROT_READ , MAP_SHARED,  lcd.fd_lcd , 0);
    if (MAP_FAILED == lcd.p_lcd)
    {
        printf("mmap msg: %s\n", strerror(errno));
        return lcd;
    }

    return lcd;
}
/* 画点函数
 **/
bool draw_point(int *address, int color, int x, int y)
{
    if (NULL == address)
    {
        printf("draw_point msg:%s\n", strerror(errno));  
        return false;
    }

    *(address + (x + (y*800))) = color; 

    return true;
}

bmp.c

#include "bmp.h"

/* 初始化图片获得图片句柄
 **/
int init_bmp(const char *bmp_path)
{
    int fd_bmp = open(bmp_path, O_RDONLY);
    if (-1 == fd_bmp)
    {
        printf("open fd_bmp msg: %s\n", strerror(errno));
        return -1;
    }
    return fd_bmp;
}

BITMAPFILEHEADER read_file_head(int fd_bmp, BITMAPFILEHEADER file_head)
{
    int ret = read(fd_bmp, &file_head, sizeof(BITMAPFILEHEADER));
    if (-1 == ret)
    {
        printf("read file_head msg: %s\n", strerror(errno));
        return file_head;
    }
    printf("read file_head %d\n", ret);

    return file_head;
}

BITMAPINFOHEADER read_file_bmp_info(int fd_bmp, BITMAPINFOHEADER bmp_info)
{
    int ret = read(fd_bmp, &bmp_info, sizeof(BITMAPINFOHEADER));
    if (-1 == ret)
    {
        printf("read bmp_info msg: %s\n", strerror(errno));
        return bmp_info;
    }
    printf("read bmp_info %d\n", ret);

    return bmp_info;
}

main.h

#define __MAIN__H__

#include <stdlib.h>

#include "lcd.h"
#include "bmp.h"

#define     LCD_PATH    "/dev/fb0"
#define     BMP_PATH    "./1.bmp" 

#define     LCD_W       800
#define     LCD_H       480

#define     LCD_SIZE    LCD_W*LCD_H*4

#endif

lcd.h

#ifndef __LCD__H__
#define __LCD__H__

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <sys/mman.h>

typedef struct Lcd_Info{

    int  fd_lcd;
    int* p_lcd;
}lcd_info;

lcd_info init_lcd(const char *path);

bool draw_point(int *address, int color, int x, int y);

#endif

bmp.h

#ifndef __BMP__H__
#define __BMP__H__

#include "lcd.h"
#include "main.h"

typedef    short            WORD;
typedef    int              DWORD;
typedef    long             LONG;

typedef struct tagBITMAPFILEHEADER
{
    WORD bfType;//位图文件的类型,必须为BM(1-2字节)
    DWORD bfSize;//位图文件的大小,以字节为单位(3-6字节,低位在前)
    WORD bfReserved1;//位图文件保留字,必须为0(7-8字节)
    WORD bfReserved2;//位图文件保留字,必须为0(9-10字节)
    DWORD bfOffBits;//位图数据的起始位置,以相对于位图(11-14字节,低位在前)
    //文件头的偏移量表示,以字节为单位
}__attribute__((packed)) BITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER{
    DWORD biSize;//本结构所占用字节数(15-18字节)
    LONG biWidth;//位图的宽度,以像素为单位(19-22字节)
    LONG biHeight;//位图的高度,以像素为单位(23-26字节)
    WORD biPlanes;//目标设备的级别,必须为1(27-28字节)
    WORD biBitCount;//每个像素所需的位数,必须是1(双色),(29-30字节)
    //4(16色),8(256色)16(高彩色)或24(真彩色)之一
    DWORD biCompression;//位图压缩类型,必须是0(不压缩),(31-34字节)
    //1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一
    DWORD biSizeImage;//位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位(35-38字节)
    LONG biXPelsPerMeter;//位图水平分辨率,每米像素数(39-42字节)
    LONG biYPelsPerMeter;//位图垂直分辨率,每米像素数(43-46字节)
    DWORD biClrUsed;//位图实际使用的颜色表中的颜色数(47-50字节)
    DWORD biClrImportant;//位图显示过程中重要的颜色数(51-54字节)
}__attribute__((packed)) BITMAPINFOHEADER;

int init_bmp(const char *bmp_path);

BITMAPFILEHEADER read_file_head(int fd_bmp, BITMAPFILEHEADER file_head);

BITMAPINFOHEADER read_file_bmp_info(int fd_bmp, BITMAPINFOHEADER bmp_info);

#endif

Makefile

CC=arm-linux-gcc
TAG=./bin/main
SRC=$(wildcard ./src/*.c)
objs = ./src/main.o ./src/lcd.o ./src/bmp.o
override CONFIG += -I./inc 

$(TAG):$(SRC)
	$(CC) $^ -o $@ $(CONFIG) 
$(SRC):$(OBJ)
	$(CC) $^ -o $@ -c $(objs)

使用LCD显示小图

 上个程序只能够显示固定大小的图片,因为LCD是顺序存放的,当图片大小和分辨不一样将会错位,如当LCD的一行像素足够存放BMP像素数据的两行,LCD便会将需要分两行显示的数据,显示成一行,从而导致了数据错位。这里只需要更改主程序,其他程序是一样的。
main.c

#include "main.h"
 

int main(int argc, char const *argv[])
{
    lcd_info  lcd; 

    BITMAPFILEHEADER file_head;
    BITMAPINFOHEADER bmp_info;

    int bmp_size;
    int x;
    int y;
    int color; 

    lcd = init_lcd(LCD_PATH);
    
    int fd_bmp = init_bmp(BMP_PATH);

    file_head =  read_file_head(fd_bmp, file_head);   
    bmp_info  =  read_file_bmp_info(fd_bmp, bmp_info);

    int bmp_w = bmp_info.biWidth; //获得图片宽度,循环时用到
    int bmp_h = bmp_info.biHeight;//获得图片高度,循环时用到

    char bmp_buf[LCD_W*LCD_H*3];
    int ret = read(fd_bmp, &bmp_buf, LCD_W*LCD_H*3);

    if (-1 == ret)
    {
        printf("read bmp_data msg: %s\n", strerror(errno));
    }
    printf("read bmp_data %d\n", ret);

            // 输出文件的信息
    printf("type:%x\tsize:%d\toffset:%d\n" , file_head.bfType , file_head.bfSize,file_head.bfOffBits );
    printf("biWidth:%ld\tbiHeight:%ld\tbiBitCount:%d\tbiSizeImage:%d\n",
            bmp_info.biWidth,
            bmp_info.biHeight,
            bmp_info.biBitCount,
            bmp_info.biSizeImage);

    int tmp_y = 0;

    //bmp每行像素的所占字节数需要被4整除,但不满足这个条件时需要补充字节
    int swallow = 0 ; //定义需要补充的字节数变量
    if ((swallow = ((bmp_w*3)%4)) != 0 ) //bmp_w*3求出bmp每行所占的字节数,再取余,得到余数
    {
        swallow = 4 - swallow; //向上补充
        printf("需要补充%d个空字节!!\n" , swallow );
    }
    else 
    {
        printf("不需要补充空字节!!\n"  );
        swallow = 0 ;
    }
           
    for (y = 0; y < bmp_h; y++)
    {
        for ( x = 0; x < bmp_w; x++)
        {
            /*  y*swallow:表示y每增加1需要跳过的字节数,因为是补充字节是没有数据的,而且不跳过会导致LCD显示错位 */
            color = bmp_buf[(x + y * bmp_w ) * 3 + 0 + y * swallow] << 0 |
                    bmp_buf[(x + y * bmp_w ) * 3 + 1 + y * swallow] << 8 |
                    bmp_buf[(x + y * bmp_w ) * 3 + 2 + y * swallow] << 16;

            
            tmp_y = (y * bmp_w + y * (LCD_W-bmp_w)) / LCD_W;  //这里是将BMP的纵轴坐标转换LCD纵轴坐标。横坐标不需要管,因为不会错位。

            draw_point(lcd.p_lcd, color, x, bmp_h - 1 - tmp_y);
        
        }
    }
    return 0;
}

推荐阅读