C#使用位图锁位处理bmp图像。正在尝试更改所有字节,但其中一些字节在保存后的值仍为0。为什么?

C#使用位图锁位处理bmp图像。正在尝试更改所有字节,但其中一些字节在保存后的值仍为0。为什么?,c#,bitmap,bmp,.net-4.6,lockbits,C#,Bitmap,Bmp,.net 4.6,Lockbits,我需要使用带锁位的图像处理,而不是GetPixel/SetPixel,以减少处理时间但最终它不会保存所有更改。 重现问题的步骤: 读取初始bmp文件中的所有字节 更改所有这些字节并将其保存到新文件 读取保存的文件并将其数据与您试图保存的数据进行比较 期望值:他们是平等的,但实际上不是 我注意到(参见输出): 所有错误的值都等于0 错误值的索引如下:x1,x1+1,x2,x2+1 对于某些文件,它可以按预期工作 还有一件让人困惑的事情:我的初始测试文件是灰色的,但在HexEditor中,在

我需要使用带锁位的图像处理,而不是GetPixel/SetPixel,以减少处理时间但最终它不会保存所有更改。

重现问题的步骤:

  • 读取初始bmp文件中的所有字节
  • 更改所有这些字节并将其保存到新文件
  • 读取保存的文件并将其数据与您试图保存的数据进行比较
期望值:他们是平等的,但实际上不是

我注意到(参见输出):

  • 所有错误的值都等于0
  • 错误值的索引如下:x1,x1+1,x2,x2+1
  • 对于某些文件,它可以按预期工作
还有一件让人困惑的事情:我的初始测试文件是灰色的,但在HexEditor中,在预期的有效负载值中,我们可以看到值“00”。看见 这是什么意思?

我已经阅读并尝试了很多关于Stack Overflow、MSDN、CodeProject和其他网站的LockBits教程,但最终效果都是一样的

下面是代码示例。MyGetByteArrayByMagefileUpdateAllBytes的代码直接来自msdn文章(方法MakeMoreBlue),但有一些小的变化:硬编码像素格式被替换,每个字节都在变化(而不是每6个字节)

程序.cs的内容

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Test
{
    class Program
    {
        const int MagicNumber = 43;

        static void Main(string[] args)
        {
            //Arrange
            string containerFilePath = "d:/test-images/initial-5.bmp";
            Bitmap containerImage = new Bitmap(containerFilePath);

            //Act 
            Bitmap resultImage = UpdateAllBytes(containerImage);
            string resultFilePath = "d:/test-result-images/result-5.bmp";
            resultImage.Save(resultFilePath, ImageFormat.Bmp);

            //Assert            
            var savedImage = new Bitmap(resultFilePath);
            byte[] actualBytes = savedImage.MyGetByteArrayByImageFile(ImageLockMode.ReadOnly).Item1;

            int count = 0;
            for (int i = 0; i < actualBytes.Length; i++)
            {
                if (actualBytes[i] != MagicNumber)
                {
                    count++;
                    Debug.WriteLine($"Index: {i}. Expected value: {MagicNumber}, but was: {actualBytes[i]};");
                }
            }

            Debug.WriteLine($"Amount of different bytes: {count}");

        }

        private static Bitmap UpdateAllBytes(Bitmap bitmap)
        {
            Tuple<byte[], BitmapData> containerTuple = bitmap.MyGetByteArrayByImageFile(ImageLockMode.ReadWrite);
            byte[] bytes = containerTuple.Item1;
            BitmapData bitmapData = containerTuple.Item2;

            // Manipulate the bitmap, such as changing all the values of every pixel in the bitmap
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = MagicNumber;
            }

            // Copy the RGB values back to the bitmap
            Marshal.Copy(bytes, 0, bitmapData.Scan0, bytes.Length);

            // Unlock the bits.
            bitmap.UnlockBits(bitmapData);

            return bitmap;
        }
    }
}
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace Test
{
    public static class ImageExtensions
    {
        public static Tuple<byte[], BitmapData> MyGetByteArrayByImageFile(this Bitmap bmp, ImageLockMode imageLockMode = ImageLockMode.ReadWrite)
        {
            // Specify a pixel format.
            PixelFormat pxf = bmp.PixelFormat;//PixelFormat.Format24bppRgb;

            // Lock the bitmap's bits.
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            BitmapData bmpData = bmp.LockBits(rect, imageLockMode, pxf);

            // Get the address of the first line.
            IntPtr ptr = bmpData.Scan0;

            // Declare an array to hold the bytes of the bitmap.
            // int numBytes = bmp.Width * bmp.Height * 3;
            int numBytes = bmpData.Stride * bmp.Height;
            byte[] rgbValues = new byte[numBytes];

            // Copy the RGB values into the array.
            Marshal.Copy(ptr, rgbValues, 0, numBytes);

            return new Tuple<byte[], BitmapData>(rgbValues, bmpData);
        }
    }
}
输出示例:

Index: 162. Expected value: 43, but was: 0;
Index: 163. Expected value: 43, but was: 0;
Index: 326. Expected value: 43, but was: 0;
Index: 327. Expected value: 43, but was: 0;
Index: 490. Expected value: 43, but was: 0;
Index: 491. Expected value: 43, but was: 0;
Index: 654. Expected value: 43, but was: 0;
...
Index: 3606. Expected value: 43, but was: 0;
Index: 3607. Expected value: 43, but was: 0;
Index: 3770. Expected value: 43, but was: 0;
Index: 3771. Expected value: 43, but was: 0;
Amount of different bytes: 46
如果有人能解释为什么会发生这种情况以及如何解决,我将不胜感激。多谢各位


