WEB开发网
开发学院软件开发VC 创建分层窗口,实现图像渐变 阅读

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

 2010-01-09 20:32:00 来源:WEB开发网   
核心提示:为了将一幅图像渐变为另一幅,你需要两幅图像,创建分层窗口,实现图像渐变(2),当用户打开一个新文档时,MFC 要做的第 一件事情是销毁旧的那个对象,你得用单位矩阵(对角线上为 1,其余都为 0),所以你考虑在 MFC 加载新图像前将旧图像保存在某个地方,因为渐变效 果概念上属于视图处理(绘制图像范畴)

为了将一幅图像渐变为另一幅,你需要两幅图像,当用户打开一个新文档时,MFC 要做的第 一件事情是销毁旧的那个对象。所以你考虑在 MFC 加载新图像前将旧图像保存在某个地方。因为渐变效 果概念上属于视图处理(绘制图像范畴),所以我把处理过程放在在视图(View)中。也就是说在视图 中保存旧图像。但视图如何知道何时要保存图像呢?你当然得告诉它。幸运的是,CDocument 具备一个 方法,你可以用它来随时通知视图发生了什么。这个方法就是 CDocument::UpdateAllViews:

// in Doc.cpp:
BOOL CPictureDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
  UpdateAllViews(NULL, PREOPENDOC, this);
  return m_pict.Load (lpszPathName);
}

PREOPENDOC 是我自己的枚举代码,在 doc.h 中定义。当你调用 UpdateAllViews 时,将自己的“提示代码”(一个32位整数)随一个指针传递到“提 示对象”,该对象可以是任何 CObject 派生的 MFC 类。这里我传的是文档本身。注意我是在加载 新图像之前调用 UpdateAllViews,而旧图像仍然有效。视图处理通知消息保存该图像:

void CPictureView::OnUpdate(CView* pSender,
LPARAM lHint, CObject* pHint)
{
   if (lHint==CPictureDoc::PREOPENDOC) {
    SaveDocImage((CPictureDoc*)pHint);
  }
}

相同的 OnUpdate 函数处理所有文档的通知消息,所以你得检查发送了哪个通 知消息。一般情况下,提示代码和提示对象背后的工作原理是文档以提示方式提供信息,告诉视图它需 要更新屏幕的哪一部份。对于 CPictureView 来说,如果提示代码是 PREOPENDOC,那么 CPictureView 则调用一个辅助函数 SaveDocImage 来保存当前图像。Figure 3 是 SaveDocImage 的代码,它创建一个 位图和内存设备上下文(DC),然后在内存设备上下文中呈现图像,在文档摧毁原来图像后有效地进行渐 变拷贝。

现在,当用户打开一个新文件,文档通知视图以及 OnUpdate 处理例程以位图形式保存 图像。渐变是怎么做出来的呢?它需要重复调用 AlphaBlend 从老图像渐变成新图像。最显而易见的方 法是设置一个定时器。假设你想用三秒来渐变。为了用 100 步来实现渐变,你可以将定时期设置成 3000/100=30毫秒。但问题是 AlphaBlend实际上花了大量的时间来处理渐变。而且,所花的时间依赖于 图像的大小。较大的图像渐变的时间较长。如果你使用定时器来做,最后得到的幻灯效果是较小的图像 更快,较大的图像更慢——可能不是你想要的结果。

保持渐变时间为常量的方法是固 定持续时间——假设为 3,000 毫秒——然后根据实际逝去的时间计算 alpha 值 ,假设第一次迭代发生在 t+20 毫秒。那么你可以用的 alpha 值为 20*255/3000 = 1 (取最近似的一 个整数)。然后根据当前时间计算的 alpha 值立即进行另一次渐变。如果逝去的时间超过一半,你最终 的 alpha 值是 .5。通过用实际逝去的时间计算 alpha 值,你可以保证渐变总是按时完成,但缺点是较 大的图像无法平滑地完成渐变,因为它们的迭代过程更少,在 AlphaBlend 中花的时间更多。

