Gra*_*rne 6 c# directx gdi+ screen-scraping directx-11
我目前正在使用 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; }
}
}
Run Code Online (Sandbox Code Playgroud)您可以在此处找到 directX 实现的文件。
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);
}
Run Code Online (Sandbox Code Playgroud)
您可以在此处找到 GDI 实现的文件。
完整的代码库可以在这里找到。
屏幕捕获性能缓慢的问题很可能是由于BitBlt()在源和目标的像素格式不匹配时进行像素转换引起的。来自文档:
如果源和目标设备上下文的颜色格式不匹配,BitBlt 函数将转换源颜色格式以匹配目标格式。
这就是导致我的代码性能缓慢的原因,尤其是在更高分辨率的情况下。
默认的像素格式似乎是PixelFormat.Format32bppArgb,所以这就是您应该用于缓冲区的格式:
var screen = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
var gfx = Graphics.FromImage(screen);
gfx.CopyFromScreen(bounds.Location, new Point(0, 0), bounds.Size);
Run Code Online (Sandbox Code Playgroud)
性能低下的下一个根源是Bitmap.GetPixel()进行边界检查。分析每个像素时切勿使用它。相反,锁定位图数据并获取指向它的指针:
public unsafe Color GetAverageColor(Bitmap image, int sampleStep = 1) {
var data = image.LockBits(
new Rectangle(Point.Empty, Image.Size),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
var row = (int*)data.Scan0.ToPointer();
var (sumR, sumG, sumB) = (0L, 0L, 0L);
var stride = data.Stride / sizeof(int) * sampleStep;
for (var y = 0; y < data.Height; y += sampleStep) {
for (var x = 0; x < data.Width; x += sampleStep) {
var argb = row[x];
sumR += (argb & 0x00FF0000) >> 16;
sumG += (argb & 0x0000FF00) >> 8;
sumB += argb & 0x000000FF;
}
row += stride;
}
image.UnlockBits(data);
var numSamples = data.Width / sampleStep * data.Height / sampleStep;
var avgR = sumR / numSamples;
var avgG = sumG / numSamples;
var avgB = sumB / numSamples;
return Color.FromArgb((int)avgR, (int)avgG, (int)avgB);
}
Run Code Online (Sandbox Code Playgroud)
这将使您的时间远低于 10 毫秒,具体取决于屏幕尺寸。如果仍然太慢,您可以增加sampleStep的参数GetAverageColor()。
我最近做了同样的事情,并想出了一些效果出奇好的东西。
诀窍是创建一个大小为 1x1 像素的附加位图,并在其图形上下文上设置良好的插值模式(双线性或双三次,但不是最近邻)。
然后,利用插值将捕获的位图绘制到该 1x1 位图中,并检索该像素以获得平均颜色。
我以大约 30 fps 的速度执行此操作。当屏幕显示 GPU 渲染时(例如,在 Chrome 中启用硬件加速的情况下全屏观看 YouTube),不会出现明显的卡顿或其他任何情况。事实上,应用程序的 CPU 利用率远低于 10%。然而,如果我关闭 Chrome 的硬件加速,那么如果你观察得足够近的话,肯定会出现一些轻微的卡顿现象。
以下是代码的相关部分:
using var screen = new Bitmap(width, height);
using var screenGfx = Graphics.FromImage(screen);
using var avg = new Bitmap(1, 1);
using var avgGfx = Graphics.FromImage(avg);
avgGfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
while (true) {
screenGfx.CopyFromScreen(left, top, 0, 0, screen.Size);
avgGfx.DrawImage(screen, 0, 0, avg.Width, avg.Height);
var color = avg.GetPixel(0, 0);
var bright = (int)Math.Round(Math.Clamp(color.GetBrightness() * 100, 1, 100));
// set color and brightness on your device
// wait 1000/fps milliseconds
}
Run Code Online (Sandbox Code Playgroud)
请注意,这适用于 GPU 渲染,因为System.Drawing.Common现在使用 GDI+。但是,当内容受 DRM 保护时,它不起作用。例如,它不适用于 Netflix :(
我在GitHub上发布了代码。尽管我由于 Netflix 的 DRM 保护而放弃了该项目,但它可能对其他人有帮助。