388 lines
9.4 KiB
ObjectPascal

// 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.