/// <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;
                }
            }
        }
Beispiel #5
0
        /// <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);
        }