/Emgu.CV.UI/PanAndZoomPictureBox.cs

https://github.com/shiftcontrol/UnityOpenCV · C# · 502 lines · 356 code · 64 blank · 82 comment · 70 complexity · b33ada87797ec574c1d0d4e57875a50a MD5 · raw file

  1. using System;
  2. using System.Drawing;
  3. using System.Drawing.Drawing2D;
  4. using System.Windows.Forms;
  5. using System.ComponentModel;
  6. namespace Emgu.CV.UI
  7. {
  8. /// <summary>
  9. /// A picture box with pan and zoom functionality
  10. /// </summary>
  11. public class PanAndZoomPictureBox : PictureBox
  12. {
  13. private bool _panableAndZoomable;
  14. /// <summary>
  15. /// The zoom scale of the image to be displayed
  16. /// </summary>
  17. private double _zoomScale;
  18. private Point _mouseDownPosition;
  19. private MouseButtons _mouseDownButton;
  20. private Point _bufferPoint;
  21. private HScrollBar horizontalScrollBar;
  22. private VScrollBar verticalScrollBar;
  23. private InterpolationMode _interpolationMode = InterpolationMode.NearestNeighbor;
  24. private static readonly Cursor _defaultCursor = Cursors.Cross;
  25. /// <summary>
  26. /// The available zoom levels for the displayed image
  27. /// </summary>
  28. public static double[] ZoomLevels = new double[] { 0.125, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0 };
  29. /// <summary>
  30. /// Create a picture box with pan and zoom functionality
  31. /// </summary>
  32. public PanAndZoomPictureBox()
  33. {
  34. InitializeComponent();
  35. _zoomScale = 1.0;
  36. SetScrollBarVisibilityAndMaxMin();
  37. //Enable double buffering
  38. ResizeRedraw = false;
  39. DoubleBuffered = true;
  40. //this.BorderStyle = BorderStyle.Fixed3D;
  41. PanableAndZoomable = true;
  42. }
  43. /// <summary>
  44. /// Get or Set the property to enable(disable) Pan and Zoom
  45. /// </summary>
  46. protected bool PanableAndZoomable
  47. {
  48. get
  49. {
  50. return _panableAndZoomable;
  51. }
  52. set
  53. {
  54. if (_panableAndZoomable != value)
  55. {
  56. _panableAndZoomable = value;
  57. if (_panableAndZoomable)
  58. {
  59. MouseEnter += OnMouseEnter;
  60. MouseWheel += OnMouseWheel;
  61. MouseMove += OnMouseMove;
  62. Resize += OnResize;
  63. MouseDown += OnMouseDown;
  64. MouseUp += OnMouseUp;
  65. }
  66. else
  67. {
  68. MouseEnter -= OnMouseEnter;
  69. MouseWheel -= OnMouseWheel;
  70. MouseMove -= OnMouseMove;
  71. Resize -= OnResize;
  72. MouseDown -= OnMouseDown;
  73. MouseUp -= OnMouseUp;
  74. }
  75. }
  76. }
  77. }
  78. private void OnMouseDown(object sender, MouseEventArgs e)
  79. {
  80. _mouseDownPosition = e.Location;
  81. _mouseDownButton = e.Button;
  82. _bufferPoint = Point.Empty;
  83. if (e.Button == MouseButtons.Middle)
  84. Cursor = Cursors.Hand;
  85. }
  86. private void OnMouseUp(object sender, MouseEventArgs e)
  87. {
  88. if (e.Button == MouseButtons.Left && _mouseDownButton == MouseButtons.Left)
  89. {
  90. ReverseRectangle();
  91. Size size = Min(GetViewSize(), GetImageSize());
  92. Rectangle imageRegion = new Rectangle(Point.Empty, size);
  93. if (!imageRegion.Contains(_mouseDownPosition))
  94. return;
  95. Rectangle selectedRectangle = GetSelectedRectangle(e.Location, _mouseDownPosition);
  96. if ((selectedRectangle.Width / _zoomScale) > 2 && (selectedRectangle.Height / _zoomScale) > 2)
  97. {
  98. int h = Math.Min(horizontalScrollBar.Maximum, horizontalScrollBar.Value + (int)(selectedRectangle.Location.X / _zoomScale));
  99. int v = Math.Min(verticalScrollBar.Maximum, verticalScrollBar.Value + (int)(selectedRectangle.Location.Y / _zoomScale));
  100. _zoomScale = _zoomScale * size.Width / selectedRectangle.Width;
  101. SetScrollBarVisibilityAndMaxMin();
  102. horizontalScrollBar.Value = h;
  103. verticalScrollBar.Value = v;
  104. Invalidate();
  105. }
  106. }
  107. Cursor = _defaultCursor;
  108. _mouseDownButton = MouseButtons.None;
  109. }
  110. private void OnMouseEnter(object sender, EventArgs e)
  111. { //set this as the active control
  112. Form f = TopLevelControl as Form;
  113. if (f != null) f.ActiveControl = this;
  114. }
  115. private void OnResize(object sender, EventArgs e)
  116. {
  117. Size viewSize = GetViewSize();
  118. if (base.Image != null && viewSize.Width > 0 && viewSize.Height > 0)
  119. {
  120. SetScrollBarVisibilityAndMaxMin();
  121. Invalidate();
  122. }
  123. }
  124. private void OnMouseWheel(object sender, MouseEventArgs e)
  125. { //handle the mouse whell scroll (for zooming)
  126. double scale = 1.0;
  127. if (e.Delta > 0)
  128. {
  129. scale = 2.0;
  130. }
  131. else if (e.Delta < 0)
  132. {
  133. scale = 0.5;
  134. }
  135. else
  136. return;
  137. SetZoomScale(ZoomScale * scale, e.Location);
  138. }
  139. /// <summary>
  140. /// The event to fire when the zoom scale is changed
  141. /// </summary>
  142. public event EventHandler OnZoomScaleChange;
  143. #region Handling ScrollBars
  144. private void OnScroll(object sender, ScrollEventArgs e)
  145. {
  146. Invalidate();
  147. }
  148. /// <summary>
  149. /// Get or Set the interpolation mode for zooming operation
  150. /// </summary>
  151. [Bindable(false)]
  152. [Category("Design")]
  153. [DefaultValue(InterpolationMode.NearestNeighbor)]
  154. public InterpolationMode InterpolationMode
  155. {
  156. get
  157. {
  158. return _interpolationMode;
  159. }
  160. set
  161. {
  162. _interpolationMode = value;
  163. }
  164. }
  165. /// <summary>
  166. /// Paint the image
  167. /// </summary>
  168. /// <param name="pe">The paint event</param>
  169. protected override void OnPaint(PaintEventArgs pe)
  170. {
  171. if (Image != null //image is set
  172. && //either pan or zoom
  173. (_zoomScale != 1.0f ||
  174. (horizontalScrollBar.Visible && horizontalScrollBar.Value != 0) ||
  175. (verticalScrollBar.Visible && verticalScrollBar.Value != 0)))
  176. {
  177. using (Matrix transform = pe.Graphics.Transform)
  178. {
  179. transform.Scale((float)_zoomScale, (float)_zoomScale, MatrixOrder.Append);
  180. transform.Translate(
  181. horizontalScrollBar.Visible ? -horizontalScrollBar.Value : 0,
  182. verticalScrollBar.Visible ? -verticalScrollBar.Value : 0);
  183. pe.Graphics.Transform = transform;
  184. }
  185. if (pe.Graphics.InterpolationMode != _interpolationMode)
  186. pe.Graphics.InterpolationMode = _interpolationMode;
  187. }
  188. base.OnPaint(pe);
  189. }
  190. private void SetScrollBarVisibilityAndMaxMin()
  191. {
  192. #region determine if the scroll bar should be visible or not
  193. horizontalScrollBar.Visible = false;
  194. verticalScrollBar.Visible = false;
  195. if (Image == null) return;
  196. // If the image is wider than the PictureBox, show the HScrollBar.
  197. horizontalScrollBar.Visible =
  198. (int)(Image.Size.Width * _zoomScale) > ClientSize.Width;
  199. // If the image is taller than the PictureBox, show the VScrollBar.
  200. verticalScrollBar.Visible =
  201. (int)(Image.Size.Height * _zoomScale) > ClientSize.Height;
  202. #endregion
  203. // Set the Maximum, LargeChange and SmallChange properties.
  204. if (horizontalScrollBar.Visible)
  205. { // If the offset does not make the Maximum less than zero, set its value.
  206. horizontalScrollBar.Maximum =
  207. Image.Size.Width -
  208. (int)(Math.Max(0, ClientSize.Width - (verticalScrollBar.Visible ? verticalScrollBar.Width : 0)) / _zoomScale);
  209. }
  210. else
  211. {
  212. horizontalScrollBar.Maximum = 0;
  213. }
  214. horizontalScrollBar.LargeChange = (int)Math.Max(horizontalScrollBar.Maximum / 10, 1);
  215. horizontalScrollBar.SmallChange = (int)Math.Max(horizontalScrollBar.Maximum / 20, 1);
  216. if (verticalScrollBar.Visible)
  217. { // If the offset does not make the Maximum less than zero, set its value.
  218. verticalScrollBar.Maximum =
  219. Image.Size.Height -
  220. (int)(Math.Max(0, ClientSize.Height - (horizontalScrollBar.Visible ? horizontalScrollBar.Height : 0)) / _zoomScale);
  221. }
  222. else
  223. {
  224. verticalScrollBar.Maximum = 0;
  225. }
  226. verticalScrollBar.LargeChange = (int)Math.Max(verticalScrollBar.Maximum / 10, 1);
  227. verticalScrollBar.SmallChange = (int)Math.Max(verticalScrollBar.Maximum / 20, 1);
  228. }
  229. #endregion
  230. /// <summary>
  231. /// Get the horizontal scroll bar from this control
  232. /// </summary>
  233. [Browsable(false)]
  234. public HScrollBar HorizontalScrollBar
  235. {
  236. get
  237. {
  238. return horizontalScrollBar;
  239. }
  240. }
  241. /// <summary>
  242. /// Get the vertical scroll bar of this control
  243. /// </summary>
  244. [Browsable(false)]
  245. public VScrollBar VerticalScrollBar
  246. {
  247. get
  248. {
  249. return verticalScrollBar;
  250. }
  251. }
  252. /// <summary>
  253. /// Used for tracking the mouse position on the image
  254. /// </summary>
  255. /// <param name="sender"></param>
  256. /// <param name="e"></param>
  257. private void OnMouseMove(object sender, MouseEventArgs e)
  258. {
  259. if (_mouseDownButton == MouseButtons.Middle && (horizontalScrollBar.Visible || verticalScrollBar.Visible))
  260. {
  261. int horizontalShift = (int)((e.X - _mouseDownPosition.X) / _zoomScale);
  262. int verticalShift = (int)((e.Y - _mouseDownPosition.Y) / _zoomScale);
  263. if (horizontalShift == 0 && verticalShift == 0) return;
  264. //if (horizontalScrollBar.Visible)
  265. horizontalScrollBar.Value =
  266. Math.Max(Math.Min(horizontalScrollBar.Value - horizontalShift, horizontalScrollBar.Maximum), horizontalScrollBar.Minimum);
  267. //if (verticalScrollBar.Visible)
  268. verticalScrollBar.Value =
  269. Math.Max(Math.Min(verticalScrollBar.Value - verticalShift, verticalScrollBar.Maximum), verticalScrollBar.Minimum);
  270. if (horizontalShift != 0) _mouseDownPosition.X = e.Location.X;
  271. if (verticalShift != 0) _mouseDownPosition.Y = e.Location.Y;
  272. Invalidate();
  273. }
  274. else if (_mouseDownButton == MouseButtons.Left)
  275. {
  276. //reverse the previous highlighted rectangle, if there is any
  277. ReverseRectangle();
  278. Rectangle rect = GetSelectedRectangle(e.Location, _mouseDownPosition);
  279. rect.Location = PointToScreen(rect.Location);
  280. ControlPaint.DrawReversibleFrame(
  281. rect,
  282. Color.White,
  283. FrameStyle.Dashed);
  284. _bufferPoint = e.Location;
  285. }
  286. }
  287. private void ReverseRectangle()
  288. {
  289. if (!_bufferPoint.IsEmpty)
  290. {
  291. Rectangle rect = GetSelectedRectangle(_bufferPoint, _mouseDownPosition);
  292. rect.Location = PointToScreen(rect.Location);
  293. ControlPaint.DrawReversibleFrame(
  294. rect,
  295. Color.White,
  296. FrameStyle.Dashed);
  297. _bufferPoint = Point.Empty;
  298. }
  299. }
  300. /// <summary>
  301. /// Get the size of the view area
  302. /// </summary>
  303. /// <returns>The size of the view area</returns>
  304. protected internal Size GetViewSize()
  305. {
  306. return new Size(
  307. ClientSize.Width - (verticalScrollBar.Visible ? verticalScrollBar.Width : 0),
  308. ClientSize.Height - (horizontalScrollBar.Visible ? horizontalScrollBar.Height : 0));
  309. }
  310. /// <summary>
  311. /// Get the size of the image
  312. /// </summary>
  313. /// <returns>The size of the image</returns>
  314. protected internal Size GetImageSize()
  315. {
  316. if (base.Image == null) return new Size();
  317. Size imageSize = base.Image.Size;
  318. return new Size(
  319. (int)Math.Round(imageSize.Width * _zoomScale),
  320. (int)Math.Round(imageSize.Height * _zoomScale));
  321. }
  322. /// <summary>
  323. /// Get the minimum of the two sizes
  324. /// </summary>
  325. /// <param name="s1">The first size</param>
  326. /// <param name="s2">The second size</param>
  327. /// <returns>The minimum of the two sizes</returns>
  328. protected internal static Size Min(Size s1, Size s2)
  329. {
  330. return new Size(
  331. s1.Width < s2.Width ? s1.Width : s2.Width,
  332. s1.Height < s2.Height ? s1.Height : s2.Height);
  333. }
  334. /// <summary>
  335. /// Get the rectangle defined by the two points
  336. /// </summary>
  337. /// <param name="p1">The first point</param>
  338. /// <param name="p2">The second point</param>
  339. /// <returns>the rectangle defined by the two points</returns>
  340. private Rectangle GetSelectedRectangle(Point p1, Point p2)
  341. {
  342. int top = Math.Min(p1.Y, p2.Y);
  343. int bottom = Math.Max(p1.Y, p2.Y);
  344. int left = Math.Min(p1.X, p2.X);
  345. int right = Math.Max(p1.X, p2.X);
  346. Size size = Min(GetViewSize(), GetImageSize());
  347. Rectangle rect = new Rectangle(left, top, right - left, bottom - top);
  348. rect.Intersect(new Rectangle(Point.Empty, size));
  349. if ((double)rect.Width / rect.Height > (double)size.Width / size.Height)
  350. rect.Width = (int)((double)size.Width / size.Height * rect.Height);
  351. else if ((double)rect.Width / rect.Height < (double)size.Width / size.Height)
  352. rect.Height = (int)((double)rect.Width / size.Width * size.Height);
  353. if (rect.Y != p2.Y)
  354. rect.Y = p2.Y - rect.Height;
  355. if (rect.X != p2.X)
  356. rect.X = p2.X - rect.Width;
  357. return rect;
  358. }
  359. /// <summary>
  360. /// Get or Set the zoom scale
  361. /// </summary>
  362. [Browsable(false)]
  363. public double ZoomScale
  364. {
  365. get
  366. {
  367. return _zoomScale;
  368. }
  369. }
  370. /// <summary>
  371. /// Set the new zoom scale for the displayed image
  372. /// </summary>
  373. /// <param name="zoomScale">The new zoom scale</param>
  374. /// <param name="fixPoint">The point to be fixed, in display coordinate</param>
  375. public void SetZoomScale(double zoomScale, Point fixPoint)
  376. {
  377. if (
  378. Image != null &&
  379. _zoomScale != zoomScale //the scale has been changed
  380. && //and, the scale is not too small
  381. !(zoomScale < _zoomScale &&
  382. (Image.Size.Width * zoomScale < 2.0
  383. || Image.Size.Height * zoomScale < 2.0))
  384. && //and, the scale is not too big
  385. !(zoomScale > _zoomScale &&
  386. (GetViewSize().Width < zoomScale * 2
  387. || GetViewSize().Height < zoomScale * 2)))
  388. {
  389. //constrain the coordinate to be within valide range
  390. fixPoint.X = Math.Min(fixPoint.X, (int)(Image.Size.Width * _zoomScale));
  391. fixPoint.Y = Math.Min(fixPoint.Y, (int)(Image.Size.Height * _zoomScale));
  392. int shiftX = (int)(fixPoint.X * (zoomScale - _zoomScale) / zoomScale / _zoomScale);
  393. int shiftY = (int)(fixPoint.Y * (zoomScale - _zoomScale) / zoomScale / _zoomScale);
  394. _zoomScale = zoomScale;
  395. int h = (int)(horizontalScrollBar.Value + shiftX);
  396. int v = (int)(verticalScrollBar.Value + shiftY);
  397. SetScrollBarVisibilityAndMaxMin();
  398. horizontalScrollBar.Value = Math.Min(Math.Max(horizontalScrollBar.Minimum, h), horizontalScrollBar.Maximum); ;
  399. verticalScrollBar.Value = Math.Min(Math.Max(verticalScrollBar.Minimum, v), verticalScrollBar.Maximum);
  400. Invalidate();
  401. if (OnZoomScaleChange != null)
  402. OnZoomScaleChange(this, new EventArgs());
  403. }
  404. }
  405. private void InitializeComponent()
  406. {
  407. this.horizontalScrollBar = new System.Windows.Forms.HScrollBar();
  408. this.verticalScrollBar = new System.Windows.Forms.VScrollBar();
  409. ((System.ComponentModel.ISupportInitialize)(this)).BeginInit();
  410. this.SuspendLayout();
  411. //
  412. // horizontalScrollBar
  413. //
  414. this.horizontalScrollBar.Dock = System.Windows.Forms.DockStyle.Bottom;
  415. this.horizontalScrollBar.Location = new System.Drawing.Point(0, 0);
  416. this.horizontalScrollBar.Name = "horizontalScrollBar";
  417. this.horizontalScrollBar.Size = new System.Drawing.Size(80, 17);
  418. this.horizontalScrollBar.TabIndex = 2;
  419. this.horizontalScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.OnScroll);
  420. this.Controls.Add(horizontalScrollBar);
  421. //
  422. // verticalScrollBar
  423. //
  424. this.verticalScrollBar.Dock = System.Windows.Forms.DockStyle.Right;
  425. this.verticalScrollBar.Location = new System.Drawing.Point(0, 0);
  426. this.verticalScrollBar.Name = "verticalScrollBar";
  427. this.verticalScrollBar.Size = new System.Drawing.Size(17, 80);
  428. this.verticalScrollBar.TabIndex = 1;
  429. this.verticalScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.OnScroll);
  430. this.Controls.Add(verticalScrollBar);
  431. //
  432. // PanAndZoomPictureBox
  433. //
  434. ((System.ComponentModel.ISupportInitialize)(this)).EndInit();
  435. this.ResumeLayout(false);
  436. }
  437. }
  438. }