一个可以翻转的面板控件






3.84/5 (9投票s)
本文展示了如何仅使用 GDI+ 图像操作来创建 3D 风格效果。

引言
我前段时间买了一台 MacBook Pro,发现预装软件中有很多有趣的视觉效果。其中一个我非常喜欢的是 Dashboard 应用程序中的效果,你可以点击窗口的某个区域,它会翻转过来显示该小程序的可用设置。在我正在开发的一个应用程序中,我认为有一个面板控件,我可以往里面添加一个控件,然后让它翻转过来显示我添加到背面的控件,这样的效果会很好。
背景
我实现这个想法的第一个方法是使用 WPF,因为它内置了 3D 功能。然而,我找到的关于在 3D 曲面上放置标准控件的示例代码并没有给我带来我想要的图像质量。因此,我决定使用 GDI+ 来自己实现。
如何使用代码
代码在你的应用程序中非常容易使用。这是我在示例应用程序中用来设置 FlipPanel 的代码:
// Add a TextBox to the front
TextBox t1 = new TextBox();
t1.Multiline = true;
t1.Size = new Size(201, 201);
t1.Text = "this is the front";
flipPanel1.Front = t1;
// Add a TextBox to the back
TextBox t2 = new TextBox();
t2.Multiline = true;
t2.Size = new Size(201, 201);
t2.Text = "this is the back";
flipPanel1.Back = t2;
// Set the animation speed
flipPanel1.TimerInterval = 5;
然后,你只需调用 flipPanel1.Flip()
来启动翻转动作。
工作原理
代码使用的基本方法如下:
- 将前后控件绘制到单独的位图中
- 计算正面图像每条垂直线的阴影(更新!这并没有真正达到我想要的效果!)
- 将正面图像绘制到屏幕上,使用重载的
Graphics.DrawImage()
方法,该方法可以对图像进行倾斜。我使用此方法自动计算简单的透视效果。
让我们来理解透视
感谢 **mcstarSatx** 的建议,我意识到真正给人 3D 感觉的是透视!而不是阴影!我曾因为在学校研究过形状から阴影算法而纠结于阴影。无论如何,我添加了一些代码来实现简单的透视效果,效果好了很多。我保留了使用阴影的选项,但它对视觉效果的帮助不大,反而会拖慢速度……
所以,让我们来弄清楚透视是如何工作的。如果你拿一张纸,把它垂直地放在你面前,你面前的四个角看起来都离你一样远。现在,把纸与你脸部呈 45 度角。那四个角在哪里?离你远的那些看起来就离你更远!它们看起来并没有暗很多!这就是为什么我这个蠢货认为阴影是创造 3D 效果最重要的因素。
创建透视的简单方法
透视变换计算起来并不容易,尤其是在只能使用 GDI+ 的限制下。所以,为了快速实现功能,因为我还有其他事情要做,我决定只使用一个简单的剪切操作,这个操作甚至可以通过 Graphics.DrawImage()
免费获得。首先,除了表示左角和右角 x 值的状态变量之外,我还跟踪一个表示控件顶部 y 值的变量。然后,在动画的每个时间步长中,我可以简单地增加或减少该值来创建图像剪切。
PointF[] destPoints = { new PointF(m_X1, 0),
new PointF(m_X2, m_Y1),
new PointF(m_X1, this.Height) };
// m_Y1 is the variable that determines the perspective
e.Graphics.DrawImage(m_PageA, destPoints);
秘密公式
我在代码中保留了使用阴影的选项,但它用处不大。我将保留文章的这部分,以防有人感兴趣。
编写这个控件最困难的部分是让动画看起来尽可能逼真。在我最初的方法中,我没有使用任何阴影,只是通过动画化图像的倾斜来近似 3D 旋转。效果还可以,但真的不令人信服。因此,我决定做一些三角学计算,弄清楚在图像旋转时如何进行阴影处理。
正如你从图中看到的,这个公式并不复杂。从图上看,y 是我用于阴影的因子,它根据 x 和 theta 计算得出:y = x * Math.Tan(theta)。
那么,在计算出阴影值之后,我如何进行阴影处理呢?这很简单。首先要意识到的是,我没有进行任何复杂的颜色处理,而是直接将 y 用作 alpha 值。这样,我就不必担心阴影层下面的内容了。由于我沿着垂直(y)轴翻转图像,所以我知道同一垂直轴上每个水平点的阴影值都将相同,因此我使用 Graphics.DrawLine()
来绘制一条带有我计算出的 alpha 值的线条。我在代码中添加了很多注释,所以如果你阅读代码,应该就能理解发生了什么。