/// <summary> /// Determines a safe array limit for when looping image bytes. /// (Takes into account pixel depth) /// </summary> public static int GetSafeArrayLimitForImage(this BitmapData bitmapData, byte[] imageBytes) { bool isColor = bitmapData.IsColor(); int pixelDepth = bitmapData.GetPixelDepth(); return(GetSafeArrayLimitForImage(isColor, pixelDepth, imageBytes)); }
/// <summary> /// Applies color filter to image. /// (Safer if source image was compressed, may be additional bytes per row) /// </summary> /// <param name="imageBytes">Color image bytes to process</param> /// <param name="r">Red component to apply</param> /// <param name="g">Green component to apply</param> /// <param name="b">Blue component to apply</param> /// <param name="bmpData">Image dimension info</param> public static void ApplyFilterDirectRGB(byte[] imageBytes, byte r, byte g, byte b, BitmapData bmpData) { if (!bmpData.IsColor()) { throw new ArgumentException("Image is not color, RGB filter cannot be applied."); } int stride = bmpData.Stride; int stridePadding = bmpData.GetStridePaddingLength(); int width = stride - stridePadding; int height = bmpData.Height; int limit = bmpData.GetSafeArrayLimitForImage(imageBytes); // May also have alpha byte int pixelDepth = bmpData.GetPixelDepth(); // Apply mask for each color pixel for (int y = 0; y < height; y++) { // Images may have extra bytes per row to pad for CPU addressing. // so need to ensure we traverse to the correct byte when moving between rows. // I.e. not divisible by 3 int offset = y * stride; for (int x = 0; x < width; x += pixelDepth) { int i = offset + x; if (i < limit) { // Red (LSB) imageBytes[i + 2] &= r; // Green imageBytes[i + 1] &= g; // Blue (MSB) imageBytes[i] &= b; } } } }
/// <summary> /// Any pixel values below threshold will be changed to 0 (black). /// Any pixel values above (or equal to) threshold will be changed to 255 (white). /// </summary> /// <param name="imageBytes">Image bytes</param> /// <param name="bitmapData">Pixel depth</param> /// <param name="threshold">Threshold value</param> private static void ApplyDirect(byte[] imageBytes, BitmapData bitmapData, byte threshold) { if (bitmapData.IsColor()) { int pixelDepth = bitmapData.GetPixelDepth(); int stride = bitmapData.Stride; int width = bitmapData.GetStrideWithoutPadding(); int height = bitmapData.Height; int limit = bitmapData.GetSafeArrayLimitForImage(imageBytes); // Exclude alpha/transparency byte when averaging int thresholdByteLength = Math.Min(pixelDepth, Constants.PixelDepthRGB); int pixelSum; bool belowThreshold; // Find distribution of pixels at each level for (int y = 0; y < height; y++) { // Images may have extra bytes per row to pad for CPU addressing. // so need to ensure we traverse to the correct byte when moving between rows. int offset = y * stride; for (int x = 0; x < width; x += pixelDepth) { int i = offset + x; if (i < limit) { pixelSum = 0; // Sum each pixel component for (int j = 0; j < thresholdByteLength && i + j < imageBytes.Length; j++) { pixelSum += imageBytes[i + j]; } // Compare average to threshold belowThreshold = (pixelSum / thresholdByteLength) < threshold; // Apply threshold for (int j = 0; j < thresholdByteLength && i + j < imageBytes.Length; j++) { imageBytes[i + j] = belowThreshold ? byte.MinValue : byte.MaxValue; } } else { break; } } } } else { // Apply threshold value to image for (int i = 0; i < imageBytes.Length; i++) { imageBytes[i] = imageBytes[i] < threshold ? byte.MinValue : byte.MaxValue; } } }
/// <summary> /// Stretches image contrast to specified min/max values. /// </summary> /// <param name="imageBytes">Image bytes (Grayscale/RGB)</param> /// <param name="bmpData">Info on image properties</param> /// <param name="min">Minimum contrast value</param> /// <param name="max">Maximum contrast value</param> public static void StretchDirect(byte[] imageBytes, BitmapData bmpData, byte min, byte max) { int pixelDepth = bmpData.GetPixelDepth(); bool isColor = bmpData.IsColor(); // Initialize with opposite values byte highest = byte.MinValue; byte lowest = byte.MaxValue; byte val; // Bitmap converted from jpeg can potentially have array with extra odd byte. int limit = bmpData.GetSafeArrayLimitForImage(imageBytes); ////////////////////////////////////// // First find current contrast range ////////////////////////////////////// for (int i = 0; i < limit; i += pixelDepth) { if (isColor) { val = (byte)((imageBytes[i] + imageBytes[i + 1] + imageBytes[i + 2]) / 3); } else { val = imageBytes[i]; } // Check contrast ranges if (val < lowest) { lowest = val; } if (val > highest) { highest = val; } } // Constant for loop double maxMinusMin = max - min; // Exclude alpha/transparency byte int pixelDepthWithoutAlpha = Math.Min(pixelDepth, Constants.PixelDepthRGB); ////////////////////////////////////// // Now contrast stretch image ////////////////////////////////////// for (int i = 0; i < limit; i += pixelDepth) { for (int j = 0; j < pixelDepthWithoutAlpha; j++) { // Contrast-stretch value val = (byte)(((imageBytes[i + j] - lowest) * (maxMinusMin / (highest - lowest))) + min); // Check limits if (val < lowest) { val = min; } else if (val > highest) { val = max; } imageBytes[i + j] = val; } } }
/// <summary> /// Applies convolution matrix/kernel to image. /// i.e. edge detection. /// </summary> /// <param name="imageBytes">Image bytes (Grayscale/RGB)</param> /// <param name="bitmapData">Info on image properties</param> /// <param name="kernelMatrix">Kernel matrix</param> /// <returns>byte array with kernel applied</returns> public static byte[] ApplyKernel(byte[] imageBytes, BitmapData bitmapData, int[,] kernelMatrix) { // Matrix is typically square, but may not be int matrixLenX = kernelMatrix.GetLength(0); int matrixLenY = kernelMatrix.GetLength(1); // Position in matrix where convolution value assigned // (Use center for 3x3 etc.) int matrixValX = (matrixLenX - 1) / 2; int matrixValY = (matrixLenY - 1) / 2; // Get bitmap info int bmpWidth = bitmapData.Width; int bmpHeight = bitmapData.Height; int bmpStride = Math.Abs(bitmapData.Stride); int pixelDepth = bitmapData.GetPixelDepth(); bool isColor = bitmapData.IsColor(); // Reused often int bmpX, bmpY, mX, mY, iX, iY; int newValue; int pixelIndex; int iYbyStride; // 3 bits per pixel on color image (RGB) int maxChannels = isColor ? 3 : 1; // Create separate array so previous convolution values // don't interfere with later values as traverse image byte[] results = new byte[imageBytes.Length]; // Perform convolution for each color channel for (int colorChannel = 0; colorChannel < maxChannels; colorChannel++) { // Move through rows for (bmpY = 0; bmpY < bmpHeight; bmpY++) { // Move through columns for (bmpX = 0; bmpX < bmpWidth; bmpX++) { // Check if edge case if ((bmpY + matrixLenY > bmpHeight) || (bmpX + matrixLenX > bmpWidth)) { // Assign non-value, black newValue = 0; // Get target pixel for value pixelIndex = (bmpX * pixelDepth) + (bmpY * bmpStride); } else { // Reset newValue = 0; // Move through matrix to calculate new value for (mY = 0; mY < matrixLenY; mY++) { // Image Y coordinate with respect to matrix. iY = bmpY + mY; iYbyStride = iY * bmpStride; for (mX = 0; mX < matrixLenX; mX++) { // Image X coordinate with respect to matrix. iX = bmpX + mX; // Get R, G, or B index pixelIndex = (iX * pixelDepth) + iYbyStride + colorChannel; // Add convolution value for pixel newValue += kernelMatrix[mX, mY] * imageBytes[pixelIndex]; } } // Check value within range // (Some filter values are negative) if (newValue < 0) { newValue = 0; } else if (newValue > byte.MaxValue) { // Value oversaturated newValue = byte.MaxValue; } // Get target pixel for value pixelIndex = (bmpX + matrixValX) * pixelDepth; pixelIndex += (bmpY + matrixValY) * bmpStride; } // Adjust for color channel pixelIndex += colorChannel; // Update pixel value results[pixelIndex] = (byte)newValue; } } } // Return new values return(results); }