Correctly displaying s 32 bit transparent PNG file in a DC - mfc

This is my method for loading a transparent PNG file into a buffer:
/* static */ void CRibbonButton::LoadImageFromRelativeFilespec(HGLOBAL& rhDIB, bool bLarge,
const CString& rstrImageRelFilespec, UINT32& ruDIBW, int& ruDIBH)
{
USES_CONVERSION;
using namespace RibbonBar ;
// Clear any existing image away.
if (rhDIB != NULL)
::GlobalFree(rhDIB);
// Build the correct filespec.
CString strThisEXE = _T("");
::GetModuleFileName(AfxGetInstanceHandle(),
strThisEXE.GetBuffer(_MAX_PATH + 1),_MAX_PATH);
strThisEXE.ReleaseBuffer();
LPCTSTR lpszPath = (LPCTSTR)strThisEXE ;
LPTSTR lpszFilename = ::PathFindFileName(lpszPath);
CString strPath = strThisEXE.Left( (int)(lpszFilename - lpszPath) );
CString strFilespec = strPath ;
::PathAppend(strFilespec.GetBuffer(_MAX_PATH + 1), rstrImageRelFilespec);
strFilespec.ReleaseBuffer();
HISSRC hSrc = is6_OpenFileSource(CT2A((LPCTSTR)strFilespec));
if (hSrc)
{
// read it
UINT32 w, h;
rhDIB = is6_ReadImage(hSrc, &w, &h, 2, 0); // the "2" = load directly to DIB, in the lowest bit depth possible.
if (rhDIB)
{
// get the dimensions
is6_DIBWidth((BITMAPINFOHEADER *)rhDIB, &ruDIBW);
is6_DIBHeight((BITMAPINFOHEADER *)rhDIB, &ruDIBH);
UINT32 bc;
is6_DIBBitCount((BITMAPINFOHEADER *)rhDIB, &bc);
is6_ClearJPGInputMarkers();
}
else
{
AfxMessageBox(_T("Can't read that image"));
}
is6_CloseSource(hSrc);
}
}
And this is the rendering code:
/* virtual */ void CRibbonButton::PaintData(CDC& rDC)
{
CDC dcMem ;
dcMem.CreateCompatibleDC(NULL); // Screen.
const CRect& rrctImage = GetImageBounds();
if (m_hDIB)
{
// draw to a memory DC
CDC memDC;
if (memDC.CreateCompatibleDC(&rDC))
{
CBitmap bmp;
if (bmp.CreateCompatibleBitmap(&rDC, rrctImage.Width(), rrctImage.Height()))
{
CBitmap *ob = memDC.SelectObject(&bmp);
if (ob)
{
// dark red background
memDC.FillSolidRect(CRect(rrctImage.left, rrctImage.top, rrctImage.Width(), rrctImage.Height()), RibbonBar::kBackColour);
// stretchDrawDIB is typically the fastest way to draw an image from ImgSource.
BOOL ok = is6_StretchDrawDIB(memDC.m_hDC, (BITMAPINFOHEADER *)m_hDIB, 0, 0, m_uDIBW, m_uDIBH);
if (!ok)
{
memDC.SetBkMode(TRANSPARENT);
memDC.SetTextColor(RGB(255, 255, 255));
memDC.TextOut(rrctImage.left, rrctImage.top, _T("X"));
}
// copy this to the window
rDC.BitBlt(rrctImage.left, rrctImage.top, rrctImage.Width(), rrctImage.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(ob);
}
}
}
}
dcMem.DeleteDC();
}
It is not drawing the transparent PNG file correctly. I always end up with a black background.
I am using the ISSource libraries for rendering. But the company is now out of business. I am using version 6 library.
Update
Based on the answer I am now loading and rendering the image like this:
CRect rct;
CImage img;
img.Load(_T("d:\\Publishers.png"));
rct.SetRect(rrctImage.left, rrctImage.top, rrctImage.left + img.GetWidth(), rrctImage.top + img.GetHeight());
img.TransparentBlt(rDC.GetSafeHdc(), rct, RGB(255,255,255));
But why do I still get black for where the transparency was set?
If I don't pass RGB(255,255,255) as the last parameter
and use the default I get an exception.
Update
According to the documentation for TransparentBit:
TransparentBlt is supported for source bitmaps of 4 bits per pixel and 8 bits per pixel. Use CImage::AlphaBlend to specify 32 bits-per-pixel bitmaps with transparency.
So, I have stopped using:
img.TransparentBlt(rDC.GetSafeHdc(), rct);
Now I am using:
img.AlphaBlend(rDC.GetSafeHdc(), rct.left, rct.top, rct.Width(), rct.Height(), rct.left, rct.top, rct.Width(), rct.Height(), 0xff, AC_SRC_OVER);
I don't see anything. I confirm the coordinates are right by doing:
CBrush br;
br.CreateStockObject(BLACK_BRUSH);
rDC.FrameRect(rct, &br);
Why do I not see anything?

This is much to complicate. There are existing methods in CImage.
Check out CImage::AlphaBlend or CImage::TransparentBlt.
AlphaBlend: The Dst fields are the coordinates in your DC. the Src values are inside your picture. Usually they start with 0,0 and have the width and height as values. Is xSrc/ySrc are not 0 you have an offset in the source.

Related

How to draw text with transparency using GDI?

My goal is to dynamically put some arbitrary text into an HICON image (at runtime.) I'm using the following code:
//Error checks are omitted for brevity
//First create font
LOGFONT lf = {0};
lf.lfHeight = -58;
lf.lfWeight = FW_NORMAL;
lf.lfOutPrecision = OUT_TT_PRECIS; //Use TrueType fonts for anti-alliasing
lf.lfQuality = CLEARTYPE_QUALITY;
lstrcpy(lf.lfFaceName, L"Segoe UI");
HFONT hFont = ::CreateFontIndirect(&lf);
//HICON hIcon = original icon to use as a source
//I'm using a large 256x256 pixel icon
hIcon = (HICON)::LoadImage(theApp.m_hInstance, MAKEINTRESOURCE(IDI_ICON_GREEN_DIAMOND), IMAGE_ICON, 256, 256, LR_DEFAULTCOLOR);
ICONINFO ii = {0};
::GetIconInfo(hIcon, &ii);
BITMAP bm = {0};
::GetObject(ii.hbmColor, sizeof(bm), &bm);
SIZE szBmp = {bm.bmWidth, bm.bmHeight};
HDC hDc = ::GetDC(hWnd);
HDC hMemDC = ::CreateCompatibleDC(hDc);
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
HGDIOBJ hOldFont = ::SelectObject(hMemDC, hFont);
::SetBkMode(hMemDC, TRANSPARENT);
::SetTextColor(hMemDC, RGB(255, 0, 0)); //Red text
//Draw text
//NOTE that DrawText API behaves in a similar way
::TextOut(hMemDC, 0, 0, L"Hello", 5);
::SelectObject(hMemDC, hOldFont);
::SelectObject(hMemDC, hOldBmp);
//We need a simple mask bitmap for the icon
HBITMAP hBmpMsk = ::CreateBitmap(szBmp.cx, szBmp.cy, 1, 1, NULL);
ICONINFO ii2 = {0};
ii2.fIcon = TRUE;
ii2.hbmColor = ii.hbmColor;
ii2.hbmMask = hBmpMsk;
//Create updated icon
HICON hIcon2 = ::CreateIconIndirect(&ii2);
//Cleanup
::DeleteObject(hBmpMsk);
::DeleteDC(hMemDC);
::ReleaseDC(hWnd, hDc);
::DeleteObject(ii.hbmColor);
::DeleteObject(ii.hbmMask);
::DeleteObject(hFont);
and then I can display the icon in my window from OnPaint() handler (so that I can see how it turns out) as such:
::DrawIconEx(dc.GetSafeHdc(), 0, 0,
hIcon2,
256, 256, NULL,
::GetSysColorBrush(COLOR_BTNFACE),
DI_NORMAL);
So here's what I get:
To see what's going on pixel-wise in my hIcon2 I called GetDIBits on its ii.hbmColor from the code above. The resulting pixel array where my word "Hello" was supposed to be shown looked like this:
The pixels are encoded as BGRA in that memory dump, so the 4th byte in each DWORD stands for transparency: 0=transparent, FF=opaque. But in this case TextOut doesn't fill out transparency, or leaves it as 0, which is interpreted as "fully transparent." Instead it seems to pre-multiply it into the RGB colors themselves.
Note that if I keep looking further down the same bitmap, where the green diamond begins, the image pixels seem to have transparency bytes set correctly:
Any idea how to draw text so that the API could set those transparency bytes?
EDIT: As was suggested below I tried the following GDI+ method:
HGDIOBJ hOldBmp = ::SelectObject(hMemDC, ii.hbmColor);
Graphics grpx(hMemDC);
RectF rcfTxt(0.0f, 0.0f, (REAL)szBmp.cx, (REAL)szBmp.cy);
Font gdiFont(L"Segoe UI", 58.0f, FontStyleRegular, UnitPixel);
SolidBrush gdiBrush(Color(255, 0, 0));
StringFormat gdiSF;
gdiSF.SetAlignment(StringAlignmentNear);
gdiSF.SetFormatFlags(StringFormatFlagsNoWrap);
gdiSF.SetHotkeyPrefix(HotkeyPrefixNone);
//The reason I was using GDI was because I was setting
//spacing between letters using SetTextCharacterExtra()
//Unfortunately with GDI+ this does not work!
HDC hTmpDC = grpx.GetHDC();
::SetTextCharacterExtra(hTmpDC, -4); //This doesn't do anything!
grpx.ReleaseHDC(hTmpDC);
grpx.DrawString(L"Hello", 5, &gdiFont, rcfTxt, &gdiSF, &gdiBrush);
::SelectObject(hMemDC, hOldBmp);
and besides not being able to set character spacing (which I could with GDI using SetTextCharacterExtra) here's what I got (slightly enlarged for visibility):
So clearly still an issue with transparency.
Taken from an old post by Microsoft MVP Mike D Sutton here.
When you create a DC it initially has default 'stock' objects selected
into it, including the stock 1*1*1 Bitmap. Since there is a Bitmap
already selected into the DC when you call DrawText() it will still
try and render to it even though pretty much everything (apart from
one pixel) will be clipped.
What you need to do is to create a Bitmap,
either DDB or DIBSection, and select that into your DC before drawing
to it.
First though you need to find the size of your Bitmap since you
want it large enough to display your text in, so for that you use the
DrawText() call again on the initial DC but include the DT_CALCRECT
flag. What this does is rather than drawing anything it simply
measures how large the text is and dumps that into the RECT you pass
the call. From here you can go ahead and create your DIBSection using
those dimensions and select it into your DC. Finally perform your
existing DrawText ()call (you may also want to use SetBkMode/Color())
which will render the text to the DIBSection from which you can get at
the data.
This seems to work pretty well here:
HBITMAP CreateAlphaTextBitmap(LPCSTR inText, HFONT inFont, COLORREF inColour) {
int TextLength = (int)strlen(inText);
if (TextLength <= 0) return NULL;
// Create DC and select font into it
HDC hTextDC = CreateCompatibleDC(NULL);
HFONT hOldFont = (HFONT)SelectObject(hTextDC, inFont);
HBITMAP hMyDIB = NULL;
// Get text area
RECT TextArea = {0, 0, 0, 0};
DrawText(hTextDC, inText, TextLength, &TextArea, DT_CALCRECT);
if ((TextArea.right > TextArea.left) && (TextArea.bottom > TextArea.top)) {
BITMAPINFOHEADER BMIH;
memset(&BMIH, 0x0, sizeof(BITMAPINFOHEADER));
void *pvBits = NULL;
// Specify DIB setup
BMIH.biSize = sizeof(BMIH);
BMIH.biWidth = TextArea.right - TextArea.left;
BMIH.biHeight = TextArea.bottom - TextArea.top;
BMIH.biPlanes = 1;
BMIH.biBitCount = 32;
BMIH.biCompression = BI_RGB;
// Create and select DIB into DC
hMyDIB = CreateDIBSection(hTextDC, (LPBITMAPINFO)&BMIH, 0, (LPVOID*)&pvBits, NULL, 0);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTextDC, hMyDIB);
if (hOldBMP != NULL) {
// Set up DC properties
SetTextColor(hTextDC, 0x00FFFFFF);
SetBkColor(hTextDC, 0x00000000);
SetBkMode(hTextDC, OPAQUE);
// Draw text to buffer
DrawText(hTextDC, inText, TextLength, &TextArea, DT_NOCLIP);
BYTE* DataPtr = (BYTE*)pvBits;
BYTE FillR = GetRValue(inColour);
BYTE FillG = GetGValue(inColour);
BYTE FillB = GetBValue(inColour);
BYTE ThisA;
for (int LoopY = 0; LoopY < BMIH.biHeight; LoopY++) {
for (int LoopX = 0; LoopX < BMIH.biWidth; LoopX++) {
ThisA = *DataPtr; // Move alpha and pre-multiply with RGB
*DataPtr++ = (FillB * ThisA) >> 8;
*DataPtr++ = (FillG * ThisA) >> 8;
*DataPtr++ = (FillR * ThisA) >> 8;
*DataPtr++ = ThisA; // Set Alpha
}
}
// De-select bitmap
SelectObject(hTextDC, hOldBMP);
}
}
// De-select font and destroy temp DC
SelectObject(hTextDC, hOldFont);
DeleteDC(hTextDC);
// Return DIBSection
return hMyDIB;
}
If you need an example of how to call it then try something like this
(inDC is the DC to render to):
void TestAlphaText(HDC inDC, int inX, int inY) {
const char *DemoText = "Hello World!\0";
RECT TextArea = {0, 0, 0, 0};
HFONT TempFont = CreateFont(50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "Arial\0");
HBITMAP MyBMP = CreateAlphaTextBitmap(DemoText, TempFont, 0xFF);
DeleteObject(TempFont);
if (MyBMP) { // Create temporary DC and select new Bitmap into it
HDC hTempDC = CreateCompatibleDC(inDC);
HBITMAP hOldBMP = (HBITMAP)SelectObject(hTempDC, MyBMP);
if (hOldBMP) {
BITMAP BMInf; // Get Bitmap image size
GetObject(MyBMP, sizeof(BITMAP), &BMInf);
// Fill blend function and blend new text to window
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 0x80;
bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(inDC, inX, inY, BMInf.bmWidth, BMInf.bmHeight,
hTempDC, 0, 0, BMInf.bmWidth, BMInf.bmHeight, bf);
// Clean up
SelectObject(hTempDC, hOldBMP);
DeleteObject(MyBMP);
DeleteDC(hTempDC);
}
}
}
All credit to answer and code go to original posters on that forum, I've simply reposted it so that this answer will be valid if the links die.
This reply is coming almost 3 years after the question was posted, but people still consult these things long into the future. So I'll explain what's happening.
DrawText (and other GDI text functions) will work on a transparent bitmap. The text is not coming out black even though it displays that way. The alpha channel is set to 0 on all pixels the text draws to, overriding whatever alpha you had set previously. If you set an alpha value in SetTextColor the text will render all black. If you're feeling ambitious you can run through pixel by pixel and target anything not your fill color (which requires a single fill color) but the problem then becomes one of the nature of ClearType being overridden and all alphas are set to whatever you set them to. The text ends up looking very funky. If you use a constant alpha for your background fill you can simply do a blanket run across the entire bitmap's bits after the text is drawn and reset all the alpha values. Since you have to read a byte to determine if it's background or not, you might as well just set every pixel's alpha to whatever the standard alpha is for that image and bypass the slow compares. This works reasonably well and I've found it to be very acceptable. In this day and age, MS should have taken care of this long ago but it's not to be.
https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-antialiasing-with-text-use
Gdiplus::Bitmap bmp( your_Width, your_Height, PixelFormat64bppARGB);
//PixelFormat64bppARGB ARGB needed
FontFamily fontFamily(L"Arial");
Font font(&fontFamily, 29, FontStyleRegular, UnitPoint);
Gdiplus::RectF rectF(00.0f, 10.0f, your_Width, your_Height);
StringFormat stringFormat;
SolidBrush solidBrush(Color(63, 0, 0, 255));
stringFormat.SetAlignment(StringAlignmentCenter);
//solidBrush Color(63, 0, 0, 255) ARGB neede
graphics.SetTextRenderingHint(TextRenderingHintAntiAlias);
graphics.DrawString("your_text", -1, &font, rectF, &stringFormat, &solidBrush);
//TextRenderingHintAntiAlias this needed

