From e39c8631a4e568ce754c836d4ae5d04276550aba Mon Sep 17 00:00:00 2001 From: michael Date: Fri, 12 Dec 2008 15:40:03 +0000 Subject: [PATCH] * Patch from Mattias Gaertner to read LZW compressed images and more robust handling of faulty images git-svn-id: trunk@12354 - --- packages/fcl-image/src/fpreadtiff.pas | 260 +++++++++++++------------- packages/fcl-image/src/fptiffcmn.pas | 3 + 2 files changed, 136 insertions(+), 127 deletions(-) diff --git a/packages/fcl-image/src/fpreadtiff.pas b/packages/fcl-image/src/fpreadtiff.pas index 899825cd96..5cbc45ed3b 100644 --- a/packages/fcl-image/src/fpreadtiff.pas +++ b/packages/fcl-image/src/fpreadtiff.pas @@ -18,11 +18,11 @@ RGB 8,16bit (optional alpha), Orientation, skipping Thumbnail to read first image, - compression: packbits, (LZW started) + compression: packbits, LZW endian ToDo: - Compression: LZW, deflate, jpeg, ... + Compression: deflate, jpeg, ... Planar ColorMap multiple images @@ -35,6 +35,8 @@ unit FPReadTiff; {$mode objfpc}{$H+} +{$inline on} + interface uses @@ -78,6 +80,8 @@ type procedure ReadShortValues(StreamPos: DWord; out Buffer: PWord; out Count: DWord); procedure ReadImage(Index: integer); + procedure ReadImgValue(BitCount: Word; var Run: Pointer; x: dword; + Predictor: word; var LastValue: word; out Value: Word); inline; function FixEndian(w: Word): Word; inline; function FixEndian(d: DWord): DWord; inline; procedure DecompressPackBits(var Buffer: Pointer; var Count: PtrInt); @@ -109,6 +113,31 @@ begin raise Exception.Create(Msg); end; +procedure TFPReaderTiff.ReadImgValue(BitCount: Word; var Run: Pointer; x: dword; + Predictor: word; var LastValue: word; out Value: Word); inline; +begin + if BitCount=8 then begin + Value:=PCUInt8(Run)^; + if Predictor=2 then begin + // horizontal difference + if x>0 then + Value:=(Value+LastValue) and $ff; + LastValue:=Value; + end; + Value:=Value shl 8+Value; + inc(Run); + end else if BitCount=16 then begin + Value:=FixEndian(PCUInt16(Run)^); + if Predictor=2 then begin + // horizontal difference + if x>0 then + Value:=(Value+LastValue) and $ffff; + LastValue:=Value; + end; + inc(Run,2); + end; +end; + procedure TFPReaderTiff.SetStreamPos(p: DWord); var NewPosition: int64; @@ -594,6 +623,19 @@ begin if Debug then writeln('TFPReaderTiff.ReadDirectoryEntry HostComputer="',IDF.HostComputer,'"'); end; + 317: + begin + // Predictor + UValue:=word(ReadEntryUnsigned); + case UValue of + 1: ; + 2: ; + else TiffError('expected Predictor, but found '+IntToStr(UValue)); + end; + IDF.Predictor:=UValue; + if Debug then + writeln('TFPReaderTiff.ReadDirectoryEntry Predictor="',IDF.Predictor,'"'); + end; 320: begin // ColorMap: N = 3*2^BitsPerSample @@ -885,21 +927,21 @@ var CurOffset: DWord; CurByteCnt: PtrInt; Strip: PByte; - Run: Dword; + Run: PByte; y: DWord; y2: DWord; x: DWord; - GrayValue: DWord; dx: LongInt; dy: LongInt; SampleCnt: DWord; SampleBits: PWord; ExtraSampleCnt: DWord; ExtraSamples: PWord; - RedValue: Word; - GreenValue: Word; - BlueValue: Word; - AlphaValue: Word; + GrayValue, LastGrayValue: Word; + RedValue, LastRedValue: Word; + GreenValue, LastGreenValue: Word; + BlueValue, LastBlueValue: Word; + AlphaValue, LastAlphaValue: Word; Col: TFPColor; i: Integer; CurImg: TFPCustomImage; @@ -909,7 +951,9 @@ var BlueBits: Word; AlphaBits: Word; BytesPerPixel: Integer; + StripBitsPerPixel: DWord; aContinue: Boolean; + ExpectedStripLength: PtrInt; begin CurImg:=nil; if Debug then @@ -973,6 +1017,14 @@ begin BlueBits:=0; AlphaBits:=0; BytesPerPixel:=0; + StripBitsPerPixel:=0; + for i:=0 to SampleCnt-1 do begin + if SampleBits[i]>64 then + TiffError('Samples bigger than 64 bit not supported'); + if SampleBits[i] and 7<>0 then + TiffError('Only samples of 8 and 16 bit supported'); + inc(StripBitsPerPixel,SampleBits[i]); + end; case IDF.PhotoMetricInterpretation of 0,1: begin @@ -983,6 +1035,10 @@ begin AlphaBits:=SampleBits[1+i]; IDF.AlphaBits:=AlphaBits; end; + if not (GrayBits in [8,16]) then + TiffError('gray image only supported with gray BitsPerSample 8 or 16'); + if not (AlphaBits in [0,8,16]) then + TiffError('gray image only supported with alpha BitsPerSample 8 or 16'); end; 2: begin @@ -997,6 +1053,14 @@ begin AlphaBits:=SampleBits[3+i]; IDF.AlphaBits:=AlphaBits; end; + if not (RedBits in [8,16]) then + TiffError('RGB image only supported with red BitsPerSample 8 or 16'); + if not (GreenBits in [8,16]) then + TiffError('RGB image only supported with green BitsPerSample 8 or 16'); + if not (BlueBits in [8,16]) then + TiffError('RGB image only supported with blue BitsPerSample 8 or 16'); + if not (AlphaBits in [0,8,16]) then + TiffError('RGB image only supported with alpha BitsPerSample 8 or 16'); end; 5: begin @@ -1013,6 +1077,16 @@ begin AlphaBits:=SampleBits[4+i]; IDF.AlphaBits:=AlphaBits; end; + if not (RedBits in [8,16]) then + TiffError('CMYK image only supported with cyan BitsPerSample 8 or 16'); + if not (GreenBits in [8,16]) then + TiffError('CMYK image only supported with magenta BitsPerSample 8 or 16'); + if not (BlueBits in [8,16]) then + TiffError('CMYK image only supported with yellow BitsPerSample 8 or 16'); + if not (GrayBits in [8,16]) then + TiffError('CMYK image only supported with black BitsPerSample 8 or 16'); + if not (AlphaBits in [0,8,16]) then + TiffError('CMYK image only supported with alpha BitsPerSample 8 or 16'); end; end; BytesPerPixel:=(GrayBits+RedBits+GreenBits+BlueBits+AlphaBits) div 8; @@ -1095,43 +1169,34 @@ begin TiffError('compression '+IntToStr(IDF.Compression)+' not supported yet'); end; if CurByteCnt<=0 then continue; + ExpectedStripLength:=(StripBitsPerPixel*IDF.ImageWidth+7) div 8; + ExpectedStripLength:=ExpectedStripLength*Min(IDF.RowsPerStrip,IDF.ImageHeight-y); + // writeln('TFPReaderTiff.ReadImage StripBitsPerPixel=',StripBitsPerPixel,' IDF.ImageWidth=',IDF.ImageWidth,' IDF.ImageHeight=',IDF.ImageHeight,' y=',y,' IDF.RowsPerStrip=',IDF.RowsPerStrip,' ExpectedStripLength=',ExpectedStripLength,' CurByteCnt=',CurByteCnt); + if CurByteCnt16 then begin // read from three bytes @@ -1406,7 +1408,7 @@ var end; Result:=v and ((1 shl CurBitLength)-1); SrcPosBit:=(SrcPosBit+CurBitLength) and 7; - writeln('GetNextCode END SrcPos=',SrcPos,' SrcPosBit=',SrcPosBit,' Result=',Result,' Result=',hexstr(Result,4)); + //writeln('GetNextCode END SrcPos=',SrcPos,' SrcPosBit=',SrcPosBit,' Result=',Result,' Result=',hexstr(Result,4)); end; procedure ClearTable; @@ -1433,44 +1435,43 @@ var var s: TLZWString; b: byte; - i: Integer; begin - WriteLn('WriteStringFromCode Code=',Code,' AddFirstChar=',AddFirstChar); + //WriteLn('WriteStringFromCode Code=',Code,' AddFirstChar=',AddFirstChar,' x=',(NewCount div 4) mod IDF.ImageWidth,' y=',(NewCount div 4) div IDF.ImageWidth,' PixelByte=',NewCount mod 4); if Code<256 then begin // write byte b:=Code; s.Data:=@b; s.Count:=1; - end else begin + end else if Code>=258 then begin // write string if Code-258>=TableCount then TiffError('LZW code out of bounds'); s:=Table[Code-258]; - end; + end else + TiffError('LZW code out of bounds'); if NewCount+s.Count+1>NewCapacity then begin NewCapacity:=NewCapacity*2+8; ReAllocMem(NewBuffer,NewCapacity); end; System.Move(s.Data^,NewBuffer[NewCount],s.Count); - for i:=0 to s.Count-1 do - write(HexStr(NewBuffer[NewCount+i],2)); + //for i:=0 to s.Count-1 do write(HexStr(NewBuffer[NewCount+i],2)); // debug inc(NewCount,s.Count); if AddFirstChar then begin NewBuffer[NewCount]:=s.Data^; - write(HexStr(NewBuffer[NewCount],2)); + //write(HexStr(NewBuffer[NewCount],2)); // debug inc(NewCount); end; - writeln(',WriteStringFromCode'); + //writeln(',WriteStringFromCode'); // debug end; procedure AddStringToTable(Code, AddFirstCharFromCode: integer); // add string from code plus first character of string from code as new string var - b: byte; + b1, b2: byte; s1, s2: TLZWString; p: PByte; begin - WriteLn('AddStringToTable Code=',Code,' FCFCode=',AddFirstCharFromCode,' TableCount=',TableCount,' TableCapacity=',TableCapacity); + //WriteLn('AddStringToTable Code=',Code,' FCFCode=',AddFirstCharFromCode,' TableCount=',TableCount,' TableCapacity=',TableCapacity); // grow table if TableCount>=TableCapacity then begin TableCapacity:=TableCapacity*2+128; @@ -1479,20 +1480,21 @@ var // find string 1 if Code<256 then begin // string is byte - b:=Code; - s1.Data:=@b; + b1:=Code; + s1.Data:=@b1; s1.Count:=1; - end else begin + end else if Code>=258 then begin // normal string if Code-258>=TableCount then TiffError('LZW code out of bounds'); s1:=Table[Code-258]; - end; + end else + TiffError('LZW code out of bounds'); // find string 2 if AddFirstCharFromCode<256 then begin // string is byte - b:=AddFirstCharFromCode; - s2.Data:=@b; + b2:=AddFirstCharFromCode; + s2.Data:=@b2; s2.Count:=1; end else begin // normal string @@ -1517,10 +1519,11 @@ var end; begin - WriteLn('TFPReaderTiff.DecompressLZW START Count=',Count); - for SrcPos:=0 to 19 do - write(HexStr(PByte(Buffer)[SrcPos],2)); - writeln(); + if Count=0 then exit; + //WriteLn('TFPReaderTiff.DecompressLZW START Count=',Count); + //for SrcPos:=0 to 19 do + // write(HexStr(PByte(Buffer)[SrcPos],2)); + //writeln(); NewBuffer:=nil; NewCount:=0; @@ -1536,12 +1539,15 @@ begin try repeat Code:=GetNextCode; - WriteLn('TFPReaderTiff.DecompressLZW Code=',Code); + //WriteLn('TFPReaderTiff.DecompressLZW Code=',Code); if Code=EoiCode then break; if Code=ClearCode then begin InitializeTable; Code:=GetNextCode; + //WriteLn('TFPReaderTiff.DecompressLZW after clear Code=',Code); if Code=EoiCode then break; + if Code=ClearCode then + TiffError('LZW code out of bounds'); WriteStringFromCode(Code); OldCode:=Code; end else begin diff --git a/packages/fcl-image/src/fptiffcmn.pas b/packages/fcl-image/src/fptiffcmn.pas index 17fccac2a1..9d92f3908c 100644 --- a/packages/fcl-image/src/fptiffcmn.pas +++ b/packages/fcl-image/src/fptiffcmn.pas @@ -62,6 +62,7 @@ type CellWidth: DWord; ColorMap: DWord;// tiff position of entry Compression: DWord; + Predictor: Word; Copyright: string; DateAndTime: string; DocumentName: string; @@ -161,6 +162,7 @@ begin PhotoMetricInterpretation:=High(PhotoMetricInterpretation); PlanarConfiguration:=0; Compression:=0; + Predictor:=1; ImageHeight:=0; ImageWidth:=0; ImageIsThumbNail:=false; @@ -202,6 +204,7 @@ begin PhotoMetricInterpretation:=IDF.PhotoMetricInterpretation; PlanarConfiguration:=IDF.PlanarConfiguration; Compression:=IDF.Compression; + Predictor:=IDF.Predictor; ImageHeight:=IDF.ImageHeight; ImageWidth:=IDF.ImageWidth; ImageIsThumbNail:=IDF.ImageIsThumbNail;