bgrasvgimagelist.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. unit BGRASVGImageList;
  2. {$mode delphi}
  3. interface
  4. uses
  5. Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, FGL,
  6. XMLConf, BGRABitmap, BGRABitmapTypes, BGRASVG;
  7. type
  8. TListOfTStringList = TFPGObjectList<TStringList>;
  9. { TBGRASVGImageList }
  10. TBGRASVGImageList = class(TComponent)
  11. private
  12. FHeight: integer;
  13. FHorizontalAlignment: TAlignment;
  14. FItems: TListOfTStringList;
  15. FReferenceDPI: integer;
  16. FTargetRasterImageList: TImageList;
  17. FUseSVGAlignment: boolean;
  18. FVerticalAlignment: TTextLayout;
  19. FWidth: integer;
  20. FRasterized: boolean;
  21. FDataLineBreak: TTextLineBreakStyle;
  22. procedure ReadData(Stream: TStream);
  23. procedure SetHeight(AValue: integer);
  24. procedure SetTargetRasterImageList(AValue: TImageList);
  25. procedure SetWidth(AValue: integer);
  26. procedure WriteData(Stream: TStream);
  27. protected
  28. procedure Load(const XMLConf: TXMLConfig);
  29. procedure Save(const XMLConf: TXMLConfig);
  30. procedure DefineProperties(Filer: TFiler); override;
  31. function GetCount: integer;
  32. // Get SVG string
  33. function GetSVGString(AIndex: integer): string; overload;
  34. procedure Rasterize;
  35. procedure RasterizeIfNeeded;
  36. procedure QueryRasterize;
  37. public
  38. constructor Create(AOwner: TComponent); override;
  39. destructor Destroy; override;
  40. function Add(ASVG: string): integer;
  41. procedure Remove(AIndex: integer);
  42. procedure Exchange(AIndex1, AIndex2: integer);
  43. procedure Replace(AIndex: integer; ASVG: string);
  44. function GetScaledSize(ATargetDPI: integer): TSize;
  45. // Get TBGRABitmap with custom width and height
  46. function GetBGRABitmap(AIndex: integer; AWidth, AHeight: integer): TBGRABitmap; overload;
  47. function GetBGRABitmap(AIndex: integer; AWidth, AHeight: integer;
  48. AUseSVGAlignment: boolean): TBGRABitmap; overload;
  49. // Get TBitmap with custom width and height
  50. function GetBitmap(AIndex: integer; AWidth, AHeight: integer): TBitmap; overload;
  51. function GetBitmap(AIndex: integer; AWidth, AHeight: integer;
  52. AUseSVGAlignment: boolean): TBitmap; overload;
  53. // Draw image with custom width and height. The Width and
  54. // Height property are in LCL coordinates.
  55. procedure Draw(AIndex: integer; AControl: TControl; ACanvas: TCanvas;
  56. ALeft, ATop, AWidth, AHeight: integer); overload;
  57. procedure Draw(AIndex: integer; AControl: TControl; ACanvas: TCanvas;
  58. ALeft, ATop, AWidth, AHeight: integer; AUseSVGAlignment: boolean;
  59. AOpacity: byte = 255); overload;
  60. // Draw image with custom width, height and canvas scale. The Width and
  61. // Height property are in LCL coordinates. CanvasScale is useful on MacOS
  62. // where LCL coordinates do not match actual pixels.
  63. procedure Draw(AIndex: integer; ACanvasScale: single; ACanvas: TCanvas;
  64. ALeft, ATop, AWidth, AHeight: integer); overload;
  65. procedure Draw(AIndex: integer; ACanvasScale: single; ACanvas: TCanvas;
  66. ALeft, ATop, AWidth, AHeight: integer; AUseSVGAlignment: boolean;
  67. AOpacity: byte = 255); overload;
  68. // Draw on the target BGRABitmap with specified Width and Height.
  69. procedure Draw(AIndex: integer; ABitmap: TBGRABitmap; const ARectF: TRectF); overload;
  70. procedure Draw(AIndex: integer; ABitmap: TBGRABitmap; const ARectF: TRectF;
  71. AUseSVGAlignment: boolean); overload;
  72. // Generate bitmaps for an image list
  73. procedure PopulateImageList(const AImageList: TImageList; AWidths: array of integer);
  74. property SVGString[AIndex: integer]: string read GetSVGString;
  75. property Count: integer read GetCount;
  76. published
  77. property Width: integer read FWidth write SetWidth;
  78. property Height: integer read FHeight write SetHeight;
  79. property ReferenceDPI: integer read FReferenceDPI write FReferenceDPI default 96;
  80. property UseSVGAlignment: boolean read FUseSVGAlignment write FUseSVGAlignment default False;
  81. property HorizontalAlignment: TAlignment read FHorizontalAlignment write FHorizontalAlignment default taCenter;
  82. property VerticalAlignment: TTextLayout read FVerticalAlignment write FVerticalAlignment default tlCenter;
  83. property TargetRasterImageList: TImageList read FTargetRasterImageList write SetTargetRasterImageList default nil;
  84. end;
  85. procedure Register;
  86. implementation
  87. uses LCLType, XMLRead;
  88. procedure Register;
  89. begin
  90. RegisterComponents('BGRA Themes', [TBGRASVGImageList]);
  91. end;
  92. {$IF FPC_FULLVERSION < 30203}
  93. type
  94. { TPatchedXMLConfig }
  95. TPatchedXMLConfig = class(TXMLConfig)
  96. public
  97. procedure LoadFromStream(S : TStream); reintroduce;
  98. end;
  99. { TPatchedXMLConfig }
  100. procedure TPatchedXMLConfig.LoadFromStream(S: TStream);
  101. begin
  102. FreeAndNil(Doc);
  103. ReadXMLFile(Doc,S);
  104. FModified := False;
  105. if (Doc.DocumentElement.NodeName<>RootName) then
  106. raise EXMLConfigError.CreateFmt(SWrongRootName,[RootName,Doc.DocumentElement.NodeName]);
  107. end;
  108. {$ENDIF}
  109. { TBGRASVGImageList }
  110. procedure TBGRASVGImageList.ReadData(Stream: TStream);
  111. // Detects EOL marker used in the text stream
  112. function GetLineEnding(AStream: TStream; AMaxLookAhead: integer = 4096): TTextLineBreakStyle;
  113. var c: char;
  114. i: integer;
  115. begin
  116. c := #0;
  117. for i := 0 to AMaxLookAhead-1 do
  118. begin
  119. if AStream.Read(c, sizeof(c)) = 0 then break;
  120. Case c of
  121. #10: exit(tlbsLF);
  122. #13: begin
  123. if AStream.Read(c, sizeof(c)) = 0 then c := #0;
  124. if c = #10 then
  125. exit(tlbsCRLF)
  126. else
  127. exit(tlbsCR);
  128. end;
  129. end;
  130. end;
  131. // no marker found, return system default
  132. exit(DefaultTextLineBreakStyle);
  133. end;
  134. var
  135. FXMLConf: TXMLConfig;
  136. begin
  137. FXMLConf := TXMLConfig.Create(Self);
  138. try
  139. // Detect the line EOL marker
  140. Stream.Position := 0;
  141. FDataLineBreak:= GetLineEnding(Stream);
  142. // Actually load the XML file
  143. Stream.Position := 0;
  144. {$IF FPC_FULLVERSION < 30203}TPatchedXMLConfig(FXMLConf){$ELSE}FXMLConf{$ENDIF}.LoadFromStream(Stream);
  145. Load(FXMLConf);
  146. finally
  147. FXMLConf.Free;
  148. end;
  149. end;
  150. procedure TBGRASVGImageList.SetHeight(AValue: integer);
  151. begin
  152. if FHeight = AValue then
  153. Exit;
  154. FHeight := AValue;
  155. QueryRasterize;
  156. end;
  157. procedure TBGRASVGImageList.SetTargetRasterImageList(AValue: TImageList);
  158. begin
  159. if FTargetRasterImageList=AValue then Exit;
  160. if Assigned(FTargetRasterImageList) then FTargetRasterImageList.Clear;
  161. FTargetRasterImageList:=AValue;
  162. QueryRasterize;
  163. end;
  164. procedure TBGRASVGImageList.SetWidth(AValue: integer);
  165. begin
  166. if FWidth = AValue then
  167. Exit;
  168. FWidth := AValue;
  169. QueryRasterize;
  170. end;
  171. procedure TBGRASVGImageList.WriteData(Stream: TStream);
  172. var
  173. FXMLConf: TXMLConfig;
  174. FTempStream: TStringStream;
  175. FNormalizedData: string;
  176. begin
  177. FXMLConf := TXMLConfig.Create(Self);
  178. FTempStream := TStringStream.Create;
  179. try
  180. Save(FXMLConf);
  181. // Save to temporary string stream.
  182. // EOL marker will depend on OS (#13#10 or #10),
  183. // because TXMLConfig automatically changes EOL to platform default.
  184. FXMLConf.SaveToStream(FTempStream);
  185. // Normalize EOL marker, as data will be saved as binary data.
  186. // Saving without normalization would lead to different binary
  187. // data when saving on different platforms.
  188. FNormalizedData := AdjustLineBreaks(FTempStream.DataString, FDataLineBreak);
  189. if FNormalizedData <> '' then
  190. Stream.WriteBuffer(FNormalizedData[1], Length(FNormalizedData));
  191. FXMLConf.Flush;
  192. finally
  193. FXMLConf.Free;
  194. FTempStream.Free;
  195. end;
  196. end;
  197. procedure TBGRASVGImageList.Load(const XMLConf: TXMLConfig);
  198. var
  199. i, j, index: integer;
  200. begin
  201. try
  202. FItems.Clear;
  203. j := XMLConf.GetValue('Count', 0);
  204. for i := 0 to j - 1 do
  205. begin
  206. index := FItems.Add(TStringList.Create);
  207. FItems[index].Text := XMLConf.GetValue('Item' + i.ToString + '/SVG', '');
  208. end;
  209. finally
  210. end;
  211. end;
  212. procedure TBGRASVGImageList.Save(const XMLConf: TXMLConfig);
  213. var
  214. i: integer;
  215. begin
  216. try
  217. XMLConf.SetValue('Count', FItems.Count);
  218. for i := 0 to FItems.Count - 1 do
  219. XMLConf.SetValue('Item' + i.ToString + '/SVG', AdjustLineBreaks(FItems[i].Text, FDataLineBreak));
  220. finally
  221. end;
  222. end;
  223. procedure TBGRASVGImageList.DefineProperties(Filer: TFiler);
  224. begin
  225. inherited DefineProperties(Filer);
  226. Filer.DefineBinaryProperty('Items', ReadData, WriteData, True);
  227. end;
  228. constructor TBGRASVGImageList.Create(AOwner: TComponent);
  229. begin
  230. inherited Create(AOwner);
  231. FItems := TListOfTStringList.Create(True);
  232. FWidth := 16;
  233. FHeight := 16;
  234. FReferenceDPI := 96;
  235. FUseSVGAlignment:= false;
  236. FHorizontalAlignment := taCenter;
  237. FVerticalAlignment := tlCenter;
  238. FDataLineBreak := DefaultTextLineBreakStyle;
  239. end;
  240. destructor TBGRASVGImageList.Destroy;
  241. begin
  242. FItems.Free;
  243. inherited Destroy;
  244. end;
  245. function TBGRASVGImageList.Add(ASVG: string): integer;
  246. var
  247. list: TStringList;
  248. begin
  249. list := TStringList.Create;
  250. list.Text := ASVG;
  251. Result := FItems.Add(list);
  252. QueryRasterize;
  253. end;
  254. procedure TBGRASVGImageList.Remove(AIndex: integer);
  255. begin
  256. FItems.Remove(FItems[AIndex]);
  257. QueryRasterize;
  258. end;
  259. procedure TBGRASVGImageList.Exchange(AIndex1, AIndex2: integer);
  260. begin
  261. FItems.Exchange(AIndex1, AIndex2);
  262. QueryRasterize;
  263. end;
  264. function TBGRASVGImageList.GetSVGString(AIndex: integer): string;
  265. begin
  266. Result := FItems[AIndex].Text;
  267. end;
  268. procedure TBGRASVGImageList.Rasterize;
  269. begin
  270. if Assigned(FTargetRasterImageList) then
  271. begin
  272. FTargetRasterImageList.Clear;
  273. FTargetRasterImageList.Width := Width;
  274. FTargetRasterImageList.Height := Height;
  275. {$IFDEF DARWIN}
  276. PopulateImageList(FTargetRasterImageList, [Width, Width*2]);
  277. {$ELSE}
  278. PopulateImageList(FTargetRasterImageList, [Width]);
  279. {$ENDIF}
  280. end;
  281. end;
  282. procedure TBGRASVGImageList.RasterizeIfNeeded;
  283. begin
  284. if not FRasterized then
  285. begin
  286. Rasterize;
  287. FRasterized := true;
  288. end;
  289. end;
  290. procedure TBGRASVGImageList.QueryRasterize;
  291. var method: TThreadMethod;
  292. begin
  293. FRasterized := false;
  294. method := RasterizeIfNeeded;
  295. TThread.ForceQueue(nil, method);
  296. end;
  297. procedure TBGRASVGImageList.Replace(AIndex: integer; ASVG: string);
  298. begin
  299. FItems[AIndex].Text := ASVG;
  300. QueryRasterize;
  301. end;
  302. function TBGRASVGImageList.GetCount: integer;
  303. begin
  304. Result := FItems.Count;
  305. end;
  306. function TBGRASVGImageList.GetScaledSize(ATargetDPI: integer): TSize;
  307. begin
  308. result.cx := MulDiv(Width, ATargetDPI, ReferenceDPI);
  309. result.cy := MulDiv(Height, ATargetDPI, ReferenceDPI);
  310. end;
  311. function TBGRASVGImageList.GetBGRABitmap(AIndex: integer; AWidth,
  312. AHeight: integer): TBGRABitmap;
  313. begin
  314. result := GetBGRABitmap(AIndex, AWidth, AHeight, UseSVGAlignment);
  315. end;
  316. function TBGRASVGImageList.GetBGRABitmap(AIndex: integer; AWidth, AHeight: integer;
  317. AUseSVGAlignment: boolean): TBGRABitmap;
  318. var
  319. bmp: TBGRABitmap;
  320. svg: TBGRASVG;
  321. begin
  322. bmp := TBGRABitmap.Create(AWidth, AHeight);
  323. svg := TBGRASVG.CreateFromString(FItems[AIndex].Text);
  324. try
  325. svg.StretchDraw(bmp.Canvas2D, 0, 0, AWidth, AHeight, AUseSVGAlignment);
  326. finally
  327. svg.Free;
  328. end;
  329. Result := bmp;
  330. end;
  331. function TBGRASVGImageList.GetBitmap(AIndex: integer; AWidth, AHeight: integer): TBitmap;
  332. begin
  333. result := GetBitmap(AIndex, AWidth, AHeight, UseSVGAlignment);
  334. end;
  335. function TBGRASVGImageList.GetBitmap(AIndex: integer; AWidth, AHeight: integer;
  336. AUseSVGAlignment: boolean): TBitmap;
  337. var
  338. bmp: TBGRABitmap;
  339. ms: TMemoryStream;
  340. begin
  341. bmp := GetBGRABitmap(AIndex, AWidth, AHeight, AUseSVGAlignment);
  342. ms := TMemoryStream.Create;
  343. bmp.Bitmap.SaveToStream(ms);
  344. bmp.Free;
  345. Result := TBitmap.Create;
  346. ms.Position := 0;
  347. Result.LoadFromStream(ms);
  348. ms.Free;
  349. end;
  350. procedure TBGRASVGImageList.Draw(AIndex: integer; AControl: TControl;
  351. ACanvas: TCanvas; ALeft, ATop, AWidth, AHeight: integer);
  352. begin
  353. Draw(AIndex, AControl, ACanvas, ALeft, ATop, AWidth, AHeight, UseSVGAlignment);
  354. end;
  355. procedure TBGRASVGImageList.Draw(AIndex: integer; AControl: TControl; ACanvas: TCanvas;
  356. ALeft, ATop, AWidth, AHeight: integer; AUseSVGAlignment: boolean; AOpacity: byte);
  357. begin
  358. Draw(AIndex, AControl.GetCanvasScaleFactor, ACanvas, ALeft, ATop, AWidth, AHeight,
  359. AUseSVGAlignment, AOpacity);
  360. end;
  361. procedure TBGRASVGImageList.Draw(AIndex: integer; ACanvasScale: single;
  362. ACanvas: TCanvas; ALeft, ATop, AWidth, AHeight: integer);
  363. begin
  364. Draw(AIndex, ACanvasScale, ACanvas, ALeft, ATop, AWidth, AHeight, UseSVGAlignment);
  365. end;
  366. procedure TBGRASVGImageList.Draw(AIndex: integer; ACanvasScale: single; ACanvas: TCanvas;
  367. ALeft, ATop, AWidth, AHeight: integer; AUseSVGAlignment: boolean; AOpacity: byte);
  368. var
  369. bmp: TBGRABitmap;
  370. begin
  371. if (AWidth = 0) or (AHeight = 0) or (ACanvasScale = 0) then
  372. Exit;
  373. bmp := TBGRABitmap.Create(round(AWidth * ACanvasScale), round(AHeight * ACanvasScale));
  374. try
  375. Draw(AIndex, bmp, rectF(0, 0, bmp.Width, bmp.Height), AUseSVGAlignment);
  376. bmp.ApplyGlobalOpacity(AOpacity);
  377. bmp.Draw(ACanvas, RectWithSize(ALeft, ATop, AWidth, AHeight), False);
  378. finally
  379. bmp.Free;
  380. end;
  381. end;
  382. procedure TBGRASVGImageList.Draw(AIndex: integer; ABitmap: TBGRABitmap; const ARectF: TRectF);
  383. begin
  384. Draw(AIndex, ABitmap, ARectF, UseSVGAlignment);
  385. end;
  386. procedure TBGRASVGImageList.Draw(AIndex: integer; ABitmap: TBGRABitmap; const ARectF: TRectF;
  387. AUseSVGAlignment: boolean);
  388. var
  389. svg: TBGRASVG;
  390. begin
  391. svg := TBGRASVG.CreateFromString(FItems[AIndex].Text);
  392. try
  393. if AUseSVGAlignment then
  394. svg.StretchDraw(ABitmap.Canvas2D, ARectF, true)
  395. else svg.StretchDraw(ABitmap.Canvas2D, HorizontalAlignment, VerticalAlignment, ARectF.Left, ARectF.Top, ARectF.Width, ARectF.Height);
  396. finally
  397. svg.Free;
  398. end;
  399. end;
  400. procedure TBGRASVGImageList.PopulateImageList(const AImageList: TImageList;
  401. AWidths: array of integer);
  402. var
  403. i, j: integer;
  404. arr: array of TCustomBitmap;
  405. begin
  406. AImageList.Width := AWidths[0];
  407. AImageList.Height := MulDiv(AWidths[0], Height, Width);
  408. AImageList.Scaled := True;
  409. AImageList.RegisterResolutions(AWidths);
  410. SetLength({%H-}arr, Length(AWidths));
  411. for j := 0 to Count - 1 do
  412. begin
  413. for i := 0 to Length(arr) - 1 do
  414. arr[i] := GetBitmap(j, AWidths[i], MulDiv(AWidths[i], Height, Width), True);
  415. AImageList.AddMultipleResolutions(arr);
  416. for i := 0 to Length(arr) - 1 do
  417. TBitmap(Arr[i]).Free;
  418. end;
  419. end;
  420. end.