Create 32 bit color Icon programmatically

I would like to create 32 bit color icons programmatically using C++ and Win API. For this purpose I use the following code which I found here.
HICON CreateSolidColorIcon(COLORREF iconColor, int width, int height)
{
// Obtain a handle to the screen device context.
HDC hdcScreen = GetDC(NULL);
// Create a memory device context, which we will draw into.
HDC hdcMem = CreateCompatibleDC(hdcScreen);
// Create the bitmap, and select it into the device context for drawing.
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, width, height);
HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcMem, hbmp);
// Draw your icon.
//
// For this simple example, we're just drawing a solid color rectangle
// in the specified color with the specified dimensions.
HPEN hpen = CreatePen(PS_SOLID, 1, iconColor);
HPEN hpenOld = (HPEN)SelectObject(hdcMem, hpen);
HBRUSH hbrush = CreateSolidBrush(iconColor);
HBRUSH hbrushOld = (HBRUSH)SelectObject(hdcMem, hbrush);
Rectangle(hdcMem, 0, 0, width, height);
SelectObject(hdcMem, hbrushOld);
SelectObject(hdcMem, hpenOld);
DeleteObject(hbrush);
DeleteObject(hpen);
// Create an icon from the bitmap.
//
// Icons require masks to indicate transparent and opaque areas. Since this
// simple example has no transparent areas, we use a fully opaque mask.
HBITMAP hbmpMask = CreateCompatibleBitmap(hdcScreen, width, height);
ICONINFO ii;
ii.fIcon = TRUE;
ii.hbmMask = hbmpMask;
ii.hbmColor = hbmp;
HICON hIcon = CreateIconIndirect(&ii);
DeleteObject(hbmpMask);
// Clean-up.
SelectObject(hdcMem, hbmpOld);
DeleteObject(hbmp);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
// Return the icon.
return hIcon;
}
In principle the code works and I can use it to create colored icons at runtime using the Win API. However, I have some problems and questions about that code (and creating icons in general) which I would like to discuss.
The icons created with this function don't seem to be of 32 bit color depth. If I use a color like RGB(218, 112, 214) I would expect it to be some light purple. However, the actual displayed color is gray. How can I change the code such that the color is really 32 bit RGB?
The icon created is completly filled with the color, I would like to have a thin black boarder around it... how can this be achieved?
In the MSDN documentation (a bit downwards) it is mentioned that "Before closing, your application must use DestroyIcon to destroy any icon it created by using CreateIconIndirect. It is not necessary to destroy icons created by other functions. " However, in the documentation for e.g. CreateIcon in MSDN it is said that "When you are finished using the icon, destroy it using the DestroyIcon function." which is pretty much a contradiction. When do I actually have to destroy the icon?
Do these rules then also apply when I add the icon to an image list and this list to a combobox? I.e. do I have to clean up the image list and each associated icon?
Any help is highly appreciated.
When do I actually have to destroy the icon?
read about DestroyIcon
It is only necessary to call DestroyIcon for icons and cursors
created with the following functions: CreateIconFromResourceEx (if
called without the LR_SHARED flag), CreateIconIndirect, and
CopyIcon. Do not use this function to destroy a shared icon. A
shared icon is valid as long as the module from which it was loaded
remains in memory. The following functions obtain a shared icon.
LoadIcon
LoadImage (if you use the LR_SHARED flag)
CopyImage (if you use the LR_COPYRETURNORG flag and the hImage parameter is a shared icon)
CreateIconFromResource
CreateIconFromResourceEx (if you use the LR_SHARED flag)
so you need call DestroyIcon for not shared icon, when you are finished using it
ComboBoxEx not destroy image list which you assign to it with CBEM_SETIMAGELIST - so this image list must be valid until ComboBoxEx valid and you must destroy it yourself later.
ImageList_AddIcon
Because the system does not save hicon, you can destroy it after the
macro returns
in other words ImageList_AddIcon make copy of your icon, and you can destroy your original icon, after macro return
for create 32 bit color icon try code like this:
HICON CreateGradientColorIcon(COLORREF iconColor, int width, int height)
{
HICON hIcon = 0;
ICONINFO ii = { TRUE };
ULONG n = width * height;
if (PULONG lpBits = new ULONG[n])
{
PULONG p = lpBits;
ULONG x, y = height, t;
do
{
x = width, t = --y << 8;
do
{
*p++ = iconColor | ((t * --x) / n << 24);
} while (x);
} while (y);
if (ii.hbmColor = CreateBitmap(width, height, 1, 32, lpBits))
{
if (ii.hbmMask = CreateBitmap(width, height, 1, 1, 0))
{
hIcon = CreateIconIndirect(&ii);
DeleteObject(ii.hbmMask);
}
DeleteObject(ii.hbmColor);
}
delete [] lpBits;
}
return hIcon;
}
when I draw (DrawIconEx(, DI_IMAGE|DI_MASK)) this icon over green mesh I view next:
To everyone who has stumbled upon this solution, I am simply posting a little bit more of a documented solution to RbMm's answer. This is basically the same as his solution (maybe not as performant, I'm not sure):
static HICON CreateIconFromBytes(HDC DC, int width, int height, uint32* bytes) {
HICON hIcon = NULL;
ICONINFO iconInfo = {
TRUE, // fIcon, set to true if this is an icon, set to false if this is a cursor
NULL, // xHotspot, set to null for icons
NULL, // yHotspot, set to null for icons
NULL, // Monochrome bitmap mask, set to null initially
NULL // Color bitmap mask, set to null initially
};
uint32* rawBitmap = new uint32[width * height];
ULONG uWidth = (ULONG)width;
ULONG uHeight = (ULONG)height;
uint32* bitmapPtr = rawBitmap;
for (ULONG y = 0; y < uHeight; y++) {
for (ULONG x = 0; x < uWidth; x++) {
// Bytes are expected to be in RGB order (8 bits each)
// Swap G and B bytes, so that it is in BGR order for windows
uint32 byte = bytes[x + y * width];
uint8 A = (byte & 0xff000000) >> 24;
uint8 R = (byte & 0xff0000) >> 16;
uint8 G = (byte & 0xff00) >> 8;
uint8 B = (byte & 0xff);
*bitmapPtr = (A << 24) | (R << 16) | (G << 8) | B;
bitmapPtr++;
}
}
iconInfo.hbmColor = CreateBitmap(width, height, 1, 32, rawBitmap);
if (iconInfo.hbmColor) {
iconInfo.hbmMask = CreateCompatibleBitmap(DC, width, height);
if (iconInfo.hbmMask) {
hIcon = CreateIconIndirect(&iconInfo);
if (hIcon == NULL) {
Log::Warning("Failed to create icon.");
}
DeleteObject(iconInfo.hbmMask);
} else {
Log::Warning("Failed to create color mask.");
}
DeleteObject(iconInfo.hbmColor);
} else {
Log::Warning("Failed to create bitmap mask.");
}
delete[] rawBitmap;
return hIcon;
}
This solution will work with STB image library for loading images. So you can literally just load an image with stb, then pass the byte data to this function, and you will get an icon as a result. I had a little bit of trouble setting the icon as well, and eventually did this to get that to work:
HICON icon = CreateIconFromBytes(DC, image.m_Width, image.m_Height, image.m_Pixels);
SendMessage(WND, WM_SETICON, ICON_SMALL, (LPARAM)icon);
SendMessage(WND, WM_SETICON, ICON_BIG, (LPARAM)icon);
SendMessage(WND, WM_SETICON, ICON_SMALL2, (LPARAM)icon);
The only thing you should note about this is that you should probably use 3 different sized icons for the SendMessage() functions, but other than that this worked good for me :)
Edit:
Here's the links to official MSDN documentation as well.
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createiconindirect
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createbitmap
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createcompatiblebitmap
https://learn.microsoft.com/en-us/windows/win32/menurc/using-icons

