您的位置:首页 > 编程语言 > C#

CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#)

2016-04-24 17:35 791 查看
[b]CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#) [/b]

效果图

本文解决了将OpenGL渲染出来的内容保存到PNG图片的方法。

struct Pixel
{
public byte r;
public byte g;
public byte b;
public byte a;

public Pixel(byte r, byte g, byte b, byte a)
{
this.r = r; this.g = g; this.b = b; this.a = a;
}

public Color ToColor()
{
return Color.FromArgb(a, r, g, b);
}

public override string ToString()
{
return string.Format("{0}, {1}, {2}, {3}", r, g, b, a);
}
}


struct Pixel
为了使用非托管数组,还需要用到 UnmanagedArray<T> 。关于这个类型详情见(C#+无unsafe的非托管大数组(large unmanaged array in c# without 'unsafe' keyword))。

方法一:Bitmap.SetPixel()

最直接的方法是用Bitmap.SetPixel()来一个一个地指定图片的像素值。

/// <summary>
/// 把OpenGL渲染的内容保存到图片文件。
/// </summary>
/// <param name="x">左下角坐标为(0, 0)</param>
/// <param name="y">左下角坐标为(0, 0)</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="filename"></param>
public static void Save2Picture(int x, int y, int width, int height, string filename)
{
var pdata = new UnmanagedArray<Pixel>(width * height);
GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
var bitmap = new Bitmap(width, height);
int index = 0;
for (int j = height - 1; j >= 0; j--)
{
for (int i = 0; i < width; i++)
{
Pixel v = pdata[index++];
Color c = v.ToColor();
bitmap.SetPixel(i, j, c);
}
}

bitmap.Save(filename);
}


方法二:Marshal.Copy

方法一用到的SetPixel()速度是很慢的。

先把读到的内容写入一个byte[],然后再用Marshal.Copy()复制到Bitmap。这个方法的思路是(非托管数组->托管数组->bmpData.Scan0)

/// <summary>
/// 把OpenGL渲染的内容保存到图片文件。
/// </summary>
/// <param name="x">左下角坐标为(0, 0)</param>
/// <param name="y">左下角坐标为(0, 0)</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="filename"></param>
public static void Save2Picture(int x, int y, int width, int height, string filename)
{
var pdata = new UnmanagedArray<Pixel>(width * height);
GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
var bitmap = new Bitmap(width, height, format);
var bitmapRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
{
int length = Math.Abs(bmpData.Stride) * bitmap.Height;
byte[] bitmapBytes = new byte[length];
int index = 0;
for (int j = height - 1; j >= 0; j--)
{
for (int i = 0; i < width; i++)
{
Pixel v = pdata[index++];
bitmapBytes[j * bmpData.Stride + i * 4 + 0] = v.b;
bitmapBytes[j * bmpData.Stride + i * 4 + 1] = v.g;
bitmapBytes[j * bmpData.Stride + i * 4 + 2] = v.r;
bitmapBytes[j * bmpData.Stride + i * 4 + 3] = v.a;
}
}

System.Runtime.InteropServices.Marshal.Copy(bitmapBytes, 0, bmpData.Scan0, length);
}
bitmap.UnlockBits(bmpData);

bitmap.Save(filename);
}


方法三:直接写入bmpData.Scan0

上一个方法里,通过托管数组byte[]进行过渡,是为了使用Marshal.Copy()这个method。但是我明明可以直接操作bmpData.Scan0啊,何必弄个byte[]。

/// <summary>
/// 把OpenGL渲染的内容保存到图片文件。
/// </summary>
/// <param name="x">左下角坐标为(0, 0)</param>
/// <param name="y">左下角坐标为(0, 0)</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="filename"></param>
public static void Save2Picture(int x, int y, int width, int height, string filename)
{
var pdata = new UnmanagedArray<Pixel>(width * height);
GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
var bitmap = new Bitmap(width, height, format);
Rectangle bitmapRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
unsafe
{
var array = (byte*)bmpData.Scan0.ToPointer();
int index = 0;
for (int j = height - 1; j >= 0; j--)
{
for (int i = 0; i < width; i++)
{
Pixel v = pdata[index++];
array[j * bmpData.Stride + i * 4 + 0] = v.b;
array[j * bmpData.Stride + i * 4 + 1] = v.g;
array[j * bmpData.Stride + i * 4 + 2] = v.r;
array[j * bmpData.Stride + i * 4 + 3] = v.a;
}
}
}
bitmap.UnlockBits(bmpData);

bitmap.Save(filename);
}


方法四:ReadPixels直接搞定

在上面的方法里,思路是(非托管数组->非托管数组)。

显然这个转换步骤也是多余的,直接让ReadPixels写入bmpData.Scan0的位置不就好了嘛。

/// <summary>
/// 把OpenGL渲染的内容保存到图片文件。
/// </summary>
/// <param name="x">左下角坐标为(0, 0)</param>
/// <param name="y">左下角坐标为(0, 0)</param>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="filename"></param>
public static void Save2Picture(int x, int y, int width, int height, string filename)
{
var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
var bitmap = new Bitmap(width, height, format);
var bitmapRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
GL.ReadPixels(x, y, width, height, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, bmpData.Scan0);
bitmap.UnlockBits(bmpData);
bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);

bitmap.Save(filename);
}


总结

从OpenGL窗口读取出图片,是非常有用的。

原CSharpGL的其他功能(UI、3ds解析器、TTF2Bmp、CSSL等),我将逐步加入新CSharpGL。

欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: