{******************************************} { } { FastReport v4.0 } { Picture Cache } { } { Copyright (c) 1998-2008 } { by Alexander Tzyganenko, } { Fast Reports Inc. } { } {******************************************} unit FMX.frxPictureCache; interface {$I fmx.inc} {$I frx.inc} uses System.Classes, System.SysUtils, System.Variants, FMX.frxClass, FMX.frxXML, FMX.frxUtils; type TfrxCacheItem = packed record Segment: Longint; Offset: Longint; end; PfrxCacheItem = ^TfrxCacheItem; TfrxCacheList = class(TObject) private function Get(Index: Integer): PfrxCacheItem; protected FItems: TList; protected procedure Clear; public constructor Create; destructor Destroy; override; function Add: PfrxCacheItem; function Count: Integer; property Items[Index: Integer]: PfrxCacheItem read Get; default; end; TfrxFileStream = class(TFileStream) private FSz: LongWord; public function Seek(Offset: Longint; Origin: Word): Longint; overload; override; function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override; end; TfrxMemoryStream = class(TMemoryStream) private FSz: LongWord; public function Seek(Offset: Longint; Origin: Word): Longint; overload; override; function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; override; end; TfrxPictureCache = class(TObject) private FItems: TfrxCacheList; FCacheStreamList: TList; FTempFile: TStringList; FTempDir: String; FUseFileCache: Boolean; procedure Add; procedure SetTempDir(const Value: String); procedure SetUseFileCache(const Value: Boolean); public constructor Create; destructor Destroy; override; procedure Clear; procedure AddPicture(Picture: TfrxPictureView); procedure GetPicture(Picture: TfrxPictureView); procedure SaveToXML(Item: TfrxXMLItem); procedure LoadFromXML(Item: TfrxXMLItem); procedure AddSegment; property UseFileCache: Boolean read FUseFileCache write SetUseFileCache; property TempDir: String read FTempDir write SetTempDir; end; implementation {$IFDEF POSIX} uses Posix.Unistd; {$ENDIF} function frxStreamToString(Stream: TStream; Size: Integer): String; var p: PChar; begin SetLength(Result, Size * 2); GetMem(p, Size); Stream.Read(p^, Size); BinToHex(p, PChar(@Result[1]), Size); FreeMem(p, Size); end; procedure frxStringToStream(const s: String; Stream: TStream); var Size: Integer; p: PChar; begin Size := Length(s) div 2; GetMem(p, Size); HexToBin(PChar(@s[1]), p, Size * 2); Stream.Write(p^, Size); FreeMem(p, Size); end; { TfrxPictureCache } constructor TfrxPictureCache.Create; begin FItems := TfrxCacheList.Create; FCacheStreamList := TList.Create; FTempFile := TStringList.Create; FUseFileCache := False; end; destructor TfrxPictureCache.Destroy; begin Clear; FItems.Free; FCacheStreamList.Free; FTempFile.Free; inherited; end; procedure TfrxPictureCache.Clear; begin while FCacheStreamList.Count > 0 do begin TObject(FCacheStreamList[0]).Free; FCacheStreamList.Delete(0); if FUseFileCache then begin DeleteFile(FTempFile[0]); FTempFile.Delete(0); end; end; FItems.Clear; end; procedure TfrxPictureCache.Add; begin if (FCacheStreamList.Count = 0) or (TStream(FCacheStreamList[FCacheStreamList.Count - 1]).Size >= Round(MaxInt - MaxInt/6)) then AddSegment; with FItems.Add^ do begin Segment := FCacheStreamList.Count - 1; Offset := TStream(FCacheStreamList[FCacheStreamList.Count - 1]).Size; TStream(FCacheStreamList[FCacheStreamList.Count - 1]).Position := Offset; end; end; procedure TfrxPictureCache.AddPicture(Picture: TfrxPictureView); begin if (Picture.Picture = nil) or (Picture.Picture.IsEmpty) then Picture.ImageIndex := 0 else begin Picture.ImageIndex := FItems.Count + 1; Add; Picture.Picture.SaveToStream(TStream(FCacheStreamList[FItems[Picture.ImageIndex - 1]^.Segment])); end; end; procedure TfrxPictureCache.GetPicture(Picture: TfrxPictureView); var Size, Offset, Segment: Longint; ImageIndex: Integer; Stream: TStream; begin if (Picture.ImageIndex <= 0) or (Picture.ImageIndex > FItems.Count) then Picture.Picture.Assign(nil) else begin if FCacheStreamList.Count = 0 then Exit; ImageIndex := Picture.ImageIndex ; Segment := Fitems[ImageIndex - 1]^.Segment; Offset := FItems[ImageIndex - 1]^.Offset; Stream := TStream(FCacheStreamList[Segment]); if (Picture.ImageIndex < FItems.Count) and (Fitems[ImageIndex]^.Segment = Segment) then Size := FItems[ImageIndex]^.Offset - Offset else Size := Stream.Size - Offset; Stream.Position := Offset; if FUseFileCache then TfrxFileStream(Stream).FSz := Offset + Size else TfrxMemoryStream(Stream).FSz := Offset + Size; Picture.Picture.LoadFromStream(Stream); if FUseFileCache then TfrxFileStream(Stream).FSz := 0 else TfrxMemoryStream(Stream).FSz := 0 end; end; procedure TfrxPictureCache.LoadFromXML(Item: TfrxXMLItem); var i: Integer; xi: TfrxXMLItem; begin Clear; for i := 0 to Item.Count - 1 do begin xi := Item[i]; Add; frxStringToStream(xi.Prop['stream'], TStream(FCacheStreamList[FCacheStreamList.Count - 1])); end; end; procedure TfrxPictureCache.SaveToXML(Item: TfrxXMLItem); var i, Size: Integer; xi: TfrxXMLItem; begin Item.Clear; for i := 0 to FCacheStreamList.Count - 1 do TStream(FCacheStreamList[i]).Position := 0; for i := 0 to FItems.Count - 1 do begin if (i + 1 < FItems.Count) and (Fitems[i]^.Segment = Fitems[i + 1]^.Segment) then Size := frxInteger(FItems[i + 1]^.Offset) - frxInteger(FItems[i]^.Offset) else Size := TStream(FCacheStreamList[FItems[i]^.Segment]).Size - frxInteger(FItems[i]^.Offset); xi := Item.Add; xi.Name := 'item'; xi.Text := 'stream="' + frxStreamToString(TStream(FCacheStreamList[FItems[i]^.Segment]), Size) + '"'; end; end; procedure TfrxPictureCache.SetTempDir(const Value: String); begin if FCacheStreamList.Count = 0 then FTempDir := Value; end; procedure TfrxPictureCache.SetUseFileCache(const Value: Boolean); begin if FCacheStreamList.Count = 0 then FUseFileCache := Value; end; procedure TfrxPictureCache.AddSegment; var Stream: TStream; Path: WideString; //FileName: WideString; begin if FUseFileCache then begin Path := GetTempFile; FTempFile.Add(String(Path)); Stream := TfrxFileStream.Create(String(Path), fmOpenReadWrite); TfrxFileStream(Stream).FSz := 0; end else begin Stream := TfrxMemoryStream.Create; TfrxMemoryStream(Stream).FSz := 0; end; FCacheStreamList.Add(Stream); end; function TfrxMemoryStream.Seek(Offset: Longint; Origin: Word): Longint; begin if (FSz <> 0) and (Offset = 0) and (Origin = soFromEnd) then Result := FSz else Result := inherited Seek(Offset, Origin); end; function TfrxMemoryStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; begin if (FSz <> 0) and (Offset = 0) and (Origin = soEnd) then Result := FSz else Result := inherited Seek(Offset, Origin); end; function TfrxFileStream.Seek(Offset: Longint; Origin: Word): Longint; begin if (FSz <> 0) and (Offset = 0) and (Origin = soFromEnd) then Result := FSz else Result := inherited Seek(Offset, Origin); end; function TfrxFileStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; begin if (FSz <> 0) and (Offset = 0) and (Origin = soEnd) then Result := FSz else Result := inherited Seek(Offset, Origin); end; { TfrxCacheList } function TfrxCacheList.Add: PfrxCacheItem; begin GetMem(Result, sizeof(TfrxCacheItem)); FItems.Add(Result); end; procedure TfrxCacheList.Clear; var idx: Integer; begin for idx := 0 to FItems.Count - 1 do FreeMem(FItems[idx], sizeof(TfrxCacheItem)); FItems.Clear; end; function TfrxCacheList.Count: Integer; begin Result := FItems.Count; end; constructor TfrxCacheList.Create; begin FItems := TList.Create; end; destructor TfrxCacheList.Destroy; begin Clear; FItems.Free; inherited; end; function TfrxCacheList.Get(Index: Integer): PfrxCacheItem; begin Result := PfrxCacheItem(FItems[Index]); end; end.