Twain for WPF Applications - Look Ma, No Handles
托管的接口允许在 WPF 应用程序中简单使用 Twain。演示项目提供了从相机和扫描仪扫描、显示多张图像以及通过 Web 服务上传到服务器的功能。
引言
该项目提供了一个干净的接口,可以在 WPF 项目中使用 Twain。
代码包含两部分
- 一个 Twain 抽象层
- 一个演示应用程序
抽象层公开了一个干净的 C# 接口(没有句柄,IntPtr
s 等)。它包含一些 CS 文件,用于处理必要的接口和 Twain 的激活。
该应用程序演示了在典型的 WPF 应用程序中使用 Twain 扫描
- 带扫描仪 UI 和不带扫描仪 UI 的扫描
- 在 WPF 中显示多个扫描的图像
- 使用 Web 服务将图像上传到服务器
背景
Twain 是一个广泛使用的软件标准,常见于从扫描仪和相机获取图像。
Twain 接口使用低级窗口定义,大量使用非托管指针、窗口句柄和消息。
2001 年,一篇 CodeProject 文章介绍了 TwainLib
- Twain 接口的 C# 包装器。这段代码一直是许多已发布作品的参考。不幸的是,TwainLib
接口仍然依赖于句柄、显式消息以及广泛使用 GDI。
希望隐藏这些实现细节,使其不被 WPF 应用程序所感知。抽象出低级窗口内容可以使生成的代码更干净,并简化 WPF 功能的使用。
当前的接口类 WpfTwain
构建在经典的 TwainLib
接口之上。它处理 Twain 消息循环与 WPF 系统的集成,并使用托管的 BitmapSource
而不是 TwainLib
中使用的 GDI+。
Using the Code
WFP 要使用的接口类是 WpfTwain
。
获取图像的步骤如下
- 创建接口对象,通常在
MainWindow Load
事件中 - 选择 Twain 源(如果系统中只有一个 Twain 源,则不是必需的)
- 启动采集
- 处理采集结果事件
所有这些步骤都直接且直观。请参阅演示应用程序中的 MainWindow.cs 代码以获取完整示例。
1. 创建接口对象
private void Window_Loaded(object sender, RoutedEventArgs e)
{
TwainInterface = new WpfTwain();
TwainInterface.TwainTransferReady += new TwainTransferReadyHandler
(TwainWin_TwainTransferReady);
...
}
创建后,Twain 接口将内部挂钩消息并负责实现细节。
请注意,我们注册了一个事件 - TwainTransferReady
。这将使我们能够处理获取的图像。
也可以挂钩到其他事件,但对于简单的采集来说不是必需的。如果您需要对过程进行更精细、更低级别的控制,请参阅 WpfTwain
类的实现。在大多数情况下可能不需要。
2. 选择源
private void SelecctButton_Click(object sender, RoutedEventArgs e)
{
TwainInterface.Select();
}
这不言自明,不是吗?调用 Twain 接口的 Select
方法将弹出 Twain 源选择对话框。这是一个 Windows 对话框,由 Twain 系统控制。
3. 开始扫描
private void ScanButton_Click(object sender, RoutedEventArgs e)
{
TwainInterface.Acquire(false /*show UI*/);
}
唯一需要提及的是 showUI
参数。将其设置为 true
将导致采集窗口显示。此窗口特定于设备(由设备驱动程序安装),因此其外观和行为因设备而异。
在某些情况下(例如,需要自动化时),最好隐藏此选项,然后直接运行扫描。演示应用程序提供了这两种选项。
4. 处理扫描结果
private void TwainWin_TwainTransferReady(WpfTwain sender, List<ImageSource> imageSources)
{
foreach (ImageSource ims in imageSources)
AddImageThumbnail(ims); // process the image by the application
// alternatively if the program should only support one-image scans
// you can use imageSources[0]
this.Activate();
}
此事件处理程序接收采集到的图像列表,并执行应用程序所需的任何操作。
就是这样。使用 WPF 成功运行扫描仪并获取图像只需要这些。
演示应用程序
演示应用程序利用了一些很棒的 WPF 功能,只是为了提醒我们当初为什么想使用 WPF。
1. 多图像支持

