struct BitmapDataAccessor
{
private readonly byte[] data;
private readonly int[] rowStarts;
public readonly int Height;
public readonly int Width;
public BitmapDataAccessor(byte[] data, int width, int height)
{
this.data = data;
this.Height = height;
this.Width = width;
rowStarts = new int[height];
for (int y = 0; y < Height; y++)
rowStarts[y] = y * width;
}
public byte this[int x, int y, int color] // Maybe use an enum with Red = 0, Green = 1, and Blue = 2 members?
{
get { return data[(rowStarts[y] + x) * 3 + color]; }
set { data[(rowStarts[y] + x) * 3 + color] = value; }
}
public byte[] Data
{
get { return data; }
}
}
public static byte[, ,] Bitmap2Byte(Bitmap obraz)
{
int h = obraz.Height;
int w = obraz.Width;
byte[, ,] wynik = new byte[w, h, 3];
BitmapData bd = obraz.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
int bytes = Math.Abs(bd.Stride) * h;
byte[] rgbValues = new byte[bytes];
IntPtr ptr = bd.Scan0;
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
BitmapDataAccessor bda = new BitmapDataAccessor(rgbValues, w, h);
for (int i = 0; i < h; i++)
{
for (int j = 0; j < w; j++)
{
wynik[j, i, 0] = bda[j, i, 2];
wynik[j, i, 1] = bda[j, i, 1];
wynik[j, i, 2] = bda[j, i, 0];
}
}
obraz.UnlockBits(bd);
return wynik;
}
public static Bitmap Byte2Bitmap(byte[, ,] tablica)
{
if (tablica.GetLength(2) != 3)
{
throw new NieprawidlowyWymiarTablicyException();
}
int w = tablica.GetLength(0);
int h = tablica.GetLength(1);
Bitmap obraz = new Bitmap(w, h, PixelFormat.Format24bppRgb);
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color kol = Color.FromArgb(tablica[i, j, 0], tablica[i, j, 1], tablica[i, j, 2]);
obraz.SetPixel(i, j, kol);
}
}
return obraz;
}
现在,如果我这样做:
private void btnLoad_Click(object sender, EventArgs e)
{
if (dgOpenFile.ShowDialog() == DialogResult.OK)
{
try
{
Bitmap img = new Bitmap(dgOpenFile.FileName);
byte[, ,] tab = Grafika.Bitmap2Byte(img);
picture.Image = Grafika.Byte2Bitmap(tab);
picture.Size = img.Size;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
大部分照片都是正确处理但不是。 不起作用的图片示例:
http://ifotos.pl/img/1018038_hpnarpq.jpg
产生以下结果(这只是图片的片段):
http://ifotos.pl/img/example_hpnarhp.jpg
为什么?
答案 0 :(得分:3)
访问数据时,您需要考虑BitmapData.Stride
。
修改强>
这是我用来将DirectX表面复制到Bitmap的解决方案。这个想法是一样的,但你需要稍微修改它。我通过调用RtlMoveMemory
(P / Invoke to kernel32.dll)一次复制图像的一条扫描线
//// Snippet
int pitch;
int bytesPerPixel = 4;
Rectangle lockRectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
// Lock the bitmap
GraphicsStream surfacedata = surface.LockRectangle(LockFlags.ReadOnly, out pitch);
BitmapData bitmapdata = bitmap.LockBits(lockRectangle, ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
// Copy surface to bitmap
for (int scanline = 0; scanline < bitmap.Height; ++scanline)
{
byte* dest = (byte*)bitmapdata.Scan0 + (scanline * bitmap.Width * bytesPerPixel);
byte* source = (byte*)surfacedata.InternalData + (scanline * pitch);
RtlMoveMemory(new IntPtr(dest), new IntPtr(source), (bitmap.Width * bytesPerPixel));
}
////
编辑#2:
这一切都针对DirectX,但概念是一样的。
答案 1 :(得分:1)
似乎为位图分配的内存必须在32位边界上对齐,因此由于它们的大小,某些图像上可能存在填充。由于这里有24位像素,因此某些线宽将以32位结束,其他线宽则不会。您需要使用以下公式来计算正在使用的填充,然后考虑它:
int padding = bd.Stride - (((w * 24) + 7) / 8);
您可能希望使用GetPixel(x,y)加载字节数组,而不是在开始读取像素之前完成整个转换到字节数组。
答案 2 :(得分:1)
Thanx到@Lazarus和tbridge我设法如何做到这一点。
首先我们需要计算Bitmap2Byte
中的填充:
int padding = bd.Stride - (((w * 24) + 7) / 8);
并将其传递给BitmapDataAccessor
并修改该行
this.Width = width;
到
this.Width = width + (4-padding)%4;
这就是全部。 Thanx家伙。