解决方案 基于此,我更改了代码:

在文件
Program.cs
中,我仅将方法
updatealBytes
更改为使用新的扩展方法
updatebitmappayoladbytes

private static Bitmap UpdateAllBytes(Bitmap bitmap)
{
    Tuple<byte[], BitmapData> containerTuple = bitmap.MyGetByteArrayByImageFile(ImageLockMode.ReadWrite);
    byte[] bytes = containerTuple.Item1;
    BitmapData bitmapData = containerTuple.Item2;

    for (int i = 0; i < bytes.Length; i++)
    {
        bytes[i] = MagicNumber;
    }

    bitmap.UpdateBitmapPayloadBytes(bytes, bitmapData);

    return bitmap;
}
此代码通过了图像的测试:

  • 8,24,32个基点
  • 当需要和不需要填充字节时(例如, 宽度=63,bpp=24,宽度=64,bpp=24,…)

在我的任务中,我不必关心我正在更改哪些值,但是如果您需要计算红色、绿色和蓝色值的确切位置,请记住字节是按BGR顺序排列的,如答案中所述

您的问题中唯一缺少的是一个示例输入文件,根据您的输出,我推断它是一个54 x 23像素的24bpp位图

与您设置的值不匹配的字节是填充字节

发件人:

对于宽度为54像素、每像素24位的图像,这将提供
楼层((24*54+31)/32)*4
=
楼层(1327/32)*4
=
41*4
=
164

在本例中,
164
Stride
属性的值,请注意,这比
54*3
=
162
的原始RGB值多了
2
字节

此填充确保每一行(像素行)从
4的倍数开始

当我在文件保存后检查原始字节时,在我的机器上,所有字节都与幻数匹配

using (var fs = new FileStream(resultFilePath, FileMode.Open))
{
    // Skip the bitmap header
    fs.Position = 54;

    int rawCount = 0;
    int i = 0;
    int byteValue;
    while ((byteValue = fs.ReadByte()) != -1)
    {
        if (byteValue != MagicNumber)
        {
            rawCount++;
            Debug.WriteLine($"Index: {i}. Expected value: {MagicNumber}, but was: {byteValue};");
        }
    }

    Debug.WriteLine($"Amount of different bytes: {rawCount}");
}
而使用扩展方法读取字节会产生不同的填充字节(如
0
s)

如果我随后将
savedImage
保存到一个新的目标,并检查该目标的字节,则填充字节现在是
0

我假设这是因为C#/.NET在加载文件时没有实际读取填充字节

这些填充字节始终位于每行的末尾,如果逐像素操作图像,则可以/应该跳过每行的最后0-3字节,具体取决于每像素的位数和宽度,因为它们并不代表图像的有效像素;更好的是,根据图像的
像素格式
宽度
高度
,精确计算每个像素的
红色
绿色
蓝色
偏移的位置

不幸的是,您链接到的MSDN示例具有误导性,它们的示例图像的宽度恰好可以被
32
除以
BitsPerPixel
(即
(24*100)/32=75
),因此不需要填充字节,在逐字复制时问题不会显现出来

然而,使用不同维度的源图像可以揭示问题。通过添加
6
,他们试图移动到每隔一个像素的蓝色值;字节是按BGR顺序排列的,因此从
0
开始就可以了,添加
6
将跳过
Green0
Red0
Blue1
Green1
Red1
,并落在
Blue2
值上。第一行按预期继续,但是,一旦到达
162
处的行尾,
+6
不考虑填充字节,填充字节现在减少2,这意味着它现在每隔一个像素更新一次
绿色
值,直到它到达下一组填充字节,这将把它移到
红色
值,依此类推

     Row

Col   0    0   1   2   3   4   5   6   7   8  ...   159 160 161 162 163
          B0  G0  R0  B1  G1  R1  B2  G2  R2        B53 G53 R53  P   P

      1   164 165 166 167 168 169    ...
          B54 G54 R54 B55 G56 B56

      ...
从一个54 x 23像素的白色位图开始,将“每隔6个字节”设置为
43
,我们可以看到这一点

for(int counter = 0; counter < bytes.Length; counter+=6)
    bytes[counter] = MagicNumber;
for(int计数器=0;计数器

而不是这个

for (var r = 0; r < bitmapData.Height; r++)
{
    for (var c = 0; c < bitmapData.Width * 3; c += 6)
    {
        bytes[r * bitmapData.Stride + c] = MagicNumber;
    }
}
for(var r=0;r

对于你的第一个问题,这是一个写得很好的问题:)我无法重现这个问题。我不得不改成常量字节MagicNumber
,并做了一些其他的更改。谢谢你的解释。我已经根据你的答案修改了我的代码,它现在可以正常工作了,所以我将很快用新版本的代码更新这个问题。哇,非常好的答案!
for (var r = 0; r < bitmapData.Height; r++)
{
    for (var c = 0; c < bitmapData.Width * 3; c += 6)
    {
        bytes[r * bitmapData.Stride + c] = MagicNumber;
    }
}