2
0

BitmapImage.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. unit BitmapImage;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. A TImage-like component for bitmaps and png files without the TPicture bloat
  8. Also supports other TGraphic types which can be assigned to a TBitmap
  9. Also see TBitmapButton which is the TWinControl version
  10. }
  11. interface
  12. uses
  13. Windows, ShellAPI, Controls, Graphics, Classes, Imaging.pngimage;
  14. type
  15. TPaintEvent = procedure(Sender: TObject; Canvas: TCanvas; var ARect: TRect) of object;
  16. TBitmapImageImplementation = record
  17. private
  18. FControl: TControl;
  19. public
  20. AutoSize: Boolean;
  21. AutoSizeExtraWidth, AutoSizeExtraHeight: Integer;
  22. BackColor: TColor;
  23. Bitmap: TBitmap;
  24. Center: Boolean;
  25. Opacity: Byte;
  26. PngImage: TPngImage;
  27. ReplaceColor: TColor;
  28. ReplaceWithColor: TColor;
  29. Stretch: Boolean;
  30. StretchedBitmap: TBitmap;
  31. StretchedBitmapValid: Boolean;
  32. OnPaint: TPaintEvent;
  33. procedure Init(const AControl: TControl; const AAutoSizeExtraWidth: Integer = 0;
  34. const AAutoSizeExtraHeight: Integer = 0);
  35. procedure DeInit;
  36. class function AdjustColorForStyle(const Control: TControl; const Color: TColor): TColor; static;
  37. function GetInitializeSize(const AscendingTrySizes: array of Integer): Integer;
  38. function InitializeFromIcon(const Instance: HINST; const Name: PChar; const BkColor: TColor; const AscendingTrySizes: array of Integer): Boolean;
  39. function InitializeFromStockIcon(const Siid: SHSTOCKICONID; const BkColor: TColor; const AscendingTrySizes: array of Integer): Boolean;
  40. procedure BitmapChanged(Sender: TObject);
  41. procedure PngImageChanged(Sender: TObject);
  42. procedure SetAutoSize(Sender: TObject; Value: Boolean);
  43. procedure SetBackColor(Sender: TObject; Value: TColor);
  44. procedure SetBitmap(Value: TBitmap);
  45. procedure SetCenter(Sender: TObject; Value: Boolean);
  46. procedure SetGraphic(Value: TGraphic);
  47. procedure SetOpacity(Sender: TObject; Value: Byte);
  48. procedure SetPngImage(Value: TPngImage);
  49. procedure SetReplaceColor(Sender: TObject; Value: TColor);
  50. procedure SetReplaceWithColor(Sender: TObject; Value: TColor);
  51. procedure SetStretch(Sender: TObject; Value: Boolean);
  52. function GetPalette: HPALETTE;
  53. procedure Paint(const Sender: TObject; const Canvas: TCanvas; var R: TRect);
  54. end;
  55. TBitmapImage = class(TGraphicControl)
  56. private
  57. FImpl: TBitmapImageImplementation;
  58. procedure SetBackColor(Value: TColor);
  59. procedure SetBitmap(Value: TBitmap);
  60. procedure SetCenter(Value: Boolean);
  61. procedure SetOpacity(Value: Byte);
  62. procedure SetGraphic(Value: TGraphic);
  63. procedure SetPngImage(Value: TPngImage);
  64. procedure SetReplaceColor(Value: TColor);
  65. procedure SetReplaceWithColor(Value: TColor);
  66. procedure SetStretch(Value: Boolean);
  67. protected
  68. function GetPalette: HPALETTE; override;
  69. procedure Paint; override;
  70. procedure SetAutoSize(Value: Boolean); override;
  71. public
  72. constructor Create(AOwner: TComponent); override;
  73. destructor Destroy; override;
  74. function InitializeFromIcon(const Instance: HINST; const Name: PChar; const BkColor: TColor; const AscendingTrySizes: array of Integer): Boolean;
  75. function InitializeFromStockIcon(const Siid: SHSTOCKICONID; const BkColor: TColor; const AscendingTrySizes: array of Integer): Boolean;
  76. property Bitmap: TBitmap read FImpl.Bitmap write SetBitmap;
  77. property Graphic: TGraphic write SetGraphic;
  78. published
  79. property Align;
  80. property Anchors;
  81. property AutoSize: Boolean read FImpl.AutoSize write SetAutoSize default False;
  82. property BackColor: TColor read FImpl.BackColor write SetBackColor default clBtnFace;
  83. property Center: Boolean read FImpl.Center write SetCenter default False;
  84. property DragCursor;
  85. property DragMode;
  86. property Enabled;
  87. property Opacity: Byte read FImpl.Opacity write SetOpacity default 255;
  88. property ParentShowHint;
  89. property PngImage: TPngImage read FImpl.PngImage write SetPngImage;
  90. property PopupMenu;
  91. property ShowHint;
  92. property Stretch: Boolean read FImpl.Stretch write SetStretch default False;
  93. property ReplaceColor: TColor read FImpl.ReplaceColor write SetReplaceColor default clNone;
  94. property ReplaceWithColor: TColor read FImpl.ReplaceWithColor write SetReplaceWithColor default clNone;
  95. property Visible;
  96. property OnClick;
  97. property OnDblClick;
  98. property OnDragDrop;
  99. property OnDragOver;
  100. property OnEndDrag;
  101. property OnMouseDown;
  102. property OnMouseMove;
  103. property OnMouseUp;
  104. property OnPaint: TPaintEvent read FImpl.OnPaint write FImpl.OnPaint;
  105. property OnStartDrag;
  106. end;
  107. procedure Register;
  108. implementation
  109. uses
  110. CommCtrl, SysUtils, Math, Themes, Resample;
  111. procedure Register;
  112. begin
  113. RegisterComponents('JR', [TBitmapImage]);
  114. end;
  115. { TBitmapImageImplementation }
  116. procedure TBitmapImageImplementation.Init(const AControl: TControl;
  117. const AAutoSizeExtraWidth, AAutoSizeExtraHeight: Integer);
  118. begin
  119. FControl := AControl;
  120. AutoSizeExtraWidth := AAutoSizeExtraWidth;
  121. AutoSizeExtraHeight := AAutoSizeExtraHeight;
  122. BackColor := clNone;
  123. Bitmap := TBitmap.Create;
  124. Bitmap.OnChange := BitmapChanged;
  125. Opacity := 255;
  126. PngImage := TPngImage.Create;
  127. PngImage.OnChange := PngImageChanged;
  128. ReplaceColor := clNone;
  129. ReplaceWithColor := clNone;
  130. StretchedBitmap := TBitmap.Create;
  131. end;
  132. procedure TBitmapImageImplementation.DeInit;
  133. begin
  134. FreeAndNil(StretchedBitmap);
  135. FreeAndNil(PngImage);
  136. FreeAndNil(Bitmap);
  137. end;
  138. class function TBitmapImageImplementation.AdjustColorForStyle(const Control: TControl;
  139. const Color: TColor): TColor;
  140. begin
  141. Result := Color;
  142. if (Result = clBtnFace) or (Result = clWindow) then begin
  143. var LStyle := StyleServices(Control);
  144. if not LStyle.Enabled or LStyle.IsSystemStyle then
  145. LStyle := nil;
  146. if LStyle <> nil then begin
  147. if Result = clBtnFace then
  148. Result := LStyle.GetStyleColor(scPanel)
  149. else
  150. Result := LStyle.GetStyleColor(scWindow);
  151. end;
  152. end;
  153. end;
  154. function TBitmapImageImplementation.GetInitializeSize(const AscendingTrySizes: array of Integer): Integer;
  155. begin
  156. { Find the largest regular icon size smaller than the scaled image }
  157. Result := 0;
  158. for var I := Length(AscendingTrySizes)-1 downto 0 do begin
  159. if (FControl.Width >= AscendingTrySizes[I]) and (FControl.Height >= AscendingTrySizes[I]) then begin
  160. Result := AscendingTrySizes[I];
  161. Break;
  162. end;
  163. end;
  164. if Result = 0 then
  165. Result := Min(FControl.Width, FControl.Height);
  166. end;
  167. function TBitmapImageImplementation.InitializeFromIcon(const Instance: HINST; const Name: PChar; const BkColor: TColor; const AscendingTrySizes: array of Integer): Boolean;
  168. begin
  169. const Size = GetInitializeSize(AscendingTrySizes);
  170. { Load the desired icon }
  171. var Flags: UINT := LR_DEFAULTCOLOR;
  172. if Instance = 0 then
  173. Flags := Flags or LR_LOADFROMFILE;
  174. var Handle := LoadImage(Instance, Name, IMAGE_ICON, Size, Size, Flags);
  175. if Handle = 0 then
  176. Handle := LoadImage(Instance, Name, IMAGE_ICON, 0, 0, Flags);
  177. if Handle <> 0 then begin
  178. const Icon = TIcon.Create;
  179. try
  180. Icon.Handle := Handle;
  181. { Set sizes (overrides any scaling) }
  182. FControl.Width := Icon.Width;
  183. FControl.Height := Icon.Height;
  184. { Set bitmap }
  185. AutoSize := False;
  186. BackColor := BkColor;
  187. Stretch := True;
  188. Bitmap.Assign(Icon);
  189. Result := True;
  190. finally
  191. Icon.Free;
  192. end;
  193. end else
  194. Result := False;
  195. end;
  196. const
  197. IID_IImageList: TGUID = '{46EB5926-582E-4017-9FDF-E8998DAA0950}';
  198. function TBitmapImageImplementation.InitializeFromStockIcon(const Siid: SHSTOCKICONID; const BkColor: TColor; const AscendingTrySizes: array of Integer): Boolean;
  199. begin
  200. { Currently this function always fails on Wine: SHGetStockIconInfo succeeds but simply always
  201. returns -1 as iSysImageIndex, which makes ImageList_GetIcon fail }
  202. Result := False;
  203. var SHStockIconInfo: TSHStockIconInfo;
  204. SHStockIconInfo.cbSize := SizeOf(SHStockIconInfo);
  205. if Succeeded(SHGetStockIconInfo(siid, SHGSI_SYSICONINDEX, SHStockIconInfo)) then begin
  206. var ImageList: HIMAGELIST;
  207. { The SHGetImageList documentation remarks that SHIL_SMALL and SHIL_LARGE are DPI-aware. However
  208. because this does not provide per-monitor DPI awareness, we always use SHIL_JUMBO and perform
  209. scaling ourselves. It also remarks that "the IImageList pointer type, such as that returned in
  210. the ppv parameter can be cast as an HIMAGELIST as needed", and we make use of that. }
  211. const Size = GetInitializeSize(AscendingTrySizes);
  212. var iImageList: Integer;
  213. if Size > 24 then
  214. iImageList := SHIL_JUMBO
  215. else
  216. iImageList := SHIL_EXTRALARGE; { For small images use SHIL_EXTRALARGE, which should be 48x48 at least }
  217. if Succeeded(SHGetImageList(iImageList, IID_IImageList, Pointer(ImageList))) then begin
  218. var Handle := ImageList_GetIcon(ImageList, SHStockIconInfo.iSysImageIndex, ILD_TRANSPARENT);
  219. if Handle <> 0 then begin
  220. const Icon = TIcon.Create;
  221. try
  222. Icon.Handle := Handle;
  223. { Set sizes (overrides any scaling) }
  224. FControl.Width := Size;
  225. FControl.Height := Size;
  226. { Set bitmap }
  227. AutoSize := False;
  228. BackColor := BkColor;
  229. Stretch := True;
  230. Bitmap.Assign(Icon);
  231. Result := True;
  232. finally
  233. Icon.Free;
  234. end;
  235. end;
  236. end;
  237. end;
  238. end;
  239. procedure TBitmapImageImplementation.BitmapChanged(Sender: TObject);
  240. begin
  241. StretchedBitmapValid := False;
  242. if AutoSize and (Bitmap.Width > 0) and (Bitmap.Height > 0) then
  243. FControl.SetBounds(FControl.Left, FControl.Top, Bitmap.Width + AutoSizeExtraWidth,
  244. Bitmap.Height + AutoSizeExtraHeight);
  245. FControl.Invalidate;
  246. end;
  247. procedure TBitmapImageImplementation.PngImageChanged(Sender: TObject);
  248. begin
  249. Bitmap.Assign(PngImage);
  250. end;
  251. procedure TBitmapImageImplementation.SetAutoSize(Sender: TObject; Value: Boolean);
  252. begin
  253. AutoSize := Value;
  254. BitmapChanged(Sender);
  255. end;
  256. procedure TBitmapImageImplementation.SetBackColor(Sender: TObject; Value: TColor);
  257. begin
  258. if BackColor <> Value then begin
  259. BackColor := Value;
  260. BitmapChanged(Sender);
  261. end;
  262. end;
  263. procedure TBitmapImageImplementation.SetBitmap(Value: TBitmap);
  264. begin
  265. Bitmap.Assign(Value);
  266. end;
  267. procedure TBitmapImageImplementation.SetCenter(Sender: TObject; Value: Boolean);
  268. begin
  269. if Center <> Value then begin
  270. Center := Value;
  271. BitmapChanged(Sender);
  272. end;
  273. end;
  274. procedure TBitmapImageImplementation.SetGraphic(Value: TGraphic);
  275. begin
  276. if Value is TPngImage then
  277. SetPngImage(Value as TPngImage)
  278. else
  279. Bitmap.Assign(Value);
  280. end;
  281. procedure TBitmapImageImplementation.SetOpacity(Sender: TObject; Value: Byte);
  282. begin
  283. if Opacity <> Value then begin
  284. Opacity := Value;
  285. BitmapChanged(Sender);
  286. end;
  287. end;
  288. procedure TBitmapImageImplementation.SetPngImage(Value: TPngImage);
  289. begin
  290. PngImage.Assign(Value);
  291. end;
  292. procedure TBitmapImageImplementation.SetReplaceColor(Sender: TObject; Value: TColor);
  293. begin
  294. if ReplaceColor <> Value then begin
  295. ReplaceColor := Value;
  296. BitmapChanged(Sender);
  297. end;
  298. end;
  299. procedure TBitmapImageImplementation.SetReplaceWithColor(Sender: TObject; Value: TColor);
  300. begin
  301. if ReplaceWithColor <> Value then begin
  302. ReplaceWithColor := Value;
  303. BitmapChanged(Sender);
  304. end;
  305. end;
  306. procedure TBitmapImageImplementation.SetStretch(Sender: TObject; Value: Boolean);
  307. begin
  308. if Stretch <> Value then begin
  309. Stretch := Value;
  310. StretchedBitmap.Assign(nil);
  311. BitmapChanged(Sender);
  312. end;
  313. end;
  314. function TBitmapImageImplementation.GetPalette: HPALETTE;
  315. begin
  316. Result := Bitmap.Palette;
  317. end;
  318. procedure TBitmapImageImplementation.Paint(const Sender: TObject; const Canvas: TCanvas; var R: TRect);
  319. begin
  320. const Is32bit = Bitmap.SupportsPartialTransparency;
  321. var W, H: Integer;
  322. var Bmp: TBitmap;
  323. if Stretch and not Bitmap.Empty then begin
  324. W := R.Width;
  325. H := R.Height;
  326. Bmp := StretchedBitmap;
  327. if not StretchedBitmapValid or (StretchedBitmap.Width <> W) or
  328. (StretchedBitmap.Height <> H) then begin
  329. StretchedBitmapValid := True;
  330. if (Bitmap.Width = W) and (Bitmap.Height = H) then
  331. StretchedBitmap.Assign(Bitmap)
  332. else begin
  333. StretchedBitmap.Assign(nil);
  334. if not StretchBmp(Bitmap, StretchedBitmap, W, H, Is32bit) then begin
  335. if Is32bit then begin
  336. StretchedBitmapValid := False;
  337. Bmp := Bitmap;
  338. end else begin
  339. StretchedBitmap.Palette := CopyPalette(Bitmap.Palette);
  340. StretchedBitmap.Width := W;
  341. StretchedBitmap.Height := H;
  342. StretchedBitmap.Canvas.StretchDraw(Rect(0, 0, W, H), Bitmap);
  343. end;
  344. end;
  345. end;
  346. end;
  347. end else begin
  348. Bmp := Bitmap;
  349. W := Bmp.Width;
  350. H := Bmp.Height;
  351. end;
  352. if (BackColor <> clNone) and (Is32Bit or (Bmp.Width < FControl.Width) or (Bmp.Height < FControl.Height)) then begin
  353. var BrushColor := BackColor;
  354. if Sender is TControl then
  355. BrushColor := AdjustColorForStyle(TControl(Sender), BrushColor);
  356. Canvas.Brush.Style := bsSolid;
  357. Canvas.Brush.Color := BrushColor;
  358. Canvas.FillRect(R);
  359. end;
  360. if csDesigning in FControl.ComponentState then begin
  361. Canvas.Pen.Style := psDash;
  362. Canvas.Brush.Style := bsClear;
  363. Canvas.Rectangle(0, 0, FControl.Width, FControl.Height);
  364. end;
  365. if not Bmp.Empty then begin
  366. var X := R.Left;
  367. var Y := R.Top;
  368. if Center then begin
  369. Inc(X, (R.Width - W) div 2);
  370. if X < 0 then
  371. X := 0;
  372. Inc(Y, (R.Height - H) div 2);
  373. if Y < 0 then
  374. Y := 0;
  375. end;
  376. if not Is32bit and (ReplaceColor <> clNone) and (ReplaceWithColor <> clNone) then begin
  377. Canvas.Brush.Color := ReplaceWithColor;
  378. Canvas.BrushCopy(Rect(X, Y, X + W, Y + H), Bmp, Rect(0, 0, Bmp.Width, Bmp.Height), ReplaceColor);
  379. end else if Opacity <> 255 then
  380. Canvas.Draw(X, Y, Bmp, Opacity)
  381. else
  382. Canvas.Draw(X, Y, Bmp);
  383. end;
  384. if Assigned(OnPaint) then
  385. OnPaint(Sender, Canvas, R);
  386. end;
  387. { TBitmapImage }
  388. constructor TBitmapImage.Create(AOwner: TComponent);
  389. begin
  390. inherited;
  391. ControlStyle := ControlStyle + [csParentBackground, csReplicatable];
  392. FImpl.Init(Self);
  393. if IsCustomStyleActive then
  394. FImpl.BackColor := StyleServices(Self).GetStyleColor(scWindow)
  395. else
  396. FImpl.BackColor := clBtnFace;
  397. Width := 105;
  398. Height := 105;
  399. end;
  400. destructor TBitmapImage.Destroy;
  401. begin
  402. FImpl.DeInit;
  403. inherited;
  404. end;
  405. function TBitmapImage.InitializeFromIcon(const Instance: HINST; const Name: PChar; const BkColor: TColor; const AscendingTrySizes: array of Integer): Boolean;
  406. begin
  407. Result := FImpl.InitializeFromIcon(HInstance, Name, BkColor, AscendingTrySizes);
  408. end;
  409. function TBitmapImage.InitializeFromStockIcon(const Siid: SHSTOCKICONID; const BkColor: TColor; const AscendingTrySizes: array of Integer): Boolean;
  410. begin
  411. Result := FImpl.InitializeFromStockIcon(siid, BkColor, AscendingTrySizes);
  412. end;
  413. procedure TBitmapImage.SetAutoSize(Value: Boolean);
  414. begin
  415. FImpl.SetAutoSize(Self, Value);
  416. end;
  417. procedure TBitmapImage.SetBackColor(Value: TColor);
  418. begin
  419. FImpl.SetBackColor(Self, Value);
  420. end;
  421. procedure TBitmapImage.SetBitmap(Value: TBitmap);
  422. begin
  423. FImpl.SetBitmap(Value);
  424. end;
  425. procedure TBitmapImage.SetCenter(Value: Boolean);
  426. begin
  427. FImpl.SetCenter(Self, Value);
  428. end;
  429. procedure TBitmapImage.SetGraphic(Value: TGraphic);
  430. begin
  431. FImpl.SetGraphic(Value);
  432. end;
  433. procedure TBitmapImage.SetOpacity(Value: Byte);
  434. begin
  435. FImpl.SetOpacity(Self, Value);
  436. end;
  437. procedure TBitmapImage.SetPngImage(Value: TPngImage);
  438. begin
  439. FImpl.SetPngImage(Value);
  440. end;
  441. procedure TBitmapImage.SetReplaceColor(Value: TColor);
  442. begin
  443. FImpl.SetReplaceColor(Self, Value);
  444. end;
  445. procedure TBitmapImage.SetReplaceWithColor(Value: TColor);
  446. begin
  447. FImpl.SetReplaceWithColor(Self, Value);
  448. end;
  449. procedure TBitmapImage.SetStretch(Value: Boolean);
  450. begin
  451. FImpl.SetStretch(Self, Value);
  452. end;
  453. function TBitmapImage.GetPalette: HPALETTE;
  454. begin
  455. Result := FImpl.GetPalette;
  456. end;
  457. procedure TBitmapImage.Paint;
  458. begin
  459. var R := ClientRect;
  460. FImpl.Paint(Self, Canvas, R);
  461. end;
  462. end.