ubrushtype.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UBrushType;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, UScripting, BGRABitmap, BGRABitmapTypes;
  7. type
  8. { TLazPaintBrush }
  9. TLazPaintBrush = class
  10. private
  11. function GetAsString: string;
  12. procedure SetAsString(AValue: string);
  13. protected
  14. FVariable: TVariableSet;
  15. FSize: Single;
  16. FBrushImage: TBGRABitmap;
  17. FSourceImage: TBGRABitmap;
  18. FSourceImageFetched: boolean;
  19. procedure SetIsGradient(AValue: boolean);
  20. procedure SetSize(AValue: Single);
  21. procedure InvalidateBrush;
  22. procedure InvalidateSource;
  23. function GetFileName: string;
  24. function GetIsGradient: boolean;
  25. function GetSideCount: integer;
  26. function GetSourceImage: TBGRABitmap;
  27. function MakeBrushBitmap: TBGRABitmap;
  28. procedure SetFileName(AValue: string);
  29. procedure SetSideCount(AValue: integer);
  30. function GetBrushImage: TBGRABitmap;
  31. function GetStream64: string;
  32. procedure SetStream64(AValue: string);
  33. procedure Init;
  34. public
  35. constructor Create;
  36. constructor Create(ASideCount: integer; AIsGradient: boolean);
  37. constructor CreateFromStream64(AStream64: string);
  38. constructor CreateFromStream(AStream: TStream);
  39. destructor Destroy; override;
  40. procedure AssignBrushImage(ABitmap: TBGRABitmap);
  41. function MakeColoredBrushImage(AColor: TBGRAPixel): TBGRABitmap;
  42. procedure Put(ADest: TBGRABitmap; x,y: integer; AColor: TBGRAPixel);
  43. property Size: Single read FSize write SetSize;
  44. property IsGradient: boolean read GetIsGradient write SetIsGradient;
  45. property SideCount: integer read GetSideCount write SetSideCount;
  46. property FileName: string read GetFileName write SetFileName;
  47. property Stream64: string read GetStream64 write SetStream64;
  48. property SourceImage: TBGRABitmap read GetSourceImage;
  49. property BrushImage: TBGRABitmap read GetBrushImage;
  50. property AsString: string read GetAsString write SetAsString;
  51. end;
  52. implementation
  53. uses ULoadImage, Math, base64, Dialogs;
  54. { TLazPaintBrush }
  55. function TLazPaintBrush.MakeBrushBitmap: TBGRABitmap;
  56. var source: TBGRABitmap;
  57. sourceSize, resultSize: integer;
  58. brushSides: integer;
  59. brushGrad: boolean;
  60. pts: ArrayOfTPointF;
  61. i: Integer;
  62. tmp: TBGRABitmap;
  63. ASize: single;
  64. orig: TPointF;
  65. begin
  66. ASize := Size;
  67. resultSize := ceil(ASize);
  68. if resultSize < 0 then resultSize:= 0;
  69. if not odd(resultSize) then resultSize += 1;
  70. result:= TBGRABitmap.Create(resultSize,resultSize,BGRAWhite);
  71. source := SourceImage;
  72. brushGrad := IsGradient;
  73. brushSides := SideCount;
  74. if source <> nil then
  75. begin
  76. if source.Width > source.Height then sourceSize := source.Width else sourceSize := source.Height;
  77. end
  78. else sourceSize := 0;
  79. if sourceSize <> 0 then
  80. begin
  81. if sourceSize > 2*ASize then
  82. begin
  83. tmp := source.Resample(round(source.Width/sourceSize*ASize),round(source.Height/sourceSize*ASize)) as TBGRABitmap;
  84. orig := PointF((result.Width-tmp.Width)/2,(result.Height-tmp.Height)/2);
  85. result.PutImageAffine(orig, orig+PointF(tmp.Width,0),orig+PointF(0,tmp.Height), tmp);
  86. tmp.Free;
  87. end else
  88. begin
  89. orig := PointF((result.Width-source.Width/sourceSize*ASize)/2,(result.Height-source.Height/sourceSize*ASize)/2);
  90. result.PutImageAffine(orig, orig+PointF(source.Width/sourceSize*ASize,0),orig+PointF(0,source.Height/sourceSize*ASize), source);
  91. end;
  92. end else
  93. begin
  94. if brushGrad then
  95. begin
  96. if brushSides <= 2 then
  97. begin
  98. result.GradientFill(0,0,result.width,result.height, BGRABlack,BGRAPixelTransparent, gtRadial, PointF((result.Width-1)/2,(result.Height-1)/2),
  99. PointF((result.Width-1)/2+(ASize+0.4)/2,(result.Height-1)/2),dmDrawWithTransparency);
  100. end else
  101. begin
  102. tmp := TBGRABitmap.Create(result.width,result.height);
  103. result.Fill(BGRABlack);
  104. for i := 0 to brushSides-1 do
  105. begin
  106. tmp.GradientFill(0,0,result.width,result.height, BGRABlack,BGRAWhite, gtLinear, PointF((result.Width-1)/2,(result.Height-1)/2),
  107. PointF((result.Width-1)/2+(ASize+0.4)/2*(sin(i*2*Pi/brushSides)+sin((i+1)*2*Pi/brushSides))/2,
  108. (result.Height-1)/2-(ASize+0.4)/2*(cos(i*2*Pi/brushSides)+cos((i+1)*2*Pi/brushSides))/2),dmDrawWithTransparency);
  109. result.BlendImage(0,0,tmp,boLighten);
  110. end;
  111. tmp.Free;
  112. end;
  113. end else
  114. begin
  115. if brushSides <= 2 then
  116. begin
  117. result.FillEllipseAntialias((result.Width-1)/2,(result.Height-1)/2,ASize/2,ASize/2,BGRABlack);
  118. end else
  119. begin
  120. pts := nil;
  121. setlength(pts, brushSides);
  122. for i := 0 to high(pts) do
  123. pts[i] := PointF((result.Width-1)/2+sin(i*2*Pi/brushSides)*ASize/2,
  124. (result.Height-1)/2-cos(i*2*Pi/brushSides)*ASize/2);
  125. result.FillPolyAntialias(pts,BGRABlack);
  126. end;
  127. end;
  128. end;
  129. result.ConvertToLinearRGB;
  130. result.LinearNegative;
  131. end;
  132. function TLazPaintBrush.GetFileName: string;
  133. begin
  134. result := FVariable.Strings['FileName'];
  135. end;
  136. function TLazPaintBrush.GetIsGradient: boolean;
  137. begin
  138. result := FVariable.Booleans['IsGradient'];
  139. end;
  140. function TLazPaintBrush.GetSideCount: integer;
  141. begin
  142. result := FVariable.Integers['SideCount'];
  143. end;
  144. function TLazPaintBrush.GetSourceImage: TBGRABitmap;
  145. var
  146. string64: TStringStream;
  147. decode64: TBase64DecodingStream;
  148. temp: TMemoryStream;
  149. begin
  150. if not FSourceImageFetched then
  151. begin
  152. FSourceImage := nil;
  153. try
  154. if FileName <> '' then
  155. FSourceImage := LoadFlatImageUTF8(FileName, 0).bmp
  156. else
  157. if Stream64<> '' then
  158. begin
  159. string64 := TStringStream.Create(Stream64);
  160. temp := TMemoryStream.Create;
  161. decode64 := TBase64DecodingStream.Create(string64);
  162. try
  163. temp.CopyFrom(decode64, decode64.Size);
  164. temp.Position := 0;
  165. FSourceImage := TBGRABitmap.Create(temp);
  166. finally
  167. decode64.Free;
  168. temp.Free;
  169. string64.Free;
  170. end;
  171. end;
  172. except
  173. on ex:exception do
  174. ShowMessage(ex.Message);
  175. end;
  176. FSourceImageFetched := true;
  177. end;
  178. result := FSourceImage;
  179. end;
  180. procedure TLazPaintBrush.SetFileName(AValue: string);
  181. begin
  182. if AValue = FileName then exit;
  183. if AValue <> '' then Stream64 := '';
  184. FVariable.Strings['FileName'] := AValue;
  185. InvalidateSource;
  186. end;
  187. procedure TLazPaintBrush.SetSideCount(AValue: integer);
  188. begin
  189. if AValue <= 2 then AValue := 0;
  190. if AValue = SideCount then exit;
  191. FVariable.Integers['SideCount'] := AValue;
  192. InvalidateBrush;
  193. end;
  194. procedure TLazPaintBrush.Init;
  195. begin
  196. FVariable := TVariableSet.Create('');
  197. IsGradient:= false;
  198. SideCount:= 0;
  199. Size := 10;
  200. FileName := '';
  201. Stream64 := '';
  202. FSourceImage := nil;
  203. FSourceImageFetched := false;
  204. end;
  205. function TLazPaintBrush.GetBrushImage: TBGRABitmap;
  206. begin
  207. if FBrushImage = nil then
  208. FBrushImage := MakeBrushBitmap;
  209. result := FBrushImage;
  210. end;
  211. function TLazPaintBrush.GetStream64: string;
  212. begin
  213. result := FVariable.Strings['Stream64'];
  214. end;
  215. procedure TLazPaintBrush.SetStream64(AValue: string);
  216. begin
  217. if AValue = Stream64 then exit;
  218. if AValue <> '' then FileName := '';
  219. FVariable.Strings['Stream64'] := AValue;
  220. InvalidateSource;
  221. end;
  222. function TLazPaintBrush.GetAsString: string;
  223. begin
  224. result := FVariable.VariablesAsString;
  225. end;
  226. procedure TLazPaintBrush.SetAsString(AValue: string);
  227. var temp: TVariableSet;
  228. begin
  229. temp := TVariableSet.Create('',AValue);
  230. temp.CopyValuesTo(FVariable);
  231. temp.Free;
  232. end;
  233. procedure TLazPaintBrush.SetIsGradient(AValue: boolean);
  234. begin
  235. if IsGradient=AValue then Exit;
  236. FVariable.Booleans['IsGradient'] := AValue;
  237. InvalidateBrush;
  238. end;
  239. procedure TLazPaintBrush.SetSize(AValue: Single);
  240. begin
  241. if AValue < 1 then AValue := 1;
  242. if FSize=AValue then Exit;
  243. FSize:=AValue;
  244. InvalidateBrush;
  245. end;
  246. procedure TLazPaintBrush.InvalidateBrush;
  247. begin
  248. FreeAndNil(FBrushImage);
  249. end;
  250. procedure TLazPaintBrush.InvalidateSource;
  251. begin
  252. FreeAndNil(FSourceImage);
  253. FSourceImageFetched := false;
  254. InvalidateBrush;
  255. end;
  256. constructor TLazPaintBrush.Create;
  257. begin
  258. Init;
  259. end;
  260. constructor TLazPaintBrush.Create(ASideCount: integer; AIsGradient: boolean);
  261. begin
  262. Init;
  263. SideCount:= ASideCount;
  264. IsGradient := AIsGradient;
  265. end;
  266. constructor TLazPaintBrush.CreateFromStream64(AStream64: string);
  267. begin
  268. Init;
  269. Stream64 := AStream64;
  270. end;
  271. constructor TLazPaintBrush.CreateFromStream(AStream: TStream);
  272. var
  273. encoder: TBase64EncodingStream;
  274. str: TStringStream;
  275. begin
  276. Init;
  277. str:= TStringStream.Create('');
  278. encoder := TBase64EncodingStream.Create(str);
  279. encoder.CopyFrom(AStream, AStream.Size);
  280. encoder.Free;
  281. Stream64:= str.DataString;
  282. str.Free;
  283. end;
  284. destructor TLazPaintBrush.Destroy;
  285. begin
  286. FreeAndNil(FBrushImage);
  287. FreeAndNil(FSourceImage);
  288. FreeAndNil(FVariable);
  289. inherited Destroy;
  290. end;
  291. procedure TLazPaintBrush.AssignBrushImage(ABitmap: TBGRABitmap);
  292. var
  293. temp: TMemoryStream;
  294. encode64: TBase64EncodingStream;
  295. str: TStringStream;
  296. reduced,filtered,opaque: TBGRABitmap;
  297. begin
  298. if (ABitmap.Width > 999) or (ABitmap.Height > 999) then
  299. begin
  300. reduced := ABitmap.Resample(Min(ABitmap.Width,999),Min(ABitmap.Height,999)) as TBGRABitmap;
  301. try
  302. AssignBrushImage(reduced);
  303. finally
  304. reduced.free;
  305. end;
  306. exit;
  307. end;
  308. str := TStringStream.Create('');
  309. filtered := ABitmap.FilterGrayscale as TBGRABitmap;
  310. if filtered.HasTransparentPixels then
  311. begin
  312. opaque := TBGRABitmap.Create(filtered.Width,filtered.Height,BGRAWhite);
  313. opaque.PutImage(0,0,filtered,dmDrawWithTransparency);
  314. filtered.Free;
  315. end else
  316. opaque := filtered;
  317. try
  318. encode64 := TBase64EncodingStream.Create(str);
  319. temp := TMemoryStream.Create;
  320. try
  321. opaque.SaveToStreamAs(temp,ifLazPaint);
  322. temp.Position := 0;
  323. encode64.CopyFrom(temp,temp.Size);
  324. Stream64:= str.DataString;
  325. finally
  326. temp.Free;
  327. encode64.Free;
  328. end;
  329. finally
  330. str.Free;
  331. opaque.free;
  332. end;
  333. end;
  334. function TLazPaintBrush.MakeColoredBrushImage(AColor: TBGRAPixel): TBGRABitmap;
  335. begin
  336. result := TBGRABitmap.Create(BrushImage.Width,BrushImage.Height);
  337. result.FillMask(0,0,BrushImage,AColor);
  338. end;
  339. procedure TLazPaintBrush.Put(ADest: TBGRABitmap; x, y: integer;
  340. AColor: TBGRAPixel);
  341. var
  342. img: TBGRABitmap;
  343. begin
  344. img := MakeColoredBrushImage(AColor);
  345. ADest.PutImage(x-img.width div 2,y-img.height div 2,img,dmDrawWithTransparency);
  346. img.Free;
  347. end;
  348. end.