"Fossies" - the Fresh Open Source Software Archive

Member "jpegoptim-1.4.6/jpegoptim.c" (18 Apr 2018, 26070 Bytes) of package /linux/privat/jpegoptim-1.4.6.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "jpegoptim.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.4.5_vs_1.4.6.

    1 /*******************************************************************
    2  * JPEGoptim
    3  * Copyright (c) Timo Kokkonen, 1996-2016.
    4  * All Rights Reserved.
    5  *
    6  * requires libjpeg (Independent JPEG Group's JPEG software 
    7  *                     release 6a or later...)
    8  *
    9  * $Id: 74017e528ed731e76a8d244d80cf8db865aaaf76 $
   10  */
   11 
   12 #ifdef HAVE_CONFIG_H
   13 #include "config.h"
   14 #endif
   15 #include <stdio.h>
   16 #include <stdlib.h>
   17 #ifdef HAVE_UNISTD_H
   18 #include <unistd.h>
   19 #endif
   20 #include <dirent.h>
   21 #if HAVE_GETOPT_H && HAVE_GETOPT_LONG
   22 #include <getopt.h>
   23 #else
   24 #include "getopt.h"
   25 #endif
   26 #include <signal.h>
   27 #include <string.h>
   28 #include <jpeglib.h>
   29 #include <jerror.h>
   30 #include <setjmp.h>
   31 #include <time.h>
   32 #include <math.h>
   33 
   34 #include "jpegoptim.h"
   35 
   36 
   37 #define VERSIO "1.4.6"
   38 #define COPYRIGHT  "Copyright (C) 1996-2018, Timo Kokkonen"
   39 
   40 
   41 #define LOG_FH (logs_to_stdout ? stdout : stderr)
   42 
   43 #define FREE_LINE_BUF(buf,lines)  {             \
   44     int j;                          \
   45     for (j=0;j<lines;j++) free(buf[j]);             \
   46     free(buf);                          \
   47     buf=NULL;                           \
   48   }
   49 
   50 #define STRNCPY(dest,src,n) { strncpy(dest,src,n); dest[n-1]=0; }
   51 
   52 struct my_error_mgr {
   53   struct jpeg_error_mgr pub;
   54   jmp_buf setjmp_buffer;   
   55   int     jump_set;
   56 };
   57 typedef struct my_error_mgr * my_error_ptr;
   58 
   59 const char *rcsid = "$Id: 74017e528ed731e76a8d244d80cf8db865aaaf76 $";
   60 
   61 
   62 int verbose_mode = 0;
   63 int quiet_mode = 0;
   64 int global_error_counter = 0;
   65 int preserve_mode = 0;
   66 int preserve_perms = 0;
   67 int overwrite_mode = 0;
   68 int totals_mode = 0;
   69 int stdin_mode = 0;
   70 int stdout_mode = 0;
   71 int noaction = 0;
   72 int quality = -1;
   73 int retry = 0;
   74 int dest = 0;
   75 int force = 0;
   76 int save_exif = 1;
   77 int save_iptc = 1;
   78 int save_com = 1;
   79 int save_icc = 1;
   80 int save_xmp = 1;
   81 int strip_none = 0;
   82 int threshold = -1;
   83 int csv = 0;
   84 int all_normal = 0;
   85 int all_progressive = 0;
   86 int target_size = 0;
   87 int logs_to_stdout = 1;
   88 
   89 
   90 struct option long_options[] = {
   91   {"verbose",0,0,'v'},
   92   {"help",0,0,'h'},
   93   {"quiet",0,0,'q'},
   94   {"max",1,0,'m'},
   95   {"totals",0,0,'t'},
   96   {"noaction",0,0,'n'},
   97   {"dest",1,0,'d'},
   98   {"force",0,0,'f'},
   99   {"version",0,0,'V'},
  100   {"overwrite",0,0,'o'},
  101   {"preserve",0,0,'p'},
  102   {"preserve-perms",0,0,'P'},
  103   {"strip-all",0,0,'s'},
  104   {"strip-none",0,&strip_none,1},
  105   {"strip-com",0,&save_com,0},
  106   {"strip-exif",0,&save_exif,0},
  107   {"strip-iptc",0,&save_iptc,0},
  108   {"strip-icc",0,&save_icc,0},
  109   {"strip-xmp",0,&save_xmp,0},
  110   {"threshold",1,0,'T'},
  111   {"csv",0,0,'b'},
  112   {"all-normal",0,&all_normal,1},
  113   {"all-progressive",0,&all_progressive,1},
  114   {"size",1,0,'S'},
  115   {"stdout",0,&stdout_mode,1},
  116   {"stdin",0,&stdin_mode,1},
  117   {0,0,0,0}
  118 };
  119 
  120 
  121 /*****************************************************************/
  122 
  123 METHODDEF(void) 
  124 my_error_exit (j_common_ptr cinfo)
  125 {
  126   my_error_ptr myerr = (my_error_ptr)cinfo->err;
  127 
  128   (*cinfo->err->output_message) (cinfo);
  129   if (myerr->jump_set) 
  130     longjmp(myerr->setjmp_buffer,1);
  131   else
  132     fatal("fatal error");
  133 }
  134 
  135 METHODDEF(void)
  136 my_output_message (j_common_ptr cinfo)
  137 {
  138   char buffer[JMSG_LENGTH_MAX+1];
  139 
  140   if (verbose_mode) {
  141     (*cinfo->err->format_message)((j_common_ptr)cinfo,buffer);
  142     buffer[sizeof(buffer)-1]=0;
  143     fprintf(LOG_FH," (%s) ",buffer);
  144   }
  145   global_error_counter++;
  146 }
  147 
  148 
  149 void print_usage(void) 
  150 {
  151   fprintf(stderr,PROGRAMNAME " v" VERSIO "  " COPYRIGHT "\n");
  152 
  153   fprintf(stderr,
  154       "Usage: " PROGRAMNAME " [options] <filenames> \n\n"
  155       "  -d<path>, --dest=<path>\n"
  156       "                    specify alternative destination directory for \n"
  157       "                    optimized files (default is to overwrite originals)\n"
  158       "  -f, --force       force optimization\n"
  159       "  -h, --help        display this help and exit\n"
  160       "  -m<quality>, --max=<quality>\n"
  161       "                    set maximum image quality factor (disables lossless\n"
  162       "                    optimization mode, which is by default on)\n"
  163       "                    Valid quality values: 0 - 100\n"
  164       "  -n, --noaction    don't really optimize files, just print results\n"
  165       "  -S<size>, --size=<size>\n"
  166       "                    Try to optimize file to given size (disables lossless\n"
  167       "                    optimization mode). Target size is specified either in\n"
  168       "                    kilo bytes (1 - n) or as percentage (1%% - 99%%)\n"
  169       "  -T<threshold>, --threshold=<threshold>\n"
  170       "                    keep old file if the gain is below a threshold (%%)\n"
  171       "  -b, --csv         print progress info in CSV format\n"
  172       "  -o, --overwrite   overwrite target file even if it exists (meaningful\n"
  173           "                    only when used with -d, --dest option)\n"
  174       "  -p, --preserve    preserve file timestamps\n"
  175       "  -P, --preserve-perms\n"
  176           "                    preserve original file permissions by overwriting it\n"
  177       "  -q, --quiet       quiet mode\n"
  178       "  -t, --totals      print totals after processing all files\n"
  179       "  -v, --verbose     enable verbose mode (positively chatty)\n"
  180       "  -V, --version     print program version\n\n"
  181       "  -s, --strip-all   strip all markers from output file\n"
  182       "  --strip-none      do not strip any markers\n"
  183       "  --strip-com       strip Comment markers from output file\n"
  184       "  --strip-exif      strip Exif markers from output file\n"
  185       "  --strip-iptc      strip IPTC/Photoshop (APP13) markers from output file\n"
  186       "  --strip-icc       strip ICC profile markers from output file\n"
  187       "  --strip-xmp       strip XMP markers markers from output file\n"
  188       "\n"
  189       "  --all-normal      force all output files to be non-progressive\n"
  190       "  --all-progressive force all output files to be progressive\n"
  191       "  --stdout          send output to standard output (instead of a file)\n"
  192       "  --stdin           read input from standard input (instead of a file)\n"
  193       "\n\n");
  194 }
  195 
  196 void print_version() 
  197 {
  198   struct jpeg_error_mgr jcerr, *err;
  199 
  200   
  201   printf(PROGRAMNAME " v%s  %s\n",VERSIO,HOST_TYPE);
  202   printf(COPYRIGHT "\n\n");
  203   printf("This program comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
  204      "and you are welcome to redistirbute it under certain conditions.\n"
  205      "See the GNU General Public License for more details.\n\n");
  206 
  207   if (!(err=jpeg_std_error(&jcerr)))
  208     fatal("jpeg_std_error() failed");
  209 
  210   printf("\nlibjpeg version: %s\n%s\n",
  211      err->jpeg_message_table[JMSG_VERSION],
  212      err->jpeg_message_table[JMSG_COPYRIGHT]);
  213 }
  214 
  215 
  216 void own_signal_handler(int a)
  217 {
  218   if (verbose_mode > 1) 
  219     fprintf(stderr,PROGRAMNAME ": signal: %d\n",a);
  220   exit(1);
  221 }
  222 
  223 
  224 
  225 
  226 
  227 
  228 void write_markers(struct jpeg_decompress_struct *dinfo,
  229            struct jpeg_compress_struct *cinfo)
  230 {
  231   jpeg_saved_marker_ptr mrk;
  232   int write_marker;
  233 
  234   if (!cinfo || !dinfo) fatal("invalid call to write_markers()");
  235 
  236   mrk=dinfo->marker_list;
  237   while (mrk) {
  238     write_marker=0;
  239 
  240     /* check for markers to save... */
  241 
  242     if (save_com && mrk->marker == JPEG_COM) 
  243       write_marker++;
  244    
  245     if (save_iptc && mrk->marker == IPTC_JPEG_MARKER) 
  246       write_marker++;
  247     
  248     if (save_exif && mrk->marker == EXIF_JPEG_MARKER &&
  249     mrk->data_length >= EXIF_IDENT_STRING_SIZE &&
  250     !memcmp(mrk->data,EXIF_IDENT_STRING,EXIF_IDENT_STRING_SIZE)) 
  251       write_marker++;
  252     
  253     if (save_icc && mrk->marker == ICC_JPEG_MARKER &&
  254     mrk->data_length >= ICC_IDENT_STRING_SIZE &&
  255     !memcmp(mrk->data,ICC_IDENT_STRING,ICC_IDENT_STRING_SIZE)) 
  256       write_marker++;
  257    
  258     if (save_xmp && mrk->marker == XMP_JPEG_MARKER &&
  259     mrk->data_length >= XMP_IDENT_STRING_SIZE &&
  260     !memcmp(mrk->data,XMP_IDENT_STRING,XMP_IDENT_STRING_SIZE)) 
  261       write_marker++;
  262 
  263     if (strip_none) write_marker++;
  264 
  265 
  266     /* libjpeg emits some markers automatically so skip these to avoid duplicates... */
  267 
  268     /* skip JFIF (APP0) marker */
  269     if ( mrk->marker == JPEG_APP0 && mrk->data_length >= 14 &&
  270      mrk->data[0] == 0x4a &&
  271      mrk->data[1] == 0x46 &&
  272      mrk->data[2] == 0x49 &&
  273      mrk->data[3] == 0x46 &&
  274      mrk->data[4] == 0x00 ) 
  275       write_marker=0;
  276 
  277     /* skip Adobe (APP14) marker */
  278     if ( mrk->marker == JPEG_APP0+14 && mrk->data_length >= 12 &&
  279      mrk->data[0] == 0x41 &&
  280      mrk->data[1] == 0x64 &&
  281      mrk->data[2] == 0x6f &&
  282      mrk->data[3] == 0x62 &&
  283      mrk->data[4] == 0x65 ) 
  284       write_marker=0;
  285 
  286     
  287 
  288     if (write_marker) 
  289       jpeg_write_marker(cinfo,mrk->marker,mrk->data,mrk->data_length);
  290     
  291     mrk=mrk->next;
  292   }
  293 }
  294 
  295 
  296 
  297 
  298 
  299 /*****************************************************************/
  300 int main(int argc, char **argv) 
  301 {
  302   struct jpeg_decompress_struct dinfo;
  303   struct jpeg_compress_struct cinfo;
  304   struct my_error_mgr jcerr,jderr;
  305   JSAMPARRAY buf = NULL;
  306   jvirt_barray_ptr *coef_arrays = NULL;
  307   char marker_str[256];
  308   char tmpfilename[MAXPATHLEN],tmpdir[MAXPATHLEN];
  309   char newname[MAXPATHLEN], dest_path[MAXPATHLEN];
  310   volatile int i;
  311   int c,j, searchcount, searchdone;
  312   int opt_index = 0;
  313   long insize = 0, outsize = 0, lastsize = 0;
  314   int oldquality;
  315   double ratio;
  316   struct stat file_stat;
  317   jpeg_saved_marker_ptr cmarker; 
  318   unsigned char *outbuffer = NULL;
  319   size_t outbuffersize;
  320   char *outfname = NULL;
  321   FILE *infile = NULL, *outfile = NULL;
  322   int marker_in_count, marker_in_size;
  323   int compress_err_count = 0;
  324   int decompress_err_count = 0;
  325   long average_count = 0;
  326   double average_rate = 0.0, total_save = 0.0;
  327 
  328 
  329   if (rcsid)
  330   ; /* so compiler won't complain about "unused" rcsid string */
  331 
  332   umask(077);
  333   signal(SIGINT,own_signal_handler);
  334   signal(SIGTERM,own_signal_handler);
  335 
  336   /* initialize decompression object */
  337   dinfo.err = jpeg_std_error(&jderr.pub);
  338   jpeg_create_decompress(&dinfo);
  339   jderr.pub.error_exit=my_error_exit;
  340   jderr.pub.output_message=my_output_message;
  341   jderr.jump_set = 0;
  342 
  343   /* initialize compression object */
  344   cinfo.err = jpeg_std_error(&jcerr.pub);
  345   jpeg_create_compress(&cinfo);
  346   jcerr.pub.error_exit=my_error_exit;
  347   jcerr.pub.output_message=my_output_message;
  348   jcerr.jump_set = 0;
  349 
  350 
  351   if (argc<2) {
  352     if (!quiet_mode) fprintf(stderr,PROGRAMNAME ": file arguments missing\n"
  353                  "Try '" PROGRAMNAME " --help' for more information.\n");
  354     exit(1);
  355   }
  356  
  357   /* parse command line parameters */
  358   while(1) {
  359     opt_index=0;
  360     if ((c=getopt_long(argc,argv,"d:hm:nstqvfVpPoT:S:b",long_options,&opt_index))
  361           == -1) 
  362       break;
  363 
  364     switch (c) {
  365     case 'm':
  366       {
  367         int tmpvar;
  368 
  369         if (sscanf(optarg,"%d",&tmpvar) == 1) {
  370       quality=tmpvar;
  371       if (quality < 0) quality=0;
  372       if (quality > 100) quality=100;
  373     }
  374     else 
  375       fatal("invalid argument for -m, --max");
  376       }
  377       break;
  378     case 'd':
  379       if (realpath(optarg,dest_path)==NULL)
  380     fatal("invalid destination directory: %s", optarg);
  381       if (!is_directory(dest_path))
  382     fatal("destination not a directory: %s", dest_path);
  383       strncat(dest_path,DIR_SEPARATOR_S,sizeof(dest_path)-strlen(dest_path)-1);
  384 
  385       if (verbose_mode) 
  386     fprintf(stderr,"Destination directory: %s\n",dest_path);
  387       dest=1;
  388       break;
  389     case 'v':
  390       verbose_mode++;
  391       break;
  392     case 'h':
  393       print_usage();
  394       exit(0);
  395       break;
  396     case 'q':
  397       quiet_mode=1;
  398       break;
  399     case 't':
  400       totals_mode=1;
  401       break;
  402     case 'n':
  403       noaction=1;
  404       break;
  405     case 'f':
  406       force=1;
  407       break;
  408     case 'b':
  409       csv=1;
  410       quiet_mode=1;
  411       break;
  412     case '?':
  413       break;
  414     case 'V':
  415       print_version();
  416       exit(0);
  417       break;
  418     case 'o':
  419       overwrite_mode=1;
  420       break;
  421     case 'p':
  422       preserve_mode=1;
  423       break;
  424     case 'P':
  425       preserve_perms=1;
  426       break;
  427     case 's':
  428       save_exif=0;
  429       save_iptc=0;
  430       save_com=0;
  431       save_icc=0;
  432       save_xmp=0;
  433       break;
  434     case 'T':
  435       {
  436     int tmpvar;
  437     if (sscanf(optarg,"%d",&tmpvar) == 1) {
  438       threshold=tmpvar;
  439       if (threshold < 0) threshold=0;
  440       if (threshold > 100) threshold=100;
  441     }
  442     else fatal("invalid argument for -T, --threshold");
  443       }
  444       break;
  445     case 'S':
  446       {
  447     unsigned int tmpvar;
  448     if (sscanf(optarg,"%u",&tmpvar) == 1) {
  449       if (tmpvar > 0 && tmpvar < 100 && optarg[strlen(optarg)-1] == '%' ) {
  450         target_size=-tmpvar;
  451       } else {
  452         target_size=tmpvar;
  453       }
  454       quality=100;
  455     }
  456     else fatal("invalid argument for -S, --size");
  457       }
  458       break;
  459 
  460     }
  461   }
  462 
  463 
  464   /* check for '-' option indicating input is from stdin... */
  465   i=1;
  466   while (argv[i]) {
  467     if (argv[i][0]=='-' && argv[i][1]==0) stdin_mode=1;
  468     i++;
  469   }
  470 
  471   if (stdin_mode) { stdout_mode=1; force=1; }
  472   if (stdout_mode) { logs_to_stdout=0; }
  473 
  474   if (all_normal && all_progressive)
  475     fatal("cannot specify both --all-normal and --all-progressive"); 
  476 
  477   if (verbose_mode) {
  478     if (quality>=0 && target_size==0) 
  479       fprintf(stderr,"Image quality limit set to: %d\n",quality);
  480     if (threshold>=0) 
  481       fprintf(stderr,"Compression threshold (%%) set to: %d\n",threshold);
  482     if (all_normal) 
  483       fprintf(stderr,"All output files will be non-progressive\n");
  484     if (all_progressive) 
  485       fprintf(stderr,"All output files will be progressive\n");
  486     if (target_size > 0) 
  487       fprintf(stderr,"Target size for output files set to: %u Kbytes.\n",
  488           target_size);
  489     if (target_size < 0) 
  490       fprintf(stderr,"Target size for output files set to: %u%%\n",
  491           -target_size);
  492   }
  493 
  494 
  495   /* loop to process the input files */
  496   i=(optind > 0 ? optind : 1);
  497   do {
  498     if (stdin_mode) {
  499       infile=stdin;
  500       set_filemode_binary(infile);
  501     } else {
  502       if (i >= argc || !argv[i][0]) continue;
  503       if (argv[i][0]=='-') continue;
  504       if (strlen(argv[i]) >= MAXPATHLEN) {
  505     warn("skipping too long filename: %s",argv[i]);
  506     continue;
  507       }
  508 
  509       if (!noaction) {
  510     /* generate tmp dir & new filename */
  511     if (dest) {
  512       STRNCPY(tmpdir,dest_path,sizeof(tmpdir));
  513       STRNCPY(newname,dest_path,sizeof(newname));
  514       if (!splitname(argv[i],tmpfilename,sizeof(tmpfilename)))
  515         fatal("splitname() failed for: %s",argv[i]);
  516       strncat(newname,tmpfilename,sizeof(newname)-strlen(newname)-1);
  517     } else {
  518       if (!splitdir(argv[i],tmpdir,sizeof(tmpdir))) 
  519         fatal("splitdir() failed for: %s",argv[i]);
  520       STRNCPY(newname,argv[i],sizeof(newname));
  521     }
  522       }
  523       
  524     retry_point:
  525       
  526       if (!is_file(argv[i],&file_stat)) {
  527     if (is_directory(argv[i])) 
  528       warn("skipping directory: %s",argv[i]);
  529     else
  530       warn("skipping special file: %s",argv[i]); 
  531     continue;
  532       }
  533       if ((infile=fopen(argv[i],"rb"))==NULL) {
  534     warn("cannot open file: %s", argv[i]);
  535     continue;
  536       }
  537     }
  538 
  539    if (setjmp(jderr.setjmp_buffer)) {
  540      /* error handler for decompress */
  541      jpeg_abort_decompress(&dinfo);
  542      fclose(infile);
  543      if (buf) FREE_LINE_BUF(buf,dinfo.output_height);
  544      if (!quiet_mode || csv) 
  545        fprintf(LOG_FH,csv ? ",,,,,error\n" : " [ERROR]\n");
  546      decompress_err_count++;
  547      jderr.jump_set=0;
  548      continue;
  549    } else {
  550      jderr.jump_set=1;
  551    }
  552 
  553    if (!retry && (!quiet_mode || csv)) {
  554      fprintf(LOG_FH,csv ? "%s," : "%s ",(stdin_mode?"stdin":argv[i])); fflush(LOG_FH); 
  555    }
  556 
  557    /* prepare to decompress */
  558    global_error_counter=0;
  559    jpeg_save_markers(&dinfo, JPEG_COM, 0xffff);
  560    for (j=0;j<=15;j++) 
  561      jpeg_save_markers(&dinfo, JPEG_APP0+j, 0xffff);
  562    jpeg_stdio_src(&dinfo, infile);
  563    jpeg_read_header(&dinfo, TRUE); 
  564 
  565    /* check for Exif/IPTC/ICC/XMP markers */
  566    marker_str[0]=0;
  567    marker_in_count=0;
  568    marker_in_size=0;
  569    cmarker=dinfo.marker_list;
  570 
  571    while (cmarker) {
  572      marker_in_count++;
  573      marker_in_size+=cmarker->data_length;
  574 
  575      if (cmarker->marker == EXIF_JPEG_MARKER &&
  576      cmarker->data_length >= EXIF_IDENT_STRING_SIZE &&
  577      !memcmp(cmarker->data,EXIF_IDENT_STRING,EXIF_IDENT_STRING_SIZE))
  578        strncat(marker_str,"Exif ",sizeof(marker_str)-strlen(marker_str)-1);
  579 
  580      if (cmarker->marker == IPTC_JPEG_MARKER)
  581        strncat(marker_str,"IPTC ",sizeof(marker_str)-strlen(marker_str)-1);
  582 
  583      if (cmarker->marker == ICC_JPEG_MARKER &&
  584      cmarker->data_length >= ICC_IDENT_STRING_SIZE &&
  585      !memcmp(cmarker->data,ICC_IDENT_STRING,ICC_IDENT_STRING_SIZE))
  586        strncat(marker_str,"ICC ",sizeof(marker_str)-strlen(marker_str)-1);
  587 
  588      if (cmarker->marker == XMP_JPEG_MARKER &&
  589      cmarker->data_length >= XMP_IDENT_STRING_SIZE &&
  590      !memcmp(cmarker->data,XMP_IDENT_STRING,XMP_IDENT_STRING_SIZE)) 
  591        strncat(marker_str,"XMP ",sizeof(marker_str)-strlen(marker_str)-1);
  592 
  593      cmarker=cmarker->next;
  594    }
  595 
  596 
  597    if (verbose_mode > 1) 
  598      fprintf(LOG_FH,"%d markers found in input file (total size %d bytes)\n",
  599          marker_in_count,marker_in_size);
  600    if (!retry && (!quiet_mode || csv)) {
  601      fprintf(LOG_FH,csv ? "%dx%d,%dbit,%c," : "%dx%d %dbit %c ",(int)dinfo.image_width,
  602          (int)dinfo.image_height,(int)dinfo.num_components*8,
  603          (dinfo.progressive_mode?'P':'N'));
  604 
  605      if (!csv) {
  606        fprintf(LOG_FH,"%s",marker_str);
  607        if (dinfo.saw_Adobe_marker) fprintf(LOG_FH,"Adobe ");
  608        if (dinfo.saw_JFIF_marker) fprintf(LOG_FH,"JFIF ");
  609      }
  610      fflush(LOG_FH);
  611    }
  612 
  613    if ((insize=file_size(infile)) < 0)
  614      fatal("failed to stat() input file");
  615 
  616   /* decompress the file */
  617    if (quality>=0 && !retry) {
  618      jpeg_start_decompress(&dinfo);
  619 
  620      /* allocate line buffer to store the decompressed image */
  621      buf = malloc(sizeof(JSAMPROW)*dinfo.output_height);
  622      if (!buf) fatal("not enough memory");
  623      for (j=0;j<dinfo.output_height;j++) {
  624        buf[j]=malloc(sizeof(JSAMPLE)*dinfo.output_width*
  625              dinfo.out_color_components);
  626        if (!buf[j]) fatal("not enough memory");
  627      }
  628 
  629      while (dinfo.output_scanline < dinfo.output_height) {
  630        jpeg_read_scanlines(&dinfo,&buf[dinfo.output_scanline],
  631                dinfo.output_height-dinfo.output_scanline);
  632      }
  633    } else {
  634      coef_arrays = jpeg_read_coefficients(&dinfo);
  635    }
  636 
  637    if (!retry && !quiet_mode) {
  638      if (global_error_counter==0) fprintf(LOG_FH," [OK] ");
  639      else fprintf(LOG_FH," [WARNING] ");
  640      fflush(LOG_FH);
  641    }
  642 
  643      
  644 
  645    if (dest && !noaction) {
  646      if (file_exists(newname) && !overwrite_mode) {
  647        warn("target file already exists: %s\n",newname);
  648        jpeg_abort_decompress(&dinfo);
  649        fclose(infile);
  650        if (buf) FREE_LINE_BUF(buf,dinfo.output_height);
  651        continue;
  652      }
  653    }
  654 
  655 
  656    if (setjmp(jcerr.setjmp_buffer)) {
  657      /* error handler for compress failures */
  658      
  659      jpeg_abort_compress(&cinfo);
  660      jpeg_abort_decompress(&dinfo);
  661      fclose(infile);
  662      if (!quiet_mode) fprintf(LOG_FH," [Compress ERROR]\n");
  663      if (buf) FREE_LINE_BUF(buf,dinfo.output_height);
  664      compress_err_count++;
  665      jcerr.jump_set=0;
  666      continue;
  667    } else {
  668      jcerr.jump_set=1;
  669    }
  670 
  671 
  672    lastsize = 0;
  673    searchcount = 0;
  674    searchdone = 0;
  675    oldquality = 200;
  676 
  677 
  678 
  679   binary_search_loop:
  680 
  681    /* allocate memory buffer that should be large enough to store the output JPEG... */
  682    if (outbuffer) free(outbuffer);
  683    outbuffersize=insize + 32768;
  684    outbuffer=malloc(outbuffersize);
  685    if (!outbuffer) fatal("not enough memory");
  686 
  687    /* setup custom "destination manager" for libjpeg to write to our buffer */
  688    jpeg_memory_dest(&cinfo, &outbuffer, &outbuffersize, 65536);
  689 
  690    if (quality>=0 && !retry) {
  691      /* lossy "optimization" ... */
  692 
  693      cinfo.in_color_space=dinfo.out_color_space;
  694      cinfo.input_components=dinfo.output_components;
  695      cinfo.image_width=dinfo.image_width;
  696      cinfo.image_height=dinfo.image_height;
  697      jpeg_set_defaults(&cinfo); 
  698      jpeg_set_quality(&cinfo,quality,TRUE);
  699      if (all_normal) {
  700        cinfo.scan_info = NULL; // Explicitly disables progressive if libjpeg had it on by default
  701        cinfo.num_scans = 0;
  702      } else if ( dinfo.progressive_mode || all_progressive ) {
  703        jpeg_simple_progression(&cinfo);
  704      }
  705      cinfo.optimize_coding = TRUE;
  706 
  707      j=0;
  708      jpeg_start_compress(&cinfo,TRUE);
  709      
  710      /* write markers */
  711      write_markers(&dinfo,&cinfo);
  712 
  713      /* write image */
  714      while (cinfo.next_scanline < cinfo.image_height) {
  715        jpeg_write_scanlines(&cinfo,&buf[cinfo.next_scanline],
  716                 dinfo.output_height);
  717      }
  718 
  719    } else {
  720      /* lossless "optimization" ... */
  721 
  722      jpeg_copy_critical_parameters(&dinfo, &cinfo);
  723      if (all_normal) {
  724        cinfo.scan_info = NULL; // Explicitly disables progressive if libjpeg had it on by default
  725        cinfo.num_scans = 0;
  726      } else if ( dinfo.progressive_mode || all_progressive ) {
  727        jpeg_simple_progression(&cinfo);
  728      }
  729      cinfo.optimize_coding = TRUE;
  730 
  731      /* write image */
  732      jpeg_write_coefficients(&cinfo, coef_arrays);
  733 
  734      /* write markers */
  735      write_markers(&dinfo,&cinfo);
  736 
  737    }
  738 
  739    jpeg_finish_compress(&cinfo);
  740    outsize=outbuffersize;
  741 
  742    if (target_size != 0 && !retry) {
  743      /* perform (binary) search to try to reach target file size... */
  744 
  745      long osize = outsize/1024;
  746      long isize = insize/1024;
  747      long tsize = target_size;
  748 
  749      if (tsize < 0) { 
  750        tsize=((-target_size)*insize/100)/1024; 
  751        if (tsize < 1) tsize=1;
  752      }
  753 
  754      if (osize == tsize || searchdone || searchcount >= 8 || tsize > isize) {
  755        if (searchdone < 42 && lastsize > 0) {
  756      if (labs(osize-tsize) > labs(lastsize-tsize)) {
  757        if (verbose_mode) fprintf(LOG_FH,"(revert to %d)",oldquality);
  758        searchdone=42;
  759        quality=oldquality;
  760        goto binary_search_loop;
  761      }
  762        }
  763        if (verbose_mode) fprintf(LOG_FH," ");
  764        
  765      } else {
  766        int newquality;
  767        int dif = floor((abs(oldquality-quality)/2.0)+0.5);
  768        if (osize > tsize) {
  769      newquality=quality-dif;
  770      if (dif < 1) { newquality--; searchdone=1; }
  771      if (newquality < 0) { newquality=0; searchdone=2; }
  772        } else {
  773      newquality=quality+dif;
  774      if (dif < 1) { newquality++; searchdone=3; }
  775      if (newquality > 100) { newquality=100; searchdone=4; }
  776        }
  777        oldquality=quality;
  778        quality=newquality;
  779 
  780        if (verbose_mode) fprintf(LOG_FH,"(try %d)",quality);
  781 
  782        lastsize=osize;
  783        searchcount++;
  784        goto binary_search_loop;
  785      }
  786    } 
  787 
  788    if (buf) FREE_LINE_BUF(buf,dinfo.output_height);
  789    jpeg_finish_decompress(&dinfo);
  790    fclose(infile);
  791 
  792 
  793    if (quality>=0 && outsize>=insize && !retry && !stdin_mode) {
  794      if (verbose_mode) fprintf(LOG_FH,"(retry w/lossless) ");
  795      retry=1;
  796      goto retry_point; 
  797    }
  798 
  799    retry=0;
  800    ratio=(insize-outsize)*100.0/insize;
  801    if (!quiet_mode || csv)
  802      fprintf(LOG_FH,csv ? "%ld,%ld,%0.2f," : "%ld --> %ld bytes (%0.2f%%), ",insize,outsize,ratio);
  803    average_count++;
  804    average_rate+=(ratio<0 ? 0.0 : ratio);
  805 
  806    if ((outsize < insize && ratio >= threshold) || force) {
  807         total_save+=(insize-outsize)/1024.0;
  808     if (!quiet_mode || csv) fprintf(LOG_FH,csv ? "optimized\n" : "optimized.\n");
  809         if (noaction) continue;
  810 
  811     if (stdout_mode) {
  812       outfname=NULL;
  813       set_filemode_binary(stdout);
  814       if (fwrite(outbuffer,outbuffersize,1,stdout) != 1)
  815         fatal("%s, write failed to stdout",(stdin_mode?"stdin":argv[i]));
  816     } else {
  817       if (preserve_perms && !dest) {
  818         /* make backup of the original file */
  819         snprintf(tmpfilename,sizeof(tmpfilename),"%s.jpegoptim.bak",newname);
  820         if (verbose_mode > 1 && !quiet_mode) 
  821           fprintf(LOG_FH,"%s, creating backup as: %s\n",(stdin_mode?"stdin":argv[i]),tmpfilename);
  822         if (file_exists(tmpfilename))
  823           fatal("%s, backup file already exists: %s",(stdin_mode?"stdin":argv[i]),tmpfilename);
  824         if (copy_file(newname,tmpfilename))
  825           fatal("%s, failed to create backup: %s",(stdin_mode?"stdin":argv[i]),tmpfilename);
  826         if ((outfile=fopen(newname,"wb"))==NULL)
  827           fatal("%s, error opening output file: %s",(stdin_mode?"stdin":argv[i]),newname);
  828         outfname=newname;
  829       } else {
  830 #ifdef HAVE_MKSTEMPS
  831         /* rely on mkstemps() to create us temporary file safely... */  
  832         snprintf(tmpfilename,sizeof(tmpfilename),
  833              "%sjpegoptim-%d-%d.XXXXXX.tmp", tmpdir, (int)getuid(), (int)getpid());
  834         int tmpfd = mkstemps(tmpfilename,4);
  835         if (tmpfd < 0) 
  836           fatal("%s, error creating temp file %s: mkstemps() failed",(stdin_mode?"stdin":argv[i]),tmpfilename);
  837         if ((outfile=fdopen(tmpfd,"wb"))==NULL) 
  838 #else
  839           /* if platform is missing mkstemps(), try to create at least somewhat "safe" temp file... */  
  840           snprintf(tmpfilename,sizeof(tmpfilename),
  841                "%sjpegoptim-%d-%d.%ld.tmp", tmpdir, (int)getuid(), (int)getpid(),(long)time(NULL));
  842         if ((outfile=fopen(tmpfilename,"wb"))==NULL) 
  843 #endif
  844           fatal("error opening temporary file: %s",tmpfilename);
  845         outfname=tmpfilename;
  846       }
  847 
  848       if (verbose_mode > 1 && !quiet_mode) 
  849         fprintf(LOG_FH,"writing %lu bytes to file: %s\n",
  850             (long unsigned int)outbuffersize, outfname);
  851       if (fwrite(outbuffer,outbuffersize,1,outfile) != 1)
  852         fatal("write failed to file: %s", outfname);
  853       fclose(outfile);
  854     }
  855 
  856     if (outfname) {
  857       
  858       if (preserve_mode) {
  859         /* preserve file modification time */
  860         struct utimbuf time_save;
  861         time_save.actime=file_stat.st_atime;
  862         time_save.modtime=file_stat.st_mtime;
  863         if (utime(outfname,&time_save) != 0) 
  864           warn("failed to reset output file time/date");
  865       }
  866 
  867       if (preserve_perms && !dest) {
  868         /* original file was already replaced, remove backup... */
  869         if (delete_file(tmpfilename))
  870           warn("failed to remove backup file: %s",tmpfilename);
  871       } else {
  872         /* make temp file to be the original file... */
  873 
  874         /* preserve file mode */
  875         if (chmod(outfname,(file_stat.st_mode & 0777)) != 0) 
  876           warn("failed to set output file mode"); 
  877 
  878         /* preserve file group (and owner if run by root) */
  879         if (chown(outfname,
  880               (geteuid()==0 ? file_stat.st_uid : -1),
  881               file_stat.st_gid) != 0)
  882           warn("failed to reset output file group/owner");
  883 
  884         if (verbose_mode > 1 && !quiet_mode) 
  885           fprintf(LOG_FH,"renaming: %s to %s\n",outfname,newname);
  886         if (rename_file(outfname,newname)) fatal("cannot rename temp file");
  887       }
  888     }
  889    } else {
  890      if (!quiet_mode || csv) fprintf(LOG_FH,csv ? "skipped\n" : "skipped.\n");
  891    }
  892    
  893 
  894   } while (++i<argc && !stdin_mode);
  895 
  896 
  897   if (totals_mode && !quiet_mode)
  898     fprintf(LOG_FH,"Average ""compression"" (%ld files): %0.2f%% (%0.0fk)\n",
  899         average_count, average_rate/average_count, total_save);
  900   jpeg_destroy_decompress(&dinfo);
  901   jpeg_destroy_compress(&cinfo);
  902   free(outbuffer);
  903 
  904   return (decompress_err_count > 0 || compress_err_count > 0 ? 1 : 0);;
  905 }
  906 
  907 /* :-) */