user366312
user366312

Reputation: 16908

How can I do padding of an image which is less than 4 bytes?

In the following source code, I am trying to do the following:

  1. Obtain a 3x3 array of double values
  2. Convert that double array to Bitmap
  3. Pad that Bitmap.

        Bitmap image = ImageDataConverter.ToBitmap(new double[,]  
                                    {   
                                    { .11, .11, .11, }, 
                                    { .11, .11, .11, }, 
                                    { .11, .11, .11, }, 
                                    });
    
        Bitmap paddedBitmap = ImagePadder.Pad(image, 512, 512);
    
        pictureBox1.Image = paddedBitmap;
    

But, this source code is generating the following exception in the BitmapLocker.GetPixel(), because, i = 8, and dataLength = 7.

enter image description here

Please, note that, image-stride is always found to be 4, no matter what the size of the dimensions are.

How can I fix this?

.

Relevant Source Code

ImageDataConverter.cs

public class ImageDataConverter
{
    public static Bitmap ToBitmap(double[,] input)
    {
        int width = input.GetLength(0);
        int height = input.GetLength(1);

        Bitmap output = Grayscale.CreateGrayscaleImage(width, height);

        BitmapData data = output.LockBits(new Rectangle(0, 0, width, height),
                                            ImageLockMode.WriteOnly,
                                            output.PixelFormat);            

        int pixelSize = System.Drawing.Image.GetPixelFormatSize(output.PixelFormat) / 8;

        int offset = data.Stride - width * pixelSize;

        double Min = 0.0;
        double Max = 255.0;

        unsafe
        {
            byte* address = (byte*)data.Scan0.ToPointer();

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    double v = 255 * (input[x, y] - Min) / (Max - Min);

                    byte value = unchecked((byte)v);

                    for (int c = 0; c < pixelSize; c++, address++)
                    {
                        *address = value;
                    }
                }

                address += offset;
            }
        }

        output.UnlockBits(data);

        return output;
    }
}

ImagePadder.cs

public class ImagePadder
{
    public static Bitmap Pad(Bitmap image, int newWidth, int newHeight)
    {
        int width = image.Width;
        int height = image.Height;

        if (width >= newWidth) throw new Exception("New width must be larger than the old width");
        if (height >= newHeight) throw new Exception("New height must be larger than the old height");

        Bitmap paddedImage = Grayscale.CreateGrayscaleImage(newWidth, newHeight);

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

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

        //Reading row by row
        for (int y = 0; y < image.Height; y++)
        {
            for (int x = 0; x < image.Width; x++)
            {
                Color col = inputImageLocker.GetPixel(x, y);

                paddedImageLocker.SetPixel(x, y, col);
            }
        }

        string str = string.Empty;

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

        return paddedImage;
    }
}

BitmapLocker.cs

public class BitmapLocker : IDisposable
{
    //private properties
    Bitmap _bitmap = null;
    BitmapData _bitmapData = null;
    private byte[] _imageData = null;

    //public properties
    public bool IsLocked { get; set; }
    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)
    {
        IsLocked = false;
        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 _imageData
                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 _imageData 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 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;

        int dataLength = _imageData.Length - cCount;

        if (i > dataLength)
        {
            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 - 1) * Stride + x * cCount;

        try
        {
            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;
            }
        }
        catch (Exception ex)
        {
            throw new Exception("(" + x + ", " + y + "), " + _imageData.Length + ", " + ex.Message + ", i=" + i);
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // free managed resources
            _bitmap = null;
            _bitmapData = null;
            _imageData = null;
            IntegerPointer = IntPtr.Zero;
        }
    }
}

Upvotes: 0

Views: 914

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205619

The problem is in BitmapLocker class. Besides obvious inefficiencies, the class contains two serious bugs.

The first (which is causing the exception) is the incorrect buffer size calculation inside Lock method:

int noOfBitsNeededForStorage = _bitmapData.Stride * _bitmapData.Height;

int noOfBytesNeededForStorage = noOfBitsNeededForStorage / 8;

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

The Stride property returns

The stride width, in bytes, of the Bitmap object.

and also

The stride is the width of a single row of pixels (a scan line), rounded up to a four-byte boundary. If the stride is positive, the bitmap is top-down. If the stride is negative, the bitmap is bottom-up.

so the correct calculation (shown in several LockBits related MSDN samples) is:

int noOfBytesNeededForStorage = Math.Abs(_bitmapData.Stride) * _bitmapData.Height;

_imageData = new byte[noOfBytesNeededForStorage];

which will fix the exception (your code was doing (12 / 8) * 8 which resulted in 8 rather than the expected 12).

The second issue is the determination of the start index of the specified pixel here:

int i = (Height - y - 1) * Stride + x * cCount;

which is calculation for bottom-up bitmap with positive Stride, which as you can see from the documentation is not possible.

Hence the correct calculation should be something like this:

int i = (Stride > 0 ? y * Stride : (Height - y - 1) * -Stride) + x * cCount;

or

int i = (Stride > 0 ? y : y - Height + 1) * Stride + x * cCount;

This should be changed in both GetPixel and SetPixel methods.

Upvotes: 2

Related Questions