图像未正确填充

Image is not being padded correctly

输出

我认为下面的代码没有给出正确的结果。

以下代码有什么问题?

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

相关源码:

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

Windows 位图的布局与您预期的不同。图像的 bottom 行是内存中的 first 行,并从那里向后继续。高度为负值时也可以反方向布局,但这种情况不常见。

您计算的位图中的偏移量似乎考虑到了这一点,因此您的问题一定更微妙。

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

问题是 BitmapData class 已经考虑到这一点并尝试为您修复它。我上面描述的位图是一个自下而上的位图。来自 BitmapData.Stride 的文档:

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.

它旨在与 Scan0 属性 一起使用,以一致的方式访问位图,无论是自上而下还是自下而上。