diff --git a/BlazorServer/Startup.cs b/BlazorServer/Startup.cs index bfff7fad0..934a14b6d 100644 --- a/BlazorServer/Startup.cs +++ b/BlazorServer/Startup.cs @@ -80,7 +80,7 @@ public void ConfigureServices(IServiceCollection services) } else { - services.AddCoreConfiguration(); + services.AddCoreConfiguration(log); } services.AddFrontend(); diff --git a/BlazorServer/appsettings.json b/BlazorServer/appsettings.json index b42fd9d5c..162d744a7 100644 --- a/BlazorServer/appsettings.json +++ b/BlazorServer/appsettings.json @@ -18,7 +18,7 @@ "Id": -1 }, "Reader": { - "Type": "GDI" // from Win7 'GDI' - from Win8 'DXGI' - from Win7 background 'GDIBlit' + "Type": "DXGI" // from Win7 'GDI' - from Win8 'DXGI' - from Win7 background 'GDIBlit' }, "Diagnostics": { "Enabled": false diff --git a/Core/AddonDataProvider/AddonDataProviderBitBlt.cs b/Core/AddonDataProvider/AddonDataProviderBitBlt.cs deleted file mode 100644 index a8feb2d54..000000000 --- a/Core/AddonDataProvider/AddonDataProviderBitBlt.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.Text; - -using Game; - -using WinAPI; - -using static WinAPI.NativeMethods; - -namespace Core; - -public sealed class AddonDataProviderBitBlt : IAddonDataProvider -{ - public int[] Data { get; private init; } - public StringBuilder TextBuilder { get; } = new(3); - - private readonly DataFrame[] frames; - - private readonly Rectangle rect; - - private readonly Bitmap bitmap; - private readonly Graphics graphics; - - private readonly IWowScreen wowScreen; - - private IntPtr hWnd = IntPtr.Zero; - private IntPtr windowDC = IntPtr.Zero; - - private readonly bool windowedMode; - private Point p; - - public AddonDataProviderBitBlt(IWowScreen wowScreen, DataFrame[] frames) - { - this.wowScreen = wowScreen; - this.frames = frames; - - Data = new int[frames.Length]; - - for (int i = 0; i < frames.Length; i++) - { - rect.Width = Math.Max(rect.Width, frames[i].X); - rect.Height = Math.Max(rect.Height, frames[i].Y); - } - rect.Width++; - rect.Height++; - - bitmap = new(rect.Width, rect.Height, AddonDataProviderConfig.PIXEL_FORMAT); - graphics = Graphics.FromImage(bitmap); - - wowScreen.GetRectangle(out Rectangle pRect); - p = pRect.Location; - windowedMode = IsWindowedMode(p); - } - - public void Dispose() - { - ReleaseDC(hWnd, windowDC); - - graphics.Dispose(); - bitmap.Dispose(); - } - - public void UpdateData() - { - if (windowedMode) - { - wowScreen.GetRectangle(out Rectangle pRect); - p = pRect.Location; - } - - if (hWnd != wowScreen.ProcessHwnd) - { - ReleaseDC(hWnd, windowDC); - - hWnd = wowScreen.ProcessHwnd; - windowDC = GetWindowDC(hWnd); - } - - IntPtr memoryDC = graphics.GetHdc(); - - BitBlt(memoryDC, - 0, 0, - rect.Width, rect.Height, - windowDC, 0, 0, - TernaryRasterOperations.SRCCOPY); - - graphics.ReleaseHdc(memoryDC); - - BitmapData bd = bitmap.LockBits(rect, ImageLockMode.ReadOnly, AddonDataProviderConfig.PIXEL_FORMAT); - - // TODO: problem the - // int nXSrc, int nYSrc - // 0 0 coordinates dosent point to the top left corner in the window - // instead there are (68,28) area offset ?! - //bitmap.Save("helpme.bmp"); - - IAddonDataProvider.InternalUpdate(bd, frames, Data); - - bitmap.UnlockBits(bd); - } -} diff --git a/Core/AddonDataProvider/AddonDataProviderConfig.cs b/Core/AddonDataProvider/AddonDataProviderConfig.cs deleted file mode 100644 index 6c288c8de..000000000 --- a/Core/AddonDataProvider/AddonDataProviderConfig.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Drawing.Imaging; - -namespace Core; - -internal static class AddonDataProviderConfig -{ - // B G R A - public static readonly byte[] fColor = { 0, 0, 0, 255 }; - public static readonly byte[] lColor = { 129, 132, 30, 255 }; - - public const PixelFormat PIXEL_FORMAT = PixelFormat.Format32bppPArgb; - public const int BYTES_PER_PIXEL = 4; -} diff --git a/Core/AddonDataProvider/AddonDataProviderGDI.cs b/Core/AddonDataProvider/AddonDataProviderGDI.cs deleted file mode 100644 index 9ccd8ec05..000000000 --- a/Core/AddonDataProvider/AddonDataProviderGDI.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Game; - -using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.Text; - -using WinAPI; - -namespace Core; - -public sealed class AddonDataProviderGDI : IAddonDataProvider -{ - public int[] Data { get; private init; } - public StringBuilder TextBuilder { get; } = new(3); - - private readonly DataFrame[] frames; - - private readonly Rectangle rect; - private readonly Bitmap bitmap; - private readonly Graphics graphics; - - private readonly IWowScreen wowScreen; - - private readonly bool windowedMode; - private Point p; - - public AddonDataProviderGDI(IWowScreen wowScreen, DataFrame[] frames) - { - this.wowScreen = wowScreen; - this.frames = frames; - - Data = new int[frames.Length]; - - for (int i = 0; i < frames.Length; i++) - { - rect.Width = Math.Max(rect.Width, frames[i].X); - rect.Height = Math.Max(rect.Height, frames[i].Y); - } - rect.Width++; - rect.Height++; - - bitmap = new(rect.Width, rect.Height, AddonDataProviderConfig.PIXEL_FORMAT); - graphics = Graphics.FromImage(bitmap); - - wowScreen.GetRectangle(out Rectangle pRect); - p = pRect.Location; - windowedMode = NativeMethods.IsWindowedMode(p); - } - - public void Dispose() - { - graphics.Dispose(); - bitmap.Dispose(); - } - - public void UpdateData() - { - if (windowedMode) - { - wowScreen.GetRectangle(out Rectangle pRect); - p = pRect.Location; - } - - graphics.CopyFromScreen(p, Point.Empty, rect.Size); - - BitmapData bd = bitmap.LockBits(rect, ImageLockMode.ReadOnly, AddonDataProviderConfig.PIXEL_FORMAT); - - IAddonDataProvider.InternalUpdate(bd, frames, Data); - - bitmap.UnlockBits(bd); - } -} - diff --git a/Core/AddonDataProvider/AddonDataProviderGDIConfig.cs b/Core/AddonDataProvider/AddonDataProviderGDIConfig.cs deleted file mode 100644 index 1c53d3dd8..000000000 --- a/Core/AddonDataProvider/AddonDataProviderGDIConfig.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Game; - -using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.Text; -using System.Threading; - -namespace Core; - -public sealed class AddonDataProviderGDIConfig : IAddonDataProvider -{ - public int[] Data { get; private set; } = Array.Empty(); - public StringBuilder TextBuilder { get; } = new(3); - - private readonly CancellationToken ct; - private readonly ManualResetEventSlim manualReset = new(true); - private readonly IWowScreen wowScreen; - - private DataFrame[] frames = Array.Empty(); - - private Rectangle rect; - private Bitmap? bitmap; - private Graphics? graphics; - - private bool disposing; - - public AddonDataProviderGDIConfig(CancellationTokenSource cts, IWowScreen wowScreen, DataFrame[] frames) - { - ct = cts.Token; - this.wowScreen = wowScreen; - InitFrames(frames); - } - - public void Dispose() - { - if (disposing) - return; - - disposing = true; - - graphics?.Dispose(); - bitmap?.Dispose(); - } - - public void UpdateData() - { - manualReset.Wait(); - ct.WaitHandle.WaitOne(25); - - if (ct.IsCancellationRequested || - disposing || - Data.Length == 0 || - frames.Length == 0 || - bitmap == null || - graphics == null) - return; - - Point p = new(); - wowScreen.GetRectangle(out Rectangle pRect); - p = pRect.Location; - graphics.CopyFromScreen(p, Point.Empty, rect.Size); - - BitmapData bd = bitmap.LockBits(rect, ImageLockMode.ReadOnly, AddonDataProviderConfig.PIXEL_FORMAT); - - IAddonDataProvider.InternalUpdate(bd, frames, Data); - - bitmap.UnlockBits(bd); - } - - public void InitFrames(DataFrame[] frames) - { - manualReset.Reset(); - - this.frames = frames; - Data = new int[frames.Length]; - - for (int i = 0; i < frames.Length; i++) - { - rect.Width = Math.Max(rect.Width, frames[i].X); - rect.Height = Math.Max(rect.Height, frames[i].Y); - } - rect.Width++; - rect.Height++; - - bitmap = new(rect.Width, rect.Height, AddonDataProviderConfig.PIXEL_FORMAT); - graphics = Graphics.FromImage(bitmap); - - manualReset.Set(); - } - - public int GetInt(int index) - { - return index > Data.Length ? 0 : Data[index]; - } -} - diff --git a/Core/AddonDataProvider/IAddonDataProvider.cs b/Core/AddonDataProvider/IAddonDataProvider.cs index c50f2767d..345e16e21 100644 --- a/Core/AddonDataProvider/IAddonDataProvider.cs +++ b/Core/AddonDataProvider/IAddonDataProvider.cs @@ -1,39 +1,36 @@ -using System.Drawing.Imaging; -using System; +using System; -using static Core.AddonDataProviderConfig; using System.Text; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp; namespace Core; public interface IAddonDataProvider : IDisposable { + private static readonly Bgra32 firstColor = new(0, 0, 0, 255); + private static readonly Bgra32 lastlColor = new(30, 132, 129, 255); + void UpdateData(); - void InitFrames(DataFrame[] frames) { } + void InitFrames(DataFrame[] frames); int[] Data { get; } StringBuilder TextBuilder { get; } [SkipLocalsInit] - static unsafe void InternalUpdate(BitmapData bd, + static unsafe void InternalUpdate(Image bd, ReadOnlySpan frames, Span output) { - int stride = bd.Stride; - - ReadOnlySpan bitmapSpan = - new(bd.Scan0.ToPointer(), bd.Height * stride); + Bgra32 first = bd.DangerousGetPixelRowMemory(frames[0].Y) + .Span[frames[0].X]; - ReadOnlySpan first = - bitmapSpan.Slice(frames[0].Y * stride + frames[0].X * BYTES_PER_PIXEL, - BYTES_PER_PIXEL); + Bgra32 last = bd.DangerousGetPixelRowMemory(frames[^1].Y) + .Span[frames[^1].X]; - ReadOnlySpan last = - bitmapSpan.Slice(frames[^1].Y * stride + frames[^1].X * BYTES_PER_PIXEL, - BYTES_PER_PIXEL); - - if (!first.SequenceEqual(fColor) || - !last.SequenceEqual(lColor)) + if (!first.Equals(firstColor) || + !last.Equals(lastlColor)) { return; } @@ -42,13 +39,10 @@ static unsafe void InternalUpdate(BitmapData bd, { DataFrame frame = frames[i]; - int yOffset = frame.Y * stride; - int xOffset = frame.X * BYTES_PER_PIXEL; - - ReadOnlySpan pixel = - bitmapSpan.Slice(yOffset + xOffset, BYTES_PER_PIXEL); + Span row = bd.DangerousGetPixelRowMemory(frame.Y).Span; + ref Bgra32 pixel = ref row[frame.X]; - output[frame.Index] = pixel[0] | (pixel[1] << 8) | (pixel[2] << 16); + output[frame.Index] = pixel.B | (pixel.G << 8) | (pixel.R << 16); } } diff --git a/Core/BotController.cs b/Core/BotController.cs index d37e490d6..2163cc091 100644 --- a/Core/BotController.cs +++ b/Core/BotController.cs @@ -1,21 +1,22 @@ using Core.Goals; using Core.GOAP; +using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; -using static Newtonsoft.Json.JsonConvert; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; +using System.Numerics; using System.Threading; using Game; +using PPather.Data; using WinAPI; using SharedLib.NpcFinder; -using PPather.Data; -using Microsoft.Extensions.DependencyInjection; -using System.Numerics; using SharedLib; -using Microsoft.Extensions.Options; + +using static System.Diagnostics.Stopwatch; +using static Newtonsoft.Json.JsonConvert; namespace Core; @@ -100,9 +101,7 @@ public BotController( overlayOptions.Value.ShowSkinning, overlayOptions.Value.ShowTargetVsAdd); - bool isDXGI = wowScreen is WowScreenDXGI; - - addonThread = new(isDXGI ? DXGI_AddonThread : GDI_AddonThread); + addonThread = new(AddonThread); addonThread.Priority = ThreadPriority.AboveNormal; addonThread.Start(); @@ -118,7 +117,7 @@ public BotController( logger.LogInformation($"{playerReader.Race.ToStringF()} " + $"{playerReader.Class.ToStringF()}!"); - screenshotThread = new(isDXGI ? DXGI_ScreenshotThread : GDI_ScreenshotThread); + screenshotThread = new(ScreenshotThread); screenshotThread.Start(); if (pather is RemotePathingAPI) @@ -133,91 +132,7 @@ public ClassConfiguration ResolveLoadedProfile() return ClassConfig!; } - private void GDI_AddonThread() - { - while (!cts.IsCancellationRequested) - { - addonReader.Update(); - } - logger.LogWarning("Addon thread stopped!"); - } - - private void GDI_ScreenshotThread() - { - long time; - int tickCount = 0; - - const int SIZE = 8; - const int MOD = SIZE - 1; - Span screen = stackalloc double[SIZE]; - Span npc = stackalloc double[SIZE]; - - WaitHandle[] waitHandles = new[] { - cts.Token.WaitHandle, - npcResetEvent.WaitHandle, - }; - - while (true) - { - if (wowScreen.Enabled) - { - time = Stopwatch.GetTimestamp(); - wowScreen.Update(); - screen[tickCount & MOD] = - Stopwatch.GetElapsedTime(time).TotalMilliseconds; - - time = Stopwatch.GetTimestamp(); - npcNameFinder.Update(); - npc[tickCount & MOD] = - Stopwatch.GetElapsedTime(time).TotalMilliseconds; - - if (wowScreen.EnablePostProcess) - wowScreen.PostProcess(); - - AvgScreenLatency = Average(screen); - AvgNPCLatency = Average(npc); - } - - if (ClassConfig?.Mode == Mode.AttendedGather) - { - time = Stopwatch.GetTimestamp(); - wowScreen.UpdateMinimapBitmap(); - minimapNodeFinder.Update(); - screen[tickCount & MOD] = - Stopwatch.GetElapsedTime(time).TotalMilliseconds; - - AvgScreenLatency = Average(screen); - } - - int waitResult = - WaitHandle.WaitAny(waitHandles, - Math.Max( - screenshotTickMs - - (int)screen[tickCount & MOD] - - (int)npc[tickCount & MOD], - 20)); - - tickCount++; - - if (waitResult == 0) - break; - } - - if (logger.IsEnabled(LogLevel.Debug)) - logger.LogDebug("Screenshot Thread stopped!"); - - static double Average(Span span) - { - double sum = 0; - for (int i = 0; i < SIZE; i++) - { - sum += span[i]; - } - return sum / SIZE; - } - } - - private void DXGI_AddonThread() + private void AddonThread() { long time; int tickCount = 0; @@ -228,10 +143,10 @@ private void DXGI_AddonThread() while (!cts.IsCancellationRequested) { - time = Stopwatch.GetTimestamp(); + time = GetTimestamp(); wowScreen.Update(); screen[tickCount & MOD] = - Stopwatch.GetElapsedTime(time).TotalMilliseconds; + GetElapsedTime(time).TotalMilliseconds; addonReader.Update(); @@ -240,11 +155,11 @@ private void DXGI_AddonThread() tickCount++; // attempt to reduce CPU usage - cts.Token.WaitHandle.WaitOne(4); + Thread.Sleep(4); } logger.LogWarning("Addon thread stopped!"); - static double Average(Span span) + static double Average(ReadOnlySpan span) { double sum = 0; for (int i = 0; i < SIZE; i++) @@ -255,7 +170,7 @@ static double Average(Span span) } } - private void DXGI_ScreenshotThread() + private void ScreenshotThread() { long time; int tickCount = 0; @@ -273,29 +188,27 @@ private void DXGI_ScreenshotThread() { if (wowScreen.Enabled) { - time = Stopwatch.GetTimestamp(); + time = GetTimestamp(); npcNameFinder.Update(); npc[tickCount & MOD] = - Stopwatch.GetElapsedTime(time).TotalMilliseconds; + GetElapsedTime(time).TotalMilliseconds; if (wowScreen.EnablePostProcess) wowScreen.PostProcess(); - - AvgNPCLatency = Average(npc); } - if (ClassConfig?.Mode == Mode.AttendedGather) + if (wowScreen.MinimapEnabled) { - time = Stopwatch.GetTimestamp(); + time = GetTimestamp(); minimapNodeFinder.Update(); npc[tickCount & MOD] = - Stopwatch.GetElapsedTime(time).TotalMilliseconds; + GetElapsedTime(time).TotalMilliseconds; wowScreen.PostProcess(); - - AvgScreenLatency = Average(npc); } + AvgNPCLatency = Average(npc); + int waitResult = WaitHandle.WaitAny(waitHandles, Math.Max( @@ -312,7 +225,7 @@ private void DXGI_ScreenshotThread() if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("Screenshot Thread stopped!"); - static double Average(Span span) + static double Average(ReadOnlySpan span) { double sum = 0; for (int i = 0; i < SIZE; i++) @@ -323,7 +236,6 @@ static double Average(Span span) } } - private void RemotePathingThread() { bool newLoaded = false; @@ -384,7 +296,7 @@ public void ToggleBotStatus() private bool InitialiseFromFile(string classFile, string? pathFile) { - long startTime = Stopwatch.GetTimestamp(); + long startTime = GetTimestamp(); try { ClassConfig = ReadClassConfiguration(classFile); @@ -401,7 +313,7 @@ private bool InitialiseFromFile(string classFile, string? pathFile) } LogProfileLoadedTime(logger, - Stopwatch.GetElapsedTime(startTime).TotalMilliseconds); + GetElapsedTime(startTime).TotalMilliseconds); return true; } @@ -436,6 +348,8 @@ static ClassConfiguration GetConfig(IServiceProvider sp) => RouteInfo = sessionScope. ServiceProvider.GetService(); + + wowScreen.MinimapEnabled = config.Mode == Mode.AttendedGather; } private static IEnumerable GetPathProviders(IServiceProvider sp) diff --git a/Core/ConfigBotController.cs b/Core/ConfigBotController.cs index 78ea7d62d..6452ccffc 100644 --- a/Core/ConfigBotController.cs +++ b/Core/ConfigBotController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading; using Microsoft.Extensions.Logging; +using Game; namespace Core; @@ -13,6 +14,7 @@ public sealed class ConfigBotController : IBotController, IDisposable private readonly Thread addonThread; private readonly IAddonReader addonReader; + private readonly IWowScreen wowScreen; public GoapAgent? GoapAgent => throw new NotImplementedException(); public RouteInfo? RouteInfo => throw new NotImplementedException(); @@ -29,11 +31,15 @@ public sealed class ConfigBotController : IBotController, IDisposable public event Action? ProfileLoaded; public event Action? StatusChanged; - public ConfigBotController(ILogger logger, IAddonReader addonReader, CancellationTokenSource cts) + public ConfigBotController(ILogger logger, + IAddonReader addonReader, + IWowScreen wowScreen, + CancellationTokenSource cts) { this.logger = logger; this.cts = cts; this.addonReader = addonReader; + this.wowScreen = wowScreen; addonThread = new(AddonThread); addonThread.Start(); @@ -48,7 +54,9 @@ private void AddonThread() { while (!cts.IsCancellationRequested) { + wowScreen.Update(); addonReader.Update(); + Thread.Sleep(20); } logger.LogWarning("Thread stopped!"); } diff --git a/Core/Configurator/FrameConfigurator.cs b/Core/Configurator/FrameConfigurator.cs index cd8c173ad..352c89c10 100644 --- a/Core/Configurator/FrameConfigurator.cs +++ b/Core/Configurator/FrameConfigurator.cs @@ -4,10 +4,11 @@ using SharedLib; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Processing; + using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; using System.Threading; namespace Core; @@ -197,15 +198,20 @@ private bool DoConfig(bool auto) } break; case Stage.CreateDataFrames: - Bitmap bitmap = wowScreen.GetBitmap(size.Width, size.Height); + + Size addonSize = size; + var cropped = wowScreen.ScreenImage.Clone(cropSize); + void cropSize(IImageProcessingContext x) + { + x.Crop(addonSize.Width, addonSize.Height); + } + if (!auto) { - using MemoryStream ms = new(); - bitmap.Save(ms, ImageFormat.Png); - this.ImageBase64 = Convert.ToBase64String(ms.ToArray()); + ImageBase64 = cropped.ToBase64String(JpegFormat.Instance); } - DataFrames = FrameConfig.TryCreateFrames(DataFrameMeta, bitmap); + DataFrames = FrameConfig.TryCreateFrames(DataFrameMeta, cropped); if (DataFrames.Length == DataFrameMeta.frames) { stage++; @@ -219,7 +225,6 @@ private bool DoConfig(bool auto) return false; } - bitmap.Dispose(); break; case Stage.ReturnNormalMode: if (auto) @@ -228,15 +233,32 @@ private bool DoConfig(bool auto) wowProcessInput.SetForegroundWindow(); ToggleInGameConfiguration(execGameCommand); wait.Fixed(INTERVAL); + wait.Update(); } - if (GetDataFrameMeta() == DataFrameMeta.Empty) + temp = GetDataFrameMeta(); + if (temp == DataFrameMeta.Empty) + { + logger.LogDebug(temp.ToString()); stage++; + } + else + { + if (auto) + { + + logger.LogError("Unable to return normal mode!"); + ResetConfigState(); + } + + return false; + } break; case Stage.UpdateReader: reader.InitFrames(DataFrames); wait.Update(); wait.Update(); + reader.UpdateData(); stage++; break; case Stage.ValidateData: @@ -282,12 +304,13 @@ private void ResetConfigState() reader.InitFrames(DataFrames); wait.Update(); + + logger.LogDebug("ResetConfigState"); } private DataFrameMeta GetDataFrameMeta() { - using Bitmap bitmap = wowScreen.GetBitmap(5, 5); - return FrameConfig.GetMeta(bitmap.GetPixel(0, 0)); + return FrameConfig.GetMeta(wowScreen.ScreenImage[0, 0]); } public void ToggleManualConfig() @@ -351,6 +374,14 @@ private void ToggleInGameConfiguration(ExecGameCommand exec) public bool TryResolveRaceAndClass(out UnitRace race, out UnitClass @class, out ClientVersion version) { + if (reader.Data.Length < 46) + { + race = 0; + @class = 0; + version = 0; + return false; + } + int value = reader.GetInt(46); // RACE_ID * 10000 + CLASS_ID * 100 + ClientVersion diff --git a/Core/Cursor/CursorClassifier.cs b/Core/Cursor/CursorClassifier.cs index 2b6e69ec2..4cc2cca3c 100644 --- a/Core/Cursor/CursorClassifier.cs +++ b/Core/Cursor/CursorClassifier.cs @@ -34,20 +34,20 @@ public sealed class CursorClassifier : IDisposable private readonly Bitmap bitmap; private readonly Graphics graphics; - private readonly Bitmap workBitmap; - private readonly Graphics workGraphics; + private readonly Bitmap scaledBitmap; + private readonly Graphics scaledGraphics; public CursorClassifier() { - Size size = GetCursorSize(); + SixLabors.ImageSharp.Size size = GetCursorSize(); bitmap = new(size.Width, size.Height); graphics = Graphics.FromImage(bitmap); - workBitmap = new(8, 8, PixelFormat.Format32bppArgb); - workGraphics = Graphics.FromImage(workBitmap); - workGraphics.CompositingQuality = CompositingQuality.HighQuality; - workGraphics.InterpolationMode = InterpolationMode.HighQualityBilinear; - workGraphics.SmoothingMode = SmoothingMode.HighQuality; + scaledBitmap = new(8, 8, PixelFormat.Format32bppArgb); + scaledGraphics = Graphics.FromImage(scaledBitmap); + scaledGraphics.CompositingQuality = CompositingQuality.HighQuality; + scaledGraphics.InterpolationMode = InterpolationMode.HighQualityBilinear; + scaledGraphics.SmoothingMode = SmoothingMode.HighQuality; } public void Dispose() @@ -55,8 +55,8 @@ public void Dispose() graphics.Dispose(); bitmap.Dispose(); - workGraphics.Dispose(); - workBitmap.Dispose(); + scaledGraphics.Dispose(); + scaledBitmap.Dispose(); } @@ -72,7 +72,7 @@ public void Classify(out CursorType classification, out double similarity) graphics.ReleaseHdc(); } - ulong cursorHash = ImageHashing.AverageHash(bitmap, workBitmap, workGraphics); + ulong cursorHash = ImageHashing.AverageHash(bitmap, scaledBitmap, scaledGraphics); if (saveImage) { string path = Path.Join("..", "..", "..", "..", "Cursors", $"{cursorHash}.bmp"); diff --git a/Core/Cursor/ImageHashing.cs b/Core/Cursor/ImageHashing.cs index 5cbb471ae..eaeedf45e 100644 --- a/Core/Cursor/ImageHashing.cs +++ b/Core/Cursor/ImageHashing.cs @@ -14,22 +14,6 @@ namespace Core; /// public static class ImageHashing { - /// - /// Bitcounts array used for BitCount method (used in Similarity comparisons). - /// Don't try to read this or understand it, I certainly don't. Credit goes to - /// David Oftedal of the University of Oslo, Norway for this. - /// http://folk.uio.no/davidjo/computing.php - /// - private static readonly byte[] bitCounts = { - 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4, - 2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, - 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6, - 4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, - 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5, - 3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, - 4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8 - }; - /// /// Computes the average hash of an image according to the algorithm given by Dr. Neal Krawetz /// on his blog: http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html. @@ -37,17 +21,17 @@ public static class ImageHashing /// The image to hash. /// The hash of the image. [SkipLocalsInit] - public static unsafe ulong AverageHash(Bitmap image, Bitmap squeezed, Graphics canvas) + public static unsafe ulong AverageHash(Bitmap image, Bitmap scaled, Graphics g) { Rectangle rect = new(0, 0, 8, 8); - canvas.Clear(Color.Transparent); - canvas.DrawImage(image, rect); + g.Clear(Color.Transparent); + g.DrawImage(image, rect); // Reduce colors to 6-bit grayscale and calculate average color value Span grayscale = stackalloc byte[64]; const int bytesPerPixel = 4; //Image.GetPixelFormatSize(squeezed.PixelFormat) / 8; - BitmapData data = squeezed.LockBits(rect, ImageLockMode.ReadOnly, squeezed.PixelFormat); + BitmapData data = scaled.LockBits(rect, ImageLockMode.ReadOnly, scaled.PixelFormat); uint averageValue = 0; for (int i = 0; i < 64; i++) @@ -62,7 +46,7 @@ public static unsafe ulong AverageHash(Bitmap image, Bitmap squeezed, Graphics c grayscale[i] = (byte)gray; averageValue += gray; } - squeezed.UnlockBits(data); + scaled.UnlockBits(data); averageValue /= 64; diff --git a/Core/DataFrame/DataFrameConfig.cs b/Core/DataFrame/DataFrameConfig.cs index 489a6b972..9f3c704a9 100644 --- a/Core/DataFrame/DataFrameConfig.cs +++ b/Core/DataFrame/DataFrameConfig.cs @@ -1,5 +1,5 @@ using System; -using System.Drawing; +using SixLabors.ImageSharp; namespace Core; diff --git a/Core/DataFrame/DataFrameMeta.cs b/Core/DataFrame/DataFrameMeta.cs index 7bb3da79a..6ed3bee2e 100644 --- a/Core/DataFrame/DataFrameMeta.cs +++ b/Core/DataFrame/DataFrameMeta.cs @@ -1,5 +1,5 @@ using System; -using System.Drawing; +using SixLabors.ImageSharp; using Newtonsoft.Json; namespace Core; @@ -45,7 +45,7 @@ public Size EstimatedSize(Rectangle screenRect) return Size.Empty; } - return estimatedSize.ToSize(); + return (Size)estimatedSize; } public override int GetHashCode() diff --git a/Core/DataFrame/FrameConfig.cs b/Core/DataFrame/FrameConfig.cs index 6cce2ab4b..3cdcce611 100644 --- a/Core/DataFrame/FrameConfig.cs +++ b/Core/DataFrame/FrameConfig.cs @@ -1,6 +1,9 @@ using Newtonsoft.Json; + +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + using System; -using System.Drawing; using System.IO; namespace Core; @@ -72,7 +75,7 @@ public static void Delete() } } - public static DataFrameMeta GetMeta(Color color) + public static DataFrameMeta GetMeta(Bgra32 color) { int hash = color.R * 65536 + color.G * 256 + color.B; if (hash == 0) @@ -87,7 +90,7 @@ public static DataFrameMeta GetMeta(Color color) return new DataFrameMeta(hash, spacing, size, rows, count); } - public static DataFrame[] TryCreateFrames(DataFrameMeta meta, Bitmap bmp) + public static DataFrame[] TryCreateFrames(DataFrameMeta meta, Image bmp) { DataFrame[] frames = new DataFrame[meta.frames]; frames[0] = new(0, 0, 0); @@ -107,13 +110,13 @@ public static DataFrame[] TryCreateFrames(DataFrameMeta meta, Bitmap bmp) return frames; } - private static bool TryGetNextPoint(Bitmap bmp, int i, int startX, out int x, out int y) + private static bool TryGetNextPoint(Image bmp, int i, int startX, out int x, out int y) { for (int xi = startX; xi < bmp.Width; xi++) { for (int yi = 0; yi < bmp.Height; yi++) { - Color pixel = bmp.GetPixel(xi, yi); + Bgra32 pixel = bmp[xi, yi]; if (pixel.B == i && pixel.R == 0 && pixel.G == 0) { x = xi; diff --git a/Core/DependencyInjection.cs b/Core/DependencyInjection.cs index 64f7cb2a3..5ff29d73e 100644 --- a/Core/DependencyInjection.cs +++ b/Core/DependencyInjection.cs @@ -1,5 +1,5 @@ using System; -using System.Drawing; +using SixLabors.ImageSharp; using System.Threading; using Core.Addon; @@ -151,9 +151,9 @@ public static IServiceCollection AddCoreFrontend( } public static IServiceCollection AddCoreConfiguration( - this IServiceCollection s) + this IServiceCollection s, ILogger log) { - s.AddSingleton(); + s.AddSingleton(x => GetAddonDataProvider(x.GetRequiredService(), log)); s.AddSingleton(); s.AddSingleton(); @@ -203,7 +203,7 @@ public static IServiceCollection AddCoreBase(this IServiceCollection s) s.AddSingleton(x => DataConfig.Load( x.GetRequiredService().Path)); - s.ForwardSingleton( + s.ForwardSingleton( x => GetWoWScreen(x.GetRequiredService())); s.ForwardSingleton(); @@ -294,11 +294,6 @@ private static IAddonDataProvider GetAddonDataProvider( IAddonDataProvider value = scr.Value.ReaderType switch { - AddonDataProviderType.GDIConfig or - AddonDataProviderType.GDI => - new AddonDataProviderGDI(wowScreen, frames), - AddonDataProviderType.GDIBlit => - new AddonDataProviderBitBlt(wowScreen, frames), AddonDataProviderType.DXGI => (IAddonDataProvider)wowScreen, _ => throw new NotImplementedException(), @@ -379,10 +374,6 @@ private static IWowScreen GetWoWScreen(IServiceProvider sp) IWowScreen value = scr.Value.ReaderType switch { - AddonDataProviderType.GDIConfig or - AddonDataProviderType.GDI or - AddonDataProviderType.GDIBlit => - new WowScreenGDI(factory.CreateLogger(), wowProcess), AddonDataProviderType.DXGI => new WowScreenDXGI(factory.CreateLogger(), wowProcess, frames), _ => throw new NotImplementedException(), diff --git a/Core/GoalsComponent/NpcNameTargeting.cs b/Core/GoalsComponent/NpcNameTargeting.cs index 981848702..07403b44f 100644 --- a/Core/GoalsComponent/NpcNameTargeting.cs +++ b/Core/GoalsComponent/NpcNameTargeting.cs @@ -1,5 +1,5 @@ using System; -using System.Drawing; +using SixLabors.ImageSharp; using System.Threading; using SharedLib.Extensions; using SharedLib.NpcFinder; diff --git a/Core/GoalsComponent/NpcNameTargetingLocations.cs b/Core/GoalsComponent/NpcNameTargetingLocations.cs index 2cd643a11..af345d88e 100644 --- a/Core/GoalsComponent/NpcNameTargetingLocations.cs +++ b/Core/GoalsComponent/NpcNameTargetingLocations.cs @@ -1,4 +1,4 @@ -using System.Drawing; +using SixLabors.ImageSharp; using SharedLib.Extensions; using SharedLib.NpcFinder; diff --git a/Core/Minimap/MinimapNodeFinder.cs b/Core/Minimap/MinimapNodeFinder.cs index 29607154f..064b0e48e 100644 --- a/Core/Minimap/MinimapNodeFinder.cs +++ b/Core/Minimap/MinimapNodeFinder.cs @@ -1,15 +1,17 @@ using System; using System.Buffers; -using System.Drawing; -using System.Drawing.Imaging; -using System.Threading.Tasks; +using System.Diagnostics.Metrics; -using Game; +using Core.Minimap; using Microsoft.Extensions.Logging; using SharedLib; using SharedLib.Extensions; +using SharedLib.NpcFinder; + +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Advanced; #pragma warning disable 162 @@ -17,141 +19,55 @@ namespace Core; public sealed class MinimapNodeFinder { - private readonly struct PixelPoint - { - public readonly int X; - public readonly int Y; - - public PixelPoint(int x, int y) - { - X = x; - Y = y; - } - } - - private const bool DEBUG_MASK = false; - private readonly ILogger logger; - private readonly IMinimapBitmapProvider provider; + private readonly IMinimapImageProvider provider; public event EventHandler? NodeEvent; - private const int minScore = 2; - private const byte maxBlue = 34; - private const byte minRedGreen = 176; + private readonly ArrayCounter counter; - private readonly int maxY; + private const int minScore = 2; - public MinimapNodeFinder(ILogger logger, IMinimapBitmapProvider provider) + public MinimapNodeFinder(ILogger logger, IMinimapImageProvider provider) { this.logger = logger; this.provider = provider; - maxY = provider.MiniMapBitmap.Height - 6; + counter = new(); } public void Update() { - var span = FindYellowPoints(); - ScorePoints(span, out PixelPoint best, out int amountAboveMin); + ReadOnlySpan span = FindYellowPoints(); + ScorePoints(span, out Point best, out int amountAboveMin); NodeEvent?.Invoke(this, new MinimapNodeEventArgs(best.X, best.Y, amountAboveMin)); } - private ReadOnlySpan FindYellowPoints() + private ReadOnlySpan FindYellowPoints() { - const int SIZE = 100; - var pooler = ArrayPool.Shared; - PixelPoint[] points = pooler.Rent(SIZE); - - // TODO: adjust these values based on resolution - // The reference resolution is 1920x1080 - const int minX = 6; - const int maxX = 170; - const int minY = 36; - - Rectangle rect = new(minX, minY, maxX - minX, maxY - minY); - Point center = rect.Centre(); - float radius = (maxX - minX) / 2f; - - int count = 0; + var pooler = ArrayPool.Shared; + Point[] points = pooler.Rent(MinimapRowOperation.SIZE); - lock (provider.MiniMapLock) - { - Bitmap bitmap = provider.MiniMapBitmap; + counter.count = 0; - unsafe - { - BitmapData bd = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), - DEBUG_MASK ? ImageLockMode.ReadWrite : ImageLockMode.ReadOnly, bitmap.PixelFormat); - const int bytesPerPixel = 4; //Bitmap.GetPixelFormatSize(bitmap.PixelFormat) / 8; + MinimapRowOperation operation = new( + provider.MiniMapImage.Frames[0].PixelBuffer, + provider.MiniMapRect, counter, points); - Parallel.For(minY, maxY, y => - { - ReadOnlySpan bitmapSpan = new(bd.Scan0.ToPointer(), bd.Height * bd.Stride); - ReadOnlySpan currentLine = bitmapSpan.Slice(y * bd.Stride, bd.Stride); - - for (int x = minX; x < maxX; x++) - { - if (!IsValidSquareLocation(x, y, center, radius)) - { - /* - if (DEBUG_MASK) - { - int xii = x * bytesPerPixel; - currentLine[xii + 2] = 0; - currentLine[xii + 1] = 0; - currentLine[xii + 0] = 0; - } - */ - continue; - } - - int xi = x * bytesPerPixel; - if (IsMatch(currentLine[xi + 2], currentLine[xi + 1], currentLine[xi])) - { - if (count >= SIZE) - return; - - points[count++] = new PixelPoint(x, y); - - /* - if (DEBUG_MASK) - { - currentLine[xi + 2] = 255; - currentLine[xi + 1] = 0; - currentLine[xi + 0] = 0; - } - */ - } - } - }); - - bitmap.UnlockBits(bd); - } - } + ParallelRowIterator.IterateRows( + Configuration.Default, + operation.rect, + in operation); - if (count >= SIZE) - { - //logger.LogWarning("Too much yellow in this image!"); - } + pooler.Return(points); - return points.AsSpan(0, count); - - static bool IsValidSquareLocation(int x, int y, Point center, float width) - { - return MathF.Sqrt(((x - center.X) * (x - center.X)) + ((y - center.Y) * (y - center.Y))) < width; - } - - static bool IsMatch(byte red, byte green, byte blue) - { - return blue < maxBlue && red > minRedGreen && green > minRedGreen; - } + return points.AsSpan(0, counter.count); } - private static void ScorePoints(ReadOnlySpan points, out PixelPoint best, out int amountAboveMin) + private static void ScorePoints(ReadOnlySpan points, out Point best, out int amountAboveMin) { const int size = 5; - best = new PixelPoint(); + best = new Point(); amountAboveMin = 0; int maxIndex = -1; @@ -159,12 +75,12 @@ private static void ScorePoints(ReadOnlySpan points, out PixelPoint for (int i = 0; i < points.Length; i++) { - PixelPoint pi = points[i]; + Point pi = points[i]; int score = 0; for (int j = 0; j < points.Length; j++) { - PixelPoint pj = points[j]; + Point pj = points[j]; if (i != j && (Math.Abs(pi.X - pj.X) < size || diff --git a/Core/Minimap/MinimapRowOperation.cs b/Core/Minimap/MinimapRowOperation.cs new file mode 100644 index 000000000..350cca4e4 --- /dev/null +++ b/Core/Minimap/MinimapRowOperation.cs @@ -0,0 +1,95 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +using SharedLib.Extensions; +using SharedLib; + +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace Core.Minimap; +internal readonly struct MinimapRowOperation : IRowOperation +{ + public const int SIZE = 100; + + private const byte maxBlue = 34; + private const byte minRedGreen = 176; + + private const int minX = 6; + private const int maxX = 170; + private const int minY = 36; + private readonly int maxY; + + public readonly Rectangle rect; + private readonly Point center; + private readonly float radius; + + private readonly Buffer2D source; + private readonly Point[] points; + private readonly ArrayCounter counter; + + public MinimapRowOperation(Buffer2D source, + Rectangle minimapRect, ArrayCounter counter, Point[] points) + { + this.source = source; + this.points = points; + this.counter = counter; + + maxY = minimapRect.Height - 6; + + rect = new(minX, minY, maxX - minX, maxY - minY); + center = rect.Centre(); + radius = (maxX - minX) / 2f; + } + + public int GetRequiredBufferLength(Rectangle bounds) + { + return 64; // SIZE / 2 + } + + [SkipLocalsInit] + public void Invoke(int y, Span span) + { + ReadOnlySpan row = source.DangerousGetRowSpan(y); + + int i = 0; + + for (int x = minX; x < maxX; x++) + { + if (!IsValidSquareLocation(x, y, center, radius)) + { + continue; + } + + Bgra32 pixel = row[x]; + + if (IsMatch(pixel.R, pixel.G, pixel.B)) + { + if (i >= SIZE) + break; + + points[i++] = new(x, y); + } + } + + if (i == 0) + return; + + Interlocked.Add(ref counter.count, i); + + span[..i].CopyTo(points.AsSpan(counter.count, i)); + + static bool IsValidSquareLocation(int x, int y, Point center, float width) + { + return MathF.Sqrt(((x - center.X) * (x - center.X)) + ((y - center.Y) * (y - center.Y))) < width; + } + + static bool IsMatch(byte red, byte green, byte blue) + { + return blue < maxBlue && red > minRedGreen && green > minRedGreen; + } + } +} diff --git a/Core/Overlay/NpcNameOverlay.cs b/Core/Overlay/NpcNameOverlay.cs index e646725c8..2f0f266a4 100644 --- a/Core/Overlay/NpcNameOverlay.cs +++ b/Core/Overlay/NpcNameOverlay.cs @@ -8,8 +8,8 @@ using SharedLib.Extensions; using SharedLib.NpcFinder; -using DPoint = System.Drawing.Point; -using DRectangle = System.Drawing.Rectangle; +using DPoint = SixLabors.ImageSharp.Point; +using DRectangle = SixLabors.ImageSharp.Rectangle; namespace Core; diff --git a/Core/ScreenCapture/ScreenCapture.cs b/Core/ScreenCapture/ScreenCapture.cs index c6146904f..85d0fcd45 100644 --- a/Core/ScreenCapture/ScreenCapture.cs +++ b/Core/ScreenCapture/ScreenCapture.cs @@ -1,6 +1,4 @@ using System; -using System.Drawing; -using System.Drawing.Imaging; using System.IO; using System.Threading; @@ -8,6 +6,8 @@ using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; + namespace Core; public sealed partial class ScreenCapture : ScreenCaptureCleaner, IDisposable @@ -20,9 +20,6 @@ public sealed partial class ScreenCapture : ScreenCaptureCleaner, IDisposable private readonly ManualResetEventSlim manualReset; private readonly Thread thread; - private readonly Bitmap bitmap; - private readonly Graphics graphics; - public ScreenCapture(ILogger logger, DataConfig dataConfig, CancellationTokenSource cts, IWowScreen wowScreen) : base(logger, dataConfig) @@ -32,9 +29,6 @@ public ScreenCapture(ILogger logger, DataConfig dataConfig, this.token = cts.Token; this.wowScreen = wowScreen; - bitmap = new(wowScreen.Rect.Width, wowScreen.Rect.Height); - graphics = Graphics.FromImage(bitmap); - manualReset = new(false); thread = new(Thread); thread.Start(); @@ -43,9 +37,6 @@ public ScreenCapture(ILogger logger, DataConfig dataConfig, public void Dispose() { manualReset.Set(); - - graphics.Dispose(); - bitmap.Dispose(); } private void Thread() @@ -60,8 +51,7 @@ private void Thread() string fileName = $"{DateTimeOffset.Now:MM_dd_HH_mm_ss_fff}.jpg"; LogScreenCapture(logger, fileName); - wowScreen.DrawBitmapTo(graphics); - bitmap.Save(Path.Join(dataConfig.Screenshot, fileName), ImageFormat.Jpeg); + wowScreen.ScreenImage.SaveAsJpeg(Path.Join(dataConfig.Screenshot, fileName)); } catch (Exception ex) { diff --git a/Core/WoWScreen/WowScreenDXGI.cs b/Core/WoWScreen/WowScreenDXGI.cs index 5ad0d97ce..124078255 100644 --- a/Core/WoWScreen/WowScreenDXGI.cs +++ b/Core/WoWScreen/WowScreenDXGI.cs @@ -4,10 +4,12 @@ using SharpGen.Runtime; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; + using System; -using System.Drawing; -using System.Drawing.Imaging; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; using System.Text; using Vortice.Direct3D; @@ -25,28 +27,27 @@ public sealed class WowScreenDXGI : IWowScreen, IAddonDataProvider { private readonly ILogger logger; private readonly WowProcess wowProcess; + private readonly int Bgra32Size; public event Action? OnScreenChanged; public bool Enabled { get; set; } - public bool EnablePostProcess { get; set; } - public Bitmap Bitmap { get; private set; } - public object Lock { get; init; } - private const double screenshotTickMs = 180; - private DateTime lastScreenUpdate; + public bool MinimapEnabled { get; set; } + + public Rectangle ScreenRect => screenRect; + private Rectangle screenRect; + + public Image ScreenImage { get; init; } + + private readonly SixLabors.ImageSharp.Configuration ContiguousJpegConfiguration + = new(new JpegConfigurationModule()) { PreferContiguousImageBuffers = true }; // TODO: make it work for higher resolution ex. 4k public const int MiniMapSize = 200; - public Bitmap MiniMapBitmap { get; private set; } public Rectangle MiniMapRect { get; private set; } - public object MiniMapLock { get; init; } - - private readonly int minimapBytesToCopy; - - private const double miniMapTickMs = 180; - private DateTime lastMinimapUpdate; + public Image MiniMapImage { get; init; } public IntPtr ProcessHwnd => wowProcess.Process.MainWindowHandle; @@ -63,26 +64,20 @@ public sealed class WowScreenDXGI : IWowScreen, IAddonDataProvider private readonly ID3D11Texture2D minimapTexture; private readonly ID3D11Texture2D screenTexture; - private readonly ID3D11Texture2D addonTexture; + private ID3D11Texture2D addonTexture = null!; private readonly ID3D11Device device; private readonly IDXGIOutputDuplication duplication; - private Point p; - - private Rectangle screenRect; - public Rectangle Rect => screenRect; - private readonly bool windowedMode; - private readonly int screenBytesToCopy; // IAddonDataProvider - private readonly Rectangle addonRect; - private readonly Bitmap addonBitmap; - private readonly DataFrame[] frames; + private Rectangle addonRect; + private DataFrame[] frames = null!; + private Image addonImage = null!; - public int[] Data { get; private init; } + public int[] Data { get; private set; } = Array.Empty(); public StringBuilder TextBuilder { get; } = new(3); public WowScreenDXGI(ILogger logger, @@ -90,39 +85,16 @@ public WowScreenDXGI(ILogger logger, { this.logger = logger; this.wowProcess = wowProcess; - this.frames = frames; - - this.frames = frames; - - Data = new int[frames.Length]; - - for (int i = 0; i < frames.Length; i++) - { - addonRect.Width = Math.Max(addonRect.Width, frames[i].X); - addonRect.Height = Math.Max(addonRect.Height, frames[i].Y); - } - addonRect.Width++; - addonRect.Height++; - addonBitmap = - new(addonRect.Right, addonRect.Bottom, PixelFormat.Format32bppRgb); + Bgra32Size = Unsafe.SizeOf(); GetRectangle(out screenRect); - p = screenRect.Location; windowedMode = IsWindowedMode(screenRect.Location); - screenBytesToCopy = windowedMode - ? (screenRect.Right - screenRect.Left) * 4 - : (screenRect.Right - screenRect.Left) * 4 * screenRect.Bottom; - - Bitmap = - new(screenRect.Width, screenRect.Height, PixelFormat.Format32bppPArgb); - Lock = new(); + ScreenImage = new(ContiguousJpegConfiguration, screenRect.Width, screenRect.Height); MiniMapRect = new(0, 0, MiniMapSize, MiniMapSize); - MiniMapBitmap = new(MiniMapSize, MiniMapSize, PixelFormat.Format32bppPArgb); - minimapBytesToCopy = (MiniMapRect.Right - MiniMapRect.Left) * 4; - MiniMapLock = new(); + MiniMapImage = new(ContiguousJpegConfiguration, MiniMapSize, MiniMapSize); IntPtr hMonitor = MonitorFromWindow(wowProcess.Process.MainWindowHandle, MONITOR_DEFAULT_TO_NULL); @@ -169,20 +141,7 @@ public WowScreenDXGI(ILogger logger, }; screenTexture = device.CreateTexture2D(screenTextureDesc); - Texture2DDescription addonTextureDesc = new() - { - CPUAccessFlags = CpuAccessFlags.Read, - BindFlags = BindFlags.None, - Format = Format.B8G8R8A8_UNorm, - Width = addonRect.Right, - Height = addonRect.Bottom, - MiscFlags = ResourceOptionFlags.None, - MipLevels = 1, - ArraySize = 1, - SampleDescription = { Count = 1, Quality = 0 }, - Usage = ResourceUsage.Staging - }; - addonTexture = device.CreateTexture2D(addonTextureDesc); + InitFrames(frames); Texture2DDescription miniMapTextureDesc = new() { @@ -217,21 +176,64 @@ public void Dispose() adapter.Dispose(); output1.Dispose(); output.Dispose(); + } + + public void InitFrames(DataFrame[] frames) + { + this.frames = frames; + Data = new int[frames.Length]; + + addonRect = new(); + for (int i = 0; i < frames.Length; i++) + { + addonRect.Width = Math.Max(addonRect.Width, frames[i].X); + addonRect.Height = Math.Max(addonRect.Height, frames[i].Y); + } + addonRect.Width++; + addonRect.Height++; + + addonImage = new(ContiguousJpegConfiguration, addonRect.Right, addonRect.Bottom); - addonBitmap.Dispose(); - Bitmap.Dispose(); + Texture2DDescription addonTextureDesc = new() + { + CPUAccessFlags = CpuAccessFlags.Read, + BindFlags = BindFlags.None, + Format = Format.B8G8R8A8_UNorm, + Width = addonRect.Right, + Height = addonRect.Bottom, + MiscFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Staging + }; + + addonTexture?.Dispose(); + addonTexture = device.CreateTexture2D(addonTextureDesc); + + logger.LogDebug($"DataFrames {frames.Length} - Texture: {addonRect.Width}x{addonRect.Height}"); } + [SkipLocalsInit] public void Update() { if (windowedMode) + { GetRectangle(out screenRect); + // TODO: bounds check + if (screenRect.X < 0 || + screenRect.Y < 0 || + screenRect.Right > output.Description.DesktopCoordinates.Right || + screenRect.Bottom > output.Description.DesktopCoordinates.Bottom) + return; + } + duplication.ReleaseFrame(); Result result = duplication.AcquireNextFrame(0, out OutduplFrameInfo frame, - out IDXGIResource resource); + out IDXGIResource idxgiResource); // If only the pointer was updated(that is, the desktop image was not updated), // the AccumulatedFrames, TotalMetadataBufferSize, LastPresentTime members are set to zero. @@ -243,93 +245,124 @@ public void Update() return; } - ID3D11Texture2D texture = resource.QueryInterface(); + ID3D11Texture2D texture + = idxgiResource.QueryInterface(); + + UpdateAddonImage(texture); - // Addon + if (Enabled) + UpdateScreenImage(texture); + + if (MinimapEnabled) + UpdateMinimapImage(texture); + + texture.Dispose(); + } - Box box = new(p.X, p.Y, 0, p.X + addonRect.Right, p.Y + addonRect.Bottom, 1); + [SkipLocalsInit] + private void UpdateAddonImage(ID3D11Texture2D texture) + { + Box box = new(screenRect.X, screenRect.Y, 0, screenRect.X + addonRect.Right, screenRect.Y + addonRect.Bottom, 1); device.ImmediateContext.CopySubresourceRegion(addonTexture, 0, 0, 0, 0, texture, 0, box); - MappedSubresource dataBoxAddon = device.ImmediateContext.Map(addonTexture, 0, MapMode.Read, Vortice.Direct3D11.MapFlags.None); - int sizeInBytesToCopy = (addonRect.Right - addonRect.Left) * AddonDataProviderConfig.BYTES_PER_PIXEL; + MappedSubresource resource = + device.ImmediateContext.Map(addonTexture, 0, MapMode.Read, Vortice.Direct3D11.MapFlags.None); + + int width = addonRect.Width; + int bytesToCopy = width * Bgra32Size; + nint dataPointer = resource.DataPointer; + int rowPitch = resource.RowPitch; - BitmapData bdAddon = addonBitmap.LockBits(addonRect, ImageLockMode.WriteOnly, addonBitmap.PixelFormat); - for (int y = 0; y < addonRect.Bottom; y++) + if (addonImage.DangerousTryGetSinglePixelMemory(out Memory memory) && memory.Length > 1) { - MemoryHelpers.CopyMemory(bdAddon.Scan0 + y * bdAddon.Stride, dataBoxAddon.DataPointer + y * dataBoxAddon.RowPitch, sizeInBytesToCopy); + unsafe + { + for (int y = 0; y < addonRect.Height; y++) + { + ReadOnlySpan src = new((dataPointer + (y * rowPitch)).ToPointer(), bytesToCopy); + var dest = memory.Span.Slice(y * width, width).GetPointerUnsafe(); + MemoryHelpers.CopyMemory(dest, src); + } + } } - device.ImmediateContext.Unmap(addonTexture, 0); - //bitmap.Save($"bitmap.bmp", ImageFormat.Bmp); - //System.Threading.Thread.Sleep(1000); + device.ImmediateContext.Unmap(addonTexture, 0); + } - if (frames.Length > 0) - IAddonDataProvider.InternalUpdate(bdAddon, frames, Data); + [SkipLocalsInit] + private void UpdateScreenImage(ID3D11Texture2D texture) + { + Box box = new(screenRect.X, screenRect.Y, 0, screenRect.Right, screenRect.Bottom, 1); + device.ImmediateContext.CopySubresourceRegion(screenTexture, 0, 0, 0, 0, texture, 0, box); - addonBitmap.UnlockBits(bdAddon); + MappedSubresource resource = device.ImmediateContext.Map(screenTexture, 0, MapMode.Read, Vortice.Direct3D11.MapFlags.None); - // Screen + int width = screenRect.Width; + int bytesToCopy = width * Bgra32Size; + nint dataPointer = resource.DataPointer; + int rowPitch = resource.RowPitch; - if (DateTime.UtcNow > lastScreenUpdate.AddMilliseconds(screenshotTickMs)) + if (ScreenImage.DangerousTryGetSinglePixelMemory(out Memory memory)) { - box = new(p.X, p.Y, 0, p.X + screenRect.Right, p.Y + screenRect.Bottom, 1); - device.ImmediateContext.CopySubresourceRegion(screenTexture, 0, 0, 0, 0, texture, 0, box); - - MappedSubresource dataBoxScreen = device.ImmediateContext.Map(screenTexture, 0, MapMode.Read, Vortice.Direct3D11.MapFlags.None); - - lock (Lock) + if (!windowedMode) { - BitmapData bdScreen = Bitmap.LockBits(screenRect, ImageLockMode.WriteOnly, Bitmap.PixelFormat); - if (windowedMode) + unsafe { - for (int y = 0; y < screenRect.Bottom; y++) - { - MemoryHelpers.CopyMemory(bdScreen.Scan0 + y * bdScreen.Stride, dataBoxScreen.DataPointer + y * dataBoxScreen.RowPitch, screenBytesToCopy); - } + ReadOnlySpan src + = new(dataPointer.ToPointer(), screenRect.Height * bytesToCopy); + MemoryHelpers.CopyMemory(memory.Span.GetPointerUnsafe(), src); } - else + } + else + { + unsafe { - MemoryHelpers.CopyMemory(bdScreen.Scan0, dataBoxScreen.DataPointer, screenBytesToCopy); + for (int y = 0; y < screenRect.Height; y++) + { + ReadOnlySpan src = new((dataPointer + (y * rowPitch)).ToPointer(), bytesToCopy); + var dest = memory.Span.Slice(y * width, width).GetPointerUnsafe(); + MemoryHelpers.CopyMemory(dest, src); + } } - - device.ImmediateContext.Unmap(screenTexture, 0); - - Bitmap.UnlockBits(bdScreen); } - - lastScreenUpdate = DateTime.UtcNow; } + } + + [SkipLocalsInit] + private void UpdateMinimapImage(ID3D11Texture2D texture) + { + Box box = new(screenRect.Right - MiniMapSize, screenRect.Y, 0, screenRect.Right, screenRect.Top + MiniMapRect.Bottom, 1); + device.ImmediateContext.CopySubresourceRegion(minimapTexture, 0, 0, 0, 0, texture, 0, box); - // Minimap + MappedSubresource resource = device.ImmediateContext.Map(minimapTexture, 0, MapMode.Read, Vortice.Direct3D11.MapFlags.None); - if (DateTime.UtcNow > lastMinimapUpdate.AddMilliseconds(miniMapTickMs)) + if (MiniMapImage.DangerousTryGetSinglePixelMemory(out Memory memory)) { - box = new(p.X + screenRect.Right - MiniMapSize, p.Y, 0, p.X + MiniMapRect.Right, p.Y + MiniMapRect.Bottom, 1); - device.ImmediateContext.CopySubresourceRegion(minimapTexture, 0, 0, 0, 0, texture, 0, box); - - MappedSubresource dataBoxMap = device.ImmediateContext.Map(minimapTexture, 0, MapMode.Read, Vortice.Direct3D11.MapFlags.None); + int width = MiniMapRect.Width; + int bytesToCopy = width * Bgra32Size; + nint dataPointer = resource.DataPointer; + int rowPitch = resource.RowPitch; - lock (MiniMapLock) + unsafe { - BitmapData bdMap = MiniMapBitmap.LockBits(MiniMapRect, ImageLockMode.WriteOnly, MiniMapBitmap.PixelFormat); - for (int y = 0; y < MiniMapRect.Bottom; y++) + for (int y = 0; y < MiniMapRect.Height; y++) { - MemoryHelpers.CopyMemory(bdMap.Scan0 + y * bdMap.Stride, dataBoxMap.DataPointer + y * dataBoxMap.RowPitch, minimapBytesToCopy); + ReadOnlySpan src = new((dataPointer + (y * rowPitch)).ToPointer(), bytesToCopy); + var dest = memory.Span.Slice(y * width, width).GetPointerUnsafe(); + MemoryHelpers.CopyMemory(dest, src); } - - device.ImmediateContext.Unmap(screenTexture, 0); - - MiniMapBitmap.UnlockBits(bdMap); } - - lastMinimapUpdate = DateTime.UtcNow; } - texture.Dispose(); + + device.ImmediateContext.Unmap(screenTexture, 0); } public void UpdateData() { + if (frames.Length <= 2) + return; + IAddonDataProvider.InternalUpdate(addonImage, frames, Data); } public void PostProcess() @@ -346,56 +379,4 @@ public void GetRectangle(out Rectangle rect) { NativeMethods.GetWindowRect(wowProcess.Process.MainWindowHandle, out rect); } - - - public Bitmap GetBitmap(int width, int height) - { - Update(); - - Bitmap bitmap = new(width, height); - Rectangle sourceRect = new(0, 0, width, height); - - using Graphics graphics = Graphics.FromImage(bitmap); - lock (Lock) - { - graphics.DrawImage(Bitmap, 0, 0, sourceRect, GraphicsUnit.Pixel); - } - - return bitmap; - } - - public void DrawBitmapTo(Graphics graphics) - { - lock (Lock) - { - graphics.DrawImage(Bitmap, 0, 0, screenRect, GraphicsUnit.Pixel); - } - - GetCursorPos(out Point cursorPoint); - GetRectangle(out Rectangle windowRect); - - if (!windowRect.Contains(cursorPoint)) - return; - - CURSORINFO cursorInfo = new(); - cursorInfo.cbSize = Marshal.SizeOf(cursorInfo); - if (GetCursorInfo(ref cursorInfo) && - cursorInfo.flags == CURSOR_SHOWING) - { - DrawIcon(graphics.GetHdc(), - cursorPoint.X, cursorPoint.Y, cursorInfo.hCursor); - - graphics.ReleaseHdc(); - } - } - - public System.Drawing.Color GetColorAt(Point point) - { - return Bitmap.GetPixel(point.X, point.Y); - } - - public void UpdateMinimapBitmap() - { - // not used - } } \ No newline at end of file diff --git a/CoreTests/Input/Test_Input.cs b/CoreTests/Input/Test_Input.cs index ab46ed6a6..1d46e1d3b 100644 --- a/CoreTests/Input/Test_Input.cs +++ b/CoreTests/Input/Test_Input.cs @@ -1,5 +1,5 @@ using System; -using System.Drawing; +using SixLabors.ImageSharp; using Game; using Microsoft.Extensions.Logging; using System.Threading; @@ -17,13 +17,14 @@ public class Test_Input : IDisposable private readonly IWowScreen wowScreen; private readonly WowProcessInput wowProcessInput; - public Test_Input(ILogger logger, ILoggerFactory loggerFactory) + public Test_Input(ILogger logger, WowProcess wowProcess, IWowScreen wowScreen, ILoggerFactory loggerFactory) { this.logger = logger; + this.wowProcess = wowProcess; + this.wowScreen = wowScreen; + this.cts = new(); - wowProcess = new WowProcess(); - wowScreen = new WowScreenGDI(loggerFactory.CreateLogger(), wowProcess); wowProcessInput = new(loggerFactory.CreateLogger(), cts, wowProcess); } diff --git a/CoreTests/MinimapNodeFinder/Test_MinimapNodeFinder.cs b/CoreTests/MinimapNodeFinder/Test_MinimapNodeFinder.cs index 85b0cf908..5cf2a3a1d 100644 --- a/CoreTests/MinimapNodeFinder/Test_MinimapNodeFinder.cs +++ b/CoreTests/MinimapNodeFinder/Test_MinimapNodeFinder.cs @@ -7,6 +7,8 @@ using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; + #pragma warning disable 0162 #pragma warning disable 8618 @@ -28,12 +30,12 @@ internal sealed class Test_MinimapNodeFinder : IDisposable private readonly Stopwatch stopwatch = new(); - public Test_MinimapNodeFinder(ILogger logger, ILoggerFactory loggerFactory) + public Test_MinimapNodeFinder(ILogger logger, WowProcess wowProcess, IWowScreen wowScreen, ILoggerFactory loggerFactory) { this.logger = logger; - wowProcess = new(); - wowScreen = new WowScreenGDI(loggerFactory.CreateLogger(), wowProcess); + this.wowProcess = wowProcess; + this.wowScreen = wowScreen; minimapNodeFinder = new(logger, wowScreen); } @@ -49,7 +51,7 @@ public void Execute() if (LogEachUpdate) stopwatch.Restart(); - wowScreen.UpdateMinimapBitmap(); + wowScreen.Update(); if (LogEachUpdate) logger.LogInformation($"Capture: {stopwatch.ElapsedMilliseconds}ms"); @@ -70,6 +72,6 @@ public void Execute() private void SaveImage() { - wowScreen.MiniMapBitmap.Save("minimap.png"); + wowScreen.MiniMapImage.SaveAsPng("minimap.png"); } } diff --git a/CoreTests/NpcNameFinder/MockWoWProcess.cs b/CoreTests/NpcNameFinder/MockWoWProcess.cs index 8d74ae874..c960178df 100644 --- a/CoreTests/NpcNameFinder/MockWoWProcess.cs +++ b/CoreTests/NpcNameFinder/MockWoWProcess.cs @@ -1,4 +1,4 @@ -using System.Drawing; +using SixLabors.ImageSharp; using System.Threading; using Game; diff --git a/CoreTests/NpcNameFinder/MockWoWScreen.cs b/CoreTests/NpcNameFinder/MockWoWScreen.cs index d6481f4d5..74e2c03de 100644 --- a/CoreTests/NpcNameFinder/MockWoWScreen.cs +++ b/CoreTests/NpcNameFinder/MockWoWScreen.cs @@ -1,10 +1,9 @@ using Game; -using SharedLib; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; using System; -using System.Drawing; -using System.Threading; namespace CoreTests; @@ -12,36 +11,24 @@ internal sealed class MockWoWScreen : IWowScreen { public bool Enabled { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public Bitmap Bitmap => throw new NotImplementedException(); - - public Rectangle Rect => throw new NotImplementedException(); + public Rectangle ScreenRect => throw new NotImplementedException(); public nint ProcessHwnd => throw new NotImplementedException(); - public Bitmap MiniMapBitmap => throw new NotImplementedException(); - public Rectangle MiniMapRect => throw new NotImplementedException(); public bool EnablePostProcess { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public object Lock => throw new NotImplementedException(); + public Image ScreenImage => throw new NotImplementedException(); + + public Image MiniMapImage => throw new NotImplementedException(); - public object MiniMapLock => throw new NotImplementedException(); + public bool MinimapEnabled { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } #pragma warning disable CS0067 // The event 'MockWoWScreen.OnScreenChanged' is never used public event Action OnScreenChanged; #pragma warning restore CS0067 // The event 'MockWoWScreen.OnScreenChanged' is never used - public Bitmap GetBitmap(int width, int height) - { - throw new NotImplementedException(); - } - - public Color GetColorAt(Point point) - { - throw new NotImplementedException(); - } - public void GetPosition(ref Point point) { throw new NotImplementedException(); @@ -54,18 +41,11 @@ public void GetRectangle(out Rectangle rect) public void Update() { } - public void UpdateMinimapBitmap() { } - - public void Dispose() - { - } - - public void DrawBitmapTo(Graphics g) + public void PostProcess() { throw new NotImplementedException(); } - - public void PostProcess() + public void Dispose() { throw new NotImplementedException(); } diff --git a/CoreTests/NpcNameFinder/MockWowProcessInput.cs b/CoreTests/NpcNameFinder/MockWowProcessInput.cs index 587282fd7..fb6fc5c1c 100644 --- a/CoreTests/NpcNameFinder/MockWowProcessInput.cs +++ b/CoreTests/NpcNameFinder/MockWowProcessInput.cs @@ -1,5 +1,5 @@ using System; -using System.Drawing; +using SixLabors.ImageSharp; using System.Threading; using Game; diff --git a/CoreTests/NpcNameFinder/RectProvider.cs b/CoreTests/NpcNameFinder/RectProvider.cs index afb3e0b00..89782269a 100644 --- a/CoreTests/NpcNameFinder/RectProvider.cs +++ b/CoreTests/NpcNameFinder/RectProvider.cs @@ -1,5 +1,6 @@ using SharedLib; -using System.Drawing; + +using SixLabors.ImageSharp; namespace CoreTests; diff --git a/CoreTests/NpcNameFinder/Test_NpcNameFinder.cs b/CoreTests/NpcNameFinder/Test_NpcNameFinder.cs index 7ceb40e9e..3883cf43c 100644 --- a/CoreTests/NpcNameFinder/Test_NpcNameFinder.cs +++ b/CoreTests/NpcNameFinder/Test_NpcNameFinder.cs @@ -1,4 +1,3 @@ -using System.Drawing; using Core.Goals; using SharedLib.NpcFinder; using Microsoft.Extensions.Logging; @@ -23,7 +22,7 @@ internal sealed class Test_NpcNameFinder : IDisposable private const bool saveImage = false; private const bool showOverlay = true; - private const bool LogEachUpdate = true; + private const bool LogEachUpdate = false; private const bool LogEachDetail = false; private const bool debugTargeting = false; @@ -35,37 +34,20 @@ internal sealed class Test_NpcNameFinder : IDisposable private readonly NpcNameTargeting npcNameTargeting; private readonly NpcNameTargetingLocations locations; - private readonly WowProcess wowProcess; private readonly IWowScreen wowScreen; private readonly Stopwatch stopwatch = new(); private readonly StringBuilder stringBuilder = new(); - private readonly Graphics paint; - private readonly Bitmap paintBitmap; - private readonly Font font = new("Arial", 10); - private readonly SolidBrush brush = new(Color.White); - private readonly Pen whitePen = new(Color.White, 1); - private readonly NpcNameOverlay? npcNameOverlay; private DateTime lastNpcUpdate; private double updateDuration; - public Test_NpcNameFinder(ILogger logger, ILoggerFactory loggerFactory, NpcNames types) + public Test_NpcNameFinder(ILogger logger, WowProcess wowProcess, IWowScreen wowScreen, ILoggerFactory loggerFactory, NpcNames types) { this.logger = logger; - - // its expected to have at least 2 DataFrame - DataFrame[] mockFrames = new DataFrame[2] - { - new DataFrame(0, 0, 0), - new DataFrame(1, 0, 0), - }; - - wowProcess = new(); - wowScreen = new WowScreenDXGI(loggerFactory.CreateLogger(), wowProcess, mockFrames); - //wowScreen = new WowScreenGDI(loggerFactory.CreateLogger(), wowProcess); + this.wowScreen = wowScreen; INpcResetEvent npcResetEvent = new NpcResetEvent(); npcNameFinder = new(logger, wowScreen, npcResetEvent); @@ -74,19 +56,19 @@ public Test_NpcNameFinder(ILogger logger, ILoggerFactory loggerFactory, NpcNames MockMouseOverReader mouseOverReader = new(); MockGameMenuWindowShown gmws = new(); - MockWowProcessInput wpi = new(); + + Wait wait = new(new(false), new()); + + WowProcessInput input = new(loggerFactory.CreateLogger(), new(), wowProcess); npcNameTargeting = new(loggerFactory.CreateLogger(), - new(), wowScreen, npcNameFinder, locations, wpi, - mouseOverReader, new NoBlacklist(), null!, gmws); + new(), wowScreen, npcNameFinder, locations, input, + mouseOverReader, new NoBlacklist(), wait, gmws); npcNameFinder.ChangeNpcType(types); - paintBitmap = wowScreen.Bitmap; - paint = Graphics.FromImage(paintBitmap); - if (showOverlay) - npcNameOverlay = new(wowProcess.Process.MainWindowHandle, npcNameFinder, + npcNameOverlay = new(wowScreen.ProcessHwnd, npcNameFinder, locations, debugTargeting, debugSkinning, debugTargetVsAdd); } @@ -98,9 +80,6 @@ public Test_NpcNameFinder(ILogger logger, ILoggerFactory loggerFactory, NpcNames public void Dispose() { npcNameOverlay?.Dispose(); - - wowScreen.Dispose(); - wowProcess.Dispose(); } public void UpdateScreen() @@ -143,7 +122,7 @@ public void UpdateScreen() if (saveImage) { - SaveImage(); + // TODO: save image } if (LogEachUpdate && LogEachDetail) @@ -171,49 +150,4 @@ public bool Execute_FindTargetBy(ReadOnlySpan cursorType) { return npcNameTargeting.FindBy(cursorType, CancellationToken.None); } - - private void SaveImage() - { - if (npcNameFinder.Npcs.Count <= 0) - return; - - paint.DrawRectangle(whitePen, npcNameFinder.Area); - - for (int i = 0; i < npcNameFinder.Npcs.Count; i++) - { - NpcPosition npc = npcNameFinder.Npcs[i]; - - if (debugTargeting) - { - foreach (var l in locations.Targeting) - { - paint.DrawEllipse(whitePen, l.X + npc.ClickPoint.X, l.Y + npc.ClickPoint.Y, 5, 5); - } - } - - if (debugSkinning) - { - int c = locations.FindBy.Length; - const int e = 3; - Point[] attempts = new Point[c + (c * e)]; - for (int j = 0; j < c; j += e) - { - Point p = locations.FindBy[j]; - attempts[j] = p; - attempts[j + c] = new Point(npc.Rect.Width / 2, p.Y).Scale(npcNameFinder.ScaleToRefWidth, npcNameFinder.ScaleToRefHeight); - attempts[j + c + 1] = new Point(-npc.Rect.Width / 2, p.Y).Scale(npcNameFinder.ScaleToRefWidth, npcNameFinder.ScaleToRefHeight); - } - - foreach (var l in attempts) - { - paint.DrawEllipse(whitePen, l.X + npc.ClickPoint.X, l.Y + npc.ClickPoint.Y, 5, 5); - } - } - - paint.DrawRectangle(whitePen, npc.Rect); - paint.DrawString(i.ToString(), font, brush, new PointF(npc.Rect.Left - 20f, npc.Rect.Top)); - } - - paintBitmap.Save("target_names.png"); - } } diff --git a/CoreTests/Program.cs b/CoreTests/Program.cs index 83a6b5105..3f37aa305 100644 --- a/CoreTests/Program.cs +++ b/CoreTests/Program.cs @@ -8,16 +8,20 @@ using Core; using System; using Microsoft.Extensions.Logging; +using Game; #pragma warning disable 0162 namespace CoreTests; -sealed class Program +internal sealed class Program { private static Microsoft.Extensions.Logging.ILogger logger; private static ILoggerFactory loggerFactory; + private static WowProcess wowProcess; + private static IWowScreen wowScreen; + private const bool LogOverallTimes = false; private const int delay = 150; @@ -36,12 +40,24 @@ public static void Main() builder.ClearProviders().AddSerilog(); }); + // its expected to have at least 2 DataFrame + DataFrame[] mockFrames = new DataFrame[2] + { + new DataFrame(0, 0, 0), + new DataFrame(1, 0, 0), + }; + + wowProcess = new(); + wowScreen = new WowScreenDXGI(loggerFactory.CreateLogger(), wowProcess, mockFrames); + Test_NPCNameFinder(); //Test_Input(); //Test_CursorGrabber(); //Test_CursorCompare(); //Test_MinimapNodeFinder(); //Test_FindTargetByCursor(); + + Environment.Exit(0); } private static void Test_NPCNameFinder() @@ -53,7 +69,7 @@ private static void Test_NPCNameFinder() //NpcNames types = NpcNames.Enemy | NpcNames.Neutral | NpcNames.NamePlate; //NpcNames types = NpcNames.Friendly | NpcNames.Neutral; - using Test_NpcNameFinder test = new(logger, loggerFactory, types); + using Test_NpcNameFinder test = new(logger, wowProcess, wowScreen, loggerFactory, types); int count = 100; int i = 0; @@ -76,7 +92,6 @@ private static void Test_NPCNameFinder() sample[i] = Stopwatch.GetElapsedTime(timestamp).TotalMilliseconds; i++; - //Thread.Sleep(delay); Thread.Sleep(4); } @@ -102,7 +117,7 @@ private static void Test_NPCNameFinder() private static void Test_Input() { - Test_Input test = new(logger, loggerFactory); + Test_Input test = new(logger, wowProcess, wowScreen, loggerFactory); test.Mouse_Movement(); test.Mouse_Clicks(); test.Clipboard(); @@ -140,7 +155,7 @@ private static void Test_CursorCompare() classifier.Classify(out CursorType cursorType, out double similarity); times[i] = Stopwatch.GetElapsedTime(startTime).TotalMilliseconds; - //Log.Logger.Information($"{cursorType.ToStringF()} {similarity} {times[i]:F6}ms"); + Log.Logger.Information($"{cursorType.ToStringF()} {similarity} {times[i]:F6}ms"); i++; } @@ -161,7 +176,7 @@ private static void Test_CursorCompare() private static void Test_MinimapNodeFinder() { - using Test_MinimapNodeFinder test = new(logger, loggerFactory); + using Test_MinimapNodeFinder test = new(logger, wowProcess, wowScreen, loggerFactory); int count = 100; int i = 0; @@ -199,7 +214,7 @@ private static void Test_FindTargetByCursor() //NpcNames types = NpcNames.Enemy | NpcNames.Neutral; NpcNames types = NpcNames.Friendly | NpcNames.Neutral; - using Test_NpcNameFinder test = new(logger, loggerFactory, types); + using Test_NpcNameFinder test = new(logger, wowProcess, wowScreen, loggerFactory, types); int count = 2; int i = 0; diff --git a/Frontend/Pages/FrameConfiguration.razor b/Frontend/Pages/FrameConfiguration.razor index 1c0e59146..4864055cc 100644 --- a/Frontend/Pages/FrameConfiguration.razor +++ b/Frontend/Pages/FrameConfiguration.razor @@ -100,11 +100,11 @@
@if (@frameConfigurator.DataFrames.Length != frameConfigurator.DataFrameMeta.frames) { - Red dot + Red dot } else { - Red dot + Red dot }
@@ -170,7 +170,7 @@ addonConfig = addonConfigurator.Config; frameConfigurator.OnUpdate += OnUpdate; - if (reader.GetType() == typeof(AddonDataProviderGDIConfig)) + if (!FrameConfig.Exists()) disabled = false; } diff --git a/Frontend/Pages/MiniMapComponent.razor b/Frontend/Pages/MiniMapComponent.razor index 3e43aa31f..3b20ab801 100644 --- a/Frontend/Pages/MiniMapComponent.razor +++ b/Frontend/Pages/MiniMapComponent.razor @@ -1,13 +1,14 @@ @implements IDisposable @inject IBotController botController -@inject IMinimapBitmapProvider provider +@inject IMinimapImageProvider provider @inject IWowScreen wowScreen @inject MinimapNodeFinder minimapNodeFinder -@using System.Drawing -@using System.Drawing.Drawing2D; +@using SixLabors.ImageSharp +@using SixLabors.ImageSharp.PixelFormats +@using SixLabors.ImageSharp.Formats.Jpeg
@@ -24,16 +25,16 @@
- @if (inited && previewEnabled) + @if (previewEnabled) { if (DateTime.UtcNow > lastUpdate.AddMilliseconds(UpdateIntervalMs)) { - image = WoWScreenUtil.ToBase64(provider, bitmap, graphics); + image = provider.MiniMapImage.ToBase64String(JpegFormat.Instance); if (image != "") lastUpdate = DateTime.UtcNow; } - Red dot + Red dot }
@@ -54,26 +55,16 @@ private bool lastNodeFound = false; private int lastNodeCount = 0; - private bool inited = false; private bool preview = false; private bool previewEnabled { get => preview; set { - if (!inited) - { - inited = true; - InitBitmap(); - } - preview = value; } } - private Graphics? graphics; - private Bitmap? bitmap; - private const int UpdateIntervalMs = 500; private DateTime lastUpdate; @@ -102,44 +93,16 @@ { minimapNodeFinder.NodeEvent -= NodeFound; } - - graphics?.Dispose(); - bitmap?.Dispose(); } private async void ImageChanged() { - if (inited && previewEnabled) + if (previewEnabled) { await base.InvokeAsync(StateHasChanged); } } - private void InitBitmap() - { - int width; - int height; - - int size = (int)(Size * DownScale); - if (provider.MiniMapBitmap.Width > provider.MiniMapBitmap.Height) - { - width = size; - height = Convert.ToInt32(provider.MiniMapBitmap.Height * size / (float)provider.MiniMapBitmap.Width); - } - else - { - width = Convert.ToInt32(provider.MiniMapBitmap.Width * size / (float)provider.MiniMapBitmap.Height); - height = size; - } - - bitmap = new Bitmap(width, height); - - graphics = Graphics.FromImage(bitmap); - graphics.CompositingQuality = CompositingQuality.HighSpeed; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.CompositingMode = CompositingMode.SourceCopy; - } - private void NodeFound(object? source, MinimapNodeEventArgs e) { if (!botController.IsBotActive) return; diff --git a/Frontend/Pages/ScreenshotComponent.razor b/Frontend/Pages/ScreenshotComponent.razor index 092fbc5fe..0e7e91d08 100644 --- a/Frontend/Pages/ScreenshotComponent.razor +++ b/Frontend/Pages/ScreenshotComponent.razor @@ -1,13 +1,14 @@ @inject IWowScreen wowScreen -@inject IBitmapProvider provider +@inject IScreenImageProvider provider -@using System.Drawing -@using System.Drawing.Drawing2D; +@using SixLabors.ImageSharp +@using SixLabors.ImageSharp.PixelFormats +@using SixLabors.ImageSharp.Formats.Jpeg
- @if (inited && wowScreen.EnablePostProcess) + @if (wowScreen.EnablePostProcess) { - Toggle preview! + Toggle preview! } else { @@ -23,11 +24,6 @@ [Parameter] public bool Stretch { get; set; } = false; - private Bitmap? bitmap; - private Graphics? graphics; - - private bool inited = false; - protected override void OnAfterRender(bool firstRender) { if (firstRender) @@ -39,46 +35,15 @@ public void Dispose() { wowScreen.OnScreenChanged -= this.ImageChanged; - - graphics?.Dispose(); - bitmap?.Dispose(); } protected void Toggle() { - if (!inited) - { - int width; - int height; - - int size = Size; - if (wowScreen.MiniMapBitmap.Width > wowScreen.MiniMapBitmap.Height) - { - width = size; - height = Convert.ToInt32(wowScreen.MiniMapBitmap.Height * size / (float)wowScreen.MiniMapBitmap.Width); - } - else - { - width = Convert.ToInt32(wowScreen.MiniMapBitmap.Width * size / (float)wowScreen.MiniMapBitmap.Height); - height = size; - } - - bitmap = new Bitmap(width, height); - - graphics = Graphics.FromImage(bitmap); - graphics.CompositingQuality = CompositingQuality.HighSpeed; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.CompositingMode = CompositingMode.SourceCopy; - - inited = true; - } - wowScreen.EnablePostProcess = !wowScreen.EnablePostProcess; } private async void ImageChanged() { - if (inited) - await base.InvokeAsync(StateHasChanged); + await base.InvokeAsync(StateHasChanged); } } \ No newline at end of file diff --git a/Game/Input/IInput.cs b/Game/Input/IInput.cs index 14ac7f95a..63f39606e 100644 --- a/Game/Input/IInput.cs +++ b/Game/Input/IInput.cs @@ -1,4 +1,5 @@ -using System.Drawing; +using SixLabors.ImageSharp; + using System.Threading; namespace Game; diff --git a/Game/Input/IMouseInput.cs b/Game/Input/IMouseInput.cs index da463c076..961677d80 100644 --- a/Game/Input/IMouseInput.cs +++ b/Game/Input/IMouseInput.cs @@ -1,4 +1,4 @@ -using System.Drawing; +using SixLabors.ImageSharp; using System.Threading; namespace Game; diff --git a/Game/Input/InputSimulator.cs b/Game/Input/InputSimulator.cs index 03a7316d7..03734f146 100644 --- a/Game/Input/InputSimulator.cs +++ b/Game/Input/InputSimulator.cs @@ -1,5 +1,5 @@ using System; -using System.Drawing; +using SixLabors.ImageSharp; using System.Threading; using GregsStack.InputSimulatorStandard.Native; using static WinAPI.NativeMethods; diff --git a/Game/Input/InputWindowsNative.cs b/Game/Input/InputWindowsNative.cs index 23e0e2573..237271cb9 100644 --- a/Game/Input/InputWindowsNative.cs +++ b/Game/Input/InputWindowsNative.cs @@ -1,5 +1,5 @@ using System; -using System.Drawing; +using SixLabors.ImageSharp; using System.Threading; using TextCopy; using static WinAPI.NativeMethods; diff --git a/Game/Input/WowProcessInput.cs b/Game/Input/WowProcessInput.cs index b744d09ce..429060943 100644 --- a/Game/Input/WowProcessInput.cs +++ b/Game/Input/WowProcessInput.cs @@ -1,8 +1,9 @@ using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; + using System; using System.Collections; -using System.Drawing; using System.Threading; using WinAPI; diff --git a/Game/WoWScreen/IWowScreen.cs b/Game/WoWScreen/IWowScreen.cs index 14e50d9df..77f9db659 100644 --- a/Game/WoWScreen/IWowScreen.cs +++ b/Game/WoWScreen/IWowScreen.cs @@ -1,14 +1,15 @@ using System; -using System.Drawing; using SharedLib; namespace Game; -public interface IWowScreen : IColorReader, IRectProvider, IBitmapProvider, IMinimapBitmapProvider, IDisposable +public interface IWowScreen : IRectProvider, IScreenImageProvider, IMinimapImageProvider, IDisposable { bool Enabled { get; set; } + bool MinimapEnabled { get; set; } + IntPtr ProcessHwnd { get; } bool EnablePostProcess { get; set; } @@ -17,8 +18,4 @@ public interface IWowScreen : IColorReader, IRectProvider, IBitmapProvider, IMin event Action OnScreenChanged; void Update(); - - void UpdateMinimapBitmap(); - - void DrawBitmapTo(Graphics g); } diff --git a/Game/WoWScreen/WoWScreenUtil.cs b/Game/WoWScreen/WoWScreenUtil.cs deleted file mode 100644 index 06c459fff..000000000 --- a/Game/WoWScreen/WoWScreenUtil.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Drawing.Imaging; -using System.Drawing; -using System.IO; -using SharedLib; - -namespace Game; - -public static class WoWScreenUtil -{ - public static string ToBase64(IBitmapProvider provider, Bitmap resized, Graphics graphics) - { - lock (provider.Lock) - { - graphics.DrawImage(provider.Bitmap, 0, 0, resized.Width, resized.Height); - } - - using MemoryStream ms = new(); - resized.Save(ms, ImageFormat.Png); - - byte[] byteImage = ms.ToArray(); - return Convert.ToBase64String(byteImage); - } - - public static string ToBase64(IMinimapBitmapProvider provider, Bitmap resized, Graphics graphics) - { - lock (provider.MiniMapLock) - { - graphics.DrawImage(provider.MiniMapBitmap, 0, 0, resized.Width, resized.Height); - } - using MemoryStream ms = new(); - resized.Save(ms, ImageFormat.Png); - - byte[] byteImage = ms.ToArray(); - return Convert.ToBase64String(byteImage); - } - - public static string ToBase64(Bitmap bitmap, Bitmap resized, Graphics graphics) - { - graphics.DrawImage(bitmap, 0, 0, resized.Width, resized.Height); - - using MemoryStream ms = new(); - resized.Save(ms, ImageFormat.Png); - - byte[] byteImage = ms.ToArray(); - return Convert.ToBase64String(byteImage); - } -} diff --git a/Game/WoWScreen/WowScreenGDI.cs b/Game/WoWScreen/WowScreenGDI.cs deleted file mode 100644 index d69e0cff2..000000000 --- a/Game/WoWScreen/WowScreenGDI.cs +++ /dev/null @@ -1,150 +0,0 @@ -using Microsoft.Extensions.Logging; - -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Runtime.InteropServices; -using System.Threading; - -using WinAPI; - -using static WinAPI.NativeMethods; - -namespace Game; - -public sealed class WowScreenGDI : IWowScreen -{ - private readonly ILogger logger; - private readonly WowProcess wowProcess; - - public event Action OnScreenChanged; - - // TODO: make it work for higher resolution ex. 4k - public const int MinimapSize = 200; - - public bool Enabled { get; set; } - - public bool EnablePostProcess { get; set; } - public Bitmap Bitmap { get; private set; } - public object Lock { get; init; } - - public Bitmap MiniMapBitmap { get; private set; } - public Rectangle MiniMapRect { get; private set; } - public object MiniMapLock { get; init; } - - public IntPtr ProcessHwnd => wowProcess.Process.MainWindowHandle; - - private Rectangle rect; - public Rectangle Rect => rect; - - private readonly Graphics graphics; - private readonly Graphics graphicsMinimap; - - private readonly SolidBrush blackPen; - - private readonly bool windowedMode; - - public WowScreenGDI(ILogger logger, WowProcess wowProcess) - { - this.logger = logger; - this.wowProcess = wowProcess; - - GetRectangle(out rect); - windowedMode = IsWindowedMode(rect.Location); - - Bitmap = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppPArgb); - graphics = Graphics.FromImage(Bitmap); - Lock = new(); - - MiniMapBitmap = new Bitmap(MinimapSize, MinimapSize, PixelFormat.Format32bppPArgb); - graphicsMinimap = Graphics.FromImage(MiniMapBitmap); - MiniMapLock = new(); - - blackPen = new SolidBrush(Color.Black); - - logger.LogInformation($"{rect} - " + - $"Windowed Mode: {windowedMode} - " + - $"Scale: {DPI2PPI(GetDpi()):F2}"); - } - - public void Update() - { - if (windowedMode) - GetRectangle(out rect); - - graphics.CopyFromScreen(rect.Location, Point.Empty, Bitmap.Size); - } - - public void PostProcess() - { - OnScreenChanged?.Invoke(); - } - - public void GetPosition(ref Point point) - { - NativeMethods.GetPosition(wowProcess.Process.MainWindowHandle, ref point); - } - - public void GetRectangle(out Rectangle rect) - { - NativeMethods.GetWindowRect(wowProcess.Process.MainWindowHandle, out rect); - } - - - public Bitmap GetBitmap(int width, int height) - { - Update(); - - Bitmap bitmap = new(width, height); - Rectangle sourceRect = new(0, 0, width, height); - using (var graphics = Graphics.FromImage(bitmap)) - { - graphics.DrawImage(Bitmap, 0, 0, sourceRect, GraphicsUnit.Pixel); - } - return bitmap; - } - - public void DrawBitmapTo(Graphics graphics) - { - Update(); - graphics.DrawImage(Bitmap, 0, 0, rect, GraphicsUnit.Pixel); - - GetCursorPos(out Point cursorPoint); - GetRectangle(out Rectangle windowRect); - - if (!windowRect.Contains(cursorPoint)) - return; - - CURSORINFO cursorInfo = new(); - cursorInfo.cbSize = Marshal.SizeOf(cursorInfo); - if (GetCursorInfo(ref cursorInfo) && - cursorInfo.flags == CURSOR_SHOWING) - { - DrawIcon(graphics.GetHdc(), - cursorPoint.X, cursorPoint.Y, cursorInfo.hCursor); - - graphics.ReleaseHdc(); - } - } - - public Color GetColorAt(Point point) - { - return Bitmap.GetPixel(point.X, point.Y); - } - - public void UpdateMinimapBitmap() - { - GetRectangle(out var rect); - graphicsMinimap.CopyFromScreen(rect.Right - MinimapSize, rect.Top, 0, 0, MiniMapBitmap.Size); - } - - public void Dispose() - { - Bitmap.Dispose(); - graphics.Dispose(); - graphicsMinimap.Dispose(); - - blackPen.Dispose(); - } -} \ No newline at end of file diff --git a/HeadlessServer/RunOptions.cs b/HeadlessServer/RunOptions.cs index 8f185955b..a17b2a2e7 100644 --- a/HeadlessServer/RunOptions.cs +++ b/HeadlessServer/RunOptions.cs @@ -29,10 +29,8 @@ public sealed class RunOptions [Option('r', "reader", Required = false, - Default = AddonDataProviderType.GDI, + Default = AddonDataProviderType.DXGI, HelpText = $"Screen reader backend." + - $"'{nameof(AddonDataProviderType.GDI)}': is the default, compatible from Win7.\n" + - $"'{nameof(AddonDataProviderType.GDIBlit)}': GDI based works from Win7 allows background mode.\n" + $"'{nameof(AddonDataProviderType.DXGI)}': DirectX based works from Win8.")] public AddonDataProviderType Reader { get; set; } diff --git a/README.md b/README.md index b3f1c088a..3015ff574 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ # Features - Game fullscreen or windowed mode -- Limited Background mode support with `GDIBlit` reader. - Addon supports all available client languages - Most of the classes should work. Some classes have more support than others. - Highly configurable Combat rotation described in [Class Configuration](#12-class-configuration) @@ -251,16 +250,16 @@ In order to run `HeadlessServer` please look at the `HeadlessServer\run.bat`. | ---- | ---- | ---- | ---- | | `-m`
`-mode` | Pathfinder type | `RemoteV3` | `Local` or `RemoteV1` or `RemoteV3` | | `-p`
`-pid` | World of Warcraft process id | `-1` | open up task manager to find PID | -| `-r`
`-reader` | Addon data screen reader backend | `GDI` | `GDI` works from Win7.
`GDIBlit` allows background mode with a bit higher CPU usage.
`DXGI` work from Win8 | +| `-r`
`-reader` | Addon data screen reader backend | `DXGI` | `DXGI` works since Win8 | | `hostv1` | Navigation Remote V1 host | `localhost` | - | | `portv1` | Navigation Remote V1 port | `5001` | - | | `hostv3` | Navigation Remote V3 host | `127.0.0.1` | - | | `portv3` | Navigation Remote V3 port | `47111` | - | | `-d`
`-diag` | Diagnostics, when set, takes screen captures under `Json\cap\*.json` | - | - | -| `-o`
`-overlay` | Show NpcNameFinder Overlay | `false` | `true` or `false` | -| `-t`
`-otargeting` | While overlay enabled, show Targeting points | `false` | `true` or `false` | -| `-s`
`-oskinning` | While overlay enabled, show Skinning points | `false` | `true` or `false` | -| `-v`
`-otargetvsadd` | While overlay enabled, show Target vs Add points | `false` | `true` or `false` | +| `-o`
`-overlay` | Show NpcNameFinder Overlay | `false` | - | +| `-t`
`-otargeting` | While overlay enabled, show Targeting points | `false` | - | +| `-s`
`-oskinning` | While overlay enabled, show Skinning points | `false` | - | +| `-v`
`-otargetvsadd` | While overlay enabled, show Target vs Add points | `false` | - | e.g. run from Powershell without any optional parameter ```ps diff --git a/SharedLib/AddonDataProviderType/AddonDataProviderType.cs b/SharedLib/AddonDataProviderType/AddonDataProviderType.cs index 76a00c82c..a1b268ad3 100644 --- a/SharedLib/AddonDataProviderType/AddonDataProviderType.cs +++ b/SharedLib/AddonDataProviderType/AddonDataProviderType.cs @@ -2,8 +2,5 @@ public enum AddonDataProviderType { - GDI, - GDIBlit, - DXGI, - GDIConfig + DXGI } diff --git a/SharedLib/DirectBitmap/BitmapCapturer.cs b/SharedLib/DirectBitmap/BitmapCapturer.cs deleted file mode 100644 index b1694be3f..000000000 --- a/SharedLib/DirectBitmap/BitmapCapturer.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Drawing; -using System.Drawing.Imaging; - -namespace SharedLib; - -public sealed class BitmapCapturer : IBitmapProvider, IColorReader, IDisposable -{ - public Rectangle Rect { get; set; } - - public Bitmap Bitmap { get; private set; } - - public object Lock { get; init; } - - private readonly Graphics graphics; - - public BitmapCapturer(Rectangle rect) - { - this.Rect = rect; - Bitmap = new(rect.Width, rect.Height, PixelFormat.Format32bppPArgb); - graphics = Graphics.FromImage(Bitmap); - - Lock = new(); - } - - public void Capture() - { - graphics.CopyFromScreen(Rect.Location, Point.Empty, Bitmap.Size); - } - - public void Capture(Rectangle rect) - { - Rect = rect; - Capture(); - } - - public Color GetColorAt(Point point) - { - return Bitmap.GetPixel(point.X, point.Y); - } - - public Bitmap GetBitmap(int width, int height) - { - Bitmap bitmap = new Bitmap(width, height); - Rectangle sourceRect = new Rectangle(0, 0, width, height); - using (var graphics = Graphics.FromImage(bitmap)) - { - graphics.DrawImage(Bitmap, 0, 0, sourceRect, GraphicsUnit.Pixel); - } - return bitmap; - } - - public void Dispose() - { - Bitmap.Dispose(); - graphics.Dispose(); - } -} diff --git a/SharedLib/DirectBitmap/IBitmapProvider.cs b/SharedLib/DirectBitmap/IBitmapProvider.cs deleted file mode 100644 index c1768313e..000000000 --- a/SharedLib/DirectBitmap/IBitmapProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Drawing; - -namespace SharedLib; - -public interface IBitmapProvider -{ - Bitmap Bitmap { get; } - - Rectangle Rect { get; } - - object Lock { get; } -} diff --git a/SharedLib/DirectBitmap/IMinimapBitmapProvider.cs b/SharedLib/DirectBitmap/IMinimapBitmapProvider.cs deleted file mode 100644 index bed97b8db..000000000 --- a/SharedLib/DirectBitmap/IMinimapBitmapProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Drawing; - -namespace SharedLib; - -public interface IMinimapBitmapProvider -{ - Bitmap MiniMapBitmap { get; } - - Rectangle MiniMapRect { get; } - - object MiniMapLock { get; } -} diff --git a/SharedLib/Extensions/ImageSharpPointExt.cs b/SharedLib/Extensions/ImageSharpPointExt.cs new file mode 100644 index 000000000..3117c8eaa --- /dev/null +++ b/SharedLib/Extensions/ImageSharpPointExt.cs @@ -0,0 +1,21 @@ +using SixLabors.ImageSharp; + +namespace SharedLib.Extensions; + +public static class ImageSharpPointExt +{ + public static Point Scale(this Point p, float scale) + { + return new Point((int)(p.X * scale), (int)(p.Y * scale)); + } + + public static Point Scale(this Point p, float scaleX, float scaleY) + { + return new Point((int)(p.X * scaleX), (int)(p.Y * scaleY)); + } + + public static float SqrDistance(in Point p1, in Point p2) + { + return ((p1.X - p2.X) * (p1.X - p2.X)) + ((p1.Y - p2.Y) * (p1.Y - p2.Y)); + } +} diff --git a/SharedLib/Extensions/ImageSharpRectangleExt.cs b/SharedLib/Extensions/ImageSharpRectangleExt.cs new file mode 100644 index 000000000..f070a2eb3 --- /dev/null +++ b/SharedLib/Extensions/ImageSharpRectangleExt.cs @@ -0,0 +1,21 @@ +using SixLabors.ImageSharp; + +namespace SharedLib.Extensions; + +public static class ImageSharpRectangleExt +{ + public static Point Centre(this Rectangle r) + { + return new Point(r.Left + r.Width / 2, r.Top + r.Height / 2); + } + + public static Point Max(this Rectangle r) + { + return new Point(r.Left + r.Width, r.Top + r.Height); + } + + public static Point BottomCentre(this Rectangle r) + { + return new Point(r.Left + r.Width / 2, r.Bottom); + } +} diff --git a/SharedLib/ImageProvider/IMinimapImageProvider.cs b/SharedLib/ImageProvider/IMinimapImageProvider.cs new file mode 100644 index 000000000..272c69c22 --- /dev/null +++ b/SharedLib/ImageProvider/IMinimapImageProvider.cs @@ -0,0 +1,11 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace SharedLib; + +public interface IMinimapImageProvider +{ + Image MiniMapImage { get; } + + Rectangle MiniMapRect { get; } +} diff --git a/SharedLib/ImageProvider/IScreenImageProvider.cs b/SharedLib/ImageProvider/IScreenImageProvider.cs new file mode 100644 index 000000000..7ed1eb2fe --- /dev/null +++ b/SharedLib/ImageProvider/IScreenImageProvider.cs @@ -0,0 +1,11 @@ +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace SharedLib; + +public interface IScreenImageProvider +{ + Image ScreenImage { get; } + + Rectangle ScreenRect { get; } +} diff --git a/SharedLib/NpcFinder/LineSegmentOperation.cs b/SharedLib/NpcFinder/LineSegmentOperation.cs new file mode 100644 index 000000000..1b0965bf3 --- /dev/null +++ b/SharedLib/NpcFinder/LineSegmentOperation.cs @@ -0,0 +1,102 @@ +using System; + +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp; +using System.Threading; +using System.Runtime.CompilerServices; + +namespace SharedLib.NpcFinder; + +internal readonly struct LineSegmentOperation : IRowOperation +{ + private readonly Buffer2D source; + + private readonly int size; + private readonly Rectangle area; + + private readonly float minLength; + private readonly float minEndLength; + + private readonly Func colorMatcher; + + private readonly LineSegment[] segments; + + private readonly ArrayCounter counter; + + public LineSegmentOperation( + LineSegment[] segments, + int size, + Rectangle area, + float minLength, + float minEndLength, + ArrayCounter counter, + Func colorMatcher, + Buffer2D source) + { + this.segments = segments; + this.size = size; + this.area = area; + this.minLength = minLength; + this.minEndLength = minEndLength; + this.counter = counter; + this.colorMatcher = colorMatcher; + this.source = source; + } + + public readonly int GetRequiredBufferLength(Rectangle bounds) + { + return size; + } + + [SkipLocalsInit] + public readonly void Invoke(int y, Span span) + { + int xStart = -1; + int xEnd = -1; + int end = area.Right; + + int i = 0; + + ReadOnlySpan row = source.DangerousGetRowSpan(y); + + for (int x = area.Left; x < end; x++) + { + Bgra32 pixel = row[x]; + + if (!colorMatcher(pixel.R, pixel.G, pixel.B)) + continue; + + if (xStart > -1 && (x - xEnd) < minLength) + { + //xEnd = x; + } + else + { + if (xStart > -1 && xEnd - xStart > minEndLength) + { + if (i + 1 >= size) + break; + + span[i++] = new LineSegment(xStart, xEnd, y); + } + + xStart = x; + } + xEnd = x; + } + + if (xStart > -1 && xEnd - xStart > minEndLength) + { + span[i++] = new LineSegment(xStart, xEnd, y); + } + + if (i == 0) + return; + + Interlocked.Add(ref counter.count, i); + + span[..i].CopyTo(segments.AsSpan(counter.count, i)); + } +} diff --git a/SharedLib/NpcFinder/NpcNameFinder.cs b/SharedLib/NpcFinder/NpcNameFinder.cs index 7047cc4b3..25d47ad70 100644 --- a/SharedLib/NpcFinder/NpcNameFinder.cs +++ b/SharedLib/NpcFinder/NpcNameFinder.cs @@ -1,31 +1,26 @@ using SharedLib.Extensions; using Microsoft.Extensions.Logging; using System; -using System.Drawing; + using System.Linq; -using System.Drawing.Imaging; -using System.Threading.Tasks; using System.Runtime.CompilerServices; using System.Buffers; using System.Collections.Generic; -using System.Threading; using static SharedLib.NpcFinder.NpcNameColors; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp; namespace SharedLib.NpcFinder; -public sealed partial class NpcNameFinder : IDisposable +public sealed partial class NpcNameFinder { private readonly ILogger logger; - private readonly IBitmapProvider bitmapProvider; - private readonly PixelFormat pixelFormat; + private readonly IScreenImageProvider bitmapProvider; private readonly INpcResetEvent resetEvent; private const int bytesPerPixel = 4; - private readonly Pen whitePen; - private readonly Pen greyPen; - public readonly int screenMid; public readonly int screenTargetBuffer; public readonly int screenMidBuffer; @@ -69,12 +64,13 @@ public sealed partial class NpcNameFinder : IDisposable public int HeightOffset1 { get; set; } = 10; public int HeightOffset2 { get; set; } = 2; - public NpcNameFinder(ILogger logger, IBitmapProvider bitmapProvider, + private readonly ArrayCounter counter; + + public NpcNameFinder(ILogger logger, IScreenImageProvider bitmapProvider, INpcResetEvent resetEvent) { this.logger = logger; this.bitmapProvider = bitmapProvider; - this.pixelFormat = bitmapProvider.Bitmap.PixelFormat; this.resetEvent = resetEvent; UpdateSearchMode(); @@ -88,33 +84,26 @@ public NpcNameFinder(ILogger logger, IBitmapProvider bitmapProvider, Area = new Rectangle(new Point(0, (int)ScaleHeight(topOffset)), new Size( - (int)(bitmapProvider.Bitmap.Width * 0.87f), - (int)(bitmapProvider.Bitmap.Height * 0.6f))); + (int)(bitmapProvider.ScreenImage.Width * 0.87f), + (int)(bitmapProvider.ScreenImage.Height * 0.6f))); - int screenWidth = bitmapProvider.Rect.Width; + int screenWidth = bitmapProvider.ScreenRect.Width; screenMid = screenWidth / 2; screenMidBuffer = screenWidth / 15; screenTargetBuffer = screenMidBuffer / 2; screenAddBuffer = screenMidBuffer * 3; - whitePen = new(Color.White, 3); - greyPen = new(Color.Gray, 3); - } - - public void Dispose() - { - whitePen.Dispose(); - greyPen.Dispose(); + counter = new(); } private float ScaleWidth(int value) { - return value * (bitmapProvider.Rect.Width / refWidth); + return value * (bitmapProvider.ScreenRect.Width / refWidth); } private float ScaleHeight(int value) { - return value * (bitmapProvider.Rect.Height / refHeight); + return value * (bitmapProvider.ScreenRect.Height / refHeight); } private void CalculateHeightMultipiler() @@ -375,6 +364,7 @@ public void Update() resetEvent.Set(); } + [SkipLocalsInit] private ArraySegment DetermineNpcs(ReadOnlySpan data) { int count = 0; @@ -459,7 +449,7 @@ private ArraySegment DetermineNpcs(ReadOnlySpan data) Point pi = ii.Rect.Centre(); Point pj = jj.Rect.Centre(); - float midDistance = PointExt.SqrDistance(pi, pj); + float midDistance = ImageSharpPointExt.SqrDistance(pi, pj); if (ii.Rect.IntersectsWith(jj.Rect) || midDistance <= lineHeight * lineHeight) @@ -530,92 +520,45 @@ private int YOffset(Rectangle area, Rectangle npc) [SkipLocalsInit] private ReadOnlySpan PopulateLines( - IBitmapProvider provider, Rectangle rect, + IScreenImageProvider provider, Rectangle rect, Func colorMatcher, Rectangle area, float minLength, float lengthDiff) { - const int RESOLUTION = 64; - int width = (area.Right - area.Left) / RESOLUTION; + const int RESOLUTION = 32; + int rowSize = (area.Right - area.Left) / RESOLUTION; int height = (area.Bottom - area.Top) / RESOLUTION; - int size = width * height; + int totalSize = rowSize * height; var pooler = ArrayPool.Shared; - LineSegment[] segments = pooler.Rent(size); - int i = 0; + LineSegment[] segments = pooler.Rent(totalSize); - int end = area.Right; float minEndLength = minLength - lengthDiff; - lock (provider.Lock) - { - Bitmap bitmap = provider.Bitmap; - BitmapData bitmapData = - bitmap.LockBits(new Rectangle(Point.Empty, rect.Size), - ImageLockMode.ReadOnly, pixelFormat); - - int bdHeight = bitmapData.Height; - int bdStride = bitmapData.Stride; - - [SkipLocalsInit] - unsafe void body(int y) - { - int xStart = -1; - int xEnd = -1; - - ReadOnlySpan bitmapSpan = - new(bitmapData.Scan0.ToPointer(), bdHeight * bdStride); - - ReadOnlySpan currentLine = - bitmapSpan.Slice(y * bdStride, bdStride); - - for (int x = area.Left; x < end; x++) - { - int xi = x * bytesPerPixel; - - if (!colorMatcher( - currentLine[xi + 2], // r - currentLine[xi + 1], // g - currentLine[xi])) // b - continue; - - if (xStart > -1 && (x - xEnd) < minLength) - { - xEnd = x; - } - else - { - if (xStart > -1 && xEnd - xStart > minEndLength) - { - if (i + 1 >= size) - return; - - segments[Interlocked.Add(ref i, 1)] = - new LineSegment(xStart, xEnd, y); - } - - xStart = x; - } - xEnd = x; - } + Rectangle rectangle = new(area.X, area.Y, area.Width, area.Height); - if (xStart > -1 && xEnd - xStart > minEndLength) - { - segments[Interlocked.Add(ref i, 1)] = - new LineSegment(xStart, xEnd, y); - } - } - _ = Parallel.For(area.Top, area.Height, body); + counter.count = 0; + LineSegmentOperation operation = new( + segments, + rowSize, + rectangle, + minLength, + minEndLength, + counter, + colorMatcher, + bitmapProvider.ScreenImage.Frames[0].PixelBuffer); - bitmap.UnlockBits(bitmapData); - } + ParallelRowIterator.IterateRows( + Configuration.Default, + rectangle, + in operation); pooler.Return(segments); - return new(segments, 0, i); + return new(segments, 0, counter.count); } public Point ToScreenCoordinates() { - return bitmapProvider.Rect.Location; + return new(bitmapProvider.ScreenRect.Location.X, bitmapProvider.ScreenRect.Location.Y); } diff --git a/SharedLib/NpcFinder/NpcPosition.cs b/SharedLib/NpcFinder/NpcPosition.cs index 4ae253819..3030d4afb 100644 --- a/SharedLib/NpcFinder/NpcPosition.cs +++ b/SharedLib/NpcFinder/NpcPosition.cs @@ -1,6 +1,6 @@ -using System.Drawing; +using SharedLib.Extensions; -using SharedLib.Extensions; +using SixLabors.ImageSharp; namespace SharedLib.NpcFinder; diff --git a/SharedLib/NpcFinder/NpcPositionComparer.cs b/SharedLib/NpcFinder/NpcPositionComparer.cs index de221eaaa..5ba377ee8 100644 --- a/SharedLib/NpcFinder/NpcPositionComparer.cs +++ b/SharedLib/NpcFinder/NpcPositionComparer.cs @@ -1,24 +1,25 @@ using System.Collections.Generic; -using System.Drawing; using SharedLib.Extensions; +using SixLabors.ImageSharp; + namespace SharedLib.NpcFinder; internal sealed class NpcPositionComparer : IComparer { - private readonly IBitmapProvider bitmapProvider; + private readonly IScreenImageProvider bitmapProvider; - public NpcPositionComparer(IBitmapProvider bitmapProvider) + public NpcPositionComparer(IScreenImageProvider bitmapProvider) { this.bitmapProvider = bitmapProvider; } public int Compare(NpcPosition x, NpcPosition y) { - Point origin = bitmapProvider.Rect.Centre(); - float dx = PointExt.SqrDistance(origin, x.ClickPoint); - float dy = PointExt.SqrDistance(origin, y.ClickPoint); + Point origin = bitmapProvider.ScreenRect.Centre(); + float dx = ImageSharpPointExt.SqrDistance(origin, x.ClickPoint); + float dy = ImageSharpPointExt.SqrDistance(origin, y.ClickPoint); return dx.CompareTo(dy); } diff --git a/SharedLib/Screen/IColorReader.cs b/SharedLib/Screen/IColorReader.cs deleted file mode 100644 index c72ccb2e6..000000000 --- a/SharedLib/Screen/IColorReader.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Drawing; - -namespace SharedLib; - -public interface IColorReader -{ - Color GetColorAt(Point point); - - Bitmap GetBitmap(int width, int height); -} \ No newline at end of file diff --git a/SharedLib/Screen/IRectProvider.cs b/SharedLib/Screen/IRectProvider.cs index 1ea90dcb1..f3f18e186 100644 --- a/SharedLib/Screen/IRectProvider.cs +++ b/SharedLib/Screen/IRectProvider.cs @@ -1,4 +1,4 @@ -using System.Drawing; +using SixLabors.ImageSharp; namespace SharedLib; diff --git a/SharedLib/SharedLib.csproj b/SharedLib/SharedLib.csproj index 7a205fe88..0004bb0e9 100644 --- a/SharedLib/SharedLib.csproj +++ b/SharedLib/SharedLib.csproj @@ -8,6 +8,7 @@ + diff --git a/SharedLib/SingleSequence/SingleSequence.cs b/SharedLib/SingleSequence/SingleSequence.cs deleted file mode 100644 index 07278a877..000000000 --- a/SharedLib/SingleSequence/SingleSequence.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace SharedLib; - -public struct SingleSequence : IEnumerable -{ - public struct SingleEnumerator : IEnumerator - { - private readonly SingleSequence _parent; - private bool _couldMove; - public SingleEnumerator(ref SingleSequence parent) - { - _parent = parent; - _couldMove = true; - } - public readonly T Current => _parent._value; - - readonly object IEnumerator.Current => Current!; - public void Dispose() { } - - public bool MoveNext() - { - if (!_couldMove) return false; - _couldMove = false; - return true; - } - public void Reset() - { - _couldMove = true; - } - } - private readonly T _value; - public SingleSequence(T value) - { - _value = value; - } - public IEnumerator GetEnumerator() - { - return new SingleEnumerator(ref this); - } - IEnumerator IEnumerable.GetEnumerator() - { - return new SingleEnumerator(ref this); - } -} diff --git a/SharedLib/StartupConfig/StartupConfigReader.cs b/SharedLib/StartupConfig/StartupConfigReader.cs index 1712bfb6a..dceafbf63 100644 --- a/SharedLib/StartupConfig/StartupConfigReader.cs +++ b/SharedLib/StartupConfig/StartupConfigReader.cs @@ -11,5 +11,5 @@ public StartupConfigReader() { } public AddonDataProviderType ReaderType => System.Enum.TryParse(Type, out AddonDataProviderType m) ? m - : AddonDataProviderType.GDI; + : AddonDataProviderType.DXGI; } diff --git a/SharedLib/Util/ArrayCounter.cs b/SharedLib/Util/ArrayCounter.cs new file mode 100644 index 000000000..623bbe95c --- /dev/null +++ b/SharedLib/Util/ArrayCounter.cs @@ -0,0 +1,6 @@ +namespace SharedLib; + +public sealed class ArrayCounter +{ + public int count; +} diff --git a/WinAPI/NativeMethods.cs b/WinAPI/NativeMethods.cs index b94f3cf03..2ad382bee 100644 --- a/WinAPI/NativeMethods.cs +++ b/WinAPI/NativeMethods.cs @@ -1,6 +1,6 @@ using System; -using System.Drawing; +using SixLabors.ImageSharp; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; @@ -111,13 +111,7 @@ public static void GetPosition(IntPtr hWnd, ref Point point) public static void GetWindowRect(IntPtr hWnd, out Rectangle rect) { GetClientRect(hWnd, out RECT nRect); - rect = new Rectangle - { - X = nRect.left, - Y = nRect.top, - Width = nRect.right - nRect.left, - Height = nRect.bottom - nRect.top - }; + rect = Rectangle.FromLTRB(nRect.left, nRect.top, nRect.right, nRect.bottom); Point topLeft = new(); ClientToScreen(hWnd, ref topLeft); @@ -130,7 +124,7 @@ public static void GetWindowRect(IntPtr hWnd, out Rectangle rect) public static int GetDpi() { - using Graphics g = Graphics.FromHwnd(IntPtr.Zero); + using System.Drawing.Graphics g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero); return GetDeviceCaps(g.GetHdc(), LOGPIXELSX); } @@ -139,7 +133,7 @@ public static Size GetCursorSize() int dpi = GetDpi(); SizeF size = new(GetSystemMetrics(SM_CXCURSOR), GetSystemMetrics(SM_CYCURSOR)); size *= DPI2PPI(dpi); - return size.ToSize(); + return (Size)size; } public static float DPI2PPI(int dpi) diff --git a/WinAPI/WinAPI.csproj b/WinAPI/WinAPI.csproj index 08437078c..7f1b4cd4b 100644 --- a/WinAPI/WinAPI.csproj +++ b/WinAPI/WinAPI.csproj @@ -7,6 +7,7 @@ +