home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 1998 April A / Pcwk4a98.iso / PROGRAM / PASCAL / DUMPING / DUMPING.PAS < prev    next >
Pascal/Delphi Source File  |  1995-03-26  |  34KB  |  980 lines

  1. Program  DumPing ;
  2.  
  3. {
  4.    Copyright (c) 1995 by Oliver Fromme  --  All Rights Reserved
  5.  
  6.    Address:  Oliver Fromme, Leibnizstr. 18-61, 38678 Clausthal, Germany
  7.    Internet:  fromme@rz.tu-clausthal.de
  8.    WWW:  http://www.tu-clausthal.de/~inof/
  9.  
  10.    Freely distributable, freely usable.
  11.    The original copyright notice may not be modified or omitted.
  12. }
  13.  
  14. {$A+,B-,D+,E-,F-,G+,I-,L+,N-,O-,P-,Q-,R-,S-,T-,V+,X+,Y+}
  15. {$M 16384,0,655360}
  16.  
  17. Uses  Dos,Strings,AltCrt2,CRC32,Adler32,Inflate ;
  18.  
  19. Const  ReadBuffSize = 65528 ;
  20.  
  21. Type  tReadBuff  = Array [0..Pred(ReadBuffSize)] Of Byte ;
  22.       pReadBuff  = ^tReadBuff ;
  23.  
  24. Var  PNGHead : Record {see the PNG spec, draft #9}
  25.                   Width,Height  : LongInt ;
  26.                   BitsPerSample : Byte ;
  27.                   ColorType     : Byte ;
  28.                   CM,Filter,IL  : Byte
  29.                End ;
  30.  
  31.      RunCRC : tCRC ; {see unit CRC32 for details}
  32.      RunAdl : tAdler ; {see unit Adler32 for details}
  33.  
  34.      ChunkHead : Record
  35.                     Length : LongInt ;
  36.                     Name   : Array [0..3] Of Char
  37.                  End ;
  38.  
  39.      inname : PathStr ;
  40.      infile : File ;
  41.  
  42.      LineSize      : LongInt ; {number of bytes in one row incl. filter byte}
  43.      CurrentOffset : LongInt ; {byte position in current row}
  44.      DecompBytes   : LongInt ; {counts decompressed bytes}
  45.      BytesNeeded   : LongInt ; {bytes actually decompressed, should be = DecompBytes}
  46.      NumLines      : LongInt ; {number of scanlines = number of filter bytes}
  47.      OutputRow     : pReadBuff ;
  48.      {Rows larger than 64K are supported, but only the first 64K
  49.       are stored.  Actually we need only the filter type byte.}
  50.  
  51.      FilterCount : Array [0..5] Of LongInt ;
  52.      PaletteSize : Word ;
  53.      CurrentPass : Byte ; {Adam7 pass, 0..6}
  54.  
  55. Procedure  Help ;
  56.    Begin
  57.       WriteLn ;
  58.       WriteLn ('DUMPING v0.4  26-Mar-1995') ;
  59.       WriteLn ('Usage:  ',GetName(ParamStr(0)),' <filename[.PNG]>') ;
  60.       WriteLn ('Purpose:  Verifies the given PNG file and dumps its content to the') ;
  61.       WriteLn ('          screen.  Complies with the PNG specification, draft #9.') ;
  62.       Halt
  63.    End {Help} ;
  64.  
  65. Procedure  Die (Const msg : String) ;
  66.    Begin
  67.       WriteLn ('*** ',msg) ;
  68.       If IsOpenFile(infile) Then
  69.          Close (infile) ;
  70.       Halt (20)
  71.    End {Die} ;
  72.  
  73. Procedure  Error (Const msg : String) ;
  74.    Const  Always : Boolean = False ;
  75.    Var  c : Char ;
  76.    Begin
  77.       WriteLn ('!!! ',msg) ;
  78.       If Always Then
  79.          Exit ;
  80.       Write ('Try to continue [Y(es)/n(o)/a(lways)]? ') ;
  81.       Repeat
  82.          c := ReadKey ;
  83.          If c=#0 Then
  84.             If ReadKey=#0 Then
  85.       Until UpCase(c) In ['Y','N','A',#13,#27] ;
  86.       If UpCase(c) In ['N',#27] Then
  87.          Die ('Halted.')
  88.       Else Begin
  89.          Write (#13,EmptyString:39,#13) ;
  90.          If UpCase(c)='A' Then
  91.             Always := True
  92.       End
  93.    End {Error} ;
  94.  
  95. Procedure  CheckIO ;
  96.    Var  iores : Integer ;
  97.    Begin
  98.       iores := IOResult ;
  99.       If iores<>0 Then
  100.          If iores=100 Then
  101.             Die ('Premature end of file.')
  102.          Else
  103.             Die ('I/O error #'+IntStr(iores)+' reading from input file.')
  104.    End {CheckIO} ;
  105.  
  106. {Some read buffer routines for faster access
  107.  on devices with slow response time.}
  108.  
  109. Var  ReadBuff    : pReadBuff ;
  110.      rbpos,rbend : Word ;
  111.  
  112. Procedure  ResetReadBuffer ;
  113.    Begin
  114.       rbpos := ReadBuffSize ;
  115.       rbend := ReadBuffSize
  116.    End {ResetReadBuffer} ;
  117.  
  118. Procedure  BufferedRead (Var desti ; c : Word) ;
  119.    Var  ec,r : Word ;
  120.    Begin
  121.       If rbpos+LongInt(c)>LongInt(ReadBuffSize) Then Begin
  122.          r := ReadBuffSize-rbpos ;
  123.          If rbpos<ReadBuffSize Then
  124.             Move (ReadBuff^[rbpos],ReadBuff^,r) ;
  125.          BlockRead (infile,ReadBuff^[r],rbpos,ec) ;
  126.          If ec<rbpos Then
  127.             rbend := r+ec ;
  128.          rbpos := 0
  129.       End ;
  130.       Move (ReadBuff^[rbpos],desti,c) ;
  131.       If rbpos+c>rbend Then
  132.          InOutRes := 100
  133.       Else
  134.          Inc (rbpos,c)
  135.    End {BufferedRead} ;
  136.  
  137. Procedure  ReadCheck (Var desti ; c : Word) ;
  138.    Begin
  139.       BufferedRead (desti,c) ;
  140.       CheckIO ;
  141.       Dec (ChunkHead.Length,c) ;
  142.       UpdateCRC32 (RunCRC,desti,c)
  143.    End {ReadCheck} ;
  144.  
  145. Procedure  BufferSkipBack (c : LongInt) ;
  146.    Begin
  147.       If rbpos>=c Then
  148.          Dec (rbpos,c)
  149.       Else Begin
  150.          ResetReadBuffer ;
  151.          Seek (infile,FilePos(infile)-c)
  152.       End
  153.    End {BufferSkipBack} ;
  154.  
  155. Function  MyHeapErrorFunc (Size: Word) : Integer ; Far ;
  156.    Begin
  157.       If Size=0 Then
  158.          MyHeapErrorFUnc := 2 {success}
  159.       Else
  160.          MyHeapErrorFunc := 1 {return NIL}
  161.    End {MyHeapErrorFunc} ;
  162.  
  163. {Swap a 32 bit variable (MSB<->LSB).}
  164.  
  165. Procedure  Swap32 (Var LongVar : LongInt) ; Assembler ;
  166.    Asm
  167.                 les     si,LongVar
  168.                 mov     ax,es:[si]
  169.                 mov     dx,es:[si+2]
  170.                 xchg    al,dh
  171.                 xchg    ah,dl
  172.                 mov     es:[si],ax
  173.                 mov     es:[si+2],dx
  174.    End {Swap32} ;
  175.  
  176. {Swap a 16 bit variable (MSB<->LSB).}
  177.  
  178. Procedure  Swap16 (Var WordVar : Word) ; Assembler ;
  179.    Asm
  180.                 les     si,WordVar
  181.                 mov     ax,es:[si]
  182.                 xchg    al,ah
  183.                 mov     es:[si],ax
  184.    End {Swap16} ;
  185.  
  186. Procedure  ReadChunkHead ;
  187.    Begin
  188.       BufferedRead (ChunkHead,SizeOf(ChunkHead)) ;
  189.       CheckIO ;
  190.       With ChunkHead Do Begin
  191.          Swap32 (Length) ;
  192.          InitCRC32 (RunCRC) ;
  193.          UpdateCRC32 (RunCRC,Name,4) ;
  194.          WriteLn ('"',Copy(Name,1,4),'"',Length:7,' bytes')
  195.       End
  196.    End {ReadChunkHead} ;
  197.  
  198. {Skip to the end of the current chunk and check the CRC.}
  199.  
  200. Procedure  SkipChunk ;
  201.    Var  CheckCRC : tCRC ;
  202.         b : Byte ;
  203.    Begin
  204.       With ChunkHead Do
  205.          If Length<0 Then Begin
  206.             BufferSkipBack (-Length) ;
  207.             CheckIO
  208.          End
  209.          Else
  210.             While Length>0 Do
  211.                ReadCheck (b,1) ;
  212.       BufferedRead (CheckCRC,4) ;
  213.       CheckIO ;
  214.       Swap32 (CheckCRC) ;
  215.       If FinalCRC32(RunCRC)<>CheckCRC Then
  216.          Error ('Chunk CRC fails.')
  217.       Else
  218.          WriteLn ('   Chunk CRC ok.')
  219.    End {SkipChunk} ;
  220.  
  221. {Callback for inflate:  feed an input byte to inflate.}
  222.  
  223. Function  PNG_ReadByte : Byte ; Far ;
  224.    Var  CheckCRC : tCRC ;
  225.         b : Byte ;
  226.    Begin
  227.       While ChunkHead.Length=0 Do Begin
  228.          SkipChunk ;
  229.          ReadChunkHead ;
  230.          If ChunkHead.Name<>'IDAT' Then Begin
  231.             Error ('IDAT chunk expected (compressed stream is not complete yet).') ;
  232.             WriteLn ('   Assume that this is actually an IDAT chunk.')
  233.          End
  234.       End ;
  235.       ReadCheck (b,1) ;
  236.       PNG_ReadByte := b
  237.    End {PNG_ReadByte} ;
  238.  
  239. {Apply a filter to a single row of pixels.}
  240.  
  241. Procedure  ApplyFilter ;
  242.    Var  f : Byte ;
  243.    Begin {ApplyFilter}
  244.       f := OutputRow^[0] ;
  245.       {Since this is only a checker, there is no filter code.
  246.        Instead, the filter type frequencies are computed.}
  247.       If f In [0..4] Then
  248.          Inc (FilterCount[f])
  249.       Else
  250.          Inc (FilterCount[5]) {Illegal filter type}
  251.    End {ApplyFilter} ;
  252.  
  253. Function  GetLineSize (PixelWidth : LongInt) : LongInt ;
  254.    Begin
  255.       With PNGHead Do
  256.          Case ColorType Of
  257.             0 : GetLineSize := (BitsPerSample*PixelWidth+7) Shr 3 +1 ;
  258.             2 : GetLineSize := (BitsPerSample Shr 3)*3*PixelWidth +1 ;
  259.             3 : GetLineSize := (BitsPerSample*PixelWidth+7) Shr 3 +1 ;
  260.             4 : GetLineSize := (BitsPerSample Shr 2)*PixelWidth +1 ;
  261.             6 : GetLineSize := (BitsPerSample Shr 1)*PixelWidth +1
  262.          Else
  263.             GetLineSize := PixelWidth Shl 3 +1
  264.             {should be pretty save if user wants to ignore error}
  265.          End
  266.    End {GetLineSize} ;
  267.  
  268. {Tables for Adam7 interlacing.}
  269.  
  270. Const  Adam7_StartRow : Array [0..6] Of Byte
  271.                       = (0,0,4,0,2,0,1) ;
  272.        Adam7_StartCol : Array [0..6] Of Byte
  273.                       = (0,4,0,2,0,1,0) ;
  274.        Adam7_IncrmRow : Array [0..6] Of Byte
  275.                       = (8,8,8,4,4,2,2) ;
  276.        Adam7_IncrmCol : Array [0..6] Of Byte
  277.                       = (8,8,4,4,2,2,1) ;
  278.  
  279. Var  CurY : LongInt ;
  280.  
  281. {Number of pixels/row in current pass}
  282.  
  283. Function  PassWidth : LongInt ;
  284.    Begin
  285.       PassWidth :=
  286.          (PNGHead.Width+Adam7_IncrmCol[CurrentPass]-1-Adam7_StartCol[CurrentPass])
  287.          Div Adam7_IncrmCol[CurrentPass]
  288.    End {PassWidth} ;
  289.  
  290. {Number of rows in current pass}
  291.  
  292. Function  PassHeight : LongInt ;
  293.    Begin
  294.       PassHeight :=
  295.          (PNGHead.Height+Adam7_IncrmRow[CurrentPass]-1-Adam7_StartRow[CurrentPass])
  296.          Div Adam7_IncrmRow[CurrentPass]
  297.    End {PassHeight} ;
  298.  
  299. {Callback for inflate:  provides output data from the sliding window.}
  300.  
  301. Function  PNG_Flush (w : Word) : Integer ; Far ;
  302.    Var  CopyOffset,CopyCount,BytesPerLine : Word ;
  303.    Begin
  304.       PNG_Flush := 0 ;
  305.       CopyOffset := 0 ;
  306.       Inc (DecompBytes,w) ;
  307.       UpdateAdler32 (RunAdl,slide^,w) ;
  308.       If CurrentPass>6 Then
  309.          Exit ; {Process_IDAT detects this}
  310.       While w>0 Do Begin
  311.          If PNGHead.IL=1 Then Begin {interlaced}
  312.             {Skip empty passes}
  313.             While ((PassWidth=0) Or (PassHeight=0)) And (CurrentPass<7) Do
  314.                Inc (CurrentPass) ;
  315.             If CurrentPass>6 Then
  316.                Exit ;
  317.             BytesPerLine := GetLineSize(PassWidth)
  318.          End
  319.          Else {non-interlaced}
  320.             BytesPerLine := LineSize ;
  321.          If w>BytesPerLine-CurrentOffset Then
  322.             CopyCount := BytesPerLine-CurrentOffset
  323.          Else
  324.             CopyCount := w ;
  325.          If CurrentOffset+CopyCount<=65528 Then
  326.             Move (slide^[CopyOffset],OutputRow^[CurrentOffset],CopyCount) ;
  327.          Dec (w,CopyCount) ;
  328.          Inc (CopyOffset,CopyCount) ;
  329.          Inc (CurrentOffset,CopyCount) ;
  330.          If CurrentOffset>=BytesPerLine Then Begin {next row}
  331.             ApplyFilter ;
  332.             CurrentOffset := 0 ;
  333.             If PNGHead.IL=1 Then Begin {interlaced}
  334.                Inc (CurY,Adam7_IncrmRow[CurrentPass]) ;
  335.                If CurY>=PNGHead.Height Then Begin
  336.                   Inc (CurrentPass) ;
  337.                   If CurrentPass>6 Then
  338.                      Exit ;
  339.                   CurY := Adam7_StartRow[CurrentPass]
  340.                End
  341.             End
  342.             Else Begin {non-interlaced}
  343.                Inc (CurY) ;
  344.                If CurY>=PNGHead.Height Then
  345.                   Exit
  346.             End
  347.          End
  348.       End
  349.    End {PNG_Flush} ;
  350.  
  351. Procedure  Process_IHDR ; Far ; Forward ;
  352. Procedure  Process_PLTE ; Far ; Forward ;
  353. Procedure  Process_IDAT ; Far ; Forward ;
  354. Procedure  Process_IEND ; Far ; Forward ;
  355. Procedure  Process_GAMA ; Far ; Forward ;
  356. Procedure  Process_SBIT ; Far ; Forward ;
  357. Procedure  Process_CHRM ; Far ; Forward ;
  358. Procedure  Process_TRNS ; Far ; Forward ;
  359. Procedure  Process_BKGD ; Far ; Forward ;
  360. Procedure  Process_HIST ; Far ; Forward ;
  361. Procedure  Process_TEXT ; Far ; Forward ;
  362. Procedure  Process_ZTXT ; Far ; Forward ;
  363. Procedure  Process_PHYS ; Far ; Forward ;
  364. Procedure  Process_OFFS ; Far ; Forward ;
  365. Procedure  Process_TIME ; Far ; Forward ;
  366.  
  367. Const  NumChunks = 15 ;
  368.        Chunks : Array [1..NumChunks] Of
  369.                    Record
  370.                       Name    : Array [0..3] Of Char ;
  371.                       Process : Procedure ;
  372.                       HaveIt  : Boolean {True = chunk has appeared}
  373.                    End
  374.               = ((Name: 'IHDR'; Process: Process_IHDR; HaveIt: False),
  375.                  (Name: 'PLTE'; Process: Process_PLTE; HaveIt: False),
  376.                  (Name: 'IDAT'; Process: Process_IDAT; HaveIt: False),
  377.                  (Name: 'IEND'; Process: Process_IEND; HaveIt: False),
  378.                  (Name: 'gAMA'; Process: Process_GAMA; HaveIt: False),
  379.                  (Name: 'sBIT'; Process: Process_SBIT; HaveIt: False),
  380.                  (Name: 'cHRM'; Process: Process_CHRM; HaveIt: False),
  381.                  (Name: 'tRNS'; Process: Process_TRNS; HaveIt: False),
  382.                  (Name: 'bKGD'; Process: Process_BKGD; HaveIt: False),
  383.                  (Name: 'hIST'; Process: Process_HIST; HaveIt: False),
  384.                  (Name: 'tEXt'; Process: Process_TEXT; HaveIt: False),
  385.                  (Name: 'zTXt'; Process: Process_ZTXT; HaveIt: False),
  386.                  (Name: 'pHYs'; Process: Process_PHYS; HaveIt: False),
  387.                  (Name: 'oFFs'; Process: Process_OFFS; HaveIt: False),
  388.                  (Name: 'tIME'; Process: Process_TIME; HaveIt: False)) ;
  389.  
  390. Function  FindChunk (c : String) : Integer ;
  391.    Var  i : Integer ;
  392.    Begin
  393.       FindChunk := -1 ;
  394.       For i:=1 To NumChunks Do
  395.          If c=Chunks[i].Name Then Begin
  396.             FindChunk := i ;
  397.             Break
  398.          End
  399.    End {FindChunk} ;
  400.  
  401. Function  CheckLength (l : LongInt) : LongInt ; {returns actual length}
  402.    Begin
  403.       If ChunkHead.Length<>l Then
  404.          Error ('Illegal length of '+ChunkHead.Name+' chunk, must be '+
  405.                 LongStr(l)+' bytes.') ;
  406.       CheckLength := ChunkHead.Length
  407.    End {CheckLength} ;
  408.  
  409. Procedure  CheckMulti ;
  410.    Begin
  411.       If Chunks[FindChunk(ChunkHead.Name)].HaveIt Then
  412.          Error ('This chunk may not appear more than once.')
  413.    End {CheckMulti} ;
  414.  
  415. Procedure  Process_IHDR ;
  416.    Type  ByteSet = Set Of Byte ;
  417.    Var  w : Word ;
  418.  
  419.    Procedure  CheckTypeDepth (allowed : ByteSet) ;
  420.       Begin {CheckTypeDepth}
  421.          If Not (PNGHead.BitsPerSample In allowed) Then
  422.             Error ('Illegal color type / bit depth combination.')
  423.       End {CheckTypeDepth} ;
  424.  
  425.    Begin {Process_IHDR}
  426.       CheckMulti ;
  427.       CheckLength (SizeOf(PNGHead)) ;
  428.       FillChar (PNGHead,SizeOf(PNGHead),0) ;
  429.       ReadCheck (PNGHead,SizeOf(PNGHead)) ;
  430.       With PNGHead Do Begin
  431.          Swap32 (Width) ;
  432.          Swap32 (Height) ;
  433.          WriteLn ('   Width: ',Width:5) ;
  434.          WriteLn ('   Height:',Height:5) ;
  435.          WriteLn ('   Bit depth: ',BitsPerSample,' (max. ',
  436.                   LongInt(1) Shl BitsPerSample,' values/sample)') ;
  437.          If Not (BitsPerSample In [1,2,4,8,16]) Then
  438.             Error ('Illegal bit depth.') ;
  439.          Write ('   Color type: ',ColorType,' (') ;
  440.          Case ColorType Of
  441.             0 : Begin
  442.                    WriteLn ('greyscale)') ;
  443.                    CheckTypeDepth ([1,2,4,8,16])
  444.                 End ;
  445.             2 : Begin
  446.                    WriteLn ('RGB)') ;
  447.                    CheckTypeDepth ([8,16])
  448.                 End ;
  449.             3 : Begin
  450.                    WriteLn ('color mapped)') ;
  451.                    CheckTypeDepth ([1,2,4,8])
  452.                End ;
  453.             4 : Begin
  454.                    WriteLn ('greyscale+alpha)') ;
  455.                    CheckTypeDepth ([8,16])
  456.                 End ;
  457.             6 : Begin
  458.                    WriteLn ('RGB+alpha)') ;
  459.                    CheckTypeDepth ([8,16])
  460.                 End
  461.          Else
  462.             WriteLn ('unknown)') ;
  463.             Error ('Illegal color type.')
  464.          End ;
  465.          LineSize := GetLineSize(Width) ;
  466.          Write ('   Compression method: ',CM,' (') ;
  467.          If CM=0 Then
  468.             WriteLn ('deflate/32K)')
  469.          Else Begin
  470.             WriteLn ('unknown)') ;
  471.             Error ('Illegal compression method.')
  472.          End ;
  473.          Write ('   Filter type: ',Filter,' (') ;
  474.          If Filter=0 Then
  475.             WriteLn ('adaptive/5)')
  476.          Else Begin
  477.             WriteLn ('unknown)') ;
  478.             Error ('Illegal filter type.')
  479.          End ;
  480.          Write ('   Interlace type: ',IL,' (') ;
  481.          Case IL Of
  482.             0 : WriteLn ('none)') ;
  483.             1 : WriteLn ('Adam7)')
  484.          Else
  485.             WriteLn ('unknown)') ;
  486.             Error ('Illegal interlace type.')
  487.          End
  488.       End ;
  489.       If LineSize>65528 Then
  490.          w := 65528
  491.       Else
  492.          w := LineSize ;
  493.       GetMem (OutputRow,w) ;
  494.       If OutputRow=NIL Then
  495.          Die ('Not enough memory for output row ('+WordStr(w)+' bytes).') ;
  496.       SkipChunk
  497.    End {Process_IHDR} ;
  498.  
  499. Procedure  Process_PLTE ;
  500.    Begin
  501.       CheckMulti ;
  502.       If Chunks[FindChunk('tRNS')].HaveIt Then
  503.          Error ('Must be before tRNS chunk.') ;
  504.       If Chunks[FindChunk('bKGD')].HaveIt Then
  505.          Error ('Must be before bKGD chunk.') ;
  506.       If Chunks[FindChunk('hIST')].HaveIt Then
  507.          Error ('Must be before hIST chunk.') ;
  508.       With ChunkHead Do Begin
  509.          PaletteSize := Length Div 3 ;
  510.          If Length<3 Then
  511.             Error ('Palette smaller than 3 bytes.')
  512.          Else If Length Mod 3 <>0 Then
  513.             Error ('Palette size not divisible by 3.')
  514.          Else If (PNGHead.ColorType And 1 <>0) And
  515.                  (PaletteSize > Word(1) Shl PNGHead.BitsPerSample) Then
  516.             Error ('Palette larger than bits per index allows.')
  517.          Else If PaletteSize>256 Then
  518.             Error ('Palette contains more than 256 entries.')
  519.          Else
  520.             WriteLn ('   ',PaletteSize,' colors defined.')
  521.       End ;
  522.       SkipChunk
  523.    End {Process_PLTE} ;
  524.  
  525. Procedure  Process_IDAT ;
  526.    Var  AdlerCheck : LongInt ;
  527.         Result : Integer ;
  528.         w : Word ;
  529.    Begin
  530.       If Chunks[FindChunk(ChunkHead.Name)].HaveIt Then Begin
  531.          Error ('Image is complete, no more IDAT chunks allowed.') ;
  532.          SkipChunk ;
  533.          Exit
  534.       End ;
  535.       If (PNGHead.ColorType=3) And Not Chunks[FindChunk('PLTE')].HaveIt Then
  536.          Error ('PLTE chunk must precede IDAT for colormapped images.') ;
  537.       w := PNG_ReadByte ;
  538.       w := (w Shl 8) Or PNG_ReadByte ;
  539.       WriteLn ('   CMF/FLG: 0x',Hex4(w)) ;
  540.       If w Mod 31 <>0 Then
  541.          Error ('CMF/FLG mod 31 check fails.') ;
  542.       If Hi(w) And $f<>8 Then
  543.          Error ('CMF: illegal compression method, must be 8.')
  544.       Else Begin
  545.          If Hi(w) Shr 4>7 Then
  546.             Error ('CMF: unsupported sliding window size, must be <=7 (32K).') ;
  547.          Case Lo(w) Shr 6 Of
  548.             0 : WriteLn ('   fastest compression') ;
  549.             1 : WriteLn ('   fast compression') ;
  550.             2 : WriteLn ('   default compression') ;
  551.             3 : WriteLn ('   maximum compression')
  552.          End
  553.       End ;
  554.       If w And 32 <>0 Then
  555.          Error ('Bit 5 (reserved) in FLG is set.') ;
  556.       InitAdler32 (RunAdl) ;
  557.       DecompBytes := 0 ;
  558.       With PNGHead Do
  559.          If IL=1 Then Begin {interlaced}
  560.             BytesNeeded := 0 ;
  561.             NumLines := 0 ;
  562.             For CurrentPass:=0 To 6 Do Begin
  563.                Inc (BytesNeeded,GetLineSize(PassWidth)*PassHeight) ;
  564.                Inc (NumLines,PassHeight)
  565.             End
  566.          End
  567.          Else Begin {non-interlaced}
  568.             BytesNeeded := LineSize*Height ;
  569.             NumLines := Height
  570.          End ;
  571.       CurrentOffset := 0 ;
  572.       CurrentPass := 0 ;
  573.       CurY := 0 ;
  574.       InflateRead := PNG_ReadByte ;
  575.       InflateFlush := PNG_Flush ;
  576.       Result := InflateRun ;
  577.       If Result<>0 Then
  578.          Error ('Inflate returns error code '+IntStr(Result)+'.') ;
  579.       WriteLn ('   ',DecompBytes,' bytes decompressed.') ;
  580.       If DecompBytes<>BytesNeeded Then
  581.          Error (LongStr(BytesNeeded)+' bytes expected.') ;
  582.       WriteLn ('   Reading Adler32 checksum...') ;
  583.       For w:=1 To 4 Do
  584.          AdlerCheck := (AdlerCheck Shl 8) Or PNG_ReadByte ;
  585.       If FinalAdler32(RunAdl)<>AdlerCheck Then Begin
  586.          WriteLn ('   Adler32, file: 0x',Hex8(AdlerCheck),', computed: 0x',
  587.                   Hex8(FinalAdler32(RunAdl))) ;
  588.          Error ('Adler32 check on uncompressed data fails.')
  589.       End
  590.       Else
  591.          WriteLn ('   Adler32 check ok.') ;
  592.       If ChunkHead.Length<0 Then
  593.          Error ('Too few bytes in IDAT chunks ('+
  594.                 LongStr(-ChunkHead.Length)+' bytes missing).') ;
  595.       If ChunkHead.Length>0 Then
  596.          Error ('Too many bytes in IDAT chunks ('+
  597.                  LongStr(ChunkHead.Length)+' bytes remaining).') ;
  598.       SkipChunk
  599.    End {Process_IDATs} ;
  600.  
  601. Procedure  Process_IEND ;
  602.    Begin
  603.       CheckLength (0) ;
  604.       SkipChunk
  605.    End {Process_IEND} ;
  606.  
  607. Procedure  Process_GAMA ;
  608.    Var  gamma : LongInt ;
  609.    Begin
  610.       CheckMulti ;
  611.       If Chunks[FindChunk('IDAT')].HaveIt Then
  612.          Error ('Must be before IDAT chunks.') ;
  613.       If Chunks[FindChunk('PLTE')].HaveIt Then
  614.          Error ('Must be before PLTE chunk.') ;
  615.       If CheckLength(4)>=4 Then Begin
  616.          ReadCheck (gamma,4) ;
  617.          Swap32 (gamma) ;
  618.          WriteLn ('   Image gamma is ',gamma/100000:4:2,'.')
  619.       End ;
  620.       SkipChunk
  621.    End {Process_GAMA} ;
  622.  
  623. Procedure  Process_SBIT ;
  624.    Var  w : Word ;
  625.         bits : Byte ;
  626.         Descript : String[4] ;
  627.    Begin
  628.       CheckMulti ;
  629.       If Chunks[FindChunk('IDAT')].HaveIt Then
  630.          Error ('Must be before IDAT chunks.') ;
  631.       If Chunks[FindChunk('PLTE')].HaveIt Then
  632.          Error ('Must be before PLTE chunk.') ;
  633.       Case PNGHead.ColorType Of
  634.          0 : Begin
  635.                 CheckLength (1) ;
  636.                 Descript := 'G'
  637.              End ;
  638.          2,3 : Begin
  639.                   CheckLength (3) ;
  640.                   Descript := 'RGB'
  641.                End ;
  642.          4 : Begin
  643.                 CheckLength (2) ;
  644.                 Descript := 'GA'
  645.              End ;
  646.          6 : Begin
  647.                 CheckLength (4) ;
  648.                 Descript := 'RGBA'
  649.              End
  650.       End ;
  651.       For w:=1 To Length(Descript) Do Begin
  652.          If ChunkHead.Length<=0 Then
  653.             Break ;
  654.          ReadCheck (bits,1) ;
  655.          WriteLn ('   Significant bits (',Descript[w],'):',bits:3)
  656.       End ;
  657.       SkipChunk
  658.    End {Process_SBIT} ;
  659.  
  660. Procedure  Process_CHRM ;
  661.    Const  ChrmName : Array [0..7] Of PChar
  662.                    = ('White Point X','White Point Y','  Red X','  Red Y',
  663.                       'Green X','Green Y',' Blue X',' Blue Y') ;
  664.    Var  value : LongInt ;
  665.         w : Word ;
  666.    Begin
  667.       CheckMulti ;
  668.       If Chunks[FindChunk('IDAT')].HaveIt Then
  669.          Error ('Must be before IDAT chunks.') ;
  670.       If Chunks[FindChunk('PLTE')].HaveIt Then
  671.          Error ('Must be before PLTE chunk.') ;
  672.       CheckLength (32) ;
  673.       For w:=0 To 7 Do Begin
  674.          If ChunkHead.Length<4 Then
  675.             Break ;
  676.          ReadCheck (value,4) ;
  677.          Swap32 (value) ;
  678.          WriteLn ('   ',ChrmName[w],': ',value/100000:4:2,'.')
  679.       End ;
  680.       SkipChunk
  681.    End {Process_CHRM} ;
  682.  
  683. Procedure  Process_TRNS ;
  684.    Const  SDesc : Array [0..2] Of Char = 'RGB' ;
  685.    Var  trans : Array [0..2] Of Word ;
  686.         w : Word ;
  687.         b : Byte ;
  688.    Begin
  689.       CheckMulti ;
  690.       If Chunks[FindChunk('IDAT')].HaveIt Then
  691.          Error ('Must be before IDAT chunks.') ;
  692.       If (PNGHead.ColorType=3) And Not Chunks[FindChunk('PLTE')].HaveIt Then
  693.          Error ('Must be after PLTE chunk.') ;
  694.       If PNGHead.ColorType In [4,6] Then
  695.          Error ('tRNS chunk not allowed for full alpha images.') ;
  696.       Case PNGHead.ColorType Of
  697.          3 : Begin
  698.                 If ChunkHead.Length>PaletteSize Then
  699.                    Error ('tRNS chunk contains more entries than palette.') ;
  700.                 For w:=0 To PaletteSize Do Begin
  701.                    If ChunkHead.Length<=0 Then Begin
  702.                       If w And 15 <>0 Then
  703.                          WriteLn ;
  704.                       Break ;
  705.                    End ;
  706.                    ReadCheck (b,1) ;
  707.                    If w And 15 =0 Then
  708.                       Write ('   ',b:3)
  709.                    Else
  710.                       Write (',',b:3) ;
  711.                    If (w And 15 =15) Or (w=PaletteSize) Then
  712.                       WriteLn
  713.                 End
  714.              End ;
  715.          0,4 : If CheckLength(2)>=2 Then Begin
  716.                   ReadCheck (trans[0],2) ;
  717.                   Swap16 (trans[0]) ;
  718.                   WriteLn ('   Transparent grey level: ',trans[0]) ;
  719.                   If trans[0]>=LongInt(1) Shl PNGHead.BitsPerSample Then
  720.                      Error ('tRNS grey level exceeds maximum value.')
  721.                End ;
  722.          2,6 : If CheckLength(6)>=6 Then Begin
  723.                   ReadCheck (trans,6) ;
  724.                   For w:=0 To 2 Do Begin
  725.                      Swap16 (trans[w]) ;
  726.                      WriteLn ('   Transparent level (',SDesc[w],'): ',trans[w]) ;
  727.                      If trans[w]>=LongInt(1) Shl PNGHead.BitsPerSample Then
  728.                         Error ('tRNS level exceeds maximum value.')
  729.                   End
  730.                End
  731.       End ;
  732.       SkipChunk
  733.    End {Process_TRNS} ;
  734.  
  735. Procedure  Process_BKGD ;
  736.    Const  SDesc : Array [0..2] Of Char = 'RGB' ;
  737.    Var  back : Array [0..2] Of Word ;
  738.         w : Word ;
  739.         b : Byte ;
  740.    Begin
  741.       CheckMulti ;
  742.       If Chunks[FindChunk('IDAT')].HaveIt Then
  743.          Error ('Must be before IDAT chunks.') ;
  744.       If (PNGHead.ColorType=3) And Not Chunks[FindChunk('PLTE')].HaveIt Then
  745.          Error ('Must be after PLTE chunk.') ;
  746.       Case PNGHead.ColorType Of
  747.          3 : If CheckLength(1)>=1 Then Begin
  748.                 ReadCheck (b,1) ;
  749.                 WriteLn ('   Background color index: ',b) ;
  750.                 If b>=PaletteSize Then
  751.                    Error ('bKGD index exceeds number of palette entries.')
  752.              End ;
  753.          0,4 : If CheckLength(2)>=2 Then Begin
  754.                   ReadCheck (back[0],2) ;
  755.                   Swap16 (back[0]) ;
  756.                   WriteLn ('   Background grey level: ',back[0]) ;
  757.                   If back[0]>=LongInt(1) Shl PNGHead.BitsPerSample Then
  758.                      Error ('bKGD grey level exceeds maximum value.')
  759.                End ;
  760.          2,6 : If CheckLength(6)>=6 Then Begin
  761.                   ReadCheck (back,6) ;
  762.                   For w:=0 To 2 Do Begin
  763.                      Swap16 (back[w]) ;
  764.                      WriteLn ('   Background color (',SDesc[w],'): ',back[w]) ;
  765.                      If back[w]>=LongInt(1) Shl PNGHead.BitsPerSample Then
  766.                         Error ('bKGD color exceeds maximum value.')
  767.                   End
  768.                End
  769.       End ;
  770.       SkipChunk
  771.    End {Process_BKGD} ;
  772.  
  773. Procedure  Process_HIST ;
  774.    Begin
  775.       CheckMulti ;
  776.       If Chunks[FindChunk('IDAT')].HaveIt Then
  777.          Error ('Must be before IDAT chunks.') ;
  778.       If (PNGHead.ColorType=3) And Not Chunks[FindChunk('PLTE')].HaveIt Then
  779.          Error ('Must be after PLTE chunk.') ;
  780.       CheckLength (PaletteSize Shl 1) ;
  781.       SkipChunk
  782.    End {Process_HIST} ;
  783.  
  784. Procedure  Process_TEXT ;
  785.    Begin
  786.       SkipChunk
  787.    End {Process_TEXT} ;
  788.  
  789. Procedure  Process_ZTXT ;
  790.    Begin
  791.       SkipChunk
  792.    End {Process_ZTXT} ;
  793.  
  794. Procedure  Process_PHYS ;
  795.    Var  PhysData : Record
  796.                       perx,pery : LongInt ;
  797.                       unitspec  : Byte
  798.                    End ;
  799.    Begin
  800.       CheckMulti ;
  801.       If Chunks[FindChunk('IDAT')].HaveIt Then
  802.          Error ('Must be before IDAT chunks.') ;
  803.       If CheckLength(SizeOf(PhysData))>=SizeOf(PhysData) Then Begin
  804.          ReadCheck (PhysData,SizeOf(PhysData)) ;
  805.          With PhysData Do Begin
  806.             Swap32 (perx) ;
  807.             Swap32 (pery) ;
  808.             If unitspec>1 Then
  809.                Error ('Unknown unit specifier '+WordStr(unitspec)+'.') ;
  810.             If unitspec=1 Then Begin
  811.                WriteLn ('   X:',perx/100:7:2,' dpcm =',(perx/100)*2.54:7:2,' dpi') ;
  812.                WriteLn ('   Y:',pery/100:7:2,' dpcm =',(pery/100)*2.54:7:2,' dpi')
  813.             End
  814.             Else Begin
  815.                WriteLn ('   X: ',perx) ;
  816.                WriteLn ('   Y: ',pery)
  817.             End ;
  818.             WriteLn ('   => X/Y ascpect ratio = ',perx/pery:5:3)
  819.          End
  820.       End ;
  821.       SkipChunk
  822.    End {Process_PHYS} ;
  823.  
  824. Procedure  Process_OFFS ;
  825.    Var  OffsData : Record
  826.                       ofsx,ofsy : LongInt ;
  827.                       unitspec  : Byte
  828.                    End ;
  829.    Begin
  830.       CheckMulti ;
  831.       If Chunks[FindChunk('IDAT')].HaveIt Then
  832.          Error ('Must be before IDAT chunks.') ;
  833.       If CheckLength(SizeOf(OffsData))>=SizeOf(OffsData) Then Begin
  834.          ReadCheck (OffsData,SizeOf(OffsData)) ;
  835.          With OffsData Do Begin
  836.             Swap32 (ofsx) ;
  837.             Swap32 (ofsy) ;
  838.             If unitspec>1 Then
  839.                Error ('Unknown unit specifier '+WordStr(unitspec)+'.') ;
  840.             Case unitspec Of
  841.                0 : Begin
  842.                       WriteLn ('   X offset: ',ofsx,' pixels') ;
  843.                       WriteLn ('   Y offset: ',ofsy,' pixels')
  844.                    End ;
  845.                1 : Begin
  846.                       WriteLn ('   X offset: ',ofsx/10000:6:3,' cm =',ofsx/25400:6:3,'"') ;
  847.                       WriteLn ('   Y offset: ',ofsy/10000:6:3,' cm =',ofsy/25400:6:3,'"')
  848.                    End
  849.             Else
  850.                WriteLn ('   X offset: ',ofsx) ;
  851.                WriteLn ('   Y offset: ',ofsy)
  852.             End
  853.          End
  854.       End ;
  855.       SkipChunk
  856.    End {Process_OFFS} ;
  857.  
  858. Procedure  Process_TIME ;
  859.    Const  MonthDesc : Array [0..12] Of String[3]
  860.                     = ('???','Jan','Feb','Mar','Apr','May','Jun',
  861.                        'Jul','Aug','Sep','Oct','Nov','Dec') ;
  862.    Var  TimeData : Record
  863.                       year : Word ;
  864.                       month,day,hour,minute,second : Byte
  865.                    End ;
  866.    Begin
  867.       CheckMulti ;
  868.       If CheckLength(SizeOf(TimeData))>=SizeOf(TimeData) Then Begin
  869.          ReadCheck (TimeData,SizeOf(TimeData)) ;
  870.          With TimeData Do Begin
  871.             Swap16 (year) ;
  872.             If year<100 Then
  873.                Error ('Illegal year ('+WordStr(year)+').') ;
  874.                {could try to fix, e.g.:  Inc (year,1900)}
  875.             If Not (month In [1..12]) Then Begin
  876.                Error ('Illegal month ('+WordStr(month)+').') ;
  877.                month := 0
  878.             End ;
  879.             If Not (day In [1..31]) Then
  880.                Error ('Illegal day ('+WordStr(day)+').') ;
  881.             {We could check for day>29 when month=2 etc., but this
  882.              is already complicated and picky enough.}
  883.             If Not (hour In [0..23]) Then
  884.                Error ('Illegal hour ('+WordStr(hour)+').') ;
  885.             If Not (minute In [0..59]) Then
  886.                Error ('Illegal minute ('+WordStr(minute)+').') ;
  887.             If Not (second In [0..60]) Then
  888.                Error ('Illegal second ('+WordStr(second)+').') ;
  889.             WriteLn ('   Time of last modification: ',day,'-',MonthDesc[month],
  890.                      '-',year,', ',hour,':',Lead0(minute,2),':',Lead0(second,2))
  891.          End
  892.       End ;
  893.       SkipChunk
  894.    End {Process_TIME} ;
  895.  
  896. Procedure  Main ;
  897.    Const  PNG_Magic : Array [0..7] Of Char
  898.                     = #137'PNG'#13#10#26#10 ;
  899.    Var  BufMag    : Array [0..7] Of Char ;
  900.         First     : Boolean ; {True = first chunk}
  901.         i         : Integer ;
  902.    Begin
  903.       BufferedRead (BufMag,8) ;
  904.       CheckIO ;
  905.       If BufMag<>PNG_Magic Then
  906.          Die ('Not a valid PNG file (PNG magic mismatch in first 8 bytes).') ;
  907.       First := True ;
  908.       With ChunkHead Do
  909.          While Name<>'IEND' Do Begin
  910.             ReadChunkHead ;
  911.             If ChunkHead.Name='IHDR' Then
  912.                Process_IHDR
  913.             Else Begin
  914.                If First Then
  915.                   Error ('First chunk is not IHDR.') ;
  916.                i := FindChunk(ChunkHead.Name) ;
  917.                If i>=0 Then Begin
  918.                   Chunks[i].Process ;
  919.                   Chunks[i].HaveIt := True
  920.                End
  921.                Else Begin
  922.                   If Byte(ChunkHead.Name[0]) And 32 =0 Then
  923.                      Error ('Unknown critical chunk.')
  924.                   Else
  925.                      WriteLn ('   Unknown ancillary chunk.') ;
  926.                   SkipChunk
  927.                End
  928.             End ;
  929.             First := False
  930.          End ;
  931.       If (rbpos<rbend) Or Not EOF(infile) Then
  932.          Error ('File contains data after IEND chunk.')
  933.       Else
  934.          WriteLn ('-EOF-')
  935.    End {Main} ;
  936.  
  937. Procedure  Init ;
  938.    Begin
  939.       If ParamCount<>1 Then
  940.          Help ;
  941.       inname := ExtPath(FExpand(ParamStr(1)),'PNG') ;
  942.       WriteLn ('Input file: ',inname) ;
  943.       Assign (infile,inname) ;
  944.       Reset (infile,1) ;
  945.       If IOResult<>0 Then
  946.          Die ('Input file not found.') ;
  947.       HeapError := @MyHeapErrorFunc ;
  948.       GetMem (slide,WSIZE) ;
  949.       If slide=NIL Then
  950.          Die ('Not enough memory for sliding window ('+WordStr(WSIZE)+' bytes).') ;
  951.       GetMem (ReadBuff,ReadBuffSize) ;
  952.       If ReadBuff=NIL Then
  953.          Die ('Not enough memory for read buffer ('+WordStr(ReadBuffSize)+' bytes).') ;
  954.       ResetReadBuffer ;
  955.       FillByte (FilterCount,SizeOf(FilterCount),0)
  956.    End {Init} ;
  957.  
  958. Procedure  Done ;
  959.    Var  w : Word ;
  960.    Const  FilterName : Array [0..5] Of String[9]
  961.                      = ('(none)','(sub)','(up)','(average)',
  962.                         '(paeth)','(illegal)') ;
  963.    Begin
  964.       Close (infile) ;
  965.       WriteLn ('Filter usage statistics:') ;
  966.       For w:=0 To 5 Do Begin
  967.          WriteLn ('Filter #',w,FilterName[w]:10,':',FilterCount[w]:5,
  968.                   (FilterCount[w]*100)/NumLines:7:1,'%') ;
  969.       End ;
  970.       If FilterCount[5]<>0 Then
  971.          Error ('File contains illegal filter types.') ;
  972.       WriteLn ('-Ok-')
  973.    End {Done} ;
  974.  
  975. Begin
  976.    Init ;
  977.    Main ;
  978.    Done
  979. End.
  980.