/* Copyright (c) 2010 MJ Rutter 
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA  02110-1301, USA.
 */ 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "bmp2eps.h"

/* JPEG reader for bmp2eps
 *
 * MJR 3/01, 3/01, 4/01, 5/02
 * 10/03 added 'support' for exif
 * 5/04 restored incorrectly discarded DRI marker
 * 1/09 added support for reading DPI, and rejigged code somewhat
 * 7/12 checks JPEG for correctly positioned EOI. Now detects
 *        truncated JPEGs, and removes trailing garbage
 */

/* Important markers:
 *
 * ff01   TEM
 * ffc0   SOF   start frame
 * ffc4   DHT   Huffman table
 * ffd0   RST0  restart
 *  ..     ..   markers
 * ffd7   RST7
 * ffd8   SOI   start of image
 * ffd9   EOI   end of image
 * ffdb   DQT   quantisation table
 * ffdd   DRI   restart interval
 * ffe0   APP0
 * ffe1   APP1
 * ffe2   APP2
 * ffdd   DRI   restart information
 * ffda   SOS   start scan
 * fffe   COM   comment
 *
 * A marker is followed by a two byte length. The length includes the length
 * field, but excludes the two-byte marker.
 *
 * The following markers have neither length field nor data: RSTx, TEM, EOI
 *
 * The entropy-encode data which follows the SOS field has no marker or length.
 * The first 0xff byte which is not followed by 0x00 is the marker of the next
 * field...
 */

/* As ever, the caller has swallowed the first three bytes */

#define HEADLEN 8*1024

