0

I'm using this library in order to render an STL:

enter image description here

How do we convert this STL into a BITMAP or IMAGE?

This method is responsible for generating the STL:

private void ReadSelectedFile(string fileName)
{
    STLReader stlReader = new STLReader(fileName);
    TriangleMesh[] meshArray = stlReader.ReadFile();
    modelVAO = new Batu_GL.VAO_TRIANGLES();
    modelVAO.parameterArray = STLExport.Get_Mesh_Vertices(meshArray);
    modelVAO.normalArray = STLExport.Get_Mesh_Normals(meshArray);
    modelVAO.color = Color.Crimson;

    minPos = stlReader.GetMinMeshPosition(meshArray);
    maxPos = stlReader.GetMaxMeshPosition(meshArray);
    orb.Reset_Orientation();
    orb.Reset_Pan();
    orb.Reset_Scale();

    if (stlReader.Get_Process_Error())
    { 
        modelVAO = null;
        /* if there is an error, deinitialize the gl monitor to clear the screen */
        Batu_GL.Configure(GL_Monitor, Batu_GL.Ortho_Mode.CENTER);
        GL_Monitor.SwapBuffers();
    }
}

How do generate an image/bitmap and save it?

I've stumbled upon this, specifically this method:

// Returns a System.Drawing.Bitmap with the contents of the current framebuffer
public static Bitmap GrabScreenshot()
{
    if (GraphicsContext.CurrentContext == null)
        throw new GraphicsContextMissingException();

    Bitmap bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
    System.Drawing.Imaging.BitmapData data =
        bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
    GL.ReadPixels(0, 0, this.ClientSize.Width, this.ClientSize.Height, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);
    bmp.UnlockBits(data);

    bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
    return bmp;
}

However, I'm getting this issue:

System.Runtime.InteropServices.ExternalException
  HResult=0x80004005
  Message=A generic error occurred in GDI+.
  Source=System.Drawing
  StackTrace:
   at System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams)
   at System.Drawing.Image.Save(String filename, ImageFormat format)
   at System.Drawing.Image.Save(String filename)
   at STLViewer.AppMainForm.ReadSelectedFile(String fileName) in C:\Users\alexg\Source\Repos\STL-Viewer\STL-Viewer\AppMainForm.cs:line 152
   at STLViewer.AppMainForm.FileMenuImportBt_Click(Object sender, EventArgs e) in C:\Users\alexg\Source\Repos\STL-Viewer\STL-Viewer\AppMainForm.cs:line 162
   at System.Windows.Forms.ToolStripItem.RaiseEvent(Object key, EventArgs e)
   at System.Windows.Forms.ToolStripMenuItem.OnClick(EventArgs e)
   at System.Windows.Forms.ToolStripItem.HandleClick(EventArgs e)
   at System.Windows.Forms.ToolStripItem.HandleMouseUp(MouseEventArgs e)
   at System.Windows.Forms.ToolStripItem.FireEventInteractive(EventArgs e, ToolStripItemEventType met)
   at System.Windows.Forms.ToolStripItem.FireEvent(EventArgs e, ToolStripItemEventType met)
   at System.Windows.Forms.ToolStrip.OnMouseUp(MouseEventArgs mea)
   at System.Windows.Forms.ToolStripDropDown.OnMouseUp(MouseEventArgs mea)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
   at System.Windows.Forms.ToolStrip.WndProc(Message& m)
   at System.Windows.Forms.ToolStripDropDown.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
   at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.Run(Form mainForm)
   at STLViewer.Program.Main() in C:\Users\alexg\Source\Repos\STL-Viewer\STL-Viewer\Program.cs:line 19
