/* ------------------------- readjpeg.c ------------------------- */
#include <stdio.h>
#include <string.h>

#ifndef DOS
#include <unistd.h>
#endif

#include "psimage.h"

/* The following enum is stolen from the IJG JPEG library
* Comments added by tm
* This table contains far too many names since jpeg2ps
* is rather simple-minded about markers
*/

extern BOOL quiet;

typedef enum {          /* JPEG marker codes                    */
 M_SOF0  = 0xc0,       /* baseline DCT                         */
 M_SOF1  = 0xc1,       /* extended sequential DCT              */
 M_SOF2  = 0xc2,       /* progressive DCT                      */
 M_SOF3  = 0xc3,       /* lossless (sequential)                */

 M_SOF5  = 0xc5,       /* differential sequential DCT          */
 M_SOF6  = 0xc6,       /* differential progressive DCT         */
 M_SOF7  = 0xc7,       /* differential lossless                */

 M_JPG   = 0xc8,       /* JPEG extensions                      */
 M_SOF9  = 0xc9,       /* extended sequential DCT              */
 M_SOF10 = 0xca,       /* progressive DCT                      */
 M_SOF11 = 0xcb,       /* lossless (sequential)                */

 M_SOF13 = 0xcd,       /* differential sequential DCT          */
 M_SOF14 = 0xce,       /* differential progressive DCT         */
 M_SOF15 = 0xcf,       /* differential lossless                */

 M_DHT   = 0xc4,       /* define Huffman tables                */

 M_DAC   = 0xcc,       /* define arithmetic conditioning table */

 M_RST0  = 0xd0,       /* restart                              */
 M_RST1  = 0xd1,       /* restart                              */
 M_RST2  = 0xd2,       /* restart                              */
 M_RST3  = 0xd3,       /* restart                              */
 M_RST4  = 0xd4,       /* restart                              */
 M_RST5  = 0xd5,       /* restart                              */
 M_RST6  = 0xd6,       /* restart                              */
 M_RST7  = 0xd7,       /* restart                              */

 M_SOI   = 0xd8,       /* start of image                       */
 M_EOI   = 0xd9,       /* end of image                         */
 M_SOS   = 0xda,       /* start of scan                        */
 M_DQT   = 0xdb,       /* define quantization tables           */
 M_DNL   = 0xdc,       /* define number of lines               */
 M_DRI   = 0xdd,       /* define restart interval              */
 M_DHP   = 0xde,       /* define hierarchical progression      */
 M_EXP   = 0xdf,       /* expand reference image(s)            */

 M_APP0  = 0xe0,       /* application marker, used for JFIF    */
 M_APP1  = 0xe1,       /* application marker                   */
 M_APP2  = 0xe2,       /* application marker                   */
 M_APP3  = 0xe3,       /* application marker                   */
 M_APP4  = 0xe4,       /* application marker                   */
 M_APP5  = 0xe5,       /* application marker                   */
 M_APP6  = 0xe6,       /* application marker                   */
 M_APP7  = 0xe7,       /* application marker                   */
 M_APP8  = 0xe8,       /* application marker                   */
 M_APP9  = 0xe9,       /* application marker                   */
 M_APP10 = 0xea,       /* application marker                   */
 M_APP11 = 0xeb,       /* application marker                   */
 M_APP12 = 0xec,       /* application marker                   */
 M_APP13 = 0xed,       /* application marker                   */
 M_APP14 = 0xee,       /* application marker, used by Adobe    */
 M_APP15 = 0xef,       /* application marker                   */

 M_JPG0  = 0xf0,       /* reserved for JPEG extensions         */
 M_JPG13 = 0xfd,       /* reserved for JPEG extensions         */
 M_COM   = 0xfe,       /* comment                              */

 M_TEM   = 0x01,       /* temporary use                        */

 M_ERROR = 0x100       /* dummy marker, internal use only      */
} JPEG_MARKER;

/*
* The following routine used to be a macro in its first incarnation:
*  #define get_2bytes(fp) ((unsigned int) (getc(fp) << 8) + getc(fp))
* However, this is bad programming since C doesn't guarantee
* the evaluation order of the getc() calls! As suggested by
* Murphy's law, there are indeed compilers which produce the wrong
* order of the getc() calls, e.g. the Metrowerks C compilers for BeOS
* and Macintosh.
* Since there are only few calls we don't care about the performance
* penalty and use a simplistic C function.
*/

/* read two byte parameter, MSB first */
static unsigned int
get_2bytes(FILE *fp)
{
   unsigned int val;
   val = getc(fp) << 8;
   val += getc(fp);
   return val;
}

static int
next_marker P1(FILE *, fp)
{ /* look for next JPEG Marker  */
 int c, nbytes = 0;

 if (feof(fp))
   return M_ERROR;                 /* dummy marker               */

 do {
   do {                            /* skip to FF                 */
     nbytes++;
     c = getc(fp);
   } while (c != 0xFF);
   do {                            /* skip repeated FFs          */
     c = getc(fp);
   } while (c == 0xFF);
 } while (c == 0);                 /* repeat if FF/00            */

 return c;
}

