Drawing Image RGB Color Distribution Using OpenGL

.

This article allows ones to:

  • draw an image RGB color distribution using OpenGL in a dialog,
  • use OpenGL display lists,
  • draw antialiased smooth colored lines,
  • implement mouse interaction (rotation),
  • store and load an image from resource.

We assume that our aim is to display the RGB color distribution of the following image (Figure 1) in an interactive way. In particular, it would be nice to display the RGB cube and the image color cloud.

Figure 1: Our goal is to display the RGB color distribution of this 24 bits BMP color image in an intuitive way. OpenGL will help us.

Figure 2: Illustrates two viewpoints of the color distribution in a dialog. The viewpoint is modifed using the left mouse button that implements the x/y rotation, the dialog may be resized and a popup menu allows us to load and view the current image (Figure 3).

Figure 3: Two examples of color distribution in the RGB cube seen under two distinct viewpoints. In particular, the red cloud is linked to the nose of the baboon seen in Figure 1. Notice that the lines are antialiased, each saturated color is drawn with a sphere , and the cloud is painted using the GL_POINTS primitive.

Figure 4: A popup menu allows to load a 24 bits RGB image, change the OpenGL clear color (recalled back color), and view the current image in a dialog window.

Initialization of the Rendering Engine

Following lines make the rendering engine to be initialized in a dialog.
//*********************************
// OnCreate 
//*********************************
int CColorDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
 // Init OpenGL engine
 HWND hWnd = GetSafeHwnd();
 HDC hDC = ::GetDC(hWnd);

 if(SetWindowPixelFormat(hDC)==FALSE)
  return 0;

 if(CreateViewGLContext(hDC)==FALSE)
  return 0;

 // The lines are antialiased
 glEnable(GL_LINE_SMOOTH);
 glEnable(GL_BLEND);
 glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
 glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
 glLineWidth(1.5); // required
 glPointSize(1.0);
 glPolygonMode(GL_FRONT,GL_LINE);
 glPolygonMode(GL_BACK,GL_LINE);
 glShadeModel(GL_SMOOTH);

 // Precalculate display lists
 BuildListCube();
 BuildListCloud();

 return 0;
}
//**********************************************
// OpenGL
//**********************************************
BOOL CColorDlg::SetWindowPixelFormat(HDC hDC)
{
 PIXELFORMATDESCRIPTOR pixelDesc;

 pixelDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR);
 pixelDesc.nVersion = 1;

 pixelDesc.dwFlags = PFD_DRAW_TO_WINDOW 
                   | PFD_SUPPORT_OPENGL 
                   | PFD_DOUBLEBUFFER 
                   | PFD_STEREO_DONTCARE;

 pixelDesc.iPixelType = PFD_TYPE_RGBA;
 pixelDesc.cColorBits = 32;
 pixelDesc.cRedBits = 8;
 pixelDesc.cRedShift = 16;
 pixelDesc.cGreenBits = 8;
 pixelDesc.cGreenShift = 8;
 pixelDesc.cBlueBits = 8;
 pixelDesc.cBlueShift = 0;
 pixelDesc.cAlphaBits = 0;
 pixelDesc.cAlphaShift = 0;
 pixelDesc.cAccumBits = 64;
 pixelDesc.cAccumRedBits = 16;
 pixelDesc.cAccumGreenBits = 16;
 pixelDesc.cAccumBlueBits = 16;
 pixelDesc.cAccumAlphaBits = 0;
 pixelDesc.cDepthBits = 32;
 pixelDesc.cStencilBits = 8;
 pixelDesc.cAuxBuffers = 0;
 pixelDesc.iLayerType = PFD_MAIN_PLANE;
 pixelDesc.bReserved = 0;
 pixelDesc.dwLayerMask = 0;
 pixelDesc.dwVisibleMask = 0;
 pixelDesc.dwDamageMask = 0;

 m_GLPixelIndex = ChoosePixelFormat(hDC,&pixelDesc);
 if(m_GLPixelIndex==0) // Choose default
 {
  m_GLPixelIndex = 1;
  if(!DescribePixelFormat(hDC,m_GLPixelIndex,
  sizeof(PIXELFORMATDESCRIPTOR),&pixelDesc))
  return FALSE;
 }

 if(!SetPixelFormat(hDC,m_GLPixelIndex,&pixelDesc))
  return FALSE;

 return TRUE;
}

