Physikant's picture

Drawing text in a GLControl in C++/CLI

Hi!

I'm programming C++/CLI and I have to. Since I never used OpenGL before, I wanted to try OpenTK for basic usage of it. My main goal is to draw graphs of scientific data using openGL for speed purposes.
I managed to translate the sample code and do some basic line-drawing, moving of stuff etc. , so openTK is working with C++/CLI.
Now I need to print Text to my GLControl.
I tried to translate the code from here:
http://www.opentk.com/node/1554?page=1
But the instructions "ref", "out" and "using", as well as the new defintion of "Dispose" aren't accepted by C++/CLI and I don't know a workaround.
Do you know any other easy-to-access method for drawing text to a GLControl? Or do you know how I can fix my translation problem?

Here is my translation:

using namespace System;
using namespace System::Drawing;
using namespace System::Windows::Forms;
using namespace System::Drawing::Imaging;
using namespace System::Collections::Generic;
using namespace System::Runtime::InteropServices;
 
using namespace OpenTK;
using namespace OpenTK::Graphics;
using namespace OpenTK::Graphics::OpenGL;
using namespace OpenTK::Input;
 
ref class MyTextWriter
{
private: static Font^ TextFont = gcnew Font(FontFamily::GenericSansSerif, 8);
private: static Bitmap^ TextBitmap;
private: List<PointF>^ _positions;
private: List<String^>^ _lines;
private: List<Brush^>^ _colours;
private: int _textureId;
private: Size^ _clientSize;
 
public: void Update(int ind, String^ newText)
{
	if (ind < _lines->Count)
   {
      _lines[ind] = newText;
      UpdateText();
   }
}
 
 
public: MyTextWriter(Size^ ClientSize, Size^ areaSize)
{
   _positions = gcnew List<PointF>();
   _lines = gcnew List<String^>();
   _colours = gcnew List<Brush^>();
 
   TextBitmap = gcnew Bitmap(areaSize->Width, areaSize->Height);
   this->_clientSize = ClientSize;
   _textureId = CreateTexture();
}
 
private: int CreateTexture()
{
   int textureId;
   GL::TexEnv(TextureEnvTarget::TextureEnv, TextureEnvParameter::TextureEnvMode, (float)TextureEnvMode::Replace);//Important, or wrong color on some computers
   Bitmap^ bitmap = TextBitmap;
   GL::GenTextures(1, /*out*/ textureId);
   GL::BindTexture(TextureTarget::Texture2D, textureId);
 
   BitmapData^ data = bitmap->LockBits(Rectangle(0,0,bitmap->Width, bitmap->Height), ImageLockMode::ReadOnly, System::Drawing::Imaging::PixelFormat::Format32bppArgb);
   GL::TexImage2D(TextureTarget::Texture2D, 0, PixelInternalFormat::Rgba, data->Width, data->Height, 0, OpenTK::Graphics::OpenGL::PixelFormat::Bgra, PixelType::UnsignedByte, data->Scan0);
   GL::TexParameter(TextureTarget::Texture2D, TextureParameterName::TextureMinFilter, (int)TextureMinFilter::Linear);
   GL::TexParameter(TextureTarget::Texture2D, TextureParameterName::TextureMagFilter, (int)TextureMagFilter::Linear);
   //GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Nearest);
   //GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Nearest);
   GL::Finish();
   bitmap->UnlockBits(data);
   return textureId;
}
// public void Dispose() 
public: ~MyTextWriter()
{
   if (_textureId > 0)
   GL::DeleteTexture(_textureId);
}
 
public: void Clear()
{
   _lines->Clear();
   _positions->Clear();
   _colours->Clear();
}
 
public: void AddLine(String^ s, PointF pos, Brush^ col)
{
   _lines->Add(s);
   _positions->Add(pos);
   _colours->Add(col);
   UpdateText();
}
 
public: void UpdateText()
{
   if (_lines->Count > 0)
   {
	   /*using*/System::Drawing::Graphics^ gfx = System::Drawing::Graphics::FromImage(TextBitmap);
      {
		 gfx->Clear(Color::Black);
		 gfx->TextRenderingHint = System::Drawing::Text::TextRenderingHint::ClearTypeGridFit;
         for (int i = 0; i < _lines->Count; i++)
		 {
            gfx->DrawString(_lines[i], TextFont, _colours[i], _positions[i]);
		 }
      }
 
	  System::Drawing::Imaging::BitmapData^ data = TextBitmap->LockBits(Rectangle(0, 0, TextBitmap->Width, TextBitmap->Height),
		  System::Drawing::Imaging::ImageLockMode::ReadOnly, System::Drawing::Imaging::PixelFormat::Format32bppArgb);
	  GL::TexSubImage2D(TextureTarget::Texture2D, 0, 0, 0, TextBitmap->Width, TextBitmap->Height, OpenTK::Graphics::OpenGL::PixelFormat::Bgra, PixelType::UnsignedByte, data->Scan0);
      TextBitmap->UnlockBits(data);
   }
}
 
public: void Draw()
{
	GL::PushMatrix();
	GL::LoadIdentity();
 
	Matrix4% ortho_projection = Matrix4::CreateOrthographicOffCenter(0, (float)_clientSize->Width, (float)_clientSize->Height, 0, -1, 1);
	GL::MatrixMode(MatrixMode::Projection);
 
	GL::PushMatrix();
	GL::LoadMatrix(/*ref*/ ortho_projection);
 
	GL::Enable(EnableCap::Blend);
	GL::BlendFunc(BlendingFactorSrc::One, BlendingFactorDest::DstColor);
	GL::Enable(EnableCap::Texture2D);
	GL::BindTexture(TextureTarget::Texture2D, _textureId);
 
 
	GL::Begin(PrimitiveType::Quads);
	GL::TexCoord2(0, 0); GL::Vertex2(0, 0);
	GL::TexCoord2(1, 0); GL::Vertex2(TextBitmap->Width, 0);
	GL::TexCoord2(1, 1); GL::Vertex2(TextBitmap->Width, TextBitmap->Height);
	GL::TexCoord2(0, 1); GL::Vertex2(0, TextBitmap->Height);
	GL::End();
	GL::PopMatrix();
 
	GL::Disable(EnableCap::Blend);
	GL::Disable(EnableCap::Texture2D);
 
	GL::MatrixMode(MatrixMode::Modelview);
	GL::PopMatrix();
}
};

