From 20df6fc74c84d877d38e7f2b06a451b41d649c2b Mon Sep 17 00:00:00 2001 From: Eugene Wang <8755753+soukoku@users.noreply.github.com> Date: Fri, 7 Apr 2023 08:25:39 -0400 Subject: [PATCH] Ported old native image xfer with shared buffer test. --- samples/WinForm32/ControlExtensions.cs | 22 ++++ samples/WinForm32/Form1.cs | 87 +++++++------ src/NTwain/Data/TWAINH.cs | 14 +-- src/NTwain/Data/TWAINH_EXTRAS.cs | 8 ++ src/NTwain/DataTransferredEventArgs.cs | 44 ++++--- src/NTwain/Native/ImageTools.cs | 140 +++++++++++++++++++++ src/NTwain/TwainAppSession.PropEvents.cs | 14 +-- src/NTwain/TwainAppSession.Xfers.cs | 149 +++++++++++++++++------ src/NTwain/TwainEventDelegate.cs | 11 ++ 9 files changed, 384 insertions(+), 105 deletions(-) create mode 100644 samples/WinForm32/ControlExtensions.cs create mode 100644 src/NTwain/Native/ImageTools.cs create mode 100644 src/NTwain/TwainEventDelegate.cs diff --git a/samples/WinForm32/ControlExtensions.cs b/samples/WinForm32/ControlExtensions.cs new file mode 100644 index 0000000..8993568 --- /dev/null +++ b/samples/WinForm32/ControlExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace WinFormSample +{ + static class ControlExtensions + { + public static void SetDoubleBuffered(this Control control, bool value) + { + if (SystemInformation.TerminalServerSession) return; + + var dbprop = control.GetType().GetProperty("DoubleBuffered", BindingFlags.NonPublic | BindingFlags.Instance); + dbprop!.SetValue(control, value); + } + + } +} diff --git a/samples/WinForm32/Form1.cs b/samples/WinForm32/Form1.cs index 68b64a6..5668233 100644 --- a/samples/WinForm32/Form1.cs +++ b/samples/WinForm32/Form1.cs @@ -25,18 +25,33 @@ namespace WinFormSample twain.StateChanged += Twain_StateChanged; twain.DefaultSourceChanged += Twain_DefaultSourceChanged; twain.CurrentSourceChanged += Twain_CurrentSourceChanged; + twain.TransferReady += Twain_TransferReady; + twain.DataTransferred += Twain_DataTransferred; + twain.TransferError += Twain_TransferError; + twain.DeviceEvent += Twain_DeviceEvent; - SetDoubleBuffered(capListView); + capListView.SetDoubleBuffered(true); this.Disposed += Form1_Disposed; } - static void SetDoubleBuffered(Control control) + protected override void OnHandleCreated(EventArgs e) { - if (SystemInformation.TerminalServerSession) return; + base.OnHandleCreated(e); - var dbprop = control.GetType().GetProperty(nameof(DoubleBuffered), BindingFlags.NonPublic | BindingFlags.Instance); - dbprop!.SetValue(control, true); + + var hwnd = this.Handle; + var rc = twain.OpenDSM(hwnd); + twain.AddWinformFilter(); + Debug.WriteLine($"OpenDSM={rc}"); + } + + protected override void OnClosing(CancelEventArgs e) + { + var finalState = twain.TryStepdown(STATE.S2); + Debug.WriteLine($"Stepdown result state={finalState}"); + twain.RemoveWinformFilter(); + base.OnClosing(e); } private void Form1_Disposed(object? sender, EventArgs e) @@ -44,7 +59,38 @@ namespace WinFormSample twain.Dispose(); } - private void Twain_CurrentSourceChanged(TwainAppSession arg1, TW_IDENTITY_LEGACY ds) + + private void Twain_DeviceEvent(TwainAppSession sender, TW_DEVICEEVENT e) + { + + } + + private void Twain_TransferError(TwainAppSession sender, TransferErrorEventArgs e) + { + + } + + private void Twain_DataTransferred(TwainAppSession sender, DataTransferredEventArgs e) + { + Debug.WriteLine($"Image transferred with info {e.ImageInfo}"); + } + + private void Twain_TransferReady(TwainAppSession sender, TransferReadyEventArgs e) + { + + } + + private void Twain_DefaultSourceChanged(TwainAppSession sender, TW_IDENTITY_LEGACY ds) + { + lblDefault.Text = ds.ProductName; + } + + private void Twain_StateChanged(TwainAppSession sender, STATE state) + { + Invoke(()=> lblState.Text = state.ToString()); + } + + private void Twain_CurrentSourceChanged(TwainAppSession sender, TW_IDENTITY_LEGACY ds) { lblCurrent.Text = ds.ProductName; if (twain.State == STATE.S4) @@ -170,35 +216,6 @@ namespace WinFormSample return ""; } - private void Twain_DefaultSourceChanged(TwainAppSession arg1, TW_IDENTITY_LEGACY ds) - { - lblDefault.Text = ds.ProductName; - } - - private void Twain_StateChanged(TwainAppSession session, STATE state) - { - lblState.Text = state.ToString(); - } - - protected override void OnHandleCreated(EventArgs e) - { - base.OnHandleCreated(e); - - - var hwnd = this.Handle; - var rc = twain.OpenDSM(hwnd); - twain.AddWinformFilter(); - Debug.WriteLine($"OpenDSM={rc}"); - } - - protected override void OnClosing(CancelEventArgs e) - { - var finalState = twain.TryStepdown(STATE.S2); - Debug.WriteLine($"Stepdown result state={finalState}"); - twain.RemoveWinformFilter(); - base.OnClosing(e); - } - private void btnSelect_Click(object sender, EventArgs e) { twain.ShowUserSelect(); diff --git a/src/NTwain/Data/TWAINH.cs b/src/NTwain/Data/TWAINH.cs index c639b27..987c780 100644 --- a/src/NTwain/Data/TWAINH.cs +++ b/src/NTwain/Data/TWAINH.cs @@ -2193,7 +2193,7 @@ namespace NTwain.Data /// Describes the “real” image data, that is, the complete image being transferred between the Source and application. /// [StructLayout(LayoutKind.Sequential, Pack = 2)] - public struct TW_IMAGEINFO + public partial struct TW_IMAGEINFO { public TW_FIX32 XResolution; public TW_FIX32 YResolution; @@ -2209,9 +2209,9 @@ namespace NTwain.Data public short BitsPerSample_6; public short BitsPerSample_7; public short BitsPerPixel; - public ushort Planar; - public short PixelType; - public ushort Compression; + public TW_BOOL Planar; + public TWPT PixelType; + public TWCP Compression; } [StructLayout(LayoutKind.Sequential, Pack = 2)] public struct TW_IMAGEINFO_LINUX64 @@ -2230,9 +2230,9 @@ namespace NTwain.Data public short BitsPerSample_6; public short BitsPerSample_7; public short BitsPerPixel; - public ushort Planar; - public short PixelType; - public ushort Compression; + public TW_BOOL Planar; + public TWPT PixelType; + public TWCP Compression; } /// diff --git a/src/NTwain/Data/TWAINH_EXTRAS.cs b/src/NTwain/Data/TWAINH_EXTRAS.cs index 70366ec..715b7ea 100644 --- a/src/NTwain/Data/TWAINH_EXTRAS.cs +++ b/src/NTwain/Data/TWAINH_EXTRAS.cs @@ -754,6 +754,14 @@ namespace NTwain.Data } } + partial struct TW_IMAGEINFO + { + public override string ToString() + { + return $"{ImageWidth}x{ImageLength} {PixelType} {Compression} {BitsPerPixel}bpp"; + } + } + //partial struct TW_DEVICEEVENT //{ // public TWDE Event { get { return (TWDE)_event; } } diff --git a/src/NTwain/DataTransferredEventArgs.cs b/src/NTwain/DataTransferredEventArgs.cs index 87e4c79..8671ce1 100644 --- a/src/NTwain/DataTransferredEventArgs.cs +++ b/src/NTwain/DataTransferredEventArgs.cs @@ -1,46 +1,54 @@ -using System; -using System.Buffers; +using NTwain.Data; +using System; namespace NTwain { - public class DataTransferredEventArgs : EventArgs, IDisposable + public class DataTransferredEventArgs : EventArgs { + readonly TwainAppSession _twain; + readonly bool _isImage; + /// - /// Ctor for pooled data and the pool to clean it up. + /// Ctor for array data; /// - /// + /// /// - internal DataTransferredEventArgs(ArrayPool? pool, byte[]? data) + internal DataTransferredEventArgs(TwainAppSession twain, byte[] data, bool isImage) { - _pool = pool; + this._twain = twain; Data = data; + this._isImage = isImage; } - bool _disposed; - private readonly ArrayPool? _pool; - /// /// The complete file data if the transfer was done /// through memory. IMPORTANT: The data held /// in this array will no longer be valid once - /// this event arg has been disposed. + /// the event handler ends. /// - public byte[]? Data { get; private set; } + public byte[]? Data { get; } + TW_IMAGEINFO? _imgInfo; - public void Dispose() + /// + /// Gets the final image information if applicable. + /// + public TW_IMAGEINFO? ImageInfo { - if (!_disposed) + get { - if (_pool != null && Data != null) + if (_isImage && _imgInfo == null) { - _pool.Return(Data); - Data = null; + if (_twain.GetImageInfo(out TW_IMAGEINFO info).RC == TWRC.SUCCESS) + { + _imgInfo = info; + } } - _disposed = true; + return _imgInfo; } } + } } diff --git a/src/NTwain/Native/ImageTools.cs b/src/NTwain/Native/ImageTools.cs new file mode 100644 index 0000000..05e410b --- /dev/null +++ b/src/NTwain/Native/ImageTools.cs @@ -0,0 +1,140 @@ +using System; +using System.Runtime.InteropServices; + +namespace NTwain.Native +{ + static class ImageTools + { + // this is modified from twain cs sample + // http://sourceforge.net/projects/twainforcsharp/?source=typ_redirect + + public static bool IsDib(IntPtr data) + { + // a quick check not guaranteed correct, + // compare first 2 bytes to size of struct (which is also the first field) + var test = Marshal.ReadInt16(data); + // should be 40 + return test == BITMAPINFOHEADER.GetByteSize(); + } + public static bool IsTiff(IntPtr data) + { + var test = Marshal.ReadInt16(data); + // should be II + return test == 0x4949; + } + + + public static byte[]? GetBitmapData(System.Buffers.ArrayPool xferMemPool, IntPtr data) + { + var infoHeader = (BITMAPINFOHEADER)Marshal.PtrToStructure(data, typeof(BITMAPINFOHEADER)); + if (infoHeader.Validate()) + { + var fileHeaderSize = Marshal.SizeOf(typeof(BITMAPFILEHEADER)); + + + var fileHeader = new BITMAPFILEHEADER + { + bfType = 0x4D42, // "BM" + bfOffBits = (uint)fileHeaderSize + + infoHeader.biSize + + (infoHeader.biClrUsed * 4) + }; + fileHeader.bfSize = fileHeader.bfOffBits + infoHeader.biSizeImage; + + var dataCopy = xferMemPool.Rent((int)fileHeader.bfSize);// new byte[fileHeader.bfSize]; + + // TODO: reduce extra alloc + // write file header + IntPtr tempPtr = Marshal.AllocHGlobal(fileHeaderSize); + Marshal.StructureToPtr(fileHeader, tempPtr, true); + Marshal.Copy(tempPtr, dataCopy, 0, fileHeaderSize); + Marshal.FreeHGlobal(tempPtr); + // write image + Marshal.Copy(data, dataCopy, fileHeaderSize, (int)fileHeader.bfSize - fileHeaderSize); + + return dataCopy; + } + return null; + } + + public static byte[]? GetTiffData(System.Buffers.ArrayPool xferMemPool, IntPtr data) + { + // Find the size of the image so we can turn it into a memory stream... + var headerSize = Marshal.SizeOf(typeof(TIFFHEADER)); + var tagSize = Marshal.SizeOf(typeof(TIFFTAG)); + var tiffSize = 0; + var tagPtr = data.ToInt64() + headerSize; + for (int i = 0; i < 999; i++) + { + tagPtr += (tagSize * i); + var tag = (TIFFTAG)Marshal.PtrToStructure((IntPtr)tagPtr, typeof(TIFFTAG)); + + switch (tag.u16Tag) + { + case 273: // StripOffsets... + case 279: // StripByteCounts... + tiffSize += (int)tag.u32Value; + break; + } + } + + if (tiffSize > 0) + { + var dataCopy = xferMemPool.Rent(tiffSize);// new byte[tiffSize]; + // is this optimal? + Marshal.Copy(data, dataCopy, 0, tiffSize); + return dataCopy; + } + return null; + } + + //internal static Bitmap ReadBitmapImage(IntPtr data) + //{ + // Bitmap finalImg = null; + // Bitmap tempImg = null; + // try + // { + // var header = (BITMAPINFOHEADER)Marshal.PtrToStructure(data, typeof(BITMAPINFOHEADER)); + + // if (header.Validate()) + // { + // PixelFormat format = header.GetDrawingPixelFormat(); + // tempImg = new Bitmap(header.biWidth, Math.Abs(header.biHeight), header.GetStride(), format, header.GetScan0(data)); + // ColorPalette pal = header.GetDrawingPalette(data); + // if (pal != null) + // { + // tempImg.Palette = pal; + // } + // float xdpi = header.GetXDpi(); + // float ydpi = header.GetYDpi(); + // if (xdpi != 0 && ydpi == 0) + // { + // ydpi = xdpi; + // } + // else if (ydpi != 0 && xdpi == 0) + // { + // xdpi = ydpi; + // } + // if (xdpi != 0) + // { + // tempImg.SetResolution(xdpi, ydpi); + // } + // if (header.IsBottomUpImage) + // { + // tempImg.RotateFlip(RotateFlipType.RotateNoneFlipY); + // } + // finalImg = tempImg; + // tempImg = null; + // } + // } + // finally + // { + // if (tempImg != null) + // { + // tempImg.Dispose(); + // } + // } + // return finalImg; + //} + } +} diff --git a/src/NTwain/TwainAppSession.PropEvents.cs b/src/NTwain/TwainAppSession.PropEvents.cs index eece5e4..ab776ae 100644 --- a/src/NTwain/TwainAppSession.PropEvents.cs +++ b/src/NTwain/TwainAppSession.PropEvents.cs @@ -128,37 +128,37 @@ namespace NTwain /// Fires when changes. /// This is not guaranteed to be raised on the UI thread. /// - public event Action? StateChanged; + public event TwainEventDelegate? StateChanged; /// /// Fires when changes. /// - public event Action? DefaultSourceChanged; + public event TwainEventDelegate? DefaultSourceChanged; /// /// Fires when changes. /// - public event Action? CurrentSourceChanged; + public event TwainEventDelegate? CurrentSourceChanged; /// /// Fires when the source has some device event happening. /// - public event Action? DeviceEvent; + public event TwainEventDelegate? DeviceEvent; /// /// Fires when there's an error during transfer. /// - public event EventHandler? TransferError; + public event TwainEventDelegate? TransferError; /// /// Fires when there's an upcoming transfer. App can inspect the image info /// and cancel if needed. /// - public event EventHandler? TransferReady; + public event TwainEventDelegate? TransferReady; /// /// Fires when transferred data is available for app to use. /// - public event EventHandler? DataTransferred; + public event TwainEventDelegate? DataTransferred; } } diff --git a/src/NTwain/TwainAppSession.Xfers.cs b/src/NTwain/TwainAppSession.Xfers.cs index f5a070a..860e9fd 100644 --- a/src/NTwain/TwainAppSession.Xfers.cs +++ b/src/NTwain/TwainAppSession.Xfers.cs @@ -1,4 +1,5 @@ using NTwain.Data; +using NTwain.Native; using NTwain.Triplets; using System; using System.Buffers; @@ -18,7 +19,7 @@ namespace NTwain // 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); - public STS GetImageInfo(out TW_IMAGEINFO info) + internal STS GetImageInfo(out TW_IMAGEINFO info) { return WrapInSTS(DGImage.ImageInfo.Get(ref _appIdentity, ref _currentDS, out info)); } @@ -81,27 +82,30 @@ namespace NTwain } if (readyArgs.Cancel == CancelType.Graceful) { - sts = WrapInSTS(DGControl.PendingXfers.StopFeeder(ref _appIdentity, ref _currentDS, ref pending)); + // ignore rc in this + DGControl.PendingXfers.StopFeeder(ref _appIdentity, ref _currentDS, ref pending); } - if (readyArgs.Cancel != CancelType.SkipCurrent) + if (readyArgs.Cancel != CancelType.SkipCurrent && + DataTransferred != null) { - // transfer normally + // transfer normally and only if someone's listening + // to DataTransferred event if (xferImage) { switch (imgXferMech) { case TWSX.NATIVE: - TransferNativeImage(); + sts = TransferNativeImage(); break; case TWSX.FILE: - TransferFileImage(); + sts = TransferFileImage(); break; case TWSX.MEMORY: - TransferMemoryImage(); + sts = TransferMemoryImage(); break; case TWSX.MEMFILE: - TransferMemoryFileImage(); + sts = TransferMemoryFileImage(); break; } } @@ -110,10 +114,10 @@ namespace NTwain switch (audXferMech) { case TWSX.NATIVE: - TransferNativeAudio(); + sts = TransferNativeAudio(); break; case TWSX.FILE: - TransferFileAudio(); + sts = TransferFileAudio(); break; } } @@ -138,40 +142,109 @@ namespace NTwain { HandleNonSuccessXferCode(sts); } - _uiThreadMarshaller.BeginInvoke(() => + + //if (State > STATE.S5) + //{ + //if (_closeDsRequested) + //{ + _uiThreadMarshaller.BeginInvoke(() => + { + DisableSource(); + }); + //} + } + + private STS TransferFileAudio() + { + return default; + } + + private STS TransferNativeAudio() + { + return default; + } + + private STS TransferMemoryFileImage() + { + return default; + } + + private STS TransferMemoryImage() + { + return default; + } + + private STS TransferFileImage() + { + return default; + } + + private STS TransferNativeImage() + { + IntPtr dataPtr = IntPtr.Zero; + IntPtr lockedPtr = IntPtr.Zero; + STS sts = default; + try { - DisableSource(); - }); - } + sts = WrapInSTS(DGImage.ImageNativeXfer.Get(ref _appIdentity, ref _currentDS, out dataPtr)); + if (sts.RC == TWRC.XFERDONE) + { + State = STATE.S7; - private void TransferFileAudio() - { + lockedPtr = Lock(dataPtr); - } + byte[]? data = null; - private void TransferNativeAudio() - { - } - - private void TransferMemoryFileImage() - { - - } - - private void TransferMemoryImage() - { - - } - - private void TransferFileImage() - { - - } - - private void TransferNativeImage() - { + if (ImageTools.IsDib(lockedPtr)) + { + data = ImageTools.GetBitmapData(XferMemPool, lockedPtr); + } + else if (ImageTools.IsTiff(lockedPtr)) + { + data = ImageTools.GetTiffData(XferMemPool, lockedPtr); + } + if (data != null) + { + try + { + var args = new DataTransferredEventArgs(this, data, true); + DataTransferred?.Invoke(this, args); + } + catch { } + finally + { + XferMemPool.Return(data); + } + } + } + else + { + HandleNonSuccessXferCode(sts); + } + } + catch (Exception ex) + { + try + { + TransferError?.Invoke(this, new TransferErrorEventArgs(ex)); + } + catch { } + } + finally + { + State = STATE.S6; + if (lockedPtr != IntPtr.Zero) + { + Unlock(dataPtr); + } + if (dataPtr != IntPtr.Zero) + { + Free(dataPtr); + } + } + return sts; } private void HandleNonSuccessXferCode(STS sts) diff --git a/src/NTwain/TwainEventDelegate.cs b/src/NTwain/TwainEventDelegate.cs new file mode 100644 index 0000000..9a565bd --- /dev/null +++ b/src/NTwain/TwainEventDelegate.cs @@ -0,0 +1,11 @@ +namespace NTwain +{ + /// + /// General event delegate use by . + /// Better than basic object sender and requires EventArgs. + /// + /// + /// + /// + public delegate void TwainEventDelegate(TwainAppSession sender, TEventArgs e); +}