// SPDX-License-Identifier: LGPL-3.0-linking-exception { @abstract(Provides reader for GIF format.) This unit implements some optimisations of TFPReaderGif: decompression algorithm and direct pixel access of TBGRABitmap. Note: to read an animation use TBGRAAnimatedGif instead. } unit BGRAReadGif; {$mode objfpc}{$H+} interface uses BGRAClasses, SysUtils, FPimage, FPReadGif; type PGifRGB = ^TGifRGB; { @abstract(Reader for GIF still image format.) For animations, use TBGRAAnimatedGif class. } TBGRAReaderGif = class(TFPReaderGif) protected procedure ReadPaletteAtOnce(Stream: TStream; Size: integer); procedure InternalRead(Stream: TStream; Img: TFPCustomImage); override; function ReadScanLine(Stream: TStream): boolean; override; function WriteScanLineBGRA(Img: TFPCustomImage): Boolean; virtual; end; implementation uses BGRABitmapTypes; { TBGRAReaderGif } procedure TBGRAReaderGif.ReadPaletteAtOnce(Stream: TStream; Size: integer); Var RGBEntries, RGBEntry : PGifRGB; I : Integer; c : TFPColor; begin FPalette.count := 0; getmem(RGBEntries, sizeof(TGifRGB)*Size); Stream.Read(RGBEntries^, sizeof(TGifRGB)*Size); For I:=0 To Size-1 Do Begin RGBEntry := RGBEntries+I; With c do begin Red:=RGBEntry^.Red or (RGBEntry^.Red shl 8); Green:=RGBEntry^.Green or (RGBEntry^.Green shl 8); Blue:=RGBEntry^.Blue or (RGBEntry^.Blue shl 8); Alpha:=alphaOpaque; end; FPalette.Add(C); End; FreeMem(RGBEntries); end; procedure TBGRAReaderGif.InternalRead(Stream: TStream; Img: TFPCustomImage); var Introducer:byte; ColorTableSize :Integer; ContProgress: Boolean; begin FPalette:=nil; FScanLine:=nil; try ContProgress:=true; Progress(psStarting, 0, False, Rect(0,0,0,0), '', ContProgress); if not ContProgress then exit; FPalette := TFPPalette.Create(0); Stream.Position:=0; // header Stream.Read(FHeader,SizeOf(FHeader)); Progress(psRunning, 0, False, Rect(0,0,0,0), '', ContProgress); if not ContProgress then exit; // Endian Fix Mantis 8541. Gif is always little endian {$IFDEF ENDIAN_BIG} with FHeader do begin ScreenWidth := LEtoN(ScreenWidth); ScreenHeight := LEtoN(ScreenHeight); end; {$ENDIF} // global palette if (FHeader.Packedbit and $80) <> 0 then begin ColorTableSize := FHeader.Packedbit and 7 + 1; ReadPaletteAtOnce(stream, 1 shl ColorTableSize); end; // skip extensions Repeat Introducer:=SkipBlock(Stream); until (Introducer = $2C) or (Introducer = $3B); // descriptor Stream.Read(FDescriptor, SizeOf(FDescriptor)); {$IFDEF ENDIAN_BIG} with FDescriptor do begin Left := LEtoN(Left); Top := LEtoN(Top); Width := LEtoN(Width); Height := LEtoN(Height); end; {$ENDIF} // local palette if (FDescriptor.Packedbit and $80) <> 0 then begin ColorTableSize := FDescriptor.Packedbit and 7 + 1; ReadPaletteAtOnce(stream, 1 shl ColorTableSize); end; // parse header if not AnalyzeHeader then exit; // create image if Assigned(OnCreateImage) then OnCreateImage(Self,Img); Img.SetSize(FWidth,FHeight); // read pixels if not ReadScanLine(Stream) then exit; if Img is TBGRACustomBitmap then begin if not WriteScanLineBGRA(Img) then exit; end else if not WriteScanLine(Img) then exit; // ToDo: read further images finally FreeAndNil(FPalette); ReAllocMem(FScanLine,0); end; Progress(FPimage.psEnding, 100, false, Rect(0,0,FWidth,FHeight), '', ContProgress); end; function TBGRAReaderGif.ReadScanLine(Stream: TStream): Boolean; var OldPos, UnpackedSize, PackedSize:longint; I: Integer; Data, Bits, Code: LongWord; SourcePtr: PByte; InCode: LongWord; CodeSize: LongWord; CodeMask: LongWord; FreeCode: LongWord; OldCode: LongWord; Prefix: array[0..4095] of LongWord; Suffix, Stack: array [0..4095] of Byte; StackPointer, StackTop: PByte; StackSize: integer; DataComp, Target: PByte; {%H-}B, {%H-}FInitialCodeSize, FirstChar: Byte; ClearCode, EOICode: Word; ContProgress: Boolean; begin DataComp:=nil; ContProgress:=true; try // read dictionary size Stream.read({%H-}FInitialCodeSize, 1); // search end of compressor table OldPos:=Stream.Position; PackedSize := 0; Repeat Stream.read({%H-}B, 1); if B > 0 then begin inc(PackedSize, B); Stream.Seek(B, soFromCurrent); end; until B = 0; Progress(psRunning, trunc(100.0 * (Stream.position / Stream.size)), False, Rect(0,0,0,0), '', ContProgress); if not ContProgress then exit(false); Getmem(DataComp, PackedSize); // read compressor table SourcePtr:=DataComp; Stream.Position:=OldPos; Repeat Stream.read(B, 1); if B > 0 then begin Stream.ReadBuffer(SourcePtr^, B); Inc(SourcePtr,B); end; until B = 0; Progress(psRunning, trunc(100.0 * (Stream.position / Stream.size)), False, Rect(0,0,0,0), '', ContProgress); if not ContProgress then exit(false); SourcePtr:=DataComp; Target := FScanLine; CodeSize := FInitialCodeSize + 1; ClearCode := 1 shl FInitialCodeSize; EOICode := ClearCode + 1; FreeCode := ClearCode + 2; OldCode := 4096; CodeMask := (1 shl CodeSize) - 1; UnpackedSize:=FWidth * FHeight; for I := 0 to ClearCode - 1 do begin Prefix[I] := 4096; Suffix[I] := I; end; StackTop := @Stack[high(Stack)]; StackPointer := StackTop; FirstChar := 0; Data := 0; Bits := 0; // LZW decompression gif while (UnpackedSize > 0) and (PackedSize > 0) do begin Inc(Data, SourcePtr^ shl Bits); Inc(Bits, 8); while Bits >= CodeSize do begin Code := Data and CodeMask; Data := Data shr CodeSize; Dec(Bits, CodeSize); if Code = EOICode then Break; if Code = ClearCode then begin CodeSize := FInitialCodeSize + 1; CodeMask := (1 shl CodeSize) - 1; FreeCode := ClearCode + 2; OldCode := 4096; Continue; end; if Code > FreeCode then Break; if OldCode = 4096 then begin FirstChar := Suffix[Code]; Target^ := FirstChar; Inc(Target); Dec(UnpackedSize); OldCode := Code; Continue; end; InCode := Code; if Code = FreeCode then begin StackPointer^ := FirstChar; dec(StackPointer); Code := OldCode; end; while Code > ClearCode do begin StackPointer^ := Suffix[Code]; dec(StackPointer); Code := Prefix[Code]; end; FirstChar := Suffix[Code]; StackPointer^ := FirstChar; dec(StackPointer); Prefix[FreeCode] := OldCode; Suffix[FreeCode] := FirstChar; if (FreeCode = CodeMask) and (CodeSize < 12) then begin Inc(CodeSize); CodeMask := (1 shl CodeSize) - 1; end; if FreeCode < 4095 then Inc(FreeCode); OldCode := InCode; StackSize := StackTop-StackPointer; if StackSize > 0 then begin Move((StackPointer+1)^, Target^, StackSize); inc(Target, StackSize); StackPointer:= StackTop; dec(UnpackedSize, StackSize); end; end; Inc(SourcePtr); Dec(PackedSize); end; Progress(psRunning, trunc(100.0 * (Stream.position / Stream.size)), False, Rect(0,0,0,0), '', ContProgress); if not ContProgress then exit(false); finally if DataComp<>nil then FreeMem(DataComp); end; Result:=true; end; function TBGRAReaderGif.WriteScanLineBGRA(Img: TFPCustomImage): Boolean; Var Row, Col,i : Integer; Pass, Every : byte; P : PByte; PBGRAPalette: PBGRAPixel; PDest: PBGRAPixel; function IsMultiple(NumberA, NumberB: Integer): Boolean; begin Result := (NumberA >= NumberB) and (NumberB > 0) and (NumberA mod NumberB = 0); end; begin Result:=false; P:=FScanLine; getmem(PBGRAPalette, (FPalette.Count)*sizeof(TBGRAPixel)); for i := 0 to FPalette.Count-1 do PBGRAPalette[i] := FPColorToBGRA(FPalette.Color[i]); If FInterlace then begin For Pass := 1 to 4 do begin Case Pass of 1 : begin Row := 0; Every := 8; end; 2 : begin Row := 4; Every := 8; end; 3 : begin Row := 2; Every := 4; end; else{4} begin Row := 1; Every := 2; end; end; Repeat PDest := TBGRACustomBitmap(Img).ScanLine[Row]; for Col:=Img.Width-1 downto 0 do begin PDest^ := PBGRAPalette[P^]; Inc(P); Inc(PDest); end; Inc(Row, Every); until Row >= Img.Height; end; end else begin for Row:=0 to Img.Height-1 do begin PDest := TBGRACustomBitmap(Img).ScanLine[Row]; for Col:=Img.Width-1 downto 0 do begin PDest^ := PBGRAPalette[P^]; Inc(P); Inc(PDest); end; end; end; FreeMem(PBGRAPalette); Result:=true; end; initialization DefaultBGRAImageReader[ifGif] := TBGRAReaderGif; end.