1
\$\begingroup\$

I have written the following routines.

Do you know of any better algorithm?

Is there any faster way to pad an image for image processing?

How can I optimize them for better performance?

public class ImagePadder
{
    public static Bitmap Pad(Bitmap image, int newWidth, int newHeight)
    {
        int width = image.Width;
        int height = image.Height;
        /*
         It is always guaranteed that,

                width < newWidth

                    and

                height < newHeight                  
         */
        if ((width < newWidth && height < newHeight)
                || (width<newWidth && height == newHeight)
                || (width==newWidth && height < newHeight))
        {
            Bitmap paddedImage = Grayscale.CreateGrayscaleImage(newWidth, newHeight);

            BitmapLocker inputImageLocker = new BitmapLocker(image);
            BitmapLocker paddedImageLocker = new BitmapLocker(paddedImage);

            inputImageLocker.Lock();
            paddedImageLocker.Lock();

            int startPointX = (int)Math.Ceiling((double)(newWidth - width) / (double)2) - 1;
            int startPointY = (int)Math.Ceiling((double)(newHeight - height) / (double)2) - 1;

            for (int y = startPointY; y < (startPointY + height) ; y++)
            {
                for (int x = startPointX; x < (startPointX + width) ; x++)
                {
                    int xxx = x - startPointX;
                    int yyy = y - startPointY;

                    paddedImageLocker.SetPixel(x, y, inputImageLocker.GetPixel(xxx, yyy));



                    string str = string.Empty;
                }
            }

            paddedImageLocker.Unlock();
            inputImageLocker.Unlock();

            return paddedImage;
        }
        else if (width == newWidth && height == newHeight)
        {
            return image;
        }
        else
        {
            throw new Exception("Pad() -- threw an exception");
        }
    }

    public static double[,] Pad(double[,] image, int newWidth, int newHeight)
    {
        int width = image.GetLength(0);
        int height = image.GetLength(1);
        /*
         It is always guaranteed that,

                width < newWidth

                    and

                height < newHeight                  
         */
        if ((width < newWidth && height < newHeight)
                || (width < newWidth && height == newHeight)
                || (width == newWidth && height < newHeight))
        {
            double[,] resizedImage = new double[newWidth, newHeight];

            double color = 0.0;

            Grayscale.Fill(resizedImage, color);

            int startPointX = ((newWidth - width) / 2)-1;
            int startPointY = ((newHeight - height) / 2)-1;

            for (int y = startPointY; y < startPointY + height; y++)
            {
                for (int x = startPointX; x < startPointX + width; x++)
                {
                    int xxx = x - startPointX;
                    int yyy = y - startPointY;
                    resizedImage[x, y] = image[xxx, yyy];
                }
            }

            return resizedImage;
        }
        else if (width == newWidth && height == newHeight)
        {
            return image;
        }
        else
        {
            throw new Exception("Pad() -- threw an exception");
        }
    } 

    //public static int[,] Pad(int[,] image, int newWidth, int newHeight)
    //{
    //    int width = image.GetLength(0);
    //    int height = image.GetLength(1);

    //    if ((width == height) && (width < newWidth && height < newHeight))
    //    {
    //        int[,] resizedImage = new int[width, height];

    //        int padValue = Color.Black.ToArgb();

    //        for (int j = 0; j < height; j++)
    //        {
    //            for (int i = 0; i < width; i++)
    //            {
    //                resizedImage[j,i] = padValue;
    //            }
    //        }

    //        if (newWidth != width || newHeight != height)
    //        {
    //            int startPointX = (newWidth - width) / 2;
    //            int startPointY = (newHeight - height) / 2;

    //            for (int y = startPointY; y < startPointY + height; y++)
    //            {
    //                for (int x = startPointX; x < startPointX + width; x++)
    //                {
    //                    int temp = image[y - startPointY, x - startPointX];
    //                    resizedImage[y, x] = temp;
    //                }
    //            }

    //            string str = string.Empty;
    //        }
    //        else
    //        {
    //            for (int j = 0; j < height; j++)
    //            {
    //                for (int i = 0; i < width; i++)
    //                {
    //                    resizedImage[j,i] = image[j,i];
    //                }
    //            }
    //        }

    //        return resizedImage;
    //    }
    //    else
    //    {
    //        throw new Exception("Pad() - threw an exception!");
    //    }
    //}

    #region public static Complex[,] Pad(Complex[,] image, int newMaskWidth, int newMaskHeight, int value)
    /// <summary>
    /// Pad an image to make it bigger in dimensions.
    /// </summary>
    /// <param name="image">image to be padded</param>
    /// <param name="newMaskWidth">width to be attained</param>
    /// <param name="newMaskHeight">height to be attained</param>
    /// <param name="value">the value to be used as a pad</param>
    /// <returns></returns>
    //public static Complex[,] Pad(Complex[,] image, int newMaskWidth, int newMaskHeight, int value)
    //{
    //    int width = image.GetLength(0);
    //    int height = image.GetLength(1);

    //    if ((width == height) && (width < newMaskWidth && height < newMaskHeight))
    //    {
    //        Complex[,] newMask = new Complex[newMaskWidth, newMaskHeight];

    //        //Creating the padding mask
    //        for (int y = 0; y < newMaskHeight; y++)
    //        {
    //            for (int x = 0; x < newMaskWidth; x++)
    //            {
    //                newMask[y,x] = new Complex(value, value);
    //            }
    //        }

    //        if (newMaskWidth > width && newMaskHeight > height)
    //        {
    //            int startPointX = (newMaskWidth  - width)/ 2;
    //            int startPointY = (newMaskHeight - height)/ 2;

    //            for (int y = startPointY; y < startPointY + height; y++)
    //            {
    //                for (int x = startPointX; x < startPointX + width; x++)
    //                {
    //                    newMask[y,x] = new Complex(image[y - startPointY, x - startPointX].Real, image[y - startPointY, x - startPointX].Imaginary);
    //                }

    //                Console.WriteLine();
    //            }

    //            string str = string.Empty;
    //        }
    //        else
    //        {
    //            for (int y = 0; y < newMaskHeight; y++)
    //            {
    //                for (int x = 0; x < newMaskWidth; x++)
    //                {
    //                    newMask[y, x] = new Complex(image[y, x].Real, image[y, x].Imaginary);
    //                }
    //            }
    //        }

    //        return newMask;
    //    }
    //    else
    //    {
    //        throw new Exception("Pad() - threw an exception!");
    //    }
    //} 
    #endregion
}

.

Here is BitmapLocker class,

public class BitmapLocker
{
    //private properties
    Bitmap _bitmap = null;
    bool _isLocked = false;
    BitmapData _bitmapData = null;
    private byte[] _imageData = null;

    //public properties
    public IntPtr IntegerPointer { get; private set; }
    public int Width { get { return _bitmap.Width; } }
    public int Height { get { return _bitmap.Height; } }
    public int Stride { get { return _bitmapData.Stride; } }
    public int ColorDepth { get { return Bitmap.GetPixelFormatSize(_bitmap.PixelFormat); } }
    public int Channels { get { return ColorDepth / 8; } }
    public int PaddingOffset { get { return _bitmapData.Stride - (_bitmap.Width * Channels); } }
    public PixelFormat ImagePixelFormat { get { return _bitmap.PixelFormat; } }
    public bool IsGrayscale { get { return Grayscale.IsGrayscale(_bitmap); } }

    //Constructor
    public BitmapLocker(Bitmap source)
    {
        IntegerPointer = IntPtr.Zero;
        this._bitmap = source;
    }

