这是我关于Direct2D的第三篇介绍了,今天主要讲下其无可比拟的互操作性。为了避免繁缛末节的讲述,我们从一个实例入手:层级窗口。相对于windows的其它众多功能。层级窗口并未做相应的更新,因此使用现代图像处理技术时要特别注意,以有效发挥其功效。
本文假定读者具有一定的Direct2D 开发基础。如若不然,请参考本人六月和九月发表的Direct2D开发和绘图的文章 ( msdn.microsoft.com/magazine/dd861344 ) msdn.microsoft.com/magazine/ee413543 )
Verify(SetLayeredWindowAttributes( windowHandle, 0, // no color key 180, // alpha value LWA_ALPHA));
上述代码假定创建窗体可以通过在SetWindowLong 方法调用中设置WS_EX_LAYERED来实现其可扩展性。Figure 1为示例。这样做的好处显而易见,你无须做出任何的更改而由DWM桌面窗体管理器来自动绘制窗体。如果不这样做,所有实现都需要代码实现。如果你现在就在使用Direct2D 或其他什么最新的解析技术,也不会有上述问题!
Figure 1 Window with Alpha Value
好的,那现在该怎么继续呢?从基础层面来说,都很直观。首先,完善结构体UPDATELAYEREDWINDOWINFO在其当中完成层次窗体的位置大小以及GDI设置文本内容以完成窗体的展示设置-问题随之而来。DC只是GDI中的概念,对于速度已大幅提升的硬件和DirectX技术来说还是很滞后。等会再详细讨论这个问题。
UPDATELAYEREDWINDOWINFO结构体在WindowsSDK中的文档说明并不完整,想象了它的易用性更别说需要通过一堆指针来定位当前位置。共有五个结构体来完成定位工作。定位要从DC拷贝的bitmap的资源位置。 定位当窗体更新后的新位置。需要拷贝的bitmap 文件大小以及操作窗体的大小:
POINT sourcePosition = {}; POINT windowPosition = {}; SIZE size = { 600, 400 };
接下来是BLENDFUNCTION结构体来定义层次窗口如何与桌面互溶。这块虽有很多不同实现的结构体,但通常都是以下模式:
BLENDFUNCTION blend = {}; blend.SourceConstantAlpha = 255; blend.AlphaFormat = AC_SRC_ALPHA;
AC_SRC_ALPHA常量仅仅表明源位图文件可以支持alpha值设定。
通过设置Alpha值可以完成和SetLayeredWindowAttributes 方法同样的设置窗体透明度的功能。当设置为255时,层次窗体将使用基于像素的alpha 数值,但是还是能够更改到0从而变成全透明窗体,进而实现窗体渐进显示而无需重新绘制窗体。至于为什么将这个结构体命名为BLENDFUNCTION也显而易见了:结构体的结果就反映了可透明化窗体的透明度。
最终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类将其包装一下.
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); } };
先来看下如何使用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
这里面有很多细节,但和现在的主题没什么关系。这个API的功能历史悠久,但请注意我设置的那个bitmap的高度值是负数。BITMAPINFOHEADER结构体展示的是自顶向下还是自底向上的位图。高度为正数则是自底向上,负数为自顶向下。自顶向下位图的原点在左上角,自底向上的原点在左下角。
虽不是成文的规定,但在本例中我使用自顶向下的位图模式,这是主流图像处理模式,因此会有较好的互操作性。这样做也会得到正数模式的步展值。计算方法如下:
UINT stride = (width * 32 + 31) / 32 * 4;
这会你完全可以通过bits指针来绘制位图了。除非脑子有病,否者总得有一些方法调用去完成绘制功能。但目前GDI提供的方法中并不支持alpha计算模式,因此出现了GDI+.
尽管可以直接给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; } };
评论删除后,数据将无法恢复
评论(10)
引用来自“sevk”的评论
引用来自“哆啦比猫”的评论
引用来自“sevk”的评论
不知道Linux的接口咋样
引用来自“哆啦比猫”的评论
引用来自“sevk”的评论
不知道Linux的接口咋样
引用来自“sevk”的评论
不知道Linux的接口咋样
引用来自“sevk”的评论
不知道Linux的接口咋样
2005 年 jon smirl 写过一篇 The state of Linux graphics, 基本就是各种互相不兼容的 API 在一个热气腾腾的大锅里熬.
我因为深有感触, 所以曾经动手把它翻译成中文,
底层是架构问题,很难搞.上层库好一点点.
现在重新搞基于 OpenGL/ES 的底层, wayland 什么的, 顺利的话应该能够和 Win/Mac 匹敌了.
总的来说, linux 的图形, 目前就是渣, 但是希望在前!
许多程序员的青春都被微软的接口搭进去了。