基本概念
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;
}