TransparentSplash 控件
一个在应用程序窗体之前启动的非矩形/透明启动画面控件
引言
几年前,我写了一篇关于创建从 UserControl
派生的启动画面控件的文章 SplashScreen Control。一位读者问我,为什么他的透明图像只显示黑色背景。这是因为控件本身不是透明的,图像中的透明度显示了未绘制的背景。
我说我会看看这个要求,当时我猜想它可能涉及 BitBlt
,这是 Windows SDK 中用于进行像素块传输的函数。当时的思路是在绘制“透明”图像之前复制并绘制屏幕背景。在下面的“宿主”图像发生变化或移动之前,这是一个令人满意的解决方案。然后它会暴露为一个技巧。我放弃了这个想法,转而采用了此处提供的解决方案。
为了实现真正的透明度,我重写并使用了 OnPaintBackground
,并在 OnPaint
重写中,我只绘制所需的不透明像素。
不安全!
这个解决方案不会吸引 .NET 纯粹主义者,因为它通过使用 Interop 和 Unsafe 代码“偏离”了 .NET,这是通过指针直接代码访问内存。这是 C 和 C++ 程序员的生活方式。它还要求你找到一个合适的图像,该图像在所有条件下看起来都很好。这是因为你的图像边缘会与背景形成对比,而没有抖动、阴影或其他技术来平滑结果。但是,如果你不介意这一点,并且知道你心中所想,这就是我发现你可以做到这一点的方式,我希望它对你有所帮助。
你的实现需要什么
你需要在你的项目中提供一个图像资源,该资源被一种颜色屏蔽,代表透明或未绘制的像素。例如,请参阅演示项目,该项目使用了一个精心制作的球体,实际上是从亚美尼亚国旗派生而来的,这仅仅是因为我认为它看起来不错并且具有形状优美且平滑的边缘。
理解代码
要了解此控件背后的基本原理,请参阅我在 SplashScreen Control 中介绍的 SplashScreen
控件。
TransparentSplash
是不同的,因为它逐像素地绘制图像,使其真正非矩形。至少在此时使用 GDI+ 来做到这一点是不可行的,因为它太慢了。
示例应用程序提供的图像是 256 x 256 像素。要处理此图像,需要 65536 个 Get
操作,然后需要 Set
操作来放置不透明像素。我们需要使用本机方法来执行这种非常密集的操作,因为它们允许我们锁定一些包含位图像素的内存,然后使用指针方法进行获取。这比托管 GDI+ 代码可以实现的要快得多。
在调查此操作时,我在 Visual C# Kicks 找到了一个非常好的类,名为 FastBitmap
,它为我们提供了所有经过良好测试的代码中所需的 Unsafe
方法。感谢他们,我将代码包含在示例项目中并使用它。请记住,包含不安全代码的项目需要将其属性页标记为允许不安全代码。
正如你将在以下从 Onpaint
中提取的代码中看到的那样,FastBitmap
用于通过其 LockImage
方法锁定内存中的图像。然后我们逐列、逐行地循环遍历像素,并针对掩码颜色测试每个像素。如果它与掩码匹配,则忽略它;如果它不匹配,则通过 SetPixel
(Windows SDK 方法,由 interop 导入提供)进行绘制。有关此方法和其他使用的导入,请参阅代码中的“Interop 定义”区域。
// TransparentSplash.cs
#region image painting
Color colorMatch = Color.FromArgb(TransparencyColor.R, TransparencyColor.G, TransparencyColor.B);
IntPtr hdc = g.GetHdc();
Point point = new Point();
Color pixelColor = new Color();
Bitmap bmp = (Bitmap)BackgroundImage;
BitmapProcessing.FastBitmap fastBitmap = new BitmapProcessing.FastBitmap(bmp);
fastBitmap.LockImage();
for (point.Y = 0; point.Y < BackgroundImage.Height; point.Y++)
{
for (point.X = 0; point.X < BackgroundImage.Width; point.X++)
{
if ((pixelColor = fastBitmap.GetPixel(point.X, point.Y)) != colorMatch)
{
SetPixel(hdc, point.X, point.Y, ColorToRGB(pixelColor));
}
}
}
fastBitmap.UnlockImage();
g.ReleaseHdc();
#endregion
这就是前台绘画的完成,但是我们是如何绘制背景的?这里有更多的证据,如果你需要的话,那就是少即是多
// TransparentSplash.cs
protected override void OnPaintBackground(PaintEventArgs pevent)
{
//must override and consume
}
这足以唤起 GDI Voodoo 来达到所需的结果。我同情任何没有立即发现这一点很直观的人。你只需要记住,此控件是桌面窗口的子控件,如果它不绘制自己的背景,并且不要求基类绘制背景,那么背景将是背景。使用演示应用程序,你可以调出控件并测试移动下面的窗口。你应该看到背景会立即刷新。
分享你的技巧
如果你发现此技术有用,并在你的实现中发现任何其他有用的技巧或技术,请考虑在此处分享它们 - 这将非常感谢。
历史
- 版本 1.0.0 发布日期:2010 年 7 月 5 日