叠加工具






4.92/5 (21投票s)
2007年10月5日
5分钟阅读

177002

8902
..使用DirectDraw - 一个可自定义覆盖的显示工具



引言
此工具允许您显示一个覆盖层(它会覆盖所有内容,包括DirectX和您的桌面(这意味着您无法直接截屏))。通过更改各种设置,您可以启用和禁用多个插件并更改其行为。
开发人员可以创建自己的插件来显示自己的内容。此工具中仅包含一些通用插件或我使用的插件。
注意:Nvidia硬件仅支持DirectDraw覆盖技术的片段 - Alpha在此将无法工作(尝试设置它将导致错误)。如果您有nvidia卡,我建议使用带有浅灰色背景色的小型覆盖层,或者尝试使用FakeAlpha选项,它会将下方的屏幕混合到覆盖层上。但请记住,这相当慢,并且需要高渲染率才能正常工作 - 当覆盖层放置在背景不经常更改的位置(例如,在GUI部分之上)时,此选项可能更有用。
用法
只需启动OverlayTools.exe,然后双击任务栏上的齿轮图标即可显示选项。在此处设置您自己的设置。
标准快捷键
- F12 - 隐藏/显示
- F11 - 切换覆盖页面
背景
我一直想在玩全屏应用程序时看到其他人用ICQ和mIRC给我写的内容。听到消息声音但不知道写了什么很烦人。由于覆盖层是Windows/DirectX中一个相当困难且不受支持的部分,我尝试了几次创建有用的东西。
DirectDraw Overlay
s 总是与Nvidia硬件存在一些问题,因为Nvidia硬件只支持YUV Overlay
s,您无法使用GDI或任何其他方式在上面绘图。因此,我创建了自己的混合功能,用于将RGB位图(可在任何硬件上工作)转换为YUV曲面。
1. 覆盖库
1.1. 覆盖类
overlay
库提供了显示overlay
的基本功能。要使用它,只需创建一个Overlay
类的实例,设置您的尺寸和位置,添加您的RenderDelegate
,然后Initialise
并Update()
该overlay
。
这里最大的问题是找到一个能在所有硬件上工作的像素格式曲面。ATI卡大多支持RGB,也支持YUV;Nvidia硬件只支持YUV。但由于您无法在YUV曲面上绘图,所以它几乎没有用。所以我只是尝试找出哪种格式能在卡上工作,然后使用它。
do
{
try
{
desc.PixelFormatStructure = GetPixelFormat(m_PixelFormat);
m_Buffer = new Surface(desc, m_Device);
}
catch (DirectXException)
{
m_PixelFormat++;
}
}
while (m_Buffer == null
&& Enum.IsDefined(typeof(ePixelFormat), m_PixelFormat));
为了渲染,我使用了一个简单的位图作为渲染目标,因为它可以在任何地方工作,然后将其混合到特定的曲面上。
if (m_Renderer != null)
{
//Draw on the backbuffer
Graphics g = Graphics.FromImage(m_RenderTarget);
g.Clear(Color.Black);
m_Renderer(g);
Blit(m_RenderTarget, m_BackBuffer);
}
混合功能只是调用适用于该格式的子混合功能。
public void Blit(Bitmap src, Surface dest)
{
switch (m_PixelFormat)
{
case ePixelFormat.RGB32:
BlitRGB32(src, dest);
break;
case ePixelFormat.YUY2:
BlitYUY2(src, dest);
break;
}
}
将内容从源混合到目标的通用方法是锁定两个数据,然后逐像素(在YUY2情况下为每两个像素)进行处理。
BitmapData ds = src.LockBits(new Rectangle(0, 0, src.Width, src.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
LockedData dd = dest.Lock(LockFlags.WriteOnly);
int ps = ds.Stride - (ds.Width*3); //the space left at the end of the Y-line
byte[] pd = new byte[dd.Pitch - (dd.Width*2)]; //dest space
byte* ptr = (byte*) ds.Scan0;
for (int h = 0; h < ds.Height; h++)
{
for (int w = 0; w < ds.Width; w += 2)
{
...
ptr += 3;
...
}
//Skip the stride
ptr += ps;
pd.Write(pd, 0, pd.Length);
}
...
对于RGB24 -> RGB32
的混合,我只是锁定位图并复制值,留空Alpha(因为它反正也不可用,但24位曲面无法创建)。
对于RGB24 -> YUY2
的混合,我在fourcc.org上查找了该格式并尝试正确转换。但由于这些公式并不完全正确,我不得不自己修改它们 - 结果颜色可能不是100%相同。
1.2. KeyboardHook 类
对于快捷键,我使用了Win32的SetWindowHookEx
方法来挂钩WH_KEYBOARD_LL
事件。有关更多信息,请参阅Windows平台SDK。
...
hk = SetWindowsHookEx(13, hookprc, Marshal.GetHINSTANCE(GetType().Module), 0);
...
public IntPtr OnHook(int code, int wParam, int lParam)
{
IntPtr res = CallNextHookEx(hook, code, wParam, lParam);
if (code >= 0 && m_Event != null && wParam == WM_KEYDOWN)
{
KeyStruct s = (KeyStruct) Marshal.PtrToStructure(
new IntPtr(lParam), typeof (KeyStruct));
Keys key = (Keys) s.vkCode;
m_Event(key);
}
return res;
}
...
2. 覆盖工具
为了显示自定义覆盖层,我编写了这个应用程序。它负责插件管理。每个插件都可以将自己的部分渲染到覆盖层上,最终得到您完全自定义的覆盖层。
2.1. 加载插件
插件是通过反射加载的。代码被搜索,然后为IPlugin
的每个继承者创建一个实例。
foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type t in a.GetTypes())
{
if (t.IsAbstract || t.IsInterface
|| t.GetInterface(typeof (IPlugin).FullName) == null)
{
continue;
}
//Get the plugin
IPlugin p = (IPlugin) a.CreateInstance(t.FullName);
...
}
}
每个插件可能包含用于保存自定义值(例如,颜色、格式等)的数据属性。这些值通过[Save]
属性进行标记,该属性在创建插件实例后进行处理。如果一个属性拥有该属性,则从.ini文件(使用INIStreamer
,这是我最老的C#类之一)加载其内容,然后将字符串转换为适当的类型。
foreach (PropertyInfo i in t.GetProperties())
{
if (i.GetCustomAttributes(typeof (SaveAttribute), true).Length == 0)
{
continue;
}
if (!tpc.Items.ContainsKey(i.Name))
{
continue; //no value for this property saved
}
object val;
if (i.PropertyType.IsEnum)
{ //Enums cant be converted
val = Enum.Parse(i.PropertyType, tpc.Items[i.Name].ToString());
}
else
{
val = Convert.ChangeType(tpc.Items[i.Name], i.PropertyType);
}
i.SetValue(p, val, null);
}
将插件数据保存到.ini文件时,也进行了几乎相同的操作。
加载插件后,通过将PluginPanel
控件(实际上只是一个带有propertygrid
的控件)添加到选项卡控件中,为每个插件生成一个选项卡。
private static void AddToTab(IPlugin p)
{
TabPage page = new TabPage();
page.Text = p.Name;
page.Name = "tab_plugin_" + p.BaseName;
PluginPanel pp = new PluginPanel(p);
pp.Dock = DockStyle.Fill;
page.Controls.Add(pp);
MainForm.tabs.TabPages.Add(page);
}
2.2. 管理插件
所有插件都有OnTick()
和OnRender(Graphics g)
方法。它们由一个定时器为每个活动插件调用,因此每个插件都有机会完成其工作。对于渲染,应用了一个T
ranslateTransform()
矩阵操作,以便插件可以从(0|0)开始渲染。
public static void OnRender(Graphics g)
{
g.Clear(General.BackgroundColor);
if (General.FakeAlpha)
{
g.CopyFromScreen(Overlay.Boundings.Location, Point.Empty,
Overlay.Boundings.Size, CopyPixelOperation.SourceCopy);
}
foreach (IPlugin p in Plugins.Values)
{
if (p.Active && (p.Page == General.Page || p.Page == -1))
{
g.ResetTransform();
g.TranslateTransform(p.X, p.Y, MatrixOrder.Append);
p.Render(g);
}
}
}
插件然后只需绘制它们想要的内容(也许它们会使用Draw helper class
,它包含一些字体和带有阴影的绘制字符串)。
覆盖层的通用数据,如Position
和Size
,也存储在插件中:通用插件将所有重要数据存储在一个伪/空插件中(它只有一个空的onTick
/onRender
方法),该插件以与普通插件保存相同的方式保存。
2.3. 插件列表
以下是当前添加到程序中的插件
- 时间插件 - 显示时间
- CPU/内存插件 - 显示CPU使用率/可用内存
- 颜色测试插件 - 只显示一些不同的颜色
- 消息插件 - 监听UDP套接字并在收到消息时显示
- ATITool插件 - 读取ATI Tool日志文件并显示GPU温度
...以及一些不太值得提及的小插件。
创建自定义插件
如果您想创建自己的插件,只需将您的类添加到项目文件中,并在那里添加您的内容。这很简单。我没有添加动态编译/加载插件程序集,因为我认为没有这个必要,但您可以随意这样做。