using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Tango.Core; using Tango.RemoteDesktop.Utils; namespace Tango.RemoteDesktop { /// /// Represents a generic screen capture engine. /// The type will be the type of difference object delivered by the engine. /// /// The type of the frame. /// public class ScreenCaptureEngine : ExtendedObject, IScreenCaptureEngine where TFrame : class, IFrame { private Thread _captureThread; private Bitmap _previousBitmap; private bool _isDisposed; /// /// Occurs when a new screen frame is available. /// public event EventHandler> FrameReceived; /// /// Gets or sets the screen capture method. /// public ICaptureMethod CaptureMethod { get; set; } /// /// Gets or sets the screen capture region. /// public CaptureRegion CaptureRegion { get; set; } /// /// Gets a value indicating whether this instance is currently capturing. /// public bool IsStarted { get; private set; } /// /// Gets or sets the frame rate per second. /// Default is 10. /// public int FrameRate { get; set; } /// /// Gets or sets a value indicating whether to include the cursor when capturing. /// public bool CaptureCursor { get; set; } /// /// Gets or sets the bitmap comparer. /// public IBitmapComparer Comparer { get; set; } /// /// Gets or sets a value indicating whether to enable image comparison. /// public bool EnableComparer { get; set; } /// /// Initializes a new instance of the class using the specified . /// /// The comparer. public ScreenCaptureEngine(IBitmapComparer comparer) { FrameRate = 10; CaptureMethod = new CaptureMethods.GdiScreenCapture(); CaptureRegion = new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds); Comparer = comparer; CaptureCursor = true; if (Comparer != null) { EnableComparer = true; } } /// /// Start capturing. /// /// Screen capture engine cannot be started after disposed. public void Start() { if (_isDisposed) { throw new ObjectDisposedException("Screen capture engine cannot be started after disposed."); } if (!IsStarted) { IsStarted = true; _captureThread = new Thread(CaptureThreadMethod); _captureThread.IsBackground = true; _captureThread.Name = "Screen Capture Thread"; _captureThread.Start(); } } /// /// Stop capturing. /// public void Stop() { if (IsStarted) { IsStarted = false; } } /// /// Releases unmanaged and - optionally - managed resources. /// public void Dispose() { if (!_isDisposed) { _isDisposed = true; Stop(); CaptureMethod?.Dispose(); } } private void CaptureThreadMethod() { Stopwatch watch = new Stopwatch(); LogManager.Log($"Screen capture engine started with the following definitions:\nCapture Region: {CaptureRegion}\nCapture Method: {CaptureMethod.GetType().Name}\nFrame Rate: {FrameRate}\nComparer Enabled: {EnableComparer}"); while (IsStarted) { watch.Restart(); try { var bitmap = CaptureMethod.GetDesktopBitmap(CaptureRegion); if (CaptureCursor) { using (Graphics g = Graphics.FromImage(bitmap)) { CursorUtils.ApplyCursor(g, bitmap, CaptureRegion.Left, CaptureRegion.Top); } } if (EnableComparer && Comparer != null) { if (_previousBitmap == null) { _previousBitmap = bitmap.Clone() as Bitmap; OnFrameReceived(bitmap, null, 0); } else { try { BitmapComparerResult result = Comparer.CreateDifference(_previousBitmap, bitmap); _previousBitmap.Dispose(); _previousBitmap = bitmap.Clone() as Bitmap; OnFrameReceived(bitmap, result.Frame, result.DifferenceCount); } catch (MaxDifferencesReachedException) { _previousBitmap.Dispose(); _previousBitmap = bitmap.Clone() as Bitmap; OnFrameReceived(bitmap, null, (uint)(bitmap.Width * bitmap.Height)); } } } else { OnFrameReceived(bitmap, null, 0); } } catch (Exception ex) { Debug.WriteLine($"Error in screen capture engine: {ex.Message}"); _previousBitmap = null; } int delay = Math.Max(5, (1000 / FrameRate) - (int)watch.ElapsedMilliseconds); Thread.Sleep(delay); } _previousBitmap = null; LogManager.Log("Screen capture engine stopped."); } private void OnFrameReceived(Bitmap currentBitmap, TFrame diffFrame, uint differenceCount) { if (!IsStarted) return; FrameReceived?.Invoke(this, new ScreenCaptureFrameReceivedEventArgs() { Frame = new ScreenCaptureFrame(currentBitmap, diffFrame) { DifferenceCount = differenceCount } }); } } }