Related
I found on (at least) Win10 that calling ::GetClipboardData() on a CF_DIBV5 created via Alt-PrtScrn (may be a synthesized format) causes the image to be modified (and basically corrupted).
For example, on the handler for ON_WM_CLIPBOARDUPDATE() the simple loop below will cause the corruption (note that you need to use debug mode so the ::GetClipboardData() is not optimized out).
To test, first don't run your app that processes the clipboard, use Alt-PrntScrn to capture data, then paste it to Paint. Now run the app that process the clipboard (with below sample below). Repeat Alt-PrntScrn process and you'll see it's different where the right side of the captured window ends up on the left and not centered in the area.
void CMainFrame::OnClipboardUpdate()
if (::OpenClipboard(AfxGetMainWnd()->m_hWnd)) {
UINT uformat=0;
while ((uformat=::EnumClipboardFormats(uformat))!=0) {
if (uformat==CF_DIBV5) {
// get the data - run in debug mode so not optimized out
HGLOBAL hglobal=::GetClipboardData(uformat);
}
}
// clean up
::CloseClipboard();
}
}
To enable the handler you need to call AddClipboardFormatListener(GetSafeHwnd()); in something like int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) then RemoveClipboardFormatListener(GetSafeHwnd()); on void CMainFrame::OnDestroy()
So is this a bug in Win10 (and other Windows Versions) or should I be doing something else that the sample isn't doing? (I know other formats exist, but the CF_DBIV5 is what I wanted).
I'm on Version 1903 (OS Build 18362.838)
Note the sample pic has right side items on left and some garbage pixels in lower left. I alt-prtscrn while app running, pasted in paint.
My resolution is 2560x1600.
Here's a link to a project that will cause the problem:
Sample Project
You can find the following description in the documentation :
The red, green, and blue bitfield masks for BI_BITFIELD bitmaps
immediately follow the BITMAPINFOHEADER, BITMAPV4HEADER, and
BITMAPV5HEADER structures. The BITMAPV4HEADER and BITMAPV5HEADER
structures contain additional members for red, green, and blue masks
as follows.
When the biCompression member of BITMAPINFOHEADER is set to BI_BITFIELDS and the function receives an argument of type LPBITMAPINFO, the color masks will immediately follow the header. The color table, if present, will follow the color masks. BITMAPCOREHEADER bitmaps do not support color masks.
When you handle CF_DIBV5 correctly you will draw the image successfully. The following is an example of Win32 C++ you can refer to:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static UINT uFormat = (UINT) -1;
HDC hdcMem = NULL;
RECT rc = {0};
BYTE * pData = NULL;
BITMAPV5HEADER *pDibv5Info = NULL;
switch (message)
{
case WM_CLIPBOARDUPDATE:
{
if (IsClipboardFormatAvailable(CF_DIBV5))
{
uFormat = CF_DIBV5;
::CloseClipboard();
GetClientRect(hWnd, &rc);
InvalidateRect(hWnd, &rc, TRUE);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
switch (uFormat)
{
case CF_DIBV5:
hdcMem = CreateCompatibleDC(hdc);
if (hdcMem != NULL)
{
if (::OpenClipboard(hWnd)) {
HANDLE hglobal = ::GetClipboardData(uFormat);
pData = (BYTE*)GlobalLock(hglobal);
if (pData)
{
pDibv5Info = (BITMAPV5HEADER *)pData;
int offset = pDibv5Info->bV5Size + pDibv5Info->bV5ClrUsed * sizeof(RGBQUAD);
if (pDibv5Info->bV5Compression == BI_BITFIELDS)
offset += 3 * sizeof(DWORD); //three DWORD color masks that specify the red, green, and blue components
pData += offset;
SetDIBitsToDevice(hdc, 20, 20, pDibv5Info->bV5Width, pDibv5Info->bV5Height, 0, 0, 0, pDibv5Info->bV5Height, pData, (BITMAPINFO *)pDibv5Info, 0);
}
GlobalUnlock(hglobal);
::CloseClipboard();
}
}
break;
}
EndPaint(hWnd, &ps);
}
break;
}
The correct image drawn in my application window:
I can reproduce the same issue without code:
if (pDibv5Info->bV5Compression == BI_BITFIELDS)
offset += 3 * sizeof(DWORD);
The corrupted image:
I'm updating the question to remove irrelevant details. The conclusion I've drawn is that if a valid alpha channel exists, it will honor it, but if it doesn't (say a 24-bit PNG w/o alpha channel), it uses F0F0F0 as a transparent color.
I have an image being loaded into a static "picture control" (chosen in visual studio) in a dialog. I noticed that color 0xF0F0F0 is being displayed as a "transparent" color (background of the dialog bleeds through). The bitmap is loaded via CStatic::SetBitmap.
The Picture Control transparent flag is set to false.
The image is loaded via CImage::Load.
If I wanted to mask a color out of a CStatic bitmap set via SetBitmap, how would I do it? I don't, but maybe that would help me find the cause.
Minimum example below. I created a dialog project with the VS wizard, and added a picture control to the main dialog. Then I added only the following code:
//header code added
CPngImage logoImage;
CStatic pictureCtrl;
CBrush bgBrush;
....
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
//cpp code added
DDX_Control(pDX, IDC_STATICIMG, pictureCtrl);
....
ON_WM_CTLCOLOR()
....
bgBrush.CreateSolidBrush(RGB(0, 255, 0));
logoImage.LoadFromFile(_T("C:\\temp\\logo.png"));
pictureCtrl.SetBitmap(logoImage);
....
HBRUSH CMFCApplication1Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) {
return bgBrush;
}
And here is the image file I'm testing with.
And here is what it looks like on the dialog:
// MFCApplication1Dlg.h : header file
//
#pragma once
// CMFCApplication1Dlg dialog
class CMFCApplication1Dlg : public CDialogEx
{
// Construction
public:
CMFCApplication1Dlg(CWnd* pParent = nullptr); // standard constructor
CPngImage logoImage;
CStatic pictureCtrl;
CBrush bgBrush;
// Dialog Data
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_MFCAPPLICATION1_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
};
// MFCApplication1Dlg.cpp : implementation file
//
#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg();
// Dialog Data
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_ABOUTBOX };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()
// CMFCApplication1Dlg dialog
CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_STATICIMG, pictureCtrl);
}
BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_CTLCOLOR()
END_MESSAGE_MAP()
// CMFCApplication1Dlg message handlers
BOOL CMFCApplication1Dlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != nullptr)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
bgBrush.CreateSolidBrush(RGB(0, 255, 0));
logoImage.LoadFromFile(_T("C:\\temp\\logo.png"));
pictureCtrl.SetBitmap(logoImage);
return TRUE; // return TRUE unless you set the focus to a control
}
void CMFCApplication1Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialogEx::OnSysCommand(nID, lParam);
}
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CMFCApplication1Dlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
// The system calls this function to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CMFCApplication1Dlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
HBRUSH CMFCApplication1Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) {
return bgBrush;
}
On my system (Windows 10), the color 0xF0F0F0 equals GetSysColor(COLOR_BTNFACE) which is the default dialog background color. When drawing, the static control seems to replace this color in the background image with the brush returned from OnCtlColor() handler of the parent window. This does have the taste of a feature and not a bug (though I couldn't find anything in the reference that specifies this behaviour).
Here is a code snippet to reproduce this issue even without using CPngImage or CImage, just by drawing in a memory DC with color 0xF0F0F0.
As the behaviour only appears when the source image does not contain an alpha channel, a solution would be to convert the source image to 32-bpp ARGB format. This way we don't have to override CStatic::OnPaint():
// Set the alpha channel of a 32-bpp ARGB image to the given value.
HRESULT SetAlphaChannel( CImage& image, std::uint8_t alpha )
{
if( ! image.GetBits() || image.GetBPP() != 32 )
return E_INVALIDARG;
GdiFlush(); // Make sure GDI has finished all drawing in source image.
for( int y = 0; y < image.GetHeight(); ++y )
{
DWORD* pPix = reinterpret_cast<DWORD*>( image.GetPixelAddress( 0, y ) );
for( int x = 0; x < image.GetWidth(); ++x, ++pPix )
{
*pPix = ( *pPix & 0xFFFFFF ) | ( alpha << 24 );
}
}
return S_OK;
}
// Load an image and convert to 32-bpp ARGB format, if necessary.
HRESULT LoadImageAndConvertToARGB32( CImage& image, LPCWSTR pFilePath )
{
CImage tempImage;
HRESULT hr = tempImage.Load( pFilePath );
if( FAILED( hr ) )
return hr;
if( tempImage.GetBPP() == 32 ) // Assume 32 bpp image already has an alpha channel
{
image.Attach( tempImage.Detach() );
return S_OK;
}
if( ! image.Create( tempImage.GetWidth(), tempImage.GetHeight(), 32, CImage::createAlphaChannel ) )
return E_FAIL;
HDC const imageDC = image.GetDC();
BOOL const bitBltSuccess = tempImage.BitBlt( imageDC, 0, 0, SRCCOPY );
image.ReleaseDC();
if( ! bitBltSuccess )
return E_FAIL;
SetAlphaChannel( image, 255 ); // set alpha to opaque
return S_OK;
}
Usage:
Replace call to CImage::Load() by:
LoadImageAndConvertToARGB32( m_image, filePath );
Notes:
There is another static control nastiness when you assign a 32-bpp bitmap with a non-zero alpha channel to the control¹ (as you do when following my solution). In this case, the static control will make a copy of the bitmap you passed in while you are responsible to destroy this copy!
Mandatory OldNewThing read:
"When will the static control automatically delete the image loaded into it, and when is it the responsibility of the application?"
¹) More precisely: When using version 6 of the common controls, which almost all applications do these days.
It's not a PNG problem, it's a color depth problem.
According to your code, I converted 8-bit PNG picture into 8-bit BMP picture by format conversion tool, and the picture still show the background color.
So I saved 8-bit PNG picture to 32-bit png picture, and that's all right.
Why is that?
Answer: A GIF file has two parts: a color table and the image pixel data. The color table is a list of the colors used in that image (an 8-bit GIF can have up to 2^8 = 256 colors in the color table, but a 4-bit GIF can have only 2^4 = 16 colors), and each color is assigned a number. The image pixel data are for the image itself, and each pixel is assigned a number that points to its color in the color table. For example, if color #10 in the color table is red (#FF0000), then any pixel in the image with the number 10 will be displayed as red. The colors in the color table will vary from GIF file to GIF file based on the image itself; color #10 will not always be red. The color table is the set of up to 256 colors necessary to render that image.
When we add index transparency, every color in the color table is given a transparency designation in addition to its color data (i.e., RGB values):
zero (o = False in Boolean algebra) means do not display this color, or
one (1 = True in Boolean Algebra) means display this color.
There are no intermediate opacities; the color is either displayed or it is not. The end result is that a pixel with an index transparency color will not be displayed and whatever is in the background behind that pixel will show through. For example, if color #10 is red (#FF0000) and is designated as transparent (index transparency = 0), then any pixel that is color #10 will not be displayed and the background will show through.
There can be multiple transparent colors in index transparency, because every color in the color table has a designation of either opaque (1) or transparent (0). Most graphics programs assume that the canvas color (often white, but it could be any color) is the default transparent color, but you can specify any color (or any number of colors) as transparent or not.
This type of transparency is very common in GIF and PNG8 files and is easy to identify, because there is no fading, there are no partially transparent pixels, and the edges are often described as “hard” or “pixelated.”
I'm afraid the following is the best answer I'm going to get. MFC/Win32 does something funny and it's not clear why. But the image has no problems displaying correctly if you draw it manually.
I created a custom (very rough) CStatic class and added the OnPaint below. In the screenshot, the first is using my class, and the second is using regular CStatic. Both are using an identical image (24 bit BMP).
struct AFX_CTLCOLOR {
HWND hWnd;
HDC hDC;
UINT nCtlType;
};
void CMyStatic::OnPaint() {
CPaintDC dc(this); // device context for painting
CRect r;
GetClientRect(r);
WPARAM w = (WPARAM)&dc;
AFX_CTLCOLOR ctl;
ctl.hWnd = this->GetSafeHwnd();
ctl.nCtlType = CTLCOLOR_STATIC;
ctl.hDC = dc.GetSafeHdc();
HBRUSH bg=(HBRUSH)::SendMessage(GetParent()->GetSafeHwnd(), WM_CTLCOLORSTATIC, w, (LPARAM)&ctl);
CBrush cbg;
cbg.Attach(bg);
dc.FillRect(r, &cbg);
cbg.Detach();
HBITMAP hbmp=GetBitmap();
BITMAP binfo;
CBitmap* cbmp = CBitmap::FromHandle(hbmp);
cbmp->GetBitmap(&binfo);
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
dcMem.SelectObject(cbmp);
dc.BitBlt((r.Width()-binfo.bmWidth)/2, (r.Height() - binfo.bmHeight) / 2, binfo.bmWidth, binfo.bmHeight, &dcMem, 0, 0, SRCCOPY);
}
I want to write a program that needs sometimes to start processes of another applications (mainly Sumatra PDF) on Windows 10, version 1803 (April 2018 Update).
These applications should be started on a specific monitor. I also want to be able to close the processes when needed.
The preferred languages are C# and Java, but any help is appreciated.
EDIT
I've tried to use the ShellExecuteExW function suggested by IInspectable in C++ code directly, but it doesn't work, as applications appear on the main monitor. I must have certainly made a mistake as I am absolutely new to WinAPI and know very little C++.
#include <Windows.h>
HMONITOR monitors[2]; // As it's only a test and I have currently only two monitors.
int monitorsCount = 0;
BOOL CALLBACK Monitorenumproc(HMONITOR hMonitor, HDC hdc, LPRECT lprect, LPARAM lparam)
{
monitors[monitorsCount] = hMonitor;
monitorsCount++;
return TRUE;
}
int main()
{
EnumDisplayMonitors(NULL, NULL, Monitorenumproc, 0);
_SHELLEXECUTEINFOW info;
ZeroMemory(&info, sizeof(info));
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_HMONITOR;
//info.lpVerb = L"open";
info.lpFile = L"C:\\Windows\\System32\\cmd.exe";
info.nShow = SW_SHOW;
info.hMonitor = monitors[1]; // Trying to start on the second monitor.
ShellExecuteExW(&info);
return 0;
}
As suggested by others, this is intended behavior of Windows and for the good reasons.
Also, you can not rely on default window placement atleast for SumatraPDF, since it surely does not use CW_USEDEFAULT, and instead stores these values in :
%USERPROFILE%\AppData\Roaming\SumatraPDF\SumatraPDF-settings.txt
There are multiple options though:
Use third party tools that monitor top level windows and based on pre-configured rules moves them to specified display. E.g. DisplayFusion, etc.
Use ligher weight solutions like AutoHotkey/AutoIt.
Try and do this in code itself. Following is a working solution. I smoke tested on my box.
Disclaimer: I have not written the entire code, for saving time I pulled it up from couple of sources, tweaked it, glued it together, and tested with SumantraPDF. Do also note, that this code is not of highest standards, but solves your problem, and will act as a can-be-done example.
C++ code: (scroll down for C# code)
#include <Windows.h>
#include <vector>
// 0 based index for preferred monitor
static const int PREFERRED_MONITOR = 1;
struct ProcessWindowsInfo
{
DWORD ProcessID;
std::vector<HWND> Windows;
ProcessWindowsInfo(DWORD const AProcessID)
: ProcessID(AProcessID)
{
}
};
struct MonitorInfo
{
HMONITOR hMonitor;
RECT rect;
};
BOOL WINAPI EnumProcessWindowsProc(HWND hwnd, LPARAM lParam)
{
ProcessWindowsInfo *info = reinterpret_cast<ProcessWindowsInfo*>(lParam);
DWORD WindowProcessID;
GetWindowThreadProcessId(hwnd, &WindowProcessID);
if (WindowProcessID == info->ProcessID)
{
if (GetWindow(hwnd, GW_OWNER) == (HWND)0 && IsWindowVisible(hwnd))
{
info->Windows.push_back(hwnd);
}
}
return true;
}
BOOL CALLBACK Monitorenumproc(HMONITOR hMonitor, HDC hdc, LPRECT lprect, LPARAM lParam)
{
std::vector<MonitorInfo> *info = reinterpret_cast<std::vector<MonitorInfo>*>(lParam);
MonitorInfo monitorInfo = { 0 };
monitorInfo.hMonitor = hMonitor;
monitorInfo.rect = *lprect;
info->push_back(monitorInfo);
return TRUE;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
// NOTE: for now this code works only when the window is not already visible
// could be easily modified to terminate existing process as required
SHELLEXECUTEINFO info = { 0 };
info.cbSize = sizeof(info);
info.fMask = SEE_MASK_NOCLOSEPROCESS;
info.lpFile = L"C:\\Program Files\\SumatraPDF\\SumatraPDF.exe";
info.nShow = SW_SHOW;
std::vector<MonitorInfo> connectedMonitors;
// Get all available displays
EnumDisplayMonitors(NULL, NULL, Monitorenumproc, reinterpret_cast<LPARAM>(&connectedMonitors));
if (ShellExecuteEx(&info))
{
WaitForInputIdle(info.hProcess, INFINITE);
ProcessWindowsInfo Info(GetProcessId(info.hProcess));
// Go though all windows from that process
EnumWindows((WNDENUMPROC)EnumProcessWindowsProc, reinterpret_cast<LPARAM>(&Info.ProcessID));
if (Info.Windows.size() == 1)
{
// only if we got at most 1 window
// NOTE: applications can have more than 1 top level window. But at least for SumtraPDF this works!
if (connectedMonitors.size() >= PREFERRED_MONITOR)
{
// only move the window if we were able to successfully detect available monitors
SetWindowPos(Info.Windows.at(0), 0, connectedMonitors.at(PREFERRED_MONITOR).rect.left, connectedMonitors.at(PREFERRED_MONITOR).rect.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}
CloseHandle(info.hProcess);
}
return 0;
}
To emphasize one of my comments in code. This code will only work if the process in question is not already running. You can tweak the code as per your requirements otherwise.
Update: Added C# code below, as I realized OP prefers C#. This code also has the termination logic cooked in.
C# code:
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
private const int PREFERRED_MONITOR = 1;
static void Main(string[] args)
{
// NOTE: you will have to reference System.Windows.Forms and System.Drawing (or
// equivalent WPF assemblies) for Screen and Rectangle
// Terminate existing SumatraPDF process, else we will not get the MainWindowHandle by following method.
List<Process> existingProcesses = Process.GetProcessesByName("SumatraPDF").ToList();
foreach (var existingProcess in existingProcesses)
{
// Ouch! Ruthlessly kill the existing SumatraPDF instances
existingProcess.Kill();
}
// Start the new instance of SumantraPDF
Process process = Process.Start(#"C:\Program Files\SumatraPDF\SumatraPDF.exe");
// wait max 5 seconds for process to be active
process.WaitForInputIdle(5000);
if (Screen.AllScreens.Length >= PREFERRED_MONITOR)
{
SetWindowPos(process.MainWindowHandle,
IntPtr.Zero,
Screen.AllScreens[PREFERRED_MONITOR].WorkingArea.Left,
Screen.AllScreens[PREFERRED_MONITOR].WorkingArea.Top,
0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
}
SEE_MASK_HMONITOR is only a request. Applications can choose their own window placement. SEE_MASK_HMONITOR only works when the executed application relies on default window placement, i.e. it creates its first top-level window with CW_USEDEFAULT
So what you want is not generally possible. Your code is as good as you can get, if you don't control the launched application.
I'm creating a c++ project using Embarcadero RAD Studio (10.2 Tokyo starter) and the Windows GDI to draw text, via the DrawText() function.
I recently saw that Windows 10 provides a new "Segoe UI Emoji" font, that potentially allows text functions to draw colored emojis. I found several examples using Direct2D, but none with pure GDI functions.
I also tried a simple code, like this:
HDC hDC = ::GetDC(Handle);
std::auto_ptr<TCanvas> pCanvas(new TCanvas());
pCanvas->Handle = hDC;
pCanvas->Brush->Color = clWhite;
pCanvas->Brush->Style = bsSolid;
pCanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));
const std::wstring text = L"Test 😀 😬 😁 😂 😃 😄 😅 😆";
TRect textRect(10, 10, ClientWidth - 10, ClientHeight - 10);
hFont = ::CreateFont(-40,
0,
0,
0,
FW_DONTCARE,
FALSE,
FALSE,
FALSE,
DEFAULT_CHARSET,
OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY,
VARIABLE_PITCH,
L"Segoe UI Emoji");
::SelectObject(hDC, hFont);
::DrawTextW(hDC,
text.c_str(),
text.length(),
&textRect,
DT_LEFT | DT_TOP | DT_SINGLELINE);
::DeleteObject(hFont);
The output result sounds good in terms of symbols, but they are drawn in black&white, without colors, as you can see on the screenshot below:
I could not find any additional options that may allow the text to be drawn using colored symbols instead of black&white. Is there a way to activate the support of the color in GDI DrawText() function, and if yes, how to do that? Or only Direct2D may draw colored emojis?
EDITED on 30.10.2017
As the GDI cannot do the job (unfortunately, and as I thought) I publish here the Direct2D version of the above code, that worked for me.
const std::wstring text = L"Test 😀 😬 😁 😂 😃 😄 😅 😆";
HDC hDC = ::GetDC(Handle);
std::auto_ptr<TCanvas> pGDICanvas(new TCanvas());
pGDICanvas->Handle = hDC;
pGDICanvas->Brush->Color = clWhite;
pGDICanvas->Brush->Style = bsSolid;
pGDICanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));
::D2D1_RECT_F textRect;
textRect.left = 10;
textRect.top = 10;
textRect.right = ClientWidth - 10;
textRect.bottom = ClientHeight - 10;
std::auto_ptr<TDirect2DCanvas> pCanvas(new TDirect2DCanvas(hDC, TRect(0, 0, ClientWidth, ClientHeight)));
// configure Direct2D font
pCanvas->Font->Size = 40;
pCanvas->Font->Name = L"Segoe UI Emoji";
pCanvas->Font->Orientation = 0;
pCanvas->Font->Pitch = System::Uitypes::TFontPitch::fpVariable;
pCanvas->Font->Style = TFontStyles();
// get DirectWrite text format object
_di_IDWriteTextFormat pFormat = pCanvas->Font->Handle;
if (!pFormat)
return;
pCanvas->RenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
::D2D1_COLOR_F color;
color.r = 0.0f;
color.g = 0.0f;
color.b = 0.0f;
color.a = 1.0f;
::ID2D1SolidColorBrush* pBrush = NULL;
// create solid color brush, use pen color if rect is completely filled with outline
pCanvas->RenderTarget->CreateSolidColorBrush(color, &pBrush);
if (!pBrush)
return;
// set horiz alignment
pFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
// set vert alignment
pFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
// set reading direction
pFormat->SetReadingDirection(DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
// set word wrapping mode
pFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
IDWriteInlineObject* pInlineObject = NULL;
::DWRITE_TRIMMING trimming;
trimming.delimiter = 0;
trimming.delimiterCount = 0;
trimming.granularity = DWRITE_TRIMMING_GRANULARITY_NONE;
// set text trimming
pFormat->SetTrimming(&trimming, pInlineObject);
pCanvas->BeginDraw();
pCanvas->RenderTarget->DrawText(text.c_str(), text.length(), pFormat, textRect, pBrush,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
pCanvas->EndDraw();
Of course this code will draw colored emojis only on the currently most recent versions of Windows 10, and above. On previous versions the text will be drawn as above (and the code may not compile).
Bonus Reading
MSDN: Color Fonts with DirectWrite, Direct2D, and Win2D
GDI does not support color fonts (even if you go the full Uniscribe route), you have to use Direct2D if you want color font support. It makes sense that the simpler GDI APIs don't support color fonts as color fonts require using OpenType tags and none of DrawText/TextOut provide that level of control, Uniscribe allows for such tags but has simply not been extended to support color fonts.
You can use DirectWrite to draw colored emojis onto a bitmap in memory DC, then BitBlt() to your destination DC.
Basically, you need to implement a custom IDWriteTextRenderer class and call IDWriteTextLayout::Draw() with your renderer, then copy the result.
In your class, you retrieve IDWriteGdiInterop from IDWriteFactory and call IDWriteGdiInterop::CreateBitmapRenderTarget() to get the bitmap render target; call IDWriteFactory::CreateMonitorRenderingParams() to get the rendering parameters, and call IDWriteFactory::CreateTextFormat() to set up your text format.
The only significant method is DrawGlyphRun(), where you get IDWriteColorGlyphRunEnumerator with IDWriteFactory2::TranslateColorGlyphRun() and with each color run, call IDWriteBitmapRenderTarget::DrawGlyphRun() to do the work for you.
Just remember to update the render target/parameters when the window size/position changes.
You may reference this MSDN documentation:
Render to a GDI Surface
https://msdn.microsoft.com/en-us/library/windows/desktop/ff485856(v=vs.85).aspx
As mentioned by #SoronelHaetir's answer above, the win32 graphics device interface (GDI) that is used when working with static window components (via WC_STATIC) doesn't support colorized fonts. In order to display colored emojis and/or "fancier" text (i.e. colored text, etc.), you'll need to use the Direct2D API.
The example above provided by original poster (OP) #Jean-Milost Reymond isn't something that is immediately able to be compiled and tried out by the reader. Also, it uses the TCanvas class, which isn't strictly needed when working directly with the Win32 API.
For people looking for a complete example that works on the bare-metal Win32 API and can be immediately copied and pasted and compiled, then here is the code that will compile in Visual Studio:
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <wchar.h>
#include <math.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
#include <string>
#include <cassert>
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "Dwrite.lib")
HWND WindowHandle = nullptr;
IDWriteFactory * DWriteFactory = nullptr;
ID2D1Factory * Direct2dFactory = nullptr;
ID2D1HwndRenderTarget * RenderTarget = nullptr;
ID2D1SolidColorBrush * TextBlackBrush = nullptr;
const std::wstring DISPLAY_TEXT = L"Test 😀 😬 😁 😂 😃 😄 😅 😆";
template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease);
LRESULT CALLBACK WndProc (HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
HRESULT CreateDeviceIndependentResources ();
HRESULT InitInstance (HINSTANCE hInstance, int nCmdShow);
void DiscardDeviceResources ();
HRESULT OnRender ();
HRESULT CreateDeviceResources ();
template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release ();
(*ppInterfaceToRelease) = NULL;
}
}
HRESULT OnRender ()
{
HRESULT Result = S_OK;
D2D1_SIZE_F RenderCanvasArea = { 0 };
IDWriteTextFormat * TextFormat = nullptr;
D2D1_RECT_F TextCanvasArea = { 0 };
Result = CreateDeviceResources ();
if (SUCCEEDED (Result))
{
RenderTarget->BeginDraw ();
RenderCanvasArea = RenderTarget->GetSize ();
RenderTarget->Clear (D2D1::ColorF (D2D1::ColorF::White));
if (SUCCEEDED (Result))
{
Result = DWriteFactory->CreateTextFormat (L"Segoe UI",
nullptr,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
25.0f,
L"en-us",
&TextFormat);
TextFormat->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_LEADING);
TextFormat->SetParagraphAlignment (DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
TextFormat->SetReadingDirection (DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
TextFormat->SetWordWrapping (DWRITE_WORD_WRAPPING_WRAP);
if (SUCCEEDED (Result) &&
TextFormat != nullptr)
{
TextCanvasArea = D2D1::RectF (0,
0,
RenderCanvasArea.width,
RenderCanvasArea.height);
RenderTarget->DrawTextW (DISPLAY_TEXT.c_str (),
static_cast <UINT32> (DISPLAY_TEXT.size ()),
TextFormat,
TextCanvasArea,
TextBlackBrush,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
}
}
Result = RenderTarget->EndDraw ();
}
if (Result == D2DERR_RECREATE_TARGET)
{
DiscardDeviceResources ();
Result = S_OK;
}
return Result;
}
HRESULT CreateDeviceResources ()
{
HRESULT Result = S_OK;
RECT rc = { 0 };
if (!RenderTarget)
{
GetClientRect (WindowHandle,
&rc);
D2D1_SIZE_U size = D2D1::SizeU (rc.right - rc.left,
rc.bottom - rc.top);
// Create a Direct2D render target.
Result = Direct2dFactory->CreateHwndRenderTarget (D2D1::RenderTargetProperties (),
D2D1::HwndRenderTargetProperties (WindowHandle, size),
&RenderTarget);
if (SUCCEEDED (Result))
{
// Create a blue brush.
Result = RenderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black),
&TextBlackBrush);
}
}
return Result;
}
void DiscardDeviceResources ()
{
SafeRelease (&RenderTarget);
SafeRelease (&TextBlackBrush);
}
HRESULT InitInstance (HINSTANCE hInstance,
int nCmdShow)
{
HRESULT Result = S_OK;
// Create the window.
WindowHandle = CreateWindow (L"D2DTextDemo",
L"Direct2D Text Demo Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
600,
200,
nullptr,
nullptr,
hInstance,
nullptr);
if (WindowHandle == nullptr)
{
Result = E_POINTER;
}
else
{
ShowWindow (WindowHandle,
nCmdShow);
UpdateWindow (WindowHandle);
}
return Result;
}
HRESULT CreateDeviceIndependentResources ()
{
HRESULT Result = S_OK;
Result = D2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED,
&Direct2dFactory);
if (SUCCEEDED (Result))
{
Result = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
__uuidof (IDWriteFactory),
reinterpret_cast <IUnknown **> (&DWriteFactory));
}
return Result;
}
LRESULT CALLBACK WndProc (HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
LRESULT Result = 0;
switch (message)
{
case WM_SIZE:
{
UINT width = LOWORD (lParam);
UINT height = HIWORD (lParam);
if (RenderTarget != nullptr)
{
// Note: This method can fail, but it's okay to ignore the
// error here, because the error will be returned again
// the next time EndDraw is called.
RenderTarget->Resize (D2D1::SizeU (width,
height));
}
}
break;
case WM_DISPLAYCHANGE:
{
InvalidateRect (hwnd, nullptr, FALSE);
}
break;
case WM_PAINT:
{
OnRender ();
ValidateRect (hwnd,
nullptr);
}
break;
case WM_DESTROY:
{
PostQuitMessage (0);
Result = 1;
}
break;
default:
{
Result = DefWindowProc (hwnd,
message,
wParam,
lParam);
}
break;
}
return Result;
}
int APIENTRY wWinMain (_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER (hInstance);
UNREFERENCED_PARAMETER (hPrevInstance);
UNREFERENCED_PARAMETER (lpCmdLine);
UNREFERENCED_PARAMETER (nCmdShow);
HRESULT ExitCode = S_OK;
MSG NextMessage = { 0 };
WNDCLASSEX wcex = { 0 };
ATOM WindowClassId = 0;
wcex.cbSize = sizeof (WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof (LONG_PTR);
wcex.hInstance = hInstance;
wcex.hbrBackground = nullptr;
wcex.lpszMenuName = nullptr;
wcex.hCursor = LoadCursor (nullptr, IDI_APPLICATION);
wcex.lpszClassName = L"D2DTextDemo";
if (SUCCEEDED (CoInitialize (nullptr)))
{
WindowClassId = RegisterClassEx (&wcex);
if (WindowClassId == 0)
{
ExitCode = HRESULT_FROM_WIN32 (GetLastError ());
}
if (SUCCEEDED (ExitCode))
{
ExitCode = CreateDeviceIndependentResources ();
}
if (SUCCEEDED (ExitCode))
{
ExitCode = InitInstance (hInstance,
nCmdShow);
}
if (SUCCEEDED (ExitCode))
{
while (GetMessage (&NextMessage,
nullptr,
0,
0))
{
TranslateMessage (&NextMessage);
DispatchMessage (&NextMessage);
}
}
CoUninitialize ();
SafeRelease (&Direct2dFactory);
SafeRelease (&DWriteFactory);
SafeRelease (&RenderTarget);
}
return ExitCode;
}
(The above example doesn't have perfect error handling, so it's important to audit this example code if the following is used in any project the reader is working on.)
Before attempting to compile this in Visual Studio, make sure your project has the "SubSystem" linker option set to Windows /SUBSYSTEM:WINDOWS.
Once compiled successfully, the following application window will appear:
I tested this coded example in Visual Studio 2022 Community Edition on Windows 11 with success.
Reference(s):
Creating a Simple Direct2D Application
Tutorial: Getting Started with DirectWrite
I want to place a GDI+ Bitmap onto the clipboard. The obvious way would be to:
use Bitmap.GetHBITMAP to create an HBITMAP
use SetClipboardData to place the HBITMAP onto the clipboard as a CF_BITMAP
So i try pseudo-code:
void PlaceBitmapOnClipboard(Bitmap image)
{
//Convert GDI+ Bitmap to GDI Bitmap
HBITMAP bmp;
image.GetHBITMAP(0, #bmp);
OpenClipboard(this.Handle);
EmptyClipboard();
SetClipboardData(CF_BITMAP, bmp);
CloseClipboard();
}
Error checking has been omitted for expository purposes; but neither function fails:
Not GetHBITMAP and its HRESULT style error
Nor does SetClipboardData return a null
But when i try to use the CF_BITMAP on the clipboard, it can't be pasted into Paint:
So, what is the correct code to fill in the function:
void PlaceBitmapOnClipboard(Bitmap image)
{
//TODO: Ask Stackoverflow to figure this out
}
We need to call:
SetClipboardData(CF_BITMAP, hbitmap_ddb)
Where hbitmap_ddb must be a compatible bitmap (DDB), not DIB which we get from Gdiplus::GetHBitmap
Or we call:
SetClipboardData(CF_DIB, hmemory)
Where hmemory is not HBITMAP. hmemory is described in documentation as:
A memory object containing a BITMAPINFO structure followed by the
bitmap bits.
Example using CF_BITMAP
Use CreateDIBitmap to create a compatible bitmap based on our DIB bitmap. Then call SetClipboardData with the new DDB bitmap.
Gdiplus::Bitmap gdibmp(L"file.bmp");
if(gdibmp.GetLastStatus() != Gdiplus::Ok)
return;
HBITMAP hbitmap;
auto status = gdibmp.GetHBITMAP(0, &hbitmap);
if(status != Gdiplus::Ok)
return;
BITMAP bm;
GetObject(hbitmap, sizeof bm, &bm);
DIBSECTION ds;
if(sizeof ds == GetObject(hbitmap, sizeof ds, &ds))
{
HDC hdc = GetDC(NULL);
HBITMAP hbitmap_ddb = CreateDIBitmap(hdc, &ds.dsBmih, CBM_INIT,
ds.dsBm.bmBits, (BITMAPINFO*)&ds.dsBmih, DIB_RGB_COLORS);
ReleaseDC(NULL, hdc);
if(OpenClipboard(hwnd))
{
EmptyClipboard();
SetClipboardData(CF_BITMAP, hbitmap_ddb);
CloseClipboard();
}
DeleteObject(hbitmap_ddb);
}
DeleteObject(hbitmap);
Example using CF_DIB
Use GlobalAlloc to allocate memory, and copy BITMAPINFOHEADER to that memory, followed by the bits. There is no need to worry about the color table, because Gdiplus::HBitmap returns 32-bit bitmap (at least on modern displays as far as I know)
Gdiplus::Bitmap gdibmp(L"file.bmp");
if(gdibmp.GetLastStatus() != Gdiplus::Ok)
return;
HBITMAP hbitmap;
auto status = gdibmp.GetHBITMAP(NULL, &hbitmap);
if(status != Gdiplus::Ok)
return;
BITMAP bm;
GetObject(hbitmap, sizeof bm, &bm);
BITMAPINFOHEADER bi =
{ sizeof bi, bm.bmWidth, bm.bmHeight, 1, bm.bmBitsPixel, BI_RGB };
std::vector<BYTE> vec(bm.bmWidthBytes * bm.bmHeight);
auto hdc = GetDC(NULL);
GetDIBits(hdc, hbitmap, 0, bi.biHeight, vec.data(), (BITMAPINFO*)&bi, 0);
ReleaseDC(NULL, hdc);
auto hmem = GlobalAlloc(GMEM_MOVEABLE, sizeof bi + vec.size());
auto buffer = (BYTE*)GlobalLock(hmem);
memcpy(buffer, &bi, sizeof bi);
memcpy(buffer + sizeof bi, vec.data(), vec.size());
GlobalUnlock(hmem);
if(OpenClipboard(hwnd))
{
EmptyClipboard();
SetClipboardData(CF_DIB, hmem);
CloseClipboard();
}
DeleteObject(hbitmap);