/* analyze JPEG marker */
BOOL AnalyzeJPEG P1(imagedata *, image) {
 int b, c, unit;
 unsigned long i, length = 0;
#define APP_MAX 255
 unsigned char appstring[APP_MAX];
 BOOL SOF_done = FALSE;

 /* Tommy's special trick for Macintosh JPEGs: simply skip some  */
 /* hundred bytes at the beginning of the file!                  */
 do {
   do {                            /* skip if not FF             */
     c = getc(image->fp);
   } while (!feof(image->fp) && c != 0xFF);

   do {                            /* skip repeated FFs          */
     c = getc(image->fp);
   } while (c == 0xFF);

   /* remember start position */
   if ((image->startpos = ftell(image->fp)) < 0L) {
     fprintf(stderr, "Error: internal error in ftell()!\n");
     return FALSE;
   }
   image->startpos -= 2;           /* subtract marker length     */

   if (c == M_SOI) {
     fseek(image->fp, image->startpos, SEEK_SET);
     break;
   }
 } while (!feof(image->fp));

 if (feof(image->fp)) {
   fprintf(stderr, "Error: SOI marker not found!\n");
   return FALSE;
 }

 if (image->startpos > 0L && !quiet) {
   fprintf(stderr, "Note: skipped %ld bytes ", image->startpos);
   fprintf(stderr, "Probably Macintosh JPEG file?\n");
 }

 /* process JPEG markers */
 while (!SOF_done && (c = next_marker(image->fp)) != M_EOI) {
   switch (c) {
     case M_ERROR:
       fprintf(stderr, "Error: unexpected end of JPEG file!\n");
       return FALSE;

     /* The following are not officially supported in PostScript level 2 */
     case M_SOF2:
     case M_SOF3:
     case M_SOF5:
     case M_SOF6:
     case M_SOF7:
     case M_SOF9:
     case M_SOF10:
     case M_SOF11:
     case M_SOF13:
     case M_SOF14:
     case M_SOF15:
       fprintf(stderr,
        "Warning: JPEG file uses compression method %X - proceeding anyway.\n",
                 c);
       fprintf(stderr,
               "PostScript output does not work on all PS interpreters!\n");
       /* FALLTHROUGH */

     case M_SOF0:
     case M_SOF1:
       length = get_2bytes(image->fp);    /* read segment length  */

       image->bits_per_component = getc(image->fp);
       image->height             = get_2bytes(image->fp);
       image->width              = get_2bytes(image->fp);
       image->components         = getc(image->fp);

       SOF_done = TRUE;
       break;

     case M_APP0:              /* check for JFIF marker with resolution */
       length = get_2bytes(image->fp);

       for (i = 0; i < length-2; i++) {        /* get contents of marker */
         b = getc(image->fp);
         if (i < APP_MAX)                      /* store marker in appstring */
           appstring[i] = b;
       }

       /* Check for JFIF application marker and read density values
        * per JFIF spec version 1.02.
        * We only check X resolution, assuming X and Y resolution are equal.
        * Use values only if resolution not preset by user or to be ignored.
        */

#define ASPECT_RATIO    0       /* JFIF unit byte: aspect ratio only */
#define DOTS_PER_INCH   1       /* JFIF unit byte: dots per inch     */
#define DOTS_PER_CM     2       /* JFIF unit byte: dots per cm       */

       if (image->dpi == DPI_USE_FILE && length >= 14 &&
           !strncmp((const char *)appstring, "JFIF", 4)) {
         unit = appstring[7];                  /* resolution unit */
                                               /* resolution value */
         image->dpi = (float) ((appstring[8]<<8) + appstring[9]);

         if (image->dpi == 0.0) {
           image->dpi = DPI_USE_FILE;
           break;
         }

         switch (unit) {
           /* tell the caller we didn't find a resolution value */
           case ASPECT_RATIO:
             image->dpi = DPI_USE_FILE;
             break;

           case DOTS_PER_INCH:
             break;

           case DOTS_PER_CM:
             image->dpi *= (float) 2.54;
             break;

           default:                            /* unknown ==> ignore */
             fprintf(stderr,
               "Warning: JPEG file contains unknown JFIF resolution unit - ignored!\n");
             image->dpi = DPI_IGNORE;
             break;
         }
       }
       break;

     case M_APP14:                             /* check for Adobe marker */
       length = get_2bytes(image->fp);

       for (i = 0; i < length-2; i++) {        /* get contents of marker */
         b = getc(image->fp);
         if (i < APP_MAX)                      /* store marker in appstring */
           appstring[i] = b;
       }

       /* Check for Adobe application marker. It is known (per Adobe's TN5116)
        * to contain the string "Adobe" at the start of the APP14 marker.
        */
       if (length >= 12 && !strncmp((const char *) appstring, "Adobe", 5))
         image->adobe = TRUE;                  /* set Adobe flag */

       break;

     case M_SOI:               /* ignore markers without parameters */
     case M_EOI:
     case M_TEM:
     case M_RST0:
     case M_RST1:
     case M_RST2:
     case M_RST3:
     case M_RST4:
     case M_RST5:
     case M_RST6:
     case M_RST7:
       break;

     default:                  /* skip variable length markers */
       length = get_2bytes(image->fp);
       for (length -= 2; length > 0; length--)
         (void) getc(image->fp);
       break;
   }
 }

 /* do some sanity checks with the parameters */
 if (image->height <= 0 || image->width <= 0 || image->components <= 0) {
   fprintf(stderr, "Error: DNL marker not supported in PostScript Level 2!\n");
   return FALSE;
 }

 /* some broken JPEG files have this but they print anyway... */
 if (length != (unsigned int) (image->components * 3 + 8))
   fprintf(stderr, "Warning: SOF marker has incorrect length - ignored!\n");

 if (image->bits_per_component != 8) {
   fprintf(stderr, "Error: %d bits per color component ",
       image->bits_per_component);
   fprintf(stderr, "not supported in PostScript level 2!\n");
   return FALSE;
 }

 if (image->components!=1 && image->components!=3 && image->components!=4) {
   fprintf(stderr, "Error: unknown color space (%d components)!\n",
     image->components);
   return FALSE;
 }

 return TRUE;
}