//*********************************
// CreateViewGLContext 
//*********************************
BOOL CColorDlg::CreateViewGLContext(HDC hDC)
{
 m_hGLContext = wglCreateContext(hDC);

 if(m_hGLContext==NULL)
  return FALSE;

 if(wglMakeCurrent(hDC,m_hGLContext)==FALSE)
  return FALSE;

 return TRUE;
}
//*********************************
// OnDestroy 
//*********************************
void CColorDlg::OnDestroy() 
{
 CDialog::OnDestroy();

 if(wglGetCurrentContext() != NULL)
  wglMakeCurrent(NULL,NULL);

 if(m_hGLContext != NULL)
 {
  wglDeleteContext(m_hGLContext);
  m_hGLContext = NULL;
 }

 glDeleteLists(1,2);
}

RENDERING

The RGB cube and the cloud are rendered in two distinct display lists. The cube is a fixed part, while the color cloud is rebuild each time a new image is loaded.
//*********************************
// OnPaint 
//*********************************
void CColorDlg::OnPaint() 
{
 // ** Draw scene **
 CPaintDC dc(this);
 RenderScene();
 SwapBuffers(dc.m_ps.hdc); // double buffer
}
//*********************************
// RenderScene 
//*********************************
void CColorDlg::RenderScene()
{
 ::glClearColor((float)GetRValue(m_BackColor)/255.0f,
                (float)GetGValue(m_BackColor)/255.0f,
                (float)GetBValue(m_BackColor)/255.0f,1.0f);
 ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 ::glPushMatrix();
 ::glTranslated(0.0,0.0,-8.0);
 ::glRotated(m_xRotate, 1.0, 0.0, 0.0);
 ::glRotated(m_yRotate, 0.0, 1.0, 0.0);
 ::glCallList(1); // cube
 ::glCallList(2); // clouds
 ::glPopMatrix();
}

//***********************************************
// BuildList
//***********************************************
void CColorDlg::BuildListCube(BOOL list)
{
 GLUquadricObj* pQuadric = gluNewQuadric();

 if(list)
  ::glNewList(1,GL_COMPILE_AND_EXECUTE);

 float x = m_Size;

 // RGB cube
 glBegin(GL_LINE_LOOP);
  glColor3ub(0,0,0);
  glVertex3d(-x,-x,-x);
  glColor3ub(255,0,0);
  glVertex3d(x,-x,-x);
  glColor3ub(255,255,0);
  glVertex3d(x,x,-x);
  glColor3ub(0,255,0);
  glVertex3d(-x,x,-x);
 glEnd();

 glBegin(GL_LINE_LOOP);
  glColor3ub(0,0,255);
  glVertex3d(-x,-x,x);
  glColor3ub(255,0,255);
  glVertex3d(x,-x,x);
  glColor3ub(255,255,255);
  glVertex3d(x,x,x);
  glColor3ub(0,255,255);
  glVertex3d(-x,x,x);
 glEnd();

 glBegin(GL_LINES);
  glColor3ub(0,0,0);
  glVertex3d(-x,-x,-x);
  glColor3ub(0,0,255);
  glVertex3d(-x,-x,x);
  glColor3ub(255,0,0);
  glVertex3d(x,-x,-x);
  glColor3ub(255,0,255);
  glVertex3d(x,-x,x);
  glColor3ub(255,255,0);
  glVertex3d(x,x,-x);
  glColor3ub(255,255,255);
  glVertex3d(x,x,x);
  glColor3ub(0,255,0);
  glVertex3d(-x,x,-x);
  glColor3ub(0,255,255);
  glVertex3d(-x,x,x);
 glEnd();

 // Spheres
 glPolygonMode(GL_FRONT,GL_FILL);
 glPolygonMode(GL_BACK,GL_FILL);
 float radius = 0.1f;

 glPushMatrix();
 glTranslated(-m_Size,-m_Size,-m_Size);
 glColor3ub(0,0,0);
 gluSphere(pQuadric,radius,12,12); 
 glPopMatrix();

 glPushMatrix();
 glTranslated(m_Size,-m_Size,-m_Size);
 glColor3ub(255,0,0);
 gluSphere(pQuadric,radius,12,12); 
 glPopMatrix();

 glPushMatrix();
 glTranslated(-m_Size,m_Size,-m_Size);
 glColor3ub(0,255,0);
 gluSphere(pQuadric,radius,12,12); 
 glPopMatrix();

 glPushMatrix();
 glTranslated(-m_Size,-m_Size,m_Size);
 glColor3ub(0,0,255);
 gluSphere(pQuadric,radius,12,12); 
 glPopMatrix();

 glPushMatrix();
 glTranslated(m_Size,m_Size,-m_Size);
 glColor3ub(255,255,0);
 gluSphere(pQuadric,radius,12,12); 
 glPopMatrix();

 glPushMatrix();
 glTranslated(-m_Size,m_Size,m_Size);
 glColor3ub(0,255,255);
 gluSphere(pQuadric,radius,12,12); 
 glPopMatrix();

 glPushMatrix();
 glTranslated(m_Size,-m_Size,m_Size);
 glColor3ub(255,0,255);
 gluSphere(pQuadric,radius,12,12); 
 glPopMatrix();

 glPushMatrix();
 glTranslated(m_Size,m_Size,m_Size);
 glColor3ub(255,255,255);
 gluSphere(pQuadric,radius,12,12); 
 glPopMatrix();

 if(list)
  ::glEndList();

 gluDeleteQuadric(pQuadric);
}

