\/*
* jpegdump_main.cpp -- dump a JPEG stream
* by [email protected] at Tue Jun  4 13:19:00 CEST 2002
*/


/* Structure of a typical JPEG file:
(D8:SOI) (E0:APP0) (FE:COM)? (DB:DQT)*2 (C0:SOF0) (C4:DHT)*4 (DA:SOS)
.. (D9:EOI)
*/

#ifdef __GNUC__
#ifndef __clang__
#pragma implementation
#endif
#endif

#include "config2.h"
#include <stdio.h>

#if OBJDEP
#warning PROVIDES: jpegdump_main
#endif

typedef slen_t dimen_t;
static const unsigned char
   CS_UNKNOWN=0,           /* error/unspecified */
   CS_GRAYSCALE=1,         /* monochrome */
   CS_RGB=2,               /* red/green/blue */
   CS_YCbCr=3,             /* Y/Cb/Cr (also known as YUV) */
   CS_CMYK=4,              /* C/M/Y/K */
   CS_YCCK=5,              /* Y/Cb/Cr/K */
   CS_Indexed_RGB=12;


/* --- The following code is based on standard/image.c from PHP4 */

/* some defines for the different JPEG block types */
#define M_SOF0  0xC0                    /* Start Of Frame N */
#define M_SOF1  0xC1                    /* N indicates which compression process */
#define M_SOF2  0xC2                    /* Only SOF0-SOF2 are now in common use */
#define M_SOF3  0xC3
#define M_SOF5  0xC5                    /* NB: codes C4 and CC are NOT SOF markers */
#define M_SOF6  0xC6
#define M_SOF7  0xC7
#define M_SOF9  0xC9
#define M_SOF10 0xCA
#define M_SOF11 0xCB
#define M_SOF13 0xCD
#define M_SOF14 0xCE
#define M_SOF15 0xCF
#define M_SOI   0xD8
#define M_EOI   0xD9                    /* End Of Image (end of datastream) */
#define M_SOS   0xDA                    /* Start Of Scan (begins compressed data) */
#define M_COM   0xFE                    /* comment */
#define M_DQT   0xDB                    /* QuantTables */
#define M_DHT   0xC4                    /* HuffTables */
#define M_APP0  0xe0
#define M_APP1  0xe1
#define M_APP2  0xe2
#define M_APP3  0xe3
#define M_APP4  0xe4
#define M_APP5  0xe5
#define M_APP6  0xe6
#define M_APP7  0xe7
#define M_APP8  0xe8
#define M_APP9  0xe9
#define M_APP10 0xea
#define M_APP11 0xeb
#define M_APP12 0xec
#define M_APP13 0xed
#define M_APP14 0xee
#define M_APP15 0xef


static unsigned short jai_read2(FILE *fp) {
 unsigned char a[ 2 ];

 /* just return 0 if we hit the end-of-file */
 if (fread(a,sizeof(a),1,fp) != 1) return 0;

 return (((unsigned short) a[ 0 ]) << 8) + ((unsigned short) a[ 1 ]);
}

static unsigned int jai_next_marker(FILE *fp)
  /* get next marker byte from file */
{
 int c;

 /* skip unimportant stuff */

 c = MACRO_GETC(fp);

 while (c != 0xff) {
   if ((c = MACRO_GETC(fp)) == EOF)
     return M_EOI; /* we hit EOF */
 }

 /* get marker byte, swallowing possible padding */
 do {
   if ((c = MACRO_GETC(fp)) == EOF)
     return M_EOI;    /* we hit EOF */
 } while (c == 0xff);

 // printf("marker=%02X\n", c);
 return (unsigned int) c;
}

static void jai_skip_variable(FILE *fp)
  /* skip over a variable-length block; assumes proper length marker */
{
 unsigned short length;

 printf("%lu: skip variable\n", ftell(fp));

 length = jai_read2(fp);
 length -= 2;        /* length includes itself */
 if (length>0) {
   printf("... variable length=%u\n", length);
   #if 0
     fseek(fp, (long) length, SEEK_CUR);  /* skip the header */
   #endif
   while (length--!=0) MACRO_GETC(fp); /* make feof(fp) correct */
   printf("%lu: skipped variable; feof=%u\n", ftell(fp), feof(fp));
 }
}

