#region Header
//
// Project: WriteableBitmapEx - WriteableBitmap extensions
// Description: Collection of extension methods for the WriteableBitmap class.
//
// Changed by: $Author: unknown $
// Changed on: $Date: 2015-03-05 21:21:11 +0100 (Do, 05 Mrz 2015) $
// Changed in: $Revision: 113194 $
// Project: $URL: https://writeablebitmapex.svn.codeplex.com/svn/trunk/Source/WriteableBitmapEx/WriteableBitmapFillExtensions.cs $
// Id: $Id: WriteableBitmapFillExtensions.cs 113194 2015-03-05 20:21:11Z 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;
using System.Collections.Generic;
#if NETFX_CORE
namespace Windows.UI.Xaml.Media.Imaging
#else
namespace System.Windows.Media.Imaging
#endif
{
///
/// Collection of extension methods for the WriteableBitmap class.
///
internal
#if WPF
unsafe
#endif
static partial class WriteableBitmapExtensions
{
#region Methods
#region Fill Shapes
#region Rectangle
///
/// Draws a filled rectangle.
/// x2 has to be greater than x1 and y2 has to be greater than y1.
///
/// The WriteableBitmap.
/// The x-coordinate of the bounding rectangle's left side.
/// The y-coordinate of the bounding rectangle's top side.
/// The x-coordinate of the bounding rectangle's right side.
/// The y-coordinate of the bounding rectangle's bottom side.
/// The color.
internal static void FillRectangle(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, Color color)
{
var col = ConvertColor(color);
bmp.FillRectangle(x1, y1, x2, y2, col);
}
///
/// Draws a filled rectangle with or without alpha blending (default = false).
/// x2 has to be greater than x1 and y2 has to be greater than y1.
///
/// The WriteableBitmap.
/// The x-coordinate of the bounding rectangle's left side.
/// The y-coordinate of the bounding rectangle's top side.
/// The x-coordinate of the bounding rectangle's right side.
/// The y-coordinate of the bounding rectangle's bottom side.
/// The color.
/// True if alpha blending should be performed or false if not.
internal static void FillRectangle(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int color, bool doAlphaBlend = false)
{
using (var context = bmp.GetBitmapContext())
{
// Use refs for faster access (really important!) speeds up a lot!
var w = context.Width;
var h = context.Height;
int sa = ((color >> 24) & 0xff);
int sr = ((color >> 16) & 0xff);
int sg = ((color >> 8) & 0xff);
int sb = ((color) & 0xff);
bool noBlending = !doAlphaBlend || sa == 255;
var pixels = context.Pixels;
// Check boundaries
if ((x1 < 0 && x2 < 0) || (y1 < 0 && y2 < 0)
|| (x1 >= w && x2 >= w) || (y1 >= h && y2 >= h))
{
return;
}
// Clamp boundaries
if (x1 < 0) { x1 = 0; }
if (y1 < 0) { y1 = 0; }
if (x2 < 0) { x2 = 0; }
if (y2 < 0) { y2 = 0; }
if (x1 > w) { x1 = w; }
if (y1 > h) { y1 = h; }
if (x2 > w) { x2 = w; }
if (y2 > h) { y2 = h; }
//swap values
if (y1 > y2)
{
y2 -= y1;
y1 += y2;
y2 = (y1 - y2);
}
// Fill first line
var startY = y1 * w;
var startYPlusX1 = startY + x1;
var endOffset = startY + x2;
for (var idx = startYPlusX1; idx < endOffset; idx++)
{
pixels[idx] = noBlending ? color : AlphaBlendColors(pixels[idx], sa, sr, sg, sb);
}
// Copy first line
var len = (x2 - x1);
var srcOffsetBytes = startYPlusX1 * SizeOfArgb;
var offset2 = y2 * w + x1;
for (var y = startYPlusX1 + w; y < offset2; y += w)
{
if (noBlending)
{
BitmapContext.BlockCopy(context, srcOffsetBytes, context, y * SizeOfArgb, len * SizeOfArgb);
continue;
}
// Alpha blend line
for (int i = 0; i < len; i++)
{
int idx = y + i;
pixels[idx] = AlphaBlendColors(pixels[idx], sa, sr, sg, sb);
}
}
}
}
private static int AlphaBlendColors(int pixel, int sa, int sr, int sg, int sb)
{
// Alpha blend
int destPixel = pixel;
int da = ((destPixel >> 24) & 0xff);
int dr = ((destPixel >> 16) & 0xff);
int dg = ((destPixel >> 8) & 0xff);
int db = ((destPixel) & 0xff);
destPixel = ((sa + (((da * (255 - sa)) * 0x8081) >> 23)) << 24) |
((sr + (((dr * (255 - sa)) * 0x8081) >> 23)) << 16) |
((sg + (((dg * (255 - sa)) * 0x8081) >> 23)) << 8) |
((sb + (((db * (255 - sa)) * 0x8081) >> 23)));
return destPixel;
}
#endregion
#region Ellipse
///
/// A Fast Bresenham Type Algorithm For Drawing filled ellipses http://homepage.smc.edu/kennedy_john/belipse.pdf
/// x2 has to be greater than x1 and y2 has to be greater than y1.
///
/// The WriteableBitmap.
/// The x-coordinate of the bounding rectangle's left side.
/// The y-coordinate of the bounding rectangle's top side.
/// The x-coordinate of the bounding rectangle's right side.
/// The y-coordinate of the bounding rectangle's bottom side.
/// The color for the line.
internal static void FillEllipse(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, Color color)
{
var col = ConvertColor(color);
bmp.FillEllipse(x1, y1, x2, y2, col);
}
///
/// A Fast Bresenham Type Algorithm For Drawing filled ellipses http://homepage.smc.edu/kennedy_john/belipse.pdf
/// x2 has to be greater than x1 and y2 has to be greater than y1.
///
/// The WriteableBitmap.
/// The x-coordinate of the bounding rectangle's left side.
/// The y-coordinate of the bounding rectangle's top side.
/// The x-coordinate of the bounding rectangle's right side.
/// The y-coordinate of the bounding rectangle's bottom side.
/// The color for the line.
internal static void FillEllipse(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int color)
{
// Calc center and radius
int xr = (x2 - x1) >> 1;
int yr = (y2 - y1) >> 1;
int xc = x1 + xr;
int yc = y1 + yr;
bmp.FillEllipseCentered(xc, yc, xr, yr, color);
}
///
/// A Fast Bresenham Type Algorithm For Drawing filled ellipses http://homepage.smc.edu/kennedy_john/belipse.pdf
/// Uses a different parameter representation than DrawEllipse().
///
/// The WriteableBitmap.
/// The x-coordinate of the ellipses center.
/// The y-coordinate of the ellipses center.
/// The radius of the ellipse in x-direction.
/// The radius of the ellipse in y-direction.
/// The color for the line.
internal static void FillEllipseCentered(this WriteableBitmap bmp, int xc, int yc, int xr, int yr, Color color)
{
var col = ConvertColor(color);
bmp.FillEllipseCentered(xc, yc, xr, yr, col);
}
///
/// A Fast Bresenham Type Algorithm For Drawing filled ellipses http://homepage.smc.edu/kennedy_john/belipse.pdf
/// With or without alpha blending (default = false).
/// Uses a different parameter representation than DrawEllipse().
///
/// The WriteableBitmap.
/// The x-coordinate of the ellipses center.
/// The y-coordinate of the ellipses center.
/// The radius of the ellipse in x-direction.
/// The radius of the ellipse in y-direction.
/// The color for the line.
/// True if alpha blending should be performed or false if not.
internal static void FillEllipseCentered(this WriteableBitmap bmp, int xc, int yc, int xr, int yr, int color, bool doAlphaBlend = false)
{
// Use refs for faster access (really important!) speeds up a lot!
using (var context = bmp.GetBitmapContext())
{
var pixels = context.Pixels;
int w = context.Width;
int h = context.Height;
// Avoid endless loop
if (xr < 1 || yr < 1)
{
return;
}
// Skip completly outside objects
if (xc - xr >= w || xc + xr < 0 || yc - yr >= h || yc + yr < 0)
{
return;
}
// Init vars
int uh, lh, uy, ly, lx, rx;
int x = xr;
int y = 0;
int xrSqTwo = (xr * xr) << 1;
int yrSqTwo = (yr * yr) << 1;
int xChg = yr * yr * (1 - (xr << 1));
int yChg = xr * xr;
int err = 0;
int xStopping = yrSqTwo * xr;
int yStopping = 0;
int sa = ((color >> 24) & 0xff);
int sr = ((color >> 16) & 0xff);
int sg = ((color >> 8) & 0xff);
int sb = ((color) & 0xff);
bool noBlending = !doAlphaBlend || sa == 255;
// Draw first set of points counter clockwise where tangent line slope > -1.
while (xStopping >= yStopping)
{
// Draw 4 quadrant points at once
// Upper half
uy = yc + y;
// Lower half
ly = yc - y - 1;
// Clip
if (uy < 0) uy = 0;
if (uy >= h) uy = h - 1;
if (ly < 0) ly = 0;
if (ly >= h) ly = h - 1;
// Upper half
uh = uy * w;
// Lower half
lh = ly * w;
rx = xc + x;
lx = xc - x;
// Clip
if (rx < 0) rx = 0;
if (rx >= w) rx = w - 1;
if (lx < 0) lx = 0;
if (lx >= w) lx = w - 1;
// Draw line
if (noBlending)
{
for (int i = lx; i <= rx; i++)
{
pixels[i + uh] = color; // Quadrant II to I (Actually two octants)
pixels[i + lh] = color; // Quadrant III to IV
}
}
else
{
for (int i = lx; i <= rx; i++)
{
// Quadrant II to I (Actually two octants)
pixels[i + uh] = AlphaBlendColors(pixels[i + uh], sa, sr, sg, sb);
// Quadrant III to IV
pixels[i + lh] = AlphaBlendColors(pixels[i + lh], sa, sr, sg, sb);
}
}
y++;
yStopping += xrSqTwo;
err += yChg;
yChg += xrSqTwo;
if ((xChg + (err << 1)) > 0)
{
x--;
xStopping -= yrSqTwo;
err += xChg;
xChg += yrSqTwo;
}
}
// ReInit vars
x = 0;
y = yr;
// Upper half
uy = yc + y;
// Lower half
ly = yc - y;
// Clip
if (uy < 0) uy = 0;
if (uy >= h) uy = h - 1;
if (ly < 0) ly = 0;
if (ly >= h) ly = h - 1;
// Upper half
uh = uy * w;
// Lower half
lh = ly * w;
xChg = yr * yr;
yChg = xr * xr * (1 - (yr << 1));
err = 0;
xStopping = 0;
yStopping = xrSqTwo * yr;
// Draw second set of points clockwise where tangent line slope < -1.
while (xStopping <= yStopping)
{
// Draw 4 quadrant points at once
rx = xc + x;
lx = xc - x;
// Clip
if (rx < 0) rx = 0;
if (rx >= w) rx = w - 1;
if (lx < 0) lx = 0;
if (lx >= w) lx = w - 1;
// Draw line
if (noBlending)
{
for (int i = lx; i <= rx; i++)
{
pixels[i + uh] = color; // Quadrant II to I (Actually two octants)
pixels[i + lh] = color; // Quadrant III to IV
}
}
else
{
for (int i = lx; i <= rx; i++)
{
// Quadrant II to I (Actually two octants)
pixels[i + uh] = AlphaBlendColors(pixels[i + uh], sa, sr, sg, sb);
// Quadrant III to IV
pixels[i + lh] = AlphaBlendColors(pixels[i + lh], sa, sr, sg, sb);
}
}
x++;
xStopping += yrSqTwo;
err += xChg;
xChg += yrSqTwo;
if ((yChg + (err << 1)) > 0)
{
y--;
uy = yc + y; // Upper half
ly = yc - y; // Lower half
if (uy < 0) uy = 0; // Clip
if (uy >= h) uy = h - 1; // ...
if (ly < 0) ly = 0;
if (ly >= h) ly = h - 1;
uh = uy * w; // Upper half
lh = ly * w; // Lower half
yStopping -= xrSqTwo;
err += yChg;
yChg += xrSqTwo;
}
}
}
}
#endregion
#region Polygon, Triangle, Quad
///
/// Draws a filled polygon. Add the first point also at the end of the array if the line should be closed.
///
/// The WriteableBitmap.
/// The points of the polygon in x and y pairs, therefore the array is interpreted as (x1, y1, x2, y2, ..., xn, yn).
/// The color for the line.
internal static void FillPolygon(this WriteableBitmap bmp, int[] points, Color color)
{
var col = ConvertColor(color);
bmp.FillPolygon(points, col);
}
///
/// Draws a filled polygon with or without alpha blending (default = false).
/// Add the first point also at the end of the array if the line should be closed.
///
/// The WriteableBitmap.
/// The points of the polygon in x and y pairs, therefore the array is interpreted as (x1, y1, x2, y2, ..., xn, yn).
/// The color for the line.
/// True if alpha blending should be performed or false if not.
internal static void FillPolygon(this WriteableBitmap bmp, int[] points, int color, bool doAlphaBlend = false)
{
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 sa = ((color >> 24) & 0xff);
int sr = ((color >> 16) & 0xff);
int sg = ((color >> 8) & 0xff);
int sb = ((color) & 0xff);
bool noBlending = !doAlphaBlend || sa == 255;
var pixels = context.Pixels;
int pn = points.Length;
int pnh = points.Length >> 1;
int[] intersectionsX = new int[pnh];
// Find y min and max (slightly faster than scanning from 0 to height)
int yMin = h;
int yMax = 0;
for (int i = 1; i < pn; i += 2)
{
int py = points[i];
if (py < yMin) yMin = py;
if (py > yMax) yMax = py;
}
if (yMin < 0) yMin = 0;
if (yMax >= h) yMax = h - 1;
// Scan line from min to max
for (int y = yMin; y <= yMax; y++)
{
// Initial point x, y
float vxi = points[0];
float vyi = points[1];
// Find all intersections
// Based on http://alienryderflex.com/polygon_fill/
int intersectionCount = 0;
for (int i = 2; i < pn; i += 2)
{
// Next point x, y
float vxj = points[i];
float vyj = points[i + 1];
// Is the scanline between the two points
if (vyi < y && vyj >= y
|| vyj < y && vyi >= y)
{
// Compute the intersection of the scanline with the edge (line between two points)
intersectionsX[intersectionCount++] = (int)(vxi + (y - vyi) / (vyj - vyi) * (vxj - vxi));
}
vxi = vxj;
vyi = vyj;
}
// Sort the intersections from left to right using Insertion sort
// It's faster than Array.Sort for this small data set
int t, j;
for (int i = 1; i < intersectionCount; i++)
{
t = intersectionsX[i];
j = i;
while (j > 0 && intersectionsX[j - 1] > t)
{
intersectionsX[j] = intersectionsX[j - 1];
j = j - 1;
}
intersectionsX[j] = t;
}
// Fill the pixels between the intersections
for (int i = 0; i < intersectionCount - 1; i += 2)
{
int x0 = intersectionsX[i];
int x1 = intersectionsX[i + 1];
// Check boundary
if (x1 > 0 && x0 < w)
{
if (x0 < 0) x0 = 0;
if (x1 >= w) x1 = w - 1;
// Fill the pixels
for (int x = x0; x <= x1; x++)
{
int idx = y * w + x;
pixels[idx] = noBlending ? color : AlphaBlendColors(pixels[idx], sa, sr, sg, sb);
}
}
}
}
}
}
#region Multiple (possibly nested) Polygons
///
/// Helper class for storing the data of an edge.
///
///
/// The following is always true:
/// edge.StartY < edge.EndY
///
private class Edge : IComparable
{
///
/// X coordinate of starting point of edge.
///
internal readonly int StartX;
///
/// Y coordinate of starting point of edge.
///
internal readonly int StartY;
///
/// X coordinate of ending point of edge.
///
internal readonly int EndX;
///
/// Y coordinate of ending point of edge.
///
internal readonly int EndY;
///
/// The slope of the edge.
///
internal readonly float Sloap;
///
/// Initializes a new instance of the class.
///
///
/// The constructor may swap start and end point to fulfill the guarantees of this class.
///
/// The X coordinate of the start point of the edge.
/// The Y coordinate of the start point of the edge.
/// The X coordinate of the end point of the edge.
/// The Y coordinate of the end point of the edge.
internal Edge(int startX, int startY, int endX, int endY)
{
if (startY > endY)
{
// swap direction
StartX = endX;
StartY = endY;
EndX = startX;
EndY = startY;
}
else
{
StartX = startX;
StartY = startY;
EndX = endX;
EndY = endY;
}
Sloap = (EndX - StartX) / (float)(EndY - StartY);
}
///
/// Compares the current object with another object of the same type.
///
///
/// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the parameter.Zero This object is equal to . Greater than zero This object is greater than .
///
/// An object to compare with this object.
public int CompareTo(Edge other)
{
return StartY == other.StartY
? StartX.CompareTo(other.StartX)
: StartY.CompareTo(other.StartY);
}
}
///
/// Draws filled polygons using even-odd filling, therefore allowing for holes.
///
///
/// Polygons are implicitly closed if necessary.
///
/// The WriteableBitmap.
/// Array of polygons.
/// The different polygons are identified by the first index,
/// while the points of each polygon are in x and y pairs indexed by the second index,
/// therefore the array is interpreted as (x1, y1, x2, y2, ..., xn, yn).
///
/// The color for the polygon.
internal static void FillPolygonsEvenOdd(this WriteableBitmap bmp, int[][] polygons, Color color)
{
var col = ConvertColor(color);
FillPolygonsEvenOdd(bmp, polygons, col);
}
///
/// Draws filled polygons using even-odd filling, therefore allowing for holes.
///
///
/// Polygons are implicitly closed if necessary.
///
/// The WriteableBitmap.
/// Array of polygons.
/// The different polygons are identified by the first index,
/// while the points of each polygon are in x and y pairs indexed by the second index,
/// therefore the array is interpreted as (x1, y1, x2, y2, ..., xn, yn).
///
/// The color for the polygon.
internal static void FillPolygonsEvenOdd(this WriteableBitmap bmp, int[][] polygons, int color)
{
#region Algorithm
// Algorithm:
// This is using a scanline algorithm which is kept similar to the one the FillPolygon() method is using,
// but it is only comparing the edges with the scanline which are currently intersecting the line.
// To be able to do this it first builds a list of edges (var edges) from the polygons, which is then
// sorted via by their minimal y coordinate. During the scanline run only the edges which can intersect
// the current scanline are intersected to get the X coordinate of the intersection. These edges are kept
// in the list named currentEdges.
// Especially for larger sane(*) polygons this is a lot faster then the algorithm used in the FillPolygon()
// method which is always comparing all edges with the scan line.
// And sorry: the constraint to explicitly make the polygon close before using the FillPolygon() method is
// stupid, as filling an unclosed polygon is not very useful.
//
// (*) sane: the polygons in the FillSample speed test are not sane, because they contain a lot of very long
// nearly vertical lines. A sane example would be a letter 'o', in which case the currentEdges list is
// containing no more than 4 edges at any moment, regardless of the smoothness of the rendering of the
// letter into two polygons.
#endregion
int polygonCount = polygons.Length;
if (polygonCount == 0)
{
return;
}
// could use single polygon fill if count is 1, but it the algorithm used there is slower (at least for larger polygons)
using (var context = bmp.GetBitmapContext())
{
// Use refs for faster access (really important!) speeds up a lot!
int w = context.Width;
int h = context.Height;
var pixels = context.Pixels;
// Register edges, and find y max
List edges = new List();
int yMax = 0;
foreach (int[] points in polygons)
{
int pn = points.Length;
if (pn < 6)
{
// sanity check: don't care for lines or points or empty polygons
continue;
}
int lastX;
int lastY;
int start;
if (points[0] != points[pn - 2]
|| points[1] != points[pn - 1])
{
start = 0;
lastX = points[pn - 2];
lastY = points[pn - 1];
}
else
{
start = 2;
lastX = points[0];
lastY = points[1];
}
for (int i = start; i < pn; i += 2)
{
int px = points[i];
int py = points[i + 1];
if (py != lastY)
{
Edge edge = new Edge(lastX, lastY, px, py);
if (edge.StartY < h && edge.EndY >= 0)
{
if (edge.EndY > yMax) yMax = edge.EndY;
edges.Add(edge);
}
}
lastX = px;
lastY = py;
}
}
if (edges.Count == 0)
{
// sanity check
return;
}
if (yMax >= h) yMax = h - 1;
edges.Sort();
int yMin = edges[0].StartY;
if (yMin < 0) yMin = 0;
int[] intersectionsX = new int[edges.Count];
LinkedList currentEdges = new LinkedList();
int e = 0;
// Scan line from min to max
for (int y = yMin; y <= yMax; y++)
{
// Remove edges no longer intersecting
LinkedListNode node = currentEdges.First;
while (node != null)
{
LinkedListNode nextNode = node.Next;
Edge edge = node.Value;
if (edge.EndY <= y)
{
// using = here because the connecting edge will be added next
// remove edge
currentEdges.Remove(node);
}
node = nextNode;
}
// Add edges starting to intersect
while (e < edges.Count &&
edges[e].StartY <= y)
{
currentEdges.AddLast(edges[e]);
++e;
}
// Calculate intersections
int intersectionCount = 0;
foreach (Edge currentEdge in currentEdges)
{
intersectionsX[intersectionCount++] =
(int)(currentEdge.StartX + (y - currentEdge.StartY) * currentEdge.Sloap);
}
// Sort the intersections from left to right using Insertion sort
// It's faster than Array.Sort for this small data set
for (int i = 1; i < intersectionCount; i++)
{
int t = intersectionsX[i];
int j = i;
while (j > 0 && intersectionsX[j - 1] > t)
{
intersectionsX[j] = intersectionsX[j - 1];
j = j - 1;
}
intersectionsX[j] = t;
}
// Fill the pixels between the intersections
for (int i = 0; i < intersectionCount - 1; i += 2)
{
int x0 = intersectionsX[i];
int x1 = intersectionsX[i + 1];
if (x0 < 0) x0 = 0;
if (x1 >= w) x1 = w - 1;
if (x1 < x0)
{
continue;
}
// Fill the pixels
int index = y * w + x0;
for (int x = x0; x <= x1; x++)
{
pixels[index++] = color;
}
}
}
}
}
#endregion
///
/// Draws a filled quad.
///
/// The WriteableBitmap.
/// The x-coordinate of the 1st point.
/// The y-coordinate of the 1st point.
/// The x-coordinate of the 2nd point.
/// The y-coordinate of the 2nd point.
/// The x-coordinate of the 3rd point.
/// The y-coordinate of the 3rd point.
/// The x-coordinate of the 4th point.
/// The y-coordinate of the 4th point.
/// The color.
internal static void FillQuad(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, Color color)
{
var col = ConvertColor(color);
bmp.FillQuad(x1, y1, x2, y2, x3, y3, x4, y4, col);
}
///
/// Draws a filled quad.
///
/// The WriteableBitmap.
/// The x-coordinate of the 1st point.
/// The y-coordinate of the 1st point.
/// The x-coordinate of the 2nd point.
/// The y-coordinate of the 2nd point.
/// The x-coordinate of the 3rd point.
/// The y-coordinate of the 3rd point.
/// The x-coordinate of the 4th point.
/// The y-coordinate of the 4th point.
/// The color.
internal static void FillQuad(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int color)
{
bmp.FillPolygon(new int[] { x1, y1, x2, y2, x3, y3, x4, y4, x1, y1 }, color);
}
///
/// Draws a filled triangle.
///
/// The WriteableBitmap.
/// The x-coordinate of the 1st point.
/// The y-coordinate of the 1st point.
/// The x-coordinate of the 2nd point.
/// The y-coordinate of the 2nd point.
/// The x-coordinate of the 3rd point.
/// The y-coordinate of the 3rd point.
/// The color.
internal static void FillTriangle(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int x3, int y3, Color color)
{
var col = ConvertColor(color);
bmp.FillTriangle(x1, y1, x2, y2, x3, y3, col);
}
///
/// Draws a filled triangle.
///
/// The WriteableBitmap.
/// The x-coordinate of the 1st point.
/// The y-coordinate of the 1st point.
/// The x-coordinate of the 2nd point.
/// The y-coordinate of the 2nd point.
/// The x-coordinate of the 3rd point.
/// The y-coordinate of the 3rd point.
/// The color.
internal static void FillTriangle(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int x3, int y3, int color)
{
bmp.FillPolygon(new int[] { x1, y1, x2, y2, x3, y3, x1, y1 }, color);
}
#endregion
#region Beziér
///
/// Draws a filled, cubic Beziér spline defined by start, end and two control points.
///
/// 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.
/// The context with the pixels.
/// The width of the bitmap.
/// The height of the bitmap.
[Obsolete("Obsolete, left for compatibility reasons. Please use List ComputeBezierPoints(int x1, int y1, int cx1, int cy1, int cx2, int cy2, int x2, int y2) instead.")]
private static List ComputeBezierPoints(int x1, int y1, int cx1, int cy1, int cx2, int cy2, int x2, int y2, int color, BitmapContext context, int w, int h)
{
return ComputeBezierPoints(x1, y1, cx1, cy1, cx2, cy2, x2, y1);
}
///
/// Draws a filled, cubic Beziér spline defined by start, end and two control points.
///
/// 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.
private static List ComputeBezierPoints(int x1, int y1, int cx1, int cy1, int cx2, int cy2, int x2, int y2)
{
// 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
var list = new List();
if (len != 0)
{
// Init vars
var step = StepFactor / len;
int tx = x1;
int ty = y1;
// Interpolate
for (var t = 0f; t <= 1; t += step)
{
var tSq = t * t;
var t1 = 1 - t;
var t1Sq = t1 * t1;
tx = (int)(t1 * t1Sq * x1 + 3 * t * t1Sq * cx1 + 3 * t1 * tSq * cx2 + t * tSq * x2);
ty = (int)(t1 * t1Sq * y1 + 3 * t * t1Sq * cy1 + 3 * t1 * tSq * cy2 + t * tSq * y2);
list.Add(tx);
list.Add(ty);
}
// Prevent rounding gap
list.Add(x2);
list.Add(y2);
}
return list;
}
///
/// Draws a series of filled, 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 FillBeziers(this WriteableBitmap bmp, int[] points, Color color)
{
var col = ConvertColor(color);
bmp.FillBeziers(points, col);
}
///
/// Draws a series of filled, 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 FillBeziers(this WriteableBitmap bmp, int[] points, int color)
{
// Compute Bezier curve
int x1 = points[0];
int y1 = points[1];
int x2, y2;
var list = new List();
for (int i = 2; i + 5 < points.Length; i += 6)
{
x2 = points[i + 4];
y2 = points[i + 5];
list.AddRange(ComputeBezierPoints(x1, y1, points[i], points[i + 1], points[i + 2], points[i + 3], x2, y2));
x1 = x2;
y1 = y2;
}
// Fill
bmp.FillPolygon(list.ToArray(), color);
}
#endregion
#region Cardinal
///
/// Computes the discrete segment points 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 context with the pixels.
/// The width of the bitmap.
/// The height of the bitmap.
[Obsolete("Obsolete, left for compatibility reasons. Please use List ComputeSegmentPoints(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, float tension) instead.")]
private static List ComputeSegmentPoints(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)
{
return ComputeSegmentPoints(x1, y1, x2, y2, x3, y3, x4, y4, tension);
}
///
/// Computes the discrete segment points 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.
private static List ComputeSegmentPoints(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, float tension)
{
// 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
var list = new List();
if (len != 0)
{
// Init vars
var step = StepFactor / len;
// 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 = 0f; t <= 1; t += step)
{
var tSq = t * t;
int tx = (int)(ax * tSq * t + bx * tSq + sx1 * t + x2);
int ty = (int)(ay * tSq * t + by * tSq + sy1 * t + y2);
list.Add(tx);
list.Add(ty);
}
// Prevent rounding gap
list.Add(x3);
list.Add(y3);
}
return list;
}
///
/// Draws a filled 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 FillCurve(this WriteableBitmap bmp, int[] points, float tension, Color color)
{
var col = ConvertColor(color);
bmp.FillCurve(points, tension, col);
}
///
/// Draws a filled 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 FillCurve(this WriteableBitmap bmp, int[] points, float tension, int color)
{
// First segment
var list = ComputeSegmentPoints(points[0], points[1], points[0], points[1], points[2], points[3], points[4],
points[5], tension);
// Middle segments
int i;
for (i = 2; i < points.Length - 4; i += 2)
{
list.AddRange(ComputeSegmentPoints(points[i - 2], points[i - 1], points[i], points[i + 1], points[i + 2],
points[i + 3], points[i + 4], points[i + 5], tension));
}
// Last segment
list.AddRange(ComputeSegmentPoints(points[i - 2], points[i - 1], points[i], points[i + 1], points[i + 2],
points[i + 3], points[i + 2], points[i + 3], tension));
// Fill
bmp.FillPolygon(list.ToArray(), color);
}
///
/// Draws a filled, 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 FillCurveClosed(this WriteableBitmap bmp, int[] points, float tension, Color color)
{
var col = ConvertColor(color);
bmp.FillCurveClosed(points, tension, col);
}
///
/// Draws a filled, 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 FillCurveClosed(this WriteableBitmap bmp, int[] points, float tension, int color)
{
int pn = points.Length;
// First segment
var list = ComputeSegmentPoints(points[pn - 2], points[pn - 1], points[0], points[1], points[2], points[3],
points[4], points[5], tension);
// Middle segments
int i;
for (i = 2; i < pn - 4; i += 2)
{
list.AddRange(ComputeSegmentPoints(points[i - 2], points[i - 1], points[i], points[i + 1],
points[i + 2], points[i + 3], points[i + 4], points[i + 5], tension));
}
// Last segment
list.AddRange(ComputeSegmentPoints(points[i - 2], points[i - 1], points[i], points[i + 1], points[i + 2],
points[i + 3], points[0], points[1], tension));
// Last-to-First segment
list.AddRange(ComputeSegmentPoints(points[i], points[i + 1], points[i + 2], points[i + 3], points[0],
points[1], points[2], points[3], tension));
// Fill
bmp.FillPolygon(list.ToArray(), color);
}
#endregion
#endregion
#endregion
}
}