读取像素数据时索引超出范围异常

时间:2016-11-10 05:04:39

标签: c#-4.0 image-processing bitmap padding bitmapdata

  

相关文章:Stride of the BitmapData is different than the original dimension

我已采用源代码from here并对其进行了修改。

代码在不同的场合产生各种异常。

BitmapLocker.cs中的错误

Lock()的以下一行

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

正在生成以下异常:

  

未处理的类型' System.AccessViolationException'   发生在mscorlib.dll

     

附加信息:尝试读取或写入受保护的内存。   这通常表明其他内存已损坏。

对于以下驱动程序代码,

        double[,] mask = new double[,] 
                                    {   
                                    { .11, .11, .11, }, 
                                    { .11, .11, .11, }, 
                                    { .11, .11, .11, }, 
                                    };
        Bitmap bitmap = ImageDataConverter.ToBitmap(mask);

        BitmapLocker locker = new BitmapLocker(bitmap);

        locker.Lock();

        for (int i = 0; i < bitmap.Width; i++)
        {
            for (int j = 0; j < bitmap.Height; j++)
            {
                Color c = locker.GetPixel(i, j);

                locker.SetPixel(i, j, c);
            }
        }

        locker.Unlock();

GetPixel()的以下一行

        if (i > dataLength)
        {
            throw new IndexOutOfRangeException();
        }
  

未处理的类型&#39; System.IndexOutOfRangeException&#39;   发生在Simple.ImageProcessing.Framework.dll

中      

其他信息:索引超出了数组的范围。

SetPixel()的以下一行

if (ColorDepth == 8)
{
    _imageData[i] = color.B;
}
  

未处理的类型&#39; System.Exception&#39;发生在   Simple.ImageProcessing.Framework.dll

     

附加信息:(0,0),262144,索引超出界限   数组。,i = 262144

驱动程序错误

在该行,

Color c = bmp.GetPixel(i, j);
  

未处理的类型&#39; System.InvalidOperationException&#39;   发生在System.Drawing.dll

中      

其他信息:位图区域已被锁定。

源代码:

public class BitmapLocker : IDisposable
{
    //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 _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 channels = ColorDepth / 8;

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

        int dataLength = _imageData.Length - channels;

        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;
        }
        // free native resources if there are any.

        //private properties



        //public properties

    }
}

ImageDataConverter.cs

    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(PixelFormat.Format8bppIndexed) / 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;
    }

这是我用于测试的图片,

enter image description here

1 个答案:

答案 0 :(得分:4)

int noOfBitsNeededForStorage = _bitmapData.Stride * _bitmapData.Height;

这是代码中最重要的错误。 Stride * Height是存储所需的 bytes 的数量。所以它不会使_imageData数组足够大而且IndexOutOfRangeException是预期的结果。

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

本声明中有很多可能的不幸事件。它将像素格式硬编码为8bpp,但这不是LockBits()调用所使用的实际像素格式。这是output.PixelFormat。在样本图像上显然是致命的,虽然不清楚它是如何在代码中使用的,8bpp是一种非常笨拙的像素格式,因为它需要一个调色板。即使原始文件使用8bpp,PNG编解码器也会在内存中创建32bpp的图像。您必须在此处使用output.PixelFormat来获取与锁定数据的匹配,并相应地调整像素写入代码。不清楚它为何被使用,库代码提供的SetPixel()方法应该已经足够好了。

int dataLength = _imageData.Length - channels;

不清楚该声明试图做什么,减去频道数量并不是一个明智的操作。它将生成一个虚假的IndexOutOfRangeException。没有明显的理由可以提供帮助,CLR已经在_imageData数组上提供了数组索引检查。所以只需删除该代码即可。

Additional information: Bitmap region is already locked.

代码中的异常处理不自信,这是此异常的可能原因。通常,必须注意的是,在调用Lock()方法并且尚未调用Unlock()之后,除了通过_imageData之外,底层位图是完全不可访问的。最好的方法是使用try/finally并在finally块中调用Unlock(),这样您就可以始终确保位图不会被意外锁定。

byte c = _imageData[i];

这是不正确的,除了在8bpp图像的角落情况下,该图像具有明确创建用于处理灰度图像的调色板。 8bpp图像的默认调色板不符合该要求,也不是从文件加载图像时可以盲目依赖的。索引像素格式,这是20世纪90年代早期必不可少的黑客攻击,因为视频适配器还不够强大。它今天完全没有任何意义。请注意,SetPixel()也不处理16位像素格式。并且PNG编解码器永远不会创建8bpp的内存映像,也不能编码8bpp的文件。最好的建议是完全消除8bpp支持,以获得更可靠的代码。

事实上,直接访问像素数据的目的是使图像操作快速。只有一种像素格式可以始终如一地生成快速代码,它是Format32bppArgb。现在可以使用int*而不是byte*来访问像素,移动像素的速度要快4倍。并且不需要特殊的调整来处理步幅或特殊情况下的SetPixel()等方法的代码。因此,将该格式传递给LockBits(),如果实际图像格式不是32bpp,编解码器将完成必要的工作。

我应该注意Format32bppPArgb是用于在屏幕上显示图像的快速像素格式,因为它与所有现代视频适配器使用的像素格式兼容。但这不是这段代码的重点,处理预乘的alpha是尴尬的。