/*============================================================================= 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 } }