Impact Acquire SDK .NET
DotNetWinFormsBitmapUsage.cs

The DotNetWinFormsBitmapUsage program is more complex than most of the examples discussed in this manual. It uses Windows® Forms and is meant to demonstrate how to efficiently work with System.Drawing.Bitmap objects. More on this topic can also be found in the Efficiently create System.Drawing.Bitmap instances section.

Program location
The source files can be found under:
%INSTALLDIR%\apps\CSharp\DotNetWinFormsBitmapUsage\
Note
If you have installed the package without example applications, these files won't be available. On Windows® the sample application can be installed or removed from the target system at any time by simply restarting the installation package.
DotNetWinFormsBitmapUsage example:
  1. Allows to select and open a device
  2. Allows to start and stop the acquisition from the selected device
  3. Will display a live image from the selected device and allows to apply direct modifications to the image and/or shows how to draw an overlay on top of the image without modifying the actual image buffer
How it works
Open the example with Microsoft Visual Studio and try to compile and start the sample, select a device and press Start Live.

Then, you will see the following window:

On the left a couple of things can be switched on and off to either draw an overlay on top of the unmodified buffer or to modify the buffer itself. The result might be something like this:

Note
The System.Drawing.Bitmap class only supports a subset of the pixel formats offered by Impact Acquire. In case an unsupported pixel format is used, the mv.impact.acquire.RequestBitmapData constructor will throw an exception. This then might look like this:
In this case, the pixel format has to be converted to a supported format before creating the System.Drawing.Bitmap instance and for performance reasons this is not done by the .NET API. To get a pixel format supported by the .NET framework
Source code
BitmapUsageForm.cs
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Timers;
using System.Windows.Forms;
namespace mv.impact.acquire.examples
{
public partial class MainForm : Form
{
#region static private members ---------------------------------------------------
private static readonly System.Drawing.Font font_ = new System.Drawing.Font("Arial", 10);
private static readonly System.Drawing.Pen pen_ = new System.Drawing.Pen(Color.Blue, 10);
#endregion
#region private members ----------------------------------------------------------
private mv.impact.acquire.Device dev_ = null;
private mv.impact.acquire.FunctionInterface functionInterface_ = null;
private mv.impact.acquire.Statistics statistics_ = null;
private mv.impact.acquire.Request requestCurrentlyDisplayed_ = null;
private mv.impact.acquire.RequestBitmapData bitmapDataCurrentlyDisplayed_ = null;
private mv.impact.acquire.Request requestToDisplayNext_ = null;
private readonly Stopwatch imageProcessingTimer_ = new Stopwatch();
private int requestsInitiallyQueued_ = 0;
private int requestsSuccessfullyCaptured_ = 0;
private int requestsSentToDisplay_ = 0;
private double percentageOfImagesSentToTheDisplay_ = 100;
private string currentBandwidthMsg_ = string.Empty;
private string lastErrorMsg_ = string.Empty;
private readonly Object internalLock_ = new Object();
private readonly System.Timers.Timer overlayTextRefreshUpdateTimer_ = null;
private bool threadsTerminated_ = true;
private Thread updateThread_ = null;
private readonly AutoResetEvent newImageAvailableEvent_ = new AutoResetEvent(false);
#endregion
#region private methods ----------------------------------------------------------
private void CleanupPreviousRequest()
{
if (bitmapDataCurrentlyDisplayed_ != null)
{
bitmapDataCurrentlyDisplayed_.Dispose();
requestCurrentlyDisplayed_.unlock();
}
}
private Request GetRequestToDisplayNext()
{
Request r = null;
lock (internalLock_)
{
r = requestToDisplayNext_;
requestToDisplayNext_ = null;
}
return r;
}
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
overlayTextRefreshUpdateTimer_.Enabled = false;
threadsTerminated_ = true;
CleanupPreviousRequest();
}
private void MainForm_Load(object sender, EventArgs e)
{
for (int i = 0; i < DeviceManager.deviceCount; i++)
{
comboBoxDeviceSelection.Items.Add("[" + i + "] - " + (DeviceManager.getDevice(i)).serial.readS());
}
}
// This function modifies the bitmap buffer directly. Because of that this code is 'unsafe' in a .NET context and
// requires the bitmap data to be locked while the actual modification takes place.
private unsafe void ModifyBitmapBuffer(Bitmap bmp)
{
int pixWidth = 1;
switch (bmp.PixelFormat)
{
case PixelFormat.Format8bppIndexed:
pixWidth = 1;
break;
case PixelFormat.Format24bppRgb:
pixWidth = 3;
break;
case PixelFormat.Format32bppRgb:
case PixelFormat.Format32bppPArgb:
case PixelFormat.Format32bppArgb:
pixWidth = 4;
break;
default:
Debug.Assert(false, "Unsupported pixel format!", "This code needs to be adjusted to handle '" + bmp.PixelFormat.ToString() + "' data!");
break;
}
// Lock the bitmap to ensure the memory can be modified without side effects
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr scan0 = bmData.Scan0;
int stride = bmData.Stride;
int x0 = bmp.Width / 4;
int y0 = bmp.Height / 4;
int x1 = 3 * bmp.Width / 4;
int y1 = 3 * bmp.Height / 4;
byte val0 = ((byte*)scan0.ToPointer())[2];
switch (bmp.PixelFormat)
{
case PixelFormat.Format8bppIndexed:
for (int y = y0; y < y1; y++)
{
byte* p = (byte*)scan0.ToPointer() + y * stride;
for (int x = x0; x < x1; x++)
{
p[x] = (byte)(val0 + y);
}
}
break;
case PixelFormat.Format24bppRgb:
case PixelFormat.Format32bppRgb:
case PixelFormat.Format32bppPArgb:
case PixelFormat.Format32bppArgb:
for (int y = y0; y < y1; y++)
{
byte* p = (byte*)scan0.ToPointer() + y * stride + x0 * pixWidth;
for (int x = x0; x < x1; x++)
{
p[0] = (byte)(val0 + y);
p[1] = (byte)(val0 + y);
p[2] = (byte)(val0 + y);
p += pixWidth;
}
}
break;
}
bmp.UnlockBits(bmData);
}
private void OnOverlayTextRefreshUpdate(object source, ElapsedEventArgs e)
{
lock (internalLock_)
{
UpdatePercentageOfImagesSentToDisplay();
double fps = statistics_.framesPerSecond.read();
currentBandwidthMsg_ = String.Format("Capture rate: {0:0.00} Hz ({1:0.00} MB/s), display rate: {2:0.00} Hz, using {3} buffers",
fps,
(statistics_.bandwidthConsumed.read() / 1024),
fps * percentageOfImagesSentToTheDisplay_ / 100.0,
requestsInitiallyQueued_);
}
}
private void UnlockRequest(Request request)
{
//request?.unlock(); // This is nicer but then would force us to use c# 6.0 (.NET 4.6 or above), which then cannot be used with ancient compilers anymore
if (request != null)
{
request.unlock();
}
}
private void UpdateImage(Request request)
{
if (request != null)
{
// measure the time it takes to process the image and display it
imageProcessingTimer_.Reset();
imageProcessingTimer_.Start();
mv.impact.acquire.RequestBitmapData data = request.bitmapData;
if (cbModifyBitmapBuffer.Checked)
{
ModifyBitmapBuffer(data.bitmap);
}
this.pictureBoxImage.Image = data.bitmap;
CleanupPreviousRequest();
// remember the request and its bitmap data in order to be able to clean it up later.
// This is necessary as the bitmap data references the low level buffer directly
// instead of copying it for performance reasons. So the request cannot be unlocked
// until the bitmap is not used anymore and the bitmap itself must be disposed before
// the request can be unlocked.
requestCurrentlyDisplayed_ = request;
bitmapDataCurrentlyDisplayed_ = data;
imageProcessingTimer_.Stop();
}
}
private void UpdatePercentageOfImagesSentToDisplay()
{
lock (internalLock_)
{
double result = (requestsSuccessfullyCaptured_ == 0) ? 100.0 : 100.0 * (double)requestsSentToDisplay_ / (double)requestsSuccessfullyCaptured_;
requestsSentToDisplay_ = 0;
requestsSuccessfullyCaptured_ = 0;
percentageOfImagesSentToTheDisplay_ = 0.9 * percentageOfImagesSentToTheDisplay_ + 0.1 * result;
}
}
#endregion
#region private GUI methods ------------------------------------------------------
private void buttonStartStop_Click(object sender, EventArgs e)
{
buttonStartStop.Text = threadsTerminated_ ? "Stop Live" : "Start Live";
threadsTerminated_ = !threadsTerminated_;
overlayTextRefreshUpdateTimer_.Enabled = !threadsTerminated_;
Thread captureThread = new Thread(delegate ()
{
TDMR_ERROR result = TDMR_ERROR.DMR_NO_ERROR;
requestsInitiallyQueued_ = 0;
while ((result = (TDMR_ERROR)functionInterface_.imageRequestSingle()) == TDMR_ERROR.DMR_NO_ERROR)
{
++requestsInitiallyQueued_;
};
if (dev_.acquisitionStartStopBehaviour.read() == TAcquisitionStartStopBehaviour.assbUser)
{
if ((result = (TDMR_ERROR)functionInterface_.acquisitionStart()) != TDMR_ERROR.DMR_NO_ERROR)
{
lock (internalLock_)
{
lastErrorMsg_ = "'FunctionInterface.acquisitionStart' returned with an unexpected result: " + result + "( " + ImpactAcquireException.getErrorCodeAsString(result) + ")";
}
}
}
requestsSuccessfullyCaptured_ = 0;
requestsSentToDisplay_ = 0;
Request pRequest = null;
int timeout_ms = 500;
int requestNr = Device.INVALID_ID;
// run thread loop
while (!threadsTerminated_)
{
requestNr = functionInterface_.imageRequestWaitFor(timeout_ms);
pRequest = functionInterface_.isRequestNrValid(requestNr) ? functionInterface_.getRequest(requestNr) : null;
lock (internalLock_)
{
if (pRequest != null)
{
if (pRequest.isOK)
{
++requestsSuccessfullyCaptured_;
// this if/else structure makes sure, that data will always be captured at full speed while
// displaying the images as fast as possible (which might be slower than the capture rate)
if (requestToDisplayNext_ == null)
{
// if no request is currently displayed, make the current request the one to be displayed next and
// therefore don't unlock but remember it...
requestToDisplayNext_ = pRequest;
pRequest = null;
newImageAvailableEvent_.Set();
++requestsSentToDisplay_;
}
else
{
// if another request is currently about to get displayed, unlock this one
// in order to keep the capture queue filled with requests and therefore maximize the capture frame rate
UnlockRequest(pRequest);
}
lastErrorMsg_ = string.Empty;
}
else
{
// This request is either incomplete or has reported some other problem but belongs to the application right now.
// Unlock it in order to keep the capture queue filled with requests and therefore maximize the capture frame rate
UnlockRequest(pRequest);
lastErrorMsg_ = "Error: " + pRequest.requestResult.readS();
}
// send a new image request into the capture queue
functionInterface_.imageRequestSingle();
}
else
{
// if the error code is -2119(DEV_WAIT_FOR_REQUEST_FAILED), the documentation will provide
// additional information under TDMR_ERROR in the interface reference
lastErrorMsg_ = "imageRequestWaitFor failed (" + requestNr + ", " + ImpactAcquireException.getErrorCodeAsString(requestNr) + "), timeout value (" + timeout_ms + ") too small?";
}
}
}
if (dev_.acquisitionStartStopBehaviour.read() == TAcquisitionStartStopBehaviour.assbUser)
{
if ((result = (TDMR_ERROR)functionInterface_.acquisitionStop()) != TDMR_ERROR.DMR_NO_ERROR)
{
lock (internalLock_)
{
lastErrorMsg_ = "'FunctionInterface.acquisitionStop' returned with an unexpected result: " + result + "( " + ImpactAcquireException.getErrorCodeAsString(result) + ")";
}
}
}
UnlockRequest(pRequest);
functionInterface_.imageRequestReset(0, 0);
// extract and unlocking all requests that are now returned as 'aborted'
requestNr = Device.INVALID_ID;
while ((requestNr = functionInterface_.imageRequestWaitFor(0)) >= 0)
{
UnlockRequest(functionInterface_.getRequest(requestNr));
}
});
updateThread_ = new Thread(delegate ()
{
while (!threadsTerminated_)
{
if (newImageAvailableEvent_.WaitOne(TimeSpan.FromMilliseconds(100)))
{
try
{
Request pRequest = GetRequestToDisplayNext();
Invoke(new MethodInvoker(() => { UpdateImage(pRequest); }));
}
catch (Exception)
{
// Getting here means that the form is already disposed (ObjectDisposedException) and
// therefore the update is not possible anymore or the full dialog has been destroyed
// already (InvalidOperationException). However since this thread cannot be stopped from
// the main thread as this might result in a deadlock when this thread waits for the
// completion of the update and the main thread waits for the termination of this
// thread, we simply ignore this exception and continue.
}
}
}
});
if (!threadsTerminated_)
{
updateThread_.Start();
captureThread.Start();
}
}
private void comboBoxDeviceSelection_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
dev_ = DeviceManager.getDevice(comboBoxDeviceSelection.SelectedIndex);
if (dev_.interfaceLayout.isValid && dev_.interfaceLayout.listOfValidValues.Contains(TDeviceInterfaceLayout.dilGenICam))
{
dev_.interfaceLayout.write(TDeviceInterfaceLayout.dilGenICam);
dev_.acquisitionStartStopBehaviour.write(TAcquisitionStartStopBehaviour.assbUser);
}
statistics_ = new Statistics(dev_);
functionInterface_ = new FunctionInterface(dev_);
}
catch (ImpactAcquireException ex)
{
MessageBox.Show(ex.errorCodeAsString);
Environment.Exit(42);
}
comboBoxDeviceSelection.Enabled = false;
buttonStartStop.Enabled = true;
}
private void pictureBoxImage_Paint(object sender, PaintEventArgs e)
{
if (cbDrawCustomOverlay.Checked)
{
lock (internalLock_)
{
int xOff = this.pictureBoxImage.Width / 4;
int yOff = 50;
Graphics g = e.Graphics;
g.DrawString(String.Format("Bitmap ({0}x{1}@{2}) processing time: {3} ms ({4})",
this.pictureBoxImage.Image.Width,
this.pictureBoxImage.Image.Height,
this.pictureBoxImage.Image.PixelFormat.ToString(),
imageProcessingTimer_.ElapsedMilliseconds,
imageProcessingTimer_.Elapsed),
font_, Brushes.Red, xOff, yOff);
if (currentBandwidthMsg_.Length > 0)
{
yOff += 25;
g.DrawString(currentBandwidthMsg_, font_, Brushes.Red, xOff, yOff);
}
if (lastErrorMsg_.Length > 0)
{
yOff += 25;
g.DrawString(lastErrorMsg_, font_, Brushes.Red, xOff, yOff);
}
int xR = pictureBoxImage.Size.Width / 4;
int yR = pictureBoxImage.Size.Height / 4;
int wR = pictureBoxImage.Size.Width / 2;
int hR = pictureBoxImage.Size.Height / 2;
Rectangle rect = new Rectangle(xR, yR, wR, hR);
g.DrawRectangle(pen_, rect);
int xUpper = xR + wR / 2;
int yLower = yR + hR;
int xLowerRight = xR + wR;
g.DrawLine(pen_, xUpper, yR, xR, yLower);
g.DrawLine(pen_, xUpper, yR, xLowerRight, yLower);
g.DrawLine(pen_, xR, yLower, xLowerRight, yLower);
int centerX = pictureBoxImage.Size.Width / 2;
int centerY = pictureBoxImage.Size.Height / 2;
int radius = Math.Min(centerX, centerY) / 2;
g.DrawEllipse(pen_, centerX - radius, centerY - radius, 2 * radius, 2 * radius);
}
}
}
#endregion
#region public constructors ------------------------------------------------------
public MainForm()
{
this.DoubleBuffered = true;
InitializeComponent();
overlayTextRefreshUpdateTimer_ = new System.Timers.Timer();
overlayTextRefreshUpdateTimer_.Interval = 1000;
overlayTextRefreshUpdateTimer_.Elapsed += new ElapsedEventHandler(OnOverlayTextRefreshUpdate);
}
#endregion
}
}
This class and its functions represent an actual device detected by this interface in the current sys...
Definition Device.cs:91
readonly EnumPropertyI< TAcquisitionStartStopBehaviour > acquisitionStartStopBehaviour
An enumerated integer property defining the start/stop behaviour during acquisition of this driver in...
Definition Device.cs:731
T read()
Reads a value from a property.
Definition EnumPropertyF.cs:323
T read()
Reads a value from a property.
Definition EnumPropertyI.cs:342
The function interface to devices supported by this interface.
Definition FunctionInterface.cs:21
int imageRequestSingle()
Sends an image request to the mv.impact.acquire.Device driver.
Definition FunctionInterface.cs:656
int acquisitionStop()
Manually stops the acquisition engine of this device driver instance.
Definition FunctionInterface.cs:548
int acquisitionStart()
Manually starts the acquisition engine of this device driver instance.
Definition FunctionInterface.cs:521
Request getRequest(int nr)
Returns a const pointer to the desired mv.impact.acquire.Request.
Definition FunctionInterface.cs:452
int imageRequestWaitFor(int timeout_ms)
Waits for a request object to become ready.
Definition FunctionInterface.cs:1021
int imageRequestReset(int requestCtrlNr)
Deletes all requests currently queued for the specified mv.impact.acquire.ImageRequestControl.
Definition FunctionInterface.cs:575
bool isRequestNrValid(int nr)
Check if nr specifies a valid mv.impact.acquire.Request.
Definition FunctionInterface.cs:1098
A class containing a reference to a bitmap created from a request.
Definition RequestBitmapData.cs:31
void Dispose()
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resourc...
Definition RequestBitmapData.cs:80
System.Drawing.Bitmap bitmap
A reference to the System.Drawing.Bitmap contained in this mv.impact.acquire.RequestBitmapData instan...
Definition RequestBitmapData.cs:94
Contains information about a captured buffer.
Definition Request.cs:77
int unlock()
Unlocks the request for the driver again.
Definition Request.cs:619
Contains statistical information.
Definition Statistics.cs:10
readonly PropertyF bandwidthConsumed
A float property (read-only) containing the current bandwidth consumed by this device in KB/s based o...
Definition Statistics.cs:80
readonly PropertyF framesPerSecond
A float property (read-only) containing the current number of frames captured per second.
Definition Statistics.cs:98
DotNetWinFormsBitmapUsage.cs
using System;
using System.Windows.Forms;
namespace mv.impact.acquire.examples
{
static class DotNetWinFormsBitmapUsage
{
[STAThread]
static void Main()
{
mv.impact.acquire.LibraryPath.init(); // this will add the folders containing unmanaged libraries to the PATH variable.
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
A small helper class to administer various library search path related variables and paths.
Definition LibraryPath.cs:14
static void init()
Calling this method will add the folders containing unmanaged libraries to the systems library search...
Definition LibraryPath.cs:251
This namespace contains classes and functions belonging to the image acquisition module of this SDK.
Definition Enumerations.cs:2
Definition Enumerations.cs:2
Definition Enumerations.cs:2