用LCD顯示BMP(位圖)


基本概念

 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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM