Repository navigation
Expand file tree
/
Copy pathScreenSampler.cs
More file actions
94 lines (77 loc) · 2.93 KB
/
Copy pathScreenSampler.cs
File metadata and controls
94 lines (77 loc) · 2.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace V2XAmbilight;
/// <summary>
/// Captures the bottom strip of a screen and returns 7 averaged RGB color zones.
/// Uses DXGI Desktop Duplication (works with fullscreen-exclusive games) with
/// automatic fallback to GDI BitBlt when DXGI is unavailable.
/// </summary>
public sealed class ScreenSampler : IDisposable
{
readonly DxgiCapture _dxgi = new();
// GDI fallback state
Bitmap? _buffer;
int _lastWidth, _lastHeight;
/// <summary>
/// Returns 21 bytes: 7 × [R, G, B], each averaged from one horizontal zone
/// of the bottom <paramref name="stripPercent"/>% of <paramref name="screen"/>.
/// </summary>
public byte[] Sample(Screen screen, int stripPercent)
=> GdiSample(screen, stripPercent);
byte[] GdiSample(Screen screen, int stripPercent)
{
var bounds = screen.Bounds;
int stripH = Math.Max(1, bounds.Height * stripPercent / 100);
int captureY = bounds.Bottom - stripH;
if (_buffer == null || _lastWidth != bounds.Width || _lastHeight != stripH)
{
_buffer?.Dispose();
_buffer = new Bitmap(bounds.Width, stripH, PixelFormat.Format32bppRgb);
_lastWidth = bounds.Width;
_lastHeight = stripH;
}
using var g = Graphics.FromImage(_buffer);
g.CopyFromScreen(bounds.Left, captureY, 0, 0,
new Size(bounds.Width, stripH), CopyPixelOperation.SourceCopy);
return ComputeZoneColors(_buffer, zoneCount: 7);
}
static byte[] ComputeZoneColors(Bitmap bmp, int zoneCount)
{
var area = new Rectangle(0, 0, bmp.Width, bmp.Height);
var bd = bmp.LockBits(area, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
int stride = bd.Stride, w = bmp.Width, h = bmp.Height;
byte[] pixels = new byte[stride * h];
Marshal.Copy(bd.Scan0, pixels, 0, pixels.Length);
bmp.UnlockBits(bd);
byte[] result = new byte[zoneCount * 3];
int zoneW = w / zoneCount;
for (int z = 0; z < zoneCount; z++)
{
int startX = z * zoneW;
int endX = z == zoneCount - 1 ? w : startX + zoneW;
long r = 0, g = 0, b = 0, count = 0;
for (int y = 0; y < h; y += 4)
for (int x = startX; x < endX; x += 4)
{
int off = y * stride + x * 4; // Format32bppRgb: B G R _
b += pixels[off];
g += pixels[off + 1];
r += pixels[off + 2];
count++;
}
if (count > 0)
{
result[z * 3] = (byte)(r / count);
result[z * 3 + 1] = (byte)(g / count);
result[z * 3 + 2] = (byte)(b / count);
}
}
return result;
}
public void Dispose()
{
_dxgi.Dispose();
_buffer?.Dispose();
}
}