如何填充小于4个字节的图像?

时间:2017-04-08 07:09:02

标签: c# image-processing bitmap

在以下源代码中,我尝试执行以下操作:

  1. 获取3x3数组double
  2. double数组转换为Bitmap
  3. 填充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;
    
  4. 但是,此源代码在BitmapLocker.GetPixel()中生成以下异常,因为i = 8dataLength = 7

    enter image description here

    请注意,无论尺寸大小如何,图像步幅始终为4。

    我该如何解决这个问题?

    相关源代码

      

    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;
            }
        }
    }
    

1 个答案:

答案 0 :(得分:2)

问题出在BitmapLocker课程中。除了明显的低效率外,该课程还包含两个严重的错误。

第一个(导致异常)是Lock方法中的错误缓冲区大小计算:

int noOfBitsNeededForStorage = _bitmapData.Stride * _bitmapData.Height;

int noOfBytesNeededForStorage = noOfBitsNeededForStorage / 8;

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

Stride属性返回

  

Bitmap对象的步长为 bytes

以及

  

步幅是单行像素(扫描线)的宽度,向上舍入为四字节边界。如果步幅为正,则位图为自上而下。如果步幅是负数,则位图是自下而上的。

所以正确的计算(显示在几个LockBits相关的MSDN样本中)是:

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

_imageData = new byte[noOfBytesNeededForStorage];

将修复异常(您的代码正在执行(12 / 8) * 8,导致8而不是预期的12。)

第二个问题是确定指定像素的起始索引:

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

这是自下而上的位图计算为正Stride,正如您从文档中看到的那样是不可能的。

因此,正确的计算应该是这样的:

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

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

这应该在GetPixelSetPixel方法中进行更改。