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.
512 lines
18 KiB
512 lines
18 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Data;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using System.Reflection;
|
|
using System.Diagnostics;
|
|
using Emgu.CV;
|
|
using Emgu.Util;
|
|
using Emgu.CV.Structure;
|
|
|
|
namespace Emgu.CV.UI
|
|
{
|
|
/// <summary>
|
|
/// An image box is a user control that is similar to picture box, but display Emgu CV IImage and provides enhenced functionalities.
|
|
/// </summary>
|
|
public partial class ImageBox : PictureBox
|
|
{
|
|
#region private fileds
|
|
private IImage _image;
|
|
private IImage _displayedImage;
|
|
private PropertyDialog _propertyDlg;
|
|
|
|
private FunctionalModeOption _functionalMode = FunctionalModeOption.Everything;
|
|
|
|
/// <summary>
|
|
/// A cache to store the ToolStripMenuItems for different types of images
|
|
/// </summary>
|
|
private static readonly Dictionary<Type, ToolStripMenuItem[]> _typeToToolStripMenuItemsDictionary = new Dictionary<Type, ToolStripMenuItem[]>();
|
|
|
|
private Stack<Operation> _operationStack;
|
|
|
|
//private double _zoomLevel = 1.0;
|
|
|
|
/// <summary>
|
|
/// timer used for caculating the frame rate
|
|
/// </summary>
|
|
private DateTime _timerStartTime;
|
|
|
|
/// <summary>
|
|
/// one of the parameters used for caculating the frame rate
|
|
/// </summary>
|
|
private int _imageReceivedSinceCounterStart;
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Create a ImageBox
|
|
/// </summary>
|
|
public ImageBox()
|
|
: base()
|
|
{
|
|
InitializeComponent();
|
|
_operationStack = new Stack<Operation>();
|
|
}
|
|
|
|
#region properties
|
|
/// <summary>
|
|
/// Get or set the functional mode for the ImageBox
|
|
/// </summary>
|
|
[Bindable(false)]
|
|
[Category("Design")]
|
|
[DefaultValue(Emgu.CV.UI.ImageBox.FunctionalModeOption.Everything)]
|
|
public FunctionalModeOption FunctionalMode
|
|
{
|
|
get { return _functionalMode; }
|
|
set
|
|
{
|
|
//hide all menu items
|
|
foreach (ToolStripMenuItem mi in contextMenuStrip1.Items)
|
|
mi.Visible = false;
|
|
|
|
if (value == FunctionalModeOption.Everything)
|
|
foreach (ToolStripMenuItem mi in contextMenuStrip1.Items)
|
|
mi.Visible = true;
|
|
|
|
_functionalMode = value;
|
|
}
|
|
}
|
|
|
|
private bool EnablePropertyPanel
|
|
{
|
|
get
|
|
{
|
|
return _propertyDlg != null;
|
|
}
|
|
set
|
|
{
|
|
if (value)
|
|
{ //this is a call to enable the property dlg
|
|
if (_propertyDlg == null)
|
|
_propertyDlg = new PropertyDialog(this);
|
|
|
|
_propertyDlg.Show();
|
|
|
|
_propertyDlg.FormClosed +=
|
|
delegate(object sender, FormClosedEventArgs e)
|
|
{
|
|
_propertyDlg = null;
|
|
};
|
|
|
|
ImagePropertyPanel.SetOperationStack(_operationStack);
|
|
|
|
// reset the image such that the property is updated
|
|
Image = Image;
|
|
}
|
|
else
|
|
{
|
|
if (_propertyDlg != null)
|
|
{
|
|
_propertyDlg.Focus();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the image for this image box
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public new IImage Image
|
|
{
|
|
get
|
|
{
|
|
return _image;
|
|
}
|
|
set
|
|
{
|
|
if (InvokeRequired)
|
|
{
|
|
this.Invoke(new MethodInvoker(delegate() { Image = value; }));
|
|
}
|
|
else
|
|
{
|
|
_image = value;
|
|
|
|
IImage imageToBeDisplayed = _image;
|
|
|
|
if (imageToBeDisplayed != null)
|
|
{
|
|
#region perform operations on _operationStack if there is any
|
|
if (_operationStack.Count > 0)
|
|
{
|
|
bool isCloned = false;
|
|
Operation[] ops = _operationStack.ToArray();
|
|
System.Array.Reverse(ops);
|
|
foreach (Operation operation in ops)
|
|
{
|
|
if (operation.Method.ReturnType == typeof(void))
|
|
{ //if this is an inplace operation
|
|
|
|
if (!isCloned)
|
|
{ //make a clone if not already done so
|
|
imageToBeDisplayed = _image.Clone() as IImage;
|
|
isCloned = true;
|
|
}
|
|
|
|
//execute the inplace operation
|
|
operation.InvokeMethod(imageToBeDisplayed);
|
|
}
|
|
else if (operation.Method.ReturnType.GetInterface("IImage") != null)
|
|
{ //if this operation has return value
|
|
|
|
IImage tmp = null;
|
|
if (isCloned == true) //if intermediate image exist, keep a reference of it
|
|
tmp = imageToBeDisplayed;
|
|
|
|
isCloned = true;
|
|
imageToBeDisplayed = operation.InvokeMethod(imageToBeDisplayed) as IImage;
|
|
|
|
if (tmp != null) //dispose the intermediate image
|
|
tmp.Dispose();
|
|
}
|
|
else
|
|
{
|
|
throw new System.NotImplementedException(string.Format("Return type of {0} is not implemented.", operation.Method.ReturnType));
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
DisplayedImage = imageToBeDisplayed;
|
|
}
|
|
|
|
operationsToolStripMenuItem.Enabled = (_image != null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The image that is being displayed. It's the Image following by some user defined image operation
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public IImage DisplayedImage
|
|
{
|
|
get
|
|
{
|
|
return _displayedImage;
|
|
}
|
|
set
|
|
{
|
|
_displayedImage = value;
|
|
if (_displayedImage != null)
|
|
{
|
|
if (_functionalMode != FunctionalModeOption.Minimum)
|
|
BuildOperationMenuItem(_displayedImage);
|
|
|
|
//release the old Bitmap Image
|
|
if (base.Image != null)
|
|
base.Image.Dispose();
|
|
|
|
base.Image = _displayedImage.Bitmap;
|
|
|
|
if (EnablePropertyPanel)
|
|
{
|
|
ImagePropertyPanel.ImageSize = _displayedImage.Size;
|
|
|
|
ImagePropertyPanel.TypeOfColor = Reflection.ReflectIImage.GetTypeOfColor(_displayedImage);
|
|
ImagePropertyPanel.TypeOfDepth = Reflection.ReflectIImage.GetTypeOfDepth(_displayedImage);
|
|
|
|
#region calculate the frame rate
|
|
TimeSpan ts = DateTime.Now.Subtract(_timerStartTime);
|
|
if (ts.TotalSeconds > 1)
|
|
{
|
|
ImagePropertyPanel.FramesPerSecondText = _imageReceivedSinceCounterStart;
|
|
//reset the counter
|
|
_timerStartTime = DateTime.Now;
|
|
_imageReceivedSinceCounterStart = 0;
|
|
}
|
|
else
|
|
{
|
|
_imageReceivedSinceCounterStart++;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Push the specific operation onto the stack
|
|
/// </summary>
|
|
/// <param name="operation">The operation to be pushed onto the stack</param>
|
|
public void PushOperation(Operation operation)
|
|
{
|
|
_operationStack.Push(operation);
|
|
ImageProperty panel = ImagePropertyPanel;
|
|
if (panel != null)
|
|
panel.SetOperationStack(_operationStack);
|
|
|
|
try
|
|
{
|
|
Image = Image;
|
|
}
|
|
catch
|
|
{ //if pushing the operation generate exceptions
|
|
|
|
_operationStack.Pop(); //remove the operation from the stack
|
|
if (panel != null)
|
|
panel.SetOperationStack(_operationStack); //update the operation stack
|
|
|
|
Image = Image; //update the image
|
|
throw; //rethrow the exception
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all the operations from the stack
|
|
/// </summary>
|
|
public void ClearOperation()
|
|
{
|
|
_operationStack.Clear();
|
|
ImageProperty panel = ImagePropertyPanel;
|
|
if (panel != null) panel.SetOperationStack(_operationStack);
|
|
Image = Image;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove the last added operation from the stack
|
|
/// </summary>
|
|
public void PopOperation()
|
|
{
|
|
if (_operationStack.Count > 0)
|
|
{
|
|
_operationStack.Pop();
|
|
ImageProperty panel = ImagePropertyPanel;
|
|
if (panel != null) panel.SetOperationStack(_operationStack);
|
|
Image = Image;
|
|
}
|
|
}
|
|
|
|
private ToolStripMenuItem[] BuildOperationTree(IEnumerable<KeyValuePair<string, MethodInfo>> catelogMiPairList)
|
|
{
|
|
List<ToolStripMenuItem> res = new List<ToolStripMenuItem>();
|
|
|
|
SortedDictionary<String, List<KeyValuePair<String, MethodInfo>>> catelogDic = new SortedDictionary<string, List<KeyValuePair<String, MethodInfo>>>();
|
|
SortedDictionary<String, MethodInfo> operationItem = new SortedDictionary<string, MethodInfo>();
|
|
|
|
foreach (KeyValuePair<string, MethodInfo> pair in catelogMiPairList)
|
|
{
|
|
if (String.IsNullOrEmpty(pair.Key))
|
|
{ //this is an operation
|
|
operationItem.Add(pair.Value.ToString(), pair.Value);
|
|
}
|
|
else
|
|
{ //this is a category
|
|
int index = pair.Key.IndexOf('|');
|
|
String category;
|
|
String subcategory;
|
|
if (index == -1)
|
|
{ //There is no sub category
|
|
category = pair.Key;
|
|
subcategory = string.Empty;
|
|
}
|
|
else
|
|
{ //There are sub categories
|
|
category = pair.Key.Substring(0, index);
|
|
subcategory = pair.Key.Substring(index + 1, pair.Key.Length - index - 1);
|
|
}
|
|
|
|
if (!catelogDic.ContainsKey(category))
|
|
catelogDic.Add(category, new List<KeyValuePair<String, MethodInfo>>());
|
|
catelogDic[category].Add(new KeyValuePair<String, MethodInfo>(subcategory, pair.Value));
|
|
}
|
|
}
|
|
|
|
#region Add catelogs to the menu
|
|
foreach (String catelog in catelogDic.Keys)
|
|
{
|
|
ToolStripMenuItem catelogMenuItem = new ToolStripMenuItem();
|
|
catelogMenuItem.Text = catelog;
|
|
catelogMenuItem.DropDownItems.AddRange(BuildOperationTree(catelogDic[catelog]));
|
|
res.Add(catelogMenuItem);
|
|
}
|
|
#endregion
|
|
|
|
#region Add Methods to the menu
|
|
foreach (MethodInfo mi in operationItem.Values)
|
|
{
|
|
ToolStripMenuItem operationMenuItem = new ToolStripMenuItem();
|
|
operationMenuItem.Size = new System.Drawing.Size(152, 22);
|
|
|
|
Type[] genericArgs = mi.GetGenericArguments();
|
|
|
|
String genericArgString = genericArgs.Length > 0 ?
|
|
String.Format(
|
|
"<{0}>",
|
|
String.Join(",", Array.ConvertAll<Type, String>(
|
|
genericArgs,
|
|
System.Convert.ToString)))
|
|
: string.Empty;
|
|
|
|
operationMenuItem.Text = String.Format(
|
|
"{0}{1}({2})",
|
|
mi.Name,
|
|
genericArgString,
|
|
String.Join(",", System.Array.ConvertAll<ParameterInfo, String>(mi.GetParameters(), delegate(ParameterInfo pi) { return pi.Name; })));
|
|
|
|
//This is necessary to handle delegate with a loop
|
|
//Cause me lots of headache before reading the article on
|
|
//http://decav.com/blogs/andre/archive/2007/11/18/wtf-quot-problems-quot-with-anonymous-delegates-linq-lambdas-and-quot-foreach-quot-or-quot-for-quot-loops.aspx
|
|
//I wishes MSFT handle this better
|
|
MethodInfo methodInfoRef = mi;
|
|
|
|
operationMenuItem.Click += delegate(Object o, EventArgs e)
|
|
{
|
|
Object[] paramList = null;
|
|
|
|
while (true)
|
|
{
|
|
//Get the parameters for the method
|
|
//this pop up an input dialog and ask for user input
|
|
paramList = ParameterInputDialog.GetParams(methodInfoRef, paramList);
|
|
|
|
if (paramList == null) break; //user click cancel on the input dialog
|
|
|
|
//create an operation from the specific methodInfo and parameterlist
|
|
Operation operation = new Operation(methodInfoRef, paramList);
|
|
try
|
|
{
|
|
PushOperation(operation);
|
|
break;
|
|
}
|
|
catch (Exception expt)
|
|
{
|
|
MessageBox.Show(
|
|
expt.InnerException == null ?
|
|
expt.Message
|
|
: expt.InnerException.Message);
|
|
|
|
//special case, then there is no parameter and the method throw an exception
|
|
//break the loop
|
|
if (methodInfoRef.GetParameters().Length == 0)
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
res.Add(operationMenuItem);
|
|
}
|
|
#endregion
|
|
|
|
return res.ToArray();
|
|
}
|
|
|
|
private void BuildOperationMenuItem(IImage image)
|
|
{
|
|
Type typeOfImage = image.GetType();
|
|
|
|
operationsToolStripMenuItem.DropDownItems.Clear();
|
|
|
|
//check if the menu for the specific image type has been built before
|
|
if (!_typeToToolStripMenuItemsDictionary.ContainsKey(typeOfImage))
|
|
{
|
|
//if not built, build it and save to the cache.
|
|
_typeToToolStripMenuItemsDictionary.Add(
|
|
typeOfImage,
|
|
BuildOperationTree(Reflection.ReflectIImage.GetImageMethods(image))
|
|
);
|
|
}
|
|
|
|
operationsToolStripMenuItem.DropDownItems.AddRange(_typeToToolStripMenuItemsDictionary[typeOfImage]);
|
|
}
|
|
|
|
private void loadImageToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (loadImageFromFileDialog.ShowDialog() == DialogResult.OK)
|
|
try
|
|
{
|
|
String filename = loadImageFromFileDialog.FileName;
|
|
Image = new Image<Bgr, byte>(filename);
|
|
}
|
|
catch (Exception excpt)
|
|
{
|
|
MessageBox.Show(excpt.Message);
|
|
}
|
|
}
|
|
|
|
private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
if (saveImageToFileDialog.ShowDialog() == DialogResult.OK)
|
|
try
|
|
{
|
|
DisplayedImage.Save(saveImageToFileDialog.FileName);
|
|
}
|
|
catch (Exception excpt)
|
|
{
|
|
MessageBox.Show(excpt.Message);
|
|
}
|
|
}
|
|
|
|
private void propertyToolStripMenuItem_Click(object sender, EventArgs e)
|
|
{
|
|
EnablePropertyPanel = !EnablePropertyPanel;
|
|
}
|
|
|
|
private ImageProperty ImagePropertyPanel
|
|
{
|
|
get
|
|
{
|
|
return EnablePropertyPanel ? _propertyDlg.ImagePropertyPanel : null;
|
|
}
|
|
}
|
|
|
|
/// <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 (EnablePropertyPanel)
|
|
{
|
|
IImage img = DisplayedImage;
|
|
|
|
IColor color = img == null ?
|
|
null :
|
|
Reflection.ReflectIImage.GetPixelColor(img, e.Location);
|
|
|
|
ImagePropertyPanel.MousePositionOnImage = e.Location;
|
|
ImagePropertyPanel.ColorIntensity = color;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The display mode for ImageBox
|
|
/// </summary>
|
|
public enum FunctionalModeOption
|
|
{
|
|
/// <summary>
|
|
/// The ImageBox is only used for displaying image.
|
|
/// No right-click menu available
|
|
/// </summary>
|
|
Minimum = 0,
|
|
/// <summary>
|
|
/// This is the ImageBox with all functions enabled.
|
|
/// </summary>
|
|
Everything = 1
|
|
}
|
|
|
|
/// <summary>
|
|
/// Release the this Imagebox and all memory associate with it.
|
|
/// </summary>
|
|
public virtual new void Dispose()
|
|
{
|
|
if (this.Image != null)
|
|
this.Image.Dispose();
|
|
base.Dispose();
|
|
}
|
|
}
|
|
}
|