所 有这些实现难易程度不一。Figure 3 和 Figure 4 是详细代码。当 CPictureView::OnUpdate 获得 PREOPENDOC 通知时,保存旧图像之后,它将数据成员 m_iStartTime 置为当前时钟时间。时钟时间是 自该进程启动后的“时钟嘀嗒”数。每秒嘀嗒数为 CLOCKS_PER_SEC(通常为 1,000)。当 OnUpdate 返回时,控制传回到文档和 MFC,它调用视图的 OnInitialUpdate 函数,该函数调用 OnUpdate,它重画窗口。最后,Windows 向你的视图发送 MW_PAINT 消息,MFC 通过调用视图的虚拟 OnDraw 方法处理该消息。这是 MFC 的基本常识:在某个视图中,绘图在 OnDraw 进行,而不是  OnPaint 中。CPictureView 是这样绘制的:

void CPictureView::OnDraw(CDC* pDC)
{
  CPicture* ppic = // get current picture
  if (m_iStartTime) {
     // do blend
  } else {
    // render as normal
    ppic- >Render(pDC,rc);
  }
}

我省略了渐变的细节,主要突出 CPictureView 如何用 m_iStartTime 作为标志来确定是否渐变。以 下是实现渐变需要的基本步骤。

创建一个内存 DC;

在该内存 DC 中绘制位图;

计算渐变的 alpha 值;

在该内存 DC 中 AlphaBlend 位图;

将结果拷贝到屏幕(BitBlt);

在画面以外的内存 DC 中进行渐变然后拷贝到屏幕这一步是很重要的;否则用户将会看到一闪而过的 中间图像。因为 AlphaBlend 需要设备上下文,而不是 CPicture 对象,首先绘制新图像(所以我调用 CPicture::Render ),然后在其上渐变旧图像要方便一些。所以我用的 alpha 值与先从旧的图像开始 显示所用的 alpha 值相反转(1-alpha) ,换句话说,不是先从旧图像开始,然后在上面以越来越多的 效果渐变新图像。我是先从新图像开始,然后在上面以越来越少的效果渐变旧图像。很聪明,不是吗? 网格效果处理方法一样。以下是计算 AlpahBlend alpha 值的关键代码行:

int alpha = ((clock() - m_iStartTime) * 255) / BLEND_DURATION;
alpha = max(255-alpha,0);

渐变之后,如果计算的 alpha 值大于 0,那么就需要处理更多的渐变效果。所以 OnDraw 调用 Invalidate(FALSE) 在不擦除背景的情况下而重画窗口。Windows 发送另一个 WM_PAINT 消息 ——只是要等到当前消息处理完成。这样一来(使 WM_PAINT 为有效消息),没有阻塞。在渐 变期间,用户仍然能使用应用程序。你可以在渐变期间改变窗口大小来证明这一点。CPictureView 在新 的窗口尺寸下保持渐变。

如果算出的 alpha 值为 0,渐变完成。计时器停止。这时,OnDraw 调 用辅助函数 StopBlending,该函数删除旧图像并将 m_iStartTime 设置为 0,暗示 OnDraw 停止渐变。 现在当视图需要绘制时,OnDraw 通过调用 CPicture::Render 进行常规绘制,直接呈现新图像,不发生 渐变。

如果你使用活动模板库(ATL)CImage 类来保存图像(而不是用 CPicture,这是我很久 以前实现的一个类,当时 CImage 还未出现),你可以用 CImage::AlphaBlend,不过用它来进行渐变会 产生一些开销。

如果你使用微软的 .NET 框架,你可以用 Graphics.DrawImage 重载方法函数之 一来进行 alpha 渐变,该重载有一个 ImageAttributes 对象参数。ImageAttributes 中的一个方法是 SetColorMatrix。颜色矩阵为一个5x5 矩阵,定义红、绿、蓝颜色映射以及 alpha 加第五个 w 通道, 对角线上必须是 1,其它地方必须为 0(学过数学的的人都知道,第五通道被用于实现非线性转换)。 为了完成半透明渐变,你得用单位矩阵(对角线上为 1,其余都为 0),然后将 alpha 值 (ColorMatrix.Matrix33)置为 .5f 并用它绘制图像。

上一页  1 2 

Tags:创建 分层 窗口

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接