minigui:增加__mg_save_jpg保存JPEG格式图像

minigui 库中有SaveBitmapToFileSaveMyBitmapToFile可以将一个(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源码重新编译就可以了。

你可能感兴趣的:(minigui,MiniGUI)