/// <summary> /// Iterates over the <see cref="FastBitmap"/>, scaling the magnitude of each pixel by the supplied <paramref name="factor"/>. Channel values are capped at 255. /// </summary> /// <param name="factor"> /// The factor by which to scale the magnitude of each pixel. /// </param> /// <returns> /// A <see cref="FastBitmap"/> which has been scaled by the supplied <paramref name="factor"/>. /// </returns> /// <example> /// ExampleFastBitmap.ScaleBy(255) results in no scaling. /// ExampleFastBitmap.ScaleBy(128) doubles all channels. /// ExampleFastBitmap.ScaleBy(64) quadruples all channels. /// </example> public unsafe FastBitmap ScaleBy(ushort factor) { FastBitmap buffer = new FastBitmap(this.Content, this.bitsPerPixel); double actualFactor = (255D / (double)factor); int bufferHeight = buffer.Content.Height; int bufferWidth = buffer.Content.Width; FastBitmap.Operation(buffer.Content, (data, scan0) => { Parallel.For(0, bufferHeight, yPos => { int localWidth = bufferWidth; Parallel.For(0, localWidth, xPos => { byte *valR = FastBitmap.PixelPointer(scan0, xPos, yPos, data.Stride, buffer.bitsPerPixel); byte *valG = valR + 1; byte *valB = valR + 2; int newR = (int)((double)*valR * actualFactor); int newG = (int)((double)*valG * actualFactor); int newB = (int)((double)*valB * actualFactor); *valR = (byte)(newR > 255 ? 255 : newR); *valG = (byte)(newG > 255 ? 255 : newG); *valB = (byte)(newB > 255 ? 255 : newB); }); }); }); return(buffer); }
/// <summary> /// Raises the value of each pixel in the <see cref="FastBitmap"/>'s channels to the supplied <paramref name="power"/>. Channel values are capped at 255. /// </summary> /// <param name="power"> /// The power to raise each pixel's channel to. /// </param> /// <returns> /// A <see cref="FastBitmap"/> whose pixels are equal to the original's to the supplied <paramref name="power"/>. /// </returns> public unsafe FastBitmap Pow(ushort power) { FastBitmap buffer = new FastBitmap(this.Content, this.bitsPerPixel); int bufferHeight = buffer.Content.Height; int bufferWidth = buffer.Content.Width; FastBitmap.Operation(buffer.Content, (data, scan0) => { Parallel.For(0, bufferHeight, yPos => { int localWidth = bufferWidth; Parallel.For( 0, localWidth, xPos => { byte *valR = FastBitmap.PixelPointer(scan0, xPos, yPos, data.Stride, buffer.bitsPerPixel); byte *valG = valR + 1; byte *valB = valR + 2; int newR = (int)Math.Pow(*valR, power); int newG = (int)Math.Pow(*valG, power); int newB = (int)Math.Pow(*valB, power); *valR = (byte)(newR > 255 ? 255 : newR); *valG = (byte)(newG > 255 ? 255 : newG); *valB = (byte)(newB > 255 ? 255 : newB); }); }); }); return(buffer); }
/// <summary> /// Converts the <see cref="FastBitmap"/> to grayscale. /// </summary> /// <returns> /// A <see cref="FastBitmap"/> whose contents are the same as the current <see cref="FastBitmap"/>, but in grayscale. /// </returns> public unsafe FastBitmap ToGrayscale() { FastBitmap buffer = new FastBitmap(this.Content, this.bitsPerPixel); // TODO: Resolve whether checking the bpp has any impact - it looks like the Bitmap class // will read in bitmaps of arbitrary color depth, but expose them as having 32bpp. Since // we don't save the outputs, the behaviour is equivalent. ToGrayscaleOperation grayscale = null; switch (buffer.bitsPerPixel) { case 32: grayscale = (scan0, yPos, stride, width) => { for (int xPos = 0; xPos < width; xPos++) { byte *pixel = FastBitmap.PixelPointer(scan0, xPos, yPos, stride, buffer.bitsPerPixel); int valR = *pixel; int valG = *(pixel + 1); int valB = *(pixel + 2); byte avg = (byte)((double)(valR + valG + valB) / 3D); *pixel = avg; *(pixel + 1) = avg; *(pixel + 2) = avg; } }; break; default: throw new NotImplementedException(Resources.UnsupportedImageColorDepth); } int bufferHeight = buffer.Content.Height; int bufferWidth = buffer.Content.Width; FastBitmap.Operation(buffer.Content, (data, scan0) => { Parallel.For(0, bufferHeight, yPos => { int localWidth = bufferWidth; grayscale(scan0, yPos, data.Stride, localWidth); }); }); return(buffer); }
/// <summary> /// Returns the largest magnitude in the <see cref="FastBitmap"/>. /// </summary> /// <returns> /// The largest magnitude in the <see cref="FastBitmap"/>. /// </returns> public unsafe ushort Max() { object theLock = new object(); ushort returnValue = 0; using (FastBitmap asGrayscale = this.ToGrayscale()) { int bpp = asGrayscale.bitsPerPixel; FastBitmap.Operation(asGrayscale.Content, (data, scan0) => { Parallel.For(0, data.Height, yPos => { byte localMax = 0; for (int xPos = 0; xPos < data.Width; xPos++) { byte current = *FastBitmap.PixelPointer(scan0, xPos, yPos, data.Stride, bpp); if (current > localMax) { localMax = current; } } lock (theLock) { if (localMax > returnValue) { returnValue = localMax; } } }); }); } return(returnValue); }
/// <summary> /// Performs Sobel edge detection on the <see cref="FastBitmap"/>. /// </summary> /// <param name="computeThetas"> /// True if the thetas should be computed in addition to the resulting <see cref="FastBitmap"/>, and false otherwise. Defaults to false. /// </param> /// <returns> /// A named-field tuple containing the resulting Sobel-filtered <see cref="FastBitmap"/>, and the calculated thetas. If <paramref name="computeThetas"/> was false, then thetas will be null. /// </returns> public unsafe (FastBitmap magnitude, double[, ] thetas) Sobel(bool computeThetas = false) { FastBitmap horizontal = this.KernelOperation(Kernel.HorizontalSobel); FastBitmap vertical = this.KernelOperation(Kernel.VerticalSobel); FastBitmap result = new FastBitmap(horizontal.Content.Width, horizontal.Content.Height); FastBitmap.Operation(result.Content, (resultData, resultScan0) => { FastBitmap.Operation(horizontal.Content, (horizontalData, horizontalScan0) => { FastBitmap.Operation(vertical.Content, (verticalData, verticalScan0) => { for (int yPos = 0; yPos < horizontal.Content.Height; yPos++) { for (int xPos = 0; xPos < horizontal.Content.Width; xPos++) { byte *horizontalR = FastBitmap.PixelPointer( horizontalScan0, xPos, yPos, horizontalData.Stride, horizontal.bitsPerPixel); byte *horizontalG = horizontalR + 1; byte *horizontalB = horizontalR + 2; byte *verticalR = FastBitmap.PixelPointer( verticalScan0, xPos, yPos, verticalData.Stride, vertical.bitsPerPixel); byte *verticalG = verticalR + 1; byte *verticalB = verticalR + 2; byte *resultR = FastBitmap.PixelPointer( resultScan0, xPos, yPos, resultData.Stride, result.bitsPerPixel); byte *resultG = resultR + 1; byte *resultB = resultR + 2; *resultR = (byte)(255 * Math.Sqrt(*horizontalR * *horizontalR + *verticalR * *verticalR) / 360); *resultG = (byte)(255 * Math.Sqrt(*horizontalG * *horizontalG + *verticalG * *verticalG) / 360); *resultB = (byte)(255 * Math.Sqrt(*horizontalB * *horizontalB + *verticalB * *verticalB) / 360); *(resultR + 3) = 255; } } }); }); }, ImageLockMode.WriteOnly); double[,] thetas = null; if (computeThetas) { FastBitmap.Operation(horizontal.Content, (horizontalData, horizontalScan0) => { FastBitmap.Operation(vertical.Content, (verticalData, verticalScan0) => { for (int yPos = 0; yPos < horizontal.Content.Height; yPos++) { for (int xPos = 0; xPos < horizontal.Content.Width; xPos++) { thetas[xPos, yPos] = Math.Atan2( *FastBitmap.PixelPointer( verticalScan0, xPos, yPos, verticalData.Stride, vertical.bitsPerPixel), *FastBitmap.PixelPointer( horizontalScan0, xPos, yPos, horizontalData.Stride, horizontal.bitsPerPixel)); } } }); }); } return(result, thetas); }
/// <summary> /// Performs a Radon transformation on the <see cref="FastBitmap"/>. /// </summary> /// <param name="numberOfAngles"> /// The number of angles to consider. Should be a power of 2. /// </param> /// <returns> /// A <see cref="ProjectionResult"/> representing the produced transform. /// </returns> public unsafe ProjectionResult RadonTransform(int numberOfAngles) { if (numberOfAngles <= 0) { throw new ArgumentException(Resources.InvalidNumberOfAnglesSpecified, nameof(numberOfAngles)); } if ((numberOfAngles & (numberOfAngles - 1)) != 0) { throw new ArgumentException(Resources.NumberOfAnglesShouldBePowerOfTwo, nameof(numberOfAngles)); } FastBitmap buffer = new FastBitmap(new Bitmap(this.Content.Width, this.Content.Height)); using (Graphics graphics = Graphics.FromImage(buffer.Content)) { graphics.FillRectangle(Brushes.Black, 0, 0, buffer.Content.Width, buffer.Content.Height); } int[] pixelsPerLine = new int[numberOfAngles]; int height = buffer.Content.Height; int width = buffer.Content.Width; int diff = Math.Max(height, width); double xCenter = (double)width / 2f; double yCenter = (double)height / 2f; int xOffset = (int)(xCenter + FastBitmap.RoundingFactor(xCenter)); int yOffset = (int)(yCenter + FastBitmap.RoundingFactor(yCenter)); FastBitmap.Operation(this.Content, (data, scan0) => { FastBitmap.Operation(buffer.Content, (subData, subScan0) => { for (int k = 0; k < (numberOfAngles / 4) + 1; k++) { double theta = k * Math.PI / numberOfAngles; double alpha = Math.Tan(theta); for (int x = 0; x < diff; x++) { double y = alpha * (x - xOffset); int yd = (int)(y + FastBitmap.RoundingFactor(y)); if ((yd + yOffset >= 0) && (yd + yOffset < height) && (x < width)) { // Originally: *ptr_radon_map->data(k, x) = img(x, yd + yOffset); *FastBitmap.PixelPointer(subScan0, k, x, subData.Stride, buffer.bitsPerPixel) = *FastBitmap.PixelPointer(scan0, x, yd + yOffset, data.Stride, this.bitsPerPixel); pixelsPerLine[k]++; } if ((yd + xOffset >= 0) && (yd + xOffset < width) && (k != numberOfAngles / 4) && (x < height)) { // Originally: *ptr_radon_map->data(numberOfAngles / 2 - k, x) = img(yd + xOffset, x); *FastBitmap.PixelPointer( subScan0, (numberOfAngles / 2) - k, x, subData.Stride, buffer.bitsPerPixel) = *FastBitmap.PixelPointer(scan0, yd + xOffset, x, data.Stride, this.bitsPerPixel); pixelsPerLine[(numberOfAngles / 2) - k]++; } } } int j = 0; for (int k = 3 * numberOfAngles / 4; k < numberOfAngles; k++) { double theta = k * Math.PI / numberOfAngles; double alpha = Math.Tan(theta); for (int x = 0; x < diff; x++) { double y = alpha * (x - xOffset); int yd = (int)(y + FastBitmap.RoundingFactor(y)); if ((yd + yOffset >= 0) && (yd + yOffset < height) && (x < width)) { // Originally: *ptr_radon_map->data(k, x) = img(x, yd + yOffset); *FastBitmap.PixelPointer(subScan0, k, x, subData.Stride, buffer.bitsPerPixel) = *FastBitmap.PixelPointer(scan0, x, yd + yOffset, data.Stride, this.bitsPerPixel); pixelsPerLine[k]++; } if ((yOffset - yd >= 0) && (yOffset - yd < width) && ((2 * yOffset) - x >= 0) && ((2 * yOffset) - x < height) && (k != (3 * numberOfAngles) / 4)) { // Originally: *ptr_radon_map->data(k - j, x) = img(-yd + yOffset, -(x - yOffset) + yOffset); *FastBitmap.PixelPointer(subScan0, k - j, x, subData.Stride, buffer.bitsPerPixel) = *FastBitmap.PixelPointer( scan0, -yd + yOffset, -(x - yOffset) + yOffset, data.Stride, this.bitsPerPixel); pixelsPerLine[k - j]++; } } j += 2; } }); }); return(new ProjectionResult(buffer, pixelsPerLine)); }
/// <summary> /// Performs a Canny Edge Detect on the <see cref="FastBitmap"/>. /// </summary> /// <returns> /// A <see cref="FastBitmap"/> whose contents represent the edges of the current <see cref="FastBitmap"/>. /// </returns> public unsafe FastBitmap EdgeDetect() { (FastBitmap magnitude, double[,] thetas)edgeDetect = this.Sobel(computeThetas: true); FastBitmap buffer = edgeDetect.magnitude; double[,] angles = edgeDetect.thetas; FastBitmap output = new FastBitmap(new Bitmap(buffer.Content.Width - 2, buffer.Content.Height - 2)); FastBitmap.Operation(output.Content, (outputData, outputScan0) => { FastBitmap.Operation(buffer.Content, (bufferData, bufferScan0) => { int localWidth = output.Content.Width + 1; int localBpp = buffer.bitsPerPixel; for (int yPos = 1; yPos < output.Content.Height + 1; yPos++) { for (int xPos = 1; xPos < localWidth; xPos++) { int prevX = 0; int prevY = 0; int nextX = 0; int nextY = 0; switch (angles[xPos - 1, yPos - 1]) { case 0: prevX = -1; nextX = 1; break; case 1: prevX = -1; nextX = 1; prevY = 1; nextY = -1; break; case 2: prevY = -1; nextY = 1; break; case 3: prevX = -1; nextX = 1; prevY = -1; nextY = 1; break; } byte *outputA = FastBitmap.PixelPointer(outputScan0, xPos - 1, yPos - 1, outputData.Stride, output.bitsPerPixel) + 3; *outputA = 255; byte *currentR = FastBitmap.PixelPointer(bufferScan0, xPos, yPos, bufferData.Stride, localBpp); byte *previousR = FastBitmap.PixelPointer(bufferScan0, xPos + prevX, yPos + prevY, bufferData.Stride, buffer.bitsPerPixel); byte *nextR = FastBitmap.PixelPointer(bufferScan0, xPos + nextX, yPos + nextY, bufferData.Stride, buffer.bitsPerPixel); if (*currentR > *previousR && *currentR > *nextR) { *(outputA - 3) = 255; *(outputA - 2) = 255; *(outputA - 1) = 255; } else { *currentR = 0; *(currentR + 1) = 0; *(currentR + 2) = 0; } } } }); }); return(output); }