Img32.Fmt.SVG.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. unit Img32.Fmt.SVG;
  2. (*******************************************************************************
  3. * Author : Angus Johnson *
  4. * Version : 4.7 *
  5. * Date : 6 January 2025 *
  6. * Website : http://www.angusj.com *
  7. * Copyright : Angus Johnson 2019-2025 *
  8. * Purpose : SVG file format extension for TImage32 *
  9. * License : http://www.boost.org/LICENSE_1_0.txt *
  10. *******************************************************************************)
  11. interface
  12. {$I Img32.inc}
  13. uses
  14. {$IFDEF MSWINDOWS} Windows, {$ENDIF}
  15. {$IF NOT DEFINED(NEWPOSFUNC) OR DEFINED(FPC)} StrUtils, {$IFEND}
  16. {$IFDEF UNICODE} AnsiStrings, {$ENDIF}
  17. SysUtils, Classes, Math,
  18. {$IFDEF XPLAT_GENERICS} Generics.Collections, Generics.Defaults, {$ENDIF}
  19. Img32, Img32.Vector, Img32.SVG.Core, Img32.SVG.Reader
  20. {$IF DEFINED(USING_LCL)}, Types{$IFEND}
  21. ;
  22. type
  23. TImageFormat_SVG = class(TImageFormat)
  24. public
  25. class function IsValidImageStream(stream: TStream): Boolean; override;
  26. function LoadFromStream(stream: TStream;
  27. img32: TImage32; imgIndex: integer = 0): Boolean; override;
  28. // SaveToStream: not implemented for SVG streams
  29. procedure SaveToStream(stream: TStream;
  30. img32: TImage32; quality: integer = 0); override;
  31. class function CanCopyToClipboard: Boolean; override;
  32. class function CopyToClipboard(img32: TImage32): Boolean; override;
  33. class function CanPasteFromClipboard: Boolean; override;
  34. class function PasteFromClipboard(img32: TImage32): Boolean; override;
  35. end;
  36. TSvgListObject = class
  37. xml : string;
  38. name : string;
  39. end;
  40. TSvgImageList32 = class(TInterfacedObj, INotifySender)
  41. private
  42. fReader : TSvgReader;
  43. {$IFDEF XPLAT_GENERICS}
  44. fList : TList<TSvgListObject>;
  45. {$ELSE}
  46. fList : TList;
  47. {$ENDIF}
  48. fDefWidth : integer;
  49. fDefHeight : integer;
  50. fRecipientList : TRecipients;
  51. fUpdateCnt : integer;
  52. {$IFDEF MSWINDOWS}
  53. fResName : string;
  54. procedure SetResName(const resName: string);
  55. {$ENDIF}
  56. procedure SetDefWidth(value: integer);
  57. procedure SetDefHeight(value: integer);
  58. protected
  59. procedure Changed; virtual;
  60. procedure BeginUpdate;
  61. procedure EndUpdate;
  62. procedure NotifyRecipients(notifyFlag: TImg32Notification);
  63. public
  64. constructor Create;
  65. destructor Destroy; override;
  66. procedure Clear;
  67. function Count: integer;
  68. function Find(const aName: string): integer;
  69. procedure AddRecipient(recipient: INotifyRecipient);
  70. procedure DeleteRecipient(recipient: INotifyRecipient);
  71. function CreateImage(index: integer): TImage32;
  72. procedure GetImage(index: integer; image: TImage32); overload;
  73. procedure GetImage(index: integer; image: TImage32; out aName: string); overload;
  74. procedure Add(const aName, xml: string);
  75. procedure AddFromFile(const aName, filename: string);
  76. procedure AddFromResource(const aName, resName: string; resType: PChar);
  77. procedure Insert(index: integer; const name, xml: string);
  78. procedure Move(currentIndex, newIndex: integer);
  79. procedure Delete(index: integer);
  80. property DefaultWidth: integer read fDefWidth write SetDefWidth;
  81. property DefaultHeight: integer read fDefHeight write SetDefHeight;
  82. {$IFDEF MSWINDOWS}
  83. property ResourceName: string read fResName write SetResName;
  84. {$ENDIF}
  85. end;
  86. implementation
  87. //------------------------------------------------------------------------------
  88. // Three routines used to enumerate a resource type
  89. //------------------------------------------------------------------------------
  90. function Is_IntResource(lpszType: PChar): Boolean;
  91. begin
  92. Result := NativeUInt(lpszType) shr 16 = 0;
  93. end;
  94. //------------------------------------------------------------------------------
  95. function ResourceNameToString(lpszName: PChar): string;
  96. begin
  97. if Is_IntResource(lpszName) then
  98. Result := '#' + IntToStr(NativeUInt(lpszName)) else
  99. Result := lpszName;
  100. end;
  101. //------------------------------------------------------------------------------
  102. function EnumResNameProc(hModule: HMODULE; lpszType, lpszName: PChar;
  103. lParam: NativeInt): Boolean; stdcall;
  104. var
  105. n: string;
  106. begin
  107. n:= ResourceNameToString(lpszName);
  108. TSvgImageList32(lParam).AddFromResource(n, n, lpszType);
  109. Result := true;
  110. end;
  111. //------------------------------------------------------------------------------
  112. // TSvgImageList32
  113. //------------------------------------------------------------------------------
  114. constructor TSvgImageList32.Create;
  115. begin
  116. fReader := TSvgReader.Create;
  117. {$IFDEF XPLAT_GENERICS}
  118. fList := TList<TSvgListObject>.Create;
  119. {$ELSE}
  120. fList := TList.Create;
  121. {$ENDIF}
  122. end;
  123. //------------------------------------------------------------------------------
  124. destructor TSvgImageList32.Destroy;
  125. begin
  126. NotifyRecipients(inDestroy);
  127. Clear;
  128. fList.Free;
  129. fReader.Free;
  130. inherited;
  131. end;
  132. //------------------------------------------------------------------------------
  133. {$IFDEF MSWINDOWS}
  134. procedure TSvgImageList32.SetResName(const resName: string);
  135. begin
  136. if fResName = resName then Exit;
  137. fResName := resName;
  138. BeginUpdate;
  139. try
  140. Clear;
  141. EnumResourceNames(HInstance, PChar(resName), @EnumResNameProc, lParam(self));
  142. finally
  143. EndUpdate;
  144. end;
  145. end;
  146. //------------------------------------------------------------------------------
  147. {$ENDIF}
  148. function TSvgImageList32.Count: integer;
  149. begin
  150. result := fList.Count;
  151. end;
  152. //------------------------------------------------------------------------------
  153. procedure TSvgImageList32.Clear;
  154. var
  155. i: integer;
  156. begin
  157. for i := 0 to fList.Count -1 do
  158. TSvgListObject(fList[i]).Free;
  159. fList.Clear;
  160. Changed;
  161. end;
  162. //------------------------------------------------------------------------------
  163. function TSvgImageList32.Find(const aName: string): integer;
  164. var
  165. i: integer;
  166. begin
  167. for i := 0 to fList.Count -1 do
  168. with TSvgListObject(fList[i]) do
  169. if SameText(name, aName) then
  170. begin
  171. Result := i;
  172. Exit;
  173. end;
  174. Result := -1;
  175. end;
  176. //------------------------------------------------------------------------------
  177. procedure TSvgImageList32.GetImage(index: integer; image: TImage32; out aName: string);
  178. begin
  179. if not Assigned(image) or (index < 0) or (index >= count) then Exit;
  180. if image.IsEmpty then
  181. image.SetSize(fDefWidth, fDefHeight);
  182. with TSvgListObject(fList[index]) do
  183. begin
  184. fReader.LoadFromString(xml);
  185. aName := name;
  186. end;
  187. fReader.DrawImage(image, true);
  188. end;
  189. //------------------------------------------------------------------------------
  190. function TSvgImageList32.CreateImage(index: integer): TImage32;
  191. begin
  192. Result := TImage32.Create(DefaultWidth, DefaultHeight);
  193. GetImage(index, Result);
  194. end;
  195. //------------------------------------------------------------------------------
  196. procedure TSvgImageList32.GetImage(index: integer; image: TImage32);
  197. var
  198. dummy: string;
  199. begin
  200. GetImage(index, image, dummy);
  201. end;
  202. //------------------------------------------------------------------------------
  203. procedure TSvgImageList32.Add(const aName, xml: string);
  204. begin
  205. Insert(count, aName, xml);
  206. end;
  207. //------------------------------------------------------------------------------
  208. procedure TSvgImageList32.AddFromFile(const aName, filename: string);
  209. begin
  210. if not FileExists(filename) then Exit;
  211. with TStringList.Create do
  212. try
  213. LoadFromFile(filename);
  214. Self.Insert(Self.Count, aName, Text);
  215. finally
  216. Free;
  217. end;
  218. end;
  219. //------------------------------------------------------------------------------
  220. procedure TSvgImageList32.AddFromResource(const aName, resName: string; resType: PChar);
  221. var
  222. rs: TResourceStream;
  223. ansi: AnsiString;
  224. begin
  225. rs := TResourceStream.Create(hInstance, resName, resType);
  226. try
  227. SetLength(ansi, rs.Size);
  228. rs.Read(ansi[1], rs.Size);
  229. Self.Insert(Self.Count, aName, string(ansi));
  230. finally
  231. rs.Free;
  232. end;
  233. end;
  234. //------------------------------------------------------------------------------
  235. procedure TSvgImageList32.Insert(index: integer; const name, xml: string);
  236. var
  237. lo: TSvgListObject;
  238. begin
  239. if index < 0 then index := 0
  240. else if index > Count then index := Count;
  241. lo := TSvgListObject.Create;
  242. lo.name := name;
  243. lo.xml := xml;
  244. fList.Insert(index, lo);
  245. Changed;
  246. end;
  247. //------------------------------------------------------------------------------
  248. procedure TSvgImageList32.Move(currentIndex, newIndex: integer);
  249. begin
  250. fList.Move(currentIndex, newIndex);
  251. end;
  252. //------------------------------------------------------------------------------
  253. procedure TSvgImageList32.Delete(index: integer);
  254. begin
  255. TSvgListObject(fList[index]).Free;
  256. fList.Delete(index);
  257. end;
  258. //------------------------------------------------------------------------------
  259. procedure TSvgImageList32.BeginUpdate;
  260. begin
  261. inc(fUpdateCnt);
  262. end;
  263. //------------------------------------------------------------------------------
  264. procedure TSvgImageList32.EndUpdate;
  265. begin
  266. dec(fUpdateCnt);
  267. if fUpdateCnt = 0 then Changed;
  268. end;
  269. //------------------------------------------------------------------------------
  270. procedure TSvgImageList32.Changed;
  271. begin
  272. if (fUpdateCnt = 0) then
  273. NotifyRecipients(inStateChange);
  274. end;
  275. //------------------------------------------------------------------------------
  276. procedure TSvgImageList32.SetDefWidth(value: integer);
  277. begin
  278. if fDefWidth = value then Exit;
  279. fDefWidth := value;
  280. Changed;
  281. end;
  282. //------------------------------------------------------------------------------
  283. procedure TSvgImageList32.SetDefHeight(value: integer);
  284. begin
  285. if fDefHeight = value then Exit;
  286. fDefHeight := value;
  287. Changed;
  288. end;
  289. //------------------------------------------------------------------------------
  290. procedure TSvgImageList32.AddRecipient(recipient: INotifyRecipient);
  291. var
  292. len: integer;
  293. begin
  294. len := Length(fRecipientList);
  295. SetLength(fRecipientList, len+1);
  296. fRecipientList[len] := Recipient;
  297. end;
  298. //------------------------------------------------------------------------------
  299. procedure TSvgImageList32.DeleteRecipient(recipient: INotifyRecipient);
  300. var
  301. i, highI: integer;
  302. begin
  303. highI := High(fRecipientList);
  304. i := highI;
  305. while (i >= 0) and (fRecipientList[i] <> Recipient) do dec(i);
  306. if i < 0 then Exit;
  307. if i < highI then
  308. System.Move(fRecipientList[i+1], fRecipientList[i],
  309. (highI - i) * SizeOf(INotifyRecipient));
  310. SetLength(fRecipientList, highI);
  311. end;
  312. //------------------------------------------------------------------------------
  313. procedure TSvgImageList32.NotifyRecipients(notifyFlag: TImg32Notification);
  314. var
  315. i: integer;
  316. begin
  317. if fUpdateCnt > 0 then Exit;
  318. for i := High(fRecipientList) downto 0 do
  319. try
  320. //when destroying in a finalization section
  321. //it's possible for recipients to have been destroyed
  322. //without their destructors being called.
  323. fRecipientList[i].ReceiveNotification(self, notifyFlag);
  324. except
  325. end;
  326. end;
  327. //------------------------------------------------------------------------------
  328. // Loading (reading) SVG images from file ...
  329. //------------------------------------------------------------------------------
  330. function TImageFormat_SVG.LoadFromStream(stream: TStream;
  331. img32: TImage32; imgIndex: integer = 0): Boolean;
  332. var
  333. r: TRectWH;
  334. sx: double;
  335. begin
  336. with TSvgReader.Create do
  337. try
  338. Result := LoadFromStream(stream);
  339. if not Result then Exit;
  340. r := RootElement.viewboxWH;
  341. img32.BeginUpdate;
  342. try
  343. if img32.IsEmpty then
  344. begin
  345. with RootElement do
  346. if Width.IsValid and Height.IsValid then
  347. img32.SetSize(
  348. Round(Width.GetValue(defaultSvgWidth, 0)),
  349. Round(Height.GetValue(defaultSvgHeight, 0)))
  350. else if not r.IsEmpty then
  351. img32.SetSize(Round(r.Width), Round(r.Height))
  352. else
  353. img32.SetSize(defaultSvgWidth, defaultSvgHeight);
  354. end
  355. else if not r.IsEmpty then
  356. begin
  357. // scale the SVG to best fit the image dimensions
  358. sx := GetScaleForBestFit(r.Width, r.Height, img32.Width, img32.Height);
  359. img32.SetSize(Round(r.Width * sx), Round(r.Height * sx));
  360. end;
  361. //draw the SVG image to fit inside the canvas
  362. DrawImage(img32, True);
  363. finally
  364. img32.EndUpdate;
  365. end;
  366. finally
  367. Free;
  368. end;
  369. end;
  370. //------------------------------------------------------------------------------
  371. // Saving (writing) SVG images to file (not currently implemented) ...
  372. //------------------------------------------------------------------------------
  373. class function TImageFormat_SVG.IsValidImageStream(stream: TStream): Boolean;
  374. var
  375. i, savedPos, len: integer;
  376. buff: array [1..1024] of AnsiChar;
  377. begin
  378. Result := false;
  379. savedPos := stream.Position;
  380. len := Min(1024, stream.Size - savedPos);
  381. stream.Read(buff[1], len);
  382. stream.Position := savedPos;
  383. for i := 1 to len -4 do
  384. begin
  385. if buff[i] < #9 then Exit
  386. else if (buff[i] = '<') and
  387. (buff[i +1] = 's') and
  388. (buff[i +2] = 'v') and
  389. (buff[i +3] = 'g') then
  390. begin
  391. Result := true;
  392. break;
  393. end;
  394. end;
  395. end;
  396. //------------------------------------------------------------------------------
  397. procedure TImageFormat_SVG.SaveToStream(stream: TStream;
  398. img32: TImage32; quality: integer);
  399. begin
  400. //not enabled
  401. end;
  402. //------------------------------------------------------------------------------
  403. class function TImageFormat_SVG.CanCopyToClipboard: Boolean;
  404. begin
  405. Result := false;
  406. end;
  407. //------------------------------------------------------------------------------
  408. class function TImageFormat_SVG.CopyToClipboard(img32: TImage32): Boolean;
  409. begin
  410. Result := false;
  411. end;
  412. //------------------------------------------------------------------------------
  413. class function TImageFormat_SVG.CanPasteFromClipboard: Boolean;
  414. begin
  415. Result := false;
  416. end;
  417. //------------------------------------------------------------------------------
  418. class function TImageFormat_SVG.PasteFromClipboard(img32: TImage32): Boolean;
  419. begin
  420. Result := false;
  421. end;
  422. //------------------------------------------------------------------------------
  423. //------------------------------------------------------------------------------
  424. initialization
  425. TImage32.RegisterImageFormatClass('SVG', TImageFormat_SVG, cpLow);
  426. end.