minigui
库中有SaveBitmapToFile
和SaveMyBitmapToFile
可以将一个(BITMAP或MYBITMAP)图像对象存储为文件。然而图像格式只支持bmp(libminigui-3.2.0
)。
但嵌入式平台上存储空间有限,我确实需要存储为JPEG啊。
怎么办?自己撸一个。
JPEG压缩的流程都是固定的,网上有很多代码,JPEG自己也有例子,这不难,复杂的地方是将一个MYBITMAP
对象的图像数据转成JPEG压缩所需要的RGB color space(色彩空间)。minigui
支持RGB,BRG,RGBA,RGB565,index 16 index 256,几种color space,RGB最简单,不需要转,其他几种color space都需要分别进行转换。
以下为JPEG压缩流程主函数代码:
int __fl_save_jpg (MG_RWops* fp, MYBITMAP* mybmp, RGB* pal)
{
j_compress_ptr cinfo;
struct my_error_mgr *jerr;
JSAMPROW linebuffer = NULL;
JSAMPROW row_pointer[1];
MYBITMAP_get_pixel_row get_row;
int retcode = ERR_BMP_CANT_SAVE;
/* Step 1: 创建 JPEG 压缩对象 */
cinfo = calloc (1, sizeof(struct jpeg_compress_struct));
if(NULL == cinfo)
{
fprintf(stderr, "__fl_save_jpg allocation error!\n");
return ERR_BMP_MEM;
}
jpeg_create_compress(cinfo);
/* Step 2: 创建错误处理对象 */
jerr = cinfo->mem->alloc_small((j_common_ptr) cinfo, JPOOL_IMAGE,sizeof(struct my_error_mgr));
if(NULL == jerr)
{
retcode = ERR_BMP_MEM;
goto do_finally;
}
memset(jerr,0,sizeof(struct my_error_mgr));
/* We set up the normal JPEG error routines first. */
cinfo->err = jpeg_std_error (&jerr->pub);
jerr->pub.error_exit = my_error_exit;
/* Establish the setjmp return context for my_error_exit to use. */
if (setjmp (jerr->setjmp_buffer)) {
/* 出错退出 */
fprintf(stderr, "__fl_save_jpg error!\n");
goto do_finally;
}
/* 只支持 RWAREA_TYPE_STDIO 类型的MG_RWops对象,否则报错, * MEM 类型的对象不能动态管理内存,不安全,所以不支持 */
if(RWAREA_TYPE_STDIO != fp->type )
{
fprintf(stderr, "unsupported type of MG_RWops,only support RWAREA_TYPE_STDIO so far\n");
longjmp (jerr->setjmp_buffer, 1);
}
/* Step 3: 将MG_RWops对象的FILE设置为压缩数据输出对象 */
jpeg_stdio_dest(cinfo, fp->hidden.stdio.fp);
/* Step 4: 初始化JPEG 压缩对象 */
/* for JPEG compression, supported color space : JCS_GRAYSCALE,JCS_RGB,JCS_YCbCr,JCS_CMYK,JCS_YCCK * in this case,MYBITMAP is base on RGB , * we can select JCS_RGB only,so we must convert all color space (eg.RGBA,BGR,RGB565,...) to RGB * */
cinfo->in_color_space = JCS_RGB;
cinfo->image_width = mybmp->w;
cinfo->image_height = mybmp->h;
cinfo->input_components = 3;
/* set jpeg compression parameters to default */
jpeg_set_defaults(cinfo);
#if _MGIMAGE_JPG_SAVE_QUALITY > 0 && _MGIMAGE_JPG_SAVE_QUALITY <= 100
/* 如果定义了_MGIMAGE_JPG_SAVE_QUALITY,就用它来指定压缩质量,否则使用默认值 75 * see also: libjpeg/jcparam.c jpeg_set_defaults function. * */
jpeg_set_quality(cinfo, _MGIMAGE_JPG_SAVE_QUALITY, TRUE);
#endif
int mybmp_type = mybmp->flags & MYBMP_TYPE_MASK;
/* 根据色彩空间类型确定 色彩空间转换函数(本文后面有提供) */
switch(mybmp->depth)
{
case 4:
get_row = MYBITMAP_get_pixel_row_pal16;
break;
case 8:
get_row = MYBITMAP_get_pixel_row_pal256;
break;
case 16:
get_row = MYBITMAP_get_pixel_row_RGB565;
break;
case 24:
if(MYBMP_TYPE_RGB == mybmp_type)
get_row = MYBITMAP_get_pixel_row_RGB;
else
get_row = MYBITMAP_get_pixel_row_BGR;
break;
case 32:
get_row = MYBITMAP_get_pixel_row_RGBA;
break;
default:
fprintf(stderr, "invalid MYBITMAP.depth = %d\n",mybmp->depth);
longjmp (jerr->setjmp_buffer, 1);
break;
}
if(24 == mybmp->depth && MYBMP_TYPE_RGB == mybmp_type)
{
/* * do nothing while RGB type, * the MYBITMAP_get_pixel_row function will return address in MYBITMAP.bits data directly, * without using line buffer * */
}
else
{
/* Allocate one-row buffer for color space conversion(4 byte alignment) */
linebuffer = (JSAMPROW)cinfo->mem->alloc_large
((j_common_ptr) cinfo, JPOOL_IMAGE,
(((cinfo->image_width + 3) & ~3) * cinfo->input_components));
if(NULL == linebuffer)
{
retcode = ERR_BMP_MEM;
fprintf(stderr, "libjpeg allocation error!\n");
longjmp (jerr->setjmp_buffer, 1);
}
}
/* Step 5: 逐行执行压缩 */
jpeg_start_compress(cinfo, TRUE);
while (cinfo->next_scanline < cinfo->image_height) {
/* 调用色彩空间转函数将一行数据转为RGB */
row_pointer[0] = get_row(cinfo->next_scanline, mybmp, pal, linebuffer);
jpeg_write_scanlines(cinfo, row_pointer, 1);
}
jpeg_finish_compress(cinfo);
retcode = ERR_BMP_OK;
do_finally:
/* clean up the JPEG object, free the objects and return. */
jpeg_destroy_compress (cinfo);
free (cinfo);
return retcode;
}
色彩空间转代码:
// 索引色(16色) 转换为RGB
static BYTE* MYBITMAP_get_pixel_row_pal16(unsigned int next_scanline,
MYBITMAP* mybmp,RGB* pal,BYTE* linebuffer)
{
BYTE* bits = mybmp->bits + mybmp->pitch * next_scanline;
RGB rgb0,rgb1;
for (int i = 0, j = 0, end_i = (mybmp->w + 1) >> 1; i < end_i; ++i) {
rgb0 = pal[ (bits[ i ] & 0XF0) >> 4 ];
rgb1 = pal[ bits[ i ] & 0X0F ];
linebuffer[ j ++ ] = rgb0.r;
linebuffer[ j ++ ] = rgb0.g;
linebuffer[ j ++ ] = rgb0.b;
linebuffer[ j ++ ] = rgb1.r;
linebuffer[ j ++ ] = rgb1.g;
linebuffer[ j ++ ] = rgb1.b;
}
return linebuffer;
}
// 索引色(256色) 转换为RGB
static BYTE* MYBITMAP_get_pixel_row_pal256(unsigned int next_scanline,
MYBITMAP* mybmp,RGB* pal,BYTE* linebuffer)
{
BYTE* bits = mybmp->bits + mybmp->pitch * next_scanline;
RGB rgb;
for (int i = 0, j = 0; i < mybmp->w; i++) {
rgb = pal [bits[i] ];
linebuffer[ j++ ] = rgb.r;
linebuffer[ j++ ] = rgb.g;
linebuffer[ j++ ] = rgb.b;
}
return linebuffer;
}
#define RGB_FROM_RGB565(pixel, r, g, b) \
{ \
r = (((pixel&0xF800)>>11)<<3); \
g = (((pixel&0x07E0)>>5)<<2); \
b = ((pixel&0x001F)<<3); \
}
// RGB565 转换为 RGB
static BYTE* MYBITMAP_get_pixel_row_RGB565(unsigned int next_scanline,
MYBITMAP* mybmp,RGB* pal,BYTE* linebuffer)
{
Uint16* bits = (Uint16*)(mybmp->bits + mybmp->pitch * next_scanline);
for (int i = 0, j = 0; i < mybmp->w; i++) {
RGB_FROM_RGB565(bits[i], linebuffer[ j ++ ], linebuffer[ j ++ ], linebuffer[ j ++ ])
}
return linebuffer;
}
// RGB to RGB,直接返回MYBITMAP的图像数据地址
static BYTE* MYBITMAP_get_pixel_row_RGB(unsigned int next_scanline,
MYBITMAP* mybmp,RGB* pal,BYTE* linebuffer)
{
return (JSAMPROW)(mybmp->bits + mybmp->pitch * next_scanline);
}
// BGR to RGB
static BYTE* MYBITMAP_get_pixel_row_BGR(unsigned int next_scanline,
MYBITMAP* mybmp,RGB* pal,BYTE* linebuffer)
{
BYTE* bits = mybmp->bits + mybmp->pitch * next_scanline;
for (int i = 0, end_i = mybmp->w * 3; i < end_i; i += 3) {
linebuffer[ i ] = bits[ i + 2 ];
linebuffer[ i + 1 ] = bits[ i + 1 ];
linebuffer[ i + 2 ] = bits[ i ];
}
return linebuffer;
}
#ifndef _MGIMAGE_JPG_RGBA_BGCOLOR
/* background color for RGBA color space conversion,for example: 0xFF0000 is red */
#define _MGIMAGE_JPG_RGBA_BGCOLOR 0xFFFFFF
#endif
// RGBA to RGB,转时会将alpha通道值与R,G,B合并计算出新的RGB值
static BYTE* MYBITMAP_get_pixel_row_RGBA(unsigned int next_scanline,
MYBITMAP* mybmp,RGB* pal,BYTE* linebuffer)
{
RGB* bits = (RGB*)(mybmp->bits + mybmp->pitch * next_scanline);
RGB pixel;
RGB bgcolor = {
(_MGIMAGE_JPG_RGBA_BGCOLOR >> 16) & 0xFF,/* red */
(_MGIMAGE_JPG_RGBA_BGCOLOR >> 8) & 0xFF,/* green */
_MGIMAGE_JPG_RGBA_BGCOLOR & 0xFF,/* blue */
0x00 /* alpha,no used */
};
for (int i = 0,j = 0; i < mybmp->w; i++, j += 3) {
pixel = bits[i];
/* alpha composite, * C = Cx * ALPHAx + (1 - ALPHAx) * Cbg * see also : https://en.wikipedia.org/wiki/Alpha_compositing#Analytical_derivation_of_the_over_operator */
linebuffer[ j ] = (JSAMPLE)((Uint32)pixel.r * pixel.a >> 8); /* red */
linebuffer[ j + 1 ] = (JSAMPLE)((Uint32)pixel.g * pixel.a >> 8); /* green */
linebuffer[ j + 2 ] = (JSAMPLE)((Uint32)pixel.b * pixel.a >> 8); /* blue */
linebuffer[ j ] += (JSAMPLE)((Uint32)bgcolor.r * (255 - pixel.a) >> 8); /* red + background color*/
linebuffer[ j + 1 ] += (JSAMPLE)((Uint32)bgcolor.g * (255 - pixel.a) >> 8); /* green + background color */
linebuffer[ j + 2 ] += (JSAMPLE)((Uint32)bgcolor.b * (255 - pixel.a) >> 8); /* blue + background color */
}
return linebuffer;
}
好了那个童鞋,不要抄笔记了,完整的代码我已经pull request到MiniGUI
官方github仓库(https://github.com/VincentWei/minigui),MiniGUI作者已经将pull request合并到主版本。你可以直接下载三个更新文件:
src/mybmp/jpeg.c,
src/include/readbmp.h,
src/mybmp/mybmp.c
覆盖自己的MiniGUI
源码重新编译就可以了。