Files
ntwain/samples/ScannerTester/MainForm.cs
2025-11-20 20:46:15 -05:00

592 lines
21 KiB
C#

using NTwain;
using NTwain.Data;
using NTwain.Data.Kds;
using NTwain.Triplets;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
namespace ScannerTester
{
public partial class MainForm : Form
{
TwainAppSession _twain;
private bool _stopTransfer;
public MainForm()
{
InitializeComponent();
_twain = new TwainAppSession();
_twain.TransferReady += _twain_TransferReady;
_twain.Transferred += _twain_Transferred;
_twain.SourceDisabled += _twain_SourceDisabled;
_twain.AddWinformFilter();
}
private void _twain_SourceDisabled(TwainAppSession sender, TW_IDENTITY_LEGACY e)
{
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_twain.OpenDSM(Handle, SynchronizationContext.Current!);
//_ = _twain.OpenDSMAsync();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
_twain.CloseDSM();
//_ = _twain.CloseDSMAsync();
base.OnFormClosed(e);
}
private void _twain_Transferred(TwainAppSession sender, TransferredEventArgs e)
{
Invoke(() =>
{
TW_EXTIMAGEINFO extInfo = TW_EXTIMAGEINFO.CreateRequest(TWEI.CAMERA);
e.GetExtendedImageInfo(ref extInfo);
string? camera = null;
foreach (var ei in extInfo.AsInfos())
{
if (ei.ReturnCode == TWRC.SUCCESS)
{
switch (ei.InfoId)
{
//case (TWEI)KDS_TWEI.HDR_PAGENUMBER:
// LogIt($"TWEI_HDR_PAGENUMBER Value = {ei.ReadNonPointerData<int>()}");
// break;
case TWEI.CAMERA:
camera = ei.ReadHandleString(_twain);
LogIt($"{ei.InfoId} Value = {camera}");
break;
}
}
else
{
LogIt($"{ei.InfoId} RC = {ei.ReturnCode}");
}
}
extInfo.Free(_twain);
if (e.Data != null)
{
LogIt($"Received {e.ImageInfo.PixelType} in {e.ImageInfo.Compression} compressed memory image.");
var folder = boxFolder.Text;
if (string.IsNullOrWhiteSpace(folder))
{
folder = "Images";
Directory.CreateDirectory(folder);
boxFolder.Text = folder;
}
var prefix = boxNamePrefix.Text;
if (string.IsNullOrWhiteSpace(prefix))
{
prefix = "Capture_";
boxNamePrefix.Text = prefix;
}
using (var img = System.Drawing.Image.FromStream(e.Data.AsStream()))
{
//var saveFile = img.SaveToSmallestFormat(folder, prefix, lossless: false).ToString();
//LogIt($"File saved to {saveFile}");
}
}
else if (e.FileInfo != null)
{
var info = e.FileInfo.Value;
var path = info.FileName.ToString();
LogIt($"Received {e.ImageInfo.PixelType} {info.Format} in {e.ImageInfo.Compression} compressed file {path}");
}
e.Dispose();
LogIt("");
});
}
int _xferCount = 0;
private void _twain_TransferReady(TwainAppSession sender, TransferReadyEventArgs e)
{
Invoke(() =>
{
if (_stopTransfer)
{
e.Cancel = CancelType.EndNow;
return;
}
_xferCount++;
LogIt($"Got pending transfer with mode = {e.ImgXferMech}");
if (e.ImgXferMech == TWSX.FILE)
{
var folder = boxFolder.Text;
if (string.IsNullOrWhiteSpace(folder))
{
folder = "Images";
Directory.CreateDirectory(folder);
boxFolder.Text = folder;
}
var prefix = boxNamePrefix.Text;
if (string.IsNullOrWhiteSpace(prefix))
{
prefix = "Capture_";
boxNamePrefix.Text = prefix;
}
TWCP comp = TWCP.NONE;
TW_EXTIMAGEINFO extInfo = TW_EXTIMAGEINFO.CreateRequest((TWEI)KDS_TWEI.HDR_COMPRESSION);
e.GetExtendedImageInfo(ref extInfo);
foreach (var ei in extInfo.AsInfos())
{
if (ei.ReturnCode == TWRC.SUCCESS)
{
switch (ei.InfoId)
{
case (TWEI)KDS_TWEI.HDR_COMPRESSION:
comp = ei.ReadNonPointerData<TWCP>();
LogIt($"{ei.InfoId} Value = {comp}");
break;
}
}
else
{
LogIt($"{ei.InfoId} RC = {ei.ReturnCode}");
}
}
extInfo.Free(_twain);
LogIt($"Compression at ready step = {comp}");
string? targetName = $"{prefix}_{_xferCount:D4}";
TWFF format = TWFF.TIFF;
switch (comp)
{
case TWCP.JPEG:
targetName = $"{prefix}_{_xferCount:D4}.jpg";
format = TWFF.JFIF;
break;
case TWCP.NONE:
targetName = $"{prefix}_{_xferCount:D4}.bmp";
format = TWFF.BMP;
break;
case TWCP.GROUP4:
default:
targetName = $"{prefix}_{_xferCount:D4}.tif";
break;
}
TW_SETUPFILEXFER setup = new()
{
FileName = Path.Combine(folder, targetName),
Format = format
};
var sts = e.SetupFileTransfer(ref setup);
LogIt($"Want to save image as {setup.Format} {setup.FileName}.", sts);
var appId = _twain.AppIdentity;
var srcId = _twain.CurrentSource;
sts = _twain.WrapInSTS(DGControl.SetupFileXfer.Get(ref appId, ref srcId, out setup));
LogIt($"Checked actual file settings as {setup.Format} {setup.FileName}.", sts);
}
LogIt("");
});
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (_twain.State > STATE.S5)
{
e.Cancel = true;
}
_twain.TryStepdown(STATE.S2);
base.OnFormClosing(e);
}
private void btnSelectScanner_Click(object sender, EventArgs e)
{
var sts = _twain.ShowUserSelect();
if (sts.IsSuccess && _twain.DefaultSource.Id > 0)
{
if (_twain.State > STATE.S3)
{
_twain.TryStepdown(STATE.S3);
}
sts = _twain.OpenSource(_twain.DefaultSource);
LogIt("Open scanner", sts);
if (sts.IsSuccess)
{
var src = _twain.CurrentSource;
lblCurScanner.Text = $"{src.ProductName} | v{src.Version} | protocol: {src.ProtocolMajor}.{src.ProtocolMinor}";
LoadSettings();
return;
}
}
lblCurScanner.Text = "None selected";
}
private void LogIt(string msg)
{
boxLog.AppendText($"{msg}\n");
}
private void LogIt(string msg, STS sts)
{
if (sts.IsSuccess)
{
boxLog.AppendText($"{msg} result = {sts.RC}\n");
}
else
{
boxLog.AppendText($"{msg} result = {sts.RC} - {sts.ConditionCode}\n");
}
}
private void LoadSettings()
{
var mechs = _twain.Caps.ICAP_XFERMECH.Get().GetValues();
if (!mechs.Contains(TWSX.FILE))
{
LogIt("File transfer is not supported.");
}
var sts = _twain.Caps.ICAP_XFERMECH.Set(TWSX.FILE);
LogIt("Use file transfer", sts);
if (_twain.Caps.ICAP_UNITS.GetCurrent().FirstOrDefault() != TWUN.INCHES)
{
sts = _twain.Caps.ICAP_UNITS.Set(TWUN.INCHES);
LogIt("Set unit to inches", sts);
}
var dpis = _twain.Caps.ICAP_XRESOLUTION.Get().GetValues();
listDpi.Items.Clear();
if (dpis.Contains(200))
{
listDpi.Items.Add(200);
listDpi.SelectedItem = 200;
}
else
{
LogIt("200 DPI doesn't appear to be supported.");
}
if (dpis.Contains(300))
{
listDpi.Items.Add(300);
listDpi.SelectedItem = 300;
}
else
{
LogIt("300 DPI doesn't appear to be supported.");
}
var formats = _twain.Caps.ICAP_IMAGEFILEFORMAT.Get().GetValues();
listFormat.Items.Clear();
foreach (var format in formats)
{
listFormat.Items.Add(format);
}
listFormat.SelectedItem = _twain.Caps.ICAP_IMAGEFILEFORMAT.GetCurrent().FirstOrDefault();
LogIt("");
}
private void btnDriverOnly_Click(object sender, EventArgs e)
{
if (_twain.State != STATE.S4) return;
var sts = _twain.EnableSource(true, true);
LogIt("Show drivers", sts);
LogIt("");
}
private void btnTransfer_Click(object sender, EventArgs e)
{
if (_twain.State != STATE.S4 || !EnsureBoxFolder()) return;
var _isIndustrialKodak = _twain.CurrentSource.ProductName.ToString().StartsWith("KODAK Scanner: i");
if (_isIndustrialKodak)
{
CaptureAsKodakSDMI();
}
else
{
CaptureAsStandardScanner();
}
_stopTransfer = false;
_xferCount = 0;
var sts = _twain.EnableSource(ckShowUI.Checked, false);
LogIt("Start capture", sts);
LogIt("");
}
private void CaptureAsKodakSDMI()
{
LogIt("Attempting Kodak SDMI mode");
LogIt($"Resolution supports {FlattenFlag(_twain.Caps.ICAP_XRESOLUTION.Supports)}");
LogIt($"File format supports {FlattenFlag(_twain.Caps.ICAP_IMAGEFILEFORMAT.Supports)}");
LogIt($"Compression supports {FlattenFlag(_twain.Caps.ICAP_COMPRESSION.Supports)}");
LogIt($"EXTINFO supports {FlattenFlag(_twain.Caps.ICAP_EXTIMAGEINFO.Supports)}");
LogIt("");
var limit = (short)boxLimit.Value;
if (limit > 0) limit *= 2;
var sts = _twain.Caps.CAP_XFERCOUNT.Set(limit);
LogIt($"Set transfer limit {limit}", sts);
sts = _twain.Caps.ICAP_EXTIMAGEINFO.Set(TW_BOOL.True);
LogIt($"Set extimageinfo enabled", sts);
var format = (TWFF)listFormat.SelectedItem!;
sts = _twain.Caps.ICAP_IMAGEFILEFORMAT.Set(format);
LogIt($"Set {format} format.", sts);
if (!sts.IsSuccess)
{
return;
}
LogIt("");
var appId = _twain.AppIdentity;
var srcId = _twain.CurrentSource;
TW_FILESYSTEM fs = new() { InputName = "/Camera_Bitonal_Both" };
sts = _twain.WrapInSTS(DGControl.FileSystem.ChangeDirectory(ref appId, ref srcId, ref fs));
LogIt("Change to bw cameras", sts);
if (sts.IsSuccess)
{
sts = _twain.Caps.CAP_CAMERAENABLED.Set(TW_BOOL.True);
LogIt("Set camera enabled", sts);
var dpi = listDpi.SelectedValue == null ? 300 : Convert.ToInt32(listDpi.SelectedValue);
sts = _twain.Caps.ICAP_XRESOLUTION.Set(dpi);
LogIt("Set x-resolution", sts);
sts = _twain.Caps.ICAP_YRESOLUTION.Set(dpi);
LogIt("Set y-resolution", sts);
if (format != TWFF.BMP)
{
LogIt($"Current format={_twain.Caps.ICAP_IMAGEFILEFORMAT.GetCurrent().First()}");
LogIt($"Current compression={_twain.Caps.ICAP_COMPRESSION.GetCurrent().First()}");
if (_twain.Caps.ICAP_COMPRESSION.Supports.HasFlag(TWQC.SET))
{
sts = _twain.Caps.ICAP_COMPRESSION.Set(TWCP.GROUP4);
LogIt("Set compression to group4", sts);
}
}
}
LogIt("");
fs = new() { FileType = (int)TWFY.CAMERA, InputName = "/Camera_Color_Both" }; sts = _twain.WrapInSTS(DGControl.FileSystem.ChangeDirectory(ref appId, ref srcId, ref fs));
LogIt("Change to color cameras", sts);
if (sts.IsSuccess)
{
sts = _twain.Caps.CAP_CAMERAENABLED.Set(TW_BOOL.True);
LogIt("Set camera enabled", sts);
var dpi = listDpi.SelectedValue == null ? 300 : Convert.ToInt32(listDpi.SelectedValue);
sts = _twain.Caps.ICAP_XRESOLUTION.Set(dpi);
LogIt("Set x-resolution", sts);
sts = _twain.Caps.ICAP_YRESOLUTION.Set(dpi);
LogIt("Set y-resolution", sts);
if (format != TWFF.BMP)
{
LogIt($"Current format={_twain.Caps.ICAP_IMAGEFILEFORMAT.GetCurrent().First()}");
LogIt($"Current compression={_twain.Caps.ICAP_COMPRESSION.GetCurrent().First()}");
if (_twain.Caps.ICAP_COMPRESSION.Supports.HasFlag(TWQC.SET))
{
sts = _twain.Caps.ICAP_COMPRESSION.Set(TWCP.JPEG);
LogIt("Set compression to jpg", sts);
if (sts.IsSuccess)
{
LogIt($"jpeg quality supports {FlattenFlag(_twain.Caps.ICAP_JPEGQUALITY.Supports)}");
short quality = 90;
sts = _twain.Caps.ICAP_JPEGQUALITY.Set((TWJQ)quality);
LogIt($"Set jpg quality to {quality}", sts);
if (!sts.IsSuccess)
{
quality = 85;
sts = _twain.Caps.ICAP_JPEGQUALITY.Set((TWJQ)quality);
LogIt($"Set jpg quality to {quality}", sts);
if (!sts.IsSuccess)
{
sts = _twain.Caps.ICAP_JPEGQUALITY.Set(TWJQ.HIGH);
LogIt($"Set jpg quality to {TWJQ.HIGH}", sts);
}
}
}
}
}
}
LogIt("");
}
const ushort TWQC_MACHINE = 0x1000;// applies to entire session/machine
const ushort TWQC_BITONAL = 0x2000; // applies to Bitonal "cameras"
const ushort TWQC_COLOR = 0x4000; // applies to Color "cameras"
[Flags]
public enum TWQC2 : ushort
{
MACHINE = 0x1000,
BITONAL = 0x2000,
COLOR = 0x4000
}
private string FlattenFlag(TWQC val)
{
StringBuilder sb = new();
foreach (var type in Enum.GetValues(typeof(TWQC)))
{
if (((ushort)val & (ushort)type) > 0)
{
sb.Append(type).Append(", ");
}
}
foreach (var type in Enum.GetValues(typeof(TWQC2)))
{
if (((ushort)val & (ushort)type) > 0)
{
sb.Append(type).Append(", ");
}
}
if (sb.Length > 0)
{
sb.Length = sb.Length - 2;
}
return sb.ToString();
}
private void CaptureAsStandardScanner()
{
LogIt("Attempting Standard Scanner mode");
var sts = _twain.Caps.ICAP_PIXELTYPE.Set(TWPT.RGB);
LogIt("Set rgb pixel type", sts);
if (_twain.Caps.CAP_DUPLEXENABLED.Supports.HasFlag(TWQC.SET))
{
sts = _twain.Caps.CAP_DUPLEXENABLED.Set(TW_BOOL.True);
LogIt("Set duplex enabled", sts);
}
var dpi = listDpi.SelectedValue == null ? 300 : Convert.ToInt32(listDpi.SelectedValue);
sts = _twain.Caps.ICAP_XRESOLUTION.Set(dpi);
LogIt("Set x-resolution", sts);
sts = _twain.Caps.ICAP_YRESOLUTION.Set(dpi);
LogIt("Set y-resolution", sts);
if (listFormat.SelectedItem != null)
{
var format = (TWFF)listFormat.SelectedItem;
sts = _twain.Caps.ICAP_IMAGEFILEFORMAT.Set(format);
LogIt($"Set {format} format.", sts);
if (!sts.IsSuccess)
{
return;
}
if (format != TWFF.BMP)
{
if (_twain.Caps.ICAP_COMPRESSION.Supports.HasFlag(TWQC.SET))
{
sts = _twain.Caps.ICAP_COMPRESSION.Set(TWCP.JPEG);
LogIt("Set compression to jpg", sts);
if (sts.IsSuccess)
{
short quality = 90;
sts = _twain.Caps.ICAP_JPEGQUALITY.Set((TWJQ)quality);
LogIt($"Set jpg quality to {quality}", sts);
if (!sts.IsSuccess)
{
quality = 85;
sts = _twain.Caps.ICAP_JPEGQUALITY.Set((TWJQ)quality);
LogIt($"Set jpg quality to {quality}", sts);
if (!sts.IsSuccess)
{
sts = _twain.Caps.ICAP_JPEGQUALITY.Set(TWJQ.HIGH);
LogIt($"Set jpg quality to {TWJQ.HIGH}", sts);
}
}
}
}
}
}
var limit = (short)boxLimit.Value;
sts = _twain.Caps.CAP_XFERCOUNT.Set(limit);
LogIt($"Set transfer limit {boxLimit.Value}", sts);
}
private void btnStop_Click(object sender, EventArgs e)
{
_stopTransfer = true;
}
private void btnBrowseFolder_Click(object sender, EventArgs e)
{
using FolderBrowserDialog dialog = new();
if (dialog.ShowDialog() == DialogResult.OK)
{
boxFolder.Text = dialog.SelectedPath;
}
}
private void btnOpenFolder_Click(object sender, EventArgs e)
{
if (EnsureBoxFolder())
{
try
{
using var p = Process.Start("explorer.exe", boxFolder.Text);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private bool EnsureBoxFolder()
{
if (!string.IsNullOrEmpty(boxFolder.Text))
{
if (!Directory.Exists(boxFolder.Text))
{
try
{
Directory.CreateDirectory(boxFolder.Text);
}
catch (Exception ex)
{
LogIt($"Failed to ensure save folder: {ex.Message}");
return false;
}
}
return true;
}
return false;
}
}
}