#region Header // // Project: WriteableBitmapEx - WriteableBitmap extensions // Description: Collection of draw line extension and helper methods for the WriteableBitmap class. // // Changed by: $Author: unknown $ // Changed on: $Date: 2015-02-24 20:36:41 +0100 (Di, 24 Feb 2015) $ // Changed in: $Revision: 112951 $ // Project: $URL: https://writeablebitmapex.svn.codeplex.com/svn/trunk/Source/WriteableBitmapEx/WriteableBitmapTransformationExtensions.cs $ // Id: $Id: WriteableBitmapTransformationExtensions.cs 112951 2015-02-24 19:36:41Z 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 using Windows.Foundation; namespace Windows.UI.Xaml.Media.Imaging #else namespace System.Windows.Media.Imaging #endif { internal #if WPF unsafe #endif static partial class WriteableBitmapExtensions { #region Normal line /// /// Draws a colored line by connecting two points using the Bresenham algorithm. /// /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. internal static void DrawLineBresenham(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, Color color) { var col = ConvertColor(color); bmp.DrawLineBresenham(x1, y1, x2, y2, col); } /// /// Draws a colored line by connecting two points using the Bresenham algorithm. /// /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. internal static void DrawLineBresenham(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, 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; var pixels = context.Pixels; // Distance start and end point int dx = x2 - x1; int dy = y2 - y1; // Determine sign for direction x int incx = 0; if (dx < 0) { dx = -dx; incx = -1; } else if (dx > 0) { incx = 1; } // Determine sign for direction y int incy = 0; if (dy < 0) { dy = -dy; incy = -1; } else if (dy > 0) { incy = 1; } // Which gradient is larger int pdx, pdy, odx, ody, es, el; if (dx > dy) { pdx = incx; pdy = 0; odx = incx; ody = incy; es = dy; el = dx; } else { pdx = 0; pdy = incy; odx = incx; ody = incy; es = dx; el = dy; } // Init start int x = x1; int y = y1; int error = el >> 1; if (y < h && y >= 0 && x < w && x >= 0) { pixels[y * w + x] = color; } // Walk the line! for (int i = 0; i < el; i++) { // Update error term error -= es; // Decide which coord to use if (error < 0) { error += el; x += odx; y += ody; } else { x += pdx; y += pdy; } // Set pixel if (y < h && y >= 0 && x < w && x >= 0) { pixels[y * w + x] = color; } } } } /// /// Draws a colored line by connecting two points using a DDA algorithm (Digital Differential Analyzer). /// /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. internal static void DrawLineDDA(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, Color color) { var col = ConvertColor(color); bmp.DrawLineDDA(x1, y1, x2, y2, col); } /// /// Draws a colored line by connecting two points using a DDA algorithm (Digital Differential Analyzer). /// /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. internal static void DrawLineDDA(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, 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; var pixels = context.Pixels; // Distance start and end point int dx = x2 - x1; int dy = y2 - y1; // Determine slope (absolute value) int len = dy >= 0 ? dy : -dy; int lenx = dx >= 0 ? dx : -dx; if (lenx > len) { len = lenx; } // Prevent division by zero if (len != 0) { // Init steps and start float incx = dx / (float)len; float incy = dy / (float)len; float x = x1; float y = y1; // Walk the line! for (int i = 0; i < len; i++) { if (y < h && y >= 0 && x < w && x >= 0) { pixels[(int)y * w + (int)x] = color; } x += incx; y += incy; } } } } /// /// Draws a colored line by connecting two points using an optimized DDA. /// /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. internal static void DrawLine(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, Color color) { var col = ConvertColor(color); bmp.DrawLine(x1, y1, x2, y2, col); } /// /// Draws a colored line by connecting two points using an optimized DDA. /// /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. internal static void DrawLine(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int color) { using (var context = bmp.GetBitmapContext()) { DrawLine(context, context.Width, context.Height, x1, y1, x2, y2, color); } } /// /// Draws a colored line by connecting two points using an optimized DDA. /// Uses the pixels array and the width directly for best performance. /// /// The context containing the pixels as int RGBA value. /// The width of one scanline in the pixels array. /// The height of the bitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. internal static void DrawLine(BitmapContext context, int pixelWidth, int pixelHeight, int x1, int y1, int x2, int y2, int color) { // Perform cohen-sutherland clipping if either point is out of the viewport if (!CohenSutherlandLineClip(new Rect(0, 0, pixelWidth, pixelHeight), ref x1, ref y1, ref x2, ref y2)) return; var pixels = context.Pixels; // Distance start and end point int dx = x2 - x1; int dy = y2 - y1; const int PRECISION_SHIFT = 8; // Determine slope (absolute value) int lenX, lenY; if (dy >= 0) { lenY = dy; } else { lenY = -dy; } if (dx >= 0) { lenX = dx; } else { lenX = -dx; } if (lenX > lenY) { // x increases by +/- 1 if (dx < 0) { int t = x1; x1 = x2; x2 = t; t = y1; y1 = y2; y2 = t; } // Init steps and start int incy = (dy << PRECISION_SHIFT) / dx; int y1s = y1 << PRECISION_SHIFT; int y2s = y2 << PRECISION_SHIFT; int hs = pixelHeight << PRECISION_SHIFT; if (y1 < y2) { if (y1 >= pixelHeight || y2 < 0) { return; } if (y1s < 0) { if (incy == 0) { return; } int oldy1s = y1s; // Find lowest y1s that is greater or equal than 0. y1s = incy - 1 + ((y1s + 1) % incy); x1 += (y1s - oldy1s) / incy; } if (y2s >= hs) { if (incy != 0) { // Find highest y2s that is less or equal than ws - 1. // y2s = y1s + n * incy. Find n. y2s = hs - 1 - (hs - 1 - y1s) % incy; x2 = x1 + (y2s - y1s) / incy; } } } else { if (y2 >= pixelHeight || y1 < 0) { return; } if (y1s >= hs) { if (incy == 0) { return; } int oldy1s = y1s; // Find highest y1s that is less or equal than ws - 1. // y1s = oldy1s + n * incy. Find n. y1s = hs - 1 + (incy - (hs - 1 - oldy1s) % incy); x1 += (y1s - oldy1s) / incy; } if (y2s < 0) { if (incy != 0) { // Find lowest y2s that is greater or equal than 0. // y2s = y1s + n * incy. Find n. y2s = y1s % incy; x2 = x1 + (y2s - y1s) / incy; } } } if (x1 < 0) { y1s -= incy * x1; x1 = 0; } if (x2 >= pixelWidth) { x2 = pixelWidth - 1; } int ys = y1s; // Walk the line! int y = ys >> PRECISION_SHIFT; int previousY = y; int index = x1 + y * pixelWidth; int k = incy < 0 ? 1 - pixelWidth : 1 + pixelWidth; for (int x = x1; x <= x2; ++x) { pixels[index] = color; ys += incy; y = ys >> PRECISION_SHIFT; if (y != previousY) { previousY = y; index += k; } else { ++index; } } } else { // Prevent division by zero if (lenY == 0) { return; } if (dy < 0) { int t = x1; x1 = x2; x2 = t; t = y1; y1 = y2; y2 = t; } // Init steps and start int x1s = x1 << PRECISION_SHIFT; int x2s = x2 << PRECISION_SHIFT; int ws = pixelWidth << PRECISION_SHIFT; int incx = (dx << PRECISION_SHIFT) / dy; if (x1 < x2) { if (x1 >= pixelWidth || x2 < 0) { return; } if (x1s < 0) { if (incx == 0) { return; } int oldx1s = x1s; // Find lowest x1s that is greater or equal than 0. x1s = incx - 1 + ((x1s + 1) % incx); y1 += (x1s - oldx1s) / incx; } if (x2s >= ws) { if (incx != 0) { // Find highest x2s that is less or equal than ws - 1. // x2s = x1s + n * incx. Find n. x2s = ws - 1 - (ws - 1 - x1s) % incx; y2 = y1 + (x2s - x1s) / incx; } } } else { if (x2 >= pixelWidth || x1 < 0) { return; } if (x1s >= ws) { if (incx == 0) { return; } int oldx1s = x1s; // Find highest x1s that is less or equal than ws - 1. // x1s = oldx1s + n * incx. Find n. x1s = ws - 1 + (incx - (ws - 1 - oldx1s) % incx); y1 += (x1s - oldx1s) / incx; } if (x2s < 0) { if (incx != 0) { // Find lowest x2s that is greater or equal than 0. // x2s = x1s + n * incx. Find n. x2s = x1s % incx; y2 = y1 + (x2s - x1s) / incx; } } } if (y1 < 0) { x1s -= incx * y1; y1 = 0; } if (y2 >= pixelHeight) { y2 = pixelHeight - 1; } int index = x1s; int indexBaseValue = y1 * pixelWidth; // Walk the line! var inc = (pixelWidth << PRECISION_SHIFT) + incx; for (int y = y1; y <= y2; ++y) { pixels[indexBaseValue + (index >> PRECISION_SHIFT)] = color; index += inc; } } } #endregion #region Penned line /// /// Bitfields used to partition the space into 9 regions /// private const byte INSIDE = 0; // 0000 private const byte LEFT = 1; // 0001 private const byte RIGHT = 2; // 0010 private const byte BOTTOM = 4; // 0100 private const byte TOP = 8; // 1000 /// /// Draws a line using a pen / stamp for the line /// /// The WriteableBitmap containing the pixels as int RGBA value. /// The width of one scanline in the pixels array. /// The height of the bitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The pen bitmap. internal static void DrawLinePenned(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, WriteableBitmap penBmp) { using (var context = bmp.GetBitmapContext()) { using (var penContext = penBmp.GetBitmapContext()) { DrawLinePenned(context, bmp.PixelWidth, bmp.PixelHeight, x1, y1, x2, y2, penContext); } } } /// /// Draws a line using a pen / stamp for the line /// /// The context containing the pixels as int RGBA value. /// The width of one scanline in the pixels array. /// The height of the bitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The pen context. internal static void DrawLinePenned(BitmapContext context, int w, int h, int x1, int y1, int x2, int y2, BitmapContext pen) { // Edge case where lines that went out of vertical bounds clipped instead of disappearing if((y1 < 0 && y2 < 0) || (y1 > h && y2 > h)) return; if (x1 == x2 && y1 == y2) return; // Perform cohen-sutherland clipping if either point is out of the viewport if (!CohenSutherlandLineClip(new Rect(0, 0, w, h), ref x1, ref y1, ref x2, ref y2)) return; int size = pen.WriteableBitmap.PixelWidth; int pw = size; var srcRect = new Rect(0, 0, size, size); // Distance start and end point int dx = x2 - x1; int dy = y2 - y1; // Determine sign for direction x int incx = 0; if (dx < 0) { dx = -dx; incx = -1; } else if (dx > 0) { incx = 1; } // Determine sign for direction y int incy = 0; if (dy < 0) { dy = -dy; incy = -1; } else if (dy > 0) { incy = 1; } // Which gradient is larger int pdx, pdy, odx, ody, es, el; if (dx > dy) { pdx = incx; pdy = 0; odx = incx; ody = incy; es = dy; el = dx; } else { pdx = 0; pdy = incy; odx = incx; ody = incy; es = dx; el = dy; } // Init start int x = x1; int y = y1; int error = el >> 1; var destRect = new Rect(x, y, size, size); if (y < h && y >= 0 && x < w && x >= 0) { //Blit(context.WriteableBitmap, new Rect(x,y,3,3), pen.WriteableBitmap, new Rect(0,0,3,3)); Blit(context, w, h, destRect, pen, srcRect, pw); //pixels[y * w + x] = color; } // Walk the line! for (int i = 0; i < el; i++) { // Update error term error -= es; // Decide which coord to use if (error < 0) { error += el; x += odx; y += ody; } else { x += pdx; y += pdy; } // Set pixel if (y < h && y >= 0 && x < w && x >= 0) { //Blit(context, w, h, destRect, pen, srcRect, pw); Blit(context, w, h, new Rect(x, y, size, size), pen, srcRect, pw); //Blit(context.WriteableBitmap, destRect, pen.WriteableBitmap, srcRect); //pixels[y * w + x] = color; } } } /// /// Compute the bit code for a point (x, y) using the clip rectangle /// bounded diagonally by (xmin, ymin), and (xmax, ymax) /// ASSUME THAT xmax , xmin , ymax and ymin are global constants. /// /// The extents. /// The x. /// The y. /// private static byte ComputeOutCode(Rect extents, double x, double y) { // initialized as being inside of clip window byte code = INSIDE; if (x < extents.Left) // to the left of clip window code |= LEFT; else if (x > extents.Right) // to the right of clip window code |= RIGHT; if (y > extents.Bottom) // below the clip window code |= BOTTOM; else if (y < extents.Top) // above the clip window code |= TOP; return code; } #endregion #region Anti-alias line /// /// Draws an anti-aliased, alpha blended, colored line by connecting two points using Wu's antialiasing algorithm /// Uses the pixels array and the width directly for best performance. /// /// The WriteableBitmap. /// The x0. /// The y0. /// The x1. /// The y1. /// Alpha color component /// Premultiplied red color component /// Premultiplied green color component /// Premultiplied blue color component internal static void DrawLineWu(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int sa, int sr, int sg, int sb) { using (var context = bmp.GetBitmapContext()) { DrawLineWu(context, bmp.PixelWidth, bmp.PixelHeight, x1, y1, x2, y2, sa, sr, sg, sb); } } /// /// Draws an anti-aliased, alpha-blended, colored line by connecting two points using Wu's antialiasing algorithm /// Uses the pixels array and the width directly for best performance. /// /// An array containing the pixels as int RGBA value. /// The width of one scanline in the pixels array. /// The height of the bitmap. /// The x0. /// The y0. /// The x1. /// The y1. /// Alpha color component /// Premultiplied red color component /// Premultiplied green color component /// Premultiplied blue color component internal static void DrawLineWu(BitmapContext context, int pixelWidth, int pixelHeight, int x1, int y1, int x2, int y2, int sa, int sr, int sg, int sb) { // Perform cohen-sutherland clipping if either point is out of the viewport if (!CohenSutherlandLineClip(new Rect(0, 0, pixelWidth, pixelHeight), ref x1, ref y1, ref x2, ref y2)) return; var pixels = context.Pixels; const ushort INTENSITY_BITS = 8; const short NUM_LEVELS = 1 << INTENSITY_BITS; // 256 // mask used to compute 1-value by doing (value XOR mask) const ushort WEIGHT_COMPLEMENT_MASK = NUM_LEVELS - 1; // 255 // # of bits by which to shift ErrorAcc to get intensity level const ushort INTENSITY_SHIFT = (ushort)(16 - INTENSITY_BITS); // 8 ushort ErrorAdj, ErrorAcc; ushort ErrorAccTemp, Weighting; short DeltaX, DeltaY, XDir; int tmp; // ensure line runs from top to bottom if (y1 > y2) { tmp = y1; y1 = y2; y2 = tmp; tmp = x1; x1 = x2; x2 = tmp; } // draw initial pixel, which is always intersected by line to it's at 100% intensity pixels[y1 * pixelWidth + x1] = AlphaBlend(sa, sr, sg, sb, pixels[y1 * pixelWidth + x1]); //bitmap.SetPixel(X0, Y0, BaseColor); DeltaX = (short)(x2 - x1); if (DeltaX >= 0) { XDir = 1; } else { XDir = -1; DeltaX = (short)-DeltaX; /* make DeltaX positive */ } // Special-case horizontal, vertical, and diagonal lines, which // require no weighting because they go right through the center of // every pixel; also avoids division by zero later DeltaY = (short)(y2 - y1); if (DeltaY == 0) // if horizontal line { while (DeltaX-- != 0) { x1 += XDir; pixels[y1 * pixelWidth + x1] = AlphaBlend(sa, sr, sg, sb, pixels[y1 * pixelWidth + x1]); } return; } if (DeltaX == 0) // if vertical line { do { y1++; pixels[y1 * pixelWidth + x1] = AlphaBlend(sa, sr, sg, sb, pixels[y1 * pixelWidth + x1]); } while (--DeltaY != 0); return; } if (DeltaX == DeltaY) // diagonal line { do { x1 += XDir; y1++; pixels[y1 * pixelWidth + x1] = AlphaBlend(sa, sr, sg, sb, pixels[y1 * pixelWidth + x1]); } while (--DeltaY != 0); return; } // Line is not horizontal, diagonal, or vertical ErrorAcc = 0; // initialize the line error accumulator to 0 // Is this an X-major or Y-major line? if (DeltaY > DeltaX) { // Y-major line; calculate 16-bit fixed-point fractional part of a // pixel that X advances each time Y advances 1 pixel, truncating the // result so that we won't overrun the endpoint along the X axis ErrorAdj = (ushort)(((ulong)DeltaX << 16) / (ulong)DeltaY); // Draw all pixels other than the first and last while (--DeltaY != 0) { ErrorAccTemp = ErrorAcc; // remember current accumulated error ErrorAcc += ErrorAdj; // calculate error for next pixel if (ErrorAcc <= ErrorAccTemp) { // The error accumulator turned over, so advance the X coord */ x1 += XDir; } y1++; /* Y-major, so always advance Y */ // The IntensityBits most significant bits of ErrorAcc give us the // intensity weighting for this pixel, and the complement of the // weighting for the paired pixel Weighting = (ushort)(ErrorAcc >> INTENSITY_SHIFT); int weight = Weighting ^ WEIGHT_COMPLEMENT_MASK; pixels[y1 * pixelWidth + x1] = AlphaBlend(sa, (sr * weight) >> 8, (sg * weight) >> 8, (sb * weight) >> 8, pixels[y1 * pixelWidth + x1]); weight = Weighting; pixels[y1 * pixelWidth + x1 + XDir] = AlphaBlend(sa, (sr * weight) >> 8, (sg * weight) >> 8, (sb * weight) >> 8, pixels[y1 * pixelWidth + x1 + XDir]); //bitmap.SetPixel(X0, Y0, 255 - (BaseColor + Weighting)); //bitmap.SetPixel(X0 + XDir, Y0, 255 - (BaseColor + (Weighting ^ WeightingComplementMask))); } // Draw the final pixel, which is always exactly intersected by the line and so needs no weighting pixels[y2 * pixelWidth + x2] = AlphaBlend(sa, sr, sg, sb, pixels[y2 * pixelWidth + x2]); //bitmap.SetPixel(X1, Y1, BaseColor); return; } // It's an X-major line; calculate 16-bit fixed-point fractional part of a // pixel that Y advances each time X advances 1 pixel, truncating the // result to avoid overrunning the endpoint along the X axis */ ErrorAdj = (ushort)(((ulong)DeltaY << 16) / (ulong)DeltaX); // Draw all pixels other than the first and last while (--DeltaX != 0) { ErrorAccTemp = ErrorAcc; // remember current accumulated error ErrorAcc += ErrorAdj; // calculate error for next pixel if (ErrorAcc <= ErrorAccTemp) // if error accumulator turned over { // advance the Y coord y1++; } x1 += XDir; // X-major, so always advance X // The IntensityBits most significant bits of ErrorAcc give us the // intensity weighting for this pixel, and the complement of the // weighting for the paired pixel Weighting = (ushort)(ErrorAcc >> INTENSITY_SHIFT); int weight = Weighting ^ WEIGHT_COMPLEMENT_MASK; pixels[y1 * pixelWidth + x1] = AlphaBlend(sa, (sr * weight) >> 8, (sg * weight) >> 8, (sb * weight) >> 8, pixels[y1 * pixelWidth + x1]); weight = Weighting; pixels[(y1 + 1) * pixelWidth + x1] = AlphaBlend(sa, (sr * weight) >> 8, (sg * weight) >> 8, (sb * weight) >> 8, pixels[(y1 + 1) * pixelWidth + x1]); //bitmap.SetPixel(X0, Y0, 255 - (BaseColor + Weighting)); //bitmap.SetPixel(X0, Y0 + 1, // 255 - (BaseColor + (Weighting ^ WeightingComplementMask))); } // Draw the final pixel, which is always exactly intersected by the line and thus needs no weighting pixels[y2 * pixelWidth + x2] = AlphaBlend(sa, sr, sg, sb, pixels[y2 * pixelWidth + x2]); //bitmap.SetPixel(X1, Y1, BaseColor); } /// /// Draws an anti-aliased line with a desired stroke thickness /// The context containing the pixels as int RGBA value. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. /// The stroke thickness of the line. /// internal static void DrawLineAa(BitmapContext context, int pixelWidth, int pixelHeight, int x1, int y1, int x2, int y2, int color, int strokeThickness) { AAWidthLine(pixelWidth, pixelHeight, context, x1, y1, x2, y2, strokeThickness, color); } /// /// Draws an anti-aliased line with a desired stroke thickness /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. /// The stroke thickness of the line. /// internal static void DrawLineAa(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int color, int strokeThickness) { using (var context = bmp.GetBitmapContext()) { AAWidthLine(bmp.PixelWidth, bmp.PixelHeight, context, x1, y1, x2, y2, strokeThickness, color); } } /// /// Draws an anti-aliased line with a desired stroke thickness /// The context containing the pixels as int RGBA value. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. /// The stroke thickness of the line. /// internal static void DrawLineAa(BitmapContext context, int pixelWidth, int pixelHeight, int x1, int y1, int x2, int y2, Color color, int strokeThickness) { var col = ConvertColor(color); AAWidthLine(pixelWidth, pixelHeight, context, x1, y1, x2, y2, strokeThickness, col); } /// /// Draws an anti-aliased line with a desired stroke thickness /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. /// The stroke thickness of the line. /// internal static void DrawLineAa(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, Color color, int strokeThickness) { var col = ConvertColor(color); using (var context = bmp.GetBitmapContext()) { AAWidthLine(bmp.PixelWidth, bmp.PixelHeight, context, x1, y1, x2, y2, strokeThickness, col); } } /// /// Draws an anti-aliased line, using an optimized version of Gupta-Sproull algorithm /// From http://nokola.com/blog/post/2010/10/14/Anti-aliased-Lines-And-Optimizing-Code-for-Windows-Phone-7e28093First-Look.aspx /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. /// internal static void DrawLineAa(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, Color color) { var col = ConvertColor(color); bmp.DrawLineAa(x1, y1, x2, y2, col); } /// /// Draws an anti-aliased line, using an optimized version of Gupta-Sproull algorithm /// From http://nokola.com/blog/post/2010/10/14/Anti-aliased-Lines-And-Optimizing-Code-for-Windows-Phone-7e28093First-Look.aspx /// The WriteableBitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. /// internal static void DrawLineAa(this WriteableBitmap bmp, int x1, int y1, int x2, int y2, int color) { using (var context = bmp.GetBitmapContext()) { DrawLineAa(context, context.Width, context.Height, x1, y1, x2, y2, color); } } /// /// Draws an anti-aliased line, using an optimized version of Gupta-Sproull algorithm /// From http://nokola.com/blog/post/2010/10/14/Anti-aliased-Lines-And-Optimizing-Code-for-Windows-Phone-7e28093First-Look.aspx /// The context containing the pixels as int RGBA value. /// The width of one scanline in the pixels array. /// The height of the bitmap. /// The x-coordinate of the start point. /// The y-coordinate of the start point. /// The x-coordinate of the end point. /// The y-coordinate of the end point. /// The color for the line. /// internal static void DrawLineAa(BitmapContext context, int pixelWidth, int pixelHeight, int x1, int y1, int x2, int y2, int color) { if ((x1 == x2) && (y1 == y2)) return; // edge case causing invDFloat to overflow, found by Shai Rubinshtein // Perform cohen-sutherland clipping if either point is out of the viewport if (!CohenSutherlandLineClip(new Rect(0, 0, pixelWidth, pixelHeight), ref x1, ref y1, ref x2, ref y2)) return; if (x1 < 1) x1 = 1; if (x1 > pixelWidth - 2) x1 = pixelWidth - 2; if (y1 < 1) y1 = 1; if (y1 > pixelHeight - 2) y1 = pixelHeight - 2; if (x2 < 1) x2 = 1; if (x2 > pixelWidth - 2) x2 = pixelWidth - 2; if (y2 < 1) y2 = 1; if (y2 > pixelHeight - 2) y2 = pixelHeight - 2; var addr = y1 * pixelWidth + x1; var dx = x2 - x1; var dy = y2 - y1; int du; int dv; int u; int v; int uincr; int vincr; // Extract color var a = (color >> 24) & 0xFF; var srb = (uint)(color & 0x00FF00FF); var sg = (uint)((color >> 8) & 0xFF); // By switching to (u,v), we combine all eight octants int adx = dx, ady = dy; if (dx < 0) adx = -dx; if (dy < 0) ady = -dy; if (adx > ady) { du = adx; dv = ady; u = x2; v = y2; uincr = 1; vincr = pixelWidth; if (dx < 0) uincr = -uincr; if (dy < 0) vincr = -vincr; } else { du = ady; dv = adx; u = y2; v = x2; uincr = pixelWidth; vincr = 1; if (dy < 0) uincr = -uincr; if (dx < 0) vincr = -vincr; } var uend = u + du; var d = (dv << 1) - du; // Initial value as in Bresenham's var incrS = dv << 1; // Δd for straight increments var incrD = (dv - du) << 1; // Δd for diagonal increments var invDFloat = 1.0 / (4.0 * Math.Sqrt(du * du + dv * dv)); // Precomputed inverse denominator var invD2DuFloat = 0.75 - 2.0 * (du * invDFloat); // Precomputed constant const int PRECISION_SHIFT = 10; // result distance should be from 0 to 1 << PRECISION_SHIFT, mapping to a range of 0..1 const int PRECISION_MULTIPLIER = 1 << PRECISION_SHIFT; var invD = (int)(invDFloat * PRECISION_MULTIPLIER); var invD2Du = (int)(invD2DuFloat * PRECISION_MULTIPLIER * a); var zeroDot75 = (int)(0.75 * PRECISION_MULTIPLIER * a); var invDMulAlpha = invD * a; var duMulInvD = du * invDMulAlpha; // used to help optimize twovdu * invD var dMulInvD = d * invDMulAlpha; // used to help optimize twovdu * invD //int twovdu = 0; // Numerator of distance; starts at 0 var twovduMulInvD = 0; // since twovdu == 0 var incrSMulInvD = incrS * invDMulAlpha; var incrDMulInvD = incrD * invDMulAlpha; do { AlphaBlendNormalOnPremultiplied(context, addr, (zeroDot75 - twovduMulInvD) >> PRECISION_SHIFT, srb, sg); AlphaBlendNormalOnPremultiplied(context, addr + vincr, (invD2Du + twovduMulInvD) >> PRECISION_SHIFT, srb, sg); AlphaBlendNormalOnPremultiplied(context, addr - vincr, (invD2Du - twovduMulInvD) >> PRECISION_SHIFT, srb, sg); if (d < 0) { // choose straight (u direction) twovduMulInvD = dMulInvD + duMulInvD; d += incrS; dMulInvD += incrSMulInvD; } else { // choose diagonal (u+v direction) twovduMulInvD = dMulInvD - duMulInvD; d += incrD; dMulInvD += incrDMulInvD; v++; addr += vincr; } u++; addr += uincr; } while (u < uend); } /// /// Blends a specific source color on top of a destination premultiplied color /// /// Array containing destination color /// Index of destination pixel /// Source alpha (0..255) /// Source non-premultiplied red and blue component in the format 0x00rr00bb /// Source green component (0..255) private static void AlphaBlendNormalOnPremultiplied(BitmapContext context, int index, int sa, uint srb, uint sg) { var pixels = context.Pixels; var destPixel = (uint)pixels[index]; var da = (destPixel >> 24); var dg = ((destPixel >> 8) & 0xff); var drb = destPixel & 0x00FF00FF; // blend with high-quality alpha and lower quality but faster 1-off RGBs pixels[index] = (int)( ((sa + ((da * (255 - sa) * 0x8081) >> 23)) << 24) | // alpha (((sg - dg) * sa + (dg << 8)) & 0xFFFFFF00) | // green (((((srb - drb) * sa) >> 8) + drb) & 0x00FF00FF) // red and blue ); } #endregion #region Helper internal static bool CohenSutherlandLineClipWithViewPortOffset(Rect viewPort, ref float xi0, ref float yi0, ref float xi1, ref float yi1, int offset) { var viewPortWithOffset = new Rect(viewPort.X - offset, viewPort.Y - offset, viewPort.Width + 2 * offset, viewPort.Height + 2 * offset); return CohenSutherlandLineClip(viewPortWithOffset, ref xi0, ref yi0, ref xi1, ref yi1); } internal static bool CohenSutherlandLineClip(Rect extents, ref float xi0, ref float yi0, ref float xi1, ref float yi1) { // Fix #SC-1555: Log(0) issue // CohenSuzerland line clipping algorithm returns NaN when point has infinity value double x0 = ClipToInt(xi0); double y0 = ClipToInt(yi0); double x1 = ClipToInt(xi1); double y1 = ClipToInt(yi1); var isValid = CohenSutherlandLineClip(extents, ref x0, ref y0, ref x1, ref y1); // Update the clipped line xi0 = (float)x0; yi0 = (float)y0; xi1 = (float)x1; yi1 = (float)y1; return isValid; } private static float ClipToInt(float d) { if (d > int.MaxValue) return int.MaxValue; if (d < int.MinValue) return int.MinValue; return d; } internal static bool CohenSutherlandLineClip(Rect extents, ref int xi0, ref int yi0, ref int xi1, ref int yi1) { double x0 = xi0; double y0 = yi0; double x1 = xi1; double y1 = yi1; var isValid = CohenSutherlandLineClip(extents, ref x0, ref y0, ref x1, ref y1); // Update the clipped line xi0 = (int)x0; yi0 = (int)y0; xi1 = (int)x1; yi1 = (int)y1; return isValid; } /// /// Cohen–Sutherland clipping algorithm clips a line from /// P0 = (x0, y0) to P1 = (x1, y1) against a rectangle with /// diagonal from (xmin, ymin) to (xmax, ymax). /// /// See http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm for details /// a list of two points in the resulting clipped line, or zero internal static bool CohenSutherlandLineClip(Rect extents, ref double x0, ref double y0, ref double x1, ref double y1) { // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle byte outcode0 = ComputeOutCode(extents, x0, y0); byte outcode1 = ComputeOutCode(extents, x1, y1); // No clipping if both points lie inside viewport if (outcode0 == INSIDE && outcode1 == INSIDE) return true; bool isValid = false; while (true) { // Bitwise OR is 0. Trivially accept and get out of loop if ((outcode0 | outcode1) == 0) { isValid = true; break; } // Bitwise AND is not 0. Trivially reject and get out of loop else if ((outcode0 & outcode1) != 0) { break; } else { // failed both tests, so calculate the line segment to clip // from an outside point to an intersection with clip edge double x, y; // At least one endpoint is outside the clip rectangle; pick it. byte outcodeOut = (outcode0 != 0) ? outcode0 : outcode1; // Now find the intersection point; // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0) if ((outcodeOut & TOP) != 0) { // point is above the clip rectangle x = x0 + (x1 - x0) * (extents.Top - y0) / (y1 - y0); y = extents.Top; } else if ((outcodeOut & BOTTOM) != 0) { // point is below the clip rectangle x = x0 + (x1 - x0) * (extents.Bottom - y0) / (y1 - y0); y = extents.Bottom; } else if ((outcodeOut & RIGHT) != 0) { // point is to the right of clip rectangle y = y0 + (y1 - y0) * (extents.Right - x0) / (x1 - x0); x = extents.Right; } else if ((outcodeOut & LEFT) != 0) { // point is to the left of clip rectangle y = y0 + (y1 - y0) * (extents.Left - x0) / (x1 - x0); x = extents.Left; } else { x = double.NaN; y = double.NaN; } // Now we move outside point to intersection point to clip // and get ready for next pass. if (outcodeOut == outcode0) { x0 = x; y0 = y; outcode0 = ComputeOutCode(extents, x0, y0); } else { x1 = x; y1 = y; outcode1 = ComputeOutCode(extents, x1, y1); } } } return isValid; } /// /// Alpha blends 2 premultiplied colors with each other /// /// Source alpha color component /// Premultiplied source red color component /// Premultiplied source green color component /// Premultiplied source blue color component /// Premultiplied destination color /// Premultiplied blended color value internal static int AlphaBlend(int sa, int sr, int sg, int sb, int destPixel) { int dr, dg, db; int da; da = ((destPixel >> 24) & 0xff); dr = ((destPixel >> 16) & 0xff); dg = ((destPixel >> 8) & 0xff); 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 } }