I don't use c++ or MFC/ATL etc so assume I know nothing.
The whole picture is that I need to take accept a PNG with a transparency layer and write it out as a JPEG with specified compression to pass to another system.
What I would like to know here, is how can I initialize a target CImage structure with solid white?
I've done this so far (yes I know it has other stylistic issues)
void ClipBoardFuncs::savePNGASJPEG(char filePath[256], char errorBuff[256]) {
int sizeOfErrorBuff = 256;
CImage imagePNG;
CImage imageJPG;
int xPNG, yPNG = 0;
imagePNG.Load(filePath);
xPNG = imagePNG.GetWidth();
yPNG = imagePNG.GetHeight();
//Create my target JPG same size and bit depth specifiying that there is no alpha channel (dwflag last param)
imageJPG.Create(xPNG, yPNG, imagePNG.GetBPP(), 0);
//Let there be white....
for (int x = 0; x <= xPNG; x++)
{
for (int y = 0; y <= yPNG; y++)
{
imageJPG.SetPixelRGB(x, y, 255, 255, 255);
}
}
//Copy the image over onto on the white
//BitBlt copies everything....
//imagePNG.BitBlt(imageJPG.GetDC(), 0, 0);
//Draw is more like the C# draw examples I've seen
imagePNG.Draw(imageJPG.GetDC(), 0, 0);
imageJPG.ReleaseDC();
HRESULT hr = NULL;
hr = imageJPG.Save(filePath, Gdiplus::ImageFormatJPEG);
imagePNG.Destroy();
imagePNG.ReleaseGDIPlus();
imageJPG.Destroy();
imageJPG.ReleaseGDIPlus();
LPCTSTR error = _com_error(hr).ErrorMessage();
strncpy_s(errorBuff, sizeOfErrorBuff, _com_error(hr).ErrorMessage(), _TRUNCATE);
}
The lovely C# people have this answer:
Convert Transparent PNG to JPG with Non-Black Background Color
But I need the c++ MFC solution to use as an exported function in a DLL.
By exported Function I mean the same architecture as you would find in kernel32.dll - sorry I do not know the terminology to differentiate that kind of DLL from one stuffed with COM objects.
Can anyone suggest a faster way to initialize the imageJPEG structure to solid white than the nested x/y for loops I have here?
Cheers
4GLGuy
The initialization can be done with Rectangle or FillRect (for this, FillRect is probably preferred--Rectangle draws an outline of the rectangle in the current pen color, which we probably don't want).
So, the sequence looks something like this:
CImage png;
png.Load(pDoc->filename);
CRect rect{ 0, 0, png.GetWidth(), png.GetHeight() };
CImage jpeg;
jpeg.Create(rect.Width(), rect.Height(), png.GetBPP());
auto dc = jpeg.GetDC();
HBRUSH white = CreateSolidBrush(RGB(255, 255, 255));
FillRect(dc, &rect, white);
png.Draw(dc, 0, 0);
jpeg.ReleaseDC();
jpeg.Save(L"Insert File name here", Gdiplus::ImageFormatJPEG);
jpeg.Destroy();
jpeg.ReleaseGDIPlus();
png.ReleaseGDIPlus();
GDI+ is "smart" enough that when you do a .Draw with an image that has an alpha channel, it takes that channel into account, without your having to use TransparentBlt (or anything similar).
SetTransparentColor will not work for what you're trying to do here. SetTransparentColor is for an image that does not have an alpha channel ("transparency layer"). You then use it to choose a color that will be treated as if it were transparent--which can certainly be useful, but isn't what you want here.
You can use memset instead, but only for colors where the red, green, and blue channels all have the same values (i.e., black, white, or some shade of grey). Otherwise, you can do the fill on your own with a nested loop, but in most cases you probably want to use FillRect instead (it may be able to use graphics hardware for acceleration, where the loop will pretty dependably just run on the CPU--worst case, they're both about the same speed, but in some cases FillRect will be faster).
imagePNG.Draw(imageJPG.GetDC(), 0, 0);
Each call to GetDC must have a subsequent call to ReleaseDC. See also CImage::GetDC documentation.
CImage::GetDC provides a handle to memory device context. This handle can be used to draw using standard GDI functions. The handle should later be cleaned up with CImage::ReleaseDC.
CImage::Draw may not know what the transparent color is. You have to use TransparentBlt to tell it what the transparent color is. For example, to replace red color with white color:
HDC hdc = imageJPG.GetDC();
CDC dc;
dc.Attach(hdc);
CRect rc(0, 0, xPNG, yPNG);
dc.FillSolidRect(&rc, RGB(255, 255, 255));
dc.Detach();
imagePNG.TransparentBlt(hdc, rc, RGB(255, 0, 0));//replace red with white
imageJPG.ReleaseDC();
...
imageJPG.Save(...);
Or just use CImage::SetTransparentColor:
imagePNG.SetTransparentColor(RGB(255, 0, 0));
imagePNG.Draw(hdc, rc);
for (int x = 0; x <= xPNG; x++){...}
To do this using a loop, change the condition in the loop to x < xPNG and x < yPNG.
Related
What do I want to achive:
I want to create a window that behaves like a WS_OVERLAPPEDWINDOW | WS_SIZEBOX(can be minimized, maximized, closed from system context menu on Alt+Space, supports Window Snapping, has shadow if it is enabled in system Performance Options -> [x] Enable shadow under window and so on). It also should be borderless (without title bar, icon, application name in title bar and so on), just client region and nothing else.
So main goals are:
Has no border;
Supports default window functionality;
Drops shadow underneath.
Don't say that it is impossible. The most simple example of such a window is Telegram Desktop. They use Qt library. Don't say I should use it. I want to learn how they did this trick. Their window is borderless, without bugs (that are described below). I know that Qt uses OpenGL inside for their window. But I also know that it should be possible to draw with GDI+ only without OpenGL.
What I have already tried:
github:melak47/BorderlessWindow This window has a tiny border around. You can see it if you resize it really quickly holding by the top or left edge of the window. We can also set margins to be like MARGINS m{0,0,0,1} for DwmExtendFrameIntoClientArea(hWnd, &m);. But window will still remain the same border which could be seen if resize.
github:rossy/borderless-window This solution is better. It propose two variants to struggle with this problem.
The border here is hidden by enabling WS_EX_LAYERED window style with chroma keying by color (e.g. magenta). But it is obvious that now we need to perform checks every time we draw something on bitmap for a specified chroma color. If user draws clear magenta RGB(255, 0, 255) we can then replace it with RGB(254, 0, 255) to avoid holes inside. But I think it is a very bad solution that cause unneccessary checks for each pixel in bitmap.
The border is now considered as a part of client area. We disable WM_EX_LAYERED and color keying and using GDI+ to draw transparent colors on top border. The this is that you can't draw pure opaque color on this border (e.g. graphics.FillRect(&Gdiplus::SolidBrush{Gdiplus::Color{255, 255, 0, 0}}, Gdiplus::Rect{0, 0, windowWidht, windowHeight}) will not cover top border by red color - the border will be white or another color (I don't know what it depends on but I think it is window system color preference, like Personalize -> Colors -> [x] Title bars and window borders)). By the way you can cover it any color you want just by setting opacity of this color to 254. This will work. But the problem is still there: if we'll try to resize the window guess what.. we have this white border on top (or what you set this when MARGINS m{0,0,0,1} for DwmExtendFrameIntoClientArea(hWnd, &m);. And also notice that it will be a lot of pain when you will forget that you can't draw pure opaque color and draw something with GDI. So I come up to the next solution.
My idea is to draw with alpha value of 254 only the top most scanline of bitmap. Becase I also perform double buffering I can do it with AlphaBlend(hdc, 0, 0, width, 1, memHdc, 0, 0, width, 1, {AC_SRC_OVER, 0, 254, AC_SRC_ALPHA});. But this solution is still got it's own bug. When resize window we still got this border because it's been drawn faster than our window content. Also we got weird pixels on the left side if we resize the window by the top edge. And the last bug is that this alpha dissapears sometimes to and the top border is visible not only in undrawn client regions but also on top of validated regions.
The code of WM_PAINT procedure:
LRESULT wmPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps = {};
HDC hdc = BeginPaint(hWnd, &ps);
auto [width, height] = dimensions.normal;
HDC memHdc = CreateCompatibleDC(hdc);
HBITMAP hBitmap = CreateCompatibleBitmap(hdc, width, height);
SelectObject(memHdc, hBitmap);
// Children perform their drawings on "back buffer" - memDc
handleChildrenDraw(memDc);
// Copy top line as transparent and other as always to paint over non-client area as it was client
BLENDFUNCTION bf = {};
bf.BlendOp = AC_SRC_OVER;
bf.SourceConstantAlpha = 254;
bf.AlphaFormat = AC_SRC_ALPHA;
auto alphaSuccess = AlphaBlend(hdc, 0, 0, width, 1, memHdc, 0, 0, width, 1, bf);
expect(alphaSuccess == TRUE);
auto copySuccess = BitBlt(hdc, 1, 0, width, height, memHdc, 1, 0, SRCCOPY);
expect(copySuccess == TRUE);
EndPaint(hWnd, &ps);
DeleteObject(memHdc);
DeleteObject(hBitmap);
return 0;
}
Sreenshot: window rendered with a white srtipe on top (border has been rendered for some reasons)
Question:
Can you please tell me how to fix this top border drawing bug or any better solution to draw borderless window with WinAPI? Thanks in advance.
I am trying to write text to a bitmap i am getting from an usb camera using directshow.
The problem is that the text is mirror inverted upside-down and i don't know why.
Here is the code that writes the text:
BITMAPINFOHEADER bih = m_videoInfo.bmiHeader;
Bitmap bmp(bih.biWidth, bih.biHeight, m_stride, m_pixFmt, pBuffer);
Graphics g(&bmp);
if (this->introTimer->timeToDo())
{
RectF pos(10, 10, 100, 100);
SolidBrush brush(Color::Black);
Font font(FontFamily::GenericSerif(), 30);
hr = g.DrawString(this->introText, -1, &font, pos, StringFormat::GenericDefault(), &brush);
return hr;
}
I am not sure if my code is the only thing that affects the drawing of the string. Maybe there is some configuration or something.
Update
I tried using a negative height as suggested by Hans Passant. The result is that the text is not written at all.
The obvious thing to do would be to set the transformation on GDI+.
Essentially you need to invert the Y axis (though by doing this it would now draw off screen). So you need to then translate it down by the window size.
Something like this:
graphics.Transform = new Matrix2D( 1, 0,
0, -1,
0, -windowHeight );
Then draw as normal.
(Its worth noting I'm suggesting this without testing it. The y translation may not be negative so try both!).
The scanlines are stored upside down, with the first scan (scan 0) in memory being the bottommost scan in the image. This is another artifact of Presentation Manager compatibility. GDI automatically inverts the image during the Set and Get operations.
About the suggestion from Hans Passant, did you try negative height in position?
He suggests to use negative height in bih.
BITMAPINFOHEADER bih = m_videoInfo.bmiHeader;
bih.biHeight = -bih.biHeight;
Bitmap bmp(bih.biWidth, bih.biHeight, m_stride, m_pixFmt, pBuffer);
...
Also copying another solution from this page if it helps
void BitmapControl::OnPaint()
{
EnterCriticalSection (&CriticalSection);
if (_handleBMP)
{
CPaintDC dc(this);
//dc.SetMapMode(MM_ISOTROPIC);
dc.SetMapMode(MM_LOENGLISH);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CRect rect;
GetClientRect(&rect);
dc.DPtoLP(&rect);
CBitmap* pBmpOld = dcMem.SelectObject(CBitmap::FromHandle(_handleBMP));
//tst
dc.SetStretchBltMode(COLORONCOLOR);
//BitBlt(dc,rect.left,-0,rect.Width(),rect.Height(),dcMem,rect.left,rect.top,SRCCOPY); //works with MM_TEXT but upsidedown
BitBlt(dc,0,rect.bottom,rect.Width(),-rect.Height(),dcMem,0,0,SRCCOPY); //works with MM_LOENGLISH
dcMem.SelectObject(pBmpOld);
DeleteDC(dc);
DeleteDC(dcMem);
DeleteObject(_handleBMP);
DeleteObject(pBmpOld);
_handleBMP = NULL;
}
LeaveCriticalSection (&CriticalSection);
}
So I've found a lot of code samples, guides, and answers on SO about drawing an image to a layered window. I've tried using pure HBITMAPS and the WIC libs to draw, and now I'm on to GDI+ to draw (which is much simpler and is seemingly easier to use, and thus far it has solved a lot of errors that were caused by faulty WIC code).
I'm currently stuck on UpdateLayeredWindow. No matter what I try I can't get it to work. Right now, it's returning 87, or ERROR_INVALID_PARAMETER. The question is, which one is incorrect? I'm stumped! The below code seems to be the solution other than the fact that UpdateLayeredWindow is refusing to work.
What am I doing wrong?
Here is the code that sets up the HDC/bitmap information/graphics object.
// Create DC
_oGrphInf.canvasHDC = GetDC(_hwndWindow);
// Create drawing 'canvas'
_oGrphInf.lpBits = NULL;
_oGrphInf.bmpCanvas = CreateDIBSection(_oGrphInf.canvasHDC,
&_oGrphInf.bmpWinInformation, DIB_RGB_COLORS,
&_oGrphInf.lpBits, NULL, 0);
// Create graphics object
_oGrphInf.graphics = new Gdiplus::Graphics(_oGrphInf.canvasHDC);
The above works fine - I step through it and all of the pointers work.
And here is the method that draws the PNG.
void Splash::DrawPNG(PNG* lpPNG, int x, int y)
{
LOGD("Drawing bitmap!");
HDC hdcMem = CreateCompatibleDC(_oGrphInf.canvasHDC);
// Select
HBITMAP bmpOld = (HBITMAP)SelectObject(hdcMem, _oGrphInf.bmpCanvas);
Gdiplus::Color trans(0, 0, 0, 0);
_oGrphInf.graphics->Clear(trans);
_oGrphInf.graphics->DrawImage(lpPNG->GetImage(), x, y);
_oGrphInf.graphics->Flush();
SIZE szSize = {_oGrphInf.bmpWinInformation.bmiHeader.biWidth,
_oGrphInf.bmpWinInformation.bmiHeader.biHeight};
// Setup drawing location
POINT ptLoc = {0, 0};
POINT ptSrc = {0, 0};
// Set up alpha blending
BLENDFUNCTION blend = {0};
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
blend.BlendFlags = 0;
// Update
if(UpdateLayeredWindow(_hwndWindow, _oGrphInf.canvasHDC, &ptLoc,
&szSize, hdcMem, &ptSrc,
(COLORREF)RGB(0, 0, 0),
&blend, ULW_ALPHA) == FALSE)
LOGE("Could not update layered window: %u", GetLastError());
// Delete temp objects
SelectObject(hdcMem, bmpOld);
DeleteObject(hdcMem);
DeleteDC(hdcMem);
}
Pulling my hair out! Help?
EDIT: I just decided to re-write the call to UpdateLayeredWindow function, which solved the incorrect parameter issue. Here is what I came up with. However, it still does not work. What am I doing wrong?
UpdateLayeredWindow(_hwndWindow, _oGrphInf.canvasHDC,
NULL, NULL, hdcMem, &ptLoc,
RGB(0, 0, 0), &blend, ULW_ALPHA)
For alpha information to be preserved in drawing operations, you have to make your Graphics object based on a memory-backed Bitmap object, not an HDC, and of course your Bitmap needs to be in a format with an alpha channel.
You'll need to use this Bitmap constructor: http://msdn.microsoft.com/en-us/library/ms536315%28v=vs.85%29.aspx
Just give that a stride of 0, a pointer to your DIB's bits, and PixelFormat32bppPARGB.
Then use Graphics::FromImage to create your Graphics object.
I've never used UpdateLayeredWindow, so I can't verify that that side of it is correct.
Does anyone know how I can create a Completely transparent image or given am image (HBITMAP) how can I wipe it Completely so that all pixels in it are 100% transparent?
Thank you.
The bitmap needs to be 32-bit so it has an alpha channel that you can set opacity values with. If your image is not 32-bit, you will have to create a new 32-bit bitmap and copy the original pixels into it.
I also found the solution without the use of GDI+.
BITMAPV5HEADER bi = {sizeof(BITMAPV5HEADER), 320, 240, 1, 32, BI_BITFIELDS, 0, 0, 0, 0, 0, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000};
HDC hDc = ::CreateCompatibleDC(0);
if(NULL != hDc)
{
RGBQUAD* pArgb;
HBITMAP tBmp = ::CreateDIBSection(hDc, (BITMAPINFO*)&bi, DIB_RGB_COLORS, (void**)&pArgb, NULL, 0);
if(NULL != tBmp)
{
int x, y;
DWORD pBits;
pBits = (DWORD*)pArgb;
for(y = 0; y < bi.bV5Height; y++)
{
for(x = 0; x < bi.bV5Width; x++)
{
*pBits = 0;
pBits++;
}
}
}
::DeleteObject(hDc);
}
tBmp will be a 320 x 240 bitmap that is completely transparent
You can use a monochrome bitmap as a mask to create transparent images from colour ones. This is quite complex. See this example (in VB but uses Win32 APIs directly)
Alternatively, the TransparentBlt function might make what you're trying to do unnecessary.
I found the answer in GDI+. The best way to do this is to attach the device context of the image that you are working with with GDI+ graphic object and then call its clear method.
(unmanaged C++)
I already succeeded drawing PNG files to a transparent layered window that I can drag around the desktop, but now my problem is drawing text on a transparent layered window
Here's my code and my attempt at drawing text in the middle, it's important to note that i'm using the screenDC instead of using the one in WM_PAINT messages
[edit]
updated code after the comments, now i'm just trying to write text on the bitmap before getting the HBITMAP version which i need to use
this time I'm using DrawString because textout() isn't GDI+, I hope DrawString really is GDI+ lol
still doesn't work though, wonder what i'm doing wrong
void Draw() // draws a frame on the layered window AND moves it based on x and y
{
HDC screenDC( NULL ); // grab screen
HDC sourceDC( CreateCompatibleDC(screenDC) );
POINT pos = {x,y}; // drawing location
POINT sourcePos = {0,0}; // top left of image
SIZE size = {100,100}; // 100x100 image
BLENDFUNCTION blendFunction = {0};
HBITMAP bufferBitmap = {0};
Bitmap* TheBitmap = crnimage; // crnimage was already loaded earlier
// ------------important part goes here, my attempt at drawing text ------------//
Gdiplus::Graphics Gx(TheBitmap);
// Font* myFont = new Font(sourceDC);
Font myFont(L"Arial", 16);
RectF therect;
therect.Height = 20;
therect.Width = 180;
therect.X = 0;
therect.Y = 0;
StringFormat format;
format.SetAlignment(StringAlignmentCenter);
format.GenericDefault();
Gdiplus::SolidBrush GxTextBrush(Gdiplus::Color(255, 255, 0,255));
WCHAR thetext[] = L"Sample Text";
int stats = Gx.DrawString(thetext, -1, &myFont, therect, &format, &GxTextBrush);
if(stats) // DrawString returns nonzero if there is an error
msgbox(stats);
stats = Gx.DrawRectangle(&Pen(Color::Red, 3), therect);
// the rectangle and text both draw fine now
// ------------important part goes here, my attempt at drawing text ------------//
TheBitmap->GetHBITMAP(0, &bufferBitmap);
HBITMAP oldBmpSelInDC;
oldBmpSelInDC = (HBITMAP)SelectObject(sourceDC, bufferBitmap);
// some alpha blending
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.SourceConstantAlpha = wndalpha;
blendFunction.AlphaFormat = AC_SRC_ALPHA;
COLORREF colorKey( RGB(255,0,255) );
DWORD flags( ULW_ALPHA);
UpdateLayeredWindow(hWnd, screenDC, &pos, & size, sourceDC, &sourcePos,
colorKey, &blendFunction, flags);
// release buffered image from memory
SelectObject(sourceDC, oldBmpSelInDC);
DeleteDC(sourceDC);
DeleteObject(bufferBitmap);
// finally release the screen
ReleaseDC(0, screenDC);
}
I've been trying to write text on my layered window for two days now, but from those attempts I know there are several ways I can go about doing this
(unfortunately I have no idea how exactly)
The usual option I see is drawing text on a bitmap, then rendering the bitmap itself
Use Gdi+ to load a bitmap
Create a Graphics object from the bitmap
Use DrawString to write text to the bitmap
Dispose of the Graphics object
Use the bitmap Save method to save the result to a file
Apparently one can also make a graphics object from a DC, then draw text on the DC, but again i have no clue as to how to do this
The overall approach looks right, but I think you've got some problems with the DrawString call. Check out the documentation (especially the sample) on MSDN.
Gx.DrawString(thetext, 4, NULL, therect, NULL, NULL)
The third, fifth, and sixth parameters (font, format, and brush) probably need to be specified. The documentation doesn't say that they are optional. Passing NULL for these is probably causing GDI+ to treat the call as a no-op.
The second parameter should not include the terminating L'\0' in the string. It's probably safest to use -1 if your string is always terminated.