utoolbasic.pas 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UToolBasic;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, utool, BGRABitmapTypes, BGRABitmap, BGRALayerOriginal,
  7. UImage, LCVectorOriginal, LCLType;
  8. type
  9. { TToolHand }
  10. TToolHand = class(TReadonlyTool)
  11. protected
  12. handMoving, samePosition: boolean;
  13. handOriginF: TPointF;
  14. function FixSelectionTransform: boolean; override;
  15. function FixLayerOffset: boolean; override;
  16. function DoToolDown({%H-}toolDest: TBGRABitmap; {%H-}pt: TPoint; ptF: TPointF;
  17. {%H-}rightBtn: boolean): TRect; override;
  18. function DoToolMove({%H-}toolDest: TBGRABitmap; {%H-}pt: TPoint; ptF: TPointF): TRect; override;
  19. function GetStatusText: string; override;
  20. procedure TrySelect(ptF: TPointF);
  21. public
  22. constructor Create(AManager: TToolManager); override;
  23. function ToolUp: TRect; override;
  24. end;
  25. { TToolColorPicker }
  26. TToolColorPicker = class(TReadonlyTool)
  27. protected
  28. colorpicking,colorpickingRight: boolean;
  29. function DoToolDown(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF;
  30. rightBtn: boolean): TRect; override;
  31. function DoToolMove(toolDest: TBGRABitmap; pt: TPoint; {%H-}ptF: TPointF): TRect; override;
  32. function FixLayerOffset: boolean; override;
  33. public
  34. function ToolUp: TRect; override;
  35. function GetContextualToolbars: TContextualToolbars; override;
  36. end;
  37. { TToolPen }
  38. TToolPen = class(TGenericTool)
  39. protected
  40. class var HintShown: boolean;
  41. penDrawing, penDrawingRight: boolean;
  42. shiftClicking, shiftClickingRight: boolean;
  43. penOrigin: TPointF;
  44. function PickColorWithShift: boolean; virtual;
  45. function GetIsSelectingTool: boolean; override;
  46. function GetUniversalBrush(ARightButton: boolean): TUniversalBrush; virtual;
  47. function StartDrawing(toolDest: TBGRABitmap; ptF: TPointF; rightBtn: boolean): TRect; virtual;
  48. function ContinueDrawing(toolDest: TBGRABitmap; originF, destF: TPointF; rightBtn: boolean): TRect; virtual;
  49. function DoToolDown(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF; rightBtn: boolean): TRect; override;
  50. function DoToolMove(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF): TRect; override;
  51. function DoToolShiftClick(toolDest: TBGRABitmap; ptF: TPointF; rightBtn: boolean): TRect; virtual;
  52. public
  53. class procedure ForgetHintShown;
  54. function ToolUp: TRect; override;
  55. function GetContextualToolbars: TContextualToolbars; override;
  56. destructor Destroy; override;
  57. end;
  58. { TToolErase }
  59. TToolErase = class(TToolPen)
  60. protected
  61. procedure ApplyEraseMode(var image: TBGRABitmap);
  62. function BlurRadius: single;
  63. function StartDrawing(toolDest: TBGRABitmap; ptF: TPointF; {%H-}rightBtn: boolean): TRect; override;
  64. function ContinueDrawing(toolDest: TBGRABitmap; originF, destF: TPointF; {%H-}rightBtn: boolean): TRect; override;
  65. public
  66. function GetContextualToolbars: TContextualToolbars; override;
  67. end;
  68. implementation
  69. uses Types, Graphics, ugraph, Controls, LazPaintType,
  70. UResourceStrings, BGRAPen, math, BGRATransform;
  71. { TToolErase }
  72. procedure TToolErase.ApplyEraseMode(var image: TBGRABitmap);
  73. var
  74. radius: single;
  75. p: PBGRAPixel;
  76. ec: TExpandedPixel;
  77. curLight: Word;
  78. i: Integer;
  79. lightDiff: word;
  80. begin
  81. case Manager.EraserMode of
  82. emSoften: begin
  83. radius := BlurRadius;
  84. if radius < 2.5 then
  85. BGRAReplace(image, image.FilterBlurRadial(round(radius*10),rbPrecise)) else
  86. BGRAReplace(image, image.FilterBlurRadial(round(radius),rbFast));
  87. end;
  88. emSharpen: BGRAReplace(image, image.FilterSharpen(Manager.EraserAlpha/255));
  89. emLighten:
  90. begin
  91. p := image.Data;
  92. lightDiff := round(Manager.EraserAlpha*32768/255);
  93. for i := 0 to image.NbPixels-1 do
  94. begin
  95. ec := p^.ToExpanded;
  96. curLight := GetLightness(ec);
  97. p^ := SetLightness(ec, min(curLight + lightDiff, 65535), curLight);
  98. inc(p);
  99. end;
  100. end;
  101. emDarken:
  102. begin
  103. p := image.Data;
  104. lightDiff := round(Manager.EraserAlpha*32768/255);
  105. for i := 0 to image.NbPixels-1 do
  106. begin
  107. ec := p^.ToExpanded;
  108. curLight := GetLightness(ec);
  109. p^ := SetLightness(ec, max(curLight - lightDiff, 0), curLight);
  110. inc(p);
  111. end;
  112. end;
  113. end;
  114. end;
  115. function TToolErase.BlurRadius: single;
  116. begin
  117. result := manager.PenWidth/4*Manager.EraserAlpha/255;
  118. end;
  119. function TToolErase.StartDrawing(toolDest: TBGRABitmap; ptF: TPointF;
  120. rightBtn: boolean): TRect;
  121. var ix,iy: integer;
  122. areaCopy, mask: TBGRABitmap;
  123. r: TRect;
  124. begin
  125. if Manager.EraserMode <> emEraseAlpha then
  126. begin
  127. result := GetShapeBounds([ptF],Manager.PenWidth+BlurRadius);
  128. if IntersectRect(result, result, rect(0,0,toolDest.width,toolDest.height)) then
  129. begin
  130. areaCopy := toolDest.GetPart(result) as TBGRABitmap;
  131. ApplyEraseMode(areaCopy);
  132. mask := TBGRABitmap.Create(result.Right-result.left,result.bottom-result.top, BGRABlack);
  133. mask.LinearAntialiasing := true;
  134. if Manager.ShapeOptionAliasing then
  135. begin
  136. r := rect(round(ptF.X-result.left-Manager.PenWidth/2+0.5),round(ptF.Y-result.top-Manager.PenWidth/2+0.5),
  137. round(ptF.X-result.left+Manager.PenWidth/2+0.5),round(ptF.Y-result.top+Manager.PenWidth/2+0.5));
  138. mask.FillEllipseInRect(r,Manager.ApplyPressure(BGRAWhite));
  139. end
  140. else
  141. mask.FillEllipseAntialias(ptF.X-result.left,ptF.Y-result.top,
  142. Manager.PenWidth/2,Manager.PenWidth/2,Manager.ApplyPressure(BGRAWhite));
  143. areaCopy.ScanOffset := Point(-result.left,-result.top);
  144. mask.ScanOffset := Point(-result.left,-result.top);
  145. toolDest.CrossFade(result, toolDest, areaCopy, mask, dmSet);
  146. mask.Free;
  147. areaCopy.Free;
  148. end;
  149. end else
  150. begin
  151. if ((ssSnap in ShiftState) or Manager.ShapeOptionAliasing) and (Manager.PenWidth = 1) then
  152. begin
  153. ix := round(ptF.X);
  154. iy := round(ptF.Y);
  155. toolDest.ErasePixel(ix,iy,Manager.ApplyPressure(Manager.EraserAlpha));
  156. result := rect(ix,iy,ix+1,iy+1);
  157. end
  158. else
  159. begin
  160. result := GetShapeBounds([ptF],Manager.PenWidth);
  161. toolDest.ClipRect := result;
  162. if Manager.ShapeOptionAliasing then
  163. begin
  164. r := rect(round(ptF.X-Manager.PenWidth/2+0.5),round(ptF.Y-Manager.PenWidth/2+0.5),
  165. round(ptF.X+Manager.PenWidth/2+0.5),round(ptF.Y+Manager.PenWidth/2+0.5));
  166. toolDest.EraseEllipseInRect(r,Manager.ApplyPressure(Manager.EraserAlpha));
  167. end else
  168. toolDest.EraseEllipseAntialias(ptF.X,ptF.Y,Manager.PenWidth/2,Manager.PenWidth/2,Manager.ApplyPressure(Manager.EraserAlpha));
  169. toolDest.NoClip;
  170. end;
  171. end;
  172. end;
  173. function TToolErase.ContinueDrawing(toolDest: TBGRABitmap; originF,
  174. destF: TPointF; rightBtn: boolean): TRect;
  175. var areaCopy, mask: TBGRABitmap;
  176. pts: ArrayOfTPointF;
  177. cOpacity: TBGRAPixel;
  178. begin
  179. if Manager.EraserMode <> emEraseAlpha then
  180. begin
  181. result := GetShapeBounds([destF,originF],Manager.PenWidth+BlurRadius);
  182. if IntersectRect(result, result, rect(0,0,toolDest.width,toolDest.height)) then
  183. begin
  184. areaCopy := toolDest.GetPart(result) as TBGRABitmap;
  185. ApplyEraseMode(areaCopy);
  186. mask := TBGRABitmap.Create(result.Right-result.left,result.bottom-result.top, BGRABlack);
  187. mask.LinearAntialiasing := true;
  188. if Manager.EraserMode in [emLighten,emDarken] then
  189. cOpacity := BGRA(0,0,0, Manager.EraserAlpha div 2)
  190. else
  191. cOpacity := BGRA(0,0,0, Manager.EraserAlpha);
  192. pts := toolDest.Pen.ComputePolyline(
  193. [PointF(destF.X-result.left,destF.Y-result.top),
  194. PointF(originF.X-result.left,originF.Y-result.top)],
  195. Manager.PenWidth,cOpacity,False);
  196. if Manager.ShapeOptionAliasing then
  197. mask.FillPoly(pts,BGRAWhite)
  198. else
  199. mask.FillPolyAntialias(pts,BGRAWhite);
  200. areaCopy.ScanOffset := Point(-result.left,-result.top);
  201. mask.ScanOffset := Point(-result.left,-result.top);
  202. toolDest.CrossFade(result, toolDest, areaCopy, mask, dmSet);
  203. mask.Free;
  204. areaCopy.Free;
  205. end;
  206. end else
  207. if Manager.ShapeOptionAliasing then
  208. begin
  209. if Manager.PenWidth = 1 then
  210. begin
  211. toolDest.EraseLine(round(destF.X),round(destF.Y),round(originF.X),round(originF.Y),Manager.ApplyPressure(Manager.EraserAlpha),false);
  212. result := GetShapeBounds([destF,originF],1);
  213. end else
  214. begin
  215. pts := toolDest.Pen.ComputePolyline([PointF(destF.X,destF.Y),PointF(originF.X,originF.Y)],Manager.PenWidth,BGRAPixelTransparent,False);
  216. toolDest.ErasePoly(pts, Manager.ApplyPressure(Manager.EraserAlpha));
  217. result := GetShapeBounds([destF,originF],Manager.PenWidth);
  218. end;
  219. end else
  220. begin
  221. if (ssSnap in ShiftState) and (Manager.PenWidth = 1) then
  222. begin
  223. toolDest.EraseLineAntialias(round(destF.X),round(destF.Y),round(originF.X),round(originF.Y),Manager.ApplyPressure(Manager.EraserAlpha),false);
  224. result := GetShapeBounds([destF,originF],1);
  225. end else
  226. begin
  227. toolDest.EraseLineAntialias(destF.X,destF.Y,originF.X,originF.Y,Manager.ApplyPressure(Manager.EraserAlpha),Manager.PenWidth,False);
  228. result := GetShapeBounds([destF,originF],Manager.PenWidth);
  229. end;
  230. end;
  231. end;
  232. function TToolErase.GetContextualToolbars: TContextualToolbars;
  233. begin
  234. Result:= [ctPenWidth,ctEraserOption,ctAliasing];
  235. end;
  236. { TToolPen }
  237. function TToolPen.PickColorWithShift: boolean;
  238. begin
  239. result := true;
  240. end;
  241. function TToolPen.GetIsSelectingTool: boolean;
  242. begin
  243. Result:= false;
  244. end;
  245. function TToolPen.GetUniversalBrush(ARightButton: boolean): TUniversalBrush;
  246. begin
  247. if ARightButton then result := GetBackUniversalBrush
  248. else result := GetForeUniversalBrush;
  249. end;
  250. function TToolPen.StartDrawing(toolDest: TBGRABitmap; ptF: TPointF;
  251. rightBtn: boolean): TRect;
  252. var ix,iy: integer;
  253. r: TRect;
  254. b: TUniversalBrush;
  255. begin
  256. b := GetUniversalBrush(rightBtn);
  257. if ((ssSnap in ShiftState) or Manager.ShapeOptionAliasing) and (Manager.PenWidth = 1) then
  258. begin
  259. ix := round(ptF.X);
  260. iy := round(ptF.Y);
  261. toolDest.DrawPixel(ix, iy, b);
  262. result := rect(ix,iy,ix+1,iy+1);
  263. end else
  264. begin
  265. result := GetShapeBounds([ptF],Manager.PenWidth);
  266. toolDest.ClipRect := result;
  267. if Manager.ShapeOptionAliasing then
  268. begin
  269. r := rect(round(ptF.X-Manager.PenWidth/2+0.5),round(ptF.Y-Manager.PenWidth/2+0.5),
  270. round(ptF.X+Manager.PenWidth/2+0.5),round(ptF.Y+Manager.PenWidth/2+0.5));
  271. toolDest.FillEllipseInRect(r, b);
  272. end
  273. else
  274. toolDest.FillEllipseAntialias(ptF.X, ptF.Y, Manager.PenWidth/2, Manager.PenWidth/2, b);
  275. toolDest.NoClip;
  276. end;
  277. ReleaseUniversalBrushes;
  278. end;
  279. function TToolPen.ContinueDrawing(toolDest: TBGRABitmap; originF, destF: TPointF; rightBtn: boolean): TRect;
  280. var
  281. pts: ArrayOfTPointF;
  282. b: TUniversalBrush;
  283. testPix: TBGRAPixel;
  284. testContext: TUniBrushContext;
  285. begin
  286. b := GetUniversalBrush(rightBtn);
  287. if ((ssSnap in ShiftState) or Manager.ShapeOptionAliasing) and (Manager.PenWidth = 1) then
  288. begin
  289. if Manager.ShapeOptionAliasing then
  290. toolDest.DrawLine(round(destF.X), round(destF.Y), round(originF.X), round(originF.Y), b, false)
  291. else
  292. toolDest.DrawLineAntialias(round(destF.X), round(destF.Y),
  293. round(originF.X), round(originF.Y), b, false);
  294. result := GetShapeBounds([destF,originF],1);
  295. end else
  296. begin
  297. result := GetShapeBounds([destF,originF],Manager.PenWidth+1);
  298. toolDest.ClipRect := result;
  299. if Manager.ShapeOptionAliasing then
  300. begin
  301. pts := toolDest.Pen.ComputePolyline([PointF(destF.X,destF.Y),PointF(originF.X,originF.Y)],
  302. Manager.PenWidth, BGRAPixelTransparent, False);
  303. toolDest.FillPoly(pts, b);
  304. end else
  305. begin
  306. testPix := BGRAPixelTransparent;
  307. b.MoveTo(@testContext, @testPix, round(originF.X), round(originF.Y));
  308. b.PutNextPixels(@testContext, 65535, 1);
  309. pts := toolDest.Pen.ComputePolyline([PointF(destF.X,destF.Y),PointF(originF.X,originF.Y)],
  310. Manager.PenWidth, testPix, False);
  311. toolDest.FillPolyAntialias(pts, b);
  312. end;
  313. toolDest.NoClip;
  314. end;
  315. ReleaseUniversalBrushes;
  316. end;
  317. function TToolPen.DoToolDown(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF;
  318. rightBtn: boolean): TRect;
  319. var
  320. b: TUniversalBrush;
  321. begin
  322. if ssSnap in ShiftState then ptF := PointF(pt.X,pt.Y);
  323. if not penDrawing then
  324. begin
  325. if PickColorWithShift and (ssShift in ShiftState) then
  326. begin
  327. result := DoToolShiftClick(toolDest, ptF, rightBtn);
  328. shiftClicking := true;
  329. shiftClickingRight := rightBtn;
  330. end else
  331. begin
  332. b := GetUniversalBrush(rightBtn);
  333. if b.DoesNothing then
  334. begin
  335. Manager.ToolPopup(tpmOpacity0, 0, true);
  336. result := EmptyRect;
  337. end
  338. else
  339. begin
  340. toolDest.PenStyle := psSolid;
  341. penDrawing := true;
  342. penDrawingRight := rightBtn;
  343. result := StartDrawing(toolDest,ptF,rightBtn);
  344. penOrigin := ptF;
  345. end;
  346. end;
  347. end else
  348. result := EmptyRect;
  349. end;
  350. function TToolPen.DoToolMove(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF): TRect;
  351. begin
  352. if (manager.PenWidth <= 3) and not HintShown then
  353. begin
  354. Manager.ToolPopup(tpmHoldKeySnapToPixel, VK_CONTROL);
  355. HintShown:= true;
  356. end;
  357. if ssSnap in ShiftState then ptF := PointF(pt.X,pt.Y);
  358. result := EmptyRect;
  359. if penDrawing and (sqr(penOrigin.X-ptF.X)+sqr(penOrigin.Y-ptF.Y) >= 0.999) then
  360. begin
  361. toolDest.PenStyle := psSolid;
  362. result := ContinueDrawing(toolDest,penOrigin,ptF,penDrawingRight);
  363. penOrigin := ptF;
  364. end else
  365. if shiftClicking then
  366. DoToolShiftClick(toolDest,ptF,shiftClickingRight);
  367. end;
  368. function TToolPen.DoToolShiftClick(toolDest: TBGRABitmap; ptF: TPointF;
  369. rightBtn: boolean): TRect;
  370. var
  371. c: TBGRAPixel;
  372. begin
  373. c := toolDest.GetPixel(round(ptF.X),round(ptF.Y));
  374. if rightBtn then Manager.BackColor := c
  375. else Manager.ForeColor := c;
  376. result := EmptyRect;
  377. end;
  378. class procedure TToolPen.ForgetHintShown;
  379. begin
  380. HintShown:= false;
  381. end;
  382. function TToolPen.ToolUp: TRect;
  383. begin
  384. if penDrawing then
  385. begin
  386. penDrawing:= false;
  387. penDrawingRight := false;
  388. ValidateActionPartially;
  389. end else
  390. if shiftClicking then
  391. begin
  392. shiftClicking := false;
  393. shiftClickingRight := false;
  394. end;
  395. result := EmptyRect;
  396. end;
  397. function TToolPen.GetContextualToolbars: TContextualToolbars;
  398. begin
  399. Result:= [ctPenFill,ctBackFill,ctPenWidth,ctAliasing];
  400. end;
  401. destructor TToolPen.Destroy;
  402. begin
  403. if penDrawing then ValidateAction;
  404. inherited Destroy;
  405. end;
  406. { TToolColorPicker }
  407. function TToolColorPicker.DoToolDown(toolDest: TBGRABitmap; pt: TPoint;
  408. ptF: TPointF; rightBtn: boolean): TRect;
  409. begin
  410. result := EmptyRect;
  411. if not colorpicking then
  412. begin
  413. colorpicking := true;
  414. colorpickingRight := rightBtn;
  415. DoToolMove(toolDest,pt,ptF);
  416. end;
  417. end;
  418. function TToolColorPicker.DoToolMove(toolDest: TBGRABitmap; pt: TPoint;
  419. ptF: TPointF): TRect;
  420. var
  421. c: TBGRAPixel;
  422. begin
  423. result := EmptyRect;
  424. if colorpicking then
  425. begin
  426. if ssShift in ShiftState then
  427. begin
  428. c := Manager.Image.RenderedImage.GetPixel(pt.X,pt.Y);
  429. // rendered image is in fact empty
  430. if (c.alpha = 0) and Manager.Image.RenderedImage.Empty then
  431. begin
  432. Manager.ToolPopup(tpmLayerEmpty, 0, true);
  433. exit;
  434. end;
  435. end
  436. else
  437. begin
  438. if (pt.X >= 0) and (pt.Y >= 0) and (pt.X < toolDest.Width) and (pt.Y < toolDest.Height) then
  439. begin
  440. c := toolDest.GetPixel(pt.X,pt.Y);
  441. // layer is in fact empty
  442. if (c.alpha = 0) and toolDest.Empty then
  443. begin
  444. Manager.ToolPopup(tpmLayerEmpty, 0, true);
  445. exit;
  446. end;
  447. end
  448. else
  449. exit;
  450. end;
  451. if colorpickingRight then
  452. begin
  453. Manager.BackColor := c;
  454. Manager.QueryColorTarget(Manager.BackFill);
  455. end else
  456. begin
  457. Manager.ForeColor := c;
  458. Manager.QueryColorTarget(Manager.ForeFill);
  459. end;
  460. end;
  461. end;
  462. function TToolColorPicker.FixLayerOffset: boolean;
  463. begin
  464. Result:= not (ssShift in ShiftState);
  465. end;
  466. function TToolColorPicker.ToolUp: TRect;
  467. begin
  468. Result:= EmptyRect;
  469. colorpicking := false;
  470. end;
  471. function TToolColorPicker.GetContextualToolbars: TContextualToolbars;
  472. begin
  473. Result:= [ctPenFill,ctBackFill];
  474. end;
  475. { TToolHand }
  476. function TToolHand.FixSelectionTransform: boolean;
  477. begin
  478. Result:= false;
  479. end;
  480. function TToolHand.FixLayerOffset: boolean;
  481. begin
  482. Result:= false;
  483. end;
  484. function TToolHand.DoToolDown(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF;
  485. rightBtn: boolean): TRect;
  486. begin
  487. result := EmptyRect;
  488. if not handMoving then
  489. begin
  490. handMoving := true;
  491. samePosition := true;
  492. handOriginF := ptF;
  493. end;
  494. end;
  495. function TToolHand.DoToolMove(toolDest: TBGRABitmap; pt: TPoint; ptF: TPointF): TRect;
  496. var
  497. newOfs: TPoint;
  498. begin
  499. result := EmptyRect;
  500. if handMoving then
  501. begin
  502. newOfs := Point(Manager.Image.ImageOffset.X+round(ptF.X-HandOriginF.X),
  503. Manager.Image.ImageOffset.Y+round(ptF.Y-HandOriginF.Y));
  504. if newOfs <> Manager.Image.ImageOffset then
  505. begin
  506. Manager.Image.ImageOffset := newOfs;
  507. samePosition := false;
  508. result := OnlyRenderChange;
  509. end;
  510. end;
  511. end;
  512. function TToolHand.GetStatusText: string;
  513. var w,h,i,j: integer;
  514. smallestNum, smallestDenom: integer;
  515. begin
  516. w := Manager.Image.Width;
  517. h := Manager.Image.Height;
  518. Result:= rsCanvasSize + ' = ' + inttostr(w) + ' x ' + inttostr(h);
  519. if h > 0 then
  520. begin
  521. result += '|Δx/Δy = ' + FloatToStrF(w/h, ffFixed, 6, 2);
  522. smallestNum := 0;
  523. smallestDenom := 0;
  524. for i := 2 to 9 do
  525. for j := i+1 to i*2-1 do
  526. if j mod i <> 0 then
  527. if w*j = h*i then
  528. begin
  529. if (smallestNum = 0) or (i+j < smallestNum+smallestDenom) then
  530. begin
  531. smallestNum:= i;
  532. smallestDenom := j;
  533. end;
  534. end else
  535. if w*i = h*j then
  536. begin
  537. if (smallestNum = 0) or (i+j < smallestNum+smallestDenom) then
  538. begin
  539. smallestNum:= j;
  540. smallestDenom := i;
  541. end;
  542. end;
  543. if (smallestNum <> 0) then
  544. result += ' = ' + inttostr(smallestNum)+'/'+inttostr(smallestDenom);
  545. end;
  546. end;
  547. procedure TToolHand.TrySelect(ptF: TPointF);
  548. var
  549. untransformedPtF: TPointF;
  550. c: TBGRAPixel;
  551. ofs: TPoint;
  552. original: TVectorOriginal;
  553. i: Integer;
  554. begin
  555. if Manager.ToolSleeping then exit;
  556. if not Manager.Image.SelectionMaskEmpty and
  557. not Manager.Image.SelectionLayerIsEmpty and
  558. IsAffineMatrixInversible(Manager.Image.SelectionTransform) then
  559. begin
  560. untransformedPtF := AffineMatrixInverse(Manager.Image.SelectionTransform) * ptF;
  561. c := Manager.Image.SelectionLayerReadonly.GetPixel(untransformedPtF.X,untransformedPtF.Y);
  562. if c.alpha <> 0 then
  563. begin
  564. Manager.QueryExitTool(ptMoveSelection);
  565. exit;
  566. end;
  567. end;
  568. if GetCurrentLayerKind = lkVectorial then
  569. begin
  570. original := Manager.Image.LayerOriginal[Manager.Image.CurrentLayerIndex] as TVectorOriginal;
  571. for i := original.ShapeCount-1 downto 0 do
  572. begin
  573. if original.Shape[i].PointInShape(ptF) then
  574. begin
  575. original.SelectShape(i);
  576. Manager.QueryExitTool(ptEditShape);
  577. exit;
  578. end;
  579. end;
  580. end else
  581. begin
  582. ofs := Manager.Image.LayerOffset[Manager.Image.CurrentLayerIndex];
  583. c := Manager.Image.CurrentLayerReadOnly.GetPixel(ptF.X - ofs.X,ptF.Y - ofs.Y);
  584. if c.alpha <> 0 then
  585. begin
  586. Manager.QueryExitTool(ptMoveLayer);
  587. exit;
  588. end;
  589. end;
  590. end;
  591. constructor TToolHand.Create(AManager: TToolManager);
  592. begin
  593. inherited Create(AManager);
  594. handMoving := false;
  595. end;
  596. function TToolHand.ToolUp: TRect;
  597. begin
  598. if handMoving then
  599. begin
  600. handMoving := false;
  601. if samePosition then
  602. begin
  603. TrySelect(handOriginF);
  604. end;
  605. end;
  606. result := EmptyRect;
  607. end;
  608. initialization
  609. RegisterTool(ptHand,TToolHand);
  610. RegisterTool(ptColorPicker,TToolColorPicker);
  611. RegisterTool(ptPen,TToolPen);
  612. RegisterTool(ptEraser,TToolErase);
  613. end.