bcroundedimage.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. // SPDX-License-Identifier: LGPL-3.0-linking-exception
  2. {
  3. BCRoundedImage
  4. by Lainz
  5. Last modified: 2020-09-06 19:16 GMT-3
  6. Changelog:
  7. - 2020-09-06: Initial version supporting circle, rounded rectangle and square.
  8. Changing the quality of the resample, setting the rounding.
  9. OnPaintEvent to customize the final drawing.
  10. - 2025-01: MaxM, Changed class ancestor to TCustomBGRAGraphicControl;
  11. Added TBGRABitmap Bitmap draw;
  12. Added Stretch, Proportional, Alignments.
  13. }
  14. unit BCRoundedImage;
  15. {$mode objfpc}{$H+}
  16. interface
  17. uses
  18. Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
  19. BGRABitmap, BGRABitmapTypes, BGRAGraphicControl, BCTypes;
  20. type
  21. TBCRoundedImage = class;
  22. // Event to draw before the image is sent to canvas
  23. //TBCRoundedImagePaintEvent = procedure (const Sender: TBCRoundedImage; const Bitmap: TBGRABitmap) of object;
  24. TBCRoundedImagePaintEvent = TBGRARedrawEvent;
  25. // Supported styles are circle, rounded rectangle and square
  26. TBCRoundedImageStyle = (isCircle, isRoundedRectangle, isSquare);
  27. // Control that draws an image within a rounded border
  28. { TBCRoundedImage }
  29. TBCRoundedImage = class(TCustomBGRAGraphicControl)
  30. private
  31. FBorderStyle: TRoundRectangleOptions;
  32. FOnPaintEvent: TBCRoundedImagePaintEvent;
  33. FPicture: TPicture;
  34. FImageBitmap: TBGRABitmap;
  35. FQuality: TResampleFilter;
  36. FStyle: TBCRoundedImageStyle;
  37. FRounding: single;
  38. FProportional: Boolean;
  39. FOnChange: TNotifyEvent;
  40. FAlignment: TAlignment;
  41. FStretch: Boolean;
  42. FVerticalAlignment: TTextLayout;
  43. function GetOnPaintEvent: TBCRoundedImagePaintEvent;
  44. procedure SetAlignment(AValue: TAlignment);
  45. procedure SetBitmap(AValue: TBGRABitmap);
  46. procedure SetBorderStyle(AValue: TRoundRectangleOptions);
  47. procedure SetOnPaintEvent(AValue: TBCRoundedImagePaintEvent);
  48. procedure SetPicture(AValue: TPicture);
  49. procedure SetProportional(AValue: Boolean);
  50. procedure SetQuality(AValue: TResampleFilter);
  51. procedure SetStretch(AValue: Boolean);
  52. procedure SetStyle(AValue: TBCRoundedImageStyle);
  53. procedure SetRounding(AValue: single);
  54. procedure SetVerticalAlignment(AValue: TTextLayout);
  55. protected
  56. procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer; WithThemeSpace: boolean); override;
  57. public
  58. constructor Create(AOwner: TComponent); override;
  59. destructor Destroy; override;
  60. procedure Paint; override;
  61. procedure Draw(ABitmap: TBGRABitmap);
  62. property Bitmap: TBGRABitmap read FImageBitmap write setBitmap;
  63. published
  64. // The image that's used as background
  65. property Picture: TPicture read FPicture write SetPicture;
  66. // The style can be circle, rounded rectangle or square
  67. property Style: TBCRoundedImageStyle read FStyle write SetStyle;
  68. // The style of the rounded rectangle
  69. property BorderStyle: TRoundRectangleOptions read FBorderStyle write SetBorderStyle;
  70. // Rounding is used when you choose the rounded rectangle style
  71. property Rounding: single read FRounding write SetRounding;
  72. // The quality when resizing the image
  73. property Quality: TResampleFilter read FQuality write SetQuality;
  74. // Stretch Proportianally
  75. property Proportional: Boolean read FProportional write SetProportional;
  76. property Stretch: Boolean read FStretch write SetStretch default True;
  77. // Alignments of the Image inside the Control
  78. property Alignment: TAlignment read FAlignment write SetAlignment default taCenter;
  79. property VerticalAlignment: TTextLayout read FVerticalAlignment write SetVerticalAlignment default tlCenter;
  80. property OnChange: TNotifyEvent read FOnChange write FOnChange;
  81. // You can paint before the bitmap is drawn on canvas
  82. property OnPaintEvent: TBCRoundedImagePaintEvent read GetOnPaintEvent write SetOnPaintEvent; deprecated 'Use OnRedraw instead';
  83. property Anchors;
  84. property Align;
  85. property OnMouseEnter;
  86. property OnMouseLeave;
  87. property OnClick;
  88. end;
  89. { #todo -oMaxM : we could move it to a common unit and use it in BGRAImageList too }
  90. function CalcProportionalRect(AWidth, AHeight, AImageWidth, AImageHeight: Integer;
  91. AHorizAlign: TAlignment; AVertAlign: TTextLayout): TRect;
  92. procedure Register;
  93. implementation
  94. function CalcProportionalRect(AWidth, AHeight, AImageWidth, AImageHeight: Integer; AHorizAlign: TAlignment;
  95. AVertAlign: TTextLayout): TRect;
  96. var
  97. rW, rH:Single;
  98. newWidth,
  99. newHeight:Integer;
  100. begin
  101. FillChar(Result, sizeof(Result), 0);
  102. if (AImageWidth > 0) and (AImageHeight > 0) then
  103. begin
  104. rW := AImageWidth / AWidth;
  105. rH := AImageHeight / AHeight;
  106. if (rW > rH)
  107. then begin
  108. newHeight:= round(AImageHeight / rW);
  109. newWidth := AWidth;
  110. end
  111. else begin
  112. newWidth := round(AImageWidth / rH);
  113. newHeight := AHeight;
  114. end;
  115. case AHorizAlign of
  116. taCenter: Result.Left:= (AWidth-newWidth) div 2;
  117. taRightJustify: Result.Left:= AWidth-newWidth;
  118. end;
  119. case AVertAlign of
  120. tlCenter: Result.Top:= (AHeight-newHeight) div 2;
  121. tlBottom: Result.Top:= AHeight-newHeight;
  122. end;
  123. Result.Right:= Result.Left+newWidth;
  124. Result.Bottom:= Result.Top+newHeight;
  125. end;
  126. end;
  127. procedure Register;
  128. begin
  129. RegisterComponents('BGRA Controls', [TBCRoundedImage]);
  130. end;
  131. procedure TBCRoundedImage.SetProportional(AValue: Boolean);
  132. begin
  133. if FProportional=AValue then Exit;
  134. FProportional:=AValue;
  135. if Assigned(FOnChange) then FOnChange(Self);
  136. Invalidate;
  137. end;
  138. procedure TBCRoundedImage.SetBorderStyle(AValue: TRoundRectangleOptions);
  139. begin
  140. if FBorderStyle=AValue then Exit;
  141. FBorderStyle:=AValue;
  142. if Assigned(FOnChange) then FOnChange(Self);
  143. Invalidate;
  144. end;
  145. function TBCRoundedImage.GetOnPaintEvent: TBCRoundedImagePaintEvent;
  146. begin
  147. Result:= OnRedraw;
  148. end;
  149. procedure TBCRoundedImage.SetAlignment(AValue: TAlignment);
  150. begin
  151. if FAlignment=AValue then Exit;
  152. FAlignment:=AValue;
  153. if Assigned(FOnChange) then FOnChange(Self);
  154. Invalidate;
  155. end;
  156. procedure TBCRoundedImage.SetBitmap(AValue: TBGRABitmap);
  157. begin
  158. if (AValue <> FImageBitmap) then
  159. begin
  160. // Clear actual image
  161. FImageBitmap.Free;
  162. FImageBitmap :=TBGRABitmap.Create(Width, Height, BGRAPixelTransparent);
  163. if (AValue<>nil) then FImageBitmap.Assign(AValue, True); // Associate the new bitmap
  164. if Assigned(FOnChange) then FOnChange(Self);
  165. Invalidate;
  166. end;
  167. end;
  168. procedure TBCRoundedImage.SetPicture(AValue: TPicture);
  169. begin
  170. if (AValue <> FPicture) then
  171. begin
  172. // Clear actual Picture
  173. FPicture.Free;
  174. FPicture :=TPicture.Create;
  175. if (AValue<>nil) then FPicture.Assign(AValue); // Associate the new Picture
  176. if Assigned(FOnChange) then FOnChange(Self);
  177. Invalidate;
  178. end;
  179. end;
  180. procedure TBCRoundedImage.SetOnPaintEvent(AValue: TBCRoundedImagePaintEvent);
  181. begin
  182. OnRedraw:= AValue;
  183. end;
  184. procedure TBCRoundedImage.SetQuality(AValue: TResampleFilter);
  185. begin
  186. if FQuality = AValue then
  187. Exit;
  188. FQuality := AValue;
  189. if Assigned(FOnChange) then FOnChange(Self);
  190. Invalidate;
  191. end;
  192. procedure TBCRoundedImage.SetStretch(AValue: Boolean);
  193. begin
  194. if FStretch=AValue then Exit;
  195. FStretch:=AValue;
  196. if Assigned(FOnChange) then FOnChange(Self);
  197. Invalidate;
  198. end;
  199. procedure TBCRoundedImage.SetStyle(AValue: TBCRoundedImageStyle);
  200. begin
  201. if FStyle = AValue then
  202. Exit;
  203. FStyle := AValue;
  204. if Assigned(FOnChange) then FOnChange(Self);
  205. Invalidate;
  206. end;
  207. procedure TBCRoundedImage.SetRounding(AValue: single);
  208. begin
  209. if FRounding = AValue then
  210. Exit;
  211. FRounding := AValue;
  212. if Assigned(FOnChange) then FOnChange(Self);
  213. Invalidate;
  214. end;
  215. procedure TBCRoundedImage.SetVerticalAlignment(AValue: TTextLayout);
  216. begin
  217. if FVerticalAlignment=AValue then Exit;
  218. FVerticalAlignment:=AValue;
  219. if Assigned(FOnChange) then FOnChange(Self);
  220. Invalidate;
  221. end;
  222. {$hints off}
  223. procedure TBCRoundedImage.CalculatePreferredSize(var PreferredWidth, PreferredHeight: integer; WithThemeSpace: boolean);
  224. begin
  225. PreferredWidth := 100;
  226. PreferredHeight := 100;
  227. end;
  228. constructor TBCRoundedImage.Create(AOwner: TComponent);
  229. begin
  230. inherited Create(AOwner);
  231. FAlignment:= taCenter;
  232. FVerticalAlignment:= tlCenter;
  233. FStretch:= True;
  234. // Create the Image Bitmap
  235. FPicture := TPicture.Create;
  236. FImageBitmap := TBGRABitmap.Create;
  237. FRounding := 10;
  238. FQuality := rfBestQuality;
  239. FBGRA.FillTransparent;
  240. end;
  241. destructor TBCRoundedImage.Destroy;
  242. begin
  243. FPicture.Free;
  244. FImageBitmap.Free;
  245. inherited Destroy;
  246. end;
  247. procedure TBCRoundedImage.Paint;
  248. begin
  249. if (ClientWidth <> FBGRA.Width) or (ClientHeight <> FBGRA.Height)
  250. then FBGRA.SetSize(ClientWidth, ClientHeight);
  251. Draw(FBGRA);
  252. if Assigned(OnRedraw) then OnRedraw(Self, FBGRA);
  253. FBGRA.Draw(Canvas, 0, 0, False);
  254. end;
  255. procedure TBCRoundedImage.Draw(ABitmap: TBGRABitmap);
  256. var
  257. image,
  258. imageD: TBGRABitmap;
  259. imgRect: TRect;
  260. begin
  261. ABitmap.FillTransparent;
  262. if ((FPicture.Width = 0) or (FPicture.Height = 0)) and
  263. FImageBitmap.Empty then exit;
  264. try
  265. if FImageBitmap.Empty
  266. then image := TBGRABitmap.Create(FPicture.Bitmap)
  267. else image := TBGRABitmap.Create(FImageBitmap.Bitmap);
  268. imageD:= TBGRABitmap.Create(Width, Height, BGRAPixelTransparent);
  269. if FProportional
  270. then imgRect:= CalcProportionalRect(Width, Height, image.Width, image.Height,
  271. FAlignment, FVerticalAlignment)
  272. else begin
  273. if FStretch
  274. then imgRect:= Rect(0,0,Width,Height)
  275. else begin
  276. case FAlignment of
  277. taLeftJustify: imgRect.Left:= 0;
  278. taCenter: imgRect.Left:= (Width-image.Width) div 2;
  279. taRightJustify: imgRect.Left:= Width-image.Width;
  280. end;
  281. case FVerticalAlignment of
  282. tlTop: imgRect.Top:= 0;
  283. tlCenter: imgRect.Top:= (Height-image.Height) div 2;
  284. tlBottom: imgRect.Top:= Height-image.Height;
  285. end;
  286. imgRect.Right:= imgRect.Left+image.Width;
  287. imgRect.Bottom:= imgRect.Top+image.Height;
  288. end;
  289. end;
  290. if FStretch or FProportional then
  291. begin
  292. // Stretch with Quality
  293. image.ResampleFilter := FQuality;
  294. BGRAReplace(image, image.Resample(imgRect.Width, imgRect.Height));
  295. end;
  296. imageD.PutImage(imgRect.Left, imgRect.Top, image, dmDrawWithTransparency);
  297. // Style
  298. case FStyle of
  299. isCircle: ABitmap.FillEllipseAntialias(Width div 2, Height div 2,
  300. (Width div 2)-FRounding, (Height div 2)-FRounding, imageD);
  301. isRoundedRectangle: ABitmap.FillRoundRectAntialias(0, 0, Width,
  302. Height, FRounding, FRounding, imageD, FBorderStyle);
  303. else ABitmap.PutImage(0, 0, imageD, dmDrawWithTransparency);
  304. end;
  305. finally
  306. imageD.Free;
  307. image.Free;
  308. end;
  309. end;
  310. end.