As you can see, I changed the Dispose-member to a destructor, simply removed the out-command and omitted the ref-attribute by making the Matrix4 ortho_projection a Matrix4%.
Now it compiles but when I use it, my image in the GLControl is broken and only the Text can be read.

Best regards,
Nikolas


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture
Quote:

Now it compiles but when I use it, my image in the GLControl is broken and only the Text can be read.

Screenshot? Do you mean that the text is overwriting the image, instead of being blended with it? Or something different entirely?

Physikant's picture

Hey,
thanks for the effort, but I solved this one myself, the draw() function had to be placed in the correct spot inside the render() function of my window.
Now basically everything works, except for a pretty nasty error which drives me crazy:
The text is only visible for dark backgrounds AND the font can't be black. If the background is too bright, the text gets brighter and brighter until it isn't visible anymore for white or close to white. If the text ist Color.Black, it's generally invisible.
Any ideas how this can happen?
I wrote a complete form just for showing this error. I tried to simplify it as much as possible, but the error is still happening. If you wan't to try it yourself:
The text vanishes if GL::ClearColor() is very bright, especially Color::White. It also vanishes if SolidBrush() in the gfx->DrawString() where the text-bitmap is created is Color::Black. So if you want to see the text, try ClearColor Color::Black and DrawString color Color::Red.
I know, it's not C#, but you can replace :: and -> by . and you have basically C# code ...
Sorry, it's pretty long, but most of it is just necessary but common stuff. I hope anybody has enough time to take a short look :(
By the way: Fiddler, you are the guy who made OpenTK happen, right? If so: thank you VERY VERY much :)
Here it is:

