388 lines
9.4 KiB
ObjectPascal
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.
|