//***********************************************
// BuildListCloud
//***********************************************
void CColorDlg::BuildListCloud()
{
 TRACE("Build list cloud...");

 // Image area
 unsigned int area = m_Image.GetWidth()
                   * m_Image.GetHeight();

 TRACE("area : %d...",area);

 // Need valid image (24 bits)
 if(area == 0 ||
  m_Image.GetDepth() != 24)
 return;

 // Maximum -> area distinct colors / 2^24
 // This table is memory expansive, but short lived
 TRACE("alloc...");
 unsigned char *pTable = new unsigned char[16777216];
 memset(pTable,0,16777216); // init 0

 // Image data
 unsigned int wb32 = m_Image.GetWidthByte32();
 unsigned char *pData = m_Image.GetData();

 // Build a new list
 TRACE("build list...");
 int nb = 0;
 ::glNewList(2,GL_COMPILE_AND_EXECUTE);
 glBegin(GL_POINTS);
 float tmp = 2.0f/255.0f*m_Size;
 for(unsigned int j=0;j<m_Image.GetHeight();j++)
  for(unsigned int i=0;i<m_Image.GetWidth();i++)
  {
   unsigned char b = pData[wb32*j+3*i];
   unsigned char g = pData[wb32*j+3*i+1];
   unsigned char r = pData[wb32*j+3*i+2];
   if(!pTable[b*65536+g*256+r])
   {
    glColor3ub(r,g,b);
    float x = -m_Size+(float)r*tmp;
    float y = -m_Size+(float)g*tmp;
    float z = -m_Size+(float)b*tmp;
    glVertex3d(x,y,z);
    pTable[b*65536+g*256+r] = 1;
    nb++;
   }
  }
 
 glEnd();
 ::glEndList();
 TRACE("%d points...",nb);

 TRACE("cleanup...");
 delete [] pTable;
 TRACE("ok\n");
}

Downloads

Download source - 10 Kb
Download (built) demo project - 530 Kb



Comments

  • wglDeleteContext error

    Posted by Legacy on 05/08/2001 12:00am

    Originally posted by: de Halleux Jonathan

    I tried to run your code in debug mode but when wglDeleteContext is called in void " CColorDlg::OnDestroy " an error occur ("user break point from code blablabla").

    I have the same problem for another application. Do you have any clue about this ?


    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Intelligent N+X Redundancy, Placement Affinities, & Future Proofing in the Virtualized Data Center Virtualization brought about the ability to simplify business continuity management in IT. Workload portability and data replication capabilities mean that physical infrastructure failures no longer need impact application services, and they can rapidly be recovered even in the event of complete site failure. However, Enterprises and Service Providers face new challenges ensuring they have enough compute …

  • On-demand Event Event Date: June 23, 2015 Business transformation is not just important, it is vital to the success of all organizations in the 21st century marketplace. How IT approaches and executes the move from cost center to revenue contributor will not only transform the IT organization, it will change how companies retain competitive advantage and meet the requirements of internal and external customers. This shift in thinking is not new, but actualizing it is. Check out this eSeminar to learn the …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date