diff --git a/samples/WinForm32/Form1.Designer.cs b/samples/WinForm32/Form1.Designer.cs
index d242f96..0ee6652 100644
--- a/samples/WinForm32/Form1.Designer.cs
+++ b/samples/WinForm32/Form1.Designer.cs
@@ -41,6 +41,7 @@ private void InitializeComponent()
lblState = new System.Windows.Forms.Label();
label3 = new System.Windows.Forms.Label();
btnOpenDef = new System.Windows.Forms.Button();
+ ckShowUI = new System.Windows.Forms.CheckBox();
capListView = new System.Windows.Forms.ListView();
colCap = new System.Windows.Forms.ColumnHeader();
colType = new System.Windows.Forms.ColumnHeader();
@@ -52,7 +53,7 @@ private void InitializeComponent()
btnStart = new System.Windows.Forms.Button();
btnShowSettings = new System.Windows.Forms.Button();
btnClose = new System.Windows.Forms.Button();
- ckShowUI = new System.Windows.Forms.CheckBox();
+ ckBgImageHandling = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
splitContainer1.Panel1.SuspendLayout();
splitContainer1.Panel2.SuspendLayout();
@@ -167,6 +168,7 @@ private void InitializeComponent()
//
// splitContainer1.Panel2
//
+ splitContainer1.Panel2.Controls.Add(ckBgImageHandling);
splitContainer1.Panel2.Controls.Add(ckShowUI);
splitContainer1.Panel2.Controls.Add(capListView);
splitContainer1.Panel2.Controls.Add(label4);
@@ -207,6 +209,18 @@ private void InitializeComponent()
btnOpenDef.UseVisualStyleBackColor = true;
btnOpenDef.Click += btnOpenDef_Click;
//
+ // ckShowUI
+ //
+ ckShowUI.AutoSize = true;
+ ckShowUI.Checked = true;
+ ckShowUI.CheckState = System.Windows.Forms.CheckState.Checked;
+ ckShowUI.Location = new System.Drawing.Point(437, 35);
+ ckShowUI.Name = "ckShowUI";
+ ckShowUI.Size = new System.Drawing.Size(69, 19);
+ ckShowUI.TabIndex = 9;
+ ckShowUI.Text = "Show UI";
+ ckShowUI.UseVisualStyleBackColor = true;
+ //
// capListView
//
capListView.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
@@ -289,17 +303,17 @@ private void InitializeComponent()
btnClose.UseVisualStyleBackColor = true;
btnClose.Click += btnClose_Click;
//
- // ckShowUI
+ // ckBgImageHandling
//
- ckShowUI.AutoSize = true;
- ckShowUI.Checked = true;
- ckShowUI.CheckState = System.Windows.Forms.CheckState.Checked;
- ckShowUI.Location = new System.Drawing.Point(437, 35);
- ckShowUI.Name = "ckShowUI";
- ckShowUI.Size = new System.Drawing.Size(69, 19);
- ckShowUI.TabIndex = 9;
- ckShowUI.Text = "Show UI";
- ckShowUI.UseVisualStyleBackColor = true;
+ ckBgImageHandling.AutoSize = true;
+ ckBgImageHandling.Checked = true;
+ ckBgImageHandling.CheckState = System.Windows.Forms.CheckState.Checked;
+ ckBgImageHandling.Location = new System.Drawing.Point(512, 35);
+ ckBgImageHandling.Name = "ckBgImageHandling";
+ ckBgImageHandling.Size = new System.Drawing.Size(220, 19);
+ ckBgImageHandling.TabIndex = 10;
+ ckBgImageHandling.Text = "Handle image data in another thread";
+ ckBgImageHandling.UseVisualStyleBackColor = true;
//
// Form1
//
@@ -345,5 +359,6 @@ private void InitializeComponent()
private System.Windows.Forms.ColumnHeader colSupport;
private System.Windows.Forms.ColumnHeader colExtended;
private System.Windows.Forms.CheckBox ckShowUI;
+ private System.Windows.Forms.CheckBox ckBgImageHandling;
}
}
\ No newline at end of file
diff --git a/samples/WinForm32/Form1.cs b/samples/WinForm32/Form1.cs
index a656e5f..421ee39 100644
--- a/samples/WinForm32/Form1.cs
+++ b/samples/WinForm32/Form1.cs
@@ -6,12 +6,11 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
-using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Runtime.Versioning;
using System.Threading;
+using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinFormSample
@@ -20,6 +19,8 @@ public partial class Form1 : Form
{
private TwainAppSession twain;
private readonly string saveFolder;
+ readonly Stopwatch watch = new();
+ private bool _useThreadForImag;
public Form1()
{
@@ -33,8 +34,9 @@ public Form1()
twain.StateChanged += Twain_StateChanged;
twain.DefaultSourceChanged += Twain_DefaultSourceChanged;
twain.CurrentSourceChanged += Twain_CurrentSourceChanged;
+ twain.SourceDisabled += Twain_SourceDisabled;
twain.TransferReady += Twain_TransferReady;
- twain.Transferred += Twain_DataTransferred;
+ twain.Transferred += Twain_Transferred;
twain.TransferError += Twain_TransferError;
twain.DeviceEvent += Twain_DeviceEvent;
@@ -47,6 +49,15 @@ public Form1()
this.Disposed += Form1_Disposed;
}
+ private void Twain_SourceDisabled(TwainAppSession sender, TW_IDENTITY_LEGACY e)
+ {
+ if (watch.IsRunning)
+ {
+ watch.Stop();
+ MessageBox.Show($"Took {watch.Elapsed} to finish that transfer.");
+ }
+ }
+
private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
switch (e.Reason)
@@ -103,28 +114,54 @@ private void Twain_TransferError(TwainAppSession sender, TransferErrorEventArgs
}
- private void Twain_DataTransferred(TwainAppSession sender, TransferredEventArgs e)
+ private void Twain_Transferred(TwainAppSession sender, TransferredEventArgs e)
{
Debug.WriteLine($"[thread {Environment.CurrentManagedThreadId}] data transferred with info {e.ImageInfo}");
- if (e.Data == null) return;
+ // if using a high-speed scanner, imaging handling could be a bottleneck
+ // so it's possible to pass the data to another thread while the scanning
+ // loop happens. Just remember to dispose it after.
- // example of using some lib to handle image data
- var saveFile = Path.Combine(saveFolder, (DateTime.Now.Ticks / 1000).ToString());
- using (var img = new ImageMagick.MagickImage(e.Data))
+ if (_useThreadForImag)
{
- if (img.ColorType == ImageMagick.ColorType.Palette)
+ // bad thread example but whatev. should use a dedicated thread of some sort for real
+ Task.Run(() =>
{
- // bw or gray
- saveFile += ".png";
- }
- else
+ HandleTransferredData(e);
+ });
+ }
+ else
+ {
+ HandleTransferredData(e);
+ }
+ }
+
+ private void HandleTransferredData(TransferredEventArgs e)
+ {
+ try
+ {
+ // example of using some lib to handle image data
+ var saveFile = Path.Combine(saveFolder, (DateTime.Now.Ticks / 1000).ToString());
+ using (var img = new ImageMagick.MagickImage(e.Data))
{
- // color
- saveFile += ".jpg";
- img.Quality = 75;
+ if (img.ColorType == ImageMagick.ColorType.Palette)
+ {
+ // bw or gray
+ saveFile += ".png";
+ }
+ else
+ {
+ // color
+ saveFile += ".jpg";
+ img.Quality = 75;
+ }
+ img.Write(saveFile);
+ Debug.WriteLine($"Saved image to {saveFile}");
}
- img.Write(saveFile);
- Debug.WriteLine($"Saved image to {saveFile}");
+ }
+ catch { }
+ finally
+ {
+ e.Dispose();
}
}
@@ -362,7 +399,11 @@ private void btnShowSettings_Click(object sender, EventArgs e)
private void btnStart_Click(object sender, EventArgs e)
{
- twain.EnableSource(ckShowUI.Checked, false);
+ if (twain.EnableSource(ckShowUI.Checked, false).IsSuccess)
+ {
+ _useThreadForImag = ckBgImageHandling.Checked;
+ watch.Restart();
+ }
}
}
}
\ No newline at end of file
diff --git a/src/NTwain/Data/BufferedData.cs b/src/NTwain/Data/BufferedData.cs
index 1705c01..3130bb9 100644
--- a/src/NTwain/Data/BufferedData.cs
+++ b/src/NTwain/Data/BufferedData.cs
@@ -1,22 +1,45 @@
using System;
+using System.Buffers;
namespace NTwain.Data
{
///
- /// Simple struct with bytes buffer and the valid data length.
+ /// Simple thing with shared bytes buffer and the valid data length.
///
- public struct BufferedData
+ public class BufferedData : IDisposable
{
+ // experiment using array pool for things transferred in memory.
+ // this can pool up to a "normal" max of legal size paper in 24 bit at 300 dpi (~31MB)
+ // so the array max is made with 32 MB. Typical usage should be a lot less.
+ internal static readonly ArrayPool MemPool = ArrayPool.Create(32 * 1024 * 1024, 8);
+
+ public BufferedData(int size)
+ {
+ _buffer = MemPool.Rent(size);
+ _length = size;
+ _fromPool = true;
+ }
+
+ internal BufferedData(byte[] data, int size, bool fromPool)
+ {
+ _buffer = data;
+ _length = size;
+ _fromPool = fromPool;
+ }
+
+ bool _fromPool;
+
///
/// Bytes buffer. This may be bigger than the data size
/// and contain invalid data.
///
- public byte[]? Buffer;
+ byte[]? _buffer;
+ public byte[]? Buffer { get; }
///
/// Actual usable data length in the buffer.
///
- public int Length;
+ int _length;
///
/// As a span of usable data.
@@ -24,8 +47,17 @@ public struct BufferedData
///
public ReadOnlySpan AsSpan()
{
- if (Buffer != null) return Buffer.AsSpan(0, Length);
+ if (_buffer != null) return _buffer.AsSpan(0, _length);
return Span.Empty;
}
+
+ public void Dispose()
+ {
+ if (_fromPool && _buffer != null)
+ {
+ MemPool.Return(_buffer);
+ _buffer = null;
+ }
+ }
}
}
diff --git a/src/NTwain/Native/ImageTools.cs b/src/NTwain/Native/ImageTools.cs
index df4a5bf..d987285 100644
--- a/src/NTwain/Native/ImageTools.cs
+++ b/src/NTwain/Native/ImageTools.cs
@@ -24,7 +24,7 @@ public static bool IsTiff(IntPtr data)
return test == 0x4949;
}
- public static unsafe BufferedData GetBitmapData(System.Buffers.ArrayPool xferMemPool, IntPtr data)
+ public static unsafe BufferedData? GetBitmapData(IntPtr data)
{
var infoHeader = Marshal.PtrToStructure(data);
if (infoHeader.Validate())
@@ -40,7 +40,7 @@ public static unsafe BufferedData GetBitmapData(System.Buffers.ArrayPool x
};
fileHeader.bfSize = fileHeader.bfOffBits + infoHeader.biSizeImage;
- var dataCopy = xferMemPool.Rent((int)fileHeader.bfSize); // new byte[fileHeader.bfSize];
+ var dataCopy = BufferedData.MemPool.Rent((int)fileHeader.bfSize); // new byte[fileHeader.bfSize];
// TODO: run benchmark on which one is faster
@@ -58,12 +58,12 @@ public static unsafe BufferedData GetBitmapData(System.Buffers.ArrayPool x
// write image
Marshal.Copy(data, dataCopy, fileHeaderSize, (int)fileHeader.bfSize - fileHeaderSize);
- return new BufferedData { Buffer = dataCopy, Length = (int)fileHeader.bfSize };
+ return new BufferedData(dataCopy, (int)fileHeader.bfSize, true);
}
return default;
}
- public static BufferedData GetTiffData(System.Buffers.ArrayPool xferMemPool, IntPtr data)
+ public static BufferedData? GetTiffData(IntPtr data)
{
// Find the size of the image so we can turn it into a memory stream...
var headerSize = Marshal.SizeOf(typeof(TIFFHEADER));
@@ -86,10 +86,9 @@ public static BufferedData GetTiffData(System.Buffers.ArrayPool xferMemPoo
if (tiffSize > 0)
{
- var dataCopy = xferMemPool.Rent(tiffSize);// new byte[tiffSize];
- // is this optimal?
+ var dataCopy = BufferedData.MemPool.Rent(tiffSize);// new byte[tiffSize];
Marshal.Copy(data, dataCopy, 0, tiffSize);
- return new BufferedData { Buffer = dataCopy, Length = tiffSize };
+ return new BufferedData(dataCopy, tiffSize, true);
}
return default;
}
diff --git a/src/NTwain/TransferReadyEventArgs.cs b/src/NTwain/TransferReadyEventArgs.cs
index 8d6a5f7..9b88486 100644
--- a/src/NTwain/TransferReadyEventArgs.cs
+++ b/src/NTwain/TransferReadyEventArgs.cs
@@ -15,13 +15,6 @@ public TransferReadyEventArgs(int pendingCount, TWEJ endOfJobFlag)
EndOfJobFlag = endOfJobFlag;
}
-
- ///
- /// Gets or sets whether the current transfer should be skipped
- /// and continue next transfer if there are more data.
- ///
- public bool SkipCurrent { get; set; }
-
///
/// Gets or sets whether to cancel the capture phase.
///
diff --git a/src/NTwain/TransferredEventArgs.cs b/src/NTwain/TransferredEventArgs.cs
index 2d6c2bc..cbbc558 100644
--- a/src/NTwain/TransferredEventArgs.cs
+++ b/src/NTwain/TransferredEventArgs.cs
@@ -3,10 +3,8 @@
namespace NTwain
{
- // TODO: maybe a 2-level "dispose" with end of event being 1
- // and manual dispose 2 for perf if this is not good enough.
- public class TransferredEventArgs : EventArgs
+ public class TransferredEventArgs : EventArgs, IDisposable
{
public TransferredEventArgs(TW_AUDIOINFO info, TW_SETUPFILEXFER fileInfo)
{
@@ -19,7 +17,7 @@ public TransferredEventArgs(TW_AUDIOINFO info, BufferedData data)
_data = data;
}
- public TransferredEventArgs(TwainAppSession twain, TW_IMAGEINFO info, TW_SETUPFILEXFER? fileInfo, BufferedData data)
+ public TransferredEventArgs(TwainAppSession twain, TW_IMAGEINFO info, TW_SETUPFILEXFER? fileInfo, BufferedData? data)
{
ImageInfo = info;
FileInfo = fileInfo;
@@ -35,13 +33,13 @@ public TransferredEventArgs(TwainAppSession twain, TW_IMAGEINFO info, TW_SETUPFI
///
public bool IsImage { get; }
- private readonly BufferedData _data;
+ private readonly BufferedData? _data;
///
/// The complete file data if memory was involved in the transfer.
- /// IMPORTANT: Content of this array may not valid once
- /// the event handler ends.
+ /// IMPORTANT: Content of this array will not be valid once
+ /// this event arg has been disposed.
///
- public ReadOnlySpan Data => _data.AsSpan();
+ public ReadOnlySpan Data => _data == null ? ReadOnlySpan.Empty : _data.AsSpan();
///
/// The file info if the transfer involved file information.
@@ -73,5 +71,9 @@ public STS GetExtendedImageInfo(ref TW_EXTIMAGEINFO container)
return _twain.GetExtendedImageInfo(ref container);
}
+ public void Dispose()
+ {
+ _data?.Dispose();
+ }
}
}
\ No newline at end of file
diff --git a/src/NTwain/TwainAppSession.Xfers.cs b/src/NTwain/TwainAppSession.Xfers.cs
index 601b4cf..0e195a3 100644
--- a/src/NTwain/TwainAppSession.Xfers.cs
+++ b/src/NTwain/TwainAppSession.Xfers.cs
@@ -12,11 +12,6 @@ namespace NTwain
partial class TwainAppSession
{
- // experiment using array pool for things transferred in memory.
- // this can pool up to a "normal" max of legal size paper in 24 bit at 300 dpi (~31MB)
- // so the array max is made with 32 MB. Typical usage should be a lot less.
- static readonly ArrayPool XferMemPool = ArrayPool.Create(32 * 1024 * 1024, 8);
-
///
/// Can only be called in state 7, so it's hidden here and
/// only exposed in data transferred event.
@@ -265,11 +260,11 @@ private STS TransferNativeAudio(ref TW_PENDINGXFERS pending)
{
State = STATE.S7;
lockedPtr = Lock(dataPtr);
- BufferedData data = default;
+ BufferedData? data = default;
// TODO: don't know how to read wav/aiff from pointer yet
- if (data.Buffer != null)
+ if (data != null)
{
try
{
@@ -277,10 +272,9 @@ private STS TransferNativeAudio(ref TW_PENDINGXFERS pending)
var args = new TransferredEventArgs(info, data);
Transferred?.Invoke(this, args);
}
- catch { }
- finally
+ catch
{
- XferMemPool.Return(data.Buffer);
+ data.Dispose();
}
}
}
@@ -321,7 +315,7 @@ private STS TransferMemoryImage(ref TW_PENDINGXFERS pending)
};
memXferOSX.Memory = memXfer.Memory;
- byte[] dotnetBuff = XferMemPool.Rent((int)buffSize);
+ byte[] dotnetBuff = BufferedData.MemPool.Rent((int)buffSize);
try
{
do
@@ -354,7 +348,7 @@ private STS TransferMemoryImage(ref TW_PENDINGXFERS pending)
finally
{
if (memPtr != IntPtr.Zero) Free(memPtr);
- XferMemPool.Return(dotnetBuff);
+ BufferedData.MemPool.Return(dotnetBuff);
}
@@ -400,7 +394,7 @@ private STS TransferMemoryFileImage(ref TW_PENDINGXFERS pending)
// TODO: how to get actual file size before hand? Is it imagelayout?
// otherwise will just write to stream with lots of copies
- byte[] dotnetBuff = XferMemPool.Rent((int)buffSize);
+ byte[] dotnetBuff = BufferedData.MemPool.Rent((int)buffSize);
using var outStream = new MemoryStream();
try
{
@@ -431,7 +425,7 @@ private STS TransferMemoryFileImage(ref TW_PENDINGXFERS pending)
finally
{
if (memPtr != IntPtr.Zero) Free(memPtr);
- XferMemPool.Return(dotnetBuff);
+ BufferedData.MemPool.Return(dotnetBuff);
}
if (rc == TWRC.XFERDONE)
@@ -440,7 +434,7 @@ private STS TransferMemoryFileImage(ref TW_PENDINGXFERS pending)
{
DGImage.ImageInfo.Get(ref _appIdentity, ref _currentDS, out TW_IMAGEINFO info);
// ToArray bypasses the XferMemPool but I guess this will have to do for now
- var args = new TransferredEventArgs(this, info, fileSetup, new BufferedData { Buffer = outStream.ToArray(), Length = (int)outStream.Length });
+ var args = new TransferredEventArgs(this, info, fileSetup, new BufferedData(outStream.ToArray(), (int)outStream.Length, false));
Transferred?.Invoke(this, args);
}
catch { }
@@ -494,15 +488,15 @@ private STS TransferNativeImage(ref TW_PENDINGXFERS pending)
{
State = STATE.S7;
lockedPtr = Lock(dataPtr);
- BufferedData data = default;
+ BufferedData? data = default;
if (ImageTools.IsDib(lockedPtr))
{
- data = ImageTools.GetBitmapData(XferMemPool, lockedPtr);
+ data = ImageTools.GetBitmapData(lockedPtr);
}
else if (ImageTools.IsTiff(lockedPtr))
{
- data = ImageTools.GetTiffData(XferMemPool, lockedPtr);
+ data = ImageTools.GetTiffData(lockedPtr);
}
else
{
@@ -510,7 +504,7 @@ private STS TransferNativeImage(ref TW_PENDINGXFERS pending)
// don't support more formats :(
}
- if (data.Buffer != null)
+ if (data != null)
{
try
{
@@ -519,10 +513,9 @@ private STS TransferNativeImage(ref TW_PENDINGXFERS pending)
var args = new TransferredEventArgs(this, info, null, data);
Transferred?.Invoke(this, args);
}
- catch { }
- finally
+ catch
{
- XferMemPool.Return(data.Buffer);
+ data.Dispose();
}
}