本文主要探讨基于s5pv210实现图片解码显示项目。
项目概述:
硬件:s5pv210(已移植uboot,kernel,busybox,已搭建tftp,已挂在ntfs)
软件:ubuntu14(已搭建tftp,搭建ntfs)
项目功能:基于s5pv210实现对bmp,jpeg,png三种图片的解析和显示,并且支持点击触摸屏两侧实现图片翻页
项目文件架构:(文件具体功能可阅读RADEME(后面))
项目编译:
项目执行调试信息:
项目部分图片:
start.bmp:为起始页面信息,包含触摸翻页的触摸区域显示
项目图片依赖库移植:
jpeg库移植
wget https://sourceforge.net/projects/libjpeg/files/libjpeg/6b/jpegsrc.v6b.tar.gz --no-check-certificate
mkdir jpeg
tar -zxvf jpegsrc.v6b.tar.gz -C jpeg
apt-get install -y libtool
cd jpeg/jpeg-6b
cp /usr/share/libtool/config/config.guess .
cp /usr/share/libtool/config/config.sub .
mkdir -p /opt/lib_fb/include/
mkdir -p /opt/lib_fb/lib/
./configure --prefix=/opt/lib_fb --exec-prefix=/opt/lib_fb --enable-shared --enable-static -build=i386 -host=arm
vim Makefile
CC = arm-linux-gcc
AR = arm-linux-ar rc
AR2= arm-linux-ranlib
make && make install-lib
zlib移植
wget wget http://www.zlib.net/zlib-1.3.1.tar.gz
tar -zxvf zlib-1.3.1.tar.gz
cd zlib-1.3.1
export CC=arm-linux-gcc
./configure -shared --prefix=/opt/lib_fb
make && make install
png库移植
wget https://download.sourceforge.net/libpng/libpng-1.6.6.tar.gz --no-check-certificate
cd libpng-1.6.6
export LDFLAGS="-L/opt/lib_fb/lib"
export CFLAGS="-I/opt/lib_fb/include"
export CPPFLAGS="-I/opt/lib_fb/include"
./configure --host=arm-linux --enable-shared --enable-static --prefix=/opt/lib_fb
make && make install
cp /opt/lib_fb/lib/ /root/rootfs/usr/lib -rdf
cp /opt/lib_fb/include/ /root/rootfs/usr/include -rdf
项目代码:
README
概述:
本程序为图片解析显示程序,支持点击屏幕图片翻页功能,该程序可对bmp,jpeg,png三种图片解析显示。
程序包含image文件,include文件,display文件等其他文件。
Makefile
Makefile:顶层Makefile,顶层Makefile.build,子Makefile
子Makefile:
obj-y += file.o file.c编进程序
obj-y += subdir/ subdir子目录下文件编进程序,"/"不可省略
顶层Makefile:
obj-y编进程序的文件、子目录
定义工具链、编译参数、链接参数,用export导出变量
顶层Makefile.build:
把目录及子目录要编进程序的文件编译并打包为built-in.o
Makefile使用:
修改顶层Makefile工具链,编译选项、链接选项,编译文件及子目录(obj-y),TARGET为编译的程序名
子Makefile:编译文件及子目录(obj-y)
make 编译
make clean 清除
make distclean 彻底清除
include
config.h 硬件参数,程序全局变量等
init_list.h 链表初始化函数
fb_bmp.h bmp图片函数
fb.h framebuffer函数
fb_jpeg.h jpeg图片函数
fb_png.h png图片函数
list.h 链表函数
display
fb_bmp.c bmp图片程序
fb_jpeg.c jpeg图片程序
fb_png.c png图片程序
fb.c framebuffer程序
Makefile 编译子文件规则
image
bmp bmp图片
jpg jpeg图片
png png图片
start 起始页图片
list
init_list.c 链表初始化程序
list.c 链表程序
Makefile 编译子文件规则
main.c 主程序
run.sh 程序执行脚本
run.sh
#!/bin/bash
./main image
Makefile
CROSS_COMPILE = arm-linux-
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP
CFLAGS := -Wall -O2 -g -DDEBUG
CFLAGS += -I $(shell pwd)/include -I /opt/lib_fb/include/
LDFLAGS := -ljpeg -lpng -lz -L /opt/lib_fb/lib/
export CFLAGS LDFLAGS
TOPDIR := $(shell pwd)
export TOPDIR
TARGET := main
obj-y += main.o
obj-y += display/
obj-y += list/
all :
make -C ./ -f $(TOPDIR)/Makefile.build
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
Makefile.build
PHONY := __build
__build:
obj-y :=
subdir-y :=
include Makefile
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y)) : c/ d/
# __subdir-y : c d
# subdir-y : c d
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
# c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)
include $(dep_files)
endif
PHONY += $(subdir-y)
__build : $(subdir-y) built-in.o
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
dep_file = .$@.d
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY : $(PHONY)
main.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/input.h>
#include <string.h>
#include <unistd.h>
#include "list.h"
#include "init_list.h"
#include "fb_bmp.h"
#include "fb_jpeg.h"
#include "fb_png.h"
#include "fb.h"
int main(int argc,char *argv[])
{
int fd = -1, ret = -1;
struct input_event ev;
pic *tmp;
//打开触摸屏设备文件
fd = open(X210_TOUCHSCREEN, O_RDONLY);
if (fd < 0)
{
perror("open");
return -1;
}
//创建图片链表头节点
head = create_node(start_page);
tmp = head;
//遍历image,填充图片节点信息
insert_pic_link(argv[1]);
init_pic_link(head);
//初始化framebuffer
init_fb();
//显示起始页
display_bmp(start_page);
sleep(5);
//监听触摸事件,并显示对应图片
while (1)
{
//监听事件
memset(&ev, 0, sizeof(struct input_event));
ret = read(fd, &ev, sizeof(struct input_event));
if (ret != sizeof(struct input_event))
{
perror("read");
close(fd);
return -1;
}
//判断监听结果(按下屏幕位置)
if ((ev.type == EV_ABS) && (ev.code == ABS_X))
{
if ((ev.value >= 0) && (ev.value < 200))
{
//显示上一页图片,打印图片路径
tmp = prev_printf_link(head,tmp);
debug("%s\n",tmp->pathname);
(*tmp->display)(tmp->pathname);
}
else if ((ev.value > 800) && (ev.value <= 1024))
{
//显示上一页图片,打印图片路径
tmp = next_printf_link(head,tmp);
debug("%s\n",tmp->pathname);
(*tmp->display)(tmp->pathname);
}
else
{
debug("posion : %d\n",ev.value);
}
}
}
close(fd);
return 0;
}
include:
config.h
#ifndef __CONFIG_H__
#define __CONFIG_H__
//图片类型
enum pic_type
{
BMP,
JPEG,
PNG,
UNKNOW
};
//图片信息
typedef struct pic_info
{
char pathname[256]; //图片路径
enum pic_type type; //图片类型
int (*display)(char *pathname); //图片显示
struct pic_info *next;
struct pic_info *prev;
} pic;
//图片链表头
pic *head;
//像素变量
unsigned int *pfb;
//起始页
#define start_page "image/start/start.bmp"
//图片存储
#define RGB_MAX_RESOLUTION (1920*1080)
#define RGB_BUF_SIZE (RGB_MAX_RESOLUTION*3)
unsigned char rgb_buf[RGB_BUF_SIZE];
//显示器宽高
#define HIGH 600
#define WIDTH 1024
//framebuffer 设备
#define FB_DEV "/dev/fb0"
//触摸屏设备
#define X210_TOUCHSCREEN "/dev/input/event1"
//颜色参数
#define WHITE 0xFFFFFFFF
#define BLACK 0x0
#ifdef DEBUG
#define debug(...) \
{ \
fprintf(stderr, "[debug][%s:%s:%d] ", \
__FILE__, __FUNCTION__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
}
#else
#define debug(...)
#endif
#endif
fb.h
//初始化framebuffer
int init_fb();
//初始化背景
int init_background();
fb_bmp.h
//bmp 头信息
typedef struct {
unsigned char bmp_type_B; // BMP类型标志(必须为"BM")
unsigned char bmp_type_M;
unsigned int bmp_Size; // BMP文件大小
unsigned short bmp_reserved1; // 保留字段1
unsigned short bmp_reserved2; // 保留字段2
unsigned int bmp_off_bits; // 从文件头到像素数据起始位置的偏移量
}__attribute__((packed)) bmp_head ;
//bmp 所有信息
typedef struct {
unsigned int bmp_size; // 结构体长度
signed int bmp_width; // 图片宽度
signed int bmp_height; // 图片高度
unsigned short bmp_lanes; // 目标设备的平面数(通常为1)
unsigned short bmp_bit_count; // 每个像素所需的位数
unsigned int bmp_compression;// 压缩类型(0表示不压缩)
unsigned int bmp_size_image; // 图像大小(对于未压缩的RGB格式,该值应为0)
signed int bmp_x_pels_per_meter;// 水平分辨率(像素/米)
signed int bmp_y_pels_per_meter;// 垂直分辨率(像素/米)
unsigned int bmp_clor_used; // 调色板中使用的颜色索引数(若为0则使用全部颜色)
unsigned int bmp_color_important;// 重要颜色索引数(若为0则都被认为是重要的)
} bmp_info;
//判断是否为bmp,0 : 是, 1 : 不是
int is_bmp(char *pathname);
//bmp显示
int display_bmp(char *pathname);
fb_jpeg.h
//判断是否为jpeg,0 : 是, 1 : 不是
int is_jpeg(char *pathname);
//显示jpeg
int display_jpeg(char *pathname);
fb_png.h
//判断是否为png,0 : 是, 1 : 不是
int is_png(char *pathname);
//显示png
int display_png(char *pathname);
list.h
#include "config.h"
//创建节点
pic * create_node(const char *path_name);
//插入尾节点
void insert_tail(pic *phead,pic *new);
//返回当前节点的下一个节点
pic *next_printf_link(pic* head,pic *p);
//返回当前节点的上一个节点
pic *prev_printf_link(pic* head,pic *p);
init_list.h
//遍历image中的图片,创建图片节点,并且填充图片路径
int insert_pic_link(char *pathname);
//初始化节点中图片类型和图片显示函数
void init_pic_link(pic *head);
display:
Makefile
obj-y += fb.o
obj-y += fb_bmp.o
obj-y += fb_jpeg.o
obj-y += fb_png.o
fb_bmp.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "config.h"
#include "fb_bmp.h"
int is_bmp(char *pathname)
{
int fd;
bmp_head head_info;
fd = open(pathname,O_RDWR);
if(fd == -1)
{
perror("open");
return 1;
}
read(fd,&head_info,sizeof(head_info));
if(head_info.bmp_type_B != 'B' || head_info.bmp_type_M != 'M')
{
close(fd);
return 1;
}
close(fd);
return 0;
}
int display_bmp(char *pathname)
{
int fd = -1;
int v_len = 0;
int rgb_offset = 0;
int cnt = 0;
unsigned int i,j;
unsigned int *tmp = pfb;
bmp_info all_info;
bmp_head head_info;
fd = open(pathname,O_RDWR);
if(fd == -1)
{
perror("open");
return 1;
}
debug("open %s success\n",pathname);
read(fd,&head_info,sizeof(head_info));
read(fd,&all_info,sizeof(all_info));
debug("width : %d high : %d bpp : %d\n",all_info.bmp_width,all_info.bmp_height,all_info.bmp_bit_count);
v_len = all_info.bmp_width * all_info.bmp_height * all_info.bmp_bit_count / 3;
lseek(fd,head_info.bmp_off_bits,SEEK_SET);
memset(rgb_buf,0,RGB_BUF_SIZE);
read(fd,rgb_buf,v_len);
if(all_info.bmp_bit_count != 32 && all_info.bmp_bit_count != 24)
{
printf("%d bpp is not support\n",all_info.bmp_bit_count);
close(fd);
return -1;
}
for(i = 0;i < all_info.bmp_height;i++)
{
for(j = 0;j < all_info.bmp_width;j++)
{
cnt = WIDTH * i + (1024 - j);
*(tmp + cnt) = ((rgb_buf[rgb_offset + 0] << 0) | (rgb_buf[rgb_offset + 1] << 8) | (rgb_buf[rgb_offset + 2] << 16));
rgb_offset += 3;
}
}
return 0;
}
fb_jpeg.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include "config.h"
#include <jpeglib.h>
#include <jerror.h>
struct my_error_mgr
{
struct jpeg_error_mgr pub; /* "public" fields */
};
typedef struct my_error_mgr * my_error_ptr;
METHODDEF(void)my_error_exit (j_common_ptr cinfo)
{
fprintf(stderr, "my_error_exit\n");
}
int is_jpeg(char *pathname)
{
FILE * infile;
char head_tmp[2] = {0};
if ((infile = fopen(pathname, "rb")) == NULL)
{
fprintf(stderr, "can't open %s\n", pathname);
return 1;
}
fread(head_tmp,2,1,infile);
if(!((head_tmp[0] == 0xff) && (head_tmp[1] == 0xd8)))
{
fclose(infile);
return 1;
}
return 0;
}
int display_jpeg(char *pathname)
{
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
FILE * infile;
int row_stride;
unsigned char *buffer = NULL;
unsigned int i,j;
int rgb_offset = 0;
int cnt = 0;
unsigned int *tmp = pfb;
if ((infile = fopen(pathname, "rb")) == NULL)
{
fprintf(stderr, "can't open %s\n", pathname);
return 1;
}
debug("open %s sucess\n",pathname);
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
(void) jpeg_read_header(&cinfo, TRUE);
(void) jpeg_start_decompress(&cinfo);
debug("width : %d height : %d bpp : %d\n",cinfo.output_width, cinfo.output_height, 8 * cinfo.output_components);
if((cinfo.output_components * 8) != 24 && (cinfo.output_components * 8) != 32)
{
printf("%d bpp is not support\n",cinfo.output_components * 8);
fclose(infile);
return -1;
}
row_stride = cinfo.output_width * cinfo.output_components;
buffer = (unsigned char *)malloc(row_stride);
if(buffer == NULL)
{
printf("cinfo buffer malloc fail\n");
fclose(infile);
return -1;
}
memset(rgb_buf,0,RGB_BUF_SIZE);
while (cinfo.output_scanline < cinfo.output_height)
{
(void) jpeg_read_scanlines(&cinfo, &buffer, 1);
memcpy(rgb_buf + (cinfo.output_scanline-1) * row_stride, buffer, row_stride);
}
rgb_offset = cinfo.output_height * cinfo.output_width * 3 - 3;
for(i = 0;i < cinfo.output_height;i++)
{
for(j = 0;j < cinfo.output_width;j++)
{
cnt = WIDTH * i + j;
*(tmp + cnt) = ((rgb_buf[rgb_offset + 0] << 0) | (rgb_buf[rgb_offset + 1] << 8) | (rgb_buf[rgb_offset + 2] << 16));
rgb_offset -= 3;
}
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return 0;
}
fb_png.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <png.h>
#include <pngstruct.h>
#include <pnginfo.h>
#include "config.h"
#define PNG_BYTES_TO_CHECK 8
int is_png(char *pathname)
{
FILE *fp = NULL;
char buf[PNG_BYTES_TO_CHECK] = {0};
if ((fp = fopen(pathname, "rb")) == NULL)
{
printf("open %s fail\n",pathname);
return 1;
}
if (fread(buf, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK)
{
printf("read 2 bit error\n");
fclose(fp);
return 1;
}
if (!strncmp(buf+1, "PNG", 3))
{
return 0;
}
else
{
return 1;
}
}
int display_png(char *pathname)
{
FILE *fp = NULL;
png_structp png_ptr;
png_infop info_ptr;
png_bytep* row_pointers;
unsigned int i,j;
int cnt = 0;
int pos = 0;
int rgb_offset = 0;
unsigned int *tmp = pfb;
if ((fp = fopen(pathname, "rb")) == NULL)
{
printf("open %s fail\n",pathname);
return -1;
}
debug("open %s sucess\n",pathname);
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (png_ptr == 0)
{
printf("png_create_read_struct fail\n");
fclose(fp);
return -1;
}
info_ptr = png_create_info_struct(png_ptr);
if (info_ptr == 0)
{
printf("png_create_info_struct fail \n");
fclose(fp);
png_destroy_read_struct(&png_ptr, 0, 0);
return -1;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
printf("png_jmpbuf fail \n");
fclose(fp);
return -1;
}
png_init_io(png_ptr, fp);
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA, 0);
debug("width : %d height : %d bpp : %d\n",info_ptr->width,info_ptr->height,info_ptr->pixel_depth);
if(info_ptr->pixel_depth != 24 && info_ptr->pixel_depth != 32)
{
printf("%d bpp is not support\n",info_ptr->pixel_depth);
fclose(fp);
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
png_destroy_read_struct(&png_ptr, 0, 0);
return -1;
}
row_pointers = png_get_rows(png_ptr,info_ptr);
memset(rgb_buf,0,RGB_BUF_SIZE);
if(info_ptr->color_type == PNG_COLOR_TYPE_RGB)
{
for(j=0; j< info_ptr->width * 3; j+=3)
{
for(i=0; i< info_ptr->height ; i++)
{
rgb_buf[pos++] = row_pointers[i][j+0];
rgb_buf[pos++] = row_pointers[i][j+1];
rgb_buf[pos++] = row_pointers[i][j+2];
}
}
}
for(j = 0;j < info_ptr->width;j++)
{
for(i = 0;i < info_ptr->height;i++)
{
cnt = WIDTH * (600 - i) + ( 1024 - j);
*(tmp + cnt) = ((rgb_buf[rgb_offset + 2] << 0) | (rgb_buf[rgb_offset + 1] << 8) | (rgb_buf[rgb_offset + 0] << 16));
rgb_offset += 3;
}
}
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
return 0;
}
fb.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include "config.h"
int init_background()
{
unsigned int *tmp = pfb;
unsigned int i, j;
for(i = 0; i < HIGH;i++)
{
for(j = 0;j < WIDTH;j++)
{
*(tmp + WIDTH *i + j) = BLACK;
}
}
return 0;
}
int init_fb()
{
int fb = -1;
struct fb_var_screeninfo var_info = {0};
int p_len;
int ret = -1;
fb = open(FB_DEV,O_RDWR);
if(fb == -1)
{
perror("open");
return -1;
}
ret = ioctl(fb, FBIOGET_VSCREENINFO, &var_info);
if (ret < 0)
{
perror("ioctl");
return -1;
}
p_len = var_info.xres_virtual * var_info.yres_virtual * var_info.bits_per_pixel / 8;
pfb = mmap(NULL,p_len,PROT_READ | PROT_WRITE, MAP_SHARED,fb,0);
if(pfb == NULL)
{
perror("mmap");
close(fb);
}
init_background();
return 0;
}
list:
Makefile
obj-y += list.o
obj-y += init_list.o
list.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
pic * create_node(const char *path_name)
{
pic *p = (pic *)malloc(sizeof(pic));
if(p == NULL)
{
printf("malloc error\n");
return NULL;
}
strcpy(p->pathname,path_name);
p->next = NULL;
p->prev = NULL;
return p;
}
void insert_tail(pic *phead,pic *new)
{
pic *p = phead;
if(p == NULL)
exit(0);
if(p->next == NULL)
goto insert;
while(p->next != phead)
{
p = p->next;
}
insert:
p->next = new;
new->prev = p;
new->next = phead;
phead->prev = new;
}
pic *next_printf_link(pic* head,pic *p)
{
if(p == NULL)
exit(0);
if(p->next == head)
return p->next->next;
return p->next;
}
pic *prev_printf_link(pic* head,pic *p)
{
if(p == NULL)
exit(0);
if(p->prev == head)
return p->prev->prev;
return p->prev;
}
init_list.c
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include "list.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "fb_bmp.h"
#include "fb_jpeg.h"
#include "fb_png.h"
#include "fb.h"
int insert_pic_link(char *pathname)
{
DIR *dir;
struct dirent *entry;
struct stat sta;
char path[256];
if ((dir = opendir(pathname)) == NULL)
{
perror("opendir\n");
return -1;
}
while ((entry = readdir(dir)) != NULL)
{
if(strcmp(entry->d_name, ".")==0 || strcmp(entry->d_name, "..")==0 || strcmp(entry->d_name, "start")==0)
continue;
sprintf(path, "%s/%s", pathname, entry->d_name);
lstat(path, &sta);
if(S_ISDIR(sta.st_mode))
{
insert_pic_link(path);
}
if(S_ISREG(sta.st_mode))
{
insert_tail(head,create_node(path));
}
}
closedir(dir);
return 0;
}
void init_pic_link(pic *head)
{
pic *tmp = head->next;
while(tmp != head)
{
if(!is_bmp(tmp->pathname))
{
tmp->type = BMP;
tmp->display = display_bmp;
}
if(!is_jpeg(tmp->pathname))
{
tmp->type = JPEG;
tmp->display = display_jpeg;
}
if(!is_png(tmp->pathname))
{
tmp->type = PNG;
tmp->display = display_png;
}
tmp = tmp->next;
}
if(!is_bmp(tmp->pathname))
{
tmp->type = BMP;
tmp->display = display_bmp;
}
}
image: