/// <summary> /// BoxBlurHorizontal is a private helper method for the BoxBlur, only for IFastBitmaps with alpha channel /// </summary> /// <param name="unmanagedBitmap">UnmanagedBitmap</param> /// <param name="range">Range must be odd!</param> private static void BoxBlurHorizontal(UnmanagedBitmap <Bgr32> unmanagedBitmap, int range) { var halfRange = range / 2; Parallel.For(0, unmanagedBitmap.Height, y => { unsafe { var rgb32 = unmanagedBitmap[y]; Span <Bgr32> averages = stackalloc Bgr32[range]; var r = 0; var g = 0; var b = 0; var hits = halfRange; for (var x = 0; x < halfRange; x++) { ref Bgr32 color = ref rgb32[x]; r += color.R; g += color.G; b += color.B; } for (var x = 0; x < unmanagedBitmap.Width; x++) { var leftSide = x - halfRange - 1; if (leftSide >= 0) { // Get value at the left side of range ref Bgr32 color = ref rgb32[leftSide]; r -= color.R; g -= color.G; b -= color.B; hits--; } var rightSide = x + halfRange; if (rightSide < unmanagedBitmap.Width) { ref Bgr32 color = ref rgb32[rightSide]; r += color.R; g += color.G; b += color.B; hits++; } ref Bgr32 average = ref averages[x % range]; average.R = (byte)(r / hits); average.G = (byte)(g / hits); average.B = (byte)(b / hits); if (leftSide >= 0) { // Now we can write the value from the calculated avarages var readLocation = (leftSide % range); rgb32[leftSide] = averages[readLocation]; } }
/// <summary> /// Apply BoxBlur to the UnmanagedBitmap /// </summary> /// <param name="unmanagedBitmap">UnmanagedBitmap</param> /// <param name="range">Must be even!</param> public static void ApplyBoxBlur(this UnmanagedBitmap <Bgra32> unmanagedBitmap, int range) { // Range must be odd! if ((range & 1) == 0) { range++; } if (range <= 1) { return; } // Box blurs are frequently used to approximate a Gaussian blur. // By the central limit theorem, if applied 3 times on the same image, a box blur approximates the Gaussian kernel to within about 3%, yielding the same result as a quadratic convolution kernel. // This might be true, but the GDI+ BlurEffect doesn't look the same, a 2x blur is more simular and we only make 2x Box-Blur. // (Might also be a mistake in our blur, but for now it looks great) BoxBlurHorizontal(unmanagedBitmap, range); BoxBlurVertical(unmanagedBitmap, range); BoxBlurHorizontal(unmanagedBitmap, range); BoxBlurVertical(unmanagedBitmap, range); }
/// <summary> /// Use "Scale2x" algorithm to produce bitmap from the original. /// Every pixel from input texture produces 4 output pixels, for more details check out http://scale2x.sourceforge.net/algorithm.html /// </summary> /// <param name="sourceBitmap">UnmanagedBitmap to scale 2x</param> /// <returns>UnmanagedBitmap</returns> public static UnmanagedBitmap <TPixelLayout> Scale2X <TPixelLayout>(this UnmanagedBitmap <TPixelLayout> sourceBitmap) where TPixelLayout : unmanaged { if (Marshal.SizeOf <TPixelLayout>() != 4) { throw new NotSupportedException("Only 4 byte unmanaged structs are supported."); } var destinationBitmap = new UnmanagedBitmap <TPixelLayout>(sourceBitmap.Width << 1, sourceBitmap.Height << 1); var sourceWidth = sourceBitmap.Width; var destinationWidth = destinationBitmap.Width; ReadOnlySpan <uint> sourceSpan = MemoryMarshal.Cast <TPixelLayout, uint>(sourceBitmap.Span); var destinationSpan = MemoryMarshal.Cast <TPixelLayout, uint>(destinationBitmap.Span); unsafe { for (var y = 0; y < sourceBitmap.Height; y++) { var sourceYOffset = y * sourceWidth; var destinationYOffset = (y << 1) * destinationWidth; for (var x = 0; x < sourceWidth; x++) { var sourceOffset = sourceYOffset + x; ref readonly uint colorE = ref sourceSpan[sourceOffset];
/// <summary> /// Use "Scale2x" algorithm to produce bitmap from the original. /// Every pixel from input texture produces 4 output pixels, for more details check out http://scale2x.sourceforge.net/algorithm.html /// </summary> /// <param name="sourceBitmap">UnmanagedBitmap to scale 2x</param> /// <returns>UnmanagedBitmap</returns> public static UnmanagedBitmap <TPixelLayout> Scale2XReference <TPixelLayout>(this UnmanagedBitmap <TPixelLayout> sourceBitmap) where TPixelLayout : unmanaged { if (Marshal.SizeOf <TPixelLayout>() != 4) { throw new NotSupportedException("Only 4 byte unmanaged structs are supported."); } var destinationBitmap = new UnmanagedBitmap <TPixelLayout>(sourceBitmap.Width << 1, sourceBitmap.Height << 1); var colorBOffset = -sourceBitmap.Width; var colorHOffset = sourceBitmap.Width; ReadOnlySpan <int> sourceSpan = MemoryMarshal.Cast <TPixelLayout, int>(sourceBitmap.Span); var destinationSpan = MemoryMarshal.Cast <TPixelLayout, int>(destinationBitmap.Span); unsafe { var colors = stackalloc int[5]; var colorsE = stackalloc int[4]; for (var y = 0; y < sourceBitmap.Height; y++) { var offset = y * sourceBitmap.Width; for (var x = 0; x < sourceBitmap.Width; x++) { var xOffset = offset + x; colors[ColorE] = sourceSpan[xOffset]; if (y != 0) { colors[ColorB] = sourceSpan[xOffset + colorBOffset]; } else { colors[ColorB] = colors[ColorE]; } if (y != sourceBitmap.Height - 1) { colors[ColorH] = sourceSpan[xOffset + colorHOffset]; } else { colors[ColorH] = colors[ColorE]; } if (x > 0) { colors[ColorD] = sourceSpan[xOffset - 1]; } else { colors[ColorD] = colors[ColorE]; } if (x < sourceBitmap.Width - 1) { colors[ColorF] = sourceSpan[xOffset + 1]; } else { colors[ColorF] = colors[ColorE]; } if (colors[ColorB] != colors[ColorH] && colors[ColorD] != colors[ColorF]) { colorsE[0] = colors[ColorD] == colors[ColorB] ? colors[ColorD] : colors[ColorE]; colorsE[1] = colors[ColorB] == colors[ColorF] ? colors[ColorF] : colors[ColorE]; colorsE[2] = colors[ColorD] == colors[ColorH] ? colors[ColorD] : colors[ColorE]; colorsE[3] = colors[ColorH] == colors[ColorF] ? colors[ColorF] : colors[ColorE]; } else { colorsE[0] = colors[ColorE]; colorsE[1] = colors[ColorE]; colorsE[2] = colors[ColorE]; colorsE[3] = colors[ColorE]; } var destinationOffset = (x << 1) + ((y << 1) * destinationBitmap.Width); destinationSpan[destinationOffset] = colorsE[0]; destinationSpan[destinationOffset + 1] = colorsE[1]; destinationSpan[destinationOffset + destinationBitmap.Width] = colorsE[2]; destinationSpan[destinationOffset + 1 + destinationBitmap.Width] = colorsE[3]; } } } return(destinationBitmap); }