zoukankan      html  css  js  c++  java
  • CSharpGL(19)用glReadPixels把渲染的内容保存为PNG图片(C#)

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

    效果图

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

    下载

    CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL

    glReadPixels

    C#里声明glReadPixels的形式如下:

     1         /// <summary>
     2         /// Reads a block of pixels from the frame buffer.
     3         /// </summary>
     4         /// <param name="x">Top-Left X value.</param>
     5         /// <param name="y">Top-Left Y value.</param>
     6         /// <param name="width">Width of block to read.</param>
     7         /// <param name="height">Height of block to read.</param>
     8         /// <param name="format">Specifies the format of the pixel data. The following symbolic values are accepted: OpenGL.COLOR_INDEX, OpenGL.STENCIL_INDEX, OpenGL.DEPTH_COMPONENT, OpenGL.RED, OpenGL.GREEN, OpenGL.BLUE, OpenGL.ALPHA, OpenGL.RGB, OpenGL.RGBA, OpenGL.LUMINANCE and OpenGL.LUMINANCE_ALPHA.</param>
     9         /// <param name="type">Specifies the data type of the pixel data.Must be one of OpenGL.UNSIGNED_BYTE, OpenGL.BYTE, OpenGL.BITMAP, OpenGL.UNSIGNED_SHORT, OpenGL.SHORT, OpenGL.UNSIGNED_INT, OpenGL.INT or OpenGL.FLOAT.</param>
    10         /// <param name="pixels">Storage for the pixel data received.</param>
    11         [DllImport(Win32.OpenGL32, EntryPoint = "glReadPixels", SetLastError = true)]
    12         public static extern void ReadPixels(int x, int y, int width, int height, uint format, uint type, IntPtr pixels);

    这个函数的功能是:将指定范围(x, y, width, height)的像素值读入指定的内存处(pixels)。能把像素信息读到内存里,就可以保存到文件了。

    其中(x, y)是以窗口左下角为原点(0, 0)的。而Windows窗口是以左上角为原点的。所以用的时候要注意转换一下。

    数据结构

    为方便起见,我先定义一个描述像素的数据结构。

     1     struct Pixel
     2     {
     3         public byte r;
     4         public byte g;
     5         public byte b;
     6         public byte a;
     7 
     8         public Pixel(byte r, byte g, byte b, byte a)
     9         {
    10             this.r = r; this.g = g; this.b = b; this.a = a;
    11         }
    12 
    13         public Color ToColor()
    14         {
    15             return Color.FromArgb(a, r, g, b);
    16         }
    17 
    18         public override string ToString()
    19         {
    20             return string.Format("{0}, {1}, {2}, {3}", r, g, b, a);
    21         }
    22     }
    struct Pixel

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

    方法一:Bitmap.SetPixel()

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

     1         /// <summary>
     2         /// 把OpenGL渲染的内容保存到图片文件。
     3         /// </summary>
     4         /// <param name="x">左下角坐标为(0, 0)</param>
     5         /// <param name="y">左下角坐标为(0, 0)</param>
     6         /// <param name="width">宽度</param>
     7         /// <param name="height">高度</param>
     8         /// <param name="filename"></param>
     9         public static void Save2Picture(int x, int y, int width, int height, string filename)
    10         {
    11             var pdata = new UnmanagedArray<Pixel>(width * height);
    12             GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
    13             var bitmap = new Bitmap(width, height);
    14             int index = 0;
    15             for (int j = height - 1; j >= 0; j--)
    16             {
    17                 for (int i = 0; i < width; i++)
    18                 {
    19                     Pixel v = pdata[index++];
    20                     Color c = v.ToColor();
    21                     bitmap.SetPixel(i, j, c);
    22                 }
    23             }
    24 
    25             bitmap.Save(filename);
    26         }

    方法二:Marshal.Copy

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

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

     1         /// <summary>
     2         /// 把OpenGL渲染的内容保存到图片文件。
     3         /// </summary>
     4         /// <param name="x">左下角坐标为(0, 0)</param>
     5         /// <param name="y">左下角坐标为(0, 0)</param>
     6         /// <param name="width">宽度</param>
     7         /// <param name="height">高度</param>
     8         /// <param name="filename"></param>
     9         public static void Save2Picture(int x, int y, int width, int height, string filename)
    10         {
    11             var pdata = new UnmanagedArray<Pixel>(width * height);
    12             GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
    13             var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
    14             var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
    15             var bitmap = new Bitmap(width, height, format);
    16             var bitmapRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
    17             System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
    18             {
    19                 int length = Math.Abs(bmpData.Stride) * bitmap.Height;
    20                 byte[] bitmapBytes = new byte[length];
    21                 int index = 0;
    22                 for (int j = height - 1; j >= 0; j--)
    23                 {
    24                     for (int i = 0; i < width; i++)
    25                     {
    26                         Pixel v = pdata[index++];
    27                         bitmapBytes[j * bmpData.Stride + i * 4 + 0] = v.b;
    28                         bitmapBytes[j * bmpData.Stride + i * 4 + 1] = v.g;
    29                         bitmapBytes[j * bmpData.Stride + i * 4 + 2] = v.r;
    30                         bitmapBytes[j * bmpData.Stride + i * 4 + 3] = v.a;
    31                     }
    32                 }
    33 
    34                 System.Runtime.InteropServices.Marshal.Copy(bitmapBytes, 0, bmpData.Scan0, length);
    35             }
    36             bitmap.UnlockBits(bmpData);
    37 
    38             bitmap.Save(filename);
    39         }

    方法三:直接写入bmpData.Scan0

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

     1         /// <summary>
     2         /// 把OpenGL渲染的内容保存到图片文件。
     3         /// </summary>
     4         /// <param name="x">左下角坐标为(0, 0)</param>
     5         /// <param name="y">左下角坐标为(0, 0)</param>
     6         /// <param name="width">宽度</param>
     7         /// <param name="height">高度</param>
     8         /// <param name="filename"></param>
     9         public static void Save2Picture(int x, int y, int width, int height, string filename)
    10         {
    11             var pdata = new UnmanagedArray<Pixel>(width * height);
    12             GL.ReadPixels(x, y, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pdata.Header);
    13             var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
    14             var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
    15             var bitmap = new Bitmap(width, height, format);
    16             Rectangle bitmapRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
    17             System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
    18             unsafe
    19             {
    20                 var array = (byte*)bmpData.Scan0.ToPointer();
    21                 int index = 0;
    22                 for (int j = height - 1; j >= 0; j--)
    23                 {
    24                     for (int i = 0; i < width; i++)
    25                     {
    26                         Pixel v = pdata[index++];
    27                         array[j * bmpData.Stride + i * 4 + 0] = v.b;
    28                         array[j * bmpData.Stride + i * 4 + 1] = v.g;
    29                         array[j * bmpData.Stride + i * 4 + 2] = v.r;
    30                         array[j * bmpData.Stride + i * 4 + 3] = v.a;
    31                     }
    32                 }
    33             }
    34             bitmap.UnlockBits(bmpData);
    35 
    36             bitmap.Save(filename);
    37         }

    方法四:ReadPixels直接搞定

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

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

     1         /// <summary>
     2         /// 把OpenGL渲染的内容保存到图片文件。
     3         /// </summary>
     4         /// <param name="x">左下角坐标为(0, 0)</param>
     5         /// <param name="y">左下角坐标为(0, 0)</param>
     6         /// <param name="width">宽度</param>
     7         /// <param name="height">高度</param>
     8         /// <param name="filename"></param>
     9         public static void Save2Picture(int x, int y, int width, int height, string filename)
    10         {
    11             var format = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
    12             var lockMode = System.Drawing.Imaging.ImageLockMode.WriteOnly;
    13             var bitmap = new Bitmap(width, height, format);
    14             var bitmapRect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
    15             System.Drawing.Imaging.BitmapData bmpData = bitmap.LockBits(bitmapRect, lockMode, format);
    16             GL.ReadPixels(x, y, width, height, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, bmpData.Scan0);
    17             bitmap.UnlockBits(bmpData);
    18             bitmap.RotateFlip(RotateFlipType.Rotate180FlipX);
    19 
    20             bitmap.Save(filename);
    21         }

    总结

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

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

    欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL

  • 相关阅读:
    Java基础知识➣面向对象(八)
    Linux(CentOS7)安装Tomcat
    Java基础知识➣发送Emai和访问MySQL数据库(七)
    Java基础知识➣网络Socket(六)
    JS 的点点滴滴
    git 快速入门(二)
    zxing 生成二维码
    js生成二维码
    Markdown简介
    java常用string inputStream转换
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/save-opengl-rendering-result-to-picture-using-glReadPixels.html
Copyright © 2011-2022 走看看