UnitMain.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.Samples.Spin, Vcl.ComCtrls, GR32_Image, System.Actions,
  6. Vcl.ActnList,
  7. GR32,
  8. GR32_Resamplers;
  9. type
  10. TFormMain = class(TForm)
  11. ImageSource: TImgView32;
  12. ImageDest: TImgView32;
  13. Panel1: TPanel;
  14. TrackBarAngle: TTrackBar;
  15. Label1: TLabel;
  16. Label2: TLabel;
  17. SpinEditWidth: TSpinEdit;
  18. Label3: TLabel;
  19. SpinEditHeight: TSpinEdit;
  20. TimerApply: TTimer;
  21. CheckBoxUpdate: TCheckBox;
  22. Button1: TButton;
  23. Bevel1: TBevel;
  24. Bevel2: TBevel;
  25. ActionList1: TActionList;
  26. ActionApply: TAction;
  27. Bevel3: TBevel;
  28. StatusBar: TStatusBar;
  29. Label4: TLabel;
  30. ComboBoxResampler: TComboBox;
  31. procedure FormCreate(Sender: TObject);
  32. procedure SettingChanged(Sender: TObject);
  33. procedure CheckBoxUpdateClick(Sender: TObject);
  34. procedure ActionApplyExecute(Sender: TObject);
  35. procedure ActionApplyUpdate(Sender: TObject);
  36. procedure TimerApplyTimer(Sender: TObject);
  37. procedure FormResize(Sender: TObject);
  38. private
  39. FNeedUpdate: boolean;
  40. FLastResized: boolean;
  41. FLastRotated: boolean;
  42. procedure Status(const Msg: string);
  43. procedure QueueUpdate;
  44. procedure PerformUpdate;
  45. procedure PerformResize(BitmapSource, BitmapDest: TBitmap32; NewWidth, NewHeight: integer; ResamplerClass: TCustomResamplerClass = nil; KernelClass: TCustomKernelClass = nil);
  46. procedure PerformRotate(BitmapSource, BitmapDest: TBitmap32; Angle: Single; ResamplerClass: TCustomResamplerClass = nil; KernelClass: TCustomKernelClass = nil);
  47. public
  48. end;
  49. var
  50. FormMain: TFormMain;
  51. implementation
  52. {$R *.dfm}
  53. uses
  54. Diagnostics,
  55. Math,
  56. Types, // Inlining
  57. GR32_Math,
  58. GR32_Transforms,
  59. GR32_Rasterizers,
  60. GR32_Backends_Generic,
  61. GR32.Examples,
  62. GR32.ImageFormats.PNG32;
  63. procedure TFormMain.FormCreate(Sender: TObject);
  64. var
  65. i: integer;
  66. begin
  67. ImageSource.Bitmap.LoadFromFile(Graphics32Examples.MediaFolder+'\coffee.png');
  68. (*
  69. ImageSource.Bitmap.SetSize(3, 3);
  70. ImageSource.Bitmap.Clear(clBlue32);
  71. ImageSource.Bitmap.FillRect(1,1, 2,2, clRed32);
  72. *)
  73. ImageDest.Bitmap.Assign(ImageSource.Bitmap);
  74. SpinEditWidth.Value := ImageSource.Bitmap.Width;
  75. SpinEditHeight.Value := ImageSource.Bitmap.Height;
  76. for i := 0 to ResamplerList.Count-1 do
  77. ComboBoxResampler.Items.AddObject(ResamplerList[i].ClassName, TObject(ResamplerList[i]));
  78. ComboBoxResampler.ItemIndex := 0;
  79. FNeedUpdate := False;
  80. Status('');
  81. end;
  82. procedure TFormMain.FormResize(Sender: TObject);
  83. begin
  84. ImageSource.Width := ClientWidth div 2;
  85. end;
  86. procedure TFormMain.ActionApplyExecute(Sender: TObject);
  87. begin
  88. PerformUpdate;
  89. end;
  90. procedure TFormMain.ActionApplyUpdate(Sender: TObject);
  91. begin
  92. TAction(Sender).Enabled := FNeedUpdate and (not CheckBoxUpdate.Checked);
  93. end;
  94. procedure TFormMain.SettingChanged(Sender: TObject);
  95. begin
  96. QueueUpdate;
  97. end;
  98. procedure TFormMain.TimerApplyTimer(Sender: TObject);
  99. begin
  100. TimerApply.Enabled := False;
  101. PerformUpdate;
  102. end;
  103. procedure TFormMain.CheckBoxUpdateClick(Sender: TObject);
  104. begin
  105. TimerApply.Enabled := FNeedUpdate and CheckBoxUpdate.Checked;
  106. end;
  107. procedure TFormMain.Status(const Msg: string);
  108. begin
  109. StatusBar.SimpleText := Msg;
  110. Update;
  111. end;
  112. type
  113. TBitmap32Cracker = class(TBitmap32);
  114. type
  115. // A backend that allows us to create a bitmap with its own properties but
  116. // which uses the memory storage from a host bitmap.
  117. TGhostingBackend = class(TCustomBackend)
  118. public
  119. procedure GhostBitmap(ABitmap: TBitmap32);
  120. end;
  121. procedure TGhostingBackend.GhostBitmap(ABitmap: TBitmap32);
  122. begin
  123. FOwner.SetSizeFrom(ABitmap);
  124. TBitmap32Cracker(ABitmap).CopyPropertiesTo(FOwner);
  125. FBits := ABitmap.Bits;
  126. Changed;
  127. end;
  128. procedure TFormMain.PerformResize(BitmapSource, BitmapDest: TBitmap32; NewWidth, NewHeight: integer;
  129. ResamplerClass: TCustomResamplerClass; KernelClass: TCustomKernelClass);
  130. var
  131. Resampler: TCustomResampler;
  132. SourceGhost: TBitmap32;
  133. begin
  134. BitmapDest.SetSize(NewWidth, NewHeight);
  135. if (ResamplerClass = nil) then
  136. ResamplerClass := TCustomResamplerClass(BitmapSource.Resampler.ClassType);
  137. SourceGhost := TBitmap32.Create(TGhostingBackend);
  138. try
  139. TGhostingBackend(SourceGhost.Backend).GhostBitmap(BitmapSource);
  140. Resampler := ResamplerClass.Create(SourceGhost);
  141. if (Resampler is TKernelResampler) then
  142. begin
  143. TKernelResampler(Resampler).KernelMode := kmTableLinear;
  144. TKernelResampler(Resampler).TableSize := 256;
  145. if (KernelClass = nil) then
  146. KernelClass := TCubicKernel;
  147. TKernelResampler(Resampler).Kernel := KernelClass.Create;
  148. if (TKernelResampler(Resampler).Kernel is TWindowedSincKernel) then
  149. TWindowedSincKernel(TKernelResampler(Resampler).Kernel).Width := 4;
  150. end;
  151. Resampler.PixelAccessMode := pamTransparentEdge;
  152. // Note: pamSafe relies on BackgroundColor
  153. // Resampler.PixelAccessMode := pamSafe;
  154. StretchTransfer(BitmapDest, BitmapDest.BoundsRect, BitmapDest.BoundsRect, SourceGhost, SourceGhost.BoundsRect, Resampler, dmOpaque, nil);
  155. finally
  156. SourceGhost.Free;
  157. end;
  158. end;
  159. procedure TFormMain.PerformRotate(BitmapSource, BitmapDest: TBitmap32; Angle: Single; ResamplerClass: TCustomResamplerClass; KernelClass: TCustomKernelClass);
  160. var
  161. Transformation: TAffineTransformation;
  162. Resampler: TCustomResampler;
  163. Rasterizer: TRasterizer;
  164. CombineInfo: TCombineInfo;
  165. Transformer: TTransformer;
  166. TransformedBounds: TFloatRect;
  167. TransformedFloatWidth, TransformedFloatHeight: Single;
  168. TransformedWidth, TransformedHeight: integer;
  169. SourceGhost: TBitmap32;
  170. begin
  171. if (Abs(Frac(Angle / 360)) < 0.1/360) then
  172. begin
  173. BitmapSource.CopyMapTo(BitmapDest);
  174. exit;
  175. end;
  176. SourceGhost := TBitmap32.Create(TGhostingBackend);
  177. try
  178. TGhostingBackend(SourceGhost.Backend).GhostBitmap(BitmapSource);
  179. Transformation := TAffineTransformation.Create;
  180. try
  181. Transformation.Clear;
  182. Transformation.SrcRect := FloatRect(0, 0, SourceGhost.Width, SourceGhost.Height);
  183. // Move origin so we will be rotating around center of bitmap
  184. Transformation.Translate(-SourceGhost.Width * 0.5, -SourceGhost.Height * 0.5);
  185. // Rotate
  186. Transformation.Rotate(0, 0, Angle);
  187. TransformedBounds := Transformation.GetTransformedBounds;
  188. // Size destination to fit transformed bitmap
  189. TransformedFloatWidth := TransformedBounds.Right-TransformedBounds.Left;
  190. TransformedWidth := Ceil(TransformedFloatWidth-0.00001);
  191. TransformedFloatHeight := TransformedBounds.Bottom-TransformedBounds.Top;
  192. TransformedHeight := Ceil(TransformedFloatHeight-0.00001);
  193. // Center in destination bitmap
  194. Transformation.Translate(-TransformedBounds.Left + (TransformedWidth-TransformedFloatWidth) * 0.5, -TransformedBounds.Top + (TransformedHeight-TransformedFloatHeight) * 0.5);
  195. if (ResamplerClass = nil) then
  196. ResamplerClass := TCustomResamplerClass(SourceGhost.Resampler.ClassType);
  197. Resampler := ResamplerClass.Create(SourceGhost);
  198. if (Resampler is TKernelResampler) then
  199. begin
  200. TKernelResampler(Resampler).KernelMode := kmTableLinear;
  201. TKernelResampler(Resampler).TableSize := 256;
  202. if (KernelClass = nil) then
  203. KernelClass := TCubicKernel;
  204. TKernelResampler(Resampler).Kernel := KernelClass.Create;
  205. if (TKernelResampler(Resampler).Kernel is TWindowedSincKernel) then
  206. TWindowedSincKernel(TKernelResampler(Resampler).Kernel).Width := 4;
  207. end;
  208. Resampler.PixelAccessMode := pamTransparentEdge;
  209. // Note: pamSafe relies on BackgroundColor
  210. // Resampler.PixelAccessMode := pamSafe;
  211. Transformer := TTransformer.Create(Resampler, Transformation);
  212. try
  213. // Rasterizer := DefaultRasterizerClass.Create;
  214. Rasterizer := TMultithreadedRegularRasterizer.Create;
  215. try
  216. Rasterizer.Sampler := Transformer;
  217. // We use CombineInfo so BufferSource's MasterAlpha isn't used in the rasterization.
  218. CombineInfo.SrcAlpha := 255;
  219. CombineInfo.DrawMode := dmOpaque;
  220. // cmMerge minimizes blend artifacts: rotate pure color rectangle on transparent background. Rotated edges does not retain original color.
  221. CombineInfo.CombineMode := cmMerge;
  222. CombineInfo.CombineCallBack := nil;
  223. CombineInfo.TransparentColor := 0;
  224. BitmapDest.BeginUpdate;
  225. try
  226. BitmapDest.SetSize(TransformedWidth, TransformedHeight);
  227. BitmapDest.Clear(0);
  228. Rasterizer.Rasterize(BitmapDest, BitmapDest.BoundsRect, CombineInfo);
  229. finally
  230. BitmapDest.EndUpdate;
  231. end;
  232. finally
  233. Rasterizer.Free;
  234. end;
  235. finally
  236. Transformer.Free;
  237. end;
  238. finally
  239. Transformation.Free;
  240. end;
  241. finally
  242. SourceGhost.Free;
  243. end;
  244. end;
  245. procedure TFormMain.PerformUpdate;
  246. var
  247. ResamplerClass: TCustomResamplerClass;
  248. NeedResize, NeedRotate: boolean;
  249. StopWatch: TStopWatch;
  250. BitmapTemp: TBitmap32;
  251. BitmapRotateSource: TBitmap32;
  252. BitmapResizeDest: TBitmap32;
  253. begin
  254. Cursor := crHourGlass;
  255. if (ComboBoxResampler.ItemIndex <> -1) then
  256. ResamplerClass := TCustomResamplerClass(ComboBoxResampler.Items.Objects[ComboBoxResampler.ItemIndex])
  257. else
  258. ResamplerClass := TCustomResamplerClass(ImageSource.Bitmap.Resampler.ClassType);
  259. NeedResize := (SpinEditWidth.Value <> ImageSource.Bitmap.Width) or (SpinEditHeight.Value <> ImageSource.Bitmap.height);
  260. NeedRotate := (TrackBarAngle.Position <> 0);
  261. if NeedResize and NeedRotate then
  262. begin
  263. BitmapTemp := TBitmap32.Create(TMemoryBackend);
  264. BitmapResizeDest := BitmapTemp;
  265. BitmapRotateSource := BitmapTemp;
  266. end else
  267. begin
  268. BitmapTemp := nil;
  269. BitmapResizeDest := ImageDest.Bitmap;
  270. BitmapRotateSource := ImageSource.Bitmap;
  271. end;
  272. try
  273. StopWatch := TStopWatch.StartNew;
  274. if (NeedResize) or (NeedRotate) then
  275. begin
  276. if NeedResize then
  277. begin
  278. Status('Resizing...');
  279. PerformResize(ImageSource.Bitmap, BitmapResizeDest, SpinEditWidth.Value, SpinEditHeight.Value, ResamplerClass);
  280. end;
  281. if NeedRotate then
  282. begin
  283. Status('Rotating...');
  284. PerformRotate(BitmapRotateSource, ImageDest.Bitmap, TrackBarAngle.Position, ResamplerClass);
  285. end;
  286. end else
  287. if (FLastResized or FLastRotated) then
  288. ImageSource.Bitmap.CopyMapTo(ImageDest.Bitmap);
  289. StopWatch.Stop;
  290. finally
  291. BitmapTemp.Free;
  292. end;
  293. Status(Format('Completed in %.0n mS', [StopWatch.ElapsedMilliseconds * 1.0]));
  294. Cursor := crDefault;
  295. FNeedUpdate := False;
  296. FLastResized := NeedResize;
  297. FLastRotated := NeedRotate;
  298. end;
  299. procedure TFormMain.QueueUpdate;
  300. begin
  301. FNeedUpdate := True;
  302. TimerApply.Enabled := False;
  303. if (CheckBoxUpdate.Checked) then
  304. begin
  305. TimerApply.Enabled := True;
  306. Status('Update queued...');
  307. end else
  308. Status('Update pending; Press Apply.');
  309. end;
  310. end.