#region Header
//
// Project: WriteableBitmapEx - WriteableBitmap extensions
// Description: Collection of draw spline extension methods for the WriteableBitmap class.
//
// Changed by: $Author: unknown $
// Changed on: $Date: 2015-03-05 18:18:24 +0100 (Do, 05 Mrz 2015) $
// Changed in: $Revision: 113191 $
// Project: $URL: https://writeablebitmapex.svn.codeplex.com/svn/trunk/Source/WriteableBitmapEx/WriteableBitmapSplineExtensions.cs $
// Id: $Id: WriteableBitmapSplineExtensions.cs 113191 2015-03-05 17:18:24Z unknown $
//
//
// Copyright © 2009-2015 Rene Schulte and WriteableBitmapEx Contributors
//
// This code is open source. Please read the License.txt for details. No worries, we won't sue you! ;)
//
#endregion
using System;
#if NETFX_CORE
namespace Windows.UI.Xaml.Media.Imaging
#else
namespace System.Windows.Media.Imaging
#endif
{
///
/// Collection of draw spline extension methods for the WriteableBitmap class.
///
internal
#if WPF
unsafe
#endif
static partial class WriteableBitmapExtensions
{
#region Fields
private const float StepFactor = 2f;
#endregion
#region Methods
#region Beziér
///
/// Draws a cubic Beziér spline defined by start, end and two control points.
///
/// The WriteableBitmap.
/// The x-coordinate of the start point.
/// The y-coordinate of the start point.
/// The x-coordinate of the 1st control point.
/// The y-coordinate of the 1st control point.
/// The x-coordinate of the 2nd control point.
/// The y-coordinate of the 2nd control point.
/// The x-coordinate of the end point.
/// The y-coordinate of the end point.
/// The color.
internal static void DrawBezier(this WriteableBitmap bmp, int x1, int y1, int cx1, int cy1, int cx2, int cy2, int x2, int y2, Color color)
{
var col = ConvertColor(color);
bmp.DrawBezier(x1, y1, cx1, cy1, cx2, cy2, x2, y2, col);
}
///
/// Draws a cubic Beziér spline defined by start, end and two control points.
///
/// The WriteableBitmap.
/// The x-coordinate of the start point.
/// The y-coordinate of the start point.
/// The x-coordinate of the 1st control point.
/// The y-coordinate of the 1st control point.
/// The x-coordinate of the 2nd control point.
/// The y-coordinate of the 2nd control point.
/// The x-coordinate of the end point.
/// The y-coordinate of the end point.
/// The color.
internal static void DrawBezier(this WriteableBitmap bmp, int x1, int y1, int cx1, int cy1, int cx2, int cy2, int x2, int y2, int color)
{
// Determine distances between controls points (bounding rect) to find the optimal stepsize
var minX = Math.Min(x1, Math.Min(cx1, Math.Min(cx2, x2)));
var minY = Math.Min(y1, Math.Min(cy1, Math.Min(cy2, y2)));
var maxX = Math.Max(x1, Math.Max(cx1, Math.Max(cx2, x2)));
var maxY = Math.Max(y1, Math.Max(cy1, Math.Max(cy2, y2)));
// Get slope
var lenx = maxX - minX;
var len = maxY - minY;
if (lenx > len)
{
len = lenx;
}
// Prevent division by zero
if (len != 0)
{
using (var context = bmp.GetBitmapContext())
{
// Use refs for faster access (really important!) speeds up a lot!
int w = context.Width;
int h = context.Height;
// Init vars
var step = StepFactor / len;
int tx1 = x1;
int ty1 = y1;
int tx2, ty2;
// Interpolate
for (var t = step; t <= 1; t += step)
{
var tSq = t * t;
var t1 = 1 - t;
var t1Sq = t1 * t1;
tx2 = (int)(t1 * t1Sq * x1 + 3 * t * t1Sq * cx1 + 3 * t1 * tSq * cx2 + t * tSq * x2);
ty2 = (int)(t1 * t1Sq * y1 + 3 * t * t1Sq * cy1 + 3 * t1 * tSq * cy2 + t * tSq * y2);
// Draw line
DrawLine(context, w, h, tx1, ty1, tx2, ty2, color);
tx1 = tx2;
ty1 = ty2;
}
// Prevent rounding gap
DrawLine(context, w, h, tx1, ty1, x2, y2, color);
}
}
}
///
/// Draws a series of cubic Beziér splines each defined by start, end and two control points.
/// The ending point of the previous curve is used as starting point for the next.
/// Therefore the initial curve needs four points and the subsequent 3 (2 control and 1 end point).
///
/// The WriteableBitmap.
/// The points for the curve in x and y pairs, therefore the array is interpreted as (x1, y1, cx1, cy1, cx2, cy2, x2, y2, cx3, cx4 ..., xn, yn).
/// The color for the spline.
internal static void DrawBeziers(this WriteableBitmap bmp, int[] points, Color color)
{
var col = ConvertColor(color);
bmp.DrawBeziers(points, col);
}
///
/// Draws a series of cubic Beziér splines each defined by start, end and two control points.
/// The ending point of the previous curve is used as starting point for the next.
/// Therefore the initial curve needs four points and the subsequent 3 (2 control and 1 end point).
///
/// The WriteableBitmap.
/// The points for the curve in x and y pairs, therefore the array is interpreted as (x1, y1, cx1, cy1, cx2, cy2, x2, y2, cx3, cx4 ..., xn, yn).
/// The color for the spline.
internal static void DrawBeziers(this WriteableBitmap bmp, int[] points, int color)
{
int x1 = points[0];
int y1 = points[1];
int x2, y2;
for (int i = 2; i + 5 < points.Length; i += 6)
{
x2 = points[i + 4];
y2 = points[i + 5];
bmp.DrawBezier(x1, y1, points[i], points[i + 1], points[i + 2], points[i + 3], x2, y2, color);
x1 = x2;
y1 = y2;
}
}
#endregion
#region Cardinal
///
/// Draws a segment of a Cardinal spline (cubic) defined by four control points.
///
/// The x-coordinate of the 1st control point.
/// The y-coordinate of the 1st control point.
/// The x-coordinate of the 2nd control point.
/// The y-coordinate of the 2nd control point.
/// The x-coordinate of the 3rd control point.
/// The y-coordinate of the 3rd control point.
/// The x-coordinate of the 4th control point.
/// The y-coordinate of the 4th control point.
/// The tension of the curve defines the shape. Usually between 0 and 1. 0 would be a straight line.
/// The color.
/// The pixel context.
/// The width of the bitmap.
/// The height of the bitmap.
private static void DrawCurveSegment(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, float tension, int color, BitmapContext context, int w, int h)
{
// Determine distances between controls points (bounding rect) to find the optimal stepsize
var minX = Math.Min(x1, Math.Min(x2, Math.Min(x3, x4)));
var minY = Math.Min(y1, Math.Min(y2, Math.Min(y3, y4)));
var maxX = Math.Max(x1, Math.Max(x2, Math.Max(x3, x4)));
var maxY = Math.Max(y1, Math.Max(y2, Math.Max(y3, y4)));
// Get slope
var lenx = maxX - minX;
var len = maxY - minY;
if (lenx > len)
{
len = lenx;
}
// Prevent division by zero
if (len != 0)
{
// Init vars
var step = StepFactor / len;
int tx1 = x2;
int ty1 = y2;
int tx2, ty2;
// Calculate factors
var sx1 = tension * (x3 - x1);
var sy1 = tension * (y3 - y1);
var sx2 = tension * (x4 - x2);
var sy2 = tension * (y4 - y2);
var ax = sx1 + sx2 + 2 * x2 - 2 * x3;
var ay = sy1 + sy2 + 2 * y2 - 2 * y3;
var bx = -2 * sx1 - sx2 - 3 * x2 + 3 * x3;
var by = -2 * sy1 - sy2 - 3 * y2 + 3 * y3;
// Interpolate
for (var t = step; t <= 1; t += step)
{
var tSq = t * t;
tx2 = (int)(ax * tSq * t + bx * tSq + sx1 * t + x2);
ty2 = (int)(ay * tSq * t + by * tSq + sy1 * t + y2);
// Draw line
DrawLine(context, w, h, tx1, ty1, tx2, ty2, color);
tx1 = tx2;
ty1 = ty2;
}
// Prevent rounding gap
DrawLine(context, w, h, tx1, ty1, x3, y3, color);
}
}
///
/// Draws a Cardinal spline (cubic) defined by a point collection.
/// The cardinal spline passes through each point in the collection.
///
/// The WriteableBitmap.
/// The points for the curve in x and y pairs, therefore the array is interpreted as (x1, y1, x2, y2, x3, y3, x4, y4, x1, x2 ..., xn, yn).
/// The tension of the curve defines the shape. Usually between 0 and 1. 0 would be a straight line.
/// The color for the spline.
internal static void DrawCurve(this WriteableBitmap bmp, int[] points, float tension, Color color)
{
var col = ConvertColor(color);
bmp.DrawCurve(points, tension, col);
}
///
/// Draws a Cardinal spline (cubic) defined by a point collection.
/// The cardinal spline passes through each point in the collection.
///
/// The WriteableBitmap.
/// The points for the curve in x and y pairs, therefore the array is interpreted as (x1, y1, x2, y2, x3, y3, x4, y4, x1, x2 ..., xn, yn).
/// The tension of the curve defines the shape. Usually between 0 and 1. 0 would be a straight line.
/// The color for the spline.
internal static void DrawCurve(this WriteableBitmap bmp, int[] points, float tension, int color)
{
using (var context = bmp.GetBitmapContext())
{
// Use refs for faster access (really important!) speeds up a lot!
int w = context.Width;
int h = context.Height;
// First segment
DrawCurveSegment(points[0], points[1], points[0], points[1], points[2], points[3], points[4], points[5], tension, color, context, w, h);
// Middle segments
int i;
for (i = 2; i < points.Length - 4; i += 2)
{
DrawCurveSegment(points[i - 2], points[i - 1], points[i], points[i + 1], points[i + 2], points[i + 3], points[i + 4], points[i + 5], tension, color, context, w, h);
}
// Last segment
DrawCurveSegment(points[i - 2], points[i - 1], points[i], points[i + 1], points[i + 2], points[i + 3], points[i + 2], points[i + 3], tension, color, context, w, h);
}
}
///
/// Draws a closed Cardinal spline (cubic) defined by a point collection.
/// The cardinal spline passes through each point in the collection.
///
/// The WriteableBitmap.
/// The points for the curve in x and y pairs, therefore the array is interpreted as (x1, y1, x2, y2, x3, y3, x4, y4, x1, x2 ..., xn, yn).
/// The tension of the curve defines the shape. Usually between 0 and 1. 0 would be a straight line.
/// The color for the spline.
internal static void DrawCurveClosed(this WriteableBitmap bmp, int[] points, float tension, Color color)
{
var col = ConvertColor(color);
bmp.DrawCurveClosed(points, tension, col);
}
///
/// Draws a closed Cardinal spline (cubic) defined by a point collection.
/// The cardinal spline passes through each point in the collection.
///
/// The WriteableBitmap.
/// The points for the curve in x and y pairs, therefore the array is interpreted as (x1, y1, x2, y2, x3, y3, x4, y4, x1, x2 ..., xn, yn).
/// The tension of the curve defines the shape. Usually between 0 and 1. 0 would be a straight line.
/// The color for the spline.
internal static void DrawCurveClosed(this WriteableBitmap bmp, int[] points, float tension, int color)
{
using (var context = bmp.GetBitmapContext())
{
// Use refs for faster access (really important!) speeds up a lot!
int w = context.Width;
int h = context.Height;
int pn = points.Length;
// First segment
DrawCurveSegment(points[pn - 2], points[pn - 1], points[0], points[1], points[2], points[3], points[4], points[5], tension, color, context, w, h);
// Middle segments
int i;
for (i = 2; i < pn - 4; i += 2)
{
DrawCurveSegment(points[i - 2], points[i - 1], points[i], points[i + 1], points[i + 2], points[i + 3], points[i + 4], points[i + 5], tension, color, context, w, h);
}
// Last segment
DrawCurveSegment(points[i - 2], points[i - 1], points[i], points[i + 1], points[i + 2], points[i + 3], points[0], points[1], tension, color, context, w, h);
// Last-to-First segment
DrawCurveSegment(points[i], points[i + 1], points[i + 2], points[i + 3], points[0], points[1], points[2], points[3], tension, color, context, w, h);
}
}
#endregion
#endregion
}
}