程序显示图像缩略图列表和一个选定的放大图像。当新图像被采集时,它会被添加到缩略图列表中。
每个缩略图实际上是一个 WPF 按钮。单击按钮会在全屏视图中打开相应的图像。
缩略图和全屏图像之间的边界可以移动。这样做会改变缩略图区域的宽度。缩略图和全屏图像的大小会自动调整。所有这些都由 WPF 引擎完成 - 无需编码。
这是自动调整大小的缩略图背后的 XAML
<Border BorderThickness="1" BorderBrush="#FF6E789A" Margin="12,70,12,12" >
<Grid Name="imageGrid"><Grid.ColumnDefinitions><ColumnDefinition Width="75" />
<ColumnDefinition Width="5" /><ColumnDefinition Width="*" />
</Grid.ColumnDefinitions><GridSplitter HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.Column="1" ResizeBehavior="PreviousAndNext"
Width="Auto" Background="#FF787896" Height="Auto" />
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="0,0,0,0" Name="ThumbnailStackPanel" >
<button
height="Auto" width="Auto"><Button.Content>
<Image HorizontalAlignment="Left" Stretch="Uniform"
VerticalAlignment="Top"
Source="/scan2web;component/Resources/
free-drink-pictures-espresso-coffee.jpg" />
</Button.Content>
</button></StackPanel>
</ScrollViewer>
<Image Grid.Column="2" HorizontalAlignment="Left" Name="image1"
Stretch="Uniform" VerticalAlignment="Top"
Source="/scan2web;component/Resources/
free-drink-pictures-espresso-coffee.jpg" />
</Grid>
</Border>
扫描会添加图像,按下清除会清除所有缩略图。
以下是滑块向右移动的屏幕截图。请注意,缩略图按钮被拉伸以适应宽度,此外,还会自动添加一个垂直滚动条。

注意:以上图像是使用 Teac mx-10 网络摄像头采集的。
2. 将图像上传到 Web 服务器
在许多应用程序中,需要提供扫描仪自动化,其中扫描和上传到 Web 服务器一键完成。
代码用于上传图像以及相应的服务器端,以完成此示例。请注意,C# 4 更改了服务代理,这里客户端使用 C# 2 风格的 Web 服务代理以获得更广泛的兼容性。
客户端如下
public void UploadImage()
{
MemoryStream stream = new MemoryStream();
try {
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
// TBD: encoding parameters (quality etc.)
TextBlock myTextBlock = new TextBlock();
BitmapSource bs = image1.Source as BitmapSource;
BitmapFrame bf = BitmapFrame.Create(bs);
//encoder.Frames.Add(BitmapFrame.Create(image1.Source));
encoder.Frames.Add(bf);
encoder.Save(stream);
stream.Flush();
// upload
scan2web.ScanServer.Scanner scanerServerProxy =
new scan2web.ScanServer.Scanner();
string result = scanerServerProxy.UploadScan
(stream.GetBuffer(), "test 1");
UploadResultLabel.Content = result;
} catch (Exception ex) {
UploadResultLabel.Content = "Error: " + ex.Message;
}
stream.Close();
// This will come handy if we want to annotate the image
//RenderTargetBitmap rendered = new RenderTargetBitmap
( (int)bs.Width, (int)bs.Height, bs.DpiX, bs.DpiY, bs.Format);
//rendered.Render(image1);
}
代码使用 Web 方法将当前图像作为字节数组上传。可以发送其他参数来完成信息 - 根据您的应用程序的相关性。
以及相应的服务器端是
[WebMethod]
public string UploadScan(byte[] data, string scanKey)
{
Guid fileID = Guid.NewGuid();
string path = Server.MapPath("~/Documents");
string filePath = path + "/" + fileID.ToString() + ".jpg";
try {
// TBD: folder by date of upload to prevent too many files in the uploads folder
FileStream traget = new FileStream
(filePath, FileMode.Create); // the jpg extension is for debug
traget.Write(data, 0, data.Length);
traget.Flush();
traget.Close();
// TBD: register in the DB (fileID, scanKey, person, date etc)
} catch (Exception ex) {
// TBD: cleanup - delete file, clear DB atc.
// TBD: register the error and alert operators
return "Error: " + ex.Message;
}
return "Saved";
}
关注点
该项目是一次关于抽象的实践练习,特别是旨在通过从客户端代码中移除低级细节来简化 Twain 的使用。
大部分工作不是花在应用程序上,而是花在弄清楚如何激活旧代码、转换位图等方面。我希望这项工作可以节省那些对 Twain 感兴趣的开发者的时间和精力。
此代码已在几台具有 32 位和 64 位操作系统以及几种类型源的计算机上进行了测试
- Teac MX-10 网络摄像头(使用 twain 接口)
- HP LaserJet 3055 多功能设备(使用 Twain 和 Twain over WIA)
- Brother MFC-6490W 多功能扫描仪
- Canon Lide 100
我们不知道有任何问题,但测试是有限的。
限制
当前代码并未利用许多更高级的 Twain 功能。使用这些功能(例如多页扫描)超出了本项目范围。
反馈
任何反馈、请求、问题、建议、修复和改进都将受到高度欢迎。
如有请求和评论,请联系 Baruch:rnd@ibn-labs.com。
历史
- 2011 年 3 月 21 日:初始版本