utoolbrush.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UToolBrush;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, UToolBasic, BGRABitmapTypes, BGRABitmap, UTool,
  7. UBrushType, LCVectorialFill;
  8. type
  9. { TToolGenericBrush }
  10. TToolGenericBrush = class(TToolPen)
  11. private
  12. function GetBrushInfo: TLazPaintBrush;
  13. protected
  14. brushOrigin: TPointF;
  15. originDrawn: boolean;
  16. defaultBrush: TLazPaintBrush;
  17. function DrawBrushAt(toolDest: TBGRABitmap; x, y: single): TRect; virtual; abstract;
  18. procedure PrepareBrush(rightBtn: boolean); virtual; abstract;
  19. procedure ReleaseBrush; virtual; abstract;
  20. function StartDrawing(toolDest: TBGRABitmap; ptF: TPointF; rightBtn: boolean): TRect; override;
  21. function ContinueDrawing(toolDest: TBGRABitmap; {%H-}originF, destF: TPointF; {%H-}rightBtn: boolean): TRect; override;
  22. function GetBrushAlpha(AAlpha: byte): byte;
  23. function GetLayerOffset: TPoint; override;
  24. public
  25. constructor Create(AManager: TToolManager); override;
  26. function ToolUp: TRect; override;
  27. function SubPixelAccuracy: boolean; virtual;
  28. destructor Destroy; override;
  29. property BrushInfo: TLazPaintBrush read GetBrushInfo;
  30. end;
  31. { TToolBrush }
  32. TToolBrush = class(TToolGenericBrush)
  33. protected
  34. coloredBrushImage: TBGRABitmap;
  35. function DrawBrushAt(toolDest: TBGRABitmap; x, y: single): TRect; override;
  36. procedure PrepareBrush({%H-}rightBtn: boolean); override;
  37. procedure ReleaseBrush; override;
  38. function GetAllowedForeFillTypes: TVectorialFillTypes; override;
  39. function GetAllowedBackFillTypes: TVectorialFillTypes; override;
  40. public
  41. destructor Destroy; override;
  42. function GetContextualToolbars: TContextualToolbars; override;
  43. end;
  44. { TToolClone }
  45. TToolClone = class(TToolGenericBrush)
  46. protected
  47. class var RightClickHintShown: boolean;
  48. definingSource: boolean;
  49. class var sourceLayerId: integer;
  50. class var sourcePosition: TPoint;
  51. class var sourcePositionRelative: boolean;
  52. class var sourceFlattened: boolean;
  53. class var sourceDefined: boolean;
  54. function PickColorWithShift: boolean; override;
  55. function DrawBrushAt(toolDest: TBGRABitmap; x, y: single): TRect; override;
  56. procedure PrepareBrush(rightBtn: boolean); override;
  57. procedure ReleaseBrush; override;
  58. function DoToolMove(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF): TRect; override;
  59. function DoToolShiftClick({%H-}toolDest: TBGRABitmap; {%H-}ptF: TPointF; {%H-}rightBtn: boolean): TRect; override;
  60. public
  61. class procedure ForgetHintShown;
  62. function SubPixelAccuracy: boolean; override;
  63. constructor Create(AManager: TToolManager); override;
  64. destructor Destroy; override;
  65. function GetContextualToolbars: TContextualToolbars; override;
  66. function Render(VirtualScreen: TBGRABitmap; VirtualScreenWidth, VirtualScreenHeight: integer;
  67. BitmapToVirtualScreen: TBitmapToVirtualScreenFunction): TRect; override;
  68. end;
  69. implementation
  70. uses Math, UGraph, UResourceStrings, Graphics, LazPaintType;
  71. { TToolClone }
  72. function TToolClone.PickColorWithShift: boolean;
  73. begin
  74. Result:= false;
  75. end;
  76. function TToolClone.DrawBrushAt(toolDest: TBGRABitmap; x, y: single): TRect;
  77. var source: TBGRABitmap;
  78. sourceOfs: TPoint;
  79. sourceIdx: Integer;
  80. begin
  81. if definingSource then
  82. begin
  83. sourceOfs := Manager.Image.LayerOffset[Manager.Image.CurrentLayerIndex];
  84. sourcePosition := Point(round(x) + sourceOfs.x,round(y) + sourceOfs.y);
  85. sourceLayerId := Manager.Image.LayerId[Manager.Image.CurrentLayerIndex];
  86. sourcePositionRelative:= false;
  87. sourceFlattened := ssShift in ShiftState;
  88. sourceDefined := true;
  89. result := OnlyRenderChange;
  90. end else
  91. begin
  92. if not sourceDefined then
  93. begin
  94. Manager.ToolPopup(tpmRightClickForSource, 0, true);
  95. result := EmptyRect;
  96. exit;
  97. end;
  98. if (ssShift in ShiftState) or sourceFlattened then
  99. begin
  100. source := Manager.Image.RenderedImage;
  101. sourceOfs := Point(0,0);
  102. end else
  103. begin
  104. sourceIdx := Manager.Image.GetLayerIndexById(sourceLayerId);
  105. if sourceIdx = -1 then
  106. begin
  107. Manager.ToolPopup(tpmRightClickForSource, 0, true);
  108. result := EmptyRect;
  109. exit;
  110. end;
  111. source := Manager.Image.LayerBitmap[sourceIdx];
  112. sourceOfs := Manager.Image.LayerOffset[sourceIdx];
  113. end;
  114. if not SubPixelAccuracy then
  115. begin
  116. x := round(x);
  117. y := round(y);
  118. end;
  119. if not sourcePositionRelative then
  120. begin
  121. sourcePosition.x -= round(x) + sourceOfs.x;
  122. sourcePosition.y -= round(y) + sourceOfs.y;
  123. sourcePositionRelative := true;
  124. end;
  125. with BrushInfo.BrushImage do
  126. begin
  127. x -= (Width-1)/2;
  128. y -= (Height-1)/2;
  129. result := rect(floor(x-0.5),floor(y-0.5),ceil(x+0.5)+Width,ceil(y+0.5)+Height);
  130. end;
  131. toolDest.ClipRect := result;
  132. source.ScanOffset := Point(sourcePosition.x, sourcePosition.y);
  133. toolDest.FillMask(round(x),round(y),BrushInfo.BrushImage,source,dmDrawWithTransparency,Manager.ApplyPressure(255));
  134. source.ScanOffset := Point(0,0);
  135. toolDest.NoClip;
  136. end;
  137. end;
  138. procedure TToolClone.PrepareBrush(rightBtn: boolean);
  139. begin
  140. definingSource := rightBtn;
  141. end;
  142. procedure TToolClone.ReleaseBrush;
  143. begin
  144. end;
  145. function TToolClone.DoToolMove(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF
  146. ): TRect;
  147. begin
  148. if not RightClickHintShown then
  149. begin
  150. Manager.ToolPopup(tpmRightClickForSource);
  151. RightClickHintShown := true;
  152. end;
  153. Result:=inherited DoToolMove(toolDest, pt, ptF);
  154. end;
  155. function TToolClone.DoToolShiftClick(toolDest: TBGRABitmap; ptF: TPointF;
  156. rightBtn: boolean): TRect;
  157. begin
  158. Result:= EmptyRect;
  159. end;
  160. class procedure TToolClone.ForgetHintShown;
  161. begin
  162. RightClickHintShown:= false;
  163. end;
  164. function TToolClone.SubPixelAccuracy: boolean;
  165. begin
  166. Result:=false;
  167. end;
  168. constructor TToolClone.Create(AManager: TToolManager);
  169. begin
  170. inherited Create(AManager);
  171. end;
  172. destructor TToolClone.Destroy;
  173. begin
  174. inherited Destroy;
  175. end;
  176. function TToolClone.GetContextualToolbars: TContextualToolbars;
  177. begin
  178. Result:= [ctPenWidth,ctBrush];
  179. end;
  180. function TToolClone.Render(VirtualScreen: TBGRABitmap; VirtualScreenWidth,
  181. VirtualScreenHeight: integer;
  182. BitmapToVirtualScreen: TBitmapToVirtualScreenFunction): TRect;
  183. var sourcePosF: TPointF;
  184. begin
  185. Result:=inherited Render(VirtualScreen, VirtualScreenWidth,
  186. VirtualScreenHeight, BitmapToVirtualScreen);
  187. if not sourcePositionRelative and (sourceFlattened or
  188. (Manager.Image.LayerBitmapById[sourceLayerId] <> nil)) then
  189. begin
  190. sourcePosF := BitmapToVirtualScreen(PointF(sourcePosition.X mod Manager.Image.Width,
  191. sourcePosition.Y mod Manager.Image.Height));
  192. result := RectUnion(result,NicePoint(VirtualScreen, sourcePosF.X,sourcePosF.Y));
  193. if sourcePosF.Y > virtualScreenHeight/2 then
  194. result := RectUnion(result,NiceText(VirtualScreen, round(sourcePosF.X),round(sourcePosF.Y-6), VirtualScreenWidth,VirtualScreenHeight, rsSourcePosition, taCenter, tlBottom))
  195. else
  196. result := RectUnion(result,NiceText(VirtualScreen, round(sourcePosF.X),round(sourcePosF.Y+6), VirtualScreenWidth,VirtualScreenHeight, rsSourcePosition, taCenter, tlTop));
  197. end;
  198. end;
  199. { TToolBrush }
  200. function TToolBrush.DrawBrushAt(toolDest: TBGRABitmap; x, y: single): TRect;
  201. begin
  202. if not Assigned(coloredBrushImage) then
  203. begin
  204. result := EmptyRect;
  205. exit;
  206. end;
  207. if not SubPixelAccuracy then
  208. begin
  209. x := round(x);
  210. y := round(y);
  211. end;
  212. x -= (coloredBrushImage.Width-1)/2;
  213. y -= (coloredBrushImage.Height-1)/2;
  214. result := rect(floor(x-0.5),floor(y-0.5),ceil(x+0.5)+coloredBrushImage.Width,ceil(y+0.5)+coloredBrushImage.Height);
  215. toolDest.ClipRect := result;
  216. if not SubPixelAccuracy then
  217. toolDest.PutImage(round(x),round(y),coloredBrushImage,dmDrawWithTransparency,GetBrushAlpha(Manager.ApplyPressure(255)))
  218. else
  219. toolDest.PutImageSubpixel(x,y,coloredBrushImage,GetBrushAlpha(Manager.ApplyPressure(255)));
  220. toolDest.NoClip;
  221. end;
  222. procedure TToolBrush.PrepareBrush(rightBtn: boolean);
  223. var
  224. penColor: TBGRAPixel;
  225. begin
  226. FreeAndNil(coloredBrushImage);
  227. if rightBtn then penColor := Manager.BackColor else penColor := Manager.ForeColor;
  228. coloredBrushImage := BrushInfo.MakeColoredBrushImage(BGRA(penColor.red,penColor.green,penColor.blue,GetBrushAlpha(penColor.alpha)));
  229. end;
  230. procedure TToolBrush.ReleaseBrush;
  231. begin
  232. FreeAndNil(coloredBrushImage);
  233. end;
  234. function TToolBrush.GetAllowedForeFillTypes: TVectorialFillTypes;
  235. begin
  236. Result:= [vftSolid];
  237. end;
  238. function TToolBrush.GetAllowedBackFillTypes: TVectorialFillTypes;
  239. begin
  240. Result:= [vftSolid];
  241. end;
  242. destructor TToolBrush.Destroy;
  243. begin
  244. ReleaseBrush;
  245. inherited Destroy;
  246. end;
  247. function TToolBrush.GetContextualToolbars: TContextualToolbars;
  248. begin
  249. Result:= [ctPenFill,ctBackFill,ctPenWidth,ctBrush];
  250. end;
  251. { TToolGenericBrush }
  252. function TToolGenericBrush.GetBrushInfo: TLazPaintBrush;
  253. begin
  254. result := manager.BrushInfo;
  255. if result = nil then
  256. begin
  257. if defaultBrush = nil then
  258. defaultBrush := TLazPaintBrush.Create;
  259. result := defaultBrush;
  260. end;
  261. result.Size := manager.PenWidth;
  262. end;
  263. function TToolGenericBrush.StartDrawing(toolDest: TBGRABitmap; ptF: TPointF;
  264. rightBtn: boolean): TRect;
  265. begin
  266. if not SubPixelAccuracy then
  267. brushOrigin:= PointF(round(ptF.x),round(ptF.y))
  268. else brushOrigin := ptF;
  269. originDrawn := false;
  270. PrepareBrush(rightBtn);
  271. result := ContinueDrawing(toolDest, brushOrigin, brushOrigin, rightBtn);
  272. end;
  273. function TToolGenericBrush.ContinueDrawing(toolDest: TBGRABitmap; originF,
  274. destF: TPointF; rightBtn: boolean): TRect;
  275. var v: TPointF;
  276. count: integer;
  277. len, minLen: single;
  278. begin
  279. result := EmptyRect;
  280. if not originDrawn then //and ((destF <> brushOrigin) or not Manager.ToolBrushOriented) then
  281. begin
  282. result := RectUnion(result, DrawBrushAt(toolDest, brushOrigin.x,brushOrigin.y));
  283. originDrawn:= true;
  284. end;
  285. if destF<>brushOrigin then
  286. begin
  287. v := destF-brushOrigin;
  288. if not SubPixelAccuracy then
  289. len := max(abs(v.x),abs(v.y))
  290. else
  291. len := sqrt(v**v);
  292. minLen := round(power(BrushInfo.Size/10,0.8));
  293. if minLen < 1 then minLen := 1;
  294. if minLen > 5 then minLen := 5;
  295. minLen *=Manager.BrushSpacing;
  296. if len >= minLen then
  297. begin
  298. v := v*(1/len)*minLen;
  299. count := trunc(len/minLen);
  300. while count > 0 do
  301. begin
  302. brushOrigin += v;
  303. result := RectUnion(result, DrawBrushAt(toolDest, brushOrigin.x,brushOrigin.y));
  304. originDrawn:= true;
  305. dec(count);
  306. end;
  307. end;
  308. end;
  309. end;
  310. function TToolGenericBrush.GetBrushAlpha(AAlpha: byte): byte;
  311. var exponent: single;
  312. begin
  313. exponent := (BrushInfo.Size-1)/10+1;
  314. if exponent > 2 then exponent := 2;
  315. result := round(Power(AAlpha/255,exponent)*255)
  316. end;
  317. function TToolGenericBrush.GetLayerOffset: TPoint;
  318. begin
  319. if IsSelectingTool or not Manager.Image.SelectionMaskEmpty then
  320. result := Manager.Image.LayerOffset[Manager.Image.CurrentLayerIndex]
  321. else
  322. result := Point(0,0);
  323. end;
  324. constructor TToolGenericBrush.Create(AManager: TToolManager);
  325. begin
  326. inherited Create(AManager);
  327. end;
  328. function TToolGenericBrush.ToolUp: TRect;
  329. var penWasDrawing: boolean;
  330. begin
  331. penWasDrawing:= penDrawing;
  332. Result:=inherited ToolUp;
  333. if not penDrawing and penWasDrawing then ReleaseBrush;
  334. end;
  335. function TToolGenericBrush.SubPixelAccuracy: boolean;
  336. begin
  337. result := BrushInfo.Size < 10;
  338. end;
  339. destructor TToolGenericBrush.Destroy;
  340. begin
  341. FreeAndNil(defaultBrush);
  342. inherited Destroy;
  343. end;
  344. initialization
  345. RegisterTool(ptBrush,TToolBrush);
  346. RegisterTool(ptClone,TToolClone);
  347. TToolClone.sourceLayerId := -1;
  348. end.