/// <summary> /// Gets a BitmapContext within which to perform nested IO operations on the bitmap /// </summary> /// <remarks>For WPF the BitmapContext will lock the bitmap. Call Dispose on the context to unlock</remarks> /// <param name="bmp"></param> /// <returns></returns> public static BitmapContext GetBitmapContext(this BitmapBuffer bmp) { return(new BitmapContext(bmp)); }
/// <summary> /// Draws a colored line by connecting two points using the Bresenham algorithm. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="x1">The x-coordinate of the start point.</param> /// <param name="y1">The y-coordinate of the start point.</param> /// <param name="x2">The x-coordinate of the end point.</param> /// <param name="y2">The y-coordinate of the end point.</param> /// <param name="color">The color for the line.</param> /// <param name="clipRect">The region in the image to restrict drawing to.</param> public static unsafe void DrawLineBresenham(this BitmapBuffer bmp, int x1, int y1, int x2, int y2, int color, RectD?clipRect = null) { using (BitmapContext context = bmp.GetBitmapContext()) { // Use refs for faster access (really important!) speeds up a lot! int w = context.Width; int h = context.Height; int *pixels = context.Pixels._inf32Buffer; // Get clip coordinates int clipX1 = 0; int clipX2 = w; int clipY1 = 0; int clipY2 = h; if (clipRect.HasValue) { var c = clipRect.Value; clipX1 = (int)c.X; clipX2 = (int)(c.X + c.Width); clipY1 = (int)c.Y; clipY2 = (int)(c.Y + c.Height); } // 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 < clipY2 && y >= clipY1 && x < clipX2 && x >= clipX1) { 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 < clipY2 && y >= clipY1 && x < clipX2 && x >= clipX1) { pixels[y * w + x] = color; } } } }
private const byte TOP = 8; // 1000 /// <summary> /// Draws a line using a pen / stamp for the line /// </summary> /// <param name="bmp">The WriteableBitmap containing the pixels as int RGBA value.</param> /// <param name="w">The width of one scanline in the pixels array.</param> /// <param name="h">The height of the bitmap.</param> /// <param name="x1">The x-coordinate of the start point.</param> /// <param name="y1">The y-coordinate of the start point.</param> /// <param name="x2">The x-coordinate of the end point.</param> /// <param name="y2">The y-coordinate of the end point.</param> /// <param name="penBmp">The pen bitmap.</param> public static void DrawLinePenned(this BitmapBuffer bmp, int x1, int y1, int x2, int y2, BitmapBuffer penBmp, RectD?clipRect = null) { using (BitmapContext context = bmp.GetBitmapContext()) using (BitmapContext penContext = penBmp.GetBitmapContext(ReadWriteMode.ReadOnly)) { DrawLinePenned(context, bmp.PixelWidth, bmp.PixelHeight, x1, y1, x2, y2, penContext, clipRect); } }
/// <summary> /// Draws a cubic Beziér spline defined by start, end and two control points. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="x1">The x-coordinate of the start point.</param> /// <param name="y1">The y-coordinate of the start point.</param> /// <param name="cx1">The x-coordinate of the 1st control point.</param> /// <param name="cy1">The y-coordinate of the 1st control point.</param> /// <param name="cx2">The x-coordinate of the 2nd control point.</param> /// <param name="cy2">The y-coordinate of the 2nd control point.</param> /// <param name="x2">The x-coordinate of the end point.</param> /// <param name="y2">The y-coordinate of the end point.</param> /// <param name="color">The color.</param> public static void DrawBezier(this BitmapBuffer bmp, int x1, int y1, int cx1, int cy1, int cx2, int cy2, int x2, int y2, ColorInt color) { bmp.DrawBezier(x1, y1, cx1, cy1, cx2, cy2, x2, y2, color.ToPreMultAlphaColor()); }
/// <summary> /// Draws a colored line by connecting two points using the Bresenham algorithm. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="x1">The x-coordinate of the start point.</param> /// <param name="y1">The y-coordinate of the start point.</param> /// <param name="x2">The x-coordinate of the end point.</param> /// <param name="y2">The y-coordinate of the end point.</param> /// <param name="color">The color for the line.</param> /// <param name="clipRect">The region in the image to restrict drawing to.</param> public static void DrawLineBresenham(this BitmapBuffer bmp, int x1, int y1, int x2, int y2, ColorInt color, RectD?clipRect = null) { bmp.DrawLineBresenham(x1, y1, x2, y2, color.ToPreMultAlphaColor(), clipRect); }
/// <summary> /// Rotates the bitmap in 90° steps clockwise and returns a new rotated WriteableBitmap. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="angle">The angle in degrees the bitmap should be rotated in 90° steps clockwise.</param> /// <returns>A new WriteableBitmap that is a rotated version of the input.</returns> public static BitmapBuffer Rotate(this BitmapBuffer bmp, FastRotateAngle angle) { using (BitmapContext context = bmp.GetBitmapContext(ReadWriteMode.ReadOnly)) { // Use refs for faster access (really important!) speeds up a lot! int w = context.Width; int h = context.Height; int[] p = context.Pixels; int i = 0; switch (angle) { default: { return(bmp.Clone()); } case FastRotateAngle.Rotate90: { var result = BitmapBufferFactory.New(h, w); using (BitmapContext destContext = result.GetBitmapContext()) { var rp = destContext.Pixels; for (int x = 0; x < w; x++) { for (int y = h - 1; y >= 0; y--) { int srcInd = y * w + x; rp[i] = p[srcInd]; i++; } } } return(result); } case FastRotateAngle.Rotate180: { var result = BitmapBufferFactory.New(w, h); using (BitmapContext destContext = result.GetBitmapContext()) { var rp = destContext.Pixels; for (int y = h - 1; y >= 0; y--) { for (int x = w - 1; x >= 0; x--) { int srcInd = y * w + x; rp[i] = p[srcInd]; i++; } } } return(result); } case FastRotateAngle.Rotate270: { var result = BitmapBufferFactory.New(h, w); using (BitmapContext destContext = result.GetBitmapContext()) { int[] rp = destContext.Pixels; for (int x = w - 1; x >= 0; x--) { for (int y = 0; y < h; y++) { int srcInd = y * w + x; rp[i] = p[srcInd]; i++; } } } return(result); } } } }
/// <summary> /// Draws a closed Cardinal spline (cubic) defined by a point collection. /// The cardinal spline passes through each point in the collection. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="points">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).</param> /// <param name="tension">The tension of the curve defines the shape. Usually between 0 and 1. 0 would be a straight line.</param> /// <param name="color">The color for the spline.</param> public static void DrawCurveClosed(this BitmapBuffer bmp, int[] points, float tension, ColorInt color) { bmp.DrawCurveClosed(points, tension, color.ToPreMultAlphaColor()); }
/// <summary> /// Copies all the Pixels from the WriteableBitmap into a ARGB byte array. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <returns>The color buffer as byte ARGB values.</returns> public static byte[] ToByteArray(this BitmapBuffer bmp) { return(bmp.ToByteArray(0, -1)); }
/// <summary> /// Copies color information from an ARGB byte array into this WriteableBitmap. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="count">The number of bytes to copy from the buffer.</param> /// <param name="buffer">The color buffer as byte ARGB values.</param> /// <returns>The WriteableBitmap that was passed as parameter.</returns> public static BitmapBuffer FromByteArray(this BitmapBuffer bmp, byte[] buffer, int count) { return(bmp.FromByteArray(buffer, 0, count)); }
/// <summary> /// Writes the WriteableBitmap as a TGA image to a stream. /// Used with permission from Nokola: http://nokola.com/blog/post/2010/01/21/Quick-and-Dirty-Output-of-WriteableBitmap-as-TGA-Image.aspx /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="destination">The destination stream.</param> public static void WriteTga(this BitmapBuffer bmp, Stream destination) { using (BitmapContext context = bmp.GetBitmapContext(ReadWriteMode.ReadOnly)) { int width = context.Width; int height = context.Height; int[] pixels = context.Pixels; byte[] data = new byte[context.Length * ARGB_SIZE]; // Copy bitmap data as BGRA int offsetSource = 0; int width4 = width << 2; int width8 = width << 3; int offsetDest = (height - 1) * width4; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Account for pre-multiplied alpha int c = pixels[offsetSource]; byte a = (byte)(c >> 24); // Prevent division by zero int ai = a; if (ai == 0) { ai = 1; } // Scale inverse alpha to use cheap integer mul bit shift ai = ((255 << 8) / ai); data[offsetDest + 3] = (byte)a; // A data[offsetDest + 2] = (byte)((((c >> 16) & 0xFF) * ai) >> 8); // R data[offsetDest + 1] = (byte)((((c >> 8) & 0xFF) * ai) >> 8); // G data[offsetDest] = (byte)((((c & 0xFF) * ai) >> 8)); // B offsetSource++; offsetDest += ARGB_SIZE; } offsetDest -= width8; } // Create header var header = new byte[] { 0, // ID length 0, // no color map 2, // uncompressed, true color 0, 0, 0, 0, 0, 0, 0, 0, 0, // x and y origin (byte)(width & 0x00FF), (byte)((width & 0xFF00) >> 8), (byte)(height & 0x00FF), (byte)((height & 0xFF00) >> 8), 32, // 32 bit bitmap 0 }; // Write header and data using (var writer = new BinaryWriter(destination)) { writer.Write(header); writer.Write(data); } } }
/// <summary> /// Copies the Pixels from the WriteableBitmap into a ARGB byte array. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="count">The number of pixels to copy.</param> /// <returns>The color buffer as byte ARGB values.</returns> public static byte[] ToByteArray(this BitmapBuffer bmp, int count) { return(bmp.ToByteArray(0, count)); }
/// <summary> /// Copies all the color information from an ARGB byte array into this WriteableBitmap. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="buffer">The color buffer as byte ARGB values.</param> /// <returns>The WriteableBitmap that was passed as parameter.</returns> public static BitmapBuffer FromByteArray(this BitmapBuffer bmp, byte[] buffer) { return(bmp.FromByteArray(buffer, 0, buffer.Length)); }
/// <summary> /// Creates a new filtered WriteableBitmap. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="kernel">The kernel used for convolution.</param> /// <param name="kernelFactorSum">The factor used for the kernel summing.</param> /// <param name="kernelOffsetSum">The offset used for the kernel summing.</param> /// <returns>A new WriteableBitmap that is a filtered version of the input.</returns> public static unsafe BitmapBuffer Convolute(this BitmapBuffer bmp, int[,] kernel, int kernelFactorSum, int kernelOffsetSum) { int kh = kernel.GetUpperBound(0) + 1; int kw = kernel.GetUpperBound(1) + 1; if ((kw & 1) == 0) { throw new System.InvalidOperationException("Kernel width must be odd!"); } if ((kh & 1) == 0) { throw new System.InvalidOperationException("Kernel height must be odd!"); } using (BitmapContext srcContext = bmp.GetBitmapContext(ReadWriteMode.ReadOnly)) { int w = srcContext.Width; int h = srcContext.Height; BitmapBuffer result = BitmapBufferFactory.New(w, h); using (BitmapContext resultContext = result.GetBitmapContext()) { int *pixels = srcContext.Pixels._inf32Buffer; int *resultPixels = resultContext.Pixels._inf32Buffer; int index = 0; int kwh = kw >> 1; int khh = kh >> 1; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { int a = 0; int r = 0; int g = 0; int b = 0; for (int kx = -kwh; kx <= kwh; kx++) { int px = kx + x; // Repeat pixels at borders if (px < 0) { px = 0; } else if (px >= w) { px = w - 1; } for (int ky = -khh; ky <= khh; ky++) { int py = ky + y; // Repeat pixels at borders if (py < 0) { py = 0; } else if (py >= h) { py = h - 1; } int col = pixels[py * w + px]; int k = kernel[ky + kwh, kx + khh]; a += ((col >> 24) & 0xff) * k; r += ((col >> 16) & 0xff) * k; g += ((col >> 8) & 0xff) * k; b += ((col) & 0xff) * k; } } int ta = ((a / kernelFactorSum) + kernelOffsetSum); int tr = ((r / kernelFactorSum) + kernelOffsetSum); int tg = ((g / kernelFactorSum) + kernelOffsetSum); int tb = ((b / kernelFactorSum) + kernelOffsetSum); // Clamp to byte boundaries byte ba = (byte)((ta > 255) ? 255 : ((ta < 0) ? 0 : ta)); byte br = (byte)((tr > 255) ? 255 : ((tr < 0) ? 0 : tr)); byte bg = (byte)((tg > 255) ? 255 : ((tg < 0) ? 0 : tg)); byte bb = (byte)((tb > 255) ? 255 : ((tb < 0) ? 0 : tb)); resultPixels[index++] = (ba << 24) | (br << 16) | (bg << 8) | (bb); } } return(result); } } }
/// <summary> /// Rotates the bitmap in any degree returns a new rotated WriteableBitmap. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="angle">Arbitrary angle in 360 Degrees (positive = clockwise).</param> /// <param name="crop">if true: keep the size, false: adjust canvas to new size</param> /// <returns>A new WriteableBitmap that is a rotated version of the input.</returns> public static BitmapBuffer RotateFree(this BitmapBuffer bmp, double angle, bool crop = true) { // rotating clockwise, so it's negative relative to Cartesian quadrants double cnAngle = -1.0 * (Math.PI / 180) * angle; // general iterators int i, j; // calculated indices in Cartesian coordinates int x, y; double fDistance, fPolarAngle; // for use in neighboring indices in Cartesian coordinates int iFloorX, iCeilingX, iFloorY, iCeilingY; // calculated indices in Cartesian coordinates with trailing decimals double fTrueX, fTrueY; // for interpolation double fDeltaX, fDeltaY; // interpolated "top" pixels double fTopRed, fTopGreen, fTopBlue, fTopAlpha; // interpolated "bottom" pixels double fBottomRed, fBottomGreen, fBottomBlue, fBottomAlpha; // final interpolated color components int iRed, iGreen, iBlue, iAlpha; int iCentreX, iCentreY; int iDestCentreX, iDestCentreY; int iWidth, iHeight, newWidth, newHeight; using (var bmpContext = bmp.GetBitmapContext(ReadWriteMode.ReadOnly)) { iWidth = bmpContext.Width; iHeight = bmpContext.Height; if (crop) { newWidth = iWidth; newHeight = iHeight; } else { double rad = angle / (180 / Math.PI); newWidth = (int)Math.Ceiling(Math.Abs(Math.Sin(rad) * iHeight) + Math.Abs(Math.Cos(rad) * iWidth)); newHeight = (int)Math.Ceiling(Math.Abs(Math.Sin(rad) * iWidth) + Math.Abs(Math.Cos(rad) * iHeight)); } iCentreX = iWidth / 2; iCentreY = iHeight / 2; iDestCentreX = newWidth / 2; iDestCentreY = newHeight / 2; BitmapBuffer bmBilinearInterpolation = BitmapBufferFactory.New(newWidth, newHeight); using (BitmapContext bilinearContext = bmBilinearInterpolation.GetBitmapContext()) { int[] newp = bilinearContext.Pixels; int[] oldp = bmpContext.Pixels; int oldw = bmpContext.Width; // assigning pixels of destination image from source image // with bilinear interpolation for (i = 0; i < newHeight; ++i) { for (j = 0; j < newWidth; ++j) { // convert raster to Cartesian x = j - iDestCentreX; y = iDestCentreY - i; // convert Cartesian to polar fDistance = Math.Sqrt(x * x + y * y); if (x == 0) { if (y == 0) { // center of image, no rotation needed newp[i * newWidth + j] = oldp[iCentreY * oldw + iCentreX]; continue; } if (y < 0) { fPolarAngle = 1.5 * Math.PI; } else { fPolarAngle = 0.5 * Math.PI; } } else { fPolarAngle = Math.Atan2(y, x); } // the crucial rotation part // "reverse" rotate, so minus instead of plus fPolarAngle -= cnAngle; // convert polar to Cartesian fTrueX = fDistance * Math.Cos(fPolarAngle); fTrueY = fDistance * Math.Sin(fPolarAngle); // convert Cartesian to raster fTrueX = fTrueX + iCentreX; fTrueY = iCentreY - fTrueY; iFloorX = (int)(Math.Floor(fTrueX)); iFloorY = (int)(Math.Floor(fTrueY)); iCeilingX = (int)(Math.Ceiling(fTrueX)); iCeilingY = (int)(Math.Ceiling(fTrueY)); // check bounds if (iFloorX < 0 || iCeilingX < 0 || iFloorX >= iWidth || iCeilingX >= iWidth || iFloorY < 0 || iCeilingY < 0 || iFloorY >= iHeight || iCeilingY >= iHeight) { continue; } fDeltaX = fTrueX - iFloorX; fDeltaY = fTrueY - iFloorY; int clrTopLeft = oldp[iFloorY * oldw + iFloorX]; int clrTopRight = oldp[iFloorY * oldw + iCeilingX]; int clrBottomLeft = oldp[iCeilingY * oldw + iFloorX]; int clrBottomRight = oldp[iCeilingY * oldw + iCeilingX]; fTopAlpha = (1 - fDeltaX) * ((clrTopLeft >> 24) & 0xFF) + fDeltaX * ((clrTopRight >> 24) & 0xFF); fTopRed = (1 - fDeltaX) * ((clrTopLeft >> 16) & 0xFF) + fDeltaX * ((clrTopRight >> 16) & 0xFF); fTopGreen = (1 - fDeltaX) * ((clrTopLeft >> 8) & 0xFF) + fDeltaX * ((clrTopRight >> 8) & 0xFF); fTopBlue = (1 - fDeltaX) * (clrTopLeft & 0xFF) + fDeltaX * (clrTopRight & 0xFF); // linearly interpolate horizontally between bottom neighbors fBottomAlpha = (1 - fDeltaX) * ((clrBottomLeft >> 24) & 0xFF) + fDeltaX * ((clrBottomRight >> 24) & 0xFF); fBottomRed = (1 - fDeltaX) * ((clrBottomLeft >> 16) & 0xFF) + fDeltaX * ((clrBottomRight >> 16) & 0xFF); fBottomGreen = (1 - fDeltaX) * ((clrBottomLeft >> 8) & 0xFF) + fDeltaX * ((clrBottomRight >> 8) & 0xFF); fBottomBlue = (1 - fDeltaX) * (clrBottomLeft & 0xFF) + fDeltaX * (clrBottomRight & 0xFF); // linearly interpolate vertically between top and bottom interpolated results iRed = (int)(Math.Round((1 - fDeltaY) * fTopRed + fDeltaY * fBottomRed)); iGreen = (int)(Math.Round((1 - fDeltaY) * fTopGreen + fDeltaY * fBottomGreen)); iBlue = (int)(Math.Round((1 - fDeltaY) * fTopBlue + fDeltaY * fBottomBlue)); iAlpha = (int)(Math.Round((1 - fDeltaY) * fTopAlpha + fDeltaY * fBottomAlpha)); // make sure color values are valid if (iRed < 0) { iRed = 0; } if (iRed > 255) { iRed = 255; } if (iGreen < 0) { iGreen = 0; } if (iGreen > 255) { iGreen = 255; } if (iBlue < 0) { iBlue = 0; } if (iBlue > 255) { iBlue = 255; } if (iAlpha < 0) { iAlpha = 0; } if (iAlpha > 255) { iAlpha = 255; } int a = iAlpha + 1; newp[i * newWidth + j] = (iAlpha << 24) | ((byte)((iRed * a) >> 8) << 16) | ((byte)((iGreen * a) >> 8) << 8) | ((byte)((iBlue * a) >> 8)); } } return(bmBilinearInterpolation); } } }
/// <summary> /// Gets a BitmapContext within which to perform nested IO operations on the bitmap /// </summary> /// <remarks>For WPF the BitmapContext will lock the bitmap. Call Dispose on the context to unlock</remarks> /// <param name="bmp">The bitmap.</param> /// <param name="mode">The ReadWriteMode. If set to ReadOnly, the bitmap will not be invalidated on dispose of the context, else it will</param> /// <returns></returns> public static BitmapContext GetBitmapContext(this BitmapBuffer bmp, ReadWriteMode mode) { return(new BitmapContext(bmp, mode)); }
/// <summary> /// 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). /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="points">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).</param> /// <param name="color">The color for the spline.</param> public static void DrawBeziers(this BitmapBuffer bmp, int[] points, ColorInt color) { bmp.DrawBeziers(points, color.ToPreMultAlphaColor()); }
/// <summary> /// Creates an instance of a BitmapContext, with default mode = ReadWrite /// </summary> /// <param name="writeableBitmap"></param> public BitmapContext(BitmapBuffer writeableBitmap) : this(writeableBitmap, ReadWriteMode.ReadWrite) { }
/// <summary> /// Creates a new cropped WriteableBitmap. /// </summary> /// <param name="bmp">The WriteableBitmap.</param> /// <param name="region">The rectangle that defines the crop region.</param> /// <returns>A new WriteableBitmap that is a cropped version of the input.</returns> public static BitmapBuffer Crop(this BitmapBuffer bmp, RectD region) { return(bmp.Crop((int)region.X, (int)region.Y, (int)region.Width, (int)region.Height)); }