451 lines
13 KiB
ObjectPascal
451 lines
13 KiB
ObjectPascal
unit BGRASVGImageList;
|
|
|
|
{$mode delphi}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, FGL,
|
|
XMLConf, BGRABitmap, BGRABitmapTypes, BGRASVG;
|
|
|
|
type
|
|
|
|
TListOfTStringList = TFPGObjectList<TStringList>;
|
|
|
|
{ TBGRASVGImageList }
|
|
|
|
TBGRASVGImageList = class(TComponent)
|
|
private
|
|
FHeight: integer;
|
|
FHorizontalAlignment: TAlignment;
|
|
FItems: TListOfTStringList;
|
|
FReferenceDPI: integer;
|
|
FTargetRasterImageList: TImageList;
|
|
FUseSVGAlignment: boolean;
|
|
FVerticalAlignment: TTextLayout;
|
|
FWidth: integer;
|
|
FRasterized: boolean;
|
|
FDataLineBreak: TTextLineBreakStyle;
|
|
procedure ReadData(Stream: TStream);
|
|
procedure SetHeight(AValue: integer);
|
|
procedure SetTargetRasterImageList(AValue: TImageList);
|
|
procedure SetWidth(AValue: integer);
|
|
procedure WriteData(Stream: TStream);
|
|
protected
|
|
procedure Load(const XMLConf: TXMLConfig);
|
|
procedure Save(const XMLConf: TXMLConfig);
|
|
procedure DefineProperties(Filer: TFiler); override;
|
|
function GetCount: integer;
|
|
// Get SVG string
|
|
function GetSVGString(AIndex: integer): string; overload;
|
|
procedure Rasterize;
|
|
procedure RasterizeIfNeeded;
|
|
procedure QueryRasterize;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function Add(ASVG: string): integer;
|
|
procedure Remove(AIndex: integer);
|
|
procedure Exchange(AIndex1, AIndex2: integer);
|
|
procedure Replace(AIndex: integer; ASVG: string);
|
|
function GetScaledSize(ATargetDPI: integer): TSize;
|
|
// Get TBGRABitmap with custom width and height
|
|
function GetBGRABitmap(AIndex: integer; AWidth, AHeight: integer): TBGRABitmap; overload;
|
|
function GetBGRABitmap(AIndex: integer; AWidth, AHeight: integer;
|
|
AUseSVGAlignment: boolean): TBGRABitmap; overload;
|
|
// Get TBitmap with custom width and height
|
|
function GetBitmap(AIndex: integer; AWidth, AHeight: integer): TBitmap; overload;
|
|
function GetBitmap(AIndex: integer; AWidth, AHeight: integer;
|
|
AUseSVGAlignment: boolean): TBitmap; overload;
|
|
// Draw image with custom width and height. The Width and
|
|
// Height property are in LCL coordinates.
|
|
procedure Draw(AIndex: integer; AControl: TControl; ACanvas: TCanvas;
|
|
ALeft, ATop, AWidth, AHeight: integer); overload;
|
|
procedure Draw(AIndex: integer; AControl: TControl; ACanvas: TCanvas;
|
|
ALeft, ATop, AWidth, AHeight: integer; AUseSVGAlignment: boolean;
|
|
AOpacity: byte = 255); overload;
|
|
// Draw image with custom width, height and canvas scale. The Width and
|
|
// Height property are in LCL coordinates. CanvasScale is useful on MacOS
|
|
// where LCL coordinates do not match actual pixels.
|
|
procedure Draw(AIndex: integer; ACanvasScale: single; ACanvas: TCanvas;
|
|
ALeft, ATop, AWidth, AHeight: integer); overload;
|
|
procedure Draw(AIndex: integer; ACanvasScale: single; ACanvas: TCanvas;
|
|
ALeft, ATop, AWidth, AHeight: integer; AUseSVGAlignment: boolean;
|
|
AOpacity: byte = 255); overload;
|
|
// Draw on the target BGRABitmap with specified Width and Height.
|
|
procedure Draw(AIndex: integer; ABitmap: TBGRABitmap; const ARectF: TRectF); overload;
|
|
procedure Draw(AIndex: integer; ABitmap: TBGRABitmap; const ARectF: TRectF;
|
|
AUseSVGAlignment: boolean); overload;
|
|
|
|
// Generate bitmaps for an image list
|
|
procedure PopulateImageList(const AImageList: TImageList; AWidths: array of integer);
|
|
property SVGString[AIndex: integer]: string read GetSVGString;
|
|
property Count: integer read GetCount;
|
|
published
|
|
property Width: integer read FWidth write SetWidth;
|
|
property Height: integer read FHeight write SetHeight;
|
|
property ReferenceDPI: integer read FReferenceDPI write FReferenceDPI default 96;
|
|
property UseSVGAlignment: boolean read FUseSVGAlignment write FUseSVGAlignment default False;
|
|
property HorizontalAlignment: TAlignment read FHorizontalAlignment write FHorizontalAlignment default taCenter;
|
|
property VerticalAlignment: TTextLayout read FVerticalAlignment write FVerticalAlignment default tlCenter;
|
|
property TargetRasterImageList: TImageList read FTargetRasterImageList write SetTargetRasterImageList default nil;
|
|
end;
|
|
|
|
procedure Register;
|
|
|
|
implementation
|
|
|
|
uses LCLType;
|
|
|
|
procedure Register;
|
|
begin
|
|
RegisterComponents('BGRA Themes', [TBGRASVGImageList]);
|
|
end;
|
|
|
|
{ TBGRASVGImageList }
|
|
|
|
procedure TBGRASVGImageList.ReadData(Stream: TStream);
|
|
|
|
// Detects EOL marker used in the text stream
|
|
function GetLineEnding(AStream: TStream; AMaxLookAhead: integer = 4096): TTextLineBreakStyle;
|
|
var c: char;
|
|
i: integer;
|
|
begin
|
|
c := #0;
|
|
for i := 0 to AMaxLookAhead-1 do
|
|
begin
|
|
if AStream.Read(c, sizeof(c)) = 0 then break;
|
|
Case c of
|
|
#10: exit(tlbsLF);
|
|
#13: begin
|
|
if AStream.Read(c, sizeof(c)) = 0 then c := #0;
|
|
if c = #10 then
|
|
exit(tlbsCRLF)
|
|
else
|
|
exit(tlbsCR);
|
|
end;
|
|
end;
|
|
end;
|
|
// no marker found, return system default
|
|
exit(DefaultTextLineBreakStyle);
|
|
end;
|
|
|
|
var
|
|
FXMLConf: TXMLConfig;
|
|
begin
|
|
FXMLConf := TXMLConfig.Create(Self);
|
|
try
|
|
// Detect the line EOL marker
|
|
Stream.Position := 0;
|
|
FDataLineBreak:= GetLineEnding(Stream);
|
|
// Actually load the XML file
|
|
Stream.Position := 0;
|
|
FXMLConf.LoadFromStream(Stream);
|
|
Load(FXMLConf);
|
|
finally
|
|
FXMLConf.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.SetHeight(AValue: integer);
|
|
begin
|
|
if FHeight = AValue then
|
|
Exit;
|
|
FHeight := AValue;
|
|
QueryRasterize;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.SetTargetRasterImageList(AValue: TImageList);
|
|
begin
|
|
if FTargetRasterImageList=AValue then Exit;
|
|
if Assigned(FTargetRasterImageList) then FTargetRasterImageList.Clear;
|
|
FTargetRasterImageList:=AValue;
|
|
QueryRasterize;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.SetWidth(AValue: integer);
|
|
begin
|
|
if FWidth = AValue then
|
|
Exit;
|
|
FWidth := AValue;
|
|
QueryRasterize;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.WriteData(Stream: TStream);
|
|
var
|
|
FXMLConf: TXMLConfig;
|
|
FTempStream: TStringStream;
|
|
FNormalizedData: string;
|
|
begin
|
|
FXMLConf := TXMLConfig.Create(Self);
|
|
FTempStream := TStringStream.Create;
|
|
try
|
|
Save(FXMLConf);
|
|
// Save to temporary string stream.
|
|
// EOL marker will depend on OS (#13#10 or #10),
|
|
// because TXMLConfig automatically changes EOL to platform default.
|
|
FXMLConf.SaveToStream(FTempStream);
|
|
// Normalize EOL marker, as data will be saved as binary data.
|
|
// Saving without normalization would lead to different binary
|
|
// data when saving on different platforms.
|
|
FNormalizedData := AdjustLineBreaks(FTempStream.DataString, FDataLineBreak);
|
|
if FNormalizedData <> '' then
|
|
Stream.WriteBuffer(FNormalizedData[1], Length(FNormalizedData));
|
|
FXMLConf.Flush;
|
|
finally
|
|
FXMLConf.Free;
|
|
FTempStream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Load(const XMLConf: TXMLConfig);
|
|
var
|
|
i, j, index: integer;
|
|
begin
|
|
try
|
|
FItems.Clear;
|
|
j := XMLConf.GetValue('Count', 0);
|
|
for i := 0 to j - 1 do
|
|
begin
|
|
index := FItems.Add(TStringList.Create);
|
|
FItems[index].Text := XMLConf.GetValue('Item' + i.ToString + '/SVG', '');
|
|
end;
|
|
finally
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Save(const XMLConf: TXMLConfig);
|
|
var
|
|
i: integer;
|
|
begin
|
|
try
|
|
XMLConf.SetValue('Count', FItems.Count);
|
|
for i := 0 to FItems.Count - 1 do
|
|
XMLConf.SetValue('Item' + i.ToString + '/SVG', AdjustLineBreaks(FItems[i].Text, FDataLineBreak));
|
|
finally
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.DefineProperties(Filer: TFiler);
|
|
begin
|
|
inherited DefineProperties(Filer);
|
|
Filer.DefineBinaryProperty('Items', ReadData, WriteData, True);
|
|
end;
|
|
|
|
constructor TBGRASVGImageList.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FItems := TListOfTStringList.Create(True);
|
|
FWidth := 16;
|
|
FHeight := 16;
|
|
FReferenceDPI := 96;
|
|
FUseSVGAlignment:= false;
|
|
FHorizontalAlignment := taCenter;
|
|
FVerticalAlignment := tlCenter;
|
|
FDataLineBreak := DefaultTextLineBreakStyle;
|
|
end;
|
|
|
|
destructor TBGRASVGImageList.Destroy;
|
|
begin
|
|
FItems.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TBGRASVGImageList.Add(ASVG: string): integer;
|
|
var
|
|
list: TStringList;
|
|
begin
|
|
list := TStringList.Create;
|
|
list.Text := ASVG;
|
|
Result := FItems.Add(list);
|
|
QueryRasterize;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Remove(AIndex: integer);
|
|
begin
|
|
FItems.Remove(FItems[AIndex]);
|
|
QueryRasterize;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Exchange(AIndex1, AIndex2: integer);
|
|
begin
|
|
FItems.Exchange(AIndex1, AIndex2);
|
|
QueryRasterize;
|
|
end;
|
|
|
|
function TBGRASVGImageList.GetSVGString(AIndex: integer): string;
|
|
begin
|
|
Result := FItems[AIndex].Text;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Rasterize;
|
|
begin
|
|
if Assigned(FTargetRasterImageList) then
|
|
begin
|
|
FTargetRasterImageList.Clear;
|
|
FTargetRasterImageList.Width := Width;
|
|
FTargetRasterImageList.Height := Height;
|
|
{$IFDEF DARWIN}
|
|
PopulateImageList(FTargetRasterImageList, [Width, Width*2]);
|
|
{$ELSE}
|
|
PopulateImageList(FTargetRasterImageList, [Width]);
|
|
{$ENDIF}
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.RasterizeIfNeeded;
|
|
begin
|
|
if not FRasterized then
|
|
begin
|
|
Rasterize;
|
|
FRasterized := true;
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.QueryRasterize;
|
|
var method: TThreadMethod;
|
|
begin
|
|
FRasterized := false;
|
|
method := RasterizeIfNeeded;
|
|
TThread.ForceQueue(nil, method);
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Replace(AIndex: integer; ASVG: string);
|
|
begin
|
|
FItems[AIndex].Text := ASVG;
|
|
QueryRasterize;
|
|
end;
|
|
|
|
function TBGRASVGImageList.GetCount: integer;
|
|
begin
|
|
Result := FItems.Count;
|
|
end;
|
|
|
|
function TBGRASVGImageList.GetScaledSize(ATargetDPI: integer): TSize;
|
|
begin
|
|
result.cx := MulDiv(Width, ATargetDPI, ReferenceDPI);
|
|
result.cy := MulDiv(Height, ATargetDPI, ReferenceDPI);
|
|
end;
|
|
|
|
function TBGRASVGImageList.GetBGRABitmap(AIndex: integer; AWidth,
|
|
AHeight: integer): TBGRABitmap;
|
|
begin
|
|
result := GetBGRABitmap(AIndex, AWidth, AHeight, UseSVGAlignment);
|
|
end;
|
|
|
|
function TBGRASVGImageList.GetBGRABitmap(AIndex: integer; AWidth, AHeight: integer;
|
|
AUseSVGAlignment: boolean): TBGRABitmap;
|
|
var
|
|
bmp: TBGRABitmap;
|
|
svg: TBGRASVG;
|
|
begin
|
|
bmp := TBGRABitmap.Create(AWidth, AHeight);
|
|
svg := TBGRASVG.CreateFromString(FItems[AIndex].Text);
|
|
try
|
|
svg.StretchDraw(bmp.Canvas2D, 0, 0, AWidth, AHeight, AUseSVGAlignment);
|
|
finally
|
|
svg.Free;
|
|
end;
|
|
Result := bmp;
|
|
end;
|
|
|
|
function TBGRASVGImageList.GetBitmap(AIndex: integer; AWidth, AHeight: integer): TBitmap;
|
|
begin
|
|
result := GetBitmap(AIndex, AWidth, AHeight, UseSVGAlignment);
|
|
end;
|
|
|
|
function TBGRASVGImageList.GetBitmap(AIndex: integer; AWidth, AHeight: integer;
|
|
AUseSVGAlignment: boolean): TBitmap;
|
|
var
|
|
bmp: TBGRABitmap;
|
|
ms: TMemoryStream;
|
|
begin
|
|
bmp := GetBGRABitmap(AIndex, AWidth, AHeight, AUseSVGAlignment);
|
|
ms := TMemoryStream.Create;
|
|
bmp.Bitmap.SaveToStream(ms);
|
|
bmp.Free;
|
|
Result := TBitmap.Create;
|
|
ms.Position := 0;
|
|
Result.LoadFromStream(ms);
|
|
ms.Free;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Draw(AIndex: integer; AControl: TControl;
|
|
ACanvas: TCanvas; ALeft, ATop, AWidth, AHeight: integer);
|
|
begin
|
|
Draw(AIndex, AControl, ACanvas, ALeft, ATop, AWidth, AHeight, UseSVGAlignment);
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Draw(AIndex: integer; AControl: TControl; ACanvas: TCanvas;
|
|
ALeft, ATop, AWidth, AHeight: integer; AUseSVGAlignment: boolean; AOpacity: byte);
|
|
begin
|
|
Draw(AIndex, AControl.GetCanvasScaleFactor, ACanvas, ALeft, ATop, AWidth, AHeight,
|
|
AUseSVGAlignment, AOpacity);
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Draw(AIndex: integer; ACanvasScale: single;
|
|
ACanvas: TCanvas; ALeft, ATop, AWidth, AHeight: integer);
|
|
begin
|
|
Draw(AIndex, ACanvasScale, ACanvas, ALeft, ATop, AWidth, AHeight, UseSVGAlignment);
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Draw(AIndex: integer; ACanvasScale: single; ACanvas: TCanvas;
|
|
ALeft, ATop, AWidth, AHeight: integer; AUseSVGAlignment: boolean; AOpacity: byte);
|
|
var
|
|
bmp: TBGRABitmap;
|
|
begin
|
|
if (AWidth = 0) or (AHeight = 0) or (ACanvasScale = 0) then
|
|
Exit;
|
|
bmp := TBGRABitmap.Create(round(AWidth * ACanvasScale), round(AHeight * ACanvasScale));
|
|
try
|
|
Draw(AIndex, bmp, rectF(0, 0, bmp.Width, bmp.Height), AUseSVGAlignment);
|
|
bmp.ApplyGlobalOpacity(AOpacity);
|
|
bmp.Draw(ACanvas, RectWithSize(ALeft, ATop, AWidth, AHeight), False);
|
|
finally
|
|
bmp.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Draw(AIndex: integer; ABitmap: TBGRABitmap; const ARectF: TRectF);
|
|
begin
|
|
Draw(AIndex, ABitmap, ARectF, UseSVGAlignment);
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.Draw(AIndex: integer; ABitmap: TBGRABitmap; const ARectF: TRectF;
|
|
AUseSVGAlignment: boolean);
|
|
var
|
|
svg: TBGRASVG;
|
|
begin
|
|
svg := TBGRASVG.CreateFromString(FItems[AIndex].Text);
|
|
try
|
|
if AUseSVGAlignment then
|
|
svg.StretchDraw(ABitmap.Canvas2D, ARectF, true)
|
|
else svg.StretchDraw(ABitmap.Canvas2D, HorizontalAlignment, VerticalAlignment, ARectF.Left, ARectF.Top, ARectF.Width, ARectF.Height);
|
|
finally
|
|
svg.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TBGRASVGImageList.PopulateImageList(const AImageList: TImageList;
|
|
AWidths: array of integer);
|
|
var
|
|
i, j: integer;
|
|
arr: array of TCustomBitmap;
|
|
begin
|
|
AImageList.Width := AWidths[0];
|
|
AImageList.Height := MulDiv(AWidths[0], Height, Width);
|
|
AImageList.Scaled := True;
|
|
AImageList.RegisterResolutions(AWidths);
|
|
SetLength({%H-}arr, Length(AWidths));
|
|
for j := 0 to Count - 1 do
|
|
begin
|
|
for i := 0 to Length(arr) - 1 do
|
|
arr[i] := GetBitmap(j, AWidths[i], MulDiv(AWidths[i], Height, Width), True);
|
|
AImageList.AddMultipleResolutions(arr);
|
|
for i := 0 to Length(arr) - 1 do
|
|
TBitmap(Arr[i]).Free;
|
|
end;
|
|
end;
|
|
|
|
end.
|