Skip to content

Commit

Permalink
Merge pull request #39 from soukoku/v4-support-bg-handling
Browse files Browse the repository at this point in the history
Added possibility to handle transferred data by app in another thread…
  • Loading branch information
soukoku authored Apr 11, 2023
2 parents b89c186 + fa88dd4 commit d4238f5
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 79 deletions.
37 changes: 26 additions & 11 deletions samples/WinForm32/Form1.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 60 additions & 19 deletions samples/WinForm32/Form1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
{
Expand All @@ -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;

Expand All @@ -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)
Expand Down Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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();
}
}
}
}
42 changes: 37 additions & 5 deletions src/NTwain/Data/BufferedData.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,63 @@
using System;
using System.Buffers;

namespace NTwain.Data
{
/// <summary>
/// Simple struct with bytes buffer and the valid data length.
/// Simple thing with shared bytes buffer and the valid data length.
/// </summary>
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<byte> MemPool = ArrayPool<byte>.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;

/// <summary>
/// Bytes buffer. This may be bigger than the data size
/// and contain invalid data.
/// </summary>
public byte[]? Buffer;
byte[]? _buffer;
public byte[]? Buffer { get; }

/// <summary>
/// Actual usable data length in the buffer.
/// </summary>
public int Length;
int _length;

/// <summary>
/// As a span of usable data.
/// </summary>
/// <returns></returns>
public ReadOnlySpan<byte> AsSpan()
{
if (Buffer != null) return Buffer.AsSpan(0, Length);
if (_buffer != null) return _buffer.AsSpan(0, _length);
return Span<byte>.Empty;
}

public void Dispose()
{
if (_fromPool && _buffer != null)
{
MemPool.Return(_buffer);
_buffer = null;
}
}
}
}
13 changes: 6 additions & 7 deletions src/NTwain/Native/ImageTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static bool IsTiff(IntPtr data)
return test == 0x4949;
}

public static unsafe BufferedData GetBitmapData(System.Buffers.ArrayPool<byte> xferMemPool, IntPtr data)
public static unsafe BufferedData? GetBitmapData(IntPtr data)
{
var infoHeader = Marshal.PtrToStructure<BITMAPINFOHEADER>(data);
if (infoHeader.Validate())
Expand All @@ -40,7 +40,7 @@ public static unsafe BufferedData GetBitmapData(System.Buffers.ArrayPool<byte> 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

Expand All @@ -58,12 +58,12 @@ public static unsafe BufferedData GetBitmapData(System.Buffers.ArrayPool<byte> 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<byte> 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));
Expand All @@ -86,10 +86,9 @@ public static BufferedData GetTiffData(System.Buffers.ArrayPool<byte> 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;
}
Expand Down
7 changes: 0 additions & 7 deletions src/NTwain/TransferReadyEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ public TransferReadyEventArgs(int pendingCount, TWEJ endOfJobFlag)
EndOfJobFlag = endOfJobFlag;
}


/// <summary>
/// Gets or sets whether the current transfer should be skipped
/// and continue next transfer if there are more data.
/// </summary>
public bool SkipCurrent { get; set; }

/// <summary>
/// Gets or sets whether to cancel the capture phase.
/// </summary>
Expand Down
18 changes: 10 additions & 8 deletions src/NTwain/TransferredEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;
Expand All @@ -35,13 +33,13 @@ public TransferredEventArgs(TwainAppSession twain, TW_IMAGEINFO info, TW_SETUPFI
/// </summary>
public bool IsImage { get; }

private readonly BufferedData _data;
private readonly BufferedData? _data;
/// <summary>
/// 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.
/// </summary>
public ReadOnlySpan<byte> Data => _data.AsSpan();
public ReadOnlySpan<byte> Data => _data == null ? ReadOnlySpan<byte>.Empty : _data.AsSpan();

/// <summary>
/// The file info if the transfer involved file information.
Expand Down Expand Up @@ -73,5 +71,9 @@ public STS GetExtendedImageInfo(ref TW_EXTIMAGEINFO container)
return _twain.GetExtendedImageInfo(ref container);
}

public void Dispose()
{
_data?.Dispose();
}
}
}
Loading

0 comments on commit d4238f5

Please sign in to comment.