/*============================================================================= Project: SharpImage Module: siTransferFunctionPartLevoy.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.Drawing; using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Diagnostics; using System.Drawing.Drawing2D; namespace SharpImage.Rendering { public class siTransferFunctionPartLevoy : siTransferFunctionPart { #region Constants //===================================================================== private const float CONTROL_POINT_WIDTH = 6.0F; private const float CONTROL_POINT_HEIGHT = 6.0F; //===================================================================== #endregion #region Instance Variables //===================================================================== //===================================================================== #endregion #region Construction and Disposal //===================================================================== /// /// Default constructor. This part will be drawn on all layers. /// public siTransferFunctionPartLevoy() : this(null, null) { } /// /// Constructor taking a layer key. /// /// Uses the parent.Image for creating a default sized part. /// The name of layer this part is to be drawn on. public siTransferFunctionPartLevoy(siTransferFunction parent, string layer) : base(layer) { // Create a default Levoy part if (parent != null) { this.m_Value = (float)parent.Size0 * 0.5F; this.m_Height = (float)parent.Size1; this.m_Width = (float)parent.Size0 * (1F/3F); this.m_Offset = 0.0F; this.m_Cutoff = 0.0F; } } /// /// Initialise the part Metadata. /// protected override void Initialise() { // Setup default Metadata base.Initialise(); this.Metadata["IsEditing"] = false; } //===================================================================== #endregion #region Properties //===================================================================== #region BoundingBox //===================================================================== /// /// Get the rectangle bounding the part. /// [System.ComponentModel.Browsable(false)] [System.Xml.Serialization.XmlIgnore] public new RectangleF BoundingBox { get { return RectangleF.Empty; } set { } } /// /// Get the x coord of the top-left point of the rectangle bounding the part. /// [System.ComponentModel.Browsable(false)] [System.ComponentModel.Category("Bounding Box")] [System.Xml.Serialization.XmlIgnore] public new float BoundingBoxX { get { return 0.0F; } set { } } /// /// Get the y coord of the top-left point of the rectangle bounding the part. /// [System.ComponentModel.Browsable(false)] [System.ComponentModel.Category("Bounding Box")] [System.Xml.Serialization.XmlIgnore] public new float BoundingBoxY { get { return 0.0F; } set { } } /// /// Get the width point of the rectangle bounding the part. /// [System.ComponentModel.Browsable(false)] [System.ComponentModel.Category("Bounding Box")] [System.Xml.Serialization.XmlIgnore] public new float BoundingBoxWidth { get { return 0.0F; } set { } } /// /// Get the height point of the rectangle bounding the part. /// [System.ComponentModel.Browsable(false)] [System.ComponentModel.Category("Bounding Box")] [System.Xml.Serialization.XmlIgnore] public new float BoundingBoxHeight { get { return 0.0F; } set { } } //===================================================================== #endregion #region Parameters //===================================================================== private float m_Value = 0.0F; private float m_Height = 0.0F; private float m_Width = 0.0F; private float m_Offset = 0.0F; private float m_Cutoff = 0.0F; /// /// Get/set the value the Levoy part is centered around. /// [System.ComponentModel.Browsable(true)] [System.ComponentModel.Category("Parameters")] public float Value { get { return this.m_Value; } set { this.m_Value = value; this.RaiseModified(); } } /// /// Get/set the height along the y-axis. /// [System.ComponentModel.Browsable(true)] [System.ComponentModel.Category("Parameters")] public float Height { get { return this.m_Height; } set { this.m_Height = value; if (this.m_Height < this.m_Cutoff) this.m_Height = this.m_Cutoff; this.RaiseModified(); } } /// /// Get/set the distance from the bottom of the y-axis to the /// start of the function. /// [System.ComponentModel.Browsable(true)] [System.ComponentModel.Category("Parameters")] public float Cutoff { get { return this.m_Cutoff; } set { this.m_Cutoff = value; if (this.m_Cutoff > this.m_Height) this.m_Cutoff = this.m_Height; this.RaiseModified(); } } /// /// Get/set the width along the x-axis. /// [System.ComponentModel.Browsable(true)] [System.ComponentModel.Category("Parameters")] public float Width { get { return this.m_Width; } set { this.m_Width = value; this.RaiseModified(); } } /// /// Get/set the x-axis offset from the value. /// [System.ComponentModel.Browsable(true)] [System.ComponentModel.Category("Parameters")] public float Offset { get { return this.m_Offset; } set { this.m_Offset = value; this.RaiseModified(); } } //===================================================================== #endregion #region Falloff //============================================================================ private Single m_Falloff = 0.5F; /// /// Gets/sets the falloff factor (clamped between 0.0 and 1.0). /// 0.0 is at the edge, 1.0 is the center. /// [System.ComponentModel.Browsable(true)] [System.ComponentModel.Category("Color")] public Single Falloff { get { return this.m_Falloff; } set { this.m_Falloff = value; if (this.m_Falloff < 0.0F) this.m_Falloff = 0.0F; if (this.m_Falloff > 1.0F) this.m_Falloff = 1.0F; this.RaiseModified(); } } //============================================================================ #endregion //===================================================================== #endregion #region Public Methods //===================================================================== public override string ToString() { return "Levoy: " + this.Layer + " " + this.BackColor.ToString() + " " + this.Value.ToString() + " " + this.Height.ToString() + " " + this.Width.ToString(); } //===================================================================== #endregion #region Paint Methods //===================================================================== protected override void OnPaintFillFunction(siTransferFunction parent, siTransferFunctionPaintEventArgs e) { // Create the path GraphicsPath path = this.GetGraphicsPath(this.Cutoff, parent.Size0, parent.Size1); // Create the gradient path PointF[] p = this.GetParameterPoints(parent.Size0, parent.Size1); GraphicsPath pathGradient = new GraphicsPath(); List pList = new List(); List cList = new List(); pList.Add(p[0]); cList.Add(this.BackColor); pList.Add(p[1]); cList.Add(Color.Transparent); //pList.Add(p[2]); cList.Add(this.BackColor); PointF p2Left = new PointF(p[2].X - this.Width/2.0F * (1.0F-this.Falloff), p[2].Y); PointF p2Right = new PointF(p[2].X + this.Width/2.0F * (1.0F-this.Falloff), p[2].Y); pList.Add(p2Left); cList.Add(this.BackColor); pList.Add(p2Right); cList.Add(this.BackColor); //pList.Add(p[2]); cList.Add(this.BackColor); pList.Add(p[3]); cList.Add(Color.Transparent); pList.Add(p[0]); cList.Add(this.BackColor); pathGradient.AddLines(pList.ToArray()); // Fill the path //Brush brushFill = new SolidBrush(this.BackColor); PathGradientBrush brushFill = new PathGradientBrush(pathGradient); PointF t1 = new PointF(0.0F, parent.Size1 - this.Height * 0.0F); PointF t2 = new PointF(parent.Size0, parent.Size1 - this.Height * 0.0F); brushFill.CenterPoint = this.LineToLineIntersection(p[0], p[2], t1, t2); brushFill.CenterColor = this.BackColor; brushFill.SurroundColors = cList.ToArray(); Blend blend = new Blend(3); blend.Positions = new float[] { 0.0F, 0.0F, 1.0F }; blend.Factors = new float[] { 0.0F, 1.0F, 1.0F }; brushFill.Blend = blend; e.Graphics.FillPath(brushFill, path); path.Dispose(); } protected override void OnPaintControlPoints(siTransferFunction parent, siTransferFunctionPaintEventArgs e) { // Set smoothing mode to none SmoothingMode oldSmoothingMode = e.Graphics.SmoothingMode; e.Graphics.SmoothingMode = SmoothingMode.None; // Paint the control points SolidBrush brushFill = new SolidBrush(CONTROL_POINT_COLOR_FILL); SolidBrush brushOutline = new SolidBrush(CONTROL_POINT_COLOR_OUTLINE); Pen penOutline = new Pen(brushOutline, CONTROL_POINT_PEN_WIDTH); foreach (RectangleF cpt in this.GetControlPoints(parent.Size0, parent.Size1)) { e.Graphics.FillRectangle(brushFill, cpt); e.Graphics.DrawRectangle(penOutline, cpt.X, cpt.Y, cpt.Width, cpt.Height); } // Reset smoothing mode e.Graphics.SmoothingMode = oldSmoothingMode; } protected override void OnPaintBoundingBox(siTransferFunction parent, siTransferFunctionPaintEventArgs e) { // Get the graphics handle Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; // Draw an outline around the whole part GraphicsPath path = this.GetGraphicsPath(0, parent.Size0, parent.Size1); Pen penOutline = new Pen(Color.Gray, PEN_WIDTH); g.DrawPath(penOutline, path); path.Dispose(); // Draw an outline around the filled part path = this.GetGraphicsPath(this.Cutoff, parent.Size0, parent.Size1); if (this.Enabled) penOutline = new Pen(BOUNDING_BOX_COLOR_OUTLINE_ENABLED, PEN_WIDTH); else penOutline = new Pen(BOUNDING_BOX_COLOR_OUTLINE_DISABLED, PEN_WIDTH); g.DrawPath(penOutline, path); path.Dispose(); } protected override void OnPaintInformation(siTransferFunction parent, siTransferFunctionPaintEventArgs e) { // Create the info string Brush brush = new SolidBrush(Color.Black); Font font = new Font("Arial", 8.0F, FontStyle.Bold); String info = this.ConstructInformationString(true, e); SizeF sizeInfo = e.Graphics.MeasureString(info, font); // Create the point to print the string float x = this.Value + this.Offset - (sizeInfo.Width / 2.0F); float y = parent.Size1 - this.Height; float width = this.Width; float height = this.Height; PointF pointInfo = new PointF(x + INFO_OFFSET, y + INFO_OFFSET); // Only draw the info if the height will not cut off the info string if (sizeInfo.Height <= (height - 10 * INFO_OFFSET) && sizeInfo.Width <= (width - 2 * INFO_OFFSET)) { //e.Graphics.DrawString(info, font, brushShadow, pointInfo.X + 0.5F, pointInfo.Y + 0.5F); e.Graphics.DrawString(info, font, brush, pointInfo.X, pointInfo.Y); return; } // Adjust the info string info = this.ConstructInformationString(false, e); sizeInfo = e.Graphics.MeasureString(info, font); x = this.Value + this.Offset - (sizeInfo.Width / 2.0F); pointInfo = new PointF(x + INFO_OFFSET, y + INFO_OFFSET); // Only draw the info if the height will not cut off the info string if (sizeInfo.Height <= (height - 10 * INFO_OFFSET) && sizeInfo.Width <= (width - 2 * INFO_OFFSET)) { //e.Graphics.DrawString(info, font, brushShadow, pointInfo.X + 0.5F, pointInfo.Y + 0.5F); e.Graphics.DrawString(info, font, brush, pointInfo.X, pointInfo.Y); return; } } //===================================================================== #endregion #region Mouse Methods //===================================================================== protected override bool OnMouseDown(siTransferFunction parent, MouseEventArgs e) { // Do not consume events if invisible if (!this.Visible) return false; // Raise the mouse down event this.RaiseMouseDownEvent(e); // Record the last mouse down this.Metadata["LastMouseDown"] = e; this.Metadata["ValueMouseDown"] = this.Value; // Check if the mouse is over a control point if ((bool)this.Metadata["IsMouseOverControlPoint"]) { this.Metadata["IsEditing"] = true; return true; } // Check if a double click if (e.Clicks == 2) { // Toggle the enabled property this.Enabled = this.Enabled ? false : true; return true; } // Check if the mouse is over the part GraphicsPath path = this.GetGraphicsPath(this.Cutoff, parent.Size0, parent.Size1); if (path.IsVisible(e.Location)) { this.Metadata["IsTranslating"] = true; path.Dispose(); return true; } path.Dispose(); // The event was not consumed this.Metadata["IsEditing"] = false; this.Metadata["IsTranslating"] = false; return false; } protected override bool OnMouseUp(siTransferFunction parent, MouseEventArgs e) { // Do not consume events if invisible if (!this.Visible) return false; // Cancel an edit if ((bool)this.Metadata["IsEditing"]) { this.Metadata["IsEditing"] = false; return true; } // Cancel a translation if ((bool)this.Metadata["IsTranslating"]) { this.Metadata["IsTranslating"] = false; return true; } // Allow the base class to handle if (base.OnMouseUp(parent, e)) return true; // The event was not consumed this.Metadata["IsEditing"] = false; this.Metadata["IsTranslating"] = false; return false; } protected override bool OnMouseMove(siTransferFunction parent, MouseEventArgs e) { // Do not consume events if invisible if (!this.Visible) return false; // Save the last mouse move MouseEventArgs lastMouseMove = this.Metadata["LastMouseMove"] as MouseEventArgs; this.Metadata["LastMouseMove"] = e; // Consume an editing event if ((bool)this.Metadata["IsEditing"]) { int cpindex = (int)this.Metadata["CurrentControlPointIndex"]; switch (cpindex) { case 0: this.m_Value = e.X; if (this.m_Value < 0) this.m_Value = 0; if (this.m_Value > parent.Size0) this.m_Value = parent.Size0 - 1; this.RaiseModified(); return true; case 1: this.m_Height = parent.Size1 - e.Y; if (this.m_Height < 0) this.m_Height = 0; if (this.m_Height > parent.Size1) this.m_Height = parent.Size1; if (this.m_Height < this.m_Cutoff) this.m_Height = this.m_Cutoff; this.m_Width = 2.0F * (this.m_Value + this.m_Offset - e.X); if (this.m_Width < 1) this.m_Width = 1; if (this.m_Width > 2.0F * parent.Size0) this.m_Width = 2.0F * parent.Size0; this.RaiseModified(); return true; case 2: this.m_Height = parent.Size1 - e.Y; if (this.m_Height < 0) this.m_Height = 0; if (this.m_Height > parent.Size1) this.m_Height = parent.Size1; if (this.m_Height < this.m_Cutoff) this.m_Height = this.m_Cutoff; this.m_Offset = e.X - this.m_Value; this.RaiseModified(); return true; case 3: this.m_Height = parent.Size1 - e.Y; if (this.m_Height < 0) this.m_Height = 0; if (this.m_Height > parent.Size1) this.m_Height = parent.Size1; if (this.m_Height < this.m_Cutoff) this.m_Height = this.m_Cutoff; this.m_Width = 2.0F * (e.X - this.m_Value - this.m_Offset); if (this.m_Width < 1) this.m_Width = 1; if (this.m_Width > 2.0F * parent.Size0) this.m_Width = 2.0F * parent.Size0; this.RaiseModified(); return true; } } // Consume a translation event if ((bool)this.Metadata["IsTranslating"]) { MouseEventArgs lastMouseDown = this.Metadata["LastMouseDown"] as MouseEventArgs; float valueMouseDown = (float)this.Metadata["ValueMouseDown"]; this.m_Value = valueMouseDown + (e.X - lastMouseDown.X); if (this.m_Value < 0) this.m_Value = 0; if (this.m_Value > parent.Size0) this.m_Value = parent.Size0 - 1; this.RaiseModified(); return true; } // Set the mouse control point over Metadata int cpIndex = 0; foreach (RectangleF cp in this.GetControlPoints(parent.Size0, parent.Size1)) { if (cp.Contains(e.Location)) { this.Metadata["IsMouseOverControlPoint"] = true; this.Metadata["CurrentControlPointIndex"] = cpIndex; parent.Metadata["Cursor"] = this.GetControlPointCursor(cpIndex); this.RaiseModified(); return true; } cpIndex++; } // Check if the mouse is over the part GraphicsPath path = this.GetGraphicsPath(this.Cutoff, parent.Size0, parent.Size1); if (path.IsVisible(e.Location)) { parent.Metadata["Cursor"] = Cursors.SizeWE; this.Metadata["IsMouseOverControlPoint"] = false; this.Metadata["IsMouseOverPart"] = true; this.Metadata["CurrentControlPointIndex"] = -1; this.RaiseModified(); return true; } path.Dispose(); // Allow the base class to handle if (base.OnMouseMove(parent, e)) return true; // The event was not consumed this.Metadata["IsMouseOverControlPoint"] = false; this.Metadata["IsMouseOverPart"] = false; this.Metadata["CurrentControlPointIndex"] = -1; return false; } /// /// Get the cursor for the given control point index. /// /// /// protected override Cursor GetControlPointCursor(int cpIndex) { switch (cpIndex) { case 0: return Cursors.SizeWE; case 1: return Cursors.SizeAll; case 2: return Cursors.SizeAll; case 3: return Cursors.SizeAll; default: return Cursors.Default; } } //===================================================================== #endregion #region Private Methods //===================================================================== /// /// Get the control points. This property accessor builds the list /// from private member information each time it is called. /// public RectangleF[] GetControlPoints(float width, float height) { PointF[] points = this.GetParameterPoints(width, height); RectangleF[] result = new RectangleF[4]; result[0] = new RectangleF(points[0].X - CONTROL_POINT_WIDTH / 2.0F, points[0].Y - CONTROL_POINT_WIDTH / 2.0F, CONTROL_POINT_WIDTH, CONTROL_POINT_HEIGHT); result[1] = new RectangleF(points[1].X - CONTROL_POINT_WIDTH / 2.0F, points[1].Y - CONTROL_POINT_WIDTH / 2.0F, CONTROL_POINT_WIDTH, CONTROL_POINT_HEIGHT); result[2] = new RectangleF(points[2].X - CONTROL_POINT_WIDTH / 2.0F, points[2].Y - CONTROL_POINT_WIDTH / 2.0F, CONTROL_POINT_WIDTH, CONTROL_POINT_HEIGHT); result[3] = new RectangleF(points[3].X - CONTROL_POINT_WIDTH / 2.0F, points[3].Y - CONTROL_POINT_WIDTH / 2.0F, CONTROL_POINT_WIDTH, CONTROL_POINT_HEIGHT); return result; } /// /// Get the points for controlling the parameters. /// The points are returned in GDI space. /// /// The width of the parent transfer function. /// The height of the parent transfer function. /// private PointF[] GetParameterPoints(float width, float height) { // Create the points PointF pointValue = new PointF(this.Value, 0F); PointF pointTopLeft = new PointF(this.Value + this.Offset - this.Width / 2.0F, this.Height); PointF pointTopMiddle = new PointF(this.Value + this.Offset, this.Height); PointF pointTopRight = new PointF(this.Value + this.Offset + this.Width / 2.0F, this.Height); // Adjust the points to GDI space pointValue.Y = height - pointValue.Y; pointTopLeft.Y = height - pointTopLeft.Y; pointTopMiddle.Y = height - pointTopMiddle.Y; pointTopRight.Y = height - pointTopRight.Y; // Return return new PointF[] { pointValue, pointTopLeft, pointTopMiddle, pointTopRight }; } /// /// Get the path representing the function (in GDI space). /// /// /// /// private GraphicsPath GetGraphicsPath(float cutoff, float width, float height) { GraphicsPath path = new GraphicsPath(); if (cutoff == 0.0F) { path.AddPolygon(this.GetParameterPoints(width, height)); } else { PointF[] p = this.GetParameterPoints(width, height); PointF c1 = new PointF(0.0F, height - cutoff); PointF c2 = new PointF(width, height - cutoff); PointF p1 = this.LineToLineIntersection(p[0], p[1], c1, c2); PointF p2 = this.LineToLineIntersection(p[0], p[3], c1, c2); PointF[] newp = new PointF[] { p[1], p[2], p[3], p2, p1 }; path.AddPolygon(newp); } path.CloseFigure(); return path; } /// /// Computes the intersection point of two 2D lines (A and B). /// /// The start point of line A. /// The end point of line A. /// The start point of line B. /// The end point of line B. /// private PointF LineToLineIntersection(PointF p1, PointF p2, PointF p3, PointF p4) { float denom = ((p4.Y - p3.Y) * (p2.X - p1.X)) - ((p4.X - p3.X) * (p2.Y - p1.Y)); float num_a = ((p4.X - p3.X) * (p1.Y - p3.Y)) - ((p4.Y - p3.Y) * (p1.X - p3.X)); float num_b = ((p2.X - p1.X) * (p1.Y - p3.Y)) - ((p2.Y - p1.Y) * (p1.X - p3.X)); float ua = num_a / denom; float ub = num_b / denom; PointF result = new PointF(); result.X = p1.X + ua * (p2.X - p1.X); result.Y = p1.Y + ua * (p2.Y - p1.Y); return result; } //===================================================================== #endregion } }