GR32.Paint.Brush.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. unit GR32.Paint.Brush;
  2. (* ***** BEGIN LICENSE BLOCK *****
  3. * Version: MPL 1.1 or LGPL 2.1 with linking exception
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. * http://www.mozilla.org/MPL/
  9. *
  10. * Software distributed under the License is distributed on an "AS IS" basis,
  11. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. * for the specific language governing rights and limitations under the
  13. * License.
  14. *
  15. * Alternatively, the contents of this file may be used under the terms of the
  16. * Free Pascal modified version of the GNU Lesser General Public License
  17. * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
  18. * of this license are applicable instead of those above.
  19. * Please see the file LICENSE.txt for additional information concerning this
  20. * license.
  21. *
  22. * The Original Code is Paint tools for Graphics32
  23. *
  24. * The Initial Developer of the Original Code is
  25. * Anders Melander, [email protected]
  26. *
  27. * Portions created by the Initial Developer are Copyright (C) 2008-2025
  28. * the Initial Developer. All Rights Reserved.
  29. *
  30. * ***** END LICENSE BLOCK ***** *)
  31. interface
  32. {$INCLUDE GR32.inc}
  33. uses
  34. GR32;
  35. //------------------------------------------------------------------------------
  36. //
  37. // TCustomPaintBrush
  38. //
  39. //------------------------------------------------------------------------------
  40. type
  41. TCustomPaintBrush = class
  42. private
  43. protected
  44. function GetHeight: integer; virtual; abstract;
  45. function GetWidth: integer; virtual; abstract;
  46. public
  47. constructor Create(Width, Height: integer); virtual;
  48. procedure BeginBrush(Bitmap: TBitmap32); virtual;
  49. procedure EndBrush; virtual;
  50. procedure Draw(Bitmap: TBitmap32; x, y: Single); virtual; abstract;
  51. procedure DrawPreview(Bitmap: TBitmap32); virtual; abstract;
  52. property Width: integer read GetWidth;
  53. property Height: integer read GetHeight;
  54. end;
  55. //------------------------------------------------------------------------------
  56. //
  57. // TBitmapPaintBrush
  58. //
  59. //------------------------------------------------------------------------------
  60. type
  61. TBitmapPaintBrush = class(TCustomPaintBrush)
  62. private
  63. FBitmap: TBitmap32;
  64. FBlendFunc: TPixelCombineEvent;
  65. protected
  66. function GetHeight: integer; override;
  67. function GetWidth: integer; override;
  68. public
  69. constructor Create(Width, Height: integer); override;
  70. destructor Destroy; override;
  71. procedure Draw(Bitmap: TBitmap32; x, y: Single); override;
  72. procedure DrawPreview(Bitmap: TBitmap32); override;
  73. property Bitmap: TBitmap32 read FBitmap;
  74. property BlendFunc: TPixelCombineEvent read FBlendFunc write FBlendFunc;
  75. end;
  76. //------------------------------------------------------------------------------
  77. //
  78. // TSmudgePaintBrush
  79. //
  80. //------------------------------------------------------------------------------
  81. type
  82. TSmudgePaintBrush = class(TBitmapPaintBrush)
  83. private
  84. FFirst: boolean;
  85. FFinger: TBitmap32;
  86. FPaper: TBitmap32;
  87. FPressure: integer;
  88. protected
  89. procedure SmudgeBlend(Finger, Paper, Mask: TBitmap32; Pressure: integer);
  90. public
  91. constructor Create(Width, Height: integer); override;
  92. destructor Destroy; override;
  93. procedure Draw(Bitmap: TBitmap32; x, y: Single); override;
  94. property Pressure: integer read FPressure write FPressure; // 0..100
  95. end;
  96. //------------------------------------------------------------------------------
  97. //
  98. // Brush lines
  99. //
  100. //------------------------------------------------------------------------------
  101. type
  102. TPaintBrushLineState = record
  103. private
  104. LastX, LastY: Single;
  105. Offset: Single;
  106. public
  107. Valid: boolean;
  108. end;
  109. TPaintBrushLine = class
  110. private
  111. FLastX, FLastY: Single;
  112. FBitmap: TBitmap32;
  113. FBrush: TCustomPaintBrush;
  114. FStep: Single;
  115. FOffset: Single;
  116. public
  117. constructor Create(Bitmap: TBitmap32; Brush: TCustomPaintBrush; Step: Single);
  118. procedure MoveTo(X, Y: Single; Reset: boolean = True);
  119. procedure LineTo(X, Y: Single; Reset: boolean = False);
  120. procedure SaveState(var State: TPaintBrushLineState);
  121. procedure RestoreState(const State: TPaintBrushLineState);
  122. end;
  123. //------------------------------------------------------------------------------
  124. //------------------------------------------------------------------------------
  125. //------------------------------------------------------------------------------
  126. implementation
  127. uses
  128. {$if defined(UseInlining)}
  129. Types,
  130. {$ifend}
  131. Math,
  132. GR32_Resamplers,
  133. GR32_Math,
  134. GR32_Blend;
  135. //------------------------------------------------------------------------------
  136. //
  137. // TCustomPaintBrush
  138. //
  139. //------------------------------------------------------------------------------
  140. constructor TCustomPaintBrush.Create(Width, Height: integer);
  141. begin
  142. end;
  143. procedure TCustomPaintBrush.BeginBrush(Bitmap: TBitmap32);
  144. begin
  145. end;
  146. procedure TCustomPaintBrush.EndBrush;
  147. begin
  148. end;
  149. //------------------------------------------------------------------------------
  150. //
  151. // TBitmapPaintBrush
  152. //
  153. //------------------------------------------------------------------------------
  154. constructor TBitmapPaintBrush.Create(Width, Height: integer);
  155. begin
  156. inherited Create(Width, Height);
  157. FBitmap := TBitmap32.Create;
  158. FBitmap.SetSize(Width, Height);
  159. FBitmap.DrawMode := dmBlend;
  160. FBitmap.CombineMode := cmMerge;
  161. end;
  162. //------------------------------------------------------------------------------
  163. destructor TBitmapPaintBrush.Destroy;
  164. begin
  165. FBitmap.Free;
  166. inherited Destroy;
  167. end;
  168. //------------------------------------------------------------------------------
  169. procedure TBitmapPaintBrush.Draw(Bitmap: TBitmap32; x, y: Single);
  170. var
  171. DestX, DestY: integer;
  172. begin
  173. DestX := Ceil(x - FBitmap.Width / 2);
  174. DestY := Ceil(y - FBitmap.Height / 2);
  175. if (not Bitmap.MeasuringMode) then
  176. begin
  177. if (Assigned(FBlendFunc)) then
  178. BlockTransfer(Bitmap, DestX, DestY, Bitmap.ClipRect, FBitmap, FBitmap.BoundsRect, dmCustom, FBlendFunc)
  179. else
  180. FBitmap.DrawTo(Bitmap, DestX, DestY);
  181. end;
  182. Bitmap.Changed(MakeRect(DestX, DestY, DestX+FBitmap.Width, DestY+FBitmap.Height));
  183. end;
  184. //------------------------------------------------------------------------------
  185. procedure TBitmapPaintBrush.DrawPreview(Bitmap: TBitmap32);
  186. begin
  187. FBitmap.DrawTo(Bitmap);
  188. end;
  189. //------------------------------------------------------------------------------
  190. function TBitmapPaintBrush.GetHeight: integer;
  191. begin
  192. Result := FBitmap.Height;
  193. end;
  194. //------------------------------------------------------------------------------
  195. function TBitmapPaintBrush.GetWidth: integer;
  196. begin
  197. Result := FBitmap.Width;
  198. end;
  199. //------------------------------------------------------------------------------
  200. //
  201. // TPaintBrushLine
  202. //
  203. //------------------------------------------------------------------------------
  204. constructor TPaintBrushLine.Create(Bitmap: TBitmap32; Brush: TCustomPaintBrush; Step: Single);
  205. begin
  206. FBitmap := Bitmap;
  207. FBrush := Brush;
  208. FStep := Step;
  209. end;
  210. //------------------------------------------------------------------------------
  211. procedure TPaintBrushLine.LineTo(X, Y: Single; Reset: boolean);
  212. var
  213. dX, dY: Single;
  214. LineLength: Single;
  215. StartX, StartY: Single;
  216. NextX, NextY: Single;
  217. i: integer;
  218. Steps: integer;
  219. begin
  220. dX := X-FLastX;
  221. dY := Y-FLastY;
  222. if (dY = 0) and (dX = 0) then
  223. exit;
  224. LineLength := GR32_Math.Hypot(dX, dY);
  225. if (LineLength = 0) then
  226. exit;
  227. // Extend start of line to use remainder of old line length
  228. if (not Reset) then
  229. begin
  230. FLastX := FLastX-FOffset*dX/LineLength;
  231. FLastY := FLastY-FOffset*dY/LineLength;
  232. end;
  233. // Calculate step vector
  234. dX := FStep*dX/LineLength;
  235. dY := FStep*dY/LineLength;
  236. if (not Reset) then
  237. LineLength := LineLength+FOffset;
  238. StartX := FLastX;
  239. StartY := FLastY;
  240. i := 1; // Assume first point was set in MoveTo()
  241. Steps := Trunc(LineLength/FStep);
  242. // Calculate new offset
  243. FOffset := LineLength-Steps*FStep;
  244. while (Steps > 0) do
  245. begin
  246. NextX := StartX+i*dX;
  247. NextY := StartY+i*dY;
  248. FBrush.Draw(FBitmap, NextX, NextY);
  249. // FBrush.DrawTo(FBitmap, Ceil(NextX-FBrush.Width/2), Ceil(NextY-FBrush.Height/2));
  250. inc(i);
  251. dec(Steps);
  252. end;
  253. FLastX := X;
  254. FLastY := Y;
  255. end;
  256. //------------------------------------------------------------------------------
  257. procedure TPaintBrushLine.MoveTo(X, Y: Single; Reset: boolean);
  258. begin
  259. FLastX := X;
  260. FLastY := Y;
  261. if (Reset) then
  262. begin
  263. FBrush.Draw(FBitmap, X, Y);
  264. // FBrush.DrawTo(FBitmap, Ceil(X-FBrush.Width/2), Ceil(Y-FBrush.Height/2));
  265. FOffset := 0;
  266. end;
  267. end;
  268. procedure TPaintBrushLine.RestoreState(const State: TPaintBrushLineState);
  269. begin
  270. FLastX := State.LastX;
  271. FLastY := State.LastY;
  272. FOffset := State.Offset;
  273. end;
  274. procedure TPaintBrushLine.SaveState(var State: TPaintBrushLineState);
  275. begin
  276. State.LastX := FLastX;
  277. State.LastY := FLastY;
  278. State.Offset := FOffset;
  279. State.Valid := True;
  280. end;
  281. //------------------------------------------------------------------------------
  282. //
  283. // TSmudgePaintBrush
  284. //
  285. //------------------------------------------------------------------------------
  286. constructor TSmudgePaintBrush.Create(Width, Height: integer);
  287. begin
  288. inherited Create(Width, Height);
  289. FFinger := TBitmap32.Create;
  290. FPaper := TBitmap32.Create;
  291. FPaper.DrawMode := dmOpaque;
  292. FFirst := True;
  293. end;
  294. //------------------------------------------------------------------------------
  295. destructor TSmudgePaintBrush.Destroy;
  296. begin
  297. FFinger.Free;
  298. FPaper.Free;
  299. inherited Destroy;
  300. end;
  301. //------------------------------------------------------------------------------
  302. procedure TSmudgePaintBrush.SmudgeBlend(Finger, Paper, Mask: TBitmap32; Pressure: integer);
  303. procedure Blend(Finger: TColor32; var Paper: TColor32; Mask, Pressure: Byte);
  304. begin
  305. Pressure := MulDiv(Mask, Pressure, 255);
  306. if (Pressure = 0) then
  307. exit;
  308. if (Pressure = 255) then
  309. begin
  310. Paper := Finger;
  311. exit;
  312. end;
  313. if (Finger and $FF000000 = 0) then
  314. // This causes transparent to stick
  315. Paper := MergeRegEx(Paper, 0, 255-Pressure) // Modulate alpha - stupid but easy
  316. else
  317. if (Paper and $FF000000 = 0) then
  318. // This causes blend onto transparent to maintain color
  319. Paper := MergeRegEx(Finger, 0, Pressure) // Modulate alpha - stupid but easy
  320. else
  321. CombineMem(Finger, Paper, Pressure);
  322. end;
  323. var
  324. pFinger, pPaper, pMask: PColor32;
  325. Count: integer;
  326. begin
  327. ASSERT(Finger.Width = Paper.Width);
  328. ASSERT(Finger.Width = Mask.Width);
  329. ASSERT(Finger.Height = Paper.Height);
  330. ASSERT(Finger.Height = Mask.Height);
  331. pFinger := PColor32(Finger.Bits);
  332. pPaper := PColor32(Paper.Bits);
  333. pMask := PColor32(Mask.Bits);
  334. Count := Finger.Width*Finger.Height;
  335. while (Count > 0) do
  336. begin
  337. Blend(pFinger^, pPaper^, pMask^ shr 24, Pressure);
  338. pFinger^ := pPaper^;
  339. inc(pFinger);
  340. inc(pPaper);
  341. inc(pMask);
  342. dec(Count);
  343. end;
  344. end;
  345. //------------------------------------------------------------------------------
  346. procedure TSmudgePaintBrush.Draw(Bitmap: TBitmap32; x, y: Single);
  347. var
  348. r: TRect;
  349. begin
  350. r := Self.Bitmap.BoundsRect;
  351. GR32.OffsetRect(r, Ceil(x - Width/2), Ceil(y - Height/2));
  352. (*
  353. // Test: Blur brush
  354. if (FFirst) then
  355. begin
  356. FFinger.SetSize(Width, Height);
  357. FPaper.SetSize(Width, Height);
  358. FFirst := False;
  359. end;
  360. BlockTransfer(FFinger, 0, 0, FFinger.BoundsRect, Bitmap, r, dmOpaque);
  361. BoxBlur3(FFinger, 2);
  362. *)
  363. if (not Bitmap.MeasuringMode) then
  364. begin
  365. if (not FFirst) then
  366. begin
  367. // Get ink from paper
  368. BlockTransfer(FPaper, 0, 0, FPaper.BoundsRect, Bitmap, r, dmOpaque);
  369. // Blend with ink on finger
  370. SmudgeBlend(FFinger, FPaper, Self.Bitmap, MulDiv(Pressure, 255, 100));
  371. // Copy back to paper
  372. FPaper.DrawTo(Bitmap, r);
  373. end;
  374. // If first time - then create ink bitmap
  375. if (FFirst) then
  376. begin
  377. FFinger.SetSize(Width, Height);
  378. FPaper.SetSize(Width, Height);
  379. FFirst := False;
  380. // Get ink from paper
  381. BlockTransfer(FFinger, 0, 0, FFinger.BoundsRect, Bitmap, r, dmOpaque);
  382. end;
  383. end;
  384. Bitmap.Changed(r);
  385. end;
  386. //------------------------------------------------------------------------------
  387. end.