png图片是无损压缩格式,它有一个比较重要的特点是可以让特定区域透明。要显示png图片需要调用png库留出的函数接口,png库留出的函数接口很多,但是帮助手册里没有详细的说明各个函数的用法,网上也没找到太多资料,因此在这里只介绍一种能用的方法。有些函数用法在代码注释里写着。
1 /** 2 * filename: png.c 3 * author: Suzkfly 4 * date: 2021-08-14 5 * platform: S3C2416或Ubuntu 6 * 需要提前准备好lv.png这张图片,编译时要加-lpng参数,并且程序运行平台需要有png的库 7 */ 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <sys/stat.h> 12 #include <sys/types.h> 13 #include <unistd.h> 14 #include <fcntl.h> 15 #include <errno.h> 16 #include <linux/fb.h> 17 #include <sys/mman.h> 18 #include <png.h> 19 20 /**< \brief 定义每个像素点对应的位数,如果是565的屏则为16,如果是888的屏则为32 */ 21 #define BITS_PER_PIXEL 32 22 //#define BITS_PER_PIXEL 16 23 24 /**< \brief 根据实际情况修改,此处为unsigned short是565的屏,根据程序打印出的 25 bits_per_pixel的值可以判断出输出格式是565还是888 */ 26 #if (BITS_PER_PIXEL == 32) 27 typedef unsigned int color_t; 28 #elif (BITS_PER_PIXEL == 16) 29 typedef unsigned short color_t; 30 #endif 31 32 static struct fb_var_screeninfo __g_vinfo; /* 显示信息 */ 33 color_t *__gp_frame; /* 虚拟屏幕首地址 */ 34 35 /* framebuffer初始化 */ 36 int framebuffer_init (void) 37 { 38 int fd = 0; 39 40 fd = open("/dev/fb0", O_RDWR); 41 if (fd == -1) { 42 perror("fail to open /dev/fb0\n"); 43 return -1; 44 } 45 46 /* 获取显示信息 */ 47 ioctl(fd, FBIOGET_VSCREENINFO, &__g_vinfo); /* 获取显示信息 */ 48 printf("bits_per_pixel = %d\n", __g_vinfo.bits_per_pixel); /* 得到一个像素点对应的位数 */ 49 printf("xres_virtual = %d\n", __g_vinfo.xres_virtual); /* 打印虚拟屏幕列数 */ 50 printf("yres_virtual = %d\n", __g_vinfo.yres_virtual); /* 打印虚拟屏幕行数 */ 51 printf("xres = %d\n", __g_vinfo.xres); /* 打印屏幕列数 */ 52 printf("yres = %d\n", __g_vinfo.yres); /* 打印屏幕行数 */ 53 54 /* 设置显示信息 */ 55 /* 56 __g_vinfo.xres_virtual = 2048; 57 __g_vinfo.yres_virtual = 1920; 58 __g_vinfo.xres = 800; 59 __g_vinfo.yres = 600; 60 if (ioctl(fd, FBIOPUT_VSCREENINFO, &__g_vinfo)) { 61 perror ("Get VScreen Info Error\n"); 62 exit (1); 63 } 64 */ 65 66 /* 获取显示信息 */ 67 //ioctl(fd, FBIOGET_VSCREENINFO, &__g_vinfo); /* 获取显示信息 */ 68 //printf("bits_per_pixel = %d\n", __g_vinfo.bits_per_pixel); /* 得到一个像素点对应的位数 */ 69 //printf("xres_virtual = %d\n", __g_vinfo.xres_virtual); /* 打印虚拟屏幕列数 */ 70 //printf("yres_virtual = %d\n", __g_vinfo.yres_virtual); /* 打印虚拟屏幕行数 */ 71 //printf("xres = %d\n", __g_vinfo.xres); /* 打印屏幕列数 */ 72 //printf("yres = %d\n", __g_vinfo.yres); /* 打印屏幕行数 */ 73 74 __gp_frame = mmap(NULL, /* 映射区的开始地址,为NULL表示由系统决定映射区的起始地址 */ 75 __g_vinfo.xres_virtual * __g_vinfo.yres_virtual * __g_vinfo.bits_per_pixel / 8, /* 映射区大小 */ 76 PROT_WRITE | PROT_READ, /* 内存保护标志(可读可写) */ 77 MAP_SHARED, /* 映射对象类型(与其他进程共享) */ 78 fd, /* 有效的文件描述符 */ 79 0); /* 被映射内容的偏移量 */ 80 if (__gp_frame == NULL) { 81 perror("fail to mmap\n"); 82 return -1; 83 } 84 85 return 0; 86 } 87 88 /** 89 * \brief 填充整屏 90 */ 91 void full_screen (color_t color) 92 { 93 int i; 94 color_t *p = __gp_frame; 95 96 for (i = 0; i < __g_vinfo.xres_virtual * __g_vinfo.yres_virtual; i++) { 97 *p++ = color; 98 } 99 } 100 101 /** 102 * \brief 清屏 103 */ 104 void clear() 105 { 106 full_screen(0); 107 } 108 109 /** 110 * \brief 画点 111 */ 112 void draw_point(int x, int y, color_t color) 113 { 114 color_t *p = __gp_frame; 115 116 p += __g_vinfo.xres_virtual * y + x; 117 *p = color; 118 } 119 120 /** 121 * \brief 在指定位置显示PNG图片 122 * 123 * \param[in] x:x坐标 124 * \param[in] y:y坐标 125 * \param[in] file_path:file_path图片文件路径 126 * 127 * \retval 成功返回0,失败返回-1 128 */ 129 int Show_PNG(int x, int y, const char *file_path) 130 { 131 png_structp png_ptr; /* 该结构可通过png_create_read_struct函数得到,作为内部结构,会作为参数传给其他函数,一般不会被用户使用 */ 132 png_infop info_ptr; /* 用于保存png图片的信息,直接访问容易导致程序出问题,因此如果想要获取png图片信息最好调用库提供的函数接口 */ 133 int color_type = 0; 134 int image_width, image_height; 135 char sig[8] = { 0 }; /* 8个字节用来存储png文件头 */ 136 int i, j; 137 int R, G, B, A = 0; 138 FILE *p_fp; 139 png_bytep *row_pointers; /* 指针数组,数组中的每一个指针都指向一行图像数据 */ 140 int bytes_per_pix = 0; /* 一个像素点占用的字节数 */ 141 color_t color; 142 143 /* 打开png文件 */ 144 if ((p_fp = fopen(file_path, "rb")) == NULL) { 145 fprintf(stderr, "%s open fail!!!\n", file_path); 146 return -1; 147 } 148 149 /* 把文件前8个字节读出来,用来判断该文件是不是png文件,这8个字节必须是: 150 89 50 4e 47 0d 0a 1a 0a。如果使用了png_sig_cmp函数来判断文件头,那么后面 151 要读取数据之前要先调用png_set_sig_bytes,以告知文件头信息已经读取过了。 152 png_sig_cmp传递的第1个参数为需要检查的文件头数据指针,检查范围为 153 sig[第2个参数]~sig[第2个参数+第3个参数-1] */ 154 fread(sig, 1, sizeof(sig), p_fp); 155 if (0 != png_sig_cmp(sig, 0, sizeof(sig))) { 156 fprintf(stderr, "The file is not PNG\n"); 157 return -1; 158 } 159 160 /* 创建一个png_structp结构体,第1个参数为png库的版本,直接传入 161 PNG_LIBPNG_VER_STRING宏即可,第2个参数为给出错处理函数(即第3个参数)传递 162 的指针,第3个参数为出错处理函数的指针,第4个参数为警告处理函数的指针。 163 如果后3个参数传NULL,则表示使用系统默认的处理函数 */ 164 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 165 if (png_ptr == NULL) { 166 fclose(p_fp); 167 return -1; 168 } 169 170 /* 创建一个info_ptr,必须的 */ 171 info_ptr = png_create_info_struct(png_ptr); 172 if (info_ptr == NULL) { 173 fclose(p_fp); 174 png_destroy_read_struct(&png_ptr, NULL, NULL); 175 return -1; 176 } 177 178 /* 如果上面png_create_read_struct没有自定义错误处理,这里是必须的 */ 179 if (setjmp(png_jmpbuf(png_ptr))) { 180 png_destroy_read_struct(&png_ptr, &info_ptr, NULL); 181 fclose(p_fp); 182 183 return -1; 184 } 185 186 /* 初始化文件IO,相当于将文件流指针和png_ptr绑定了 */ 187 png_init_io(png_ptr, p_fp); 188 189 /* 如果前面调用了png_sig_cmp,那么就要调用本函数,否则程序在运行时会报错 */ 190 png_set_sig_bytes(png_ptr, sizeof(sig)); 191 192 /* 读取png图片信息。该函数相当于调用了png_read_info(),png_read_image()和 193 png_read_end()。如果调用了该函数,那么就不该再调用任何png_set_transform() 194 函数。 195 PNG_TRANSFORM_IDENTITY No transformation 196 PNG_TRANSFORM_STRIP_16 Strip 16-bit samples to 197 8 bits 198 PNG_TRANSFORM_STRIP_ALPHA Discard the alpha channel(丢弃透明度通道) 199 PNG_TRANSFORM_PACKING Expand 1, 2 and 4-bit 200 samples to bytes 201 PNG_TRANSFORM_PACKSWAP Change order of packed 202 pixels to LSB first 203 PNG_TRANSFORM_EXPAND Perform set_expand() 204 PNG_TRANSFORM_INVERT_MONO Invert monochrome images 205 PNG_TRANSFORM_SHIFT Normalize pixels to the 206 sBIT depth 207 PNG_TRANSFORM_BGR Flip RGB to BGR, RGBA 208 to BGRA 209 PNG_TRANSFORM_SWAP_ALPHA Flip RGBA to ARGB or GA 210 to AG 211 PNG_TRANSFORM_INVERT_ALPHA Change alpha from opacity 212 to transparency 213 PNG_TRANSFORM_SWAP_ENDIAN Byte-swap 16-bit samples 214 215 最后一个参数没有使用,传NULL即可*/ 216 png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND, NULL); 217 //png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_ALPHA, NULL); 218 219 if ((png_ptr != NULL) && (info_ptr != NULL)) { 220 /* 获取图像的宽高 */ 221 image_width = png_get_image_width(png_ptr, info_ptr); 222 image_height = png_get_image_height(png_ptr, info_ptr); 223 printf("width:%d,height:%d\n", image_width, image_height); 224 225 /* 获取图像颜色类型。如果在png_read_png中选择丢弃透明度通道,那么 226 color_type的值就是PNG_COLOR_TYPE_RGB,否则是PNG_COLOR_TYPE_RGB_ALPHA */ 227 color_type = png_get_color_type(png_ptr, info_ptr); 228 printf("color type:%d\n", color_type); 229 } 230 231 /* 获取所有的图像数据 */ 232 row_pointers = png_get_rows(png_ptr, info_ptr); 233 234 /* RGB格式 */ 235 if ((color_type == PNG_COLOR_TYPE_RGB) || (color_type == PNG_COLOR_TYPE_RGB_ALPHA)) { 236 if (color_type == PNG_COLOR_TYPE_RGB) { 237 bytes_per_pix = 3; 238 } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) { 239 bytes_per_pix = 4; 240 } 241 242 for(i = 0; i < image_height; i++) { 243 for(j = 0; j < (image_width * bytes_per_pix); j += bytes_per_pix) { 244 R = row_pointers[i][j + 0]; 245 G = row_pointers[i][j + 1]; 246 B = row_pointers[i][j + 2]; 247 #if (BITS_PER_PIXEL == 32) 248 if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) { 249 A = row_pointers[i][j + 3]; 250 } 251 /* 对于RGB888的显示屏来说,如果支持透明度,那么传入的颜色数据的高 252 8位表示的就是透明度 */ 253 color = (A << 24) | (R << 16) | (G << 8) | B; 254 #elif (BITS_PER_PIXEL == 16) 255 R >>= 3; 256 G >>= 2; 257 B >>= 3; 258 color = (R << 11) | (G << 5) | B; 259 #endif 260 draw_point(j / bytes_per_pix + x, i + y, color); 261 } 262 } 263 } 264 265 /* 清理图像,释放内存 */ 266 png_destroy_read_struct(&png_ptr, &info_ptr, 0); 267 268 fclose(p_fp); 269 270 return 0; 271 } 272 273 274 int main(int argc, const char *argv[]) 275 { 276 if (framebuffer_init()) { 277 return 0; 278 } 279 280 /* 清屏 */ 281 clear(); 282 283 Show_PNG(100, 100, "lv.png"); 284 285 return 0; 286 }
说明:lv.png这张图片需要提前准备好,编译时要加-lpng参数。
1. 如果程序运行时报错:
error while loading shared libraries: libpng.so.3: cannot open shared object file: No such file or directory
说明运行程序的平台上缺少png库,需要去下载,并将下载好的库复制到/lib目录下。
我下载的库在这里:libpng12.tar.gz
2. 如果显示的图片有透明区域,那么图片显示出来会有一些黑色区域(不一定是框框,反正就是大片的黑色区域),如下图(右),左边是我在电脑上打开后显示的样子。
为了解决这个问题,我在网上搜了很多资料,发现确实有很多网友遇到过这样的问题,但他们都是用别的平台或者语言。在linux下有很多显示png图片的资料,但是却没有人提到过这个问题。
有意思的是,我将这张图片通过微信发出去,在聊天窗口中显示的也是带黑框的(同上图右相同),但是将图片点开,并点击查看原图,黑框就会消失。这至少说明这是一个普遍存在并且可以解决的问题,希望以后的某一天我也能找到解决办法。