{Version 9.45} { Copyright (c) 1995-2008 by L. David Baldwin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Note that the source modules, HTMLGIF1.PAS, PNGZLIB1.PAS, DITHERUNIT.PAS, and URLCON.PAS are covered by separate copyright notices located in those modules. } {$i htmlcons.inc} unit HTMLGif2; interface uses {$IFNDEF LCL} Windows, mmSystem, {$ELSE} LclIntf, LMessages, Types, LclType, HtmlMisc, {$ENDIF} SysUtils, Classes, Graphics, Controls, ExtCtrls, htmlUN2, htmlgif1; type TRGBColor = packed Record Red, Green, Blue: Byte; end; TDisposalType = (dtUndefined, {Take no action} dtDoNothing, {Leave graphic, next frame goes on top of it} dtToBackground,{restore original background for next frame} dtToPrevious); {restore image as it existed before this frame} type ThtBitmap=class(TBitmap) protected htMask: TBitmap; htTransparent: boolean; procedure Draw(ACanvas: TCanvas; const Rect: TRect); override; procedure StretchDraw(ACanvas: TCanvas; const DestRect, SrcRect: TRect); public destructor Destroy; override; end; TGIFImage = class; TgfFrame = class private { private declarations } frLeft: Integer; frTop: Integer; frWidth: Integer; frHeight: Integer; frDelay: Integer; frDisposalMethod: TDisposalType; TheEnd: boolean; {end of what gets copied} IsCopy: boolean; Public constructor Create; constructor CreateCopy(Item: TgfFrame); destructor Destroy; override; end; TgfFrameList = class(TList) private function GetFrame(I: integer): TgfFrame; public {note: Frames is 1 based, goes from [1..Count]} property Frames[I: integer]: TgfFrame read GetFrame; default; end; TGIFImage = class(TPersistent) private { Private declarations } FAnimated: Boolean; FCurrentFrame: Integer; FImageWidth: Integer; FImageHeight: Integer; FNumFrames: Integer; FNumIterations: Integer; FTransparent: Boolean; FVisible: Boolean; Strip: ThtBitmap; TheEnd: boolean; {copy to here} FBitmap: TBitmap; FMaskedBitmap, FMask: TBitmap; FAnimate: Boolean; FStretchedRect: TRect; WasDisposal: TDisposalType; Frames: TgfFrameList; CurrentIteration: Integer; LastTime: DWord; CurrentInterval: DWord; procedure SetAnimate(AAnimate: Boolean); procedure SetCurrentFrame(AFrame: Integer); function GetMaskedBitmap: TBitmap; function GetMask: TBitmap; function GetBitMap: TBitmap; procedure NextFrame(OldFrame: Integer); public ShowIt: boolean; IsCopy: boolean; {set if this is a copy of one in Cache} { Public declarations } constructor Create; constructor CreateCopy(Item: TGIFImage); destructor Destroy; override; procedure Draw(Canvas: TCanvas; X, Y, Wid, Ht: integer); property Bitmap: TBitmap read GetBitmap; property MaskedBitmap: TBitmap read GetMaskedBitmap; property Mask: TBitmap read GetMask; property IsAnimated: Boolean read FAnimated; property IsTransparent: Boolean read FTransparent; property NumFrames: Integer read FNumFrames; property NumIterations: Integer read FNumIterations; procedure CheckTime(WinControl: TWinControl); property Width: integer read FImageWidth; property Height: integer read FImageHeight; property Animate: Boolean read FAnimate write SetAnimate; property CurrentFrame: Integer read FCurrentFrame write SetCurrentFrame; property Visible: Boolean read FVisible write FVisible; end; function CreateAGifFromStream(var NonAnimated: boolean; Stream: TStream): TGifImage; function CreateAGif(const Name: string; var NonAnimated: boolean): TGifImage; implementation uses Styleun, htmlsubs; function CreateBitmap(Width, Height: integer): TBitmap; begin Result := TBitmap.Create; Result.Width := Width; Result.Height := Height; end; function CreateAGifFromStream(var NonAnimated: boolean; Stream: TStream): TGifImage; var AGif: TGif; Frame: TgfFrame; I: integer; ABitmap, AMask: TBitmap; begin Result := Nil; try NonAnimated := True; AGif := TGif.Create; Try AGif.LoadFromStream(Stream); Result := TGifImage.Create; Result.FNumFrames := AGif.ImageCount; Result.FAnimated := Result.FNumFrames > 1; NonAnimated := not Result.FAnimated; Result.FImageWidth := AGif.Width; Result.FImageHeight := AGif.Height; Result.FNumIterations:= AGif.LoopCount; if Result.FNumIterations < 0 then {-1 means no loop block} Result.FNumIterations := 1 else if Result.FNumIterations > 0 then Inc(Result.FNumIterations); {apparently this is the convention} Result.FTransparent := AGif.Transparent; with Result do begin Strip := ThtBitmap.Create; ABitmap := AGif.GetStripBitmap(AMask); try Strip.Assign(ABitmap); Strip.htMask := AMask; Strip.htTransparent := Assigned(AMask); finally ABitmap.Free; end; if Result.Strip.Palette <> 0 then DeleteObject(Result.Strip.ReleasePalette); Result.Strip.Palette := CopyPalette(ThePalette); end; for I := 0 to Result.FNumFrames-1 do begin Frame := TgfFrame.Create; try Frame.frDisposalMethod := TDisposalType(AGif.ImageDisposal[I]); Frame.frLeft := AGif.ImageLeft[I]; Frame.frTop := AGif.ImageTop[I]; Frame.frWidth := AGif.ImageWidth[I]; Frame.frHeight := AGif.ImageHeight[I]; Frame.frDelay := IntMax(30, AGif.ImageDelay[I] * 10); except Frame.Free; Raise; end; Result.Frames.Add(Frame); end; if Result.IsAnimated then Result.WasDisposal := dtToBackground; finally AGif.Free; end; except FreeAndNil(Result); end; end; function CreateAGif(const Name: string; var NonAnimated: boolean): TGifImage; var Stream: TFileStream; begin Result := Nil; try Stream := TFileStream.Create(Name, fmOpenRead or fmShareDenyWrite); try Result := CreateAGifFromStream(NonAnimated, Stream); finally Stream.Free; end; except end; end; {----------------TgfFrame.Create} constructor TgfFrame.Create; begin inherited Create; end; constructor TgfFrame.CreateCopy(Item: TgfFrame); begin inherited Create; System.Move(Item.frLeft, frLeft, DWord(@TheEnd)-DWord(@frLeft)); IsCopy := True; end; {----------------TgfFrame.Destroy} destructor TgfFrame.Destroy; begin inherited Destroy; end; {----------------TGIFImage.Create} constructor TGIFImage.Create; begin inherited Create; FVisible := True; FCurrentFrame := 1; Frames := TgfFrameList.Create; CurrentIteration := 1; end; constructor TGIFImage.CreateCopy(Item: TGIFImage); var I: integer; begin inherited Create; FImageWidth := Item.Width; FimageHeight := Item.Height; System.Move(Item.FAnimated, FAnimated, DWord(@TheEnd)-DWord(@FAnimated)); IsCopy := True; Frames := TgfFrameList.Create; for I := 1 to FNumFrames do Frames.Add(TgfFrame.CreateCopy(Item.Frames[I])); FCurrentFrame := 1; CurrentIteration := 1; if FAnimated then WasDisposal := dtToBackground; end; {----------------TGIFImage.Destroy} destructor TGIFImage.Destroy; var I: Integer; begin for I := Frames.Count downto 1 do Frames[I].Free; Frames.Free; FreeAndNil(FBitmap); if not IsCopy then FreeAndNil(Strip); FMaskedBitmap.Free; FreeAndNil(FMask); inherited Destroy; end; {----------------TGIFImage.Draw} procedure TGIFImage.Draw(Canvas: TCanvas; X, Y, Wid, Ht: integer); var SRect: TRect; ALeft: integer; begin FStretchedRect := Rect(X, Y, X+Wid, Y+Ht); SetStretchBltMode(Canvas.Handle, ColorOnColor); if (FVisible) and (FNumFrames > 0) then begin with Frames[FCurrentFrame] do begin ALeft := (FCurrentFrame-1)*Width; SRect := Rect(ALeft, 0, ALeft+Width, Height); {current frame location in Strip bitmap} end; Canvas.CopyMode := cmSrcCopy; {draw the correct portion of the strip} Strip.StretchDraw(Canvas, FStretchedRect, SRect); end; end; {----------------TGifImage.CheckTime} procedure TGifImage.CheckTime(WinControl: TWinControl); var ThisTime: DWord; begin if not FAnimate then Exit; ThisTime := timeGetTime; if ThisTime - LastTime < CurrentInterval then Exit; LastTime := ThisTime; if (FCurrentFrame = FNumFrames) then begin if (FNumIterations > 0) and (CurrentIteration >= FNumIterations) then begin SetAnimate(False); Exit; end; Inc(CurrentIteration); end; NextFrame(FCurrentFrame); Inc(FCurrentFrame); if (FCurrentFrame > FNumFrames) or (FCurrentFrame <= 0) then FCurrentFrame := 1; InvalidateRect(WinControl.Handle, @FStretchedRect, True); CurrentInterval := IntMax(Frames[FCurrentFrame].frDelay, 1); end; {----------------TGIFImage.SetAnimate} procedure TGIFImage.SetAnimate(AAnimate: Boolean); begin if AAnimate = FAnimate then Exit; FAnimate := AAnimate; if AAnimate and (FNumFrames > 1) then begin CurrentInterval := IntMax(Frames[FCurrentFrame].frDelay, 1); LastTime := timeGetTime; end; end; {----------------TGIFImage.SetCurrentFrame} procedure TGIFImage.SetCurrentFrame(AFrame: Integer); begin if AFrame = FCurrentFrame then Exit; NextFrame(FCurrentFrame); if AFrame > FNumFrames then FCurrentFrame := 1 else if AFrame < 1 then FCurrentFrame := FNumFrames else FCurrentFrame := AFrame; if FAnimated then WasDisposal := dtToBackground; end; {----------------TGIFImage.GetBitmap} function TGIFImage.GetBitmap: TBitmap; begin Result := GetMaskedBitmap; end; {----------------TGIFImage.GetMaskedBitmap:} function TGIFImage.GetMaskedBitmap: TBitmap; {This returns frame 1} begin if not Assigned(FMaskedBitmap) then begin FMaskedBitmap := TBitmap.Create; FMaskedBitmap.Assign(Strip); FMaskedBitmap.Width := FImageWidth; if Strip.htTransparent then begin FMask := TBitmap.Create; FMask.Assign(Strip.htMask); FMask.Width := FImageWidth; end; FMaskedBitmap.Transparent := False; end; Result := FMaskedBitmap; end; {----------------TGIFImage.GetMask:} function TGIFImage.GetMask: TBitmap; {This returns mask for frame 1. Content is black, background is white} begin if not FTransparent then Result := nil else begin if not Assigned(FMask) then GetMaskedBitmap; Result := FMask; end; end; {----------------TGIFImage.NextFrame} procedure TGIFImage.NextFrame(OldFrame: Integer); begin WasDisposal := Frames[OldFrame].frDisposalMethod; end; {----------------TgfFrameList.GetFrame} function TgfFrameList.GetFrame(I: integer): TgfFrame; begin Assert((I <= Count) and (I >= 1 ), 'Frame index out of range'); Result := TgfFrame(Items[I-1]); end; { ThtBitmap } var AHandle: THandle; destructor ThtBitmap.Destroy; begin htMask.Free; inherited; end; {----------------ThtBitmap.Draw} procedure ThtBitmap.Draw(ACanvas: TCanvas; const Rect: TRect); var OldPalette: HPalette; RestorePalette: Boolean; DoHalftone: Boolean; Pt: TPoint; BPP: Integer; MaskDC: HDC; Save: THandle; begin with Rect do begin AHandle := ACanvas.Handle; {LDB} PaletteNeeded; OldPalette := 0; RestorePalette := False; if Palette <> 0 then begin OldPalette := SelectPalette(ACanvas.Handle, Palette, True); RealizePalette(ACanvas.Handle); RestorePalette := True; end; BPP := GetDeviceCaps(ACanvas.Handle, BITSPIXEL) * GetDeviceCaps(ACanvas.Handle, PLANES); DoHalftone := (BPP <= 8) and (PixelFormat in [pf15bit, pf16bit, pf24bit]); if DoHalftone then begin GetBrushOrgEx(ACanvas.Handle, pt); SetStretchBltMode(ACanvas.Handle, HALFTONE); SetBrushOrgEx(ACanvas.Handle, pt.x, pt.y, @pt); end else if not Monochrome then SetStretchBltMode(ACanvas.Handle, STRETCH_DELETESCANS); try AHandle := Canvas.Handle; {LDB} if htTransparent then begin Save := 0; MaskDC := 0; try MaskDC := CreateCompatibleDC(0); {LDB} Save := SelectObject(MaskDC, MaskHandle); TransparentStretchBlt(ACanvas.Handle, Left, Top, Right - Left, Bottom - Top, Canvas.Handle, 0, 0, Width, Height, htMask.Canvas.Handle, 0, 0); {LDB} finally if Save <> 0 then SelectObject(MaskDC, Save); if MaskDC <> 0 then DeleteDC(MaskDC); end; end else StretchBlt(ACanvas.Handle, Left, Top, Right - Left, Bottom - Top, Canvas.Handle, 0, 0, Width, Height, ACanvas.CopyMode); finally if RestorePalette then SelectPalette(ACanvas.Handle, OldPalette, True); end; end; end; procedure ThtBitmap.StretchDraw(ACanvas: TCanvas; const DestRect, SrcRect: TRect); {Draw parts of this bitmap on ACanvas} var OldPalette: HPalette; RestorePalette: Boolean; DoHalftone: Boolean; Pt: TPoint; BPP: Integer; begin with DestRect do begin AHandle := ACanvas.Handle; {LDB} PaletteNeeded; OldPalette := 0; RestorePalette := False; if Palette <> 0 then begin OldPalette := SelectPalette(ACanvas.Handle, Palette, True); RealizePalette(ACanvas.Handle); RestorePalette := True; end; BPP := GetDeviceCaps(ACanvas.Handle, BITSPIXEL) * GetDeviceCaps(ACanvas.Handle, PLANES); DoHalftone := (BPP <= 8) and (PixelFormat in [pf15bit, pf16bit, pf24bit]); if DoHalftone then begin GetBrushOrgEx(ACanvas.Handle, pt); SetStretchBltMode(ACanvas.Handle, HALFTONE); SetBrushOrgEx(ACanvas.Handle, pt.x, pt.y, @pt); end else if not Monochrome then SetStretchBltMode(ACanvas.Handle, STRETCH_DELETESCANS); try AHandle := Canvas.Handle; {LDB} if htTransparent then TransparentStretchBlt(ACanvas.Handle, Left, Top, Right - Left, Bottom - Top, Canvas.Handle, SrcRect.Left, SrcRect.Top, SrcRect.Right - SrcRect.Left, SrcRect.Bottom - SrcRect.Top, htMask.Canvas.Handle, SrcRect.Left, SrcRect.Top) {LDB} else StretchBlt(ACanvas.Handle, Left, Top, Right - Left, Bottom - Top, Canvas.Handle, SrcRect.Left, SrcRect.Top, SrcRect.Right - SrcRect.Left, SrcRect.Bottom - SrcRect.Top, ACanvas.CopyMode); finally if RestorePalette then SelectPalette(ACanvas.Handle, OldPalette, True); end; end; end; end.