#region Header // // Project: WriteableBitmapEx - WriteableBitmap extensions // Description: Collection of transformation 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/WriteableBitmapTransformationExtensions.cs $ // Id: $Id: WriteableBitmapTransformationExtensions.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 using Windows.Foundation; namespace Windows.UI.Xaml.Media.Imaging #else namespace System.Windows.Media.Imaging #endif { /// /// Collection of transformation extension methods for the WriteableBitmap class. /// internal #if WPF unsafe #endif static partial class WriteableBitmapExtensions { #region Enums /// /// The interpolation method. /// internal enum Interpolation { /// /// The nearest neighbor algorithm simply selects the color of the nearest pixel. /// NearestNeighbor = 0, /// /// Linear interpolation in 2D using the average of 3 neighboring pixels. /// Bilinear, } /// /// The mode for flipping. /// internal enum FlipMode { /// /// Flips the image vertical (around the center of the y-axis). /// Vertical, /// /// Flips the image horizontal (around the center of the x-axis). /// Horizontal } #endregion #region Methods #region Crop /// /// Creates a new cropped WriteableBitmap. /// /// The WriteableBitmap. /// The x coordinate of the rectangle that defines the crop region. /// The y coordinate of the rectangle that defines the crop region. /// The width of the rectangle that defines the crop region. /// The height of the rectangle that defines the crop region. /// A new WriteableBitmap that is a cropped version of the input. internal static WriteableBitmap Crop(this WriteableBitmap bmp, int x, int y, int width, int height) { using (var srcContext = bmp.GetBitmapContext()) { var srcWidth = srcContext.Width; var srcHeight = srcContext.Height; // If the rectangle is completely out of the bitmap if (x > srcWidth || y > srcHeight) { return BitmapFactory.New(0, 0); } // Clamp to boundaries if (x < 0) x = 0; if (x + width > srcWidth) width = srcWidth - x; if (y < 0) y = 0; if (y + height > srcHeight) height = srcHeight - y; // Copy the pixels line by line using fast BlockCopy var result = BitmapFactory.New(width, height); using (var destContext = result.GetBitmapContext()) { for (var line = 0; line < height; line++) { var srcOff = ((y + line) * srcWidth + x) * SizeOfArgb; var dstOff = line * width * SizeOfArgb; BitmapContext.BlockCopy(srcContext, srcOff, destContext, dstOff, width * SizeOfArgb); } return result; } } } /// /// Creates a new cropped WriteableBitmap. /// /// The WriteableBitmap. /// The rectangle that defines the crop region. /// A new WriteableBitmap that is a cropped version of the input. internal static WriteableBitmap Crop(this WriteableBitmap bmp, Rect region) { return bmp.Crop((int)region.X, (int)region.Y, (int)region.Width, (int)region.Height); } #endregion #region Resize /// /// Creates a new resized WriteableBitmap. /// /// The WriteableBitmap. /// The new desired width. /// The new desired height. /// The interpolation method that should be used. /// A new WriteableBitmap that is a resized version of the input. internal static WriteableBitmap Resize(this WriteableBitmap bmp, int width, int height, Interpolation interpolation) { using (var srcContext = bmp.GetBitmapContext()) { var pd = Resize(srcContext, srcContext.Width, srcContext.Height, width, height, interpolation); var result = BitmapFactory.New(width, height); using (var dstContext = result.GetBitmapContext()) { BitmapContext.BlockCopy(pd, 0, dstContext, 0, SizeOfArgb * pd.Length); } return result; } } /// /// Creates a new resized bitmap. /// /// The source context. /// The width of the source pixels. /// The height of the source pixels. /// The new desired width. /// The new desired height. /// The interpolation method that should be used. /// A new bitmap that is a resized version of the input. internal static int[] Resize(BitmapContext srcContext, int widthSource, int heightSource, int width, int height, Interpolation interpolation) { return Resize(srcContext.Pixels, widthSource, heightSource, width, height, interpolation); } /// /// Creates a new resized bitmap. /// /// The source pixels. /// The width of the source pixels. /// The height of the source pixels. /// The new desired width. /// The new desired height. /// The interpolation method that should be used. /// A new bitmap that is a resized version of the input. #if WPF internal static int[] Resize(int* pixels, int widthSource, int heightSource, int width, int height, Interpolation interpolation) #else internal static int[] Resize(int[] pixels, int widthSource, int heightSource, int width, int height, Interpolation interpolation) #endif { var pd = new int[width * height]; var xs = (float)widthSource / width; var ys = (float)heightSource / height; float fracx, fracy, ifracx, ifracy, sx, sy, l0, l1, rf, gf, bf; int c, x0, x1, y0, y1; byte c1a, c1r, c1g, c1b, c2a, c2r, c2g, c2b, c3a, c3r, c3g, c3b, c4a, c4r, c4g, c4b; byte a, r, g, b; // Nearest Neighbor if (interpolation == Interpolation.NearestNeighbor) { var srcIdx = 0; for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { sx = x * xs; sy = y * ys; x0 = (int)sx; y0 = (int)sy; pd[srcIdx++] = pixels[y0 * widthSource + x0]; } } } // Bilinear else if (interpolation == Interpolation.Bilinear) { var srcIdx = 0; for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { sx = x * xs; sy = y * ys; x0 = (int)sx; y0 = (int)sy; // Calculate coordinates of the 4 interpolation points fracx = sx - x0; fracy = sy - y0; ifracx = 1f - fracx; ifracy = 1f - fracy; x1 = x0 + 1; if (x1 >= widthSource) { x1 = x0; } y1 = y0 + 1; if (y1 >= heightSource) { y1 = y0; } // Read source color c = pixels[y0 * widthSource + x0]; c1a = (byte)(c >> 24); c1r = (byte)(c >> 16); c1g = (byte)(c >> 8); c1b = (byte)(c); c = pixels[y0 * widthSource + x1]; c2a = (byte)(c >> 24); c2r = (byte)(c >> 16); c2g = (byte)(c >> 8); c2b = (byte)(c); c = pixels[y1 * widthSource + x0]; c3a = (byte)(c >> 24); c3r = (byte)(c >> 16); c3g = (byte)(c >> 8); c3b = (byte)(c); c = pixels[y1 * widthSource + x1]; c4a = (byte)(c >> 24); c4r = (byte)(c >> 16); c4g = (byte)(c >> 8); c4b = (byte)(c); // Calculate colors // Alpha l0 = ifracx * c1a + fracx * c2a; l1 = ifracx * c3a + fracx * c4a; a = (byte)(ifracy * l0 + fracy * l1); // Red l0 = ifracx * c1r + fracx * c2r; l1 = ifracx * c3r + fracx * c4r; rf = ifracy * l0 + fracy * l1; // Green l0 = ifracx * c1g + fracx * c2g; l1 = ifracx * c3g + fracx * c4g; gf = ifracy * l0 + fracy * l1; // Blue l0 = ifracx * c1b + fracx * c2b; l1 = ifracx * c3b + fracx * c4b; bf = ifracy * l0 + fracy * l1; // Cast to byte r = (byte)rf; g = (byte)gf; b = (byte)bf; // Write destination pd[srcIdx++] = (a << 24) | (r << 16) | (g << 8) | b; } } } return pd; } #endregion #region Rotate /// /// Rotates the bitmap in 90° steps clockwise and returns a new rotated WriteableBitmap. /// /// The WriteableBitmap. /// The angle in degrees the bitmap should be rotated in 90° steps clockwise. /// A new WriteableBitmap that is a rotated version of the input. internal static WriteableBitmap Rotate(this WriteableBitmap bmp, int angle) { using (var context = bmp.GetBitmapContext()) { // Use refs for faster access (really important!) speeds up a lot! var w = context.Width; var h = context.Height; var p = context.Pixels; var i = 0; WriteableBitmap result = null; angle %= 360; if (angle > 0 && angle <= 90) { result = BitmapFactory.New(h, w); using (var destContext = result.GetBitmapContext()) { var rp = destContext.Pixels; for (var x = 0; x < w; x++) { for (var y = h - 1; y >= 0; y--) { var srcInd = y * w + x; rp[i] = p[srcInd]; i++; } } } } else if (angle > 90 && angle <= 180) { result = BitmapFactory.New(w, h); using (var destContext = result.GetBitmapContext()) { var rp = destContext.Pixels; for (var y = h - 1; y >= 0; y--) { for (var x = w - 1; x >= 0; x--) { var srcInd = y * w + x; rp[i] = p[srcInd]; i++; } } } } else if (angle > 180 && angle <= 270) { result = BitmapFactory.New(h, w); using (var destContext = result.GetBitmapContext()) { var rp = destContext.Pixels; for (var x = w - 1; x >= 0; x--) { for (var y = 0; y < h; y++) { var srcInd = y * w + x; rp[i] = p[srcInd]; i++; } } } } else { result = bmp.Clone(); } return result; } } /// /// Rotates the bitmap in any degree returns a new rotated WriteableBitmap. /// /// The WriteableBitmap. /// Arbitrary angle in 360 Degrees (positive = clockwise). /// if true: keep the size, false: adjust canvas to new size /// A new WriteableBitmap that is a rotated version of the input. internal static WriteableBitmap RotateFree(this WriteableBitmap 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()) { iWidth = bmpContext.Width; iHeight = bmpContext.Height; if (crop) { newWidth = iWidth; newHeight = iHeight; } else { var 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; var bmBilinearInterpolation = BitmapFactory.New(newWidth, newHeight); using (var bilinearContext = bmBilinearInterpolation.GetBitmapContext()) { var newp = bilinearContext.Pixels; var oldp = bmpContext.Pixels; var 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; var clrTopLeft = oldp[iFloorY * oldw + iFloorX]; var clrTopRight = oldp[iFloorY * oldw + iCeilingX]; var clrBottomLeft = oldp[iCeilingY * oldw + iFloorX]; var 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; var 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; } } } #endregion #region Flip /// /// Flips (reflects the image) either vertical or horizontal. /// /// The WriteableBitmap. /// The flip mode. /// A new WriteableBitmap that is a flipped version of the input. internal static WriteableBitmap Flip(this WriteableBitmap bmp, FlipMode flipMode) { using (var context = bmp.GetBitmapContext()) { // Use refs for faster access (really important!) speeds up a lot! var w = context.Width; var h = context.Height; var p = context.Pixels; var i = 0; WriteableBitmap result = null; if (flipMode == FlipMode.Horizontal) { result = BitmapFactory.New(w, h); using (var destContext = result.GetBitmapContext()) { var rp = destContext.Pixels; for (var y = h - 1; y >= 0; y--) { for (var x = 0; x < w; x++) { var srcInd = y * w + x; rp[i] = p[srcInd]; i++; } } } } else if (flipMode == FlipMode.Vertical) { result = BitmapFactory.New(w, h); using (var destContext = result.GetBitmapContext()) { var rp = destContext.Pixels; for (var y = 0; y < h; y++) { for (var x = w - 1; x >= 0; x--) { var srcInd = y * w + x; rp[i] = p[srcInd]; i++; } } } } return result; } } #endregion #endregion } }