将字节数组复制到位图时,LockBits似乎导致错误的步幅

时间:2019-07-14 02:14:43

标签: c# windows bitmap lockbits

我正在尝试将一个简单的字节数组复制到8位索引位图。使用与许多论坛上无数已回答问题中所示相同的代码,我仍然得到错误的结果。我尝试写入图像文件的数据为360字节,设置为18x20字节的线性数组。也就是说,前18个字节(0-17)属于图像的第一行,接下来的18个字节(18-35)属于第二行,依此类推。我已经确认该数据是正确的,我可以在Excel中手动解析它(甚至通过设置单元格的背景色对其进行可视化)。但是,当我尝试使用c#中的代码提取此内容时,会得到格式错误的图像。这是代码...

public Bitmap CopyByteDataToBitmap(byte[] byteData) {
    Bitmap bmp = new Bitmap(18, 20, PixelFormat.Format8bppIndexed);
    BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
    Marshal.Copy(byteData, 0, bmpData.Scan0, byteData.Length);
    bmp.UnlockBits(bmpData);

    return bmp;
}

结果如下。第一行已正确写入。但是,从第二行开始,存在2个字节的偏移量。也就是说,图像第二行的第一个字节最终是字节#20而不是字节#18(从0开始)。另外,如果我在LockBits调用之后立即设置断点,则可以看到bmpData的“ Stride”属性等于20 ...即使宽度明确设置为18。并且,如果我手动将stride设置为18,在LockBits之后,它对返回的位图没有影响。为什么会这样呢?请帮助,谢谢。

1 个答案:

答案 0 :(得分:0)

您必须逐行复制它,将读取位置按图像数据中使用的跨度向前移动,将写入位置按BitmapData对象中设置的跨度向前移动。

在您的情况下,输入数据的跨度仅是宽度,但是BitmapData's的跨度与之不符,因为正如TaW所说,它总是四舍五入到下一个4字节的倍数。

还请注意,这是一个8位图像,您需要添加调色板,否则它将最终得到标准的Windows调色板,该调色板可能根本与图像的预期颜色不匹配。

/// <summary>
/// Creates a bitmap based on data, width, height, stride and pixel format.
/// </summary>
/// <param name="sourceData">Byte array of raw source data.</param>
/// <param name="width">Width of the image.</param>
/// <param name="height">Height of the image.</param>
/// <param name="stride">Scanline length inside the data.</param>
/// <param name="pixelFormat">Pixel format.</param>
/// <param name="palette">Color palette.</param>
/// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param>
/// <returns>The new image.</returns>
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor)
{
    Bitmap newImage = new Bitmap(width, height, pixelFormat);
    BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
    Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
    Int32 targetStride = targetData.Stride;
    Int64 scan0 = targetData.Scan0.ToInt64();
    for (Int32 y = 0; y < height; ++y)
        Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
    newImage.UnlockBits(targetData);
    // For indexed images, set the palette.
    if ((pixelFormat & PixelFormat.Indexed) != 0 && (palette != null || defaultColor.HasValue))
    {
        if (palette == null)
            palette = new Color[0];
        ColorPalette pal = newImage.Palette;
        Int32 palLen = pal.Entries.Length;
        Int32 paletteLength = palette.Length;
        for (Int32 i = 0; i < palLen; ++i)
        {
            if (i < paletteLength)
                pal.Entries[i] = palette[i];
            else if (defaultColor.HasValue)
                pal.Entries[i] = defaultColor.Value;
            else
                break;
        }
        // Palette property getter creates a copy, so the newly filled in palette is
        // not actually referenced in the image until you set it again explicitly.
        newImage.Palette = pal;
    }
    return newImage;
}