Fixed wrong assumption of rented array size and added example of using non system.drawing image lib.

This commit is contained in:
Eugene Wang
2023-04-08 18:38:51 -04:00
parent 29a8031817
commit 2474fbf74d
11 changed files with 101 additions and 43 deletions

View File

@@ -1,4 +1,5 @@
using System;
using NTwain.Data;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -11,8 +12,8 @@ namespace WinFormSample
{
/// <summary>
/// For demoing loading dsm from custom path in case
/// it's not installed on system and can't be placed
/// besides the exe.
/// it's not installed on system and don't want to be
/// placed besides the exe.
/// </summary>
static class DsmLoader
{
@@ -24,7 +25,7 @@ namespace WinFormSample
{
var dll = Path.Combine(
Path.GetDirectoryName(Environment.ProcessPath ?? Assembly.GetExecutingAssembly().Location)!,
"platforms\\TWAINDSM.dll");
$@"runtimes\win-{(TwainPlatform.Is32bit ? "x86" : "x64")}\native\TWAINDSM.dll");
__dllPtr = LoadLibraryW(dll);
}

View File

@@ -105,11 +105,23 @@ namespace WinFormSample
{
Debug.WriteLine($"[thread {Environment.CurrentManagedThreadId}] data transferred with info {e.ImageInfo}");
if (e.Data == null) return;
using (var stream = new MemoryStream(e.Data))
using (var img = Image.FromStream(stream))
// 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))
{
var saveFile = Path.Combine(saveFolder, (DateTime.Now.Ticks / 1000) + ".png");
img.Save(saveFile, ImageFormat.Png);
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}");
}
}
@@ -158,7 +170,7 @@ namespace WinFormSample
twain.GetCapValues(CAP.CAP_EXTENDEDCAPS, out IList<CAP> extended);
foreach (var c in caps)
{
ListViewItem it = new(c.ToString());
ListViewItem it = new(GetFriendlyName(c));
if (twain.GetCapCurrent(c, out TW_CAPABILITY twcap).RC == TWRC.SUCCESS)
{
@@ -180,6 +192,15 @@ namespace WinFormSample
}
}
private string GetFriendlyName(CAP c)
{
if (c > CAP.CAP_CUSTOMBASE)
{
return $"{CAP.CAP_CUSTOMBASE} + {c - CAP.CAP_CUSTOMBASE}";
}
return c.ToString();
}
// there may be a better way...
MethodInfo[] twainMethods = typeof(TwainAppSession).GetMethods();

View File

@@ -9,6 +9,10 @@
<RootNamespace>WinFormSample</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q8-x86" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\NTwain\NTwain.csproj" />
</ItemGroup>
@@ -17,7 +21,7 @@
<None Update="platforms\qwindows.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="platforms\TWAINDSM.dll">
<None Update="runtimes\win-x86\native\TWAINDSM.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

View File

@@ -13,6 +13,10 @@
<ProjectReference Include="..\..\src\NTwain\NTwain.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q8-x64" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\WinForm32\**\*.cs" Exclude="..\WinForm32\**\obj\**;..\WinForm32\**\bin\**">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
@@ -26,7 +30,7 @@
<None Update="platforms\qwindows.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="platforms\TWAINDSM.dll">
<None Update="runtimes\win-x64\native\TWAINDSM.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

View File

@@ -0,0 +1,31 @@
using System;
namespace NTwain.Data
{
/// <summary>
/// Simple struct with bytes buffer and the valid data length.
/// </summary>
public struct BufferedData
{
/// <summary>
/// Bytes buffer. This may be bigger than the data size
/// and contain invalid data.
/// </summary>
public byte[]? Buffer;
/// <summary>
/// Actual usable data length in the buffer.
/// </summary>
public int Length;
/// <summary>
/// As a span of usable data.
/// </summary>
/// <returns></returns>
public ReadOnlySpan<byte> AsSpan()
{
if (Buffer != null) return Buffer.AsSpan(0, Length);
return Span<byte>.Empty;
}
}
}

View File

@@ -13,18 +13,18 @@ namespace NTwain
AudioInfo = info;
FileInfo = fileInfo;
}
public DataTransferredEventArgs(TW_AUDIOINFO info, byte[] data)
public DataTransferredEventArgs(TW_AUDIOINFO info, BufferedData data)
{
AudioInfo = info;
Data = data;
_data = data;
}
public DataTransferredEventArgs(TW_IMAGEINFO info, TW_SETUPFILEXFER? fileInfo, byte[]? data)
public DataTransferredEventArgs(TW_IMAGEINFO info, TW_SETUPFILEXFER? fileInfo, BufferedData data)
{
ImageInfo = info;
FileInfo = fileInfo;
IsImage = true;
Data = data;
_data = data;
}
/// <summary>
@@ -32,12 +32,13 @@ namespace NTwain
/// </summary>
public bool IsImage { get; }
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.
/// </summary>
public byte[]? Data { get; }
public ReadOnlySpan<byte> Data => _data.AsSpan();
/// <summary>
/// The file info if the transfer involved file information.

View File

