如何从负步幅的位图中复制像素数据?
本文关键字:复制 像素 像素数 数据 位图 | 更新日期: 2023-09-27 18:03:30
我正在寻找将位图转换为8bpp的最快方法。我找到了两种方法:
1。
public static System.Drawing.Image ConvertTo8bpp(Bitmap oldbmp)
{
using (var ms = new MemoryStream())
{
oldbmp.Save(ms, ImageFormat.Gif);
ms.Position = 0;
return System.Drawing.Image.FromStream(ms);
}
}
2。 http://www.wischik.com/lu/programmer/1bpp.html
但: 1。导致非常低质量的结果(坏托盘)
和2给了我一个负步幅的位图,当我试图锁定比特并将数据复制到字节数组时,我得到一个异常:试图读取或写入受保护的内存。这通常表明其他内存已损坏。
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat);
this.stride = bmpData.Stride;
this.bytesPerPixel = GetBytesPerPixel(bmp.PixelFormat);
int length = bmpData.Stride * bmp.Height;
if (this.stride < 0)
this.data = new byte[-length];
else
this.data = new byte[length];
Marshal.Copy(bmpData.Scan0, data, 0, length);
//Unlock the bitmap
bmp.UnlockBits(bmpData);
我如何使2给出一个积极的步伐?或者我如何使用负步幅的锁位复制数据??
每次复制1行,计算每一行的起始指针为((byte*)scan0 + (y * stride))
。对于正步幅和负步幅,代码是相同的。
这里的问题是Scan0
指向第一个扫描行的开始,而不是数据的第一个字节的开始。在自底向上的位图中,第一个扫描线是位图数据末尾的Stride
字节。
当您调用Marshal.Copy
从Scan0
复制数据时,它尝试从位置((Height-1)*Stride)
开始复制(Height*Stride)
字节。很明显,那将会消失在杂草中。
如果你只想复制位图数据,你必须用Scan0 - (Height-1)*Stride
计算起始地址。这将使您从位图数据的开头开始。你可以把计算出的地址传递给Marshal.Copy
。
如果你想按顺序复制扫描行(即top, next, next,…)底部),然后你必须一次复制一行:从Scan0
复制Stride
字节,然后添加Stride
(这是负的),复制该行,等等。Rick Brewster给出了正确的答案:https://stackoverflow.com/a/10360753/56778
我不知道为什么FromHbitmap
方法创建的位图有些奇怪,但我知道你可以通过使用Bitmap bmpClone = (Bitmap)bmp.Clone();
和在bmpClone上做LockBits来修复它。
另外,我发现如果您使用bmp.Clone()
,则在完成克隆之前无法Dispose() bmp。
这也可以工作,让您尽早处理负步幅图像:
Bitmap bmp = null;
using (Bitmap bmpT = CopyToBpp(bmpO, 1))
{
bmp = new Bitmap(bmpT);
}
来自c#文档BitmapData: stride是单行像素(扫描线)的宽度,四舍五入到四个字节的边界。如果步幅为正,则位图是自上而下的。如果步幅为负,则位图为自下而上
我设法解决了这个问题,而没有创建全新的位图对象,通过使用LockBits()
和ImageLockMode.UserInputBuffer
。附上我想出来的代码,请随意使用。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
public static class BitmapExtensions
{
public static ProperBitmapData LockBitsProper(this Bitmap bitmap, ImageLockMode flags)
{
Rectangle bitmapBounds = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
return bitmap.LockBitsProper(bitmapBounds, flags, bitmap.PixelFormat);
}
public static ProperBitmapData LockBitsProper(this Bitmap bitmap, Rectangle rect, ImageLockMode flags, PixelFormat format)
{
BitmapData bmpData = bitmap.LockBits(rect, flags, format);
int byteCount;
try
{
byteCount = Math.Abs(bmpData.Stride) * bmpData.Height;
if (bmpData.Stride > 0) return new ProperBitmapData(bitmap, bmpData, byteCount, IntPtr.Zero);
}
catch
{
bitmap.UnlockBits(bmpData);
throw;
}
// in case Stride is negative
bitmap.UnlockBits(bmpData);
// When Stride is negative, the LockBits locks the wrong memory area which results in AccessViolationException even when reading the right place
// starting with Scan0 + (Height - 1) * Stride (also not properly documented).
// This is a workaround to it using a user allocated area overload.
// For some reason, in Windows Vista (SP0) Stride is (almost?) always negative, while in >=Windows 7 it is positive more often.
// Some useful documentation: https://learn.microsoft.com/en-us/windows/win32/api/gdiplusheaders/nf-gdiplusheaders-bitmap-lockbits
IntPtr userAllocatedArea = Marshal.AllocHGlobal(byteCount);
try
{
// Actually when Stride is negative, Scan0 have to point to where the last row will be written.
// This is not properly documented anywhere, and discovered just by trial and error.
bmpData.Scan0 = (IntPtr)((long)userAllocatedArea - (bmpData.Height - 1) * bmpData.Stride);
bmpData = bitmap.LockBits(rect, ImageLockMode.UserInputBuffer | flags, format, bmpData);
try
{
return new ProperBitmapData(bitmap, bmpData, byteCount, userAllocatedArea);
}
catch
{
bitmap.UnlockBits(bmpData);
throw;
}
}
catch
{
Marshal.FreeHGlobal(userAllocatedArea);
throw;
}
}
}
public class ProperBitmapData : IDisposable
{
private Bitmap _bitmap;
private BitmapData _bitmapData;
private int _byteCount;
private IntPtr _userAllocatedBuffer;
public int Width => _bitmapData.Width;
public int Height => _bitmapData.Height;
public int Stride => _bitmapData.Stride;
public PixelFormat PixelFormat => _bitmapData.PixelFormat;
public IntPtr Scan0 => (_userAllocatedBuffer != IntPtr.Zero) ? _userAllocatedBuffer : _bitmapData.Scan0;
public int Reserved => _bitmapData.Reserved;
public int ByteCount => _byteCount;
public ProperBitmapData(Bitmap bitmap, BitmapData bitmapData, int byteCount, IntPtr userAllocatedBuffer)
{
_bitmap = bitmap;
_bitmapData = bitmapData;
_byteCount = byteCount;
_userAllocatedBuffer = userAllocatedBuffer;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
_bitmap?.UnlockBits(_bitmapData);
_bitmap = null;
_bitmapData = null;
if (_userAllocatedBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(_userAllocatedBuffer);
_userAllocatedBuffer = IntPtr.Zero;
}
}
~ProperBitmapData()
{
Dispose(false);
}
}
用法示例:
using (ProperBitmapData bmpData = bitmap.LockBitsProper(ImageLockMode.ReadOnly))
{
// Beware that bmpData.Scan0 here always points to the start of the allocated memory block.
this.data = new byte[bmpData.ByteCount];
Marshal.Copy(bmpData.Scan0, data, 0, bmpData.ByteCount);
}
我猜你得到的异常是由于
this.data = new byte[-length];
然后尝试将数据复制到负大小的字节数组中(我不知道这是如何编译的…)。