From 4d8da799c037ec74f61a296e84c702c70eb6cddd Mon Sep 17 00:00:00 2001 From: Eugene Wang <8755753+soukoku@users.noreply.github.com> Date: Tue, 11 Apr 2023 08:21:49 -0400 Subject: [PATCH] Some more QoL and sample process options. --- samples/WinForm32/Form1.Designer.cs | 67 +++++++++++++++----- samples/WinForm32/Form1.cs | 96 ++++++++++++++++++++++------- samples/WinForm32/NoOpStream.cs | 44 +++++++++++++ src/NTwain/Data/BufferedData.cs | 43 ++++++++++--- src/NTwain/TransferredEventArgs.cs | 2 +- 5 files changed, 209 insertions(+), 43 deletions(-) create mode 100644 samples/WinForm32/NoOpStream.cs diff --git a/samples/WinForm32/Form1.Designer.cs b/samples/WinForm32/Form1.Designer.cs index 0ee6652..32f957a 100644 --- a/samples/WinForm32/Form1.Designer.cs +++ b/samples/WinForm32/Form1.Designer.cs @@ -41,6 +41,9 @@ lblState = new System.Windows.Forms.Label(); label3 = new System.Windows.Forms.Label(); btnOpenDef = new System.Windows.Forms.Button(); + btnOpenFolder = new System.Windows.Forms.Button(); + ckSystemDrawing = new System.Windows.Forms.CheckBox(); + ckBgImageHandling = new System.Windows.Forms.CheckBox(); ckShowUI = new System.Windows.Forms.CheckBox(); capListView = new System.Windows.Forms.ListView(); colCap = new System.Windows.Forms.ColumnHeader(); @@ -53,7 +56,7 @@ btnStart = new System.Windows.Forms.Button(); btnShowSettings = new System.Windows.Forms.Button(); btnClose = new System.Windows.Forms.Button(); - ckBgImageHandling = new System.Windows.Forms.CheckBox(); + ckSaveDisk = new System.Windows.Forms.CheckBox(); ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit(); splitContainer1.Panel1.SuspendLayout(); splitContainer1.Panel2.SuspendLayout(); @@ -168,6 +171,9 @@ // // splitContainer1.Panel2 // + splitContainer1.Panel2.Controls.Add(ckSaveDisk); + splitContainer1.Panel2.Controls.Add(btnOpenFolder); + splitContainer1.Panel2.Controls.Add(ckSystemDrawing); splitContainer1.Panel2.Controls.Add(ckBgImageHandling); splitContainer1.Panel2.Controls.Add(ckShowUI); splitContainer1.Panel2.Controls.Add(capListView); @@ -209,6 +215,38 @@ btnOpenDef.UseVisualStyleBackColor = true; btnOpenDef.Click += btnOpenDef_Click; // + // btnOpenFolder + // + btnOpenFolder.Location = new System.Drawing.Point(512, 106); + btnOpenFolder.Name = "btnOpenFolder"; + btnOpenFolder.Size = new System.Drawing.Size(147, 23); + btnOpenFolder.TabIndex = 13; + btnOpenFolder.Text = "Open saved folder"; + btnOpenFolder.UseVisualStyleBackColor = true; + btnOpenFolder.Click += btnOpenFolder_Click; + // + // ckSystemDrawing + // + ckSystemDrawing.AutoSize = true; + ckSystemDrawing.Location = new System.Drawing.Point(512, 58); + ckSystemDrawing.Name = "ckSystemDrawing"; + ckSystemDrawing.Size = new System.Drawing.Size(209, 19); + ckSystemDrawing.TabIndex = 11; + ckSystemDrawing.Text = "Use System.Drawing to save image"; + ckSystemDrawing.UseVisualStyleBackColor = true; + // + // ckBgImageHandling + // + 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; + // // ckShowUI // ckShowUI.AutoSize = true; @@ -226,10 +264,10 @@ capListView.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right; capListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { colCap, colType, colCur, colDef, colExtended, colSupport }); capListView.FullRowSelect = true; - capListView.Location = new System.Drawing.Point(11, 87); + capListView.Location = new System.Drawing.Point(11, 135); capListView.MultiSelect = false; capListView.Name = "capListView"; - capListView.Size = new System.Drawing.Size(771, 462); + capListView.Size = new System.Drawing.Size(771, 414); capListView.TabIndex = 8; capListView.UseCompatibleStateImageBehavior = false; capListView.View = System.Windows.Forms.View.Details; @@ -267,7 +305,7 @@ // label4 // label4.AutoSize = true; - label4.Location = new System.Drawing.Point(13, 62); + label4.Location = new System.Drawing.Point(11, 110); label4.Name = "label4"; label4.Size = new System.Drawing.Size(91, 15); label4.TabIndex = 7; @@ -303,17 +341,15 @@ btnClose.UseVisualStyleBackColor = true; btnClose.Click += btnClose_Click; // - // ckBgImageHandling + // ckSaveDisk // - 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; + ckSaveDisk.AutoSize = true; + ckSaveDisk.Location = new System.Drawing.Point(512, 83); + ckSaveDisk.Name = "ckSaveDisk"; + ckSaveDisk.Size = new System.Drawing.Size(88, 19); + ckSaveDisk.TabIndex = 12; + ckSaveDisk.Text = "Save to disk"; + ckSaveDisk.UseVisualStyleBackColor = true; // // Form1 // @@ -360,5 +396,8 @@ private System.Windows.Forms.ColumnHeader colExtended; private System.Windows.Forms.CheckBox ckShowUI; private System.Windows.Forms.CheckBox ckBgImageHandling; + private System.Windows.Forms.CheckBox ckSystemDrawing; + private System.Windows.Forms.Button btnOpenFolder; + private System.Windows.Forms.CheckBox ckSaveDisk; } } \ No newline at end of file diff --git a/samples/WinForm32/Form1.cs b/samples/WinForm32/Form1.cs index 421ee39..2bbedfa 100644 --- a/samples/WinForm32/Form1.cs +++ b/samples/WinForm32/Form1.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; +using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Reflection; @@ -17,10 +18,15 @@ namespace WinFormSample { public partial class Form1 : Form { - private TwainAppSession twain; - private readonly string saveFolder; + TwainAppSession twain; + readonly string saveFolder; readonly Stopwatch watch = new(); - private bool _useThreadForImag; + readonly ImageCodecInfo _jpegEncoder; + readonly EncoderParameters _jpegParameters; + readonly int _jpegQuality = 75; + bool _useThreadForImag; + bool _useSystemDrawing; + bool _saveDisk; public Form1() { @@ -43,10 +49,15 @@ namespace WinFormSample capListView.SetDoubleBufferedAsNeeded(); SystemEvents.SessionSwitch += SystemEvents_SessionSwitch; - saveFolder = Path.Combine(Path.GetTempPath(), "ntwain-sample"); + saveFolder = Path.Combine(Path.GetTempPath(), "ntwain-sample" + Path.DirectorySeparatorChar); Directory.CreateDirectory(saveFolder); this.Disposed += Form1_Disposed; + + + _jpegParameters = new EncoderParameters(1); + _jpegParameters.Param[0] = new EncoderParameter(Encoder.Quality, (long)_jpegQuality); + _jpegEncoder = ImageCodecInfo.GetImageEncoders().First(enc => enc.FormatID == ImageFormat.Jpeg.Guid); } private void Twain_SourceDisabled(TwainAppSession sender, TW_IDENTITY_LEGACY e) @@ -137,31 +148,62 @@ namespace WinFormSample private void HandleTransferredData(TransferredEventArgs e) { - try + if (e.Data != null) { - // 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)) + try { - if (img.ColorType == ImageMagick.ColorType.Palette) + // example of using some lib to handle image data + var saveFile = Path.Combine(saveFolder, (DateTime.Now.Ticks / 1000).ToString()); + + if (_useSystemDrawing) { - // bw or gray - saveFile += ".png"; + using (var img = Image.FromStream(e.Data.AsStream())) + { + if (img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format1bppIndexed || + img.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed) + { + // bw or gray + saveFile += ".png"; + if (_saveDisk) img.Save(saveFile, ImageFormat.Png); + else img.Save(new NoOpStream(), ImageFormat.Png); + } + else + { + // color + saveFile += ".jpg"; + if (_saveDisk) img.Save(saveFile, _jpegEncoder, _jpegParameters); + else img.Save(new NoOpStream(), _jpegEncoder, _jpegParameters); + } + } } else { - // color - saveFile += ".jpg"; - img.Quality = 75; + using (var img = new ImageMagick.MagickImage(e.Data.AsSpan())) + { + var format = ImageMagick.MagickFormat.Png; + if (img.ColorType == ImageMagick.ColorType.Palette) + { + // bw or gray + saveFile += ".png"; + } + else + { + // color + saveFile += ".jpg"; + format = ImageMagick.MagickFormat.Jpeg; + img.Quality = _jpegQuality; + } + if (_saveDisk) img.Write(saveFile); + else img.Write(new NoOpStream(), format); + } + Debug.WriteLine($"Saved image to {saveFile}"); } - img.Write(saveFile); - Debug.WriteLine($"Saved image to {saveFile}"); } - } - catch { } - finally - { - e.Dispose(); + catch { } + finally + { + e.Dispose(); + } } } @@ -402,8 +444,20 @@ namespace WinFormSample if (twain.EnableSource(ckShowUI.Checked, false).IsSuccess) { _useThreadForImag = ckBgImageHandling.Checked; + _useSystemDrawing = ckSystemDrawing.Checked; + _saveDisk = ckSaveDisk.Checked; watch.Restart(); } } + + private void btnOpenFolder_Click(object sender, EventArgs e) + { + try + { + if (!Directory.Exists(saveFolder)) Directory.CreateDirectory(saveFolder); + using (Process.Start(new ProcessStartInfo { FileName = saveFolder, UseShellExecute = true })) { } + } + catch { } + } } } \ No newline at end of file diff --git a/samples/WinForm32/NoOpStream.cs b/samples/WinForm32/NoOpStream.cs new file mode 100644 index 0000000..9e72ca6 --- /dev/null +++ b/samples/WinForm32/NoOpStream.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WinFormSample +{ + internal class NoOpStream : Stream + { + public override bool CanRead => false; + + public override bool CanSeek => false; + + public override bool CanWrite => true; + + public override long Length => 0; + + public override long Position { get => 0; set { } } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + } + + public override void Write(byte[] buffer, int offset, int count) + { + } + } +} diff --git a/src/NTwain/Data/BufferedData.cs b/src/NTwain/Data/BufferedData.cs index 3130bb9..047d36c 100644 --- a/src/NTwain/Data/BufferedData.cs +++ b/src/NTwain/Data/BufferedData.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.IO; namespace NTwain.Data { @@ -13,7 +14,7 @@ namespace NTwain.Data // 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) + internal BufferedData(int size) { _buffer = MemPool.Rent(size); _length = size; @@ -27,14 +28,14 @@ namespace NTwain.Data _fromPool = fromPool; } + bool _disposed; bool _fromPool; /// /// Bytes buffer. This may be bigger than the data size /// and contain invalid data. /// - byte[]? _buffer; - public byte[]? Buffer { get; } + byte[] _buffer; /// /// Actual usable data length in the buffer. @@ -45,19 +46,47 @@ namespace NTwain.Data /// As a span of usable data. /// /// + /// public ReadOnlySpan AsSpan() { - if (_buffer != null) return _buffer.AsSpan(0, _length); - return Span.Empty; + if (_disposed) throw new ObjectDisposedException(GetType().FullName); + return _buffer.AsSpan(0, _length); + } + + /// + /// As a span of usable data. + /// + /// + /// + public ReadOnlyMemory AsMemory() + { + if (_disposed) throw new ObjectDisposedException(GetType().FullName); + return _buffer.AsMemory(0, _length); + } + + /// + /// As a readonly stream. + /// + /// + /// + public Stream AsStream() + { + if (_disposed) throw new ObjectDisposedException(GetType().FullName); + return new MemoryStream(_buffer, 0, _length, false); } public void Dispose() { - if (_fromPool && _buffer != null) + if (_fromPool && _disposed) { MemPool.Return(_buffer); - _buffer = null; + _disposed = true; } } + + public static implicit operator ReadOnlySpan(BufferedData value) => value.AsSpan(); + public static implicit operator ReadOnlyMemory(BufferedData value) => value.AsMemory(); + public static implicit operator Stream(BufferedData value) => value.AsStream(); + } } diff --git a/src/NTwain/TransferredEventArgs.cs b/src/NTwain/TransferredEventArgs.cs index cbbc558..264418f 100644 --- a/src/NTwain/TransferredEventArgs.cs +++ b/src/NTwain/TransferredEventArgs.cs @@ -39,7 +39,7 @@ namespace NTwain /// IMPORTANT: Content of this array will not be valid once /// this event arg has been disposed. /// - public ReadOnlySpan Data => _data == null ? ReadOnlySpan.Empty : _data.AsSpan(); + public BufferedData? Data => _data; /// /// The file info if the transfer involved file information.