• ijg库解码超大型jpeg图片


    1. ijg库解码超大型jpeg图片(>100M)的时候,如何避免内存溢出。

           采用边解码边压缩的策略,每次解码一行或者若干行图片数据,然后对于这些解码的数据,进行DQT(量化处理,过滤掉高频的数据,保持低频的数据),

    这样解码完,也压缩完。

    2. ijg库提供给我们的压缩接口都非常单一,仅有文件流操作,也就是仅仅只有从文件(图片)中读取,然后保存到文件中,而我们在解码大图片的时候,

    一般是希望它能够留在缓存中,所以我们需要对源文件进行数据导向内存中

    3. 一般而言,我们在进行图片压缩的时候,往往都希望能够随意调整图片的大小(w*h )比如原始图片时800*600,我们希望能够调整到300*300,而且

    保证尽可能保持原有图片清晰度的情况

      好现在对于每一个问题,我们来进行逐一的解决:

      第一个问题:

      1 #include<Windows.h>
      2 #include <stdio.h>
      3 #include "jpeglib.h"
      4 #include <setjmp.h>
      5 
      6 
      7 typedef struct  picture{
      8 
      9  int image_height;     /**/
     10  int image_width;   /**/
     11  int quality;    /*质量亏损*/
     12 } Picture;
     13 
     14 typedef struct  picture Picture;
     15 
     16 Picture  m_pict;  //设定一个设置参数的变量
     17 
     18 typedef unsigned char * wu_char;
     19 
     20 wu_char outdata;  //开辟一个较大的一维数组
     21 
     22 
     23 struct my_error_mgr {
     24     struct jpeg_error_mgr pub;    /* "public" fields */
     25 
     26     jmp_buf setjmp_buffer;    /* for return to caller */
     27 };
     28 
     29 typedef struct my_error_mgr * my_error_ptr;
     30 
     31 METHODDEF(void)
     32 my_error_exit (j_common_ptr cinfo)
     33 {
     34   my_error_ptr myerr = (my_error_ptr) cinfo->err;
     35   (*cinfo->err->output_message) (cinfo);
     36   longjmp(myerr->setjmp_buffer, 1);
     37 }
     38 
     39 
     40 GLOBAL(int)
     41 read_JPEG_file (char * filename,int* imagesize)
     42 {
     43   struct jpeg_decompress_struct cinfo;  //解压图片信息
     44   struct my_error_mgr jerr;                //解压过程中错误信息
     45   FILE * infile;                        /* 资源文件 */
     46   JSAMPARRAY buffer;                    /* 每次读取[1~N]行缓冲数据 暂定为一行数据 */
     47   int row_stride;        /*实际宽度大小*/
     48   if ((infile = fopen(filename, "rb")) == NULL) {
     49     fprintf(stderr, "文件不存在 %s
    ", filename);
     50     return 0;
     51   }
     52   cinfo.err = jpeg_std_error(&jerr.pub);    //解压过程中数据出错地址给予图片信息
     53   jerr.pub.error_exit = my_error_exit;   
     54   if (setjmp(jerr.setjmp_buffer)) {            //出错时,跳到这儿
     55     jpeg_destroy_decompress(&cinfo);
     56     fclose(infile);
     57     return 0;
     58   }
     59 
     60   jpeg_create_decompress(&cinfo);            //创建解压信息
     61   jpeg_stdio_src(&cinfo, infile);           //获得资源信息
     62   (void) jpeg_read_header(&cinfo, TRUE);             //获取图片信息
     63   (void) jpeg_start_decompress(&cinfo);                //开始解压
     64   row_stride = cinfo.output_width * cinfo.output_components;  //依据通道格式来进行每行宽度调整RGB格式3的倍数,灰度格式1的倍数
     65   buffer = (*cinfo.mem->alloc_sarray)            
     66         ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);            //分配每行数组的大小
     67  
     68   //write
     69   struct jpeg_compress_struct wcinfo;
     70   struct jpeg_error_mgr wjerr;
     71  // FILE * outfile;        /* target file */
     72   JSAMPROW row_pointer[1];    /* pointer to JSAMPLE row[s] */
     73   int wrow_stride;        /* physical row width in image buffer */
     74   wcinfo.err = jpeg_std_error(&wjerr);
     75   jpeg_create_compress(&wcinfo);
     76   /*需要在内存中完成解压和压缩,而且必须保证时间比较快,
     77    *所以使用外部内存不够理想,需要对源码进行改动,实现
     78    *将目的地接口改为我们申请的一个较小的内存块中,这里讲
     79    *所有指向File文件的数据流修改为指向char/unsigned char
     80    *型数组中,这里比较
     81    */
     82 
     83   jpeg_stdio_dest(&wcinfo, outdata,imagesize);
     84   /*保持原始的图片大小,保持质量亏损*/
     85   wcinfo.image_width =cinfo.image_width ;  
     86   wcinfo.image_height =cinfo.image_height;
     87   wcinfo.input_components = 3;          /* # of color components per pixel */
     88   wcinfo.in_color_space = JCS_RGB;       /* colorspace of input image */
     89   jpeg_set_defaults(&wcinfo);
     90   wcinfo.scale_num=3;     /*放大多少倍*/
     91   wcinfo.scale_denom=14;
     92   jpeg_set_quality(&wcinfo, m_pict.quality, TRUE );
     93   jpeg_start_compress(&wcinfo, TRUE);
     94   wrow_stride = m_pict.image_width * 3;    /* JSAMPLEs per row in image_buffer */
     95  // int cmod=cinfo.image_height/wcinfo.image_height;
     96   while (cinfo.output_scanline < cinfo.output_height) {
     97     (void) jpeg_read_scanlines(&cinfo, buffer, 1);    //解压出数据
     98     //if(cinfo.output_scanline%cmod==0)        
     99     (void) jpeg_write_scanlines(&wcinfo,buffer,1);   //压缩数据
    100   }
    101 
    102   (void) jpeg_finish_decompress(&cinfo);
    103   jpeg_destroy_decompress(&cinfo);
    104   fclose(infile);
    105   jpeg_finish_compress(&wcinfo);
    106   jpeg_destroy_compress(&wcinfo);
    107   return 1;
    108 }
    

    第二个问题,如何将文件从文件区导向缓冲区

    我们在压缩的时候,需要声明这个接口,来实现指针的传值,

     struct jpeg_compress_struct wcinfo;
     struct jpeg_error_mgr wjerr;

    同时需要用这个函数,将开辟的地址绑定,ijg源码提供的只有File* 接口,所以我们需要模仿这个函数,另外在写一个这个函数(最好方法就是用模板类来实现),这里我们只是简单的说下思路,

    重写一个这个函数

    jpeg_stdio_dest (j_compress_ptr cinfo, File* outfile,int *imagesize)

    另写一个这样的函数,将参数修改为unsigned char *型

    jpeg_stdio_dest (j_compress_ptr cinfo, unsigned char * outdata,int *imagesize)

    然后在jpeglib.h中找到

    EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile));

    将其修改为

    EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, unsigned char * outdata, int *imagesize/*返回压缩后图片大小*/));

    所以和这个File * outfile的数据类型,修改完这些之后,还需要修改的几个地方

    文件 jdatadst.c   (jpeg数据目的地文件)中

    找到这个结构体,修改或者增加几个自定义变量,中文解释部分为自己加的

    typedef struct {
      struct jpeg_destination_mgr pub; /* public fields */
    
      unsigned char * outdata; /*自定义数据缓冲地*/
      FILE * outfile;          /* target stream */
      JOCTET * buffer;          /* start of buffer */
      int *imageSize;          /*表示图片大小*/
      int moveSize ;        /*偏移量大小*/
    } my_destination_mgr;

    找到这个函数

    METHODDEF(boolean)
    empty_output_buffer (j_compress_ptr cinfo)

    将里面的文件操作,修改为内存复制便可,下面是将文件操作和缓存流结合起来放在一个文件中(加了一个标志位m_flag,ox011 表示文件操作,ox010便是缓存)

    METHODDEF(boolean)
    empty_output_buffer (j_compress_ptr cinfo)
    {
      my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
      if( dest->pub.m_flag==0x011  ){
    
      if (JFWRITE(dest->outfile, dest->buffer, OUTPUT_BUF_SIZE) !=
         (size_t) OUTPUT_BUF_SIZE)
         ERREXIT(cinfo, JERR_FILE_WRITE);
      }
      else{
         //使用的是内存缓冲数据管理
         //所以需要开辟一个新的数组
         /*这里存在一个疑问,数组的大小如何控制,偏移量如何管控? 
           需要去思考
    
           解答:  empty_mem_output_buffer 在这个函数中,因为使用了buffer不断的扩充内存,所以不需要控制
         */
          /*实现内存位置偏移,这里貌似存在一个问题,就是dest是一个临时的指针*/
         MEMCOPY( dest->outdata+ dest->moveSize,dest->buffer,OUTPUT_BUF_SIZE ); 
         dest->moveSize+=OUTPUT_BUF_SIZE;    //偏移增量
         *(dest->imageSize)=dest->moveSize; 
      }
    
      dest->pub.next_output_byte = dest->buffer;
      dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
    
      return TRUE;
    }

    同时在这个文件下面找到  

    METHODDEF(void)
    term_destination (j_compress_ptr cinfo)

    将这个文件的所有文件操作修改为内存复制

     1 METHODDEF(void)
     2 term_destination (j_compress_ptr cinfo)
     3 {
     4   my_dest_ptr dest = (my_dest_ptr) cinfo->dest;
     5   size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;
     6 
     7   if(dest->pub.m_flag==0x011){
     8     //表示使用文件为目的地
     9   /* Write any data remaining in the buffer */
    10   if (datacount > 0) {
    11     if (JFWRITE(dest->outfile, dest->buffer, datacount) != datacount)
    12       ERREXIT(cinfo, JERR_FILE_WRITE);
    13   }
    14   fflush(dest->outfile);
    15   /* Make sure we wrote the output file OK */
    16   if (ferror(dest->outfile))
    17     ERREXIT(cinfo, JERR_FILE_WRITE);
    18   }else{
    19   //否则为内存数据流
    20 if (datacount > 0){
    21     assert(dest->outdata==NULL);        //判断数组是否为空
    22     MEMCOPY(dest->outdata+dest->moveSize,dest->buffer,datacount);    
    23     dest->moveSize+=datacount;
    24     *(dest->imageSize)=dest->moveSize;
    25  }
    26   }
    27 }

    第三个问题,关于图片缩放问题

      j_compress_ptr 这个结构体有这个两个变量来设置,但是只找到了等比例缩放

      unsigned int scale_num, scale_denom; /* fraction by which to scale image */
  • 相关阅读:
    用Canvas绘制一个钟表
    用css3做一个3D立方体
    函数调用的不同方式,以及this的指向
    Javascript 严格模式use strict详解
    前端开发页面的性能优化方案总结
    Promise对象解读
    Vue爬坑之vuex初识
    WEB前端性能优化小结
    navicat 注册码
    docker
  • 原文地址:https://www.cnblogs.com/gongxijun/p/5055260.html
Copyright © 2020-2023  润新知