【发布时间】:2013-11-02 05:26:29
【问题描述】:
我目前正在使用 C#、arduino 和 Ikea Dioder 为我的电脑显示器创建流光溢彩。目前硬件部分运行完美;但是,我在检测屏幕部分的平均颜色时遇到了问题。
我使用的实现有两个问题:
- 性能 - 这两种算法都会在屏幕上添加一些明显的卡顿。没什么好看的,但是看视频的时候很烦。
-
不支持全屏游戏 - 当游戏处于全屏模式时,这两种方法都只会返回白色。
public class DirectxColorProvider : IColorProvider { private static Device d; private static Collection<long> colorPoints; public DirectxColorProvider() { PresentParameters present_params = new PresentParameters(); if (d == null) { d = new Device(new Direct3D(), 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.SoftwareVertexProcessing, present_params); } if (colorPoints == null) { colorPoints = GetColorPoints(); } } public byte[] GetColors() { var color = new byte[4]; using (var screen = this.CaptureScreen()) { DataRectangle dr = screen.LockRectangle(LockFlags.None); using (var gs = dr.Data) { color = avcs(gs, colorPoints); } } return color; } private Surface CaptureScreen() { Surface s = Surface.CreateOffscreenPlain(d, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, Format.A8R8G8B8, Pool.Scratch); d.GetFrontBufferData(0, s); return s; } private static byte[] avcs(DataStream gs, Collection<long> positions) { byte[] bu = new byte[4]; int r = 0; int g = 0; int b = 0; int i = 0; foreach (long pos in positions) { gs.Position = pos; gs.Read(bu, 0, 4); r += bu[2]; g += bu[1]; b += bu[0]; i++; } byte[] result = new byte[3]; result[0] = (byte)(r / i); result[1] = (byte)(g / i); result[2] = (byte)(b / i); return result; } private Collection<long> GetColorPoints() { const long offset = 20; const long Bpp = 4; var box = GetBox(); var colorPoints = new Collection<long>(); for (var x = box.X; x < (box.X + box.Length); x += offset) { for (var y = box.Y; y < (box.Y + box.Height); y += offset) { long pos = (y * Screen.PrimaryScreen.Bounds.Width + x) * Bpp; colorPoints.Add(pos); } } return colorPoints; } private ScreenBox GetBox() { var box = new ScreenBox(); int m = 8; box.X = (Screen.PrimaryScreen.Bounds.Width - m) / 3; box.Y = (Screen.PrimaryScreen.Bounds.Height - m) / 3; box.Length = box.X * 2; box.Height = box.Y * 2; return box; } private class ScreenBox { public long X { get; set; } public long Y { get; set; } public long Length { get; set; } public long Height { get; set; } } }
您可以找到用于directX 实现的文件here。
public class GDIColorProvider : Form, IColorProvider
{
private static Rectangle box;
private readonly IColorHelper _colorHelper;
public GDIColorProvider()
{
_colorHelper = new ColorHelper();
box = _colorHelper.GetCenterBox();
}
public byte[] GetColors()
{
var colors = new byte[3];
IntPtr hDesk = GetDesktopWindow();
IntPtr hSrce = GetDC(IntPtr.Zero);
IntPtr hDest = CreateCompatibleDC(hSrce);
IntPtr hBmp = CreateCompatibleBitmap(hSrce, box.Width, box.Height);
IntPtr hOldBmp = SelectObject(hDest, hBmp);
bool b = BitBlt(hDest, box.X, box.Y, (box.Width - box.X), (box.Height - box.Y), hSrce, 0, 0, CopyPixelOperation.SourceCopy);
using(var bmp = Bitmap.FromHbitmap(hBmp))
{
colors = _colorHelper.AverageColors(bmp);
}
SelectObject(hDest, hOldBmp);
DeleteObject(hBmp);
DeleteDC(hDest);
ReleaseDC(hDesk, hSrce);
return colors;
}
// P/Invoke declarations
[DllImport("gdi32.dll")]
static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int
wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteDC(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr DeleteObject(IntPtr hDc);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
private static extern IntPtr GetWindowDC(IntPtr ptr);
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr ptr);
}
您可以找到用于 GDI 实施的文件 Here。
可以在Here找到完整的代码库。
【问题讨论】:
-
对于口吃,你有没有剖析看看什么需要这么长时间?通过读取每 1000 个像素(仍然是来自 1080p 屏幕的约 2,000 个点的样本;您可以选择更大的值)并且仅每 10 帧执行一次,您可能会看到更快的速度。
-
GetFrontBufferData 的文档说“该方法在设计上很慢,因此不应在性能关键路径中使用。”。你试过
GetBackBuffer吗? -
不是得到一个大盒子,而是需要多少时间才能得到许多(例如 200)个带有
BitBlt的小(例如 1 像素)盒子? -
实际上,如果您想尝试这种方法,使用
GetPixel可能会更好。 -
对于游戏或某些视频播放器等全屏应用程序,您将无法轻松获得屏幕颜色。它们直接绘制到屏幕上,并且它们并不总是公开从缓冲区读取的能力。即使他们这样做,您也必须明确编写特定于 DirectX/OpenGL/等的代码。
标签: c# directx screen-scraping gdi+ directx-11