使用 Direct2D 绘制分层窗口 已翻译 100%

Dy_ 投递于 2014/01/13 21:55 (共 22 段, 翻译完成于 01-21)
阅读 10658
收藏 43
2
加载中

这是我关于Direct2D的第三篇介绍了,今天主要讲下其无可比拟的互操作性。为了避免繁缛末节的讲述,我们从一个实例入手:层级窗口。相对于windows的其它众多功能。层级窗口并未做相应的更新,因此使用现代图像处理技术时要特别注意,以有效发挥其功效。

本文假定读者具有一定的Direct2D 开发基础。如若不然,请参考本人六月和九月发表的Direct2D开发和绘图的文章 ( msdn.microsoft.com/magazine/dd861344 )   msdn.microsoft.com/magazine/ee413543

petert
翻译于 2014/01/17 13:32
1

层次窗口原本只有一些简单的用途。其中最有用的是有效地生成平滑的视觉效果。在只有GDI主导图像生成的时代,这确实是个很好的补充。但在硬件速度已大幅提升的当下,依然在32位的GDI模式中,至今没有显著功能升级来应用微软高性能高质量图像生成平台工具DirectX,使层级窗口不再具有优势。

在用windowSDK生成基于像素的 α混合窗体方面,层次窗口确实独一无二。

petert
翻译于 2014/01/20 16:40
1

有两种类型的层次窗口,其区别在于你是否需要基于像素的节点控制或是窗口整体的把控。本文是基于第一种状况的,如果需要基于窗体的控制,只需要在创建窗体后调用SetLayeredWindowAttributes 来设置alpha 值。

Verify(SetLayeredWindowAttributes(
  windowHandle,
  0, // no color key
  180, // alpha value
  LWA_ALPHA));

上述代码假定创建窗体可以通过在SetWindowLong 方法调用中设置WS_EX_LAYERED来实现其可扩展性。Figure 1为示例。这样做的好处显而易见,你无须做出任何的更改而由DWM桌面窗体管理器来自动绘制窗体。如果不这样做,所有实现都需要代码实现。如果你现在就在使用Direct2D 或其他什么最新的解析技术,也不会有上述问题!

Window with Alpha Value

Figure 1 Window with Alpha Value

petert
翻译于 2014/01/20 16:43
1

好的,那现在该怎么继续呢?从基础层面来说,都很直观。首先,完善结构体UPDATELAYEREDWINDOWINFO在其当中完成层次窗体的位置大小以及GDI设置文本内容以完成窗体的展示设置-问题随之而来。DC只是GDI中的概念,对于速度已大幅提升的硬件和DirectX技术来说还是很滞后。等会再详细讨论这个问题。

UPDATELAYEREDWINDOWINFO结构体在WindowsSDK中的文档说明并不完整,想象了它的易用性更别说需要通过一堆指针来定位当前位置。共有五个结构体来完成定位工作。定位要从DC拷贝的bitmap的资源位置。 定位当窗体更新后的新位置。需要拷贝的bitmap 文件大小以及操作窗体的大小:

POINT sourcePosition = {};
POINT windowPosition = {};
SIZE size = { 600, 400 };
petert
翻译于 2014/01/20 16:45
2

接下来是BLENDFUNCTION结构体来定义层次窗口如何与桌面互溶。这块虽有很多不同实现的结构体,但通常都是以下模式:

BLENDFUNCTION blend = {};
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;

AC_SRC_ALPHA常量仅仅表明源位图文件可以支持alpha值设定。

通过设置Alpha值可以完成和SetLayeredWindowAttributes 方法同样的设置窗体透明度的功能。当设置为255时,层次窗体将使用基于像素的alpha 数值,但是还是能够更改到0从而变成全透明窗体,进而实现窗体渐进显示而无需重新绘制窗体。至于为什么将这个结构体命名为BLENDFUNCTION也显而易见了:结构体的结果就反映了可透明化窗体的透明度。

petert
翻译于 2014/01/20 16:46
1

最终UPDATELAYEREDWINDOWINFO结构体将所有的信息统一起来:

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方法即可。为了使这部分铺垫代码更方便的使用,在后续文中我将使用图2中的LayeredWindowInfo类将其包装一下.

petert
翻译于 2014/01/20 16:49
1

Figure 2 LayeredWindowInfo Wrapper Class

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; }
};