void jpegread(FILE *jpeg, struct bitmap *image)
{
  int comps=0,prec=0,i,j,debug;

  unsigned int width=0,height=0,save,sos;
  unsigned int len,chunk=64*1024;
  unsigned char *buff,*header,*headp,*headendp,type=0,comp=0,*p;

  debug=image->flags&DEBUG_MASK;

  buff=malloc(chunk);
  header=malloc(HEADLEN);
  headp=header;
  headendp=header+HEADLEN-8;

  if (debug) fprintf(stderr,"JPEG file detected.\n");

  if ((!buff)||(!header)){
    perror("Malloc error\n");
    exit(1);
  }

  *(headp++)=0xff;
  *(headp++)=0xd8;

  do{
    fread(buff,3,1,jpeg);
    type=buff[0];
    len=buff[1]*256+buff[2]-2;
    save=0;
    fread(buff,len,1,jpeg);
    if (debug>2) fprintf(stderr,"Entry type %x length 0x%x\n",(int)type,len+2);
    if ((type==0xe0)&&!strcmp((char*)buff,"JFIF")){
      if (((buff[7]==1)||(buff[7]==2))&&(image->dpi_x==-1)){
	image->dpi_x=buff[8]*256+buff[9];
	image->dpi_y=buff[10]*256+buff[11];
	if (buff[7]==2){ /* units were pixels per cm */
	  image->dpi_x*=25.4;
	  image->dpi_y*=25.4;
	}
      }
      if (debug>1){
	fprintf(stderr,"JPEG version %d.%02d\n",(int)buff[5],(int)buff[6]);
	fprintf(stderr,"JFIF thumbnail size %dx%d\n",
                  (int)buff[12],(int)buff[13]);
        if (image->dpi_x) fprintf(stderr,"DPI: %dx%d\n",image->dpi_x,
				  image->dpi_y);
	fprintf(stderr,"JFIF APP0 entry length 0x%x\n",len+2);
      }
    }
    if ((debug>1)&&(type==0xe1)&&!strcmp((char*)buff,"Exif")){
      fprintf(stderr,"Exif JPEG file\n");
      fprintf(stderr,"Exif APP1 entry length 0x%x\n",len+2);
/*      readexif(buff);  */
    }

    if (((type&0xf0)==0xc0)&&(type!=0xc4)&&(type!=0xcc)){
      if (comp){
	fprintf(stderr,"Confused: multiple images?!\n");
	exit(1);
      }
      comp=type;
      prec=buff[0];
      height=256*buff[1]+buff[2];
      width=256*buff[3]+buff[4];
      comps=buff[5];
      save=1;
    }
    if ((type==0xfe)&&(debug>1)){
      buff[len]=0;
      fprintf(stderr,"JPEG Comment: %s\n",buff);
    }
    if ((type==0xe0)&&(debug>1)){
      fprintf(stderr,"APP0 Marker: %s\n",buff);
    }
    if ((type==0xdd)&&(debug>1)){
      fprintf(stderr,"Restart markers present\n");
    }

    if ((type==0xdb)||(type==0xc4)||(type==0xda)||(type==0xdd)) save=1;
    if (save){
      *(headp++)=0xff;
      *(headp++)=type;
      *(headp++)=(len+2)>>8;
      *(headp++)=(len+2)&0xff;
      if (headp+len>headendp){
	fprintf(stderr,"Header storage exhausted\n");
	exit(1);
      }
      for(i=0;i<len;i++) *(headp++)=buff[i];
    }
    else if (debug>1) fprintf(stderr,"Discarding section.\n");
  } while (type!=0xda && fread(buff,1,1,jpeg) && (buff[0]==0xff));

  if (debug)
    fprintf(stderr,"Image size %dx%d with %d components\n",width,height,comps);

  switch(comp){
  case 0xc0:
    if (debug>1) fprintf(stderr,"Encoding: baseline JPEG\n");
    break;
  case 0xc1:
    if (debug>1) fprintf(stderr,"Encoding: extended sequential Huffman JPEG\n");
    break;
  case 0xc2:
    fprintf(stderr,"Compression: progressive, Huffman JPEG\n"
	    "please convert to baseline JPEG using, e.g., jpegtran\n");
    exit(1);
    break;
  default:
    fprintf(stderr,"Unsupported compression type SOF%d\n",((int)comp)&0xf);
    
    exit(1);
    break;
  }

  if ((comps!=3) && (comps!=1)){
    fprintf(stderr,"Unsupported number of components\n");
    exit(1);
  }

  if (comps==3){
    image->colour=RGB;
    image->depth=24;
  }
  else{
    image->colour=GREY;
    image->depth=8;
  }

  image->width=width;
  image->height=height;
  image->type=DCT;
  image->compression=DCT;

  /* Now read JPEG file. Unfortunately, we don't know how much we'll get */

  if (debug>1) fprintf(stderr,"%d bytes of header read\n",(int)(headp-header));

  for(i=0;header+i<headp;i++) buff[i]=header[i];

  /* Read in whole of JPEG file */

  len=chunk-(headp-header);

  j=fread(buff+i,1,len,jpeg);

  if (j<len){
    image->data_len=i+j;
  }else{
    i=j;
    while(i==len){
      chunk*=2;
      p=realloc(buff,chunk);
      if(!p){
        fprintf(stderr,"Realloc error in jpeg read\n");
        exit(1);
      }
      buff=p;
      len=chunk/2;
      i=fread(buff+len,1,len,jpeg);
    }
    image->data_len=len+i;
  }

  /* Check what we have read for integrity and trailing rubbish 
   * Assume that the first two bytes are 0xffd8. Then we need not deal with
   * awkward markers which are not followed by lengths... */

  i=2;
  sos=0;

  while(i<image->data_len) {
    if (buff[i]!=0xff) {
      fprintf(stderr,"Unexpected data in JPEG - no marker where expected\n");
      if (debug) fprintf(stderr,"Offset %d\n",i);
      exit(1);
    }
    i++;
    if (debug>2) fprintf(stderr,"Check: entry type %x",buff[i]);
    if (buff[i]==0xd9) break; /* End of image */
    if (buff[i]==0xda) sos=1;
    if (buff[i]==0xd8){
      fprintf(stderr,"Unexpected extra SOI in JPEG data.\n");
      exit(1);
    }
    i++;
    len=buff[i]*256+buff[i+1]-2;
    i+=2;
    if (debug>2) fprintf(stderr," length 0x%x\n",len+2);
    i+=len;
    if (sos){ /* We now have an entropy-encoded section of unknown length */
      j=i;
      /* Entropy-encoded sections stop at a marker (0xff), save that they
       * don't for 0xff00, and it is easiest to consider the restart markers
       * 0xffd0 to 0xffd7 to be part of the entropy-encoded section */
      while (!((i>=image->data_len)||
	       ((buff[i]==0xff)&&((buff[i+1]!=0)&&((buff[i+1]&0xf8)!=0xd0)))))
         i++;
      if (debug>2)
        fprintf(stderr,"Check: entropy encoded section %d bytes\n",i-j);
      sos=0;
    }
  }

  if (debug>2) fprintf(stderr,"\n");

  if (buff[i]!=0xd9){
    fprintf(stderr,"Error -- JPEG EOI not found, file corrupted/truncated?\n");
    exit(1);
  }

  if (debug&&(i+1<image->data_len))
    fprintf(stderr,"%d bytes of trailing garbage removed from JPEG\n",
             image->data_len-i-1);

/* Release any extra memory claimed */

  if (i+1<chunk){
    p=realloc(buff,i+1);
    if(!p){
      fprintf(stderr,"Realloc error in jpeg read\n");
      exit(1);
    }
    buff=p;
  }

  image->data=buff;
  image->data_len=i+1;

}
