【问题标题】:How can I do padding of an image which is less than 4 bytes?如何填充小于 4 字节的图像?
【发布时间】:2017-04-08 07:09:01
【问题描述】:

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

  1. 获取3x3 数组double
  2. double 数组转换为Bitmap
  3. 填充该位图。

        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;
    

但是,此源代码在BitmapLocker.GetPixel() 中生成以下异常,因为i = 8dataLength = 7

请注意,无论尺寸大小如何,image-stride 始终为 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;
        }
    }
}

【问题讨论】:

    标签: c# image-processing bitmap


    【解决方案1】:

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

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

    int noOfBitsNeededForStorage = _bitmapData.Stride * _bitmapData.Height;
    
    int noOfBytesNeededForStorage = noOfBitsNeededForStorage / 8;
    
    _imageData = new byte[noOfBytesNeededForStorage * ColorDepth];//# of bytes needed for storage
    

    Stride 属性返回

    位图对象的步幅宽度,以字节为单位。

    还有

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

    所以正确的计算(显示在几个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 方法中进行更改。

    【讨论】:

    • 为什么你认为 BitmapLocker 效率低?
    • 因为 (1) 它在开始时将整个锁定位缓冲区复制到托管缓冲区 (2) 它在最后将整个托管缓冲区复制回来 (3) 它对每个 GetPixel / 进行计算SetPixel 打电话。为了比较,请查看 ImageDataConverter 类中的 unsafe 块。当然,它比常规的 Bitmap GetPixel / SetPixel 方法要好,但看起来它这样做只是为了避免 unsafe 选项。
    • 您有什么改进建议?
    • 好吧,它可以被重写以使用带有指针的不安全块。当然,另一种选择是根本不使用它 - ImageDataConverter 没有使用它,尽管它可以,所以同样适用于 ImagePadder - 目前它正在使用它,但可以直接使用它 LockBits / @ 987654347@ 和指针。
    • 我总是使用 Marshal.Copy 方法...但是计算循环内的偏移量确实更有效,因为您只需要在每次 Y 迭代开始时执行 offset = y*stride ,并在每次 X 迭代后添加像素宽度。
    猜你喜欢
    • 1970-01-01
    • 2014-09-18
    • 1970-01-01
    • 2021-01-17
    • 1970-01-01
    • 2018-09-26
    • 2018-12-18
    • 1970-01-01
    • 2010-09-29
    相关资源
    最近更新 更多