gpt4 book ai didi

C# 获取屏幕平均颜色的最快方法

转载 作者:行者123 更新时间:2023-12-03 14:51:21 29 4
gpt4 key购买 nike

我目前正在使用 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 .

    最佳答案

    更新答案
    截屏性能慢的问题很可能是由BitBlt()引起的当源和目标的像素格式不匹配时进行像素转换。来自 docs :

    If the color formats of the source and destination device contexts do not match, the BitBlt function converts the source color format to match the destination format.


    这就是导致我的代码性能下降的原因,尤其是在更高的分辨率下。
    默认像素格式似乎是 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);
    下一个性能缓慢的来源是 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);
    }
    这应该让你远低于 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
    }
    请注意,这适用于 GPU 渲染,因为 System.Drawing.Common现在使用 GDI+。但是,当内容受 DRM 保护时,它不起作用。因此,它不适用于 Netflix,例如 :(
    我在 GitHub 上发布了代码.即使由于 Netflix 的 DRM 保护我放弃了该项目,它也可能对其他人有所帮助。

    关于C# 获取屏幕平均颜色的最快方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19550114/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com