Alex Gordon
  • 57,446
  • 287
  • 670
  • 1,062
  • STL is actually C++ standard library, STL-format is file format. There is no expectation that people actually pay attention to tags when posting, but still being precise is nice. – Alexei Levenkov Dec 02 '22 at 19:52
  • you [glReadPixels(...) your rendered screen into memory](https://stackoverflow.com/a/38549548/2521214) and save it as image ... so you need to encode it in some recognized image fileformat that is simple enough like BMP or TGA. Or use some component or lib that can do this for you like libpng ... – Spektre Dec 03 '22 at 06:20
  • @Spektre thanks so much could u point to an example on stackoverflow? – Alex Gordon Dec 04 '22 at 16:05
  • @AlexGordon I do not code in C# but there surely is some class/lib supporting image save like TBitmap or TJepeg or similar ... if not you can hardcode some specific [BMP format](https://en.wikipedia.org/wiki/BMP_file_format) (copying header from existing BMP for example created in paint) and just changing resolution in it and store the scanline into file ... I could bust simple C++/VCL example if it helps ... however you woul dneed to port it to C# and change the file access to what you have at disposal – Spektre Dec 04 '22 at 17:32
  • ill continue to throw bounty points at this if needed – Alex Gordon Dec 08 '22 at 21:04
  • Worth attaching sample STL file to the question. The reason is I copy pasted your code and `GrabScreenshot` produced (correct) bmp image without any exceptions for my STL file. Or maybe your actual method is not exactly the same as in the question (because the one in question cannot compile - it references this but is static). – Evk Dec 09 '22 at 18:47
  • Stack trace also shows Image.Save which is not called anywhere in provided code. It's nice to have full code in question because you never know where the problem might be. – Evk Dec 09 '22 at 18:56
  • I hate to sound greedy but what happened to the bounty? – CommanderLake Dec 19 '22 at 01:59
  • i can make another bounty, you want it? – Alex Gordon Dec 26 '22 at 16:12

3 Answers3

2

OK I just busted up small C++/OpenGL/VCL example of this:

void gl_save_bmp(AnsiString name,int xs,int ys)
    {
    int hnd;
    // header extracted from 24bit uncompressed BMP image
    DWORD hdr[54>>2]={0x3ACE4D42,0x0,0x360000,0x280000,0x640000,0x320000,0x10000,0x18,0x3A980000,0x0,0x0,0x0,0x0};
    // chnage resolution
    ((DWORD*)(((BYTE*)hdr)+0x12))[0]=xs;
    ((DWORD*)(((BYTE*)hdr)+0x16))[0]=ys;
    // read pixel data from OpenGL into memory
    BYTE *dat=new BYTE[xs*ys*3];
    glReadPixels(0,0,xs,ys,GL_BGR,GL_UNSIGNED_BYTE,dat);
    // save header
    hnd=FileCreate(name);
    FileWrite(hnd,hdr,54);
    // save pixel data
    FileWrite(hnd,dat,xs*ys*3);
    // free up stuff
    FileClose(hnd);
    delete[] dat;
    }

simply call it after you render your scene providing out filename and resolution of your OpenGL window ...

Just port it to C# and change the file access to whatever you got at disposal. The header was extracted from uncompressed 24bit BMP created in MS Paint (win7 x64 and bigger than 1x1 size as those have different encoding for god knows what reason) ... and just changed the resolution according to BMP format specs

Here sample output it produced:

output

just beware:

  • that for resolutions not divisible by 4 it might need some align tweaking (I am too lazy to check it)...
  • use binary file access mode or functions
  • on platforms with different endianess (order of High/Low bytes) you would need to store the xs,ys on per byte (or reverse BYTE order before storing to hdr[])
Spektre
  • 49,595
  • 11
  • 110
  • 380
1

Using the Method you provided in your question I just added a SaveFileDialog and a Save Bitmap item to the File menu with this Click event handler code and it seems to work just fine:

private void saveBitmapToolStripMenuItem_Click(object sender, EventArgs e){
    if(saveFileDialog1.ShowDialog(this) != DialogResult.OK) return;
    var bmp = GrabScreenshot();
    bmp.Save(saveFileDialog1.FileName, ImageFormat.Bmp);
}
Bitmap GrabScreenshot(){
    if(GraphicsContext.CurrentContext == null) throw new GraphicsContextMissingException();
    var bmp = new Bitmap(ClientSize.Width, ClientSize.Height);
    var data = bmp.LockBits(ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
    GL.ReadPixels(0, 0, ClientSize.Width, ClientSize.Height, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);
    bmp.UnlockBits(data);
    bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
    return bmp;
}
1

In the ReadSelectedFile method, you can use the GrabScreenshot method to generate a Bitmap of the current framebuffer. Then, you can save this Bitmap to a file using the Save method of the Bitmap class

check box
  • 136
  • 1