mirror of https://github.com/emgucv/emgucv.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
525 lines
18 KiB
525 lines
18 KiB
//----------------------------------------------------------------------------
|
|
// Copyright (C) 2004-2019 by EMGU Corporation. All rights reserved.
|
|
//----------------------------------------------------------------------------
|
|
|
|
using System;
|
|
using System.Drawing;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Windows.Forms;
|
|
using System.ComponentModel;
|
|
|
|
namespace Emgu.CV.UI
|
|
{
|
|
/// <summary>
|
|
/// A picture box with pan and zoom functionality
|
|
/// </summary>
|
|
public class PanAndZoomPictureBox : PictureBox
|
|
{
|
|
private bool _panableAndZoomable;
|
|
/// <summary>
|
|
/// The zoom scale of the image to be displayed
|
|
/// </summary>
|
|
private double _zoomScale;
|
|
|
|
private Point _mouseDownPosition;
|
|
private MouseButtons _mouseDownButton;
|
|
private Point _bufferPoint;
|
|
private HScrollBar horizontalScrollBar;
|
|
private VScrollBar verticalScrollBar;
|
|
private InterpolationMode _interpolationMode = InterpolationMode.NearestNeighbor;
|
|
private static readonly Cursor _defaultCursor = Cursors.Cross;
|
|
|
|
/// <summary>
|
|
/// The available zoom levels for the displayed image
|
|
/// </summary>
|
|
public static double[] ZoomLevels = new double[] { 0.125, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0 };
|
|
|
|
/// <summary>
|
|
/// Create a picture box with pan and zoom functionality
|
|
/// </summary>
|
|
public PanAndZoomPictureBox()
|
|
{
|
|
InitializeComponent();
|
|
_zoomScale = 1.0;
|
|
|
|
SetScrollBarVisibilityAndMaxMin();
|
|
//Enable double buffering
|
|
ResizeRedraw = false;
|
|
DoubleBuffered = true;
|
|
//this.BorderStyle = BorderStyle.Fixed3D;
|
|
PanableAndZoomable = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or Set the property to enable(disable) Pan and Zoom
|
|
/// </summary>
|
|
protected bool PanableAndZoomable
|
|
{
|
|
get
|
|
{
|
|
return _panableAndZoomable;
|
|
}
|
|
set
|
|
{
|
|
if (_panableAndZoomable != value)
|
|
{
|
|
_panableAndZoomable = value;
|
|
if (_panableAndZoomable)
|
|
{
|
|
MouseEnter += OnMouseEnter;
|
|
MouseWheel += OnMouseWheel;
|
|
MouseMove += OnMouseMove;
|
|
Resize += OnResize;
|
|
MouseDown += OnMouseDown;
|
|
MouseUp += OnMouseUp;
|
|
}
|
|
else
|
|
{
|
|
MouseEnter -= OnMouseEnter;
|
|
MouseWheel -= OnMouseWheel;
|
|
MouseMove -= OnMouseMove;
|
|
Resize -= OnResize;
|
|
MouseDown -= OnMouseDown;
|
|
MouseUp -= OnMouseUp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnMouseDown(object sender, MouseEventArgs e)
|
|
{
|
|
_mouseDownPosition = e.Location;
|
|
_mouseDownButton = e.Button;
|
|
|
|
_bufferPoint = Point.Empty;
|
|
if (e.Button == MouseButtons.Middle)
|
|
Cursor = Cursors.Hand;
|
|
}
|
|
|
|
private void OnMouseUp(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Left && _mouseDownButton == MouseButtons.Left)
|
|
{
|
|
if (!_bufferPoint.IsEmpty)
|
|
{
|
|
DrawReversibleRectangle(GetRectanglePreserveAspect(_bufferPoint, _mouseDownPosition));
|
|
_bufferPoint = Point.Empty;
|
|
}
|
|
|
|
Size size = Min(GetViewSize(), GetImageSize());
|
|
Rectangle imageRegion = new Rectangle(Point.Empty, size);
|
|
if (!imageRegion.Contains(_mouseDownPosition))
|
|
return;
|
|
|
|
Rectangle selectedRectangle = GetRectanglePreserveAspect(e.Location, _mouseDownPosition);
|
|
|
|
if ((selectedRectangle.Width / _zoomScale) > 2 && (selectedRectangle.Height / _zoomScale) > 2)
|
|
{
|
|
int h = Math.Min(horizontalScrollBar.Maximum, horizontalScrollBar.Value + (int)(selectedRectangle.Location.X / _zoomScale));
|
|
int v = Math.Min(verticalScrollBar.Maximum, verticalScrollBar.Value + (int)(selectedRectangle.Location.Y / _zoomScale));
|
|
|
|
_zoomScale = _zoomScale * size.Width / selectedRectangle.Width;
|
|
|
|
SetScrollBarVisibilityAndMaxMin();
|
|
|
|
horizontalScrollBar.Value = h;
|
|
verticalScrollBar.Value = v;
|
|
|
|
Invalidate();
|
|
}
|
|
}
|
|
Cursor = _defaultCursor;
|
|
_mouseDownButton = MouseButtons.None;
|
|
}
|
|
|
|
private void OnMouseEnter(object sender, EventArgs e)
|
|
{ //set this as the active control
|
|
Form f = TopLevelControl as Form;
|
|
if (f != null) f.ActiveControl = this;
|
|
}
|
|
|
|
private void OnResize(object sender, EventArgs e)
|
|
{
|
|
Size viewSize = GetViewSize();
|
|
if (base.Image != null && viewSize.Width > 0 && viewSize.Height > 0)
|
|
{
|
|
SetScrollBarVisibilityAndMaxMin();
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
private void OnMouseWheel(object sender, MouseEventArgs e)
|
|
{ //handle the mouse whell scroll (for zooming)
|
|
double scale = 1.0;
|
|
if (e.Delta > 0)
|
|
{
|
|
scale = 2.0;
|
|
}
|
|
else if (e.Delta < 0)
|
|
{
|
|
scale = 0.5;
|
|
}
|
|
else
|
|
return;
|
|
|
|
SetZoomScale(ZoomScale * scale, e.Location);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The event to fire when the zoom scale is changed
|
|
/// </summary>
|
|
public event EventHandler OnZoomScaleChange;
|
|
|
|
#region Handling ScrollBars
|
|
|
|
private void OnScroll(object sender, ScrollEventArgs e)
|
|
{
|
|
Invalidate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or Set the interpolation mode for zooming operation
|
|
/// </summary>
|
|
[Bindable(false)]
|
|
[Category("Design")]
|
|
[DefaultValue(InterpolationMode.NearestNeighbor)]
|
|
public InterpolationMode InterpolationMode
|
|
{
|
|
get
|
|
{
|
|
return _interpolationMode;
|
|
}
|
|
set
|
|
{
|
|
_interpolationMode = value;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Paint the image
|
|
/// </summary>
|
|
/// <param name="pe">The paint event</param>
|
|
protected override void OnPaint(PaintEventArgs pe)
|
|
{
|
|
if (IsDisposed) return;
|
|
if (this.Image != null //image is set
|
|
&& //either pan or zoom
|
|
(_zoomScale != 1.0 ||
|
|
(horizontalScrollBar.Visible && horizontalScrollBar.Value != 0) ||
|
|
(verticalScrollBar.Visible && verticalScrollBar.Value != 0)))
|
|
{
|
|
if (pe.Graphics.InterpolationMode != _interpolationMode)
|
|
pe.Graphics.InterpolationMode = _interpolationMode;
|
|
|
|
using (System.Drawing.Drawing2D.Matrix transform = pe.Graphics.Transform)
|
|
{
|
|
if (_zoomScale != 1.0)
|
|
transform.Scale((float)_zoomScale, (float)_zoomScale, MatrixOrder.Append);
|
|
|
|
int horizontalTranslation = horizontalScrollBar.Visible ? -horizontalScrollBar.Value : 0;
|
|
int verticleTranslation = verticalScrollBar.Visible ? -verticalScrollBar.Value : 0;
|
|
if (horizontalTranslation != 0 || verticleTranslation != 0)
|
|
transform.Translate(horizontalTranslation,verticleTranslation);
|
|
|
|
pe.Graphics.Transform = transform;
|
|
base.OnPaint(pe);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
base.OnPaint(pe);
|
|
}
|
|
}
|
|
|
|
private void SetScrollBarVisibilityAndMaxMin()
|
|
{
|
|
#region determine if the scroll bar should be visible or not
|
|
horizontalScrollBar.Visible = false;
|
|
verticalScrollBar.Visible = false;
|
|
|
|
if (this.Image == null) return;
|
|
|
|
// If the image is wider than the PictureBox, show the HScrollBar.
|
|
horizontalScrollBar.Visible =
|
|
(int)(this.Image.Size.Width * _zoomScale) > ClientSize.Width;
|
|
|
|
// If the image is taller than the PictureBox, show the VScrollBar.
|
|
verticalScrollBar.Visible =
|
|
(int)(this.Image.Size.Height * _zoomScale) > ClientSize.Height;
|
|
|
|
#endregion
|
|
|
|
// Set the Maximum, LargeChange and SmallChange properties.
|
|
if (horizontalScrollBar.Visible)
|
|
{ // If the offset does not make the Maximum less than zero, set its value.
|
|
horizontalScrollBar.Maximum =
|
|
this.Image.Size.Width -
|
|
(int)(Math.Max(0, ClientSize.Width - (verticalScrollBar.Visible ? verticalScrollBar.Width : 0)) / _zoomScale);
|
|
}
|
|
else
|
|
{
|
|
horizontalScrollBar.Maximum = 0;
|
|
}
|
|
horizontalScrollBar.LargeChange = (int)Math.Max(horizontalScrollBar.Maximum / 10, 1);
|
|
horizontalScrollBar.SmallChange = (int)Math.Max(horizontalScrollBar.Maximum / 20, 1);
|
|
|
|
if (verticalScrollBar.Visible)
|
|
{ // If the offset does not make the Maximum less than zero, set its value.
|
|
verticalScrollBar.Maximum =
|
|
this.Image.Size.Height -
|
|
(int)(Math.Max(0, ClientSize.Height - (horizontalScrollBar.Visible ? horizontalScrollBar.Height : 0)) / _zoomScale);
|
|
}
|
|
else
|
|
{
|
|
verticalScrollBar.Maximum = 0;
|
|
}
|
|
verticalScrollBar.LargeChange = (int)Math.Max(verticalScrollBar.Maximum / 10, 1);
|
|
verticalScrollBar.SmallChange = (int)Math.Max(verticalScrollBar.Maximum / 20, 1);
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Get the horizontal scroll bar from this control
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public HScrollBar HorizontalScrollBar
|
|
{
|
|
get
|
|
{
|
|
return horizontalScrollBar;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the vertical scroll bar of this control
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public VScrollBar VerticalScrollBar
|
|
{
|
|
get
|
|
{
|
|
return verticalScrollBar;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for tracking the mouse position on the image
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="e"></param>
|
|
private void OnMouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (_mouseDownButton == MouseButtons.Middle && (horizontalScrollBar.Visible || verticalScrollBar.Visible))
|
|
{
|
|
int horizontalShift = (int)((e.X - _mouseDownPosition.X) / _zoomScale);
|
|
int verticalShift = (int)((e.Y - _mouseDownPosition.Y) / _zoomScale);
|
|
|
|
if (horizontalShift == 0 && verticalShift == 0) return;
|
|
|
|
//if (horizontalScrollBar.Visible)
|
|
horizontalScrollBar.Value =
|
|
Math.Max(Math.Min(horizontalScrollBar.Value - horizontalShift, horizontalScrollBar.Maximum), horizontalScrollBar.Minimum);
|
|
//if (verticalScrollBar.Visible)
|
|
verticalScrollBar.Value =
|
|
Math.Max(Math.Min(verticalScrollBar.Value - verticalShift, verticalScrollBar.Maximum), verticalScrollBar.Minimum);
|
|
|
|
if (horizontalShift != 0) _mouseDownPosition.X = e.Location.X;
|
|
if (verticalShift != 0) _mouseDownPosition.Y = e.Location.Y;
|
|
|
|
Invalidate();
|
|
}
|
|
else if (_mouseDownButton == MouseButtons.Left)
|
|
{
|
|
//reverse the previous highlighted rectangle, if there is any
|
|
if (!_bufferPoint.IsEmpty)
|
|
{
|
|
DrawReversibleRectangle(GetRectanglePreserveAspect(_bufferPoint, _mouseDownPosition));
|
|
}
|
|
|
|
//draw the newly selected area
|
|
DrawReversibleRectangle(GetRectanglePreserveAspect(e.Location, _mouseDownPosition));
|
|
|
|
_bufferPoint = e.Location;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the size of the view area
|
|
/// </summary>
|
|
/// <returns>The size of the view area</returns>
|
|
protected internal Size GetViewSize()
|
|
{
|
|
return new Size(
|
|
ClientSize.Width - (verticalScrollBar.Visible ? verticalScrollBar.Width : 0),
|
|
ClientSize.Height - (horizontalScrollBar.Visible ? horizontalScrollBar.Height : 0));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the size of the image
|
|
/// </summary>
|
|
/// <returns>The size of the image</returns>
|
|
protected internal Size GetImageSize()
|
|
{
|
|
if (base.Image == null) return new Size();
|
|
Size imageSize = base.Image.Size;
|
|
return new Size(
|
|
(int)Math.Round(imageSize.Width * _zoomScale),
|
|
(int)Math.Round(imageSize.Height * _zoomScale));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the minimum of the two sizes
|
|
/// </summary>
|
|
/// <param name="s1">The first size</param>
|
|
/// <param name="s2">The second size</param>
|
|
/// <returns>The minimum of the two sizes</returns>
|
|
protected internal static Size Min(Size s1, Size s2)
|
|
{
|
|
return new Size(
|
|
s1.Width < s2.Width ? s1.Width : s2.Width,
|
|
s1.Height < s2.Height ? s1.Height : s2.Height);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw a reversible rectangle on the control.
|
|
/// </summary>
|
|
/// <param name="rect">The rectangle using the control's coordinate system</param>
|
|
public void DrawReversibleRectangle(Rectangle rect)
|
|
{
|
|
rect.Location = PointToScreen(rect.Location);
|
|
ControlPaint.DrawReversibleFrame(
|
|
rect,
|
|
Color.White,
|
|
FrameStyle.Dashed);
|
|
}
|
|
|
|
private Rectangle GetRectanglePreserveAspect(Point p1, Point p2)
|
|
{
|
|
Rectangle rect = GetRectangle(p1, p2);
|
|
Size size = Min(GetViewSize(), GetImageSize());
|
|
|
|
if ((double)rect.Width / rect.Height > (double)size.Width / size.Height)
|
|
rect.Width = (int)((double)size.Width / size.Height * rect.Height);
|
|
else if ((double)rect.Width / rect.Height < (double)size.Width / size.Height)
|
|
rect.Height = (int)((double)rect.Width / size.Width * size.Height);
|
|
|
|
if (rect.Y != p2.Y)
|
|
rect.Y = p2.Y - rect.Height;
|
|
|
|
if (rect.X != p2.X)
|
|
rect.X = p2.X - rect.Width;
|
|
return rect;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Get the rectangle defined by the two points on the control
|
|
/// </summary>
|
|
/// <param name="p1">The first point on the control</param>
|
|
/// <param name="p2">The second point on the control</param>
|
|
/// <returns>the rectangle defined by the two points</returns>
|
|
public Rectangle GetRectangle(Point p1, Point p2)
|
|
{
|
|
int top = Math.Min(p1.Y, p2.Y);
|
|
int bottom = Math.Max(p1.Y, p2.Y);
|
|
int left = Math.Min(p1.X, p2.X);
|
|
int right = Math.Max(p1.X, p2.X);
|
|
|
|
Size size = Min(GetViewSize(), GetImageSize());
|
|
|
|
Rectangle rect = new Rectangle(left, top, right - left, bottom - top);
|
|
rect.Intersect(new Rectangle(Point.Empty, size));
|
|
return rect;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get or Set the zoom scale
|
|
/// </summary>
|
|
[Browsable(false)]
|
|
public double ZoomScale
|
|
{
|
|
get
|
|
{
|
|
return _zoomScale;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the new zoom scale for the displayed image
|
|
/// </summary>
|
|
/// <param name="zoomScale">The new zoom scale</param>
|
|
/// <param name="fixPoint">The point to be fixed, in display coordinate</param>
|
|
public void SetZoomScale(double zoomScale, Point fixPoint)
|
|
{
|
|
if (
|
|
this.Image != null &&
|
|
_zoomScale != zoomScale //the scale has been changed
|
|
&& //and, the scale is not too small
|
|
!(zoomScale < _zoomScale &&
|
|
(this.Image.Size.Width * zoomScale < 2.0
|
|
|| this.Image.Size.Height * zoomScale < 2.0))
|
|
&& //and, the scale is not too big
|
|
!(zoomScale > _zoomScale &&
|
|
(GetViewSize().Width < zoomScale * 2
|
|
|| GetViewSize().Height < zoomScale * 2)))
|
|
{
|
|
//constrain the coordinate to be within valide range
|
|
fixPoint.X = Math.Min(fixPoint.X, (int)(this.Image.Size.Width * _zoomScale));
|
|
fixPoint.Y = Math.Min(fixPoint.Y, (int)(this.Image.Size.Height * _zoomScale));
|
|
|
|
int shiftX = (int)(fixPoint.X * (zoomScale - _zoomScale) / zoomScale / _zoomScale);
|
|
int shiftY = (int)(fixPoint.Y * (zoomScale - _zoomScale) / zoomScale / _zoomScale);
|
|
|
|
_zoomScale = zoomScale;
|
|
|
|
int h = (int)(horizontalScrollBar.Value + shiftX);
|
|
int v = (int)(verticalScrollBar.Value + shiftY);
|
|
SetScrollBarVisibilityAndMaxMin();
|
|
horizontalScrollBar.Value = Math.Min(Math.Max(horizontalScrollBar.Minimum, h), horizontalScrollBar.Maximum); ;
|
|
verticalScrollBar.Value = Math.Min(Math.Max(verticalScrollBar.Minimum, v), verticalScrollBar.Maximum);
|
|
|
|
Invalidate();
|
|
|
|
if (OnZoomScaleChange != null)
|
|
OnZoomScaleChange(this, new EventArgs());
|
|
|
|
}
|
|
}
|
|
|
|
private void InitializeComponent()
|
|
{
|
|
this.horizontalScrollBar = new System.Windows.Forms.HScrollBar();
|
|
this.verticalScrollBar = new System.Windows.Forms.VScrollBar();
|
|
((System.ComponentModel.ISupportInitialize)(this)).BeginInit();
|
|
this.SuspendLayout();
|
|
//
|
|
// horizontalScrollBar
|
|
//
|
|
this.horizontalScrollBar.Dock = System.Windows.Forms.DockStyle.Bottom;
|
|
this.horizontalScrollBar.Location = new System.Drawing.Point(0, 0);
|
|
this.horizontalScrollBar.Name = "horizontalScrollBar";
|
|
this.horizontalScrollBar.Size = new System.Drawing.Size(80, 17);
|
|
this.horizontalScrollBar.TabIndex = 2;
|
|
this.horizontalScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.OnScroll);
|
|
this.Controls.Add(horizontalScrollBar);
|
|
//
|
|
// verticalScrollBar
|
|
//
|
|
this.verticalScrollBar.Dock = System.Windows.Forms.DockStyle.Right;
|
|
this.verticalScrollBar.Location = new System.Drawing.Point(0, 0);
|
|
this.verticalScrollBar.Name = "verticalScrollBar";
|
|
this.verticalScrollBar.Size = new System.Drawing.Size(17, 80);
|
|
this.verticalScrollBar.TabIndex = 1;
|
|
this.verticalScrollBar.Scroll += new System.Windows.Forms.ScrollEventHandler(this.OnScroll);
|
|
this.Controls.Add(verticalScrollBar);
|
|
//
|
|
// PanAndZoomPictureBox
|
|
//
|
|
|
|
((System.ComponentModel.ISupportInitialize)(this)).EndInit();
|
|
this.ResumeLayout(false);
|
|
|
|
}
|
|
}
|
|
}
|