C++ How to display Imebra image (char buffer) as an image?

I use an Imebra library to read DICOM files. I need to display DICOM image. Imebra library gives me an image in buffer (I follow this documentation):
load file
std::unique_ptr<DataSet> MyDataSet(CodecFactory::load("IM1"));
retrieving an image
Image* image(MyDataSet->getImageApplyModalityTransform(0));
int iWidth = image->getWidth();
int iHeight = image->getHeight();
TransformsChain chain;
if (ColorTransformsFactory::isMonochrome(image->getColorSpace()))
{
VOILUT voilutTransform;
vois_t vois = MyDataSet->getVOIs();
list<LUT*> luts;
for (size_t scanLUTs(0); ; ++scanLUTs)
{
try { luts.push_back(MyDataSet->getLUT(TagId(tagId_t::VOILUTSequence_0028_3010), scanLUTs)); }
catch (const MissingDataElementError&) { break; }
}
if (!vois.empty()) voilutTransform.setCenterWidth(vois[0].center, vois[0].width);
else if (!luts.empty()) voilutTransform.setLUT(*(luts.front()));
else voilutTransform.applyOptimalVOI(*image, 0, 0, iWidth, iHeight);
DrawBitmap draw(chain);
size_t requestedBufSize = draw.getBitmap(*image, drawBitmapType_t::drawBitmapRGBA, 4, 0, 0);
string buffer(requestedBufSize, char(0));
draw.getBitmap(*image, drawBitmapType_t::drawBitmapRGBA, 4, &buffer.at(0), requestedBufSize);
So I got a buffer with bitmap inside it. (following each word of documentation).
Then I get bitmap from this buffer:
hBitmap = CreateBitmap(iWidth, iHeight, 1, 24, buffer);
Now I'm trying to dispay it in MFC PictureControl (on dialog window):
void CImebraDlg::OnPaint()
{
CPaintDC dc(this);
CDC memdc;
BITMAP b;
//m_DicomImage - CStatic variable of PictureControl
m_DicomImage.GetClientRect(&rect);
::GetObject(theApp.hBitmap, sizeof(BITMAP), &b);
memdc.CreateCompatibleDC(&dc);
memdc.SelectObject(&bmp);
dc.StretchBlt(0, 0, rect.Width(), rect.Height(), &memdc,
0, 0, b.bmWidth, b.bmHeight, SRCCOPY);
dc.MoveTo(0, 0);
}
And nothig happens, no image in PictureControl.
Trying like this:
void CImebraDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
SetImageToRictureControl(theApp.hBitmap);
}
void CImebraDlg::SetImageToRictureControl(HBITMAP hbmp)
{
m_DicomImage.SetBitmap(hbmp);
UpdateData(FALSE);
}
the same result...
BITMAP structure you can see on the screenshot.
bmBits is NULL.. Is this the reason of not displaying dicom image?
How to display a DICOM image right with imebra lib?

