`

【技巧】分层窗口Layered Windows和Direct2D技术

阅读更多
作者: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)

    标题中的“桌面时钟(基于分层窗口,Layered window)”是指一个应用程序,它使用Windows操作系统中的分层窗口技术来创建一个具有特殊效果的桌面时钟,如半透明或透明背景。这种技术允许开发者创建出更加吸引人的...

    桌面钟表 (layered window 分层窗口)

    在Windows操作系统中,"分层窗口(layered window)"是一种高级窗口技术,它允许开发者创建具有透明度、半透明效果以及自定义绘制能力的窗口。这种技术在各种应用程序中得到了广泛的应用,如桌面时钟、桌面壁纸或者...

    桌面时钟 (基于分层窗口,Layered window), V2.3

    本文将探讨一个特别版本的桌面时钟——"桌面时钟 (基于分层窗口,Layered window), V2.3",该版本利用了先进的分层窗口技术和PNG图片,结合GDI+进行绘制,实现了独特的功能和视觉效果。 首先,我们来了解什么是...

    dd.rar_layeredwindow VC_分层窗口

    在Windows编程中,分层窗口(Layered Window)是一种特殊类型的窗口,它可以实现透明、半透明、alpha混合以及自定义绘制效果。这种技术在创建高级用户界面时非常有用,例如制作浮动工具栏、动态效果或者复杂的图形...

    LayeredWindow.rar

    在Windows编程中,分层窗口(Layered ...总的来说,`LayeredWindow.rar`这个示例代码是学习Windows分层窗口技术的一个好起点,它涵盖了基本的使用方法和关键API,可以帮助开发者掌握如何创建具有高级视觉效果的窗口。

    GDI+分层窗口

    ### GDI+分层窗口在VS2010下的...综上所述,利用GDI+和VS2010可以实现较为复杂的分层窗口功能,如加载PNG图片并将其绘制到窗口上,以及动态调整窗口的透明度等。这对于开发具有良好用户体验的应用程序来说非常有用。

    visual c++创建分层窗口.zip

    总之,Visual C++中的分层窗口技术为开发者提供了丰富的界面设计可能性,但同时也需要对Windows API有深入的理解和熟练的运用。通过以上步骤和文件解析,我们可以更好地理解如何在VC++项目中创建和管理分层窗口。

    e语言-易语言PNG分层窗口

    分层窗口(Layered Windows)是Windows API提供的一种特性,允许窗口具有透明度和半透明效果,可以创建出复杂的用户界面。在Windows编程中,通过设置窗口的WS_EX_LAYERED风格,并使用UpdateLayeredWindow API函数,...

    基于分层窗口创建的GIF图片显示

    总结来说,"基于分层窗口创建的GIF图片显示"技术涉及到Windows图形编程、GIF解码、分层窗口特性、透明度处理和动画实现等多个方面。通过熟练掌握这些知识点,我们可以创建出如"象瑞星的桌面小狮子一样"的动态透明GIF...

    易语言源码易语言PNG分层窗口源码.rar

    在"易语言PNG分层窗口源码"中,开发者可能已经封装了这些功能,通过易语言的语法结构,将API调用过程简化,使得其他易语言开发者可以更方便地在自己的程序中应用PNG图像和分层窗口技术。这可能包括了以下步骤: 1. ...

    directUI 界面相关

    总之,DirectUI结合分层窗口技术和WTL、GDI+,为Windows应用开发提供了强大的界面设计工具。通过深入研究"shadow_window"等具体实现,开发者可以掌握更多关于DirectUI的实践技巧,提升自己的UI开发能力。

    创建分层窗口,实现图像渐变.zip

    在Windows操作系统中,分层窗口(Layered Windows)是一种特殊的窗口类型,允许开发者控制窗口的透明度、颜色混合和alpha通道,从而实现复杂的视觉效果。通过设置窗口的WS_EX_LAYERED样式,我们可以创建一个分层窗口...

    使用分层窗体创建老板软件(0.1.0)(透明窗体示例、分层窗体示例)

    在本文中,我们将深入探讨如何使用分层窗体(Layered Windows)技术来创建一个具有透明效果的“老板软件”,即一种专为职场环境设计的轻量级应用,旨在提供隐蔽的信息查看体验。该软件的0.1.0版本已实现了窗口透明度...

    VC利用GDI+采用分层窗体实现PNG透明窗体

    这个过程虽然相对复杂,但通过GDI+和分层窗体技术,我们可以创建出具有PNG透明效果的动态窗口,使其能够在视频或其他窗口之上呈现,并与背景无缝融合。在实际项目中,可能还需要考虑性能优化,例如减少不必要的重绘...

    VC,Wind32api窗口分层淡入

    在这个"VC, Wind32api窗口分层淡入"项目中,我们将探讨如何使用Win32 API来实现窗口的分层和淡入效果。 首先,让我们理解窗口分层的概念。在Windows系统中,每个窗口都有一个Z顺序,决定了它们在屏幕上的覆盖关系。...

    layered窗体阴影

    在Windows编程领域,"layered窗体阴影"是一种高级特性,允许开发者为应用程序的窗口添加阴影效果,使得界面更加美观和现代。这个技术主要利用了Windows API中的layered window功能,结合GDI+库来处理带有透明度...

    LayeredWindow

    在Windows编程领域,"LayeredWindow"是一种特殊的窗口类型,它允许开发者创建具有透明度、半透明效果...通过深入理解和熟练运用layered window技术,开发者可以创造出更具视觉吸引力且用户体验良好的Windows应用程序。

    97288396windows中不规则窗口的实现

    为了使窗口透明,还可以设置WS_EX_LAYERED窗口样式,并使用SetLayeredWindowAttributes函数调整透明度和颜色关键值。 在Direct2D中,实现不规则窗口的过程类似,但API更为现代且强大。首先,需要创建ID2D1Factory,...

    windows透明的无边框窗口

    在Windows操作系统中,开发人员经常需要创建自定义的窗口,比如无边框窗口,以便实现特定的用户界面设计。无边框窗口可以提供更自由的布局和视觉效果,例如允许用户通过鼠标拖动来改变窗口大小,同时还能调整窗口的...

Global site tag (gtag.js) - Google Analytics