I am creating a Windows bitmap from a OpenGL multisampled pbuffer with a transparent background. I am using the alpha channel to control transparency in the resulting bitmap. The problem is that the alpha component in the RGBA value is inconsistent with the color components.
For example, I have a 2x FSAA pbuffer. I reset the RGBA color to (0,0,0,0). I then draw a filled white triangle. A pixel near the edge of the triangle will have these RGBA values: (188,188,188,127). I would have expected the RGB components to be the same as the alpha component. As it is, when I draw the bitmap into my device context, some of the blended pixels have color components that have overflowed (RGB component value would be greater than 255 which produces incorrect results). I am using the Win32 API AlphaBlend to draw the bitmap. It uses the following formula (red component example used) for normalized [0-1] values:
Dst.Red = Src.Red + (1 - Src.Alpha) * Dst.Red
So if I have a white destination pixel, and I substitute in the unnormalized values, I get this:
Dst.Red = 188 + (255 - 127) * 255 / 255 = 316 (Overflow!)
In my example, if the RGB components were the same as the alpha value, then there would never be an overflow. So my question is, for white fragments, why are the resulting RGB components not the same as the alpha component?
Here’s my code (MFC dialog application):
class CAlphaBlendDlg : public CDialogEx
{
...
HGLRC m_hBitmapRC;
HPBUFFERARB m_hBitmapPbuffer;
HDC m_hBitmapPbufferDC;
...
};
BOOL CAlphaBlendDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
CClientDC dc (this);
CRect clientRect;
GetClientRect(clientRect);
// create an off-screen buffer for bitmap rendering
int numMultiSamples = 2;
const int attributes[] =
{
// need OpenGL supported
WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
// enable render to pbuffer
WGL_DRAW_TO_PBUFFER_ARB, GL_TRUE,
// at least 24 bits for depth
WGL_DEPTH_BITS_ARB, 24,
// need RGBA colors
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
// at least 24 bits for color
WGL_COLOR_BITS_ARB, 24,
// need alpha channel for transparent background
WGL_ALPHA_BITS_ARB, 8,
// we don't need double buffering
WGL_DOUBLE_BUFFER_ARB, GL_FALSE,
// enable FSAA
WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
WGL_SAMPLES_ARB, numMultiSamples,
// end with a NULL terminator
NULL
};
int newPixelFormat = 0;
UINT numFormats = 0;
bool usePixelFormat = false;
wglChoosePixelFormat (dc.m_hDC, attributes, NULL, 1, &newPixelFormat, &numFormats);
// try to use OpenGL pbuffers to render to an off-screen buffer
m_hBitmapPbuffer = wglCreatePbuffer (dc.m_hDC, newPixelFormat, clientRect.Width(), clientRect.Height(), NULL);
m_hBitmapPbufferDC = wglGetPbufferDC (m_hBitmapPbuffer);
/* No need to set the pixel format because the pbuffer is already
created with the pixel format specified. */
m_hBitmapRC = ::wglCreateContext (m_hBitmapPbufferDC);
}
void CAlphaBlendDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect clientRect;
GetClientRect (clientRect);
CDC *pBitmapDC = new CDC();
pBitmapDC->CreateCompatibleDC (&dc);
BITMAPINFO bitMapInfo;
memset (&bitMapInfo, 0, sizeof(bitMapInfo));
bitMapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitMapInfo.bmiHeader.biWidth = clientRect.right;
bitMapInfo.bmiHeader.biHeight = clientRect.bottom;
bitMapInfo.bmiHeader.biPlanes = 1;
bitMapInfo.bmiHeader.biBitCount = 32;
bitMapInfo.bmiHeader.biCompression = BI_RGB;
void *pBitmapBits = NULL;
HBITMAP hBitmapDIB = ::CreateDIBSection (pBitmapDC->m_hDC, &bitMapInfo, DIB_RGB_COLORS, &pBitmapBits, NULL, 0);
pBitmapDC->SelectObject (hBitmapDIB);
VERIFY(wglMakeCurrent (m_hBitmapPbufferDC, m_hBitmapRC) != FALSE);
// set up orthogonal projection so we can specify window coordinates for vertices
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
gluOrtho2D (0, clientRect.right, clientRect.bottom, 0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity();
glViewport (0, 0, clientRect.Width(), clientRect.Height());
glDisable(GL_DEPTH_TEST);
glDrawBuffer (GL_FRONT);
glClearColor(0,0,0,0);
glClear(GL_COLOR_BUFFER_BIT);
// draw a bunch of filled white triangles
glColor4ub (255,255,255,255);
static const int numTriangles = 100;
static const double triangleSizeFactor = 0.1;
const int triangleSize = (int) (clientRect.Width() * triangleSizeFactor);
glBegin(GL_TRIANGLES);
for (int index = 0; index < numTriangles; index++)
{
int x = rand() % (clientRect.Width() - triangleSize) + triangleSize / 2;
int y = rand() % (clientRect.Height() - triangleSize) + triangleSize / 2;
for (int vertexIndex = 0; vertexIndex < 3; vertexIndex++)
{
int vx = x + (rand() % triangleSize) - triangleSize / 2;
int vy = y + (rand() % triangleSize) - triangleSize / 2;
glVertex3i (vx,vy,0);
}
}
glEnd();
// read the pixels
glReadBuffer(GL_FRONT);
glReadPixels (0, 0, clientRect.Width(), clientRect.Height(), GL_BGRA_EXT, GL_UNSIGNED_BYTE, pBitmapBits);
VERIFY(wglMakeCurrent (NULL, NULL) != FALSE);
// use pBitmapBits to draw onto DC using AlphaBlend
// ...
// cleanup
delete pBitmapDC;
pBitmapDC = NULL;
::DeleteObject (hBitmapDIB);
hBitmapDIB = NULL;
}