Creating, displaying, and then accessing bitmap/DIB data (w/o GetBitmapBits())

I have inherited an old-school MFC Windows CE program, and am having to make some modifications to it. As part of this I have to create a monochrome image with text on it, and both display it on a screen as well as send each row of the image to a printer one at a time.
I originally used a bitmap, and had success using DrawText() and getting a test string (Hello World) to display on the screen (this code is in Figure 1). However, I hit a wall at the stage where I am looking to extract the wrap data from the bitmap. What I am trying to get is an array with 1s or 0s representing black or white. I had first thought I would use GetBitmapBits() but unfortunately the code I am working with is so old that function is not supported yet. I thought I could get around this issue by using GetBitmap() and then accessing the bmBits parameter. However this appears to always be null which was confirmed when I found the following link: Why does GetObject return an BITMAP with null bmBits?.
My next attempt was to follow the advice in the link and use CreateDIBSection() instead of CreateCompatibleBitmap(). This seems like the right path, and I should have access to the data I want, but unfortunately I cannot get the DIB to display (code is in Figure 2). I suspect I am doing something wrong in creating the header of the DIB, but I cannot figure out what my mistake is.
If anyone has suggestions for a way to access the data in the bitmap, or can see what I am doing wrong with the DIB, I would greatly appreciate the help!
*** FIGURE 1: Code to create and display a bitmap
void CRunPage::OnPaint()
{
CPaintDC dc(this); // property page device context for painting
CBitmap mBmp; // CBitmap object for displaying built-in bitmaps
CDC mDCMem; // CDC object to handle built-in bitmap
int iWidth, iHeight; // dimension to draw on the screen
int icurLabel, // current label index of open print file
iLabelNum; // number of labels in open print file
LPBITMAPINFOHEADER pBMIH; // bitmap header object for current label
LPBYTE pImage; // bitmap data for current label
CSize size; // size of label
int PreviewLeft,PreviewTop,PreviewWidth,PreviewHeight;
CRect Rect;
BITMAP bm;
LPVOID bmBits=NULL;
// Calculate the preview area
PreviewLeft=5;
PreviewTop=5;
GetDlgItem(IDC_RUN_NEXT)->GetWindowRect(&Rect);
ScreenToClient(&Rect);
PreviewWidth=Rect.left-PreviewLeft*2;
GetDlgItem(IDC_RUN_WRAPTEXT)->GetWindowRect(&Rect);
ScreenToClient(&Rect);
PreviewHeight=Rect.top-PreviewTop*2;
CRect textRect;
CString testText(_T("Hello World"));
CBitmap * pOldBitmap;
CBrush whiteBrush, *pOldBrush;
CPen blackPen, *pOldPen;
mDCMem.CreateCompatibleDC(&dc);
mBmp.CreateCompatibleBitmap(&dc, PreviewWidth+PreviewLeft*2, PreviewHeight+PreviewTop*2);
//mBmp.CreateCompatibleBitmap(&dc, PreviewWidth, PreviewHeight);
pOldBitmap = mDCMem.SelectObject(&mBmp);
blackPen.CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
whiteBrush.CreateSolidBrush(RGB(255,255,255));
textRect.SetRect(0,0,PreviewWidth, PreviewHeight);
// this means behind the text will be a white box w/ a black boarder
pOldBrush = mDCMem.SelectObject(&whiteBrush);
pOldPen = mDCMem.SelectObject(&blackPen);
//these commands draw on the memory-only context (mDCMem)
mDCMem.Rectangle(&textRect);
mDCMem.DrawText((LPCTSTR)testText, 11, &textRect, DT_CENTER|DT_VCENTER);
mDCMem.SelectObject(pOldBrush);
mDCMem.SelectObject(pOldPen);
dc.StretchBlt(PreviewLeft,PreviewTop, PreviewWidth, PreviewHeight, & mDCMem, 0, 0, PreviewWidth, PreviewHeight, SRCCOPY);
mDCMem.SelectObject(pOldBitmap);
}
*** FIGURE 2: Trying to use a DIB instead of a bitmap
void CRunPage::OnPaint()
{
CPaintDC dc(this); // property page device context for painting
CBitmap mBmp; // CBitmap object for displaying built-in bitmaps
CDC mDCMem; // CDC object to handle built-in bitmap
int iWidth, iHeight; // dimension to draw on the screen
int icurLabel, // current label index of open print file
iLabelNum; // number of labels in open print file
LPBITMAPINFOHEADER pBMIH; // bitmap header object for current label
LPBYTE pImage; // bitmap data for current label
CSize size; // size of label
int PreviewLeft,PreviewTop,PreviewWidth,PreviewHeight;
CRect Rect;
BITMAP bm;
// Calculate the preview area
PreviewLeft=5;
PreviewTop=5;
GetDlgItem(IDC_RUN_NEXT)->GetWindowRect(&Rect);
ScreenToClient(&Rect);
PreviewWidth=Rect.left-PreviewLeft*2;
GetDlgItem(IDC_RUN_WRAPTEXT)->GetWindowRect(&Rect);
ScreenToClient(&Rect);
PreviewHeight=Rect.top-PreviewTop*2;
CRect textRect;
CString testText(_T("Hello World"));
CBitmap * pOldBitmap;
CBrush whiteBrush, *pOldBrush;
CPen blackPen, *pOldPen;
LPBYTE pFWandImageMem=NULL, pImageMem=NULL, pTemp=NULL;
int i=0,j=0, buffSize=0, numBytesPerRow=0, bitmapWidthPix,bitmapHeightPix;
char *numBytesPerRowString;
char temp;
void ** ppvBits;
BITMAPINFOHEADER bmif;
BITMAPINFO bmi;
HBITMAP myDIB, myOldDIB;
mDCMem.CreateCompatibleDC(&dc);
//this rect is the area in which I can draw (its x,y location is set by BitBlt or StretchBlt
//mBmp.CreateCompatibleBitmap(&dc, PreviewWidth+PreviewLeft*2, PreviewHeight+PreviewTop*2);
bmif.biSize = sizeof(BITMAPINFOHEADER);
bmif.biWidth = PreviewWidth+PreviewLeft*2;
bmif.biHeight = -(PreviewHeight+PreviewTop*2);//- means top down (I think? I tried both ways and neither worked)
bmif.biPlanes = 1;
bmif.biBitCount = 1;
bmif.biCompression = BI_RGB; // no compression
bmif.biSizeImage = 0; // Size (bytes) if image - this can be set to 0 for uncompressed images
bmif.biXPelsPerMeter = 0;
bmif.biYPelsPerMeter = 0;
bmif.biClrUsed =0;
bmif.biClrImportant = 0;
bmi.bmiColors[0].rgbBlue=0;
bmi.bmiColors[0].rgbGreen=0;
bmi.bmiColors[0].rgbRed=0;
bmi.bmiColors[0].rgbReserved=0;
bmi.bmiColors[1].rgbBlue=255;
bmi.bmiColors[1].rgbGreen=255;
bmi.bmiColors[1].rgbRed=255;
bmi.bmiColors[1].rgbReserved=0;
bmi.bmiHeader=bmif;
myDIB = CreateDIBSection(dc.GetSafeHdc(), &bmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
myOldDIB = (HBITMAP)mDCMem.SelectObject(myDIB);//SelectObject(mDCMem, myDIB);
blackPen.CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
whiteBrush.CreateSolidBrush(RGB(255,255,255));
textRect.SetRect(0,0,PreviewWidth, PreviewHeight);
// this means behind the text will be a white box w/ a black boarder
pOldBrush = mDCMem.SelectObject(&whiteBrush);
pOldPen = mDCMem.SelectObject(&blackPen);
//these commands draw on the memory-only context (mDCMem)
mDCMem.Rectangle(&textRect);
mDCMem.DrawText((LPCTSTR)testText, 11, &textRect, DT_CENTER|DT_VCENTER);
mDCMem.SelectObject(pOldBrush);
mDCMem.SelectObject(pOldPen);
dc.StretchBlt(PreviewLeft,PreviewTop, PreviewWidth, PreviewHeight, & mDCMem, 0, 0, PreviewWidth, PreviewHeight, SRCCOPY);
mDCMem.SelectObject(myOldDIB);
}
So I made two minor changes to the DIB code, and it is displaying the image correctly now.
First, I changed the way I passed in my pointer to the CreateDIBSection():
void ** ppvBits;
to
LPBYTE pBits;
And then I had to change how I passed that into CreateDIBSection. I also explicitly casted the return of CreateDIBSection() to an HBITMAP:
myDIB = CreateDIBSection(dc.GetSafeHdc(), &bmi, DIB_RGB_COLORS, (void**)&pBits, NULL, 0);
to
myDIB = (HBITMAP) CreateDIBSection(dc.GetSafeHdc(), &bmi, DIB_RGB_COLORS, ppvBits, NULL, 0);
I have not had a chance to see if I can access the image data, but I am past the initial issues now. Thanks to anyone who looked at this, and if people know how to do the first (device dependent bitmap) method I would be interested to know.

How to draw 32-bit alpha channel bitmaps?

I need to create a custom control to display bmp images with alpha channel. The background can be painted in different colors and the images have shadows so I need to truly "paint" the alpha channel.
Does anybody know how to do it?
I also want if possible to create a mask using the alpha channel information to know whether the mouse has been click on the image or on the transparent area.
Any kind of help will be appreciated!
Thanks.
Edited(JDePedro): As some of you have suggested I've been trying to use alpha blend to paint the bitmap with alpha channel. This just a test I've implemented where I load a 32-bit bitmap from resources and I try to paint it using AlphaBlend function:
void CAlphaDlg::OnPaint()
{
CClientDC dc(this);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP);
BITMAP BitMap;
bitmap.GetBitmap(&BitMap);
int nWidth = BitMap.bmWidth;
int nHeight = BitMap.bmHeight;
CBitmap *pOldBitmap = dcMem.SelectObject(&bitmap);
BLENDFUNCTION m_bf;
m_bf.BlendOp = AC_SRC_OVER;
m_bf.BlendFlags = 0;
m_bf.SourceConstantAlpha = 255;
m_bf.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(dc.GetSafeHdc(), 100, 100, nWidth, nHeight, dcMem.GetSafeHdc(), 0, 0,nWidth, nHeight,m_bf);
dcMem.SelectObject(pOldBitmap);
CDialog::OnPaint();
}
This is just a test so I put the code in the OnPaint of the dialog (I also tried the AlphaBlend function of the CDC object).
The non-transparent areas are being painted correctly but I get white where the bitmap should be transparent.
Any help???
This is a screenshot..it's not easy to see but there is a white rectangle around the blue circle:
alt text http://img385.imageshack.us/img385/7965/alphamh8.png
Ok. I got it! I have to pre-multiply every pixel for the alpha value. Someone can suggest the optimized way to do that?
For future google users, here is a working pre-multiply function. Note that this was taken from http://www.viksoe.dk/code/alphatut1.htm .
inline void PremultiplyBitmapAlpha(HDC hDC, HBITMAP hBmp)
{
BITMAP bm = { 0 };
GetObject(hBmp, sizeof(bm), &bm);
BITMAPINFO* bmi = (BITMAPINFO*) _alloca(sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
::ZeroMemory(bmi, sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD)));
bmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BOOL bRes = ::GetDIBits(hDC, hBmp, 0, bm.bmHeight, NULL, bmi, DIB_RGB_COLORS);
if( !bRes || bmi->bmiHeader.biBitCount != 32 ) return;
LPBYTE pBitData = (LPBYTE) ::LocalAlloc(LPTR, bm.bmWidth * bm.bmHeight * sizeof(DWORD));
if( pBitData == NULL ) return;
LPBYTE pData = pBitData;
::GetDIBits(hDC, hBmp, 0, bm.bmHeight, pData, bmi, DIB_RGB_COLORS);
for( int y = 0; y < bm.bmHeight; y++ ) {
for( int x = 0; x < bm.bmWidth; x++ ) {
pData[0] = (BYTE)((DWORD)pData[0] * pData[3] / 255);
pData[1] = (BYTE)((DWORD)pData[1] * pData[3] / 255);
pData[2] = (BYTE)((DWORD)pData[2] * pData[3] / 255);
pData += 4;
}
}
::SetDIBits(hDC, hBmp, 0, bm.bmHeight, pBitData, bmi, DIB_RGB_COLORS);
::LocalFree(pBitData);
}
So then your OnPaint becomes:
void MyButton::OnPaint()
{
CPaintDC dc(this);
CRect rect(0, 0, 16, 16);
static bool pmdone = false;
if (!pmdone) {
PremultiplyBitmapAlpha(dc, m_Image);
pmdone = true;
}
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
bf.SourceConstantAlpha = 255;
bf.AlphaFormat = AC_SRC_ALPHA;
HDC src_dc = m_Image.GetDC();
::AlphaBlend(dc, rect.left, rect.top, 16, 16, src_dc, 0, 0, 16, 16, bf);
m_Image.ReleaseDC();
}
And the loading of the image (in the constructor of your control):
if ((HBITMAP)m_Image == NULL) {
m_Image.LoadFromResource(::AfxGetResourceHandle(), IDB_RESOURCE_OF_32_BPP_BITMAP);
}
The way I usually do this is via a DIBSection - a device independent bitmap that you can modify the pixels of directly. Unfortunately there isn't any MFC support for DIBSections: you have to use the Win32 function CreateDIBSection() to use it.
Start by loading the bitmap as 32-bit RGBA (that is, four bytes per pixel: one red, one green, one blue and one for the alpha channel). In the control, create a suitably sized DIBSection. Then, in the paint routine
Copy the bitmap data into the DIBSection's bitmap data, using the alpha channel byte to blend the bitmap image with the background colour.
Create a device context and select the DIBSection into it.
Use BitBlt() to copy from the new device context to the paint device context.
You can create a mask given the raw bitmap data simply by looking at the alpha channel values - I'm not sure what you're asking here.
You need to do an alpha blend with your background color, then take out the alpha channel to paint it to the control.
The alpha channel should just be every 4th byte of your image. You can use that directly for your mask, or you can just copy every 4th byte to a new mask image.
Painting it is very easy with the AlphaBlend function.
As for you mask, you'll need to get the bits of the bitmap and examine the alpha channel byte for each pixel you're interested in.
An optimised way to pre-multiply the RGB channels with the alpha channel is to set up a [256][256] array containing the calculated multiplication results. The first dimension is the alpha value, the second is the R/G/B value, the values in the array are the pre-multiplied values you need.
With this array set up correctly, you can calculate the value you need like this:
R = multiplicationLookup[alpha][R];
G = multiplicationLookup[alpha][G];
B = multiplicationLookup[alpha][B];
You are on the right track, but need to fix two things.
First use ::LoadImage( .. LR_CREATEDIBSECTION ..) instead of CBitmap::LoadBitmap. Two, you have to "pre-multiply" RGB values of every pixel in a bitmap to their respective A value. This is a requirement of AlphaBlend function, see AlphaFormat description on this MSDN page. T
The lpng has a working code that does the premultiplication of the DIB data.