写在前面的话
WebP是Google开发的一种图像格式,支持图像数据的有损和无损压缩。保留动画和alpha透明通道数据。
可以创建和JPEG、PNG和GIF图像格式在质量相同或质量更高,但是数据量更小的一种图像格式。
如下简单的分析一下webp图像格式,并使用sdl显示图片。
webp项目地址:https://github.com/webmproject/libwebp
sdl项目地址:https://libsdl.org/
webp格式图片说明
webp格式图像数据由两部分组成,RIFF头和图像负载信息。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 'R' | 'I' | 'F' | 'F' |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| File Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 'W' | 'E' | 'B' | 'P' |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
RIFF头信息由21个字节组成。
0-3 四个字节是 RIFF 四个字符,表示 资源交换格式Resource Interchange File Format的简写。
4-7 四个字节是 WEBP文件的全部长度,这个长度包含RIFF
8-11 四个字节是 资源交换格式的名称,填WEBP这四个字符
12-15 四个字节是数据块Chunk的负载信息的编码格式,取值有VP8表示无损vp8压缩,VP8L表示有损vp8压缩
16-19 四个字节是有损压缩时的VP8数据负载信息的长度
20-以后数vp8格式的图像数据帧。
VP8格式的定义如下
struct VP8Io { // set by VP8GetHeaders() int width, height; // picture dimensions, in pixels (invariable). // These are the original, uncropped dimensions. // The actual area passed to put() is stored // in mb_w / mb_h fields. // set before calling put() int mb_y; // position of the current rows (in pixels) int mb_w; // number of columns in the sample int mb_h; // number of rows in the sample const uint8_t* y, *u, *v; // rows to copy (in yuv420 format) int y_stride; // row stride for luma int uv_stride; // row stride for chroma const uint8_t* a; };
VP8使用14位表示图像的宽和高,因此webp格式图像宽高最大是2^14 = 16384像素。
VP8的每一个像素的值是通过 左上top-left 上top 右上top-right 和左left这4个像素做预测得到。
预测的方法如下
// L = left pixel, T = top pixel, TL = top left pixel. // ARGB component estimates for prediction. int pAlpha = ALPHA(L) + ALPHA(T) - ALPHA(TL); int pRed = RED(L) + RED(T) - RED(TL); int pGreen = GREEN(L) + GREEN(T) - GREEN(TL); int pBlue = BLUE(L) + BLUE(T) - BLUE(TL); // Manhattan distances to estimates for left and top pixels. int pL = abs(pAlpha - ALPHA(L)) + abs(pRed - RED(L)) + abs(pGreen - GREEN(L)) + abs(pBlue - BLUE(L)); int pT = abs(pAlpha - ALPHA(T)) + abs(pRed - RED(T)) + abs(pGreen - GREEN(T)) + abs(pBlue - BLUE(T));
VP8的详细分析,后面增加一篇对应的文章分析。
环境准备
centos环境可以先安装png tiff jpeg和sdl
yum install libjpeg-devel yum install libpng-devel yum install libtiff-devel
osx系统可以使用brew命令按照对应的包
使用cmake编译webp项目后会生成如下小工具
cwebp 工具可以将其他格式图片转成webp图片
./cwebp -h Usage: cwebp [options] -q quality input.png -o output.webp where quality is between 0 (poor) to 100 (very good). Typical value is around 80.
dwebp 工具可以将webp图片转成png jpg tiff ppm等格式
./dwebp -h Usage: dwebp in_file [options] [-o out_file] -pam ......... save the raw RGBA samples as a color PAM -ppm ......... save the raw RGB samples as a color PPM -bmp ......... save as uncompressed BMP format -tiff ........ save as uncompressed TIFF format -pgm ......... save the raw YUV samples as a grayscale PGM file with IMC4 layout -yuv ......... save the raw YUV samples in flat layout
vwebp_sdl 工具可以通过sdl显示webp图片
做SDL显示的一个例子
首先找一张png的图片,将这个图片转成webp格式的图像。
./cwebp leopard2.png -o leopard2.webp Saving file 'leopard2.webp' File: leopard2.png Dimension: 842 x 1134 Output: 59610 bytes Y-U-V-All-PSNR 40.28 44.99 46.35 41.45 dB (0.50 bpp) block count: intra4: 2898 (77.01%) intra16: 865 (22.99%) skipped: 1 (0.03%) bytes used: header: 203 (0.3%) mode-partition: 13872 (23.3%) Residuals bytes |segment 1|segment 2|segment 3|segment 4| total macroblocks: | 12%| 37%| 10%| 42%| 3763 quantizer: | 36 | 32 | 27 | 16 | filter level: | 11 | 8 | 12 | 15 |
如下是png格式图片是webp格式图片大小的3.4倍,png格式的图像大小是1.4M,webp格式图像的大小是58k
1.4M 9 8 16:29 leopard2.png 58K 9 8 16:30 leopard2.webp
显示webp图片
./vwebp_sdl leopard2.webp
SDL图像显示原理
SDL库做图像显示时,渲染表面接收RGB或者YUV格式的图像数据。
因此,若显示webp格式的图像,需要将webp格式的图像转成RGB或者YUV格式,再将图像数据传递给SDL的显示表面实现显示效果。
注:其他格式的图片也是同样的做法,其他格式转成RGB或者YUV格式数据交给SDL显示表面。
显示逻辑如下:
结合SDL显示图像的原理如下:
SDL显示webp的主要代码
int WebpToSDL(const char* data, unsigned int data_size) { int ok = 0; // 第一步 声明webp的配置和属性 VP8StatusCode status; WebPDecoderConfig config; WebPBitstreamFeatures* const input = &config.input; WebPDecBuffer* const output = &config.output; SDL_Surface* screen = NULL; SDL_Surface* surface = NULL; // 第二步 初始化webp解码配置信息 if (!WebPInitDecoderConfig(&config)) { fprintf(stderr, "Library version mismatch! "); return 0; } if (!init_ok) { SDL_Init(SDL_INIT_VIDEO); init_ok = 1; } // 第三步 获取webp图像数据 status = WebPGetFeatures((uint8_t*)data, (size_t)data_size, &config.input); if (status != VP8_STATUS_OK) goto Error; screen = SDL_SetVideoMode(input->width, input->height, 32, SDL_SWSURFACE); if (screen == NULL) { fprintf(stderr, "Unable to set video mode (32bpp %dx%d)! ", input->width, input->height); goto Error; } surface = SDL_CreateRGBSurface(SDL_SWSURFACE, input->width, input->height, 32, 0x000000ffu, // R mask 0x0000ff00u, // G mask 0x00ff0000u, // B mask 0xff000000u); // A mask if (surface == NULL) { fprintf(stderr, "Unable to create %dx%d RGBA surface! ", input->width, input->height); goto Error; } if (SDL_MUSTLOCK(surface)) SDL_LockSurface(surface); #if SDL_BYTEORDER == SDL_BIG_ENDIAN output->colorspace = MODE_BGRA; #else output->colorspace = MODE_RGBA; #endif output->width = surface->w; output->height = surface->h; output->u.RGBA.rgba = surface->pixels; output->u.RGBA.stride = surface->pitch; output->u.RGBA.size = surface->pitch * surface->h; output->is_external_memory = 1; // 第四步 解码webp格式成rgb格式的图像数据 status = WebPDecode((const uint8_t*)data, (size_t)data_size, &config); if (status != VP8_STATUS_OK) { fprintf(stderr, "Error decoding image (%d) ", status); goto Error; } if (SDL_MUSTLOCK(surface)) SDL_UnlockSurface(surface); // 第五步 将rgb的图像数据显示到SDL的目标表面,实现显示 if (SDL_BlitSurface(surface, NULL, screen, NULL) || SDL_Flip(screen)) { goto Error; } ok = 1; Error: SDL_FreeSurface(surface); SDL_FreeSurface(screen); WebPFreeDecBuffer(output); return ok; }
参考材料:
https://developers.google.cn/speed/webp/docs/riff_container
done.
祝玩的开心~