struct gfxinfo {
 unsigned char bad, bpc, cpp, had_jfif, colortransform, id_rgb;
 dimen_t height, width;
 /** Offset of byte just _after_ the first SOF marker (FF C0 or alike)
  * from the beginning of the file.
  */
 slen_t SOF_offs;
};

/** main loop to parse JPEG structure */
static void jai_handle_jpeg(struct gfxinfo *result, FILE *fp) {
 unsigned int length, marker;
 unsigned char had_adobe, id, hvs, qtn;

 result->bad=1; /* signal invalid return value */
 result->id_rgb=0;
 result->had_jfif=0;
 result->colortransform=127; /* no Adobe marker yet */
 // fseek(fp, 0L, SEEK_SET);    /* position file pointer on SOF */

 if (MACRO_GETC(fp) != 0xFF || MACRO_GETC(fp)!=M_SOI) return; /* JPEG header... */
 printf("2: marker D8 (SOI)\n");

 for (;;) {
   assert(!feof(fp));
   printf("... %lu\n", ftell(fp));
   marker=jai_next_marker(fp);
   printf("%lu: marker %02X\n", ftell(fp), marker);
   switch (marker) {
   case M_SOF0:
     if (result->bad!=1) { result->bad=4; return; } /* only one M_SOF allowed */
     result->SOF_offs=ftell(fp);
     /* handle SOFn block */
     length=jai_read2(fp);
     result->bpc = MACRO_GETC(fp);
     result->height = jai_read2(fp);
     result->width = jai_read2(fp);
     result->cpp = MACRO_GETC(fp);
     if ((length-=8)!=3U*result->cpp) return;
     if (result->cpp==3) {
       result->id_rgb =(id=MACRO_GETC(fp)=='R');  hvs=MACRO_GETC(fp); qtn=MACRO_GETC(fp);
       printf("Comp#1 id=0x%02X h_samp_fact=%u v_samp_fact=%u quant_tbl_no=%u\n",
         id, hvs>>4, hvs&15, qtn);
       result->id_rgb&=(id=MACRO_GETC(fp)=='G');  hvs=MACRO_GETC(fp); qtn=MACRO_GETC(fp);
       printf("Comp#2 id=0x%02X h_samp_fact=%u v_samp_fact=%u quant_tbl_no=%u\n",
         id, hvs>>4, hvs&15, qtn);
       result->id_rgb&=(id=MACRO_GETC(fp)=='B');  hvs=MACRO_GETC(fp); qtn=MACRO_GETC(fp);
       printf("Comp#3 id=0x%02X h_samp_fact=%u v_samp_fact=%u quant_tbl_no=%u\n",
         id, hvs>>4, hvs&15, qtn);
     } else while (length--!=0) MACRO_GETC(fp);
     if (result->cpp!=1 && result->cpp!=3 && result->cpp!=4) {
       result->bad=5; return;
     }
     if (feof(fp)) { result->bad=8; return; }
     result->bad=2;
     break;
   case M_SOF1:
   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, "SOF%u\n", marker-M_SOF0); assert(0);
     result->bad=3;
     return;
   case M_SOS: /* we are about to hit image data. We're done. */
     jai_skip_variable(fp);    /* anything else isn't interesting */
     if (feof(fp)) { result->bad=8; return; }
     if (result->bad==2 && !feof(fp)) result->bad=0;
     /* Dat: we should really return() here, because the SOS marker is
      * followed by binary data, not surrounded by markers.
      */
     break;
   case M_EOI: /* end of image or EOF */
     if (feof(fp)) result->bad=6;
     else if (MACRO_GETC(fp)!=EOF) result->bad=7;
     return;
   case M_APP0: /* JFIF application-specific marker */
     length=jai_read2(fp);
     if (length==2+4+1+2+1+2+2+1+1) {
       result->had_jfif=MACRO_GETC(fp)=='J' && MACRO_GETC(fp)=='F' && MACRO_GETC(fp)=='I' &&
                        MACRO_GETC(fp)=='F' && MACRO_GETC(fp)==0;
       length-=7;
     } else length-=2;
     while (length--!=0) MACRO_GETC(fp);
     if (feof(fp)) { result->bad=8; return; }
     break;
   case M_APP14: /* Adobe application-specific marker */
     length=jai_read2(fp);
     if ((length-=2)==5+2+2+2+1) {
       had_adobe=MACRO_GETC(fp)=='A' && MACRO_GETC(fp)=='d' && MACRO_GETC(fp)=='o' &&
                 MACRO_GETC(fp)=='b' && MACRO_GETC(fp)=='e' && ((unsigned char)MACRO_GETC(fp))>=1;
       MACRO_GETC(fp); MACRO_GETC(fp); MACRO_GETC(fp); MACRO_GETC(fp); MACRO_GETC(fp);
       if (had_adobe) result->colortransform=MACRO_GETC(fp);
                 else MACRO_GETC(fp);
     } else while (length--!=0) MACRO_GETC(fp);
     if (feof(fp)) { result->bad=8; return; }
     break;
   case M_APP1:
   case M_APP2:
   case M_APP3:
   case M_APP4:
   case M_APP5:
   case M_APP6:
   case M_APP7:
   case M_APP8:
   case M_APP9:
   case M_APP10:
   case M_APP11:
   case M_APP12:
   case M_APP13:
   case M_APP15:
     /* fall through */
   default:
     jai_skip_variable(fp);    /* anything else isn't interesting */
     if (feof(fp)) { result->bad=8; return; }
     break;
 } }
}

