UnitMain.pas 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. unit UnitMain;
  2. interface
  3. uses
  4. Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  5. Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  6. GR32,
  7. GR32_Image,
  8. GR32_Layers,
  9. GR32.Paint.Host.API,
  10. GR32.Paint.Controller.API,
  11. GR32.Paint.MouseController.API;
  12. //------------------------------------------------------------------------------
  13. type
  14. // Interposer
  15. // Redirects WM_PAINT handling to UpdateLayeredWindow
  16. TImage32 = class(GR32_Image.TImage32)
  17. protected
  18. procedure Paint; override;
  19. end;
  20. //------------------------------------------------------------------------------
  21. type
  22. TFormMain = class(TForm)
  23. Image32: TImage32;
  24. procedure Image32PaintStage(Sender: TObject; Buffer: TBitmap32; StageNum: Cardinal);
  25. procedure FormKeyPress(Sender: TObject; var Key: Char);
  26. procedure Image32MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  27. procedure Image32MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  28. procedure Image32MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  29. private
  30. FPaintMouseController: IBitmap32PaintMouseController;
  31. FPaintController: IBitmap32PaintController;
  32. FPaintHost: IBitmap32PaintHost;
  33. FPaintLayer: TBitmapLayer;
  34. private
  35. procedure SetPaintTool(ToolID: integer);
  36. protected
  37. procedure CreateWindowHandle(const Context: TCreateParams); override;
  38. procedure DoShow; override;
  39. public
  40. constructor Create(AOwner: TComponent); override;
  41. end;
  42. var
  43. FormMain: TFormMain;
  44. implementation
  45. {$R *.dfm}
  46. uses
  47. Types,
  48. GR32_Blend,
  49. GR32.Paint.Host,
  50. GR32.Paint.Controller,
  51. GR32.Paint.MouseController,
  52. GR32.Paint.Tool.Pen,
  53. GR32.Paint.Tool.Brush;
  54. //------------------------------------------------------------------------------
  55. constructor TFormMain.Create(AOwner: TComponent);
  56. procedure SetupPaintTools;
  57. begin
  58. FPaintHost := TBitmap32PaintHost.Create(Image32);
  59. FPaintController := TBitmap32PaintController.Create(Image32, FPaintHost);
  60. (* This also works fine
  61. FPaintController := TCustomBitmap32PaintController.Create(FPaintHost);
  62. *)
  63. FPaintMouseController := TBitmap32PaintMouseController.Create(FPaintHost, FPaintController);
  64. FPaintHost.PaintLayer := FPaintLayer;
  65. FPaintHost.ColorPrimary := clWhite32;
  66. FPaintHost.ColorSecondary := clBlack32;
  67. SetPaintTool(1);
  68. end;
  69. begin
  70. inherited;
  71. SetBounds(0, 0, Monitor.Width, Monitor.Height);
  72. // We never resize the image so ensure that the buffer fits the output area exactly
  73. Image32.BufferOversize := 0;
  74. // Load a bitmap so we have something to look at. This is completely optional.
  75. Image32.Bitmap.LoadFromResourceName(HInstance, 'DICE', 'PNG');
  76. // Create a bitmap layer we can paint on.
  77. // We could also just have painted directly on the TImage32.Bitmap
  78. FPaintLayer := Image32.Layers.Add<TBitmapLayer>;
  79. FPaintLayer.Location := FloatRect(Image32.BoundsRect);
  80. FPaintLayer.Bitmap.SetSize(Image32.Width, Image32.Height);
  81. FPaintLayer.Bitmap.DrawMode := dmBlend;
  82. // Since we are blending onto a transparent bitmap, and we need that bitmap to
  83. // stay transparent, we must use the Merge combine mode.
  84. FPaintLayer.Bitmap.CombineMode := cmMerge;
  85. // Do not clear the TImage32 background; We need it transparent
  86. if (Image32.PaintStages[0].Stage = PST_CLEAR_BACKGND) then
  87. Image32.PaintStages[0].Stage := PST_CUSTOM;
  88. // Setup the paint tools so we can draw on the screen
  89. SetupPaintTools;
  90. end;
  91. procedure TFormMain.CreateWindowHandle(const Context: TCreateParams);
  92. var
  93. ExStyle: NativeInt;
  94. begin
  95. inherited;
  96. // An alpha-blended, transparent windows must use the WS_EX_LAYERED
  97. // windows style.
  98. // https://learn.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles#WS_EX_LAYERED
  99. // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setlayeredwindowattributes
  100. // CreateWindowHandle removes WS_EX_LAYERED so we can't set it in CreateParams,
  101. // before the handle is created.
  102. // Instead we must set it here, after the handle has been created.
  103. ExStyle := GetWindowLongA(Handle, GWL_EXSTYLE);
  104. if (ExStyle and WS_EX_LAYERED = 0) then
  105. SetWindowLong(Handle, GWL_EXSTYLE, ExStyle or WS_EX_LAYERED);
  106. end;
  107. //------------------------------------------------------------------------------
  108. procedure TFormMain.DoShow;
  109. begin
  110. inherited;
  111. // Initial paint
  112. Image32.Paint;
  113. end;
  114. //------------------------------------------------------------------------------
  115. procedure TFormMain.FormKeyPress(Sender: TObject; var Key: Char);
  116. begin
  117. case Key of
  118. '0'..'9': SetPaintTool(Ord(Key) - Ord('0'));
  119. else
  120. Close;
  121. end;
  122. Key := #0;
  123. end;
  124. //------------------------------------------------------------------------------
  125. procedure TFormMain.SetPaintTool(ToolID: integer);
  126. begin
  127. case ToolID of
  128. 1:
  129. begin
  130. FPaintController.PaintTool := TBitmap32PaintToolCircularBrush.Create(FPaintHost);
  131. TBitmap32PaintToolCircularBrush(FPaintController.PaintTool).BrushSize := 50;
  132. end;
  133. 2:
  134. begin
  135. FPaintController.PaintTool := TBitmap32PaintToolSmudgeBrush.Create(FPaintHost);
  136. TBitmap32PaintToolSmudgeBrush(FPaintController.PaintTool).BrushSize := 50;
  137. end;
  138. 3:
  139. begin
  140. FPaintController.PaintTool := TBitmap32PaintToolPen.Create(FPaintHost);
  141. end;
  142. else
  143. FPaintController.PaintTool := nil;
  144. end;
  145. end;
  146. //------------------------------------------------------------------------------
  147. procedure TFormMain.Image32MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  148. begin
  149. FPaintMouseController.HandleMouseDown(Sender, Button, Shift, X, Y, Layer);
  150. end;
  151. procedure TFormMain.Image32MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  152. begin
  153. FPaintMouseController.HandleMouseMove(Sender, Shift, X, Y, Layer);
  154. end;
  155. procedure TFormMain.Image32MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  156. begin
  157. FPaintMouseController.HandleMouseUp(Sender, Button, Shift, X, Y, Layer);
  158. end;
  159. //------------------------------------------------------------------------------
  160. procedure TFormMain.Image32PaintStage(Sender: TObject; Buffer: TBitmap32; StageNum: Cardinal);
  161. begin
  162. // Fully transparent background
  163. Buffer.Clear(0);
  164. end;
  165. //------------------------------------------------------------------------------
  166. //
  167. // TImage32 interposer
  168. //
  169. //------------------------------------------------------------------------------
  170. procedure TImage32.Paint;
  171. procedure PremultiplyBitmap(Bitmap: TBitmap32);
  172. var
  173. p: PColor32Entry;
  174. i: integer;
  175. PreMult: PLUT8;
  176. begin
  177. p := PColor32Entry(Bitmap.Bits);
  178. for i := 0 to Bitmap.Height*Bitmap.Width-1 do
  179. begin
  180. PreMult := @MulDiv255Table[p.A];
  181. p.R := PreMult[p.R];
  182. p.G := PreMult[p.G];
  183. p.B := PreMult[p.B];
  184. inc(p);
  185. end;
  186. end;
  187. procedure MakeBitmapOpaque(Bitmap: TBitmap32);
  188. var
  189. p: PColor32Entry;
  190. i: integer;
  191. begin
  192. p := PColor32Entry(Bitmap.Bits);
  193. for i := 0 to Bitmap.Height*Bitmap.Width-1 do
  194. begin
  195. if (p.A = 0) then
  196. p.ARGB := $01000000; // Almost transparent, not visuall noticeable
  197. Inc(p);
  198. end;
  199. end;
  200. var
  201. BlendFunction: TBlendFunction;
  202. BitmapPos: TPoint;
  203. BitmapSize: TSize;
  204. ParentForm: TWinControl;
  205. begin
  206. // Have TImage32 update the buffer
  207. DoPaintBuffer;
  208. // UpdateLayeredWindow needs alpha-premultiple ARGB
  209. PremultiplyBitmap(Buffer);
  210. // Make bitmap "not fully transparent" so we don't click through the transparent areas.
  211. // Disable this to have the form behave as a transparent form.
  212. MakeBitmapOpaque(Buffer);
  213. // Find parent form
  214. ParentForm := Self.Parent;
  215. while (ParentForm.Parent <> nil) do
  216. ParentForm := ParentForm.Parent;
  217. BlendFunction.BlendOp := AC_SRC_OVER;
  218. BlendFunction.BlendFlags := 0;
  219. BlendFunction.SourceConstantAlpha := 255;
  220. BlendFunction.AlphaFormat := AC_SRC_ALPHA;
  221. BitmapPos := GR32.Point(0, 0);
  222. BitmapSize.cx := Buffer.Width;
  223. BitmapSize.cy := Buffer.Height;
  224. if (not UpdateLayeredWindow(ParentForm.Handle, 0, nil, @BitmapSize, Buffer.Canvas.Handle, @BitmapPos, 0, @BlendFunction, ULW_ALPHA)) then
  225. RaiseLastOSError;
  226. end;
  227. //------------------------------------------------------------------------------
  228. end.