/*=============================================================================
Project: SharpImage
Module: siRegionSelection.cs
Language: C#
Author: Dan Mueller
Date: $Date: 2007-07-06 10:57:00 +1000 (Fri, 06 Jul 2007) $
Revision: $Revision: 2 $
Copyright (c) Queensland University of Technology (QUT) 2007.
All rights reserved.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notices for more information.
=============================================================================*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Diagnostics;
using SharpImage.Main;
namespace SharpImage.Rendering
{
public class siRegionSelection : siSelection
{
#region Construction and Disposal
//=====================================================================
///
/// Public constuctor, which adds a region selction to the center of the given image.
///
/// Uses the size and spacing of the image to compute the starting region.
public siRegionSelection(itk.itkImageBase input) : base()
{
// Save the primary input
this.m_PrimaryInput = input;
// Create the points array
int numPoints = (int)Math.Pow(2.0, input.Dimension);
this.m_Points = new itk.itkPoint[numPoints];
// Compute the default points
if (input.Dimension == 2)
{
itk.itkIndex index0 = new itk.itkIndex(0, 0);
itk.itkIndex index1 = new itk.itkIndex(input.Size[0], 0);
itk.itkIndex index2 = new itk.itkIndex(0, input.Size[1]);
itk.itkIndex index3 = new itk.itkIndex(input.Size[0], input.Size[1]);
input.TransformIndexToPhysicalPoint(index0, out this.Points[0]);
input.TransformIndexToPhysicalPoint(index1, out this.Points[1]);
input.TransformIndexToPhysicalPoint(index2, out this.Points[2]);
input.TransformIndexToPhysicalPoint(index3, out this.Points[3]);
}
else if (input.Dimension == 3)
{
itk.itkIndex index0 = new itk.itkIndex(0, 0, 0);
itk.itkIndex index1 = new itk.itkIndex(input.Size[0], 0, 0);
itk.itkIndex index2 = new itk.itkIndex(0, input.Size[1], 0);
itk.itkIndex index3 = new itk.itkIndex(input.Size[0], input.Size[1], 0);
itk.itkIndex index4 = new itk.itkIndex(0, 0, input.Size[2]);
itk.itkIndex index5 = new itk.itkIndex(input.Size[0], 0, input.Size[2]);
itk.itkIndex index6 = new itk.itkIndex(0, input.Size[1], input.Size[2]);
itk.itkIndex index7 = new itk.itkIndex(input.Size[0], input.Size[1], input.Size[2]);
input.TransformIndexToPhysicalPoint(index0, out this.Points[0]);
input.TransformIndexToPhysicalPoint(index1, out this.Points[1]);
input.TransformIndexToPhysicalPoint(index2, out this.Points[2]);
input.TransformIndexToPhysicalPoint(index3, out this.Points[3]);
input.TransformIndexToPhysicalPoint(index4, out this.Points[4]);
input.TransformIndexToPhysicalPoint(index5, out this.Points[5]);
input.TransformIndexToPhysicalPoint(index6, out this.Points[6]);
input.TransformIndexToPhysicalPoint(index7, out this.Points[7]);
}
// Setup the initial Metadata
this.Metadata["IsTranslating"] = false;
this.Metadata["IsResizing"] = false;
this.Metadata["IsMouseOverControlPoint"] = false;
this.Metadata["IsMouseOverSelection"] = false;
this.Metadata["CurrentControlPointIndex"] = -1;
this.Metadata["MouseDownControlIndex"] = 0;
this.Metadata["MouseDownOppositeIndex"] = 0;
}
//=====================================================================
#endregion
#region Properties
//=====================================================================
private itk.itkImageBase m_PrimaryInput = null;
private itk.itkPoint[] m_Points = null;
///
/// Get the array of points comprising this .
///
public itk.itkPoint[] Points
{
get { return this.m_Points; }
}
///
/// Gets a string describing the selection type.
/// "Circle" for 2D images, "Sphere" for 3D objects,
/// and "Cylinder" for 3D extrusions.
///
public override string TypeName
{
get { return "Region"; }
}
///
/// Returns the GenerateMaskImageFilter "Region" type.
///
public override string GenerateMaskImageFilterType
{
get { return "Region"; }
}
//=====================================================================
#endregion
#region Public Methods
//=====================================================================
///
/// Return a string representing the selection.
///
///
public override string ToString()
{
return this.TypeName + ": " + this.ToImageRegion().ToString();
}
///
/// Convert the selection to an image region, in pixel (ie. index) space.
///
///
public itk.itkImageRegion ToImageRegion()
{
// Determine if index 0 or index 3/7 is the start of the region
int a = 0;
int b = this.Points.Length - 1;
bool aIsStart = true;
for (int i = 0; i < this.Points[0].Dimension; i++)
aIsStart &= this.Points[a][i] < this.Points[b][i];
int indexStart = aIsStart ? a : b;
// Convert the region starting point to an index
itk.itkIndex index;
this.m_PrimaryInput.TransformPhysicalPointToIndex(this.Points[indexStart], out index);
// Get the size
itk.itkVector offset = this.Points[a] - this.Points[b];
itk.itkSize size = new itk.itkSize(offset.Dimension);
for (int i = 0; i < offset.Length; i++)
size[i] = (int)Math.Abs(offset[i]/this.m_PrimaryInput.Spacing[i]);
// Create and return the region
return new itk.itkImageRegion(size, index);
}
//=====================================================================
#endregion
#region Actor Methods
//=====================================================================
///
/// Allows the actor to render itself.
///
///
protected override void OnPaint(siRenderer renderer, PaintEventArgs e)
{
if (renderer is siGdiSliceRenderer)
this.OnPaint(renderer as siGdiSliceRenderer, e);
else
this.ThrowNotSupported("The given renderer is not supported.");
}
///
/// Allows the actor to render itself.
///
///
protected void OnPaint(siGdiSliceRenderer renderer, PaintEventArgs e)
{
// Set to draw smooth
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Get the brushes and pens
SolidBrush brushFill = new SolidBrush(COLOR_FILL);
SolidBrush brushOutline = new SolidBrush(COLOR_OUTLINE);
Pen penOutline = new Pen(brushOutline, PEN_OUTLINE_WIDTH);
// Get the rectangle
RectangleF r = this.GetSelectionAsScreenRectangle(renderer);
// Draw the outline and fill the rectangle
if (r != RectangleF.Empty)
{
g.DrawRectangle(penOutline, r.X, r.Y, r.Width, r.Height);
g.FillRectangle(brushFill, r.X, r.Y, r.Width, r.Height);
// Draw control points
SolidBrush brushControlFill = new SolidBrush(COLOR_CONTROL_FILL);
SolidBrush brushControlOutline = new SolidBrush(COLOR_CONTROL_OUTLINE);
Pen penControlOutline = new Pen(brushControlOutline, PEN_CONTROL_OUTLINE_WIDTH);
foreach (RectangleF controlPoint in this.GetControlPoints(renderer))
{
g.FillRectangle(brushControlFill, controlPoint);
g.DrawRectangle(penControlOutline, controlPoint.X, controlPoint.Y, controlPoint.Width, controlPoint.Height);
}
}
}
///
/// Allows the actor to consume the MouseMove event.
///
///
///
protected override bool OnMouseMove(siRenderer renderer, MouseEventArgs e)
{
base.OnMouseMove(renderer, e);
// Check the Renderer has all the required Metadata variables
if (!renderer.ContainsMetadata("LastImagePointMouseMove"))
return false;
// Get the location as an itkPoint
itk.itkImageBase input = this.GetInputAsImage(renderer);
itk.itkPoint pointMouseMove = (itk.itkPoint)renderer.Metadata["LastImagePointMouseMove"];
itk.itkPoint pointMouseMoveT = renderer.DirectionTransformPoint(pointMouseMove);
// Check if we are translating
if ( (bool)this.Metadata["IsTranslating"] )
{
itk.itkPoint[] pointsBeforeTranslation = (itk.itkPoint[])this.Metadata["PointsBeforeTranslation"];
itk.itkPoint pointMouseDown = (itk.itkPoint)renderer.Metadata["LastImagePointMouseDown"];
itk.itkPoint pointMouseDownT = renderer.DirectionTransformPoint(pointMouseDown);
itk.itkVector offset = pointMouseMoveT - pointMouseDownT;
for (int i = 0; i < this.Points.Length; i++)
this.Points[i] = pointsBeforeTranslation[i] + offset;
this.RaiseModified();
return true;
}
// Check if we are resizing
if ( (bool)this.Metadata["IsResizing"] )
{
// Get the this.Points[] indicies for the control and the opposite
int iControl = (int)this.Metadata["MouseDownControlIndex"];
int iOpposite = (int)this.Metadata["MouseDownOppositeIndex"];
// Make a copy of the points to restore if the region is sized too small
itk.itkPoint[] pointsBeforeResize = new itk.itkPoint[this.Points.Length];
for (int i = 0; i < this.Points.Length; i++)
pointsBeforeResize[i] = new itk.itkPoint(this.Points[i].Data);
// Compute which dimensions we should alter
itk.itkPoint pointXY = new itk.itkPoint(this.Points[0].Dimension);
pointXY[0] = 1.0;
pointXY[1] = 1.0;
itk.itkPoint pointXYT = renderer.DirectionTransformPoint(pointXY);
// Set the components for those points which match the point being moved
for (int n = 0; n < this.Points.Length; n++)
for (int i = 0; i < this.Points[0].Dimension; i++)
if (pointsBeforeResize[n][i] == pointsBeforeResize[iControl][i] &&
pointXYT[i] != 0.0)
this.Points[n][i] = pointMouseMoveT[i];
// Make sure the region is not too small
itk.itkVector size = this.Points[iControl] - this.Points[iOpposite];
bool reset = false;
for (int i = 0; i < size.Length; i++)
if (Math.Abs(size[i]) < input.Spacing[i])
reset = true;
if (reset)
for (int i = 0; i < this.Points.Length; i++)
this.Points[i] = new itk.itkPoint(pointsBeforeResize[i].Data);
else
this.RaiseModified();
return true;
}
// Check if the mouse is over a control point
int controlPointIndex = this.IsMouseOverControlPoint(renderer, e);
if ( controlPointIndex >= 0 )
{
// Show the correct cursor
renderer.Metadata["Cursor"] = this.GetControlPointCursor(controlPointIndex);
this.Metadata["CurrentControlPointIndex"] = controlPointIndex;
this.Metadata["IsMouseOverControlPoint"] = true;
this.Metadata["IsMouseOverSelection"] = false;
return true;
}
// Check if the mouse is over the selection
if (this.IsMouseOverSelection(renderer, pointMouseMoveT))
{
renderer.Metadata["Cursor"] = Cursors.Hand;
this.Metadata["IsMouseOverControlPoint"] = false;
this.Metadata["IsMouseOverSelection"] = true;
this.Metadata["CurrentControlPointIndex"] = -1;
return true;
}
// We did not consume this event
renderer.Metadata["Cursor"] = null;
this.Metadata["IsMouseOverControlPoint"] = false;
this.Metadata["IsMouseOverSelection"] = false;
this.Metadata["CurrentControlPointIndex"] = -1;
return false;
}
///
/// Allows the actor to consume the MouseDown event.
///
///
///
protected override bool OnMouseDown(siRenderer renderer, MouseEventArgs e)
{
base.OnMouseDown(renderer, e);
// Check if we are over the selection
if ( (bool)this.Metadata["IsMouseOverSelection"] )
{
// Override image translation
renderer.Metadata["IsTranslatingImage"] = false;
// Start selection translation
this.Metadata["IsTranslating"] = true;
itk.itkPoint[] pointsBeforeTranslation = new itk.itkPoint[this.Points.Length];
for (int i = 0; i < this.Points.Length; i++)
pointsBeforeTranslation[i] = new itk.itkPoint(this.Points[i].Data);
this.Metadata["PointsBeforeTranslation"] = pointsBeforeTranslation;
return true;
}
// Check if we are over a control point
if ( (bool)this.Metadata["IsMouseOverControlPoint"] )
{
// Override other translations
renderer.Metadata["IsTranslatingImage"] = false;
this.Metadata["IsTranslating"] = false;
// Start selection resize
this.Metadata["IsResizing"] = true;
// Get the this.Points index for the control and the opposite
int iControl = 0;
int iOpposite = 0;
itk.itkImageBase input = this.GetInputAsImage(renderer);
itk.itkPoint pointMouseDown = (itk.itkPoint)renderer.Metadata["LastImagePointMouseDown"];
itk.itkPoint pointMouseDownT = renderer.DirectionTransformPoint(pointMouseDown);
double distanceSmallest = this.Points[iControl].EuclideanDistanceTo(pointMouseDownT);
double distanceBiggest = this.Points[iOpposite].EuclideanDistanceTo(pointMouseDownT);
for (int i = 1; i < this.Points.Length; i++)
{
double distanceTemp = this.Points[i].EuclideanDistanceTo(pointMouseDownT);
if (distanceTemp < distanceSmallest)
{
distanceSmallest = distanceTemp;
iControl = i;
}
if (distanceTemp > distanceBiggest)
{
distanceBiggest = distanceTemp;
iOpposite = i;
}
}
this.Metadata["MouseDownControlIndex"] = iControl;
this.Metadata["MouseDownOppositeIndex"] = iOpposite;
return true;
}
// We did not consume this event
return false;
}
///
/// Allows the actor to consume the MouseUp event.
///
///
///
protected override bool OnMouseUp(siRenderer renderer, MouseEventArgs e)
{
base.OnMouseUp(renderer, e);
if ((bool)this.Metadata["IsTranslating"])
{
this.Metadata["IsTranslating"] = false;
return true;
}
else if ( (bool)this.Metadata["IsResizing"] )
{
this.Metadata["IsResizing"] = false;
return true;
}
// We did not consume this event
return false;
}
//=====================================================================
#endregion
#region Private Helper Methods
//=====================================================================
///
/// Computes the control point rectangles for the current center and radius.
///
///
private RectangleF[] GetControlPoints(siGdiSliceRenderer renderer)
{
// Get the control width
float widthControl = CONTROL_WIDTH * (renderer.ZoomFactor < 0.25 ? (float)renderer.ZoomFactor : 1.0F);
// Get the screen rectangle
RectangleF r = this.GetSelectionAsScreenRectangle(renderer);
if (r == RectangleF.Empty) return new RectangleF[0];
// Create the control points
RectangleF[] result = new RectangleF[4];
result[0] = new RectangleF(r.X - widthControl / 2.0F, r.Y - widthControl / 2.0F, widthControl, widthControl);
result[1] = new RectangleF(r.X + r.Width - widthControl / 2.0F, r.Y - widthControl / 2.0F, widthControl, widthControl);
result[2] = new RectangleF(r.X - widthControl / 2.0F, r.Y + r.Height - widthControl / 2.0F, widthControl, widthControl);
result[3] = new RectangleF(r.X + r.Width - widthControl / 2.0F, r.Y + r.Height - widthControl / 2.0F, widthControl, widthControl);
return result;
}
///
/// Returns the index of the control point which the mouse is over.
/// Returns -1 if not over a control point.
///
///
/// The index of the control OR -1 if not over a control point.
private int IsMouseOverControlPoint(siRenderer renderer, MouseEventArgs e)
{
RectangleF[] controlPoints = this.GetControlPoints(renderer as siGdiSliceRenderer);
int index = 0;
foreach (RectangleF controlPoint in controlPoints)
{
if (controlPoint.Contains(e.Location))
return index;
index++;
}
return -1;
}
///
/// Returns if the mouse is inside the selection.
///
/// The position of the mouse in phyiscal space, transformed by the direction matrix.
///
private bool IsMouseOverSelection(siRenderer renderer, itk.itkPoint pointMouseT)
{
// Get the screen rectangle
RectangleF r = this.GetSelectionAsScreenRectangle(renderer as siGdiSliceRenderer);
if (r == RectangleF.Empty) return false;
// Use the mouse coords to determine if the mouse is over the selection
MouseEventArgs last = renderer.Metadata["LastScreenMouseMove"] as MouseEventArgs;
return r.Contains(last.Location);
}
///
/// Get the cursor for the given control point index.
///
///
///
private Cursor GetControlPointCursor(int controlPoint)
{
switch (controlPoint)
{
case 0: return Cursors.SizeNWSE;
case 1: return Cursors.SizeNESW;
case 2: return Cursors.SizeNESW;
case 3: return Cursors.SizeNWSE;
default: return Cursors.Default;
}
}
///
/// Return the list of points, each being transformed by the direction matrix.
///
///
///
private itk.itkPoint[] GetTransformedPoints(siGdiSliceRenderer renderer)
{
// Get the input
itk.itkImageBase input = this.GetInputAsImage(renderer);
if (input == null) return new itk.itkPoint[0];
// Transform each point
itk.itkPoint[] result = new itk.itkPoint[this.Points.Length];
for (int i = 0; i < this.Points.Length; i++)
result[i] = renderer.DirectionTransformInversePoint(this.Points[i]);
return result;
}
///
/// Get a rectangle in screen coordinates for the selection.
///
///
///
private RectangleF GetSelectionAsScreenRectangle(siGdiSliceRenderer renderer)
{
// Get the input
itk.itkImageBase input = this.GetInputAsImage(renderer);
if (input == null) return RectangleF.Empty;
// Get the slice points
itk.itkPoint[] pointsSlice = this.Points;
if (input.Dimension == 3)
{
pointsSlice = new itk.itkPoint[4];
// Get the transformed points and indicies
itk.itkPoint[] pointsT = this.GetTransformedPoints(renderer);
// Find the largest and smallest indicies
double zSmallest = 500000.0;
double zLargest = -500000.0;
for (int i = 0; i < pointsT.Length; i++)
if (pointsT[i][2] < zSmallest)
zSmallest = pointsT[i][2];
else if (pointsT[i][2] > zLargest)
zLargest = pointsT[i][2];
// Check if the current slice lies between the upper and lower bounds
itk.itkPoint pointSlice;
itk.itkIndex indexSlice = new itk.itkIndex(0, 0, renderer.Slice);
input.TransformIndexToPhysicalPoint(indexSlice, out pointSlice);
if (pointSlice[2] < zSmallest || pointSlice[2] > zLargest)
{
pointsSlice = new itk.itkPoint[0];
}
else
{
// Find the four points to draw
int j = 0;
for (int i = 0; i < pointsT.Length; i++)
if (pointsT[i][2] == zSmallest && j < 4)
pointsSlice[j++] = new itk.itkPoint(pointsT[i].Data);
}
}
// Get the rectangle
if (pointsSlice.Length == 4)
{
// Transform each point into screen space
float smallestX = 50000.0F;
float smallestY = 50000.0F;
PointF[] pointsScreen = new PointF[4];
for (int i = 0; i < 4; i++)
{
pointsScreen[i] = renderer.TransformImagePointToScreenPoint(input, pointsSlice[i]);
if (pointsScreen[i].X < smallestX) smallestX = pointsScreen[i].X;
if (pointsScreen[i].Y < smallestY) smallestY = pointsScreen[i].Y;
}
// Get the rectangle
PointF lowerLeft = new PointF(0.0F, 0.0F);
PointF upperRight = new PointF(0.0F, 0.0F);
for (int i = 0; i < 4; i++)
{
// Get the lower-left and upper-right
// The lower-left is the smallest x and smallest y, and
// the upper-right is the opposite
if (pointsScreen[i].X == smallestX && pointsScreen[i].Y == smallestY)
lowerLeft = pointsScreen[i];
else if (pointsScreen[i].X != smallestX && pointsScreen[i].Y != smallestY)
upperRight = pointsScreen[i];
}
float x = (float)(lowerLeft.X);
float y = (float)(lowerLeft.Y);
float w = (float)(upperRight.X - lowerLeft.X);
float h = (float)(upperRight.Y - lowerLeft.Y);
return new RectangleF(x, y, w, h);
}
return RectangleF.Empty;
}
//=====================================================================
#endregion
}
}