#pragma once
 
 
namespace FontErrorShow {
 
	using namespace System;
	using namespace System::ComponentModel;
	using namespace System::Collections;
	using namespace System::Windows::Forms;
	using namespace System::Data;
	using namespace System::Drawing;
	using namespace System::Drawing::Imaging;
	using namespace System::Collections::Generic;
	using namespace System::Runtime::InteropServices;
 
	using namespace OpenTK;
	using namespace OpenTK::Graphics;
	using namespace OpenTK::Graphics::OpenGL;
	using namespace OpenTK::Input;
 
	public ref class Form1 : public System::Windows::Forms::Form
	{
	private: bool loaded;
	public:
		Form1(void)
		{
			loaded = false;
			InitializeComponent();
 
		}
 
	protected:
		~Form1()
		{
			if (components)
			{
				delete components;
			}
		}
	private: OpenTK::GLControl^  glControl1;
	protected: 
 
	private:
		System::ComponentModel::Container ^components;
 
#pragma region Windows Form Designer generated code
		void InitializeComponent(void)
		{
			this->glControl1 = (gcnew OpenTK::GLControl());
			this->SuspendLayout();
			// 
			// glControl1
			// 
			this->glControl1->BackColor = System::Drawing::Color::Black;
			this->glControl1->Location = System::Drawing::Point(16, 6);
			this->glControl1->Name = L"glControl1";
			this->glControl1->Size = System::Drawing::Size(400, 400);
			this->glControl1->TabIndex = 0;
			this->glControl1->VSync = false;
			this->glControl1->Load += gcnew System::EventHandler(this, &Form1::glControl1_Load);
			this->glControl1->Paint += gcnew System::Windows::Forms::PaintEventHandler(this, &Form1::glControl1_Paint);
			this->glControl1->Resize += gcnew System::EventHandler(this, &Form1::glControl1_Resize);
			// 
			// Form1
			// 
			this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
			this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
			this->ClientSize = System::Drawing::Size(434, 412);
			this->Controls->Add(this->glControl1);
			this->Name = L"Form1";
			this->Text = L"Form1";
			this->ResumeLayout(false);
 
		}
#pragma endregion
	private: System::Void glControl1_Load(System::Object^  sender, System::EventArgs^  e) 
	{
		loaded = true;
		GL::ClearColor(Color::Green); 
		SetupViewport();
		Application::Idle += gcnew System::EventHandler(this, &Form1::Application_Idle);
 
    }
 
	void Application_Idle(System::Object^  sender, System::EventArgs^  e)
    {
	  while (glControl1->IsIdle)
      {
          render();
      }
    }
 
	private: void SetupViewport()
    {
		int w = glControl1->Width;
		int h = glControl1->Height;
		GL::MatrixMode(MatrixMode::Projection);
		GL::LoadIdentity();
		GL::Ortho(0, w, 0, h, -1, 1);
		GL::Viewport(0, 0, w, h);
    }
 
    private: System::Void glControl1_Resize(System::Object^  sender, System::EventArgs^  e) 
	{
		if (!loaded)
			return;
		SetupViewport();
		glControl1->Invalidate();
	}
 
	private: System::Void glControl1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) 
	{
		if (!loaded)
			return;
		render();
	}
    private: void render()
	{
		GL::Clear(ClearBufferMask::ColorBufferBit | ClearBufferMask::DepthBufferBit);
		GL::MatrixMode(MatrixMode::Modelview);
		create_text();
		glControl1->SwapBuffers();
	}
	public: void create_text(void)
	{
		int textureId;
		GL::TexEnv(TextureEnvTarget::TextureEnv, TextureEnvParameter::TextureEnvMode, (float)TextureEnvMode::Replace);
		Bitmap^ bitmap = gcnew Bitmap(this->glControl1->Width, this->glControl1->Height);
		GL::GenTextures(1, textureId);
		GL::BindTexture(TextureTarget::Texture2D, textureId);
 
 		BitmapData^ data = bitmap->LockBits(Rectangle(0,0,bitmap->Width, bitmap->Height), ImageLockMode::ReadOnly, System::Drawing::Imaging::PixelFormat::Format32bppArgb);
		GL::TexImage2D(TextureTarget::Texture2D, 0, PixelInternalFormat::Rgba, data->Width, data->Height, 0, OpenTK::Graphics::OpenGL::PixelFormat::Bgra, PixelType::UnsignedByte, data->Scan0);
		GL::TexParameter(TextureTarget::Texture2D, TextureParameterName::TextureMinFilter, (int)TextureMinFilter::Linear);
		GL::TexParameter(TextureTarget::Texture2D, TextureParameterName::TextureMagFilter, (int)TextureMagFilter::Linear);
		//GL::TexParameter(TextureTarget::Texture2D, TextureParameterName::TextureMagFilter, (int)All::Nearest);
		//GL::TexParameter(TextureTarget::Texture2D, TextureParameterName::TextureMinFilter, (int)All::Nearest);
		GL::Finish();
		bitmap->UnlockBits(data);
		System::Drawing::Graphics^ gfx = System::Drawing::Graphics::FromImage(bitmap);
		gfx->Clear(Color::Black);
		gfx->TextRenderingHint = System::Drawing::Text::TextRenderingHint::ClearTypeGridFit;
		gfx->DrawString("testtext",gcnew System::Drawing::Font("Arial",10.0),gcnew SolidBrush(Color::Black),(float)this->glControl1->Width/2.0,(float)this->glControl1->Height/2.0); 
		delete gfx; 
		System::Drawing::Imaging::BitmapData^ data2 = bitmap->LockBits(Rectangle(0, 0, bitmap->Width, bitmap->Height), System::Drawing::Imaging::ImageLockMode::ReadOnly, System::Drawing::Imaging::PixelFormat::Format32bppArgb); 
		GL::TexSubImage2D(TextureTarget::Texture2D, 0, 0, 0, bitmap->Width, bitmap->Height, OpenTK::Graphics::OpenGL::PixelFormat::Bgra, PixelType::UnsignedByte, data2->Scan0); 
		bitmap->UnlockBits(data); 
		GL::PushMatrix();
		GL::LoadIdentity();
 
		Matrix4% ortho_projection = Matrix4::CreateOrthographicOffCenter(0, (float)this->glControl1->Width, (float)this->glControl1->Width, 0, -1, 1);
		GL::MatrixMode(MatrixMode::Projection);
 
		GL::PushMatrix();
		GL::LoadMatrix(ortho_projection);
 
		GL::Enable(EnableCap::Blend);
		GL::BlendFunc(BlendingFactorSrc::One, BlendingFactorDest::DstColor);
		GL::Enable(EnableCap::Texture2D);
		GL::BindTexture(TextureTarget::Texture2D, textureId);
 
 
		GL::Begin(PrimitiveType::Quads);
		GL::TexCoord2(0, 0); GL::Vertex2(0, 0);
		GL::TexCoord2(1, 0); GL::Vertex2(bitmap->Width, 0);
		GL::TexCoord2(1, 1); GL::Vertex2(bitmap->Width, bitmap->Height);
		GL::TexCoord2(0, 1); GL::Vertex2(0,bitmap->Height);
		GL::End();
		GL::PopMatrix();
 
		GL::Disable(EnableCap::Blend);
		GL::Disable(EnableCap::Texture2D);
 
		GL::MatrixMode(MatrixMode::Modelview);
		GL::PopMatrix();
	}
 
	};
}
Physikant's picture

I found that it's a problem with the blending, so these lines:

GL::Enable(EnableCap::Blend);
GL::BlendFunc(BlendingFactorSrc::One, BlendingFactorDest::DstColor);

If I remove these, the text is always displayed, but everything else isn't displayed since the text-image is of the color:
gfx->Clear(Color::Black);
I tried to use Color::Transparent here, but it didn't work.
Any Ideas how I can blend my text over my drawn lines (I wan't to display scientific data) without losing the text due to blending with a bright color or losing everything else since no blending is applied?
Best regards,
Nikolas