65.9K
CodeProject 正在变化。 阅读更多。
Home

叠加工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (21投票s)

2007年10月5日

5分钟阅读

viewsIcon

177002

downloadIcon

8902

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

Screenshot - render1.jpg Screenshot - render2.jpg
Screenshot - options1.jpg

引言

此工具允许您显示一个覆盖层(它会覆盖所有内容,包括DirectX和您的桌面(这意味着您无法直接截屏))。通过更改各种设置,您可以启用和禁用多个插件并更改其行为。

开发人员可以创建自己的插件来显示自己的内容。此工具中仅包含一些通用插件或我使用的插件。

注意:Nvidia硬件仅支持DirectDraw覆盖技术的片段 - Alpha在此将无法工作(尝试设置它将导致错误)。如果您有nvidia卡,我建议使用带有浅灰色背景色的小型覆盖层,或者尝试使用FakeAlpha选项,它会将下方的屏幕混合到覆盖层上。但请记住,这相当慢,并且需要高渲染率才能正常工作 - 当覆盖层放置在背景不经常更改的位置(例如,在GUI部分之上)时,此选项可能更有用。

用法

只需启动OverlayTools.exe,然后双击任务栏上的齿轮图标即可显示选项。在此处设置您自己的设置。

标准快捷键

  • F12 - 隐藏/显示
  • F11 - 切换覆盖页面

背景

我一直想在玩全屏应用程序时看到其他人用ICQ和mIRC给我写的内容。听到消息声音但不知道写了什么很烦人。由于覆盖层是Windows/DirectX中一个相当困难且不受支持的部分,我尝试了几次创建有用的东西。

DirectDraw Overlays 总是与Nvidia硬件存在一些问题,因为Nvidia硬件只支持YUV Overlays,您无法使用GDI或任何其他方式在上面绘图。因此,我创建了自己的混合功能,用于将RGB位图(可在任何硬件上工作)转换为YUV曲面。

1. 覆盖库

1.1. 覆盖类

overlay库提供了显示overlay的基本功能。要使用它,只需创建一个Overlay类的实例,设置您的尺寸和位置,添加您的RenderDelegate,然后InitialiseUpdate()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)方法。它们由一个定时器为每个活动插件调用,因此每个插件都有机会完成其工作。对于渲染,应用了一个TranslateTransform()矩阵操作,以便插件可以从(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,它包含一些字体和带有阴影的绘制字符串)。

覆盖层的通用数据,如PositionSize,也存储在插件中:通用插件将所有重要数据存储在一个伪/空插件中(它只有一个空的onTick/onRender方法),该插件以与普通插件保存相同的方式保存。

2.3. 插件列表

以下是当前添加到程序中的插件

  1. 时间插件 - 显示时间
  2. CPU/内存插件 - 显示CPU使用率/可用内存
  3. 颜色测试插件 - 只显示一些不同的颜色
  4. 消息插件 - 监听UDP套接字并在收到消息时显示
  5. ATITool插件 - 读取ATI Tool日志文件并显示GPU温度

...以及一些不太值得提及的小插件。

创建自定义插件

如果您想创建自己的插件,只需将您的类添加到项目文件中,并在那里添加您的内容。这很简单。我没有添加动态编译/加载插件程序集,因为我认为没有这个必要,但您可以随意这样做。

© . All rights reserved.