作者:Kenny Kerr 翻译:Ray Linn
在关于Direct2D技术的第三讲里,我将要展示其在互操作性上无与伦比的能力。我不打算遍历关于互操作性的所有细节,我想给你演示一个实际应用:分层窗口。分层窗口是那些已经久已存在且未被改进的Windows诸多特性之一,因此特别需要利用现代图形技术来提高它的使用效率。
这儿,我假定你有些Direct2D编程的基本知识。 如果没有,我建议你读下6月(我以前的文章:msdn.microsoft.com/magazine/dd861344 )和9月( msdn.microsoft.com/magazine/ee413543 )的MSDN有关文章,它们介绍了Direct2D的基础编程和绘图的有关内容。
一般来说,使用分层窗口总是为了些个不同的目标。通常,他们可以用来轻松,高效地制作的视觉效果和无闪烁的渲染。在过去GDI大行其道的日子里,使用分层窗口可算是个高招。但在普遍使用硬件加速的今天,分层窗口就有点落伍了,因为分层窗口仍然属于User32/GDI世界,且没有任何有效手段来支持高性能,高品质的微软图形平台-- DirectX。
分层窗口的确提供了某种独特的能力,它可以创建每像素不同透明度的窗口,目前Windows SDK尚未提供其他方式能实现同一目标。
应该指出的是,有两种分层窗口。这取决于是否需要控制每像素透明度或只需要控制整个窗口透明度。 本文的分层窗口是指前者,但如果你只需要控制整个窗口透明度,那么你可以在创建窗口之后,简单地调用之设置alpha值的SetLayeredWindowAttributes函数。
Verify(SetLayeredWindowAttributes(
windowHandle,
0, // no color key
180, // alpha value
LWA_ALPHA));
这儿假设你在采用了WS_EX_LAYERED扩展样式来创建窗口或在创建之后调用SetWindowLong来设置样式。这种方式的好处是显而易见的,你不需要对应用程序的重画窗口的方式做出任何改变,因为桌面窗口管理器(DWM)窗口会自动融合(Blend) 任何适当的窗口。如图:
在另一种情况下,你就需要自己处理一切。当然,如果使用了诸如Direct2D这样全新的渲染技术,这不是一个问题!
我们该如何进行? 从原理上来讲是相当直接的。首先,您需要填充一个UPDATELAYEREDWINDOWINFO结构,它提供了分层窗口的位置和大小,以及一个GDI设备上下文(DC),DC定义了窗口的表面,也潜藏了问题。DC是属于GDI这个旧世界的,且离硬件加速的DirectX的新世界甚远。
除了需要自己分配UPDATELAYEREDWINDOWINFO结构里的所有指针这些麻烦外,还有就是UPDATELAYEREDWINDOWINFO在Windows SDK中并未被详细说明,使用起来有些混乱。 笼统地讲,你需要为五个结构分配内存:通过DC复制的位图的所在位置,更新时窗口在桌面的位置,要复制的位图大小也就是窗口的大小:
POINT sourcePosition = {};
POINT windowPosition = {};
SIZE size = { 600, 400 };
然后是BLENDFUNCTION结构,它定义分层窗口将如何与桌面融合。这是一个令人惊讶的多功能结构,往往被忽视,但也可以非常有用。通常你可以如下填充它:
BLENDFUNCTION blend = {};
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
AC_SRC_ALPHA常量表明源位图有一个alpha通道,这是最常见的场景。
有意思的是,你可以象在SetLayeredWindowAttributes函数中一样使用SourceConstantAlpha来控制整个窗口的透明度。当它设为255时, 分层窗口将只使用每个像素的alpha值,你可以将其持续调整到零,即完全透明,来产生诸如渐入渐出的效果而不需要额外的重绘成本。这也是为什么它被称为BLENDFUNCTION结构的原因:这个结构的值生成了α-融合窗口。
最后我们如下填充LUPDATELAYEREDWINDOWINFO结构:
UPDATELAYEREDWINDOWINFO info = {};
info.cbSize = sizeof(UPDATELAYEREDWINDOWINFO);
info.pptSrc = &sourcePosition;
info.pptDst = &windowPosition;
info.psize = &size;
info.pblend = &blend;
info.dwFlags = ULW_ALPHA;
上面的代码是相当清晰的,唯一未被提及的是dwFlags成员变量。如果你使用过UpdateLayeredWindow函数,那么ULW_ALPHA常量,看起来就相当熟悉,它表明应该使用融合功能。
最后,您需要提供源DC的句柄,并调用UpdateLayeredWindowIndirect函数来更新窗口:
info.hdcSrc = sourceDC;
Verify(UpdateLayeredWindowIndirect(
windowHandle, &info));
总的就是这样。该窗口将不会收到任何WM_PAINT消息。任何时候你需要显示或更新窗口,只需调用UpdateLayeredWindowIndirect功能。 我用一个LayeredWindowInfo包装类来未上面的代码做个模板以方便后续使用:
class LayeredWindowInfo {
const POINT m_sourcePosition;
POINT m_windowPosition;
CSize m_size;
BLENDFUNCTION m_blend;
UPDATELAYEREDWINDOWINFO m_info;
public:
LayeredWindowInfo(
__in UINT width,
__in UINT height) :
m_sourcePosition(),
m_windowPosition(),
m_size(width, height),
m_blend(),
m_info() {
m_info.cbSize = sizeof(UPDATELAYEREDWINDOWINFO);
m_info.pptSrc = &m_sourcePosition;
m_info.pptDst = &m_windowPosition;
m_info.psize = &m_size;
m_info.pblend = &m_blend;
m_info.dwFlags = ULW_ALPHA;
m_blend.SourceConstantAlpha = 255;
m_blend.AlphaFormat = AC_SRC_ALPHA;
}
void Update(
__in HWND window,
__in HDC source) {
m_info.hdcSrc = source;
Verify(UpdateLayeredWindowIndirect(window, &m_info));
}
UINT GetWidth() const { return m_size.cx; }
UINT GetHeight() const { return m_size.cy; }
};
下面的代码演示了使用ATL/WTL的分层窗口和LayeredWindowInfo包装类的一个基本骨架.这首先要注意的是,代码没有必要调用UpdateWindow,因为此代码不使用WM_PAINT消息。 相反,它将立即调用Render方法,依次执行绘图,并提供DC给LayeredWindowInfo的Update方法。 绘图是如何发生、DC又从何而来,这是最有趣的地方。
class LayeredWindow :
public CWindowImpl<LayeredWindow,
CWindow, CWinTraits<WS_POPUP, WS_EX_LAYERED>> {
LayeredWindowInfo m_info;
public:
BEGIN_MSG_MAP(LayeredWindow)
MSG_WM_DESTROY(OnDestroy)
END_MSG_MAP()
LayeredWindow() :
m_info(600, 400) {
Verify(0 != __super::Create(0)); // parent
ShowWindow(SW_SHOW);
Render();
}
void Render() {
// Do some drawing here
m_info.Update(m_hWnd,
/* source DC goes here */);
}
void OnDestroy() {
PostQuitMessage(1);
}
};
GDI/GDI+的方法
我会先告诉您是在GDI/GDI+中是如何进行的。首先你需要创建一个乘以32字节/像素的位图,它采用蓝-绿-红-alpha(BGRA)颜色通道字节顺序。预先乘以32意味着颜色通道的值已经和alpha值相乘。这会给alpha融合图形带来更好的性能,但它也意味着你需要将颜色值除以alpha值以得到真正的颜色值。在GDI的术语中,这称为32BPP的设备无关位图(DIB),它通过填充BITMAPINFO结构,并传递给CreateDIBSection函数来创建。
BITMAPINFO bitmapInfo = {};
bitmapInfo.bmiHeader.biSize =
sizeof(bitmapInfo.bmiHeader);
bitmapInfo.bmiHeader.biWidth =
m_info.GetWidth();
bitmapInfo.bmiHeader.biHeight =
0 – m_info.GetHeight();
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression =
BI_RGB;
void* bits = 0;
CBitmap bitmap(CreateDIBSection(
0, // no DC palette
&bitmapInfo,
DIB_RGB_COLORS,
&bits,
0, // no file mapping object
0)); // no file offset
这儿有很多细节我们尚未涉及。这个API函数还有很长的路要走。应该注意的是,我指定为位图的负高度。BITMAPINFOHEADER结构定义位图是自上而下还是自下而上。如果高度为正,你得到一个自下而上的位图,反之,如果高度为负,则得到自下而上的位图。自上而下的位图,它们的原点在左上角,而自下而上的位图,原点则在左下角。
虽然不是严格要求,但我还是倾向使用自下而上的位图,因为这是现在的Windows图形处理元件的最常见格式,这样可以提高互操作性。我们可以通过以下方法计算出一个为正值的stride:
UINT stride = (width * 32 + 31) / 32 * 4;
现在,您有足够的信息来通过位指针绘制位图。除非您完全疯了,要使用绘图函数来实现,不幸地是,GDI提供的大部分函数并不支持alpah通道。这是GDI+的用武之地。
虽然你可以把位图的数据直接传递给GDI+,不过我们只要为它创建一个DC,反正我们传递给UpdateLayeredWindowIndirect函数的就只是它.要创建DC,我们需要调用恰当地命名为CreateCompatibleDC的函数,它会创建一个与桌面兼容的内存DC。然后,可以调用SelectObject函数来将位图选入DC中。下面的GdiBitmap包装类包含了这些有的没的功能:
class GdiBitmap {
const UINT m_width;
const UINT m_height;
const UINT m_stride;
void* m_bits;
HBITMAP m_oldBitmap;
CDC m_dc;
CBitmap m_bitmap;
public:
GdiBitmap(__in UINT width,
__in UINT height) :
m_width(width),
m_height(height),
m_stride((width * 32 + 31) / 32 * 4),
m_bits(0),
m_oldBitmap(0) {
BITMAPINFO bitmapInfo = { };
bitmapInfo.bmiHeader.biSize =
sizeof(bitmapInfo.bmiHeader);
bitmapInfo.bmiHeader.biWidth =
width;
bitmapInfo.bmiHeader.biHeight =
0 - height;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression =
BI_RGB;
m_bitmap.Attach(CreateDIBSection(
0, // device context
&bitmapInfo,
DIB_RGB_COLORS,
&m_bits,
0, // file mapping object
0)); // file offset
if (0 == m_bits) {
throw bad_alloc();
}
if (0 == m_dc.CreateCompatibleDC()) {
throw bad_alloc();
}
m_oldBitmap = m_dc.SelectBitmap(m_bitmap);
}
~GdiBitmap() {
m_dc.SelectBitmap(m_oldBitmap);
}
UINT GetWidth() const {
return m_width;
}
UINT GetHeight() const {
return m_height;
}
UINT GetStride() const {
return m_stride;
}
void* GetBits() const {
return m_bits;
}
HDC GetDC() const {
return m_dc;
}
};
GDI+的图形类,提供了在设备上绘图的一些方法,可以用来构造位图的DC。下面代码显示了如何让上面的LayeredWindow类用GDI+进行渲染。一旦我们把它和代码样板相结合,就相当直接:窗口的大小被传递给GdiBitmap的构造函数,位图的DC被传给Graphics的构造函数和Update方法。虽然简单,但GDI或GDI+的大部分函数都不支持硬件加速,也没有提供特别强大的渲染功能。
class LayeredWindow :
public CWindowImpl< ... {
LayeredWindowInfo m_info;
GdiBitmap m_bitmap;
Graphics m_graphics;
public:
LayeredWindow() :
m_info(600, 400),
m_bitmap(m_info.GetWidth(), m_info.GetHeight()),
m_graphics(m_bitmap.GetDC()) {
...
}
void Render() {
// Do some drawing with m_graphics object
m_info.Update(m_hWnd,
m_bitmap.GetDC());
}
...
架构问题
相反的,在WPF里创建一个分层窗口只需要以下几步:
class LayeredWindow : Window {
public LayeredWindow() {
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
// Do some drawing here
}
}
虽然非常简单,但它掩盖了其中的复杂性和使用分层窗口的架构限制。不管你如何优化它,分层窗口仍然必须遵循的在这篇文章里阐述的架构原则。虽然WPF中可以用硬件加速来进行渲染,但得到的结果仍然需要被复制到预乘的BGRA位图中,在调用UpdateLayeredWindowIndirect更新显示之前,它仍需被选入一个兼容的DC。WPF暴露出来的东西并不比一个bool变量多,它不得不为你无法控制的行为做些适当的选择。但为什么这是个问题?因为这涉及到硬件。
图形处理单元(GPU)提供了专用内存以达到最佳的性能。这意味着,如果你需要处理现有的位图,它就会从系统内存(RAM)复制到GPU内存,这往往要比在系统内存的两点间复制要慢得多。 反过来也是如此:如果你使用GPU创建和渲染位图,然后将它复制到系统内存,这也是一个昂贵的复制操作。
通常,这种情况不该发生的,GPU所渲染的位图应该直接被送往显示设备。但在分层窗中中,位图则必须被传送回系统内存,因为User32/GDI调用了需要访问位图的内核态和用户态的资源。例如User32里鼠标点击分层窗口的情况,在分层窗口上的点击与位图的alpha值是相关的,如果点击的地方是透明的,那么鼠标的消息会穿透该分层窗口。因此,系统内存里需要一个位图的副本来应付这种情况。一旦用UpdateLayeredWindowIndirect来拷贝位图,它又被直接送回GPU让DWM能够组合出桌面。
除了往返内存复制的开销,GPU和CPU之间的同步也是昂贵的。不象典型的CPU操作,GPU的操作往往都是异步的,当批量执行一系列渲染指令时,这提供了很好的性能。每次我们需要跨越到CPU时,GPU不的不强制清空批处理命令,而CPU不得不等待GPU完成操作,而达不到最佳性能。
这意味着我们需要对这些往返操作的开销和频率提高警惕。如果正在呈现的场景足够复杂,那么硬件加速性能可以轻松超过复制位图的开销。反之,如果渲染的开销不大,可以由CPU来进行,你可能会发现,没有硬件加速反而提供了更好的性能能。做出选择并不容易。有些图形处理器甚至没有专用的内存,而使用的系统内存,从而降低了部分复制开销。
但是不管是WPI还是GDI都不会给你选择的机会。在GDI的情况下,你只能是用CPU,而在
WPF里,你总被迫是用WPF的渲染方式,通常是硬件加速的Direct3D。
这时Direct2D来了。
- 大小: 35.2 KB
分享到:
相关推荐
标题中的“桌面时钟(基于分层窗口,Layered window)”是指一个应用程序,它使用Windows操作系统中的分层窗口技术来创建一个具有特殊效果的桌面时钟,如半透明或透明背景。这种技术允许开发者创建出更加吸引人的...
在Windows操作系统中,"分层窗口(layered window)"是一种高级窗口技术,它允许开发者创建具有透明度、半透明效果以及自定义绘制能力的窗口。这种技术在各种应用程序中得到了广泛的应用,如桌面时钟、桌面壁纸或者...
本文将探讨一个特别版本的桌面时钟——"桌面时钟 (基于分层窗口,Layered window), V2.3",该版本利用了先进的分层窗口技术和PNG图片,结合GDI+进行绘制,实现了独特的功能和视觉效果。 首先,我们来了解什么是...
在Windows编程中,分层窗口(Layered Window)是一种特殊类型的窗口,它可以实现透明、半透明、alpha混合以及自定义绘制效果。这种技术在创建高级用户界面时非常有用,例如制作浮动工具栏、动态效果或者复杂的图形...
在Windows编程中,分层窗口(Layered ...总的来说,`LayeredWindow.rar`这个示例代码是学习Windows分层窗口技术的一个好起点,它涵盖了基本的使用方法和关键API,可以帮助开发者掌握如何创建具有高级视觉效果的窗口。
### GDI+分层窗口在VS2010下的...综上所述,利用GDI+和VS2010可以实现较为复杂的分层窗口功能,如加载PNG图片并将其绘制到窗口上,以及动态调整窗口的透明度等。这对于开发具有良好用户体验的应用程序来说非常有用。
总之,Visual C++中的分层窗口技术为开发者提供了丰富的界面设计可能性,但同时也需要对Windows API有深入的理解和熟练的运用。通过以上步骤和文件解析,我们可以更好地理解如何在VC++项目中创建和管理分层窗口。
分层窗口(Layered Windows)是Windows API提供的一种特性,允许窗口具有透明度和半透明效果,可以创建出复杂的用户界面。在Windows编程中,通过设置窗口的WS_EX_LAYERED风格,并使用UpdateLayeredWindow API函数,...
总结来说,"基于分层窗口创建的GIF图片显示"技术涉及到Windows图形编程、GIF解码、分层窗口特性、透明度处理和动画实现等多个方面。通过熟练掌握这些知识点,我们可以创建出如"象瑞星的桌面小狮子一样"的动态透明GIF...
在"易语言PNG分层窗口源码"中,开发者可能已经封装了这些功能,通过易语言的语法结构,将API调用过程简化,使得其他易语言开发者可以更方便地在自己的程序中应用PNG图像和分层窗口技术。这可能包括了以下步骤: 1. ...
总之,DirectUI结合分层窗口技术和WTL、GDI+,为Windows应用开发提供了强大的界面设计工具。通过深入研究"shadow_window"等具体实现,开发者可以掌握更多关于DirectUI的实践技巧,提升自己的UI开发能力。
在Windows操作系统中,分层窗口(Layered Windows)是一种特殊的窗口类型,允许开发者控制窗口的透明度、颜色混合和alpha通道,从而实现复杂的视觉效果。通过设置窗口的WS_EX_LAYERED样式,我们可以创建一个分层窗口...
在本文中,我们将深入探讨如何使用分层窗体(Layered Windows)技术来创建一个具有透明效果的“老板软件”,即一种专为职场环境设计的轻量级应用,旨在提供隐蔽的信息查看体验。该软件的0.1.0版本已实现了窗口透明度...
这个过程虽然相对复杂,但通过GDI+和分层窗体技术,我们可以创建出具有PNG透明效果的动态窗口,使其能够在视频或其他窗口之上呈现,并与背景无缝融合。在实际项目中,可能还需要考虑性能优化,例如减少不必要的重绘...
在这个"VC, Wind32api窗口分层淡入"项目中,我们将探讨如何使用Win32 API来实现窗口的分层和淡入效果。 首先,让我们理解窗口分层的概念。在Windows系统中,每个窗口都有一个Z顺序,决定了它们在屏幕上的覆盖关系。...
在Windows编程领域,"layered窗体阴影"是一种高级特性,允许开发者为应用程序的窗口添加阴影效果,使得界面更加美观和现代。这个技术主要利用了Windows API中的layered window功能,结合GDI+库来处理带有透明度...
在Windows编程领域,"LayeredWindow"是一种特殊的窗口类型,它允许开发者创建具有透明度、半透明效果...通过深入理解和熟练运用layered window技术,开发者可以创造出更具视觉吸引力且用户体验良好的Windows应用程序。
为了使窗口透明,还可以设置WS_EX_LAYERED窗口样式,并使用SetLayeredWindowAttributes函数调整透明度和颜色关键值。 在Direct2D中,实现不规则窗口的过程类似,但API更为现代且强大。首先,需要创建ID2D1Factory,...
在Windows操作系统中,开发人员经常需要创建自定义的窗口,比如无边框窗口,以便实现特定的用户界面设计。无边框窗口可以提供更自由的布局和视觉效果,例如允许用户通过鼠标拖动来改变窗口大小,同时还能调整窗口的...