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