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);
}
}
}