Ok suppose I have a brush,
HBRUSH brush = CreateSolidBrush(RGB(0, 0, 0));
And I want to change it's color.
Not calling CreateSolidBrush and DeleteObject on it over and over again.
Like in this example,
#define INFINITY UINT64_MAX // You get the point. I am just calling it many times.
RECT rect = { 0 };
HBRUSH brush = CreateSolidBrush(RGB(0, 0, 0)); // Same brush as the one above.
for(uint64_t i = 0; i < INFINITY; i++){
SetRect(&rect, 0, i, i, i + 1); // Right angle triangle btw.
// How would I change the color of the brush?
FillRect(hdc, &rect, brush);
}
As shown above, the reason I don't want to use CreateSolidBrush and DeleteObject again and again, is that it is slow and I need to be able to change the color of the brush quickly.
I have found SetDCBrushColor. Which can change the color of the selected brush? But doesn't seem to change my brush even after selecting it to the context.
That's why I'm wondering if there is any alternative to SetDCBrushColor.
So that I can use my brush in FillRect.
Any help is greatly appreciated. Thanks in advance.
Actually, I am so sorry for asking this question. I found the answer.
Here it is:
HBRUSH dcbrush = (HBRUSH)::GetStockObject(DC_BRUSH); // Returns the DC brush.
COLORREF randomColor = RGB(69, 69, 69);
SetDCBrushColor(hdc, randomColor); // Changing the DC brush's color.
In the above snippet;
Calling GetStockObject(DC_BRUSH) returns the DC brush.
After receiving the brush, I can change it's color with the above mentioned.
SetDCBrushColor
I would also suggest saving the color like,
COLORREF holdPreviousBrushColor = SetDCBrushColor(hdc, randomColor);
SetDCBrushColor(hdc, holdPreviousBrushColor);
So that you set the DC brush back to it's original color.
So now the code snippet in the question would look like,
#define INFINITY UINT64_MAX // You get the point. I am just calling it many times.
RECT rect = { 0 };
HBRUSH brush = (HBRUSH)::GetStockObject(DC_BRUSH);
COLORREF holdPreviousBrushColor = SetDCBrushColor(hdc, RGB(0, 0, 0));
for(uint64_t i = 0; i < INFINITY; i++){
SetRect(&rect, 0, i, i, i + 1); // Right angle triangle btw.
SetDCBrushColor(hdc, /* Any color you want. */);
FillRect(hdc, &rect, brush);
}
SetDCBrushColor(hdc, holdPreviousBrushColor); // Setting the DC brush's color back to its original color
Related
As the title says I'm unable to FillRect bitmap to be transparent. I know when creating the bitmap it is not monochrome as gray brush works fine but I have no way (that I'm aware of) to check if it is colored or grayscale. I also am aware that by default the bitmap is black hence why I'm trying to change it to transparent. I am also aware that I'm likely not cleaning up the dc's correctly however that is not the main issue. I'm trying to solve the black background by making it transparent.
#include<windows.h>
#include<iostream>
int main()
{
// Init DC
HWND Wnd = GetDesktopWindow();//GetConsoleWindow();
HDC ScreenDC = GetDC(Wnd);
// Init Rectangle
RECT ClientRect;
GetClientRect(Wnd, &ClientRect);
// Init Double Buffer
HDC MemDC = CreateCompatibleDC(ScreenDC);
HBITMAP MemBM = CreateCompatibleBitmap(ScreenDC, ClientRect.right - ClientRect.left, ClientRect.bottom - ClientRect.top);
HBITMAP OldBM = (HBITMAP)SelectObject(MemDC, MemBM);
// Create Brush and Pen
HPEN Pen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
HBRUSH ClearBrush = (HBRUSH)GetStockObject(GRAY_BRUSH);
// Set Brush and Pen
SelectObject(MemDC, Pen);
SelectObject(MemDC, ClearBrush);
POINT p;
while(!GetAsyncKeyState(VK_RETURN))
{
// Clear and Draw
GetCursorPos(&p);
FillRect(MemDC, &ClientRect, ClearBrush);
Rectangle(MemDC, p.x, p.y, p.x+20, p.y+20);
BitBlt(ScreenDC, 0, 0, ClientRect.right - ClientRect.left, ClientRect.bottom + ClientRect.left, MemDC, 0, 0, SRCCOPY);
}
SelectObject(MemDC, OldBM);
DeleteObject(ClearBrush);
DeleteObject(Pen);
DeleteObject(OldBM);
DeleteObject(MemBM);
DeleteDC(MemDC);
ReleaseDC(Wnd, ScreenDC);
return 0;
}
I've tried many different ways of setting transparent background to no avail. The end result is a rectangle appearing over the mouse and following it across the screen however the background shouldn't be black I should be able to see other windows.
I am no expert in anything GDI. But I was given some code YEARS ago which has served me decently well. But, it's getting old ... and with new windows10 dark theme, it's showing it's flaws.
I am rendering a menu (in an explorer menu plugin). Here's the snippet of code used to generate the bitmap.
My goal, to convert this code to generate a bitmap with transparency of the icon preserved.
(the result HBITMAP ends up in pItem->m_hBitmap )
HICON hIcon;
if ( (iIndex >= 0) && (ExtractIconEx(iconDLLPath, iIndex, NULL, &hIcon, 1) != 0) )
{
HDC hdc = CreateIC(L"DISPLAY", NULL, NULL, NULL);
HDC hdcMem = CreateCompatibleDC(hdc);
// XP demands 12x12, otherwise use 16x16
int cx = GetSystemMetrics((m_bUseSmallerIcons) ? SM_CXMENUCHECK : SM_CXSMICON);
int cy = GetSystemMetrics((m_bUseSmallerIcons) ? SM_CYMENUCHECK : SM_CYSMICON);
pItem->m_hBitmap = CreateCompatibleBitmap(hdc, cx, cy);
HBITMAP hBmOld = (HBITMAP) SelectObject(hdcMem, pItem->m_hBitmap);
// DC: paint entire mem dc COLOR_MENU so icon looks transparent
// when painted into context menu having this background color
HBRUSH hBrush = CreateSolidBrush(GetSysColor(COLOR_MENU));
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = cx;
rect.bottom = cy;
FillRect(hdcMem, &rect, hBrush);
DeleteObject(hBrush);
// Draw icon transparently, on top of the background color. Transparent
// areas will be the background color.
DrawIconEx(hdcMem, 0, 0, hIcon, cx, cy, 0, 0, DI_NORMAL);
// Cleanup
SelectObject(hdcMem, hBmOld);
DeleteDC(hdc);
DeleteDC(hdcMem);
DestroyIcon(hIcon);
}
I should remove the which draws the white background, but how do I put down a transparent background? Everything I've tried yields a black background.
* just removing the white "fill"
* SetBkMode(TRANSPARENT)
* using the theme code to get the menu color...
How do I go about making a proper bitmap with transparency?
Ultimately, the answer is that I am doing nothing wrong. This menu is rendered as part of a menu constructed in a shell plugin using IContextMenu.
The answer - implement IContextMenu2. And in doing so, do an owner draw menu item.
I have a window created with the WS_EX_LAYERED window style. I am currently drawing onto a memory bitmap using GDI+, and using UpdateLayeredWindow to update the graphical content of my layered window.
Here's a snippet of my code:
void Redraw(HWND hWnd, int width, int height) {
static bool floppy = true;
floppy = !floppy;
HDC hScreenDC = GetDC(HWND_DESKTOP);
HDC hMemDC = CreateCompatibleDC(hScreenDC);
HBITMAP hBmp = CreateCompatibleBitmap(hScreenDC, width, height);
HGDIOBJ hObj = SelectObject(hMemDC, hBmp);
Graphics gfx(hMemDC);
SolidBrush b(Color(254, (floppy ? 255 : 0), (floppy ? 0 : 255), 0));
gfx.FillRectangle(&b, Rect(0, 0, width, height));
BLENDFUNCTION blend;
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
POINT src = { 0, 0 };
SIZE size;
size.cx = width;
size.cy = height;
Assert(UpdateLayeredWindow(
hWnd,
hScreenDC,
NULL,
&size,
hMemDC,
&src,
RGB(0, 0, 0),
&blend,
ULW_ALPHA
));
SelectObject(hMemDC, hObj);
DeleteObject(hBmp);
DeleteDC(hMemDC);
ReleaseDC(HWND_DESKTOP, hScreenDC);
}
When creating my SolidBrush, I specified the value of 254 for the alpha component. This results in a 99.6% opaque fill, which is not what I want.
When I specify 255 as the alpha component, there appears to be no fill; my window becomes completely transparent. This is an issue because I wish to draw shapes that are 100% opaque, but I also wish to draw some that aren't.
There seems to be some qwerks with FillRectangle. This becomes apparent when we observe that using FillEllipse with a SolidBrush whose alpha component is 255, results in the shape being rendered perfectly (opaque).
Here are two work-arounds that I came up with, which each solve the issue for me:
Call FillRectangle twice
SolidBrush b(Color(254, 255, 0, 0));
gfx.FillRectangle(&b, Rect(0, 0, width, height));
gfx.FillRectangle(&b, Rect(0, 0, width, height));
Since the same area is being filled twice, they will blend and create RGB(255, 0, 0) regardless of the content behind the window (it's now 100% opaque). I do not prefer this method, as it requires every rectangle to be drawn twice.
Use FillPolygon instead
Just as with FillEllipse, FillPolygon doesn't seem to have the colour issue, unless you call it like so:
SolidBrush b(Color(255, 255, 0, 0));
Point points[4];
points[0] = Point(0, 0);
points[1] = Point(width, 0);
points[2] = Point(width, height);
points[4] = Point(0, height);
gfx.FillPolygon(&b, points, 4); //don't copy and paste - this won't work
The above code will result in a 100% transparent window. I am guessing that this is either due to some form of optimisation that passes the call to FillRectangle instead. Or - most likely - there is some problem with FillPolygon, which is called by FillRectangle. However, if you add an extra Point to the array, you can get around it:
SolidBrush b(Color(255, 255, 0, 0));
Point points[5];
points[0] = Point(0, 0);
points[1] = Point(0, 0); //<-
points[2] = Point(width, 0);
points[3] = Point(width, height);
points[4] = Point(0, height);
gfx.FillPolygon(&b, points, 5);
The above code will indeed draw a 100% opaque shape, which fixes my problem.
UpdateLayeredWindow() requires a bitmap with pre-multiplied alpha:
Note that the APIs use premultiplied alpha, which means that the red,
green and blue channel values in the bitmap must be premultiplied with
the alpha channel value. For example, if the alpha channel value is x,
the red, green and blue channels must be multiplied by x and divided
by 0xff prior to the call.
You can use Bitmap::ConvertFormat() to convert a bitmap to pre-multiplied (the format is PixelFormat32bppPARGB).
I am trying to draw a simple few rectangles and store the result, I only need to draw it once. So, keeping the HDC (hdcBackround) at the top "globaly."
void drawBackground(HWND hwnd) { // hwnd is the main windows handle
// dimensions
RECT rect;
GetWindowRect(hwnd, &rect);
HDC hWinDC = GetDC(hwnd);
hdcBackground = ::CreateCompatibleDC(hWinDC); // "global"
HBITMAP hbm = ::CreateCompatibleBitmap(hWinDC, rect.right, rect.bottom);
::SelectObject(hdcBackground, hbm);
SetBkMode(hdcBackground, TRANSPARENT);
SelectObject(hdcBackground, hFont[HF_DEFAULT]);
SelectObject(hdcBackground, hBrush[HB_TOPBG]);
SelectObject(hdcBackground, hPen[HP_THINBORDER]);
// draw
Rectangle(hdcBackground, 0, 0, rect.right, 20);
SelectObject(hdcBackground, hBrush[HB_LOWBG]);
Rectangle(hdcBackground, 50, 20, rect.right, 40);
// ??? clean up after it works
ReleaseDC(hwnd, hWinDC);
}
I call that function once, and in a timer I BitBlt() hdcBackground to the screens HDC. When I test it out, it draws both the rectangles, with a 1px border (as the pen is set as,) but there is no color, it is just black and white.
The brushes and such are all fine, it is just that I am not getting color. The RGB on the brushes are (25,25,25) and (65,65,65), dark grey.
Any ideas?
Thanks.
I'm trying to change the background color of a program I did NOT write.
Looking at it with Spy++ I can see that the main class is "ThunderRT6FormDC". One of its children has the class "ThunderRT6Frame". Inside ThunderRT6Frame there are a bunch of ThunderRT6CommandButtons.
I want to change the background color behind the buttons. I tried doing this by changing the color of the ThunderRT6Frame window, but I can't get it to work. Any ideas?
This is what I tried first:
HWND hwndCnt = FindWindow("ThunderRT6FormDC", NULL);
HWND hwndCntFrame = FindWindowEx(hwndCnt, NULL, "ThunderRT6Frame", NULL);
SetClassLong(hwndCnt, GCL_HBRBACKGROUND, (LONG)CreateSolidBrush(RGB(220,220,255)));
InvalidateRect(hwndCnt, 0, TRUE);
SetClassLong(hwndCntFrame, GCL_HBRBACKGROUND, (LONG)CreateSolidBrush(RGB(220,220,255)));
InvalidateRect(hwndCntFrame, 0, TRUE);
No visible changes came out of that, so I moved on to injecting a dll and subclass WM_PAINT:
PAINTSTRUCT ps;
HDC hdcPaint = BeginPaint(Hwnd, &ps);
SetBkColor(hdcPaint, RGB(255,0,0));
HPEN pen = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
HBRUSH brush = CreateSolidBrush(RGB(255, 0, 0));
HPEN hOldPen = (HPEN)SelectObject(hdcPaint, pen);
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdcPaint, brush);
RoundRect(hdcPaint, 1, 1, 100, 100, 10, 10);
SelectObject(hdcPaint, hOldPen);
SelectObject(hdcPaint, hOldBrush);
DeleteObject(pen);
DeleteObject(brush);
EndPaint(Hwnd, &ps);
return 0;
I have WM_PAINT subclassed for both ThunderRT6FormDC and ThunderRT6Frame but no red rectangle is drawn that I can see.
What am I doing wrong? What else do I need to try?
PS. The window class names in the program I'm trying to change indicates that it is a VB6 program, if that's any help.
EDIT:
I tried adding the following to both window procedures
case WM_ERASEBKGND:
{
HDC hdcPaint = (HDC)wParam;
SetBkColor(hdcPaint, RGB(255,0,0));
HPEN pen = CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
HBRUSH brush = CreateSolidBrush(RGB(255, 0, 0));
HPEN hOldPen = (HPEN)SelectObject(hdcPaint, pen);
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdcPaint, brush);
RoundRect(hdcPaint, 1, 1, 100, 100, 10, 10);
SelectObject(hdcPaint, hOldPen);
SelectObject(hdcPaint, hOldBrush);
DeleteObject(pen);
DeleteObject(brush);
return TRUE;
}
But I get no visible results
EDIT 2:
Putting MessageBoxes in the different WM_* cases I can see the rectangles being painted and even after I have closed all the message boxes the rectangles are left on the screen. But if I don't have any message boxes at all I can't see the rectangles.
So I'm guessing something is redrawing the windows after I have painted on them. What is doing this redrawing, and where?
EDIT 3:
Cleaning up my code and keeping just WM_PAINT for the ThunderRT6Form window made it work. This is what WM_PAINT looks like now:
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdcPaint = BeginPaint(Hwnd, &ps);
HBRUSH brush = CreateSolidBrush(RGB(255, 255, 255));
RECT r;
GetClientRect(Hwnd, &r);
FillRect(hdcPaint, &r, brush);
DeleteObject(brush);
EndPaint(Hwnd, &ps);
return 0;
}
EDIT 4:
I never did find out exactly why the rectangles wouldn't show up in my first tries. But it was some error in my code somewhere.
This is what I did: First I added message boxes to make sure all code was getting called. That made the rectangles appear. Then I fiddled around a bit with where I had the message boxes (only for the Form, only for the Frame, only for WM_PAINT etc). And I always got the rectangle. Then I removed all the message boxes, and sure enough, the rectangles went away too. So I added some message boxes back in and started cleaning up my code. Some of this "clean up" fixed my error, because after that I could remove all message boxes and still be able to paint on the background.
You might see some success if you handle the WM_ERASEBKGND message similarly to the way you handle WM_PAINT.