@@ -13,17 +13,15 @@
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net462'">
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<!--<PackageReference Include="System.Buffers" Version="4.5.1" />-->
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'net462'">
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="DSM\DSMGenerator.dummy">
<DesignTime>True</DesignTime>

View File

@@ -1,4 +1,5 @@
using System;
using NTwain.Data;
using System;
using System.Runtime.InteropServices;
namespace NTwain.Native
@@ -23,15 +24,13 @@ namespace NTwain.Native
return test == 0x4949;
}
public static unsafe byte[]? GetBitmapData(System.Buffers.ArrayPool<byte> xferMemPool, IntPtr data)
public static unsafe BufferedData GetBitmapData(System.Buffers.ArrayPool<byte> xferMemPool, IntPtr data)
{
var infoHeader = Marshal.PtrToStructure<BITMAPINFOHEADER>(data);
if (infoHeader.Validate())
{
var fileHeaderSize = Marshal.SizeOf(typeof(BITMAPFILEHEADER));
var fileHeader = new BITMAPFILEHEADER
{
bfType = 0x4D42, // "BM"
@@ -41,7 +40,7 @@ namespace NTwain.Native
};
fileHeader.bfSize = fileHeader.bfOffBits + infoHeader.biSizeImage;
var dataCopy = xferMemPool.Rent((int)fileHeader.bfSize);// new byte[fileHeader.bfSize];
var dataCopy = xferMemPool.Rent((int)fileHeader.bfSize); // new byte[fileHeader.bfSize];
// TODO: run benchmark on which one is faster
@@ -59,13 +58,12 @@ namespace NTwain.Native
// write image
Marshal.Copy(data, dataCopy, fileHeaderSize, (int)fileHeader.bfSize - fileHeaderSize);
return dataCopy;
return new BufferedData { Buffer = dataCopy, Length = (int)fileHeader.bfSize };
}
return null;
return default;
}
public static byte[]? GetTiffData(System.Buffers.ArrayPool<byte> xferMemPool, IntPtr data)
public static BufferedData GetTiffData(System.Buffers.ArrayPool<byte> xferMemPool, IntPtr data)
{
// Find the size of the image so we can turn it into a memory stream...
var headerSize = Marshal.SizeOf(typeof(TIFFHEADER));
@@ -91,9 +89,9 @@ namespace NTwain.Native
var dataCopy = xferMemPool.Rent(tiffSize);// new byte[tiffSize];
// is this optimal?
Marshal.Copy(data, dataCopy, 0, tiffSize);
return dataCopy;
return new BufferedData { Buffer = dataCopy, Length = tiffSize };
}
return null;
return default;
}
//internal static Bitmap ReadBitmapImage(IntPtr data)

View File

@@ -10,6 +10,7 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Xml.Serialization;
using static NTwain.Native.ImageTools;
namespace NTwain
{
@@ -66,7 +67,7 @@ namespace NTwain
do
{
var readyArgs = new TransferReadyEventArgs(pending.Count, (TWEJ)pending.EOJ);
_uiThreadMarshaller.Invoke((ref TW_PENDINGXFERS pending) =>
_uiThreadMarshaller.Invoke(() =>
{
try
{
@@ -159,7 +160,7 @@ namespace NTwain
if (State >= STATE.S5)
{
_uiThreadMarshaller.BeginInvoke((ref TW_PENDINGXFERS pending) =>
_uiThreadMarshaller.BeginInvoke(() =>
{
DisableSource();
});
@@ -245,11 +246,11 @@ namespace NTwain
{
State = STATE.S7;
lockedPtr = Lock(dataPtr);
byte[]? data = null;
BufferedData data = default;
// TODO: don't know how to read wav/aiff from pointer yet
if (data != null)
if (data.Buffer != null)
{
try
{
@@ -260,7 +261,7 @@ namespace NTwain
catch { }
finally
{
XferMemPool.Return(data);
XferMemPool.Return(data.Buffer);
}
}
}
@@ -420,7 +421,7 @@ namespace NTwain
{
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 DataTransferredEventArgs(info, fileSetup, outStream.ToArray());
var args = new DataTransferredEventArgs(info, fileSetup, new BufferedData { Buffer = outStream.ToArray(), Length = (int)outStream.Length });
DataTransferred?.Invoke(this, args);
}
catch { }
@@ -448,7 +449,7 @@ namespace NTwain
try
{
DGImage.ImageInfo.Get(ref _appIdentity, ref _currentDS, out TW_IMAGEINFO info);
var args = new DataTransferredEventArgs(info, fileSetup, null);
var args = new DataTransferredEventArgs(info, fileSetup, default);
DataTransferred?.Invoke(this, args);
}
catch { }
@@ -473,8 +474,7 @@ namespace NTwain
{
State = STATE.S7;
lockedPtr = Lock(dataPtr);
byte[]? data = null;
BufferedData data = default;
if (ImageTools.IsDib(lockedPtr))
{
@@ -490,7 +490,7 @@ namespace NTwain
// don't support more formats :(
}
if (data != null)
if (data.Buffer != null)
{
try
{
@@ -501,7 +501,7 @@ namespace NTwain
catch { }
finally
{
XferMemPool.Return(data);
XferMemPool.Return(data.Buffer);
}
}