<think>我们之前实现的水印类CWatermarkWnd,在OnPaint中使用了双缓冲技术,但出现背景色从透明变为黑色的问题。这是因为在双缓冲绘图过程中,内存DC的位图初始状态是黑色(0,0,0)的,而我们需要的是透明背景。因此,我们需要将背景初始化为透明。解决方案:1.创建兼容的内存DC和位图后,将背景初始化为透明(而不是黑色或白色)。2.使用GDI+绘制时,需要确保绘制的背景是透明的。可以通过设置graphics.Clear为透明色来实现。具体修改步骤如下:在CWatermarkWnd::OnPaint()函数中:-创建内存DC和位图后,我们需要将位图的背景设置为透明。但是,我们不能简单地使用FillSolidRect填充白色,因为那样会变成不透明的背景。-正确的方法是使用GDI+的Clear函数,并传入一个完全透明的颜色(Alpha=0)。但是,我们注意到在原有的代码中,我们已经使用了:graphics.Clear(Gdiplus::Color(0,0,0,0));//透明背景这行代码应该已经将背景设置为透明了。然而,为什么还会出现黑色背景呢?可能是因为在将内存位图复制到屏幕DC时,黑色背景(未初始化的位图)被复制了过去。因为我们在创建位图后没有初始化,而紧接着我们使用GDI+绘制,但是GDI+的Clear操作实际上只是将位图设置为透明背景(在内存中是ARGB=0,0,0,0),但当我们使用BitBlt进行复制时,SRCCOPY复制方式会直接将这个内存位图(包含透明信息)复制到屏幕。但是,透明背景在屏幕上显示为黑色?这不对,因为BitBlt不支持透明混合。因此,问题在于:我们创建了一个分层窗口(WS_EX_LAYERED),但是我们在OnPaint中直接使用BitBlt绘制到屏幕上,这种方式无法利用分层窗口的透明特性。我们应该使用UpdateLayeredWindow来实现透明效果。所以,我们需要改变绘制策略:不使用OnPaint和CPaintDC,而是使用UpdateLayeredWindow来更新整个窗口。这样我们就可以直接使用一个带有Alpha通道的位图来更新窗口。因此,我们需要重新设计OnPaint函数,改为使用UpdateLayeredWindow的方式。步骤如下:1.在创建窗口时,我们使用了WS_EX_LAYERED样式。2.在绘制时,我们不使用OnPaint,而是自己创建一个绘制函数,并在需要更新时调用。3.绘制函数中,创建一个兼容的DC,并创建一个32位色的位图(支持Alpha通道)。4.在这个位图上,使用GDI+绘制透明背景和水印文字。5.然后使用UpdateLayeredWindow函数更新窗口。但是,由于我们使用了MFC,并且窗口是CWnd,我们可以重写OnPaint,但是不要用CPaintDC,而是自己创建DC。或者,我们可以直接使用UpdateLayeredWindow来更新。修改方案:我们将不再使用OnPaint,而是自己实现一个Redraw函数,在需要重绘的时候调用(如初始创建、文字改变、窗口大小改变时)。具体修改如下:第一步:从消息映射中移除ON_WM_PAINT(),并且移除OnPaint函数。第二步:添加一个Redraw函数,用于重新绘制分层窗口。第三步:在Redraw函数中,创建一个32位色的位图,使用GDI+绘制,然后调用UpdateLayeredWindow。第四步:在适当的地方调用Redraw(如在Create函数最后、UpdateWatermark、OnSize等)。由于改动较大,我们将重新写一个CWatermarkWnd类的绘制部分。具体代码:在CWatermarkWnd类中:1.移除消息映射中的ON_WM_PAINT2.添加成员函数voidRedraw();3.修改OnSize,不再调用基类的OnSize,而是调用Redraw4.修改UpdateWatermark,调用Redraw而不是InvalidateRedraw函数的实现:```cppvoidCWatermarkWnd::Redraw(){//如果没有创建窗口,则返回if(!GetSafeHwnd())return;CRectrect;GetWindowRect(&rect);CPointptWinScreen(rect.TopLeft());CPointptOrigin(0,0);CSizesizeWindow(rect.Width(),rect.Height());//创建内存DCCClientDCdcScreen(NULL);CDCmemDC;memDC.CreateCompatibleDC(&dcScreen);//创建32位位图CBitmapbmp;BITMAPINFObmi;memset(&bmi,0,sizeof(BITMAPINFO));bmi.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);bmi.bmiHeader.biWidth=rect.Width();bmi.bmiHeader.biHeight=rect.Height();bmi.bmiHeader.biPlanes=1;bmi.bmiHeader.biBitCount=32;bmi.bmiHeader.biCompression=BI_RGB;bmi.bmiHeader.biSizeImage=0;LPVOIDpBits=NULL;HBITMAPhBmp=::CreateDIBSection(memDC.m_hDC,&bmi,DIB_RGB_COLORS,&pBits,NULL,0);if(hBmp==NULL)return;bmp.Attach(hBmp);CBitmap*pOldBmp=memDC.SelectObject(&bmp);//使用GDI+绘制到内存DCGdiplus::Graphicsgraphics(memDC);graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver);//覆盖模式graphics.SetCompositingQuality(Gdiplus::CompositingQualityHighSpeed);graphics.SetPixelOffsetMode(Gdiplus::PixelOffsetModeNone);graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAliasGridFit);//清除为透明graphics.Clear(Gdiplus::Color(0,0,0,0));//完全透明//设置水印字体、颜色等Gdiplus::Fontfont(L"微软雅黑",m_fontSize,Gdiplus::FontStyleBold,Gdiplus::UnitPixel);BYTEr=GetRValue(m_color);BYTEg=GetGValue(m_color);BYTEb=GetBValue(m_color);Gdiplus::SolidBrushbrush(Gdiplus::Color(m_alpha,r,g,b));//使用传入的透明度//计算位置,居中Gdiplus::RectFlayoutRect(0,0,rect.Width(),rect.Height());Gdiplus::StringFormatformat;format.SetAlignment(Gdiplus::StringAlignmentCenter);format.SetLineAlignment(Gdiplus::StringAlignmentCenter);format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap);//旋转45度Gdiplus::Matrixmatrix;matrix.RotateAt(-45.0f,Gdiplus::PointF(rect.Width()/2.0f,rect.Height()/2.0f));graphics.SetTransform(&matrix);//绘制水印graphics.DrawString(m_strWatermark.GetString(),m_strWatermark.GetLength(),&font,layoutRect,&format,&brush);//恢复变换graphics.ResetTransform();//更新分层窗口BLENDFUNCTIONblendFunc;blendFunc.BlendOp=AC_SRC_OVER;blendFunc.BlendFlags=0;blendFunc.SourceConstantAlpha=255;//整个窗口的透明度,255表示不透明,但具体像素的透明度由位图的alpha通道决定blendFunc.AlphaFormat=AC_SRC_ALPHA;//说明我们带有alpha通道//使用UpdateLayeredWindow更新窗口CPointptDst=ptWinScreen;::UpdateLayeredWindow(GetSafeHwnd(),dcScreen.GetSafeHdc(),&ptDst,&sizeWindow,memDC.GetSafeHdc(),&ptOrigin,0,&blendFunc,ULW_ALPHA);//清理memDC.SelectObject(pOldBmp);bmp.DeleteObject();}```同时,在Create函数中,创建窗口后,我们调用Redraw来绘制水印。注意:我们在OnSize中调整窗口位置后,也需要重绘,所以OnSize改为:```cppvoidCWatermarkWnd::OnSize(UINTnType,intcx,intcy){CWnd::OnSize(nType,cx,cy);if(GetSafeHwnd()){//当窗口大小变化时,我们重新设置整个窗口位置以覆盖父窗口,然后重绘if(CWnd*pParent=GetParent()){CRectparentRect;pParent->GetClientRect(&parentRect);MoveWindow(parentRect);}Redraw();//重绘}}```并且,在Create函数的最后,在创建窗口后,我们不需要立即MoveWindow,因为初始创建时也会触发OnSize,所以我们在Create中只需调用一次Redraw:```cppBOOLCWatermarkWnd::Create(...){//...之前的窗口创建代码...if(CreateEx(...)){//初始位置设置if(pParentWnd){CRectrect;pParentWnd->GetClientRect(&rect);MoveWindow(rect);}Redraw();returnTRUE;}returnFALSE;}```这样,我们就使用了UpdateLayeredWindow来绘制水印,并且支持透明背景,避免黑色背景问题。同时,因为使用了UpdateLayeredWindow,窗口不会再收到WM_PAINT消息,所以我们的绘制代码在Redraw中,由我们自己触发。另外,我们在GDI+绘制时已经将背景清除为透明,所以不会再出现黑色背景。注意:UpdateLayeredWindow要求窗口是分层窗口(我们创建时使用了WS_EX_LAYERED),并且这个函数会改变窗口的属性,使用后窗口将不会收到WM_PAINT消息,所以不再使用OnPaint。因此,我们移除了消息映射中的ON_WM_PAINT,并删除了OnPaint函数。现在,背景透明问题应该就解决了。但是,由于我们使用了UpdateLayeredWindow,窗口的绘制完全由我们自己控制,所以双缓冲也不再需要(因为UpdateLayeredWindow本身就会避免闪烁),所以Redraw函数中我们直接创建了一个32位的位图,然后在这个位图上绘制,最后更新到窗口。此外,由于UpdateLayeredWindow需要整个窗口的位图,我们每次重绘都是绘制整个窗口,所以对于频繁更新可能需要优化,但水印一般更新不频繁。总结:通过将绘制方式改为UpdateLayeredWindow,使用32位带Alpha通道的位图,并清除背景为透明,我们解决了背景变黑的问题。其他注意事项:-在初始化GDI+时,我们在构造函数中初始化,在析构函数中也要记得关闭GDI+。-如果水印需要动态变化(比如文字变化),调用UpdateWatermark,它会调用Redraw。关于性能:每次重绘都创建了一个新的位图,如果频繁改变水印,可能会影响性能。但水印通常不频繁更新,所以可以接受。现在,修改后的CWatermarkWnd类应该可以正确显示透明背景的水印。</think>
基于您描述的双缓冲后水印背景变黑的问题,我提供一个完整的解决方案。关键在于正确初始化位图的透明背景和处理色彩合成方式:
### 双缓冲透明水印实现方案
```cpp
// WatermarkWnd.cpp
void CWatermarkWnd::OnPaint() {
CPaintDC dcScreen(this);
CRect rect;
GetClientRect(&rect);
// 创建兼容DC和32位位图(支持Alpha通道)
CDC memDC;
memDC.CreateCompatibleDC(&dcScreen);
CBitmap memBitmap;
// 关键修改:使用32位色深位图(支持Alpha通道)
BITMAPINFO bmi;
ZeroMemory(&bmi, sizeof(BITMAPINFO));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = rect.Width();
bmi.bmiHeader.biHeight = rect.Height();
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; // 32位色深
bmi.bmiHeader.biCompression = BI_RGB;
void* pBits = NULL;
HBITMAP hBitmap = CreateDIBSection(
dcScreen.GetSafeHdc(),
&bmi,
DIB_RGB_COLORS,
&pBits,
NULL,
0
);
memBitmap.Attach(hBitmap);
CBitmap* pOldBitmap = memDC.SelectObject(&memBitmap);
// 使用GDI+绘制到内存DC
Gdiplus::Graphics graphics(memDC.GetSafeHdc());
// 关键修改:清除为完全透明背景
graphics.Clear(Gdiplus::Color(0, 0, 0, 0)); // ARGB(0, 0, 0, 0)
// 设置水印字体和颜色
Gdiplus::Font font(L"Arial", m_fontSize, Gdiplus::FontStyleBold);
BYTE r = GetRValue(m_color);
BYTE g = GetGValue(m_color);
BYTE b = GetBValue(m_color);
Gdiplus::SolidBrush brush(Gdiplus::Color(m_alpha, r, g, b));
// 计算旋转中心点和水印位置
Gdiplus::RectF layoutRect(0, 0, rect.Width(), rect.Height());
graphics.TranslateTransform(
static_cast<Gdiplus::REAL>(rect.Width() / 2),
static_cast<Gdiplus::REAL>(rect.Height() / 2)
);
graphics.RotateTransform(-45.0f); // 旋转角度
// 绘制水印文字
graphics.DrawString(
m_strWatermark.GetString(),
m_strWatermark.GetLength(),
&font,
layoutRect,
&Gdiplus::StringFormat(),
&brush
);
// 关键修改:使用Alpha混合模式复制到屏幕
BLENDFUNCTION blend;
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255; // 完全使用位图的Alpha通道
blend.AlphaFormat = AC_SRC_ALPHA; // 标识位图包含Alpha通道
// 使用Alpha混合复制到屏幕(代替BitBlt)
dcScreen.AlphaBlend(
0, 0, rect.Width(), rect.Height(),
&memDC,
0, 0, rect.Width(), rect.Height(),
blend
);
// 清理资源
memDC.SelectObject(pOldBitmap);
memBitmap.DeleteObject();
}
```
### 解决关键问题的技术说明
1. **32位色深位图创建**
```cpp
bmi.bmiHeader.biBitCount = 32; // 启用Alpha通道
```
创建支持Alpha通道的位图(非默认的24位)[^2]
2. **透明背景初始化**
```cpp
graphics.Clear(Gdiplus::Color(0, 0, 0, 0)); // ARGB(0,0,0,0)
```
使用完全透明的背景替代默认黑色背景
3. **Alpha混合复制**
```cpp
dcScreen.AlphaBlend(..., AC_SRC_ALPHA);
```
替代BitBlt,保留源位图的Alpha信息:
$$\text{目标颜色} = \text{源颜色} \times \alpha + \text{目标颜色} \times (1 - \alpha)$$
4. **色彩混合函数设置**
```cpp
blend.AlphaFormat = AC_SRC_ALPHA; // 启用源Alpha通道
```
### 支持方法修改
1. 更新`Create()`中的窗口样式:
```cpp
return CreateEx(
WS_EX_LAYERED | WS_EX_TRANSPARENT,
className,
_T("Watermark Window"),
WS_POPUP | WS_VISIBLE,
0, 0, 0, 0,
pParentWnd->GetSafeHwnd(),
NULL
);
```
2. 修改透明度应用位置:
```cpp
void CWatermarkWnd::SetTransparency(BYTE alpha) {
m_alpha = alpha;
SetLayeredWindowAttributes(0, m_alpha, LWA_ALPHA); // 全局窗口透明度
}
```
### 工作原理流程图
```mermaid
graph TD
A[创建32位位图] --> B[初始化全透明背景]
B --> C[绘制水印内容]
C --> D[设置Alpha混合参数]
D --> E[使用AlphaBlend输出]
E --> F[保留透明度通道]
```
### 常见问题解决
1. **位图残留问题**:每次`OnPaint()`后释放位图资源
```cpp
memBitmap.DeleteObject(); // 防止GDI资源泄漏
```
2. **文字锯齿问题**:
```cpp
graphics.SetTextRenderingHint(
Gdiplus::TextRenderingHintAntiAlias
);
```
3. **多显示器支持**:
```cpp
// 在OnSize中获取主屏尺寸
CRect screenRect;
::SystemParametersInfo(SPI_GETWORKAREA, 0, &screenRect, 0);
```
### 性能优化建议
```cpp
// 在类声明中添加
class CWatermarkWnd : public CWnd {
protected:
Gdiplus::Bitmap* m_pCachedBitmap; // 缓存位图
bool m_bRedrawNeeded; // 重绘标志
};
```
通过缓存机制减少重绘次数,仅在文字或尺寸变化时重绘。