using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using FastBitmapLib; using IPCamLib; namespace IPCamAppBar { public partial class CameraView : UserControl, IRetryableCameraObserver { private readonly ICamera camera; private readonly bool overlayDateTime; private Bitmap viewBitmap; private CancellationTokenSource streamCancellationTokenSource; public CameraView(ICamera camera, bool overlayDateTime) { this.camera = camera; this.overlayDateTime = overlayDateTime; InitializeComponent(); StartCamera(); Disposed += (_, _) => { StopCamera(); }; } private void StartCamera() { streamCancellationTokenSource = new CancellationTokenSource(); Task.Run(async () => { var retryableCamera = new RetryableCamera(camera); await retryableCamera.Fetch(this, streamCancellationTokenSource.Token); }); } private void StopCamera() { streamCancellationTokenSource.Cancel(); } private Bitmap resizedBitmap; public Task OnFrame(Image image) { if (streamCancellationTokenSource.IsCancellationRequested) return Task.CompletedTask; if (overlayDateTime || image is not Bitmap bitmap || image.Width != Width || image.Height != Height) { resizedBitmap ??= new Bitmap(Width, Height); using var graphics = Graphics.FromImage(resizedBitmap); graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.DrawImage(image, 0, 0, resizedBitmap.Width, resizedBitmap.Height); if (overlayDateTime) { using (var path = new GraphicsPath()) { path.AddString( DateTime.Now.ToString("G"), FontFamily.GenericSansSerif, (int) FontStyle.Regular, graphics.DpiY * 14 / 72, Rectangle.Inflate(new Rectangle(0, 0, resizedBitmap.Width, resizedBitmap.Height), -4, -4), new StringFormat { Alignment = StringAlignment.Far, LineAlignment = StringAlignment.Far }); graphics.DrawPath(new Pen(Color.Black, 3), path); graphics.FillPath(Brushes.White, path); } } graphics.Flush(); bitmap = resizedBitmap; } Invoke(new Action(() => { InvokeFrame(bitmap); })); return Task.CompletedTask; } public Task OnFetch() { Invoke(new Action(InvokeFetch)); return Task.CompletedTask; } public Task OnDisconnected(Exception exception, TimeSpan retryDelay) { Invoke(new Action(() => { InvokeDisconnected(exception, retryDelay); })); return Task.CompletedTask; } private void InvokeFrame(Bitmap bitmap) { if (IsDisposed) return; ConnectingLabel.Visible = false; StreamView.Visible = true; IssueLabel.Visible = false; PausedOverlay.Visible = false; if (viewBitmap == null) { viewBitmap = new Bitmap(Width, Height); StreamView.Image = viewBitmap; } using (var fastViewBitmap = viewBitmap.FastLock()) { fastViewBitmap.CopyRegion(bitmap, new Rectangle(Point.Empty, bitmap.Size), new Rectangle(Point.Empty, viewBitmap.Size)); } StreamView.Invalidate(); } private void InvokeFetch() { if (IsDisposed) return; IssueLabel.Text = "Connecting..."; IssueLabel.Visible = true; IssueLabel.BringToFront(); } private void InvokeDisconnected(Exception exception, TimeSpan retryDelay) { if (IsDisposed || streamCancellationTokenSource.IsCancellationRequested) return; IssueLabel.Text = (exception?.Message ?? "Camera disconnected") + $", retrying in {retryDelay.TotalSeconds} seconds"; IssueLabel.Visible = true; IssueLabel.BringToFront(); } public void SetPaused(bool pause) { if (pause) { StopCamera(); PausedOverlay.Left = (Width - PausedOverlay.Width) / 2; PausedOverlay.Top = (Height - PausedOverlay.Height) / 2; PausedOverlay.Visible = true; PausedOverlay.BringToFront(); } else StartCamera(); } } }