static char *jai_errors[]={
 (char*)NULLP,
 /*1*/ "missing SOF0 marker",
 /*2*/ "EOF|EOI before SOS", /* "premature EOF", */
 /*3*/ "not a Baseline JPEG (SOF must be SOF0)",
 /*4*/ "more SOF0 markers",
 /*5*/ "bad # components",
 /*6*/ "EOF before EOI",
 /*7*/ "extra bytes after EOI",
 /*8*/ "EOF before end of parametric marker",
};

int main(int argc, char **argv) {
 struct gfxinfo gi;
 FILE *f=argc>=2 ? fopen(argv[1], "rb"): stdin;
 jai_handle_jpeg(&gi, f);
 if (gi.bad==1) {
   printf("JAI: Warning: %s.\n", jai_errors[gi.bad]);
   gi.cpp=1;
   gi.width=0;
   gi.height=0;
   gi.SOF_offs=0;
   gi.bpc=0;
 } else if (gi.bad!=0) { printf("JAI: %s.\n", jai_errors[gi.bad]); if (gi.bad!=1) return 1; }
 unsigned char colorspace;
 if (gi.cpp==1) {
   colorspace=CS_GRAYSCALE;
 } else if (gi.cpp==3) {
   colorspace=CS_YCbCr;
   if (gi.had_jfif!=0) ;
   else if (gi.colortransform==0) colorspace=CS_RGB;
   else if (gi.colortransform==1) ;
   else if (gi.colortransform!=127) { printf("JAI: unknown ColorTransform: %u\n", (unsigned)gi.colortransform); return 2; }
   else if (gi.id_rgb!=0) colorspace=CS_RGB;
   /* Imp: check for id_ycbcr */
   else printf("JAI: assuming YCbCr color space\n");
 } else if (gi.cpp==4) {
   colorspace=CS_CMYK;
   if (gi.colortransform==0) ;
   else if (gi.colortransform==2) colorspace=CS_YCCK;
   else if (gi.colortransform!=127) { printf("JAI: unknown ColorTransform: %u\n", (unsigned)gi.colortransform); return 2; }
 } else assert(0);
 fseek(f, 0L, 2); /* EOF */
 long flen=ftell(f); /* skip extra bytes after EOI */
 fclose(f);
 printf("%lu: flen\n", flen);
 printf("bpc=%u cpp=%u had_jfif=%u colortransform=%u id_rgb=%u colorspace=%u\n",
   gi.bpc, gi.cpp, gi.had_jfif, gi.colortransform, gi.id_rgb, colorspace);
 printf("wd=%u ht=%u SOF_offs=%u\n", gi.width, gi.height, gi.SOF_offs);
 return 0;
}

/* __EOF__ */