{$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.