using Pulsar.Server.Utilities; using System; using System.Diagnostics; using System.Drawing; using System.Windows.Forms; using System.ComponentModel; using System.Drawing.Drawing2D; namespace Pulsar.Server.Controls { public interface IRapidPictureBox { bool Running { get; set; } Image GetImageSafe { get; set; } void Start(); void Stop(); void UpdateImage(Bitmap bmp, bool cloneBitmap = false); } /// /// Custom PictureBox Control designed for rapidly-changing images. /// public class RapidPictureBox : PictureBox, IRapidPictureBox { /// /// True if the PictureBox is currently streaming images, else False. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public bool Running { get; set; } /// /// Returns the width of the original screen. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public int ScreenWidth { get; private set; } /// /// Returns the height of the original screen. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [Browsable(false)] public int ScreenHeight { get; private set; } /// /// Provides thread-safe access to the Image of this Picturebox. /// [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Image GetImageSafe { get { lock (_imageLock) { return _frame; } } set { lock (_imageLock) { var old = _frame; _frame = value as Bitmap; old?.Dispose(); } RequestRepaint(); } } /// /// The lock object for the Picturebox's image. /// private readonly object _imageLock = new object(); /// /// Latest frame to draw. We avoid assigning to PictureBox.Image to prevent cross-thread UI access and allocations. /// private Bitmap _frame; /// /// Small placeholder assigned to base.Image so existing code paths that check Image != null keep working. /// private Bitmap _placeholder; /// /// Prevent flooding the message queue; coalesce multiple UpdateImage calls into one repaint. /// private bool _repaintPending; /// /// The Stopwatch for internal FPS measuring. /// private Stopwatch _sWatch; /// /// The internal class for FPS measuring. /// private FrameCounter _frameCounter; /// /// Subscribes an Eventhandler to the FrameUpdated event. /// /// The Eventhandler to set. public void SetFrameUpdatedEvent(FrameUpdatedEventHandler e) { _frameCounter.FrameUpdated += e; } /// /// Unsubscribes an Eventhandler from the FrameUpdated event. /// /// The Eventhandler to remove. public void UnsetFrameUpdatedEvent(FrameUpdatedEventHandler e) { _frameCounter.FrameUpdated -= e; } /// /// Starts the internal FPS measuring. /// public void Start() { _frameCounter = new FrameCounter(); _sWatch = Stopwatch.StartNew(); Running = true; } /// /// Stops the internal FPS measuring. /// public void Stop() { _sWatch?.Stop(); Running = false; } /// /// Updates the Image of this Picturebox. /// /// The new bitmap to use. /// If True the bitmap will be cloned, else it uses the original bitmap. public void UpdateImage(Bitmap bmp, bool cloneBitmap) { try { CountFps(); if ((ScreenWidth != bmp.Width) || (ScreenHeight != bmp.Height)) UpdateScreenSize(bmp.Width, bmp.Height); // Swap the frame without resizing; scaling is handled in OnPaint for speed. lock (_imageLock) { var old = _frame; _frame = cloneBitmap ? (Bitmap)bmp.Clone() : bmp; old?.Dispose(); } RequestRepaint(); } catch (InvalidOperationException) { } catch (Exception) { } } /// /// Constructor, sets Picturebox double-buffered and initializes the Framecounter. /// public RapidPictureBox() { this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); _placeholder = new Bitmap(1, 1); _placeholder.SetPixel(0, 0, Color.Transparent); base.Image = _placeholder; } protected override void OnPaint(PaintEventArgs pe) { Bitmap localFrame = null; lock (_imageLock) { if (_frame == null) return; localFrame = _frame; } var g = pe.Graphics; g.SmoothingMode = SmoothingMode.None; g.CompositingMode = CompositingMode.SourceCopy; g.CompositingQuality = CompositingQuality.HighSpeed; g.PixelOffsetMode = PixelOffsetMode.Half; g.InterpolationMode = InterpolationMode.NearestNeighbor; if (localFrame == null) return; var cs = this.ClientSize; if (cs.Width <= 0 || cs.Height <= 0) return; if (localFrame.Width == cs.Width && localFrame.Height == cs.Height) { g.DrawImageUnscaled(localFrame, 0, 0); } else { g.DrawImage(localFrame, this.ClientRectangle); } } protected override void OnPaintBackground(PaintEventArgs pevent) { if (this.BackColor.A == 255) { using (var b = new SolidBrush(this.BackColor)) { pevent.Graphics.FillRectangle(b, this.ClientRectangle); } } else { base.OnPaintBackground(pevent); } } private void UpdateScreenSize(int newWidth, int newHeight) { ScreenWidth = newWidth; ScreenHeight = newHeight; } private void CountFps() { var deltaTime = (float)_sWatch.Elapsed.TotalSeconds; _sWatch = Stopwatch.StartNew(); _frameCounter.Update(deltaTime); } private void RequestRepaint() { if (_repaintPending) return; _repaintPending = true; void doInvalidate() { if (!IsDisposed) { Invalidate(); //Update(); } _repaintPending = false; } if (IsHandleCreated && InvokeRequired) { try { BeginInvoke((Action)(doInvalidate)); } catch { _repaintPending = false; } } else { doInvalidate(); } } protected override void Dispose(bool disposing) { if (disposing) { lock (_imageLock) { _frame?.Dispose(); _frame = null; } try { if (ReferenceEquals(base.Image, _placeholder)) { base.Image = null; } _placeholder?.Dispose(); } catch { } finally { _placeholder = null; } } base.Dispose(disposing); } } }