    /// Lock bitmap
    public void Lock()
    {
        if (_isLocked == false)
        {
            try
            {
                // Lock bitmap (so that no movement of data by .NET framework) and return bitmap data
                _bitmapData = _bitmap.LockBits(
                                                new Rectangle(0, 0, _bitmap.Width, _bitmap.Height),
                                                ImageLockMode.ReadWrite,
                                                _bitmap.PixelFormat);

                // Create byte array to copy pixel values
                int noOfBitsNeededForStorage = _bitmapData.Stride * _bitmapData.Height;

                int noOfBytesNeededForStorage = noOfBitsNeededForStorage / 8;

                _imageData = new byte[noOfBytesNeededForStorage * ColorDepth];//# of bytes needed for storage

                IntegerPointer = _bitmapData.Scan0;

                // Copy data from __IntegerPointer to PixelArray
                Marshal.Copy(IntegerPointer, _imageData, 0, _imageData.Length);

                _isLocked = true;
            }
            catch (Exception)
            {
                throw;
            }
        }
        else
        {
            throw new Exception("Bitmap is already locked.");
        }
    }

    /// Unlock bitmap
    public void Unlock()
    {
        if (_isLocked == true)
        {
            try
            {
                // Copy data from PixelArray to __IntegerPointer
                Marshal.Copy(_imageData, 0, IntegerPointer, _imageData.Length);

                // Unlock bitmap data
                _bitmap.UnlockBits(_bitmapData);

                _isLocked = false;
            }
            catch (Exception)
            {
                throw;
            }
        }
        else
        {
            throw new Exception("Bitmap is not locked.");
        }
    }

    //public void Show()
    //{
    //    if (_isLocked == true)
    //    {
    //        Console.WriteLine("ImagePixelFormat = " + ImagePixelFormat.ToString());
    //        Console.WriteLine("Width = " + Width + " pixels");
    //        Console.WriteLine("Height = " + Height + " pixels");
    //        Console.WriteLine("_imageData.Length = " + _imageData.Length + " memorySize");
    //        Console.WriteLine("Stride = " + Stride + " memorySize");
    //        Console.WriteLine("Color Depth = " + ColorDepth + " bits");
    //        Console.WriteLine("PaddingOffset = " + PaddingOffset + " memorySize");
    //        Console.WriteLine();
    //    }
    //    else
    //    {
    //        throw new Exception("Bitmap is not locked.");
    //    }
    //}

    /// <summary>
    /// Get the color of the specified pixel
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    public Color GetPixel(int x, int y)
    {
        Color clr = Color.Empty;

        // Get color components count
        int cCount = ColorDepth / 8;

        // Get start index of the specified pixel
        int i = (Height - y - 1) * Stride + x * cCount;

        if (i > _imageData.Length - cCount)
        {
            throw new IndexOutOfRangeException();
        }

        if (ColorDepth == 32) // For 32 bpp get Red, Green, Blue and Alpha
        {
            byte b = _imageData[i];
            byte g = _imageData[i + 1];
            byte r = _imageData[i + 2];
            byte a = _imageData[i + 3]; // a
            clr = Color.FromArgb(a, r, g, b);
        }
        if (ColorDepth == 24) // For 24 bpp get Red, Green and Blue
        {
            byte b = _imageData[i];
            byte g = _imageData[i + 1];
            byte r = _imageData[i + 2];
            clr = Color.FromArgb(r, g, b);
        }
        if (ColorDepth == 8)
        // For 8 bpp get color value (Red, Green and Blue values are the same)
        {
            byte c = _imageData[i];
            clr = Color.FromArgb(c, c, c);
        }
        return clr;
    }

    public void SetPixel(int x, int y, Color color)
    {
        // Get color components count
        int cCount = ColorDepth / 8;

        // Get start index of the specified pixel
        int i = (Height - y) * Stride + x * cCount;

        if (ColorDepth == 32) // For 32 bpp set Red, Green, Blue and Alpha
        {
            _imageData[i] = color.B;
            _imageData[i + 1] = color.G;
            _imageData[i + 2] = color.R;
            _imageData[i + 3] = color.A;
        }
        if (ColorDepth == 24) // For 24 bpp set Red, Green and Blue
        {
            _imageData[i] = color.B;
            _imageData[i + 1] = color.G;
            _imageData[i + 2] = color.R;
        }
        if (ColorDepth == 8)
        // For 8 bpp set color value (Red, Green and Blue values are the same)
        {
            _imageData[i] = color.B;
        }
    }
}

.

Here is, Grayscale.Fill()

    public static Bitmap Fill(Bitmap image, Color fill)
    {
        BitmapLocker locker = new BitmapLocker(image);
        locker.Lock();
        for (int i = 0; i < image.Width; i++)
        {
            for (int j = 0; j < image.Height; j++)
            {
                locker.SetPixel(i, j, fill);
            }
        }
        locker.Unlock();

        return image;
    }

.

N.B. The commented out code need extensive debugging and intended for future inclusion. So, those are optional for your consideration at this moment.

\$\endgroup\$
2
  • \$\begingroup\$ You are calling the public int ColorDepth { get { return Bitmap.GetPixelFormatSize(_bitmap.PixelFormat); } } for each SetPixel to calculate the color components. Make it a one time initialization property and set its value in the constructor instead of evaluating it each time. \$\endgroup\$
    – t3chb0t
    Commented Aug 28, 2016 at 16:29
  • \$\begingroup\$ I checked its source code and I think this probably would be micro-optimization becasue all it does is return(unchecked((int)pixfmt) >> 8) & 0xFF; \$\endgroup\$
    – t3chb0t
    Commented Aug 28, 2016 at 16:33

1 Answer 1

1
\$\begingroup\$

As far as performance is concerned and if your code allows it you may consider using a jagged array instead of a multidimensional one. They are said to have a better performance.

And as usual the bottle-neck of your code are the GetPixel/SetPixel that are really slow. You should be accessing the bitmap directly

Edit:

You can improve BitmapLocker - make it IDisposable and use it with a using to automatically release the bitmap - you won't however gain any performance but you'll be sure that it's released without having to thing about releasing it manually.

Edit 2:

You are calling the public int ColorDepth { get { return Bitmap.GetPixelFormatSize(_bitmap.PixelFormat); } } for each SetPixel to calculate the color components. Make it a one time initialization property and set its value in the constructor instead of evaluating it each time.

Edit 3:

I checked its source code and I think this probably would be micro-optimization because all it does is return(unchecked((int)pixfmt) >> 8) & 0xFF;

\$\endgroup\$
5
  • \$\begingroup\$ What do you think about Grayscale.Fill(resizedImage, color);? \$\endgroup\$
    – user3804
    Commented Aug 28, 2016 at 16:13
  • \$\begingroup\$ @anonymous it's not the same as LockBits becasue you are still usting GetPixel/SetPixel which are probably extensions calling inputImageLocker.Bitmap.GetPixel - arent't they? - because the locker object does not have such methods. \$\endgroup\$
    – t3chb0t
    Commented Aug 28, 2016 at 16:16
  • \$\begingroup\$ What do I think about Grayscale.Fill? - as long as you are using the Get/SetPixel approach, the image operations will be slow. \$\endgroup\$
    – t3chb0t
    Commented Aug 28, 2016 at 16:19
  • \$\begingroup\$ @anonymous oh, haha ;-) you should have posted it right away :-] no one could have known that. \$\endgroup\$
    – t3chb0t
    Commented Aug 28, 2016 at 16:21
  • 1
    \$\begingroup\$ @anonymous but you can improve this one too - make it IDisposable and use it with a using to automatically release the bitmap - you won't however gain any performance but you'll be sure that it's released without having to think about releasing it manually. \$\endgroup\$
    – t3chb0t
    Commented Aug 28, 2016 at 16:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.