{$A+,B-,D+,E+,F-,G+,I+,L+,N-,O-,P-,Q+,R+,S+,T-,V-,X-}
{:
PREFACE:

A basic jpeg/jfif file is compossed of subsections which I will call
segments. Each segment has a header ID. Some headers have information
blocks following the header, while some don not. Basically, the
stucture can be thought of as follows:  (ID [info.], ... , ID [info.]).
So, a segment can be compossed of only a header or a header with
data following.

Unlike most structures, the segments do not have a predefined order;
therefor, it is maditory to read the ID header first then treat the
following data occurding to its header. Remember, certain headers
do not have any following data - I will label these in the code.

The only predifined structure that a jpeg/jfif has is the following:
The file starts with the SOI(start of information) header and is
followed by the app0 segment. Followed by any number of other segments,
followed by the EOI(end of information) segment.

arrangment:  SOI (start of information)
             app0
             [ ... unknown ... ]
             EOI (end of information)


The usual arrangment that I have found in most jpeg/jfif files is as
follows:
             SOI    (start of information)
             app0   (JFIF label w/ image description)
             DQT    (Define quantization table)
             sof0   (start of frame)
             DHT    (Define huffman table)
             DHT    (Define huffman table)
             DHT    (Define huffman table)
             DHT    (Define huffman table)
             SOS    (Start of scan)
       NOTE: dqt,sof0, & dht don't always appear in this order.

Description:
    SOI:  just a "dumb" header - no information follows.

    app0: for jfif files - JFIF id, version #, unit,
          x & y density, thumbnail info & thumbnail (if any)
          SEE app0_info TYPE

    DQT:  defines 8x8 table used for quantization

    sof0: image height & width & color components
          jfif support (1) Y or (3) Y Cb Cr
          a yuv_to_rbg function is in this unit.

    DHT: Set's up all the huffman tables used to decompress
         the image.

    SOS: defines AC & DC huffman tables to use for each color component

Headers:
Headers are 2 bytes, in hexidecimal "FF" followed by the ID byte.
For ease of tranlastion, I have set up a list of constants to
determine the header ID (eg. sof0,dht,sos,..., ect.)
ALL headers with infomation following have a word that tells the
length of the data following. The word is not in the intel lo-hi
format, so I included a HI_LO function for translation.
To read any segment you only need to the following:
   Read the header, identify it.
   Read the hi-lo word for length, translate HI_LO().
   Read "length" bytes of data.
   NOTE: Not all headers have a length or data block.


THIS FILE----------------------------------------------

This unit is design specifically for debuging and block testing. What
this means is that no actual file needs to be used to test and debug
each segment handling procedure. The driver file is responsible for
header identification, file reading, and so on.

To process an information block, read the header & identify it.
Read the length, subtract 2 from it and save it. Read "length" bytes
into a block (array of bytes). Now, give the appropriete procedure
the block, length, and a predefined type (Quan_tables, Huff_tables,
app0_info, frame_0, scan_info).

}

UNIT jpegsegm; { see TEST program at the bottom ! }

INTERFACE
  CONST TEM  = $01;            {unknown}
      SOF0 = $c0;            {start of FRAME}
      SOF1 = $c1;            {""""""""""""""}
      SOF2 = $c2;            {following SOF usually unsupported}
      SOF3 = $c3;
      SOF5 = $c2;
      SOF6 = $c6;
      SOF7 = $c7;
      SOF9 = $c9;            {sof9 : for arithmetic coding - taboo!}
      SOF10= $ca;
      SOF11= $cb;
      SOF13= $cd;
      SOF14= $ce;
      SOF15= $cf;
      DHT  = $c4;            {Define huffman Table}
      JPG  = $c8;            {undefined/ reserved =Error?}
      DAC  = $cc;            {define arithmetic table UNSUPORTED }
      RST0 = $d0;            {Used for resync [?] ignored}
      rst1 = $d1;
      rst2 = $d2;
      rst3 = $d3;
      rst4 = $d4;
      rst5 = $d5;
      rst6 = $d6;
      rst7 = $d7;
      SOI  = $d8;            {start of image}
      EOI  = $d9;            {end   of image}
      SOS  = $da;            {start of scan }
      DQT  = $db;            {Define Quantization Table}
      DNL  = $dc;            {unknown -usually unsupported}
      DRI  = $dd;            {Define Restart Interval}
      DHP  = $de;            {ignore }
      EXP  = $df;
      APP0 = $e0;            {JFIF app0 segment marker}
      APP15= $ef;            {ignore}
      JPG0 = $f0;
      JPG13= $fd;
      COM  = $fe;            {Comment}

  {: Do App0 :}

  TYPE app0_info = record
                   revision   : record
                                  major,         {>= 1}
                                  minor : byte;
                                end;
                   XY_density : byte;
                   X,Y        : word;
                   thumb_X,
                   thumb_y    : byte;
                 end;

  {: Define Quantization Table :}

  TYPE Q_byte      = array[0..7,0..7] of byte;
       Q_word      = array[0..7,0..7] of word;
       Q_type_type = (bit_8,bit_16);
       Quan_range = 0..3;
       Quan_tables = array[Quan_range] of
                     record
                      Valid  : Boolean;
                      Q_TYPE : Q_type_type;
                      Q      : record
                                 case integer of
                                  1 : (Q_byte : array[0..7,0..7] of byte);
                                  2 : (Q_word : array[0..7,0..7] of word);
                                 end;
                     end;
       One_quan_table = record
                        case integer of
                         1 : (Q_int  : array[0..7,0..7] of Integer);
                         2 : (Q_long : array[0..7,0..7] of Longint);
                        end;

  {: Define Huffman Table :}

  TYPE huff_type   = (AC, DC);
       Huff_range  = 0..3;
       Huff_tables = array[huff_type] of
                     record
                       Table : array[Huff_range] of
                       record
                         valid     : boolean;
                         H_type    : huff_type;
                         Max_code  : array[1..16] of byte;
                         H         : array[1..257] of
                                     record
                                       len  : byte;
                                       code : word;
                                       sym  : byte;
                                     end;
                       end;
                     end;

  {: Start of Frame :}

  type  id_type   = (no_id, Y_, CB_, CR_, I_, Q_);
        comp_type = (grey, no_comp, color);
        frame_0 = record
                   precision    : byte;
                   image_height : word;
                   image_width  : word;
                   comp_num     : comp_type;
                   factor : array[1..3] of
                            record
                              id    : id_type;
                              horz_factor   : byte;
                              vert_factor   : byte;
                              Q_num : byte;
                            end;
                  end;

  {: Start of Scan :}

  type comp_range = 1..4;
       scan_info = record
                     comp_num : comp_range;
                     Each : array[comp_range] of
                            record
                              valid   : boolean;
                              id      : id_type;
                              huff_ac : huff_range;
                              huff_dc : huff_range;
                            end;
                   end;


  PROCEDURE DO_sof0(VAR block : array of byte; Len : word;
                    VAR Frame : frame_0);
  Function  DO_app0(VAR block : array of byte; Len : word;
                   VAR info : app0_info ) : boolean;
  PROCEDURE DO_DQT(VAR block : array of byte; Len : word;
                 VAR All_DQT : Quan_tables);
  PROCEDURE DO_sos(VAR block : array of byte; Len : word;
                   Var Scan : Scan_info);
  PROCEDURE DO_DHT(VAR block : array of byte; Len : word;
                 VAR all_dht : huff_tables);

  PROCEDURE DO_DRI; {unknown}

  procedure DeQuantize( VAR Q : Quan_tables; Num : byte;
                        VAR in_q : one_quan_table);
  procedure IDCT(VAR one : one_quan_table);

  FUNCTION  HI_LO(inw : word) : word;

IMPLEMENTATION

{::::::::::::::::::::::::::::::::::}
{: Change a HI-LO word to LO-HIGH :}
{::::::::::::::::::::::::::::::::::}

  FUNCTION  HI_LO(inw : word) : word;
  var dwd : word;
  begin
    dwd := 0;
    dwd := inw SHR 8;
    dwd := dwd OR (inw SHL 8);
    Hi_lo := dwd;
  end;


  procedure yuv_to_RGB( Y,CB,Cr : integer; VAR R,G,B : byte);
  begin
    r := trunc(y + 1.402 *(Cr-128));
    g := trunc(y - 0.34414 * (cb-128) - 0.71414*(cr-128));
    b := trunc(y + 1.772*(Cb-128));
  end;

{::::::::::::::}
{: Dequantize :}
{::::::::::::::}
{: component wise multiplication of 2 8x8 matricies :}
{: where b[x,y] = q[x,y] * a[x,y]                   :}
{check}

  procedure DeQuantize( VAR Q : Quan_tables; Num : byte;
                        VAR in_q : one_quan_table);
  var i,j : byte;
  begin
    with Q[num] do begin
         case q_type of
          bit_8  : begin
                    for I := 0 to 7 do
                        for j := 0 to 7 do
                        in_q.q_int[i,j] := in_q.q_int[i,j] * Q.Q_byte[i,j];
                   end;
          bit_16 : begin
                    for I := 0 to 7 do
                        for j := 0 to 7 do
                        in_q.q_long[i,j] := in_q.q_long[i,j] * Q.Q_word[i,j];
                   end;
         end;
    end;
  end;

{:::::::::::::::}
{: Inverse DCT :}
{:::::::::::::::}
                  {u}   {v}
  const C : array [0..7,0..7] of real =
  ((0.5, 0.707106781188, 0.707106781188, 0.707106781188,
    0.707106781188, 0.707106781188, 0.707106781188, 0.707106781188),
   (0.707106781188, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
   (0.707106781188, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
   (0.707106781188, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
   (0.707106781188, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
   (0.707106781188, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
   (0.707106781188, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0),
   (0.707106781188, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0));

  procedure IDCT(VAR one : one_quan_table);
  var u,v,
      x,y  : byte;
      suma,
      sumb : real;
      temp_q : one_quan_table;
  begin
    for y := 0 to 7 do begin
        for x := 0 to 7 do begin
  
            suma := 0;
            for u := 0 to 7 do begin
                sumb := 0;
                for v := 0 to 7 do begin
                    sumb := sumb + (c[u,v] * one.q_int[u,v] *
                                    cos( ((2*x+1) * u * pi) / 16 ) *
                                    cos( ((2*y+1) * v * pi) / 16 ));
                end;
                suma := suma + sumb;
            end;
            suma := suma * 0.25;
            temp_q.q_int[x,y] := trunc(suma) + 120;
        end;
    end;
  
    for y := 0 to 7 do begin
        for x := 0 to 7 do begin
            one.q_int[x,y] :=  temp_q.q_int[x,y];
        end;
    end;

  end;

{:::::::::::::::::::::::}
{: JFIF Segment marker :}
{:::::::::::::::::::::::}

{: IF JFIF+#0 does not follow the header, then skip by LEN - 7.  :}
{: Two bytes have ben passed to read LEN, and five to read JFIF0 :}

  Function DO_app0(VAR block : array of byte; Len : word;
                   VAR info : app0_info ) : boolean;
  const string_len = 5;
  VAR Jfif_ID : STRING; {JFIF + #0}
      Cur    : word;
  BEGIN
    cur := 0;
  
    move(block[cur], Jfif_ID[1], string_len); Jfif_ID[0]:= chr(string_len);
    inc(cur, string_len);
    Len := Len - string_len;
  
    IF  (Jfif_ID<>('JFIF'+#0)) then begin
        {Bskip(F, len);}
        do_app0 := false;
    end ELSE BEGIN
  
  
        move(block[cur], Info, SizeOf(Info));
        inc(cur, SizeOf(Info));
        dec(Len, SizeOf(Info));
        if  info.revision.major < 1 then begin
            {writeln(DE,' Invalid Revision version.');}
            exit;
        end;
  
  
  
        IF  (info.thumb_x * info.thumb_Y * 3 <> 0) then begin
            {Bskip(f, info.thumb_x * info.thumb_Y * 3);}
            {the thumbnail N bytes; RGB 24bit W*H*3}
            len := len - (info.thumb_x * info.thumb_Y * 3);
        end;
        if  Len = 0 then
            do_app0 := True
        else
            do_app0 := False;
    END;

  END;

{:::::::::::::::::::::::::::::}
{: Define Quantization Table :}
{:::::::::::::::::::::::::::::}


PROCEDURE DO_DQT(VAR block : array of byte; Len : word;
                   VAR All_DQT : Quan_tables);
  {might work in all cases}
  VAR k,l  : byte;

      QT_info : byte;
      QT_prec : byte;
      QT_num  : byte;
  
      Cur     : word;
  BEGIN
    Cur := 0;
  
    repeat
      {::::::::::::::::}
      {: Read QT Info :}
      {::::::::::::::::}
  
      { Set all_dqt[ QT_num ]       }
      {                      .Valid }
      {                      .type  }
  
      Qt_info := Block[cur]; Inc(cur);  Len := Len -1;
      QT_num  := Qt_info and $0F;
      QT_prec := (Qt_info and $f0) shr 4;
  
      {:::::::::::::::::::}
      {: Read in Q table :}
      {:::::::::::::::::::}
  
      { Set all_dqt[ QT_num ]       }
      {                      .Q[]   }
  
      with all_dqt[ QT_num ] do begin
           valid := True;
           case QT_prec of
            0 : begin
                  Q_type := (bit_8);
                  move(block[cur], Q.Q_byte, Sizeof(Q_byte));
                  inc(cur, sizeof(Q_byte));
                  Len := Len -  SizeOf(Q_byte);
  
                end;
            1 : begin
                  Q_type := (bit_16);
                  move(block[cur], Q.Q_word, Sizeof(Q_word));
                  inc(cur, sizeof(Q_word));
                  Len := Len -  SizeOf(Q_word);
                end;
            ELSE BEGIN
                   {writeln(DE,'Invalid QT_precison in DO_DQT');}
                   halt(1);
                 END;
            END;
      END;
    until (len = 0);
  END;
  


  {:::::::::::::::::}
  {: Start Of Scan :}
  {:::::::::::::::::}

  PROCEDURE do_sos(VAR block : array of byte; Len : word;
                   Var Scan : Scan_info);
  var k,dw : word;   Done: boolean;
      db       : byte;
      Cur      : word;
  begin
    Cur := 0;
    with Scan do begin
         Comp_num := Block[cur]; inc(cur); dec(len);
         for K := 1 to Comp_num do begin
             Each[ K ].valid := true;

             Each[ K ].ID := id_type(block[cur]); inc(cur); dec(len);

             DB := block[cur]; inc(cur); dec(len);
             Each[ K ].huff_ac := db and $f;
             Each[ K ].huff_dc := (db and $f0) shr 4;
         end;
    end;
  end;


{::::::::::::::::::::::::}
{: Define Huffman Table :}
{::::::::::::::::::::::::}

  CONST Huff_mask : array[1..16] of
                  word =(  $01,  $03,  $07,  $0f,
                           $1f,  $3f,  $7f,  $ff,
                         $01ff,$03ff,$07ff,$0fff,
                         $1fff,$3fff,$7fff,$ffff);

  PROCEDURE DO_DHT(VAR block : array of byte; Len : word;
                   VAR all_dht : huff_tables);
  
  VAR DW : Word;
    j,k,l,
      cur     : byte;
      Sum     : word;
      Size    : byte;
      code    : word;
      lenths  : array[1..16] of byte;
  
      HT_info : byte;
      HT_num  : byte;
      HT_type : byte;
      {DW      : word;}
  BEGIN
  
    Cur := 0;
    Repeat
      {::::::::::::::::::}
      {: Read Huff Info :}
      {::::::::::::::::::}
  
      { SET ALL_DHT[HT_NUM]          }
      {                   .Valid     }
      {                   .H_type    }
  
      ht_info := block[cur]; inc(cur);
      Len := Len - 1;
      HT_num  := HT_info and $F;
      HT_type := (HT_info and $F0) shr 4;
  
      with all_dht[ huff_type(HT_TYPE) ].Table[ HT_num ] do begin
           Valid  := True;
           case HT_type of
            0 : H_type := DC;
            1 : H_type := AC;
           else begin
                  {writeln(DE,'Invalid Huffman table type.');}
                  halt(1);
                end;
           end;
      end;

      {$IFDEF DEBUG } writeln(DE,'-- HT num  : ',HT_num);
                      writeln(DE,'-- HT type : ',HT_type);
      {$ENDIF}
  
      {::::::::::::::::::}
      {: Read in lenths :}
      {::::::::::::::::::}
  
      move(block[cur], Lenths[1], 16); inc(cur,16);
      Len := Len - 16;
  
      {::::::::::::::::::::::}
      {: Read in symbols    :}
      {: partially borrowed :}
      {::::::::::::::::::::::}
  
      { SET ALL_DHT[HT_NUM]          }
      {                   .Valid     }
      {                   .H[].Len   }
      {                   .H[].Sym   }
      {                   .Max_code  }
  
  
  
      with all_dht[ huff_type(HT_TYPE) ].Table[ HT_num ] do begin
           L   := 1;
           sum := 0;
           For k := 1 to 16 do begin
  
               Sum := Sum + lenths[k];
               for j := 1 to lenths[k] do begin      {: if 0 then skipped   :}
                   H[L] .len := K;
  
                   H[L] .sym := block[cur];inc(cur); {: read in symbols     :}
                                                     {: as we go            :}
                   Len := Len - 1;
                   inc(L);
               end;
               Max_code[k] := L;
           end;
           H[L] .len := 0;                        {: Last will have Zero :}
      end;
  
  
      {::::::::::::::::::::::::}
      {: Create huffman Codes :}
      {: partially borrowed   :}
      {::::::::::::::::::::::::}

      { Set all_dht[HT_NUM]. H[].CODE }


      with all_dht[ huff_type(HT_TYPE) ].Table[ HT_num ] do begin
           L    := 1;
           Size := H[1].len;
           code := 0;
           while (H[L].len <> 0) do begin
                 while (H[L].len = Size) do begin
                       H[L].code := Huff_mask[ H[L].Len] and Code;
                       inc(L);
                       inc(Code);
                 end;
                 code := code shl 1;
                 inc(Size);
           end;
      end;
  
    until (Len = 0);
  END;
  

  PROCEDURE DO_DRI;
  VAR Len, dw2,dw: word;
  BEGIN
  END;





  PROCEDURE DO_sof0(VAR block : array of byte; Len : word;
                    VAR Frame : frame_0);
  VAR K,dw : word;
      db    : byte;

      Cur      : word;
  BEGIN
    cur := 0;

    with frame do begin
         precision    := block[cur]; inc(cur); dec(len);

         move(block[cur], image_height, 2); inc(cur,2); dec(len,2);
         image_height := hi_lo(image_height);

         move(block[cur], image_width, 2); inc(cur,2); dec(len,2);
         image_width := hi_lo(image_width);

         dw := block[cur]; inc(cur); dec(len);
         case dw of
          1 : begin
                comp_num := grey;
              end;
          3 : begin
                comp_num := color;
              end;
         else begin
                writeln('SOF0: component not supported.');
                halt(1);
              end;
         end;

         for K := 1 to DW do begin
             with frame.factor[K] do begin
                  db := block[cur]; Inc(cur); dec(len);
                  case db of
                   1 : ID := Y_;
                   2 : ID := CB_;
                   3 : ID := CR_;
                   4 : ID := I_;
                   5 : ID := Q_;
                  end;

                  db := block[cur]; Inc(cur); dec(len);
                  horz_factor := db and $f;
                  vert_factor := (db and $f0) shr 4;
                  q_num       := block[cur]; Inc(cur); dec(len);
             end; {with}
         end;
    end;
  END;
END.

{ ---------------------   TEST PROGRAM -------------------- }

{$A+,B-,D+,E+,F-,G+,I-,L+,N-,O-,P-,Q+,R+,S+,T-,V-,X-}

program tjpeg;
uses jpegsegm;


var f      : file;
    dw,
    length : word;
    db     : byte;
    darray : array[0..2047] of byte;
    ai     : app0_info;
    qts    : quan_tables;
    hts    : huff_tables;
    si     : scan_info;
    f0     : frame_0;
begin
  {:::::::::::::::::::::::::::::}
  {: Required JPEG/JFIF format :}
  {:::::::::::::::::::::::::::::}

  {: open file :}
  assign(F, paramstr(1)); filemode := 0;
  reset(f,1);
  if  (IOResult <> 0) then begin
      writeln('Syntax:  tjepg [filename]');
      writeln('Unable to open file: ', paramstr(1));
      halt(1);
  end;

  {: read soi header :}
  blockread(f, db, 1);
  blockread(f, db, 1);
  if  (db <> SOI) then begin
      writeln('File is missing required SOI header.');
      halt(1);
  end;

  {: read app0 block length :}
  blockread(f, db, 1);
  blockread(f, db, 1);
  if  (db <> app0) then begin
      writeln('File is missing reqired app0 header.');
      halt(1);
  end;

  {: read app0 block :}
  blockread(f, length, 2);
  length := hi_lo(length) - 2;

  blockread(f, darray, length);
  if  not do_app0( darray, length, ai) then begin
      writeln('Missing JFIF marked app0 segment.');
      halt(1);
  end;


  {::::::::::::::::::::::::::::::}
  {: process remaining segments :}
  {::::::::::::::::::::::::::::::}
  repeat
    blockread(f, db, 1, dw); {must be FF} if dw <> 1 then halt(2);
    blockread(f, db, 1, dw); {header ID } if dw <> 1 then halt(2);

    blockread(f, length, 2, dw);
    length := hi_lo(length) - 2;
    if  db in [dht,dqt,sof0,sos] then
        blockread(f, darray[0], length, dw);
    if dw <> length then halt(3);
    case db of
      dht  : do_dht (darray, length, hts);
      dqt  : do_dqt (darray, length, qts);
      sof0 : do_sof0(darray, length, f0);
      sos  :
      begin
        do_sos (darray, length, si);
        writeln('app0 information');
        writeln('  version : ',ai.revision.major,'.',ai.revision.minor);
        writeln('  xy_density units(0-2): ',ai.xy_density);
        writeln('  x density : ',ai.x);
        writeln('  y density : ',ai.y);
        writeln('  thumb x : ',ai.thumb_x);
        writeln('  thumb y : ',ai.thumb_y);

        writeln('sof0 information');
        writeln('  precision : ',f0.precision);
        writeln('  height : ',f0.image_height);
        writeln('  width  : ',f0.image_width);
        writeln('  number of components (1,3) :',byte(f0.comp_num));


        close(f);
        halt(1);
      end;
    end;
  until false;
end.