表三展示了使用图二中LayeredWindowInfo包装类 和ATL/WTL创建层次窗体的基本结构。需要注意的是,代码中并没有WM_PAINT值所以无需调用UpdateWindow方法。他将会直接调用Render方法进而触发绘图功能并传递DC信息到LayeredWindowInfo当中以完成一次动作。如何完成绘图又从哪里获取到所需的DC信息是比较有意思的地方。

Figure 3 Layered Window Skeleton

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);
  }
};
petert
翻译于 2014/01/20 16:49
1

使用GDI/GDI+

先来看下如何使用GDI/GDI+来实现。首先创建一个使用蓝-绿-红alpha字节顺序模式计算好的32位像素的位图文件。预先计算好的意味着所有的数值都已经乘上了alpha 的值。这对于提高支持alpha 混合现实的图像性能很有帮助,但如果想获取图像初始值就需要逆向操作除去alpha值才行。

用GDI 的术语来说就是32-bpp device-independent bitmap (DIB) 是通过完善BITMAPINFO 结构体后调用CreateDIBSection 方法来实现的(见图表4)

Figure 4 Creating a DIB

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
petert
翻译于 2014/01/20 16:50
1

这里面有很多细节,但和现在的主题没什么关系。这个API的功能历史悠久,但请注意我设置的那个bitmap的高度值是负数。BITMAPINFOHEADER结构体展示的是自顶向下还是自底向上的位图。高度为正数则是自底向上,负数为自顶向下。自顶向下位图的原点在左上角,自底向上的原点在左下角。 

虽不是成文的规定,但在本例中我使用自顶向下的位图模式,这是主流图像处理模式,因此会有较好的互操作性。这样做也会得到正数模式的步展值。计算方法如下:

UINT stride = (width * 32 + 31) / 32 * 4;

这会你完全可以通过bits指针来绘制位图了。除非脑子有病,否者总得有一些方法调用去完成绘制功能。但目前GDI提供的方法中并不支持alpha计算模式,因此出现了GDI+.

petert
翻译于 2014/01/20 16:54
1

尽管可以直接给GDI+传递位图数据,咱们还是通过创建一个DC来实现该功能,反正在调用UpdateLayeredWindowIndirect方法时还是会用到。通过调用CreateCompatibleDC方法来创建基于桌面的DC内存空间。然后调用SelectObject将位图数据传给DC.图表5中的GdiBitmap包装类实现了上述过程并优化.

Figure 5 DIB Wrapper Class

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;
  }
};

petert
翻译于 2014/01/20 16:55
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(10)

我怀疑你有问题
我怀疑你有问题
Direct2D绘制Metro风格非常容易
哆啦比猫
哆啦比猫

引用来自“sevk”的评论

引用来自“哆啦比猫”的评论

引用来自“sevk”的评论

不知道Linux的接口咋样

有 opengl 和 compositor 一切都很简单

waylang 使用的的是 opengl 吧?

wayland 不使用 opengl, wayland compositor 使用 opengl es
sevk
sevk

引用来自“哆啦比猫”的评论

引用来自“sevk”的评论

不知道Linux的接口咋样

有 opengl 和 compositor 一切都很简单

waylang 使用的的是 opengl 吧?
哆啦比猫
哆啦比猫

引用来自“sevk”的评论

不知道Linux的接口咋样

有 opengl 和 compositor 一切都很简单
k
kchr
顺便看了下译文, 感觉和英文原文不是一片文章啊.
k
kchr

引用来自“sevk”的评论

不知道Linux的接口咋样

虽然我是 linux 支持者,但实话说,linux 的 xlib 结构比这个复杂百倍. 而且要用对不能只看看 API 文档,必须深入理解 x 的运作机制才行.

2005 年 jon smirl 写过一篇 The state of Linux graphics, 基本就是各种互相不兼容的 API 在一个热气腾腾的大锅里熬.

我因为深有感触, 所以曾经动手把它翻译成中文,

底层是架构问题,很难搞.上层库好一点点.

现在重新搞基于 OpenGL/ES 的底层, wayland 什么的, 顺利的话应该能够和 Win/Mac 匹敌了.

总的来说, linux 的图形, 目前就是渣, 但是希望在前!
一只囧蟹
一只囧蟹
太复杂了
sevk
sevk
不知道Linux的接口咋样
大合集
大合集
希望win9的x++/cx能释放你们
Raymin
Raymin
M$ 老是把接口搞的很复杂,
许多程序员的青春都被微软的接口搭进去了。
返回顶部
顶部