Remove old scanning types

This commit is contained in:
Ben Olden-Cooligan 2019-07-14 17:02:05 -04:00
parent 60b81d7f90
commit 0d1e273983
27 changed files with 268 additions and 2436 deletions

View File

@ -44,16 +44,6 @@ namespace NAPS2.Modules
// Scan
Bind<IScanPerformer>().To<NAPS2.Scan.Experimental.ScanPerformer>();
Bind<IBatchScanPerformer>().To<BatchScanPerformer>();
#if DEBUG && false
Bind<IScanDriverFactory>().To<Scan.Stub.StubScanDriverFactory>();
#else
Bind<IScanDriverFactory>().To<NinjectScanDriverFactory>();
#endif
Bind<IScanDriver>().To<WiaScanDriver>().InSingletonScope().Named(WiaScanDriver.DRIVER_NAME);
Bind<IScanDriver>().To<TwainScanDriver>().InSingletonScope().Named(TwainScanDriver.DRIVER_NAME);
Bind<IScanDriver>().To<SaneScanDriver>().InSingletonScope().Named(SaneScanDriver.DRIVER_NAME);
Bind<IScanDriver>().To<ProxiedScanDriver>().InSingletonScope().Named(ProxiedScanDriver.DRIVER_NAME);
Bind<ITwainWrapper>().To<TwainWrapper>();
// Config
var configScopes = new ConfigScopes(Path.Combine(Paths.Executable, "appsettings.xml"), Path.Combine(Paths.AppData, "config.xml"));

View File

@ -81,7 +81,6 @@
<Compile Include="NinjectEmailProviderFactory.cs" />
<Compile Include="NinjectFormFactory.cs" />
<Compile Include="NinjectOperationFactory.cs" />
<Compile Include="NinjectScanDriverFactory.cs" />
<Compile Include="NLogLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StaticConfiguration.cs" />

View File

@ -1,17 +0,0 @@
using NAPS2.Scan;
using Ninject;
namespace NAPS2
{
public class NinjectScanDriverFactory : IScanDriverFactory
{
private readonly IKernel kernel;
public NinjectScanDriverFactory(IKernel kernel)
{
this.kernel = kernel;
}
public IScanDriver Create(string driverName) => kernel.Get<IScanDriver>(driverName);
}
}

View File

@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.Images;
using NAPS2.ImportExport;
using NAPS2.Scan;
using NAPS2.Util;
namespace NAPS2.Sdk.Tests.Mocks
{
public class MockScanDriver : ScanDriverBase
{
public MockScanDriver(ErrorOutput errorOutput, AutoSaver autoSaver) : base(errorOutput, autoSaver)
{
}
public string MockDriverName { get; set; } = "mock";
public List<ScanDevice> MockDevices { get; set; } = new List<ScanDevice>();
public List<ScannedImage> MockOutput { get; set; } = new List<ScannedImage>();
public Exception MockError { get; set; }
public override string DriverName => "test";
public override bool IsSupported => true;
protected override List<ScanDevice> GetDeviceListInternal(ScanProfile scanProfile) => MockDevices;
protected override Task ScanInternal(ScannedImageSink sink, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent, CancellationToken cancelToken)
{
foreach (var img in MockOutput)
{
sink.PutImage(img);
}
if (MockError != null)
{
throw MockError;
}
return Task.CompletedTask;
}
}
}

View File

@ -130,7 +130,6 @@
<DesignTime>True</DesignTime>
<DependentUpon>PdfiumTestsData.resx</DependentUpon>
</Compile>
<Compile Include="Mocks\MockScanDriver.cs" />
<Compile Include="Mocks\StubScanBridge.cs" />
<Compile Include="Scan\AutoSaveTests.cs" />
<Compile Include="Config\ProfileSerializerTestsData.Designer.cs">

View File

@ -21,159 +21,160 @@ namespace NAPS2.Sdk.Tests.Scan
{
public class AutoSaveTests : ContextualTexts
{
[Fact]
public async Task NoImages()
{
var errorOutput = new Mock<ErrorOutput>();
var driver = Driver(errorOutput.Object, 0);
var scanProfile = Profile(new AutoSaveSettings
{
FilePath = Path.Combine(FolderPath, "test$(n).pdf")
});
var scanParams = new ScanParams();
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
var files = Folder.GetFiles();
Assert.Empty(scannedImages);
Assert.Empty(files);
errorOutput.VerifyNoOtherCalls();
}
[Fact]
public async Task OneImageOnePdf()
{
var errorOutput = new Mock<ErrorOutput>();
var driver = Driver(errorOutput.Object, 1);
var scanProfile = Profile(new AutoSaveSettings
{
FilePath = Path.Combine(FolderPath, "test$(n).pdf")
});
var scanParams = new ScanParams();
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
var files = Folder.GetFiles();
Assert.Single(scannedImages);
Assert.Single(files);
PdfAsserts.AssertPageCount(1, files[0].FullName);
errorOutput.VerifyNoOtherCalls();
}
[Fact]
public async Task TwoImagesOnePdf()
{
var errorOutput = new Mock<ErrorOutput>();
var driver = Driver(errorOutput.Object, 2);
var scanProfile = Profile(new AutoSaveSettings
{
FilePath = Path.Combine(FolderPath, "test$(n).pdf"),
Separator = SaveSeparator.FilePerScan
});
var scanParams = new ScanParams();
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
var files = Folder.GetFiles();
Assert.Equal(2, scannedImages.Count);
Assert.Single(files);
PdfAsserts.AssertPageCount(2, files[0].FullName);
errorOutput.VerifyNoOtherCalls();
}
[Fact]
public async Task TwoImagesTwoPdfs()
{
var errorOutput = new Mock<ErrorOutput>();
var driver = Driver(errorOutput.Object, 2);
var scanProfile = Profile(new AutoSaveSettings
{
FilePath = Path.Combine(FolderPath, "test$(n).pdf"),
Separator = SaveSeparator.FilePerPage
});
var scanParams = new ScanParams();
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
var files = Folder.GetFiles();
Assert.Equal(2, scannedImages.Count);
Assert.Equal(2, files.Length);
PdfAsserts.AssertPageCount(1, files[0].FullName);
PdfAsserts.AssertPageCount(1, files[1].FullName);
errorOutput.VerifyNoOtherCalls();
}
[Fact]
public async Task TwoImagesTwoJpegs()
{
var errorOutput = new Mock<ErrorOutput>();
var driver = Driver(errorOutput.Object, 2);
var scanProfile = Profile(new AutoSaveSettings
{
FilePath = Path.Combine(FolderPath, "test$(n).jpg")
});
var scanParams = new ScanParams();
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
var files = Folder.GetFiles();
Assert.Equal(2, scannedImages.Count);
Assert.Equal(2, files.Length);
errorOutput.VerifyNoOtherCalls();
}
[Fact]
public async Task ClearAfterSaving()
{
var errorOutput = new Mock<ErrorOutput>();
var driver = Driver(errorOutput.Object, 2);
var scanProfile = Profile(new AutoSaveSettings
{
FilePath = Path.Combine(FolderPath, "test$(n).jpg"),
ClearImagesAfterSaving = true
});
var scanParams = new ScanParams();
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
var files = Folder.GetFiles();
Assert.Empty(scannedImages);
Assert.Equal(2, files.Length);
errorOutput.VerifyNoOtherCalls();
}
// TODO: ClearAfterSaving with error, PromptForFilePath, SaveSeparator
private ScanDevice Device => new ScanDevice("test_id", "test_name");
private MockScanDriver Driver(ErrorOutput errorOutput, int images) => new MockScanDriver(errorOutput, CreateAutoSaver(errorOutput))
{
MockDevices = new List<ScanDevice> { Device },
MockOutput = Enumerable.Range(0, images).Select(i => CreateScannedImage()).ToList()
};
private ScanProfile Profile(AutoSaveSettings autoSaveSettings) => new ScanProfile
{
Device = Device,
EnableAutoSave = true,
AutoSaveSettings = autoSaveSettings
};
private AutoSaver CreateAutoSaver(ErrorOutput errorOutput)
{
return new AutoSaver(
new StubConfigProvider<PdfSettings>(new PdfSettings()),
new StubConfigProvider<ImageSettings>(new ImageSettings()),
new OcrEngineManager(),
new OcrRequestQueue(new OcrEngineManager(), new StubOperationProgress()),
errorOutput,
new StubDialogHelper(),
new StubOperationProgress(),
null,
new PdfSharpExporter(new MemoryStreamRenderer(ImageContext)),
new StubOverwritePrompt(),
new BitmapRenderer(ImageContext),
new StubConfigProvider<CommonConfig>(InternalDefaults.GetCommonConfig()));
}
// TODO: Need to make ScanPerformer testable
// [Fact]
// public async Task NoImages()
// {
// var errorOutput = new Mock<ErrorOutput>();
// var driver = Driver(errorOutput.Object, 0);
//
// var scanProfile = Profile(new AutoSaveSettings
// {
// FilePath = Path.Combine(FolderPath, "test$(n).pdf")
// });
// var scanParams = new ScanParams();
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
// var files = Folder.GetFiles();
//
// Assert.Empty(scannedImages);
// Assert.Empty(files);
// errorOutput.VerifyNoOtherCalls();
// }
//
// [Fact]
// public async Task OneImageOnePdf()
// {
// var errorOutput = new Mock<ErrorOutput>();
// var driver = Driver(errorOutput.Object, 1);
//
// var scanProfile = Profile(new AutoSaveSettings
// {
// FilePath = Path.Combine(FolderPath, "test$(n).pdf")
// });
// var scanParams = new ScanParams();
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
// var files = Folder.GetFiles();
//
// Assert.Single(scannedImages);
// Assert.Single(files);
// PdfAsserts.AssertPageCount(1, files[0].FullName);
// errorOutput.VerifyNoOtherCalls();
// }
//
// [Fact]
// public async Task TwoImagesOnePdf()
// {
// var errorOutput = new Mock<ErrorOutput>();
// var driver = Driver(errorOutput.Object, 2);
//
// var scanProfile = Profile(new AutoSaveSettings
// {
// FilePath = Path.Combine(FolderPath, "test$(n).pdf"),
// Separator = SaveSeparator.FilePerScan
// });
// var scanParams = new ScanParams();
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
// var files = Folder.GetFiles();
//
// Assert.Equal(2, scannedImages.Count);
// Assert.Single(files);
// PdfAsserts.AssertPageCount(2, files[0].FullName);
// errorOutput.VerifyNoOtherCalls();
// }
//
// [Fact]
// public async Task TwoImagesTwoPdfs()
// {
// var errorOutput = new Mock<ErrorOutput>();
// var driver = Driver(errorOutput.Object, 2);
//
// var scanProfile = Profile(new AutoSaveSettings
// {
// FilePath = Path.Combine(FolderPath, "test$(n).pdf"),
// Separator = SaveSeparator.FilePerPage
// });
// var scanParams = new ScanParams();
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
// var files = Folder.GetFiles();
//
// Assert.Equal(2, scannedImages.Count);
// Assert.Equal(2, files.Length);
// PdfAsserts.AssertPageCount(1, files[0].FullName);
// PdfAsserts.AssertPageCount(1, files[1].FullName);
// errorOutput.VerifyNoOtherCalls();
// }
//
// [Fact]
// public async Task TwoImagesTwoJpegs()
// {
// var errorOutput = new Mock<ErrorOutput>();
// var driver = Driver(errorOutput.Object, 2);
//
// var scanProfile = Profile(new AutoSaveSettings
// {
// FilePath = Path.Combine(FolderPath, "test$(n).jpg")
// });
// var scanParams = new ScanParams();
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
// var files = Folder.GetFiles();
//
// Assert.Equal(2, scannedImages.Count);
// Assert.Equal(2, files.Length);
// errorOutput.VerifyNoOtherCalls();
// }
//
// [Fact]
// public async Task ClearAfterSaving()
// {
// var errorOutput = new Mock<ErrorOutput>();
// var driver = Driver(errorOutput.Object, 2);
//
// var scanProfile = Profile(new AutoSaveSettings
// {
// FilePath = Path.Combine(FolderPath, "test$(n).jpg"),
// ClearImagesAfterSaving = true
// });
// var scanParams = new ScanParams();
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
// var files = Folder.GetFiles();
//
// Assert.Empty(scannedImages);
// Assert.Equal(2, files.Length);
// errorOutput.VerifyNoOtherCalls();
// }
//
// // TODO: ClearAfterSaving with error, PromptForFilePath, SaveSeparator
//
// private ScanDevice Device => new ScanDevice("test_id", "test_name");
//
// private MockScanDriver Driver(ErrorOutput errorOutput, int images) => new MockScanDriver(errorOutput, CreateAutoSaver(errorOutput))
// {
// MockDevices = new List<ScanDevice> { Device },
// MockOutput = Enumerable.Range(0, images).Select(i => CreateScannedImage()).ToList()
// };
//
// private ScanProfile Profile(AutoSaveSettings autoSaveSettings) => new ScanProfile
// {
// Device = Device,
// EnableAutoSave = true,
// AutoSaveSettings = autoSaveSettings
// };
//
// private AutoSaver CreateAutoSaver(ErrorOutput errorOutput)
// {
// return new AutoSaver(
// new StubConfigProvider<PdfSettings>(new PdfSettings()),
// new StubConfigProvider<ImageSettings>(new ImageSettings()),
// new OcrEngineManager(),
// new OcrRequestQueue(new OcrEngineManager(), new StubOperationProgress()),
// errorOutput,
// new StubDialogHelper(),
// new StubOperationProgress(),
// null,
// new PdfSharpExporter(new MemoryStreamRenderer(ImageContext)),
// new StubOverwritePrompt(),
// new BitmapRenderer(ImageContext),
// new StubConfigProvider<CommonConfig>(InternalDefaults.GetCommonConfig()));
// }
}
}

View File

@ -49,7 +49,7 @@ namespace NAPS2.Config
{
if (settings.Version == 1)
{
if (settings.DriverName == TwainScanDriver.DRIVER_NAME)
if (settings.DriverName == DriverNames.TWAIN)
{
settings.UseNativeUI = true;
}
@ -68,7 +68,7 @@ namespace NAPS2.Config
{
if (settings.Version == 1)
{
if (settings.DriverName == TwainScanDriver.DRIVER_NAME)
if (settings.DriverName == DriverNames.TWAIN)
{
settings.UseNativeUI = true;
}
@ -123,7 +123,7 @@ namespace NAPS2.Config
IsDefault = profile.IsDefault,
IconID = profile.IconID,
// If the driver is WIA and the profile type is not Extended, that meant the native UI was to be used
UseNativeUI = profile.DriverName == WiaScanDriver.DRIVER_NAME
UseNativeUI = profile.DriverName == DriverNames.WIA
};
if (profile is ExtendedScanSettingsV0 ext)
{

View File

@ -148,7 +148,6 @@
<Compile Include="Remoting\ClientServer\ClientContextFactory.cs" />
<Compile Include="Remoting\ClientServer\IScanCallback.cs" />
<Compile Include="Remoting\ClientServer\IScanService.cs" />
<Compile Include="Remoting\ClientServer\ProxiedScanDriver.cs" />
<Compile Include="Remoting\ClientServer\ScanCallback.cs" />
<Compile Include="Remoting\ClientServer\ScanService.cs" />
<Compile Include="Remoting\ClientServer\ServerDiscovery.cs" />
@ -231,6 +230,7 @@
<Compile Include="Scan\Batch\BatchOutputType.cs" />
<Compile Include="Scan\Batch\BatchScanType.cs" />
<Compile Include="Scan\Batch\IBatchScanPerformer.cs" />
<Compile Include="Scan\DriverNames.cs" />
<Compile Include="Scan\Exceptions\DriverNotSupportedException.cs" />
<Compile Include="Images\ScannedImageSource.cs" />
<Compile Include="Images\Storage\GdiTransformers.cs" />
@ -304,11 +304,8 @@
<Compile Include="Scan\Sane\SaneOption.cs" />
<Compile Include="Scan\Sane\SaneOptionCollection.cs" />
<Compile Include="Scan\Sane\SaneOptionParser.cs" />
<Compile Include="Scan\Sane\SaneScanDriver.cs" />
<Compile Include="Scan\Sane\SaneWrapper.cs" />
<Compile Include="Scan\Experimental\Driver.cs" />
<Compile Include="Scan\Experimental\ScanOptions.cs" />
<Compile Include="Scan\Twain\ITwainWrapper.cs" />
<Compile Include="Scan\Wia\Native\Hresult.cs" />
<Compile Include="Scan\Wia\Native\IWiaDeviceProps.cs" />
<Compile Include="Scan\Wia\Native\NativeStreamWrapper.cs" />
@ -333,7 +330,6 @@
<Compile Include="Scan\Wia\Native\WiaVersion.cs" />
<Compile Include="Scan\Wia\WiaConfiguration.cs" />
<Compile Include="Scan\Wia\WiaScanErrors.cs" />
<Compile Include="Scan\Wia\WiaScanOperation.cs" />
<Compile Include="Serialization\SerializedImageHelper.cs" />
<Compile Include="Update\UpdateOperation.cs" />
<Compile Include="Update\UpdateInfo.cs" />
@ -460,7 +456,6 @@
<Compile Include="Scan\Twain\Legacy\TwainApi.cs" />
<Compile Include="Scan\Twain\Legacy\TwainDefs.cs" />
<Compile Include="Scan\Twain\Legacy\TwainLib.cs" />
<Compile Include="Scan\Twain\TwainWrapper.cs" />
<Compile Include="Images\Transforms\UnsafeImageOps.cs" />
<Compile Include="Util\ChangeTracker.cs" />
<Compile Include="Util\ChaosMonkey.cs" />
@ -508,7 +503,6 @@
<Compile Include="Images\Transforms\CropTransform.cs" />
<Compile Include="Images\Transforms\RotationTransform.cs" />
<Compile Include="Images\Transforms\Transform.cs" />
<Compile Include="Scan\IScanDriverFactory.cs" />
<Compile Include="Util\CultureInitializer.cs" />
<Compile Include="Util\ErrorOutput.cs" />
<Compile Include="Logging\ILogger.cs" />
@ -562,16 +556,9 @@
<Compile Include="Images\ScannedImageHelper.cs" />
<Compile Include="Images\ScannedImageList.cs" />
<Compile Include="Images\ThumbnailRenderer.cs" />
<Compile Include="Scan\IScanDriver.cs" />
<Compile Include="Scan\IScanPerformer.cs" />
<Compile Include="Scan\LocalizedDescriptionAttribute.cs" />
<Compile Include="Scan\ScanDevice.cs" />
<Compile Include="Scan\ScanPerformer.cs" />
<Compile Include="Scan\Stub\StubScanDriverFactory.cs" />
<Compile Include="Scan\Stub\StubScanDriver.cs" />
<Compile Include="Scan\Twain\TwainScanDriver.cs" />
<Compile Include="Scan\ScanDriverBase.cs" />
<Compile Include="Scan\Wia\WiaScanDriver.cs" />
<Compile Include="Operation\OperationErrorEventArgs.cs" />
<Compile Include="Operation\OperationStatus.cs" />
<Compile Include="Util\NumberExtensions.cs" />

View File

@ -1,125 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.Images;
using NAPS2.ImportExport;
using NAPS2.Logging;
using NAPS2.Scan;
using NAPS2.Util;
using NAPS2.WinForms;
namespace NAPS2.Remoting.ClientServer
{
public class ProxiedScanDriver : ScanDriverBase
{
public const string DRIVER_NAME = "proxy";
private readonly ClientContextFactory clientContextFactory;
public ProxiedScanDriver(ClientContextFactory clientContextFactory, ErrorOutput errorOutput, AutoSaver autoSaver) : base(errorOutput, autoSaver)
{
this.clientContextFactory = clientContextFactory;
}
public override string DriverName => DRIVER_NAME;
public override bool IsSupported => true;
protected override List<ScanDevice> GetDeviceListInternal(ScanProfile scanProfile)
{
if (scanProfile?.ProxyConfig == null)
{
throw new ArgumentException("ScanProfile.ProxyConfig must be specified to use ProxiedScanDriver.", nameof(scanProfile));
}
using (var client = clientContextFactory.Create(scanProfile.ProxyConfig))
{
return client.Service.GetDeviceList(scanProfile);
}
}
protected override Task ScanInternal(ScannedImageSink sink, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent, CancellationToken cancelToken)
{
if (scanProfile.ProxyConfig == null)
{
throw new InvalidOperationException("ScanProfile.ProxyConfig must be specified to use ProxiedScanDriver.");
}
return Task.Run(async () =>
{
try
{
using (var client = clientContextFactory.Create(scanProfile.ProxyConfig))
{
var noUi = scanParams.NoUI;
FScanProgress form = Invoker.Current.InvokeGet(() => noUi ? null : new FScanProgress());
int pageNumber = 1;
var sem = new Semaphore(0, int.MaxValue);
client.Callback.ImageCallback += (imageBytes, indexImage) =>
{
try
{
// TODO
//indexImage.FileName = RecoveryImage.GetNextFileName() + Path.GetExtension(indexImage.FileName);
//var recoveryFilePath = Path.Combine(RecoveryImage.RecoveryFolder.FullName, indexImage.FileName);
//File.WriteAllBytes(recoveryFilePath, imageBytes);
//var image = new ScannedImage(indexImage);
//using (var bitmap = new Bitmap(new MemoryStream(imageBytes)))
//{
// scannedImageHelper.PostProcessStep2(image, bitmap, ScanProfile, ScanParams, pageNumber++, false);
//}
//source.Put(image);
//if (form != null)
//{
// form.PageNumber = pageNumber;
// Invoker.Current.SafeInvoke(() => form.RefreshStatus());
//}
}
finally
{
sem.Release();
}
};
var scanTask = client.Service.Scan(scanProfile, scanParams).ContinueWith(t =>
{
for (int i = 0; i < t.Result; i++)
{
sem.WaitOne();
}
});
if (!noUi)
{
form.PageNumber = pageNumber;
form.AsyncTransfer = async () => await scanTask;
form.CancelToken.Register(client.Service.CancelScan);
}
cancelToken.Register(client.Service.CancelScan);
if (noUi)
{
await scanTask;
}
else if (scanParams.Modal)
{
Invoker.Current.SafeInvoke(() => form.ShowDialog());
}
else
{
Invoker.Current.SafeInvoke(() => form.Show());
await scanTask;
}
}
}
catch (Exception e)
{
Log.ErrorException("Error scanning with proxy", e);
}
});
}
}
}

View File

@ -1,12 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.Platform;
using NAPS2.Scan;
using NAPS2.Scan.Sane;
using NAPS2.Scan.Twain;
using NAPS2.Scan.Wia;
using NAPS2.Scan.Experimental.Internal;
namespace NAPS2.Remoting.ClientServer
{
@ -15,95 +13,100 @@ namespace NAPS2.Remoting.ClientServer
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ScanService : IScanService
{
private readonly IScanDriverFactory scanDriverFactory;
private readonly IScanPerformer scanPerformer;
private readonly IRemoteScanController remoteScanController;
private CancellationTokenSource scanCts = new CancellationTokenSource();
public ScanService(IScanDriverFactory scanDriverFactory, IScanPerformer scanPerformer)
public ScanService() : this(new RemoteScanController())
{
this.scanDriverFactory = scanDriverFactory;
this.scanPerformer = scanPerformer;
}
internal ScanService(IRemoteScanController remoteScanController)
{
this.remoteScanController = remoteScanController;
}
public List<string> GetSupportedDriverNames()
{
var driverNames = new List<string>();
if (PlatformCompat.System.IsWiaDriverSupported)
{
driverNames.Add(WiaScanDriver.DRIVER_NAME);
}
if (PlatformCompat.System.IsTwainDriverSupported)
{
driverNames.Add(TwainScanDriver.DRIVER_NAME);
}
if (PlatformCompat.System.IsSaneDriverSupported)
{
driverNames.Add(SaneScanDriver.DRIVER_NAME);
}
return driverNames;
throw new NotImplementedException();
// var driverNames = new List<string>();
// if (PlatformCompat.System.IsWiaDriverSupported)
// {
// driverNames.Add(WiaScanDriver.DRIVER_NAME);
// }
//
// if (PlatformCompat.System.IsTwainDriverSupported)
// {
// driverNames.Add(TwainScanDriver.DRIVER_NAME);
// }
//
// if (PlatformCompat.System.IsSaneDriverSupported)
// {
// driverNames.Add(SaneScanDriver.DRIVER_NAME);
// }
//
// return driverNames;
}
public List<ScanDevice> GetDeviceList(ScanProfile scanProfile)
{
if (scanProfile.DriverName == ProxiedScanDriver.DRIVER_NAME)
{
scanProfile.DriverName = scanProfile.ProxyDriverName;
}
var driver = scanDriverFactory.Create(scanProfile.DriverName);
return driver.GetDeviceList(scanProfile);
throw new NotImplementedException();
// if (scanProfile.DriverName == ProxiedScanDriver.DRIVER_NAME)
// {
// scanProfile.DriverName = scanProfile.ProxyDriverName;
// }
// var driver = scanDriverFactory.Create(scanProfile.DriverName);
// return driver.GetDeviceList(scanProfile);
}
public async Task<int> Scan(ScanProfile scanProfile, ScanParams scanParams)
{
if (scanProfile.DriverName == ProxiedScanDriver.DRIVER_NAME)
{
scanProfile.DriverName = scanProfile.ProxyDriverName;
}
if (scanProfile.TwainImpl == TwainImpl.Legacy)
{
scanProfile.TwainImpl = TwainImpl.OldDsm;
}
scanProfile.UseNativeUI = false;
// TODO: Turn PropagateErrors on?
var internalParams = new ScanParams
{
DetectPatchCodes = scanParams.DetectPatchCodes,
NoUI = true,
NoAutoSave = true,
DoOcr = false,
SkipPostProcessing = true
};
var callback = OperationContext.Current.GetCallbackChannel<IScanCallback>();
int pages = 0;
var source = await scanPerformer.PerformScan(scanProfile, internalParams, cancelToken: scanCts.Token);
await source.ForEach(image =>
{
// TODO: Should stream this
// TODO: Also should think about avoiding the intermediate filesystem
using (image)
{
// TODO
//var indexImage = image.RecoveryIndexImage;
//var imageBytes = File.ReadAllBytes(image.RecoveryFilePath);
//var sanitizedIndexImage = new RecoveryIndexImage
//{
// FileName = Path.GetExtension(indexImage.FileName),
// TransformList = indexImage.TransformList,
// BitDepth = indexImage.BitDepth,
// HighQuality = indexImage.HighQuality
//};
//callback.ImageReceived(imageBytes, sanitizedIndexImage);
//pages++;
}
});
return pages;
throw new NotImplementedException();
// if (scanProfile.DriverName == ProxiedScanDriver.DRIVER_NAME)
// {
// scanProfile.DriverName = scanProfile.ProxyDriverName;
// }
// if (scanProfile.TwainImpl == TwainImpl.Legacy)
// {
// scanProfile.TwainImpl = TwainImpl.OldDsm;
// }
// scanProfile.UseNativeUI = false;
//
// // TODO: Turn PropagateErrors on?
// var internalParams = new ScanParams
// {
// DetectPatchCodes = scanParams.DetectPatchCodes,
// NoUI = true,
// NoAutoSave = true,
// DoOcr = false,
// SkipPostProcessing = true
// };
//
// var callback = OperationContext.Current.GetCallbackChannel<IScanCallback>();
//
// int pages = 0;
// var source = await scanPerformer.PerformScan(scanProfile, internalParams, cancelToken: scanCts.Token);
// await source.ForEach(image =>
// {
// // TODO: Should stream this
// // TODO: Also should think about avoiding the intermediate filesystem
// using (image)
// {
// // TODO
// //var indexImage = image.RecoveryIndexImage;
// //var imageBytes = File.ReadAllBytes(image.RecoveryFilePath);
// //var sanitizedIndexImage = new RecoveryIndexImage
// //{
// // FileName = Path.GetExtension(indexImage.FileName),
// // TransformList = indexImage.TransformList,
// // BitDepth = indexImage.BitDepth,
// // HighQuality = indexImage.HighQuality
// //};
// //callback.ImageReceived(imageBytes, sanitizedIndexImage);
// //pages++;
// }
// });
// return pages;
}
public void CancelScan()

View File

@ -0,0 +1,12 @@
namespace NAPS2.Scan
{
public static class DriverNames
{
// TODO: Ideally we just change everything to use the Driver enum
public const string WIA = "wia";
public const string TWAIN = "twain";
public const string SANE = "sane";
// TODO: Remove this
public const string PROXY = "proxy";
}
}

View File

@ -133,9 +133,9 @@ namespace NAPS2.Scan.Experimental
{
var options = new ScanOptions
{
Driver = scanProfile.DriverName == "wia" ? Driver.Wia
: scanProfile.DriverName == "sane" ? Driver.Sane
: scanProfile.DriverName == "twain" ? Driver.Twain
Driver = scanProfile.DriverName == DriverNames.WIA ? Driver.Wia
: scanProfile.DriverName == DriverNames.SANE ? Driver.Sane
: scanProfile.DriverName == DriverNames.TWAIN ? Driver.Twain
: Driver.Default,
WiaOptions =
{

View File

@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using NAPS2.Scan.Exceptions;
using NAPS2.Images;
namespace NAPS2.Scan
{
/// <summary>
/// An interface for document scanning drivers (WIA, TWAIN, SANE).
/// </summary>
public interface IScanDriver
{
/// <summary>
/// Gets a value indicating whether the driver is supported on the current platform.
/// </summary>
bool IsSupported { get; }
/// <summary>
/// Gets the name used to look up the driver in the IScanDriverFactory.
/// </summary>
string DriverName { get; }
/// <summary>
/// Prompts the user (via a dialog) to select a scanning device.
/// </summary>
/// <returns>The selected device, or null if no device was selected.</returns>
/// <exception cref="ScanDriverException">Throws a ScanDriverException if an error occurs when reading the available devices.</exception>
/// <exception cref="InvalidOperationException">Throws an InvalidOperationException if DialogParent has not been set.</exception>
ScanDevice PromptForDevice(ScanProfile scanProfile = null, IntPtr dialogParent = default);
/// <summary>
/// Gets a list of available scanning devices.
/// </summary>
/// <returns>The list of devices.</returns>
/// <exception cref="ScanDriverException">Throws a ScanDriverException if an error occurs when reading the available devices.</exception>
List<ScanDevice> GetDeviceList(ScanProfile scanProfile = null);
/// <summary>
/// Scans one or more images, interacting with the user as necessary.
/// </summary>
/// <returns>A list of scanned images.</returns>
/// <exception cref="ScanDriverException">Throws a ScanDriverException if an error occurs while scanning.</exception>
/// /// <exception cref="InvalidOperationException">Throws an InvalidOperationException if ScanProfile or DialogParent has not been set.</exception>
ScannedImageSource Scan(ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent = default, CancellationToken cancelToken = default);
}
}

View File

@ -1,10 +0,0 @@
namespace NAPS2.Scan
{
/// <summary>
/// An interface used to create instances of IScanDriver based on the driver name.
/// </summary>
public interface IScanDriverFactory
{
IScanDriver Create(string driverName);
}
}

View File

@ -1,280 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using NAPS2.Logging;
using NAPS2.Platform;
using NAPS2.Scan.Exceptions;
using NAPS2.Images;
using NAPS2.Images.Storage;
using NAPS2.Images.Transforms;
using NAPS2.ImportExport;
using NAPS2.Util;
using NAPS2.WinForms;
namespace NAPS2.Scan.Sane
{
public class SaneScanDriver : ScanDriverBase
{
public const string DRIVER_NAME = "sane";
private static readonly Dictionary<string, SaneOptionCollection> SaneOptionCache = new Dictionary<string, SaneOptionCollection>();
private readonly ImageContext imageContext;
private readonly SaneWrapper saneWrapper;
private readonly IFormFactory formFactory;
private readonly BlankDetector blankDetector;
private readonly ScannedImageHelper scannedImageHelper;
public SaneScanDriver(ImageContext imageContext, SaneWrapper saneWrapper, IFormFactory formFactory, BlankDetector blankDetector, ScannedImageHelper scannedImageHelper, ErrorOutput errorOutput, AutoSaver autoSaver) : base(errorOutput, autoSaver)
{
this.imageContext = imageContext;
this.saneWrapper = saneWrapper;
this.formFactory = formFactory;
this.blankDetector = blankDetector;
this.scannedImageHelper = scannedImageHelper;
}
public override string DriverName => DRIVER_NAME;
public override bool IsSupported => PlatformCompat.System.IsSaneDriverSupported;
protected override List<ScanDevice> GetDeviceListInternal(ScanProfile scanProfile)
{
return saneWrapper.GetDeviceList().ToList();
}
protected override async Task ScanInternal(ScannedImageSink sink, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent, CancellationToken cancelToken)
{
// TODO: Test ADF
var options = new Lazy<KeyValueScanOptions>(() => GetOptions(scanProfile, scanDevice));
int pageNumber = 1;
var (img, done) = await Transfer(options, pageNumber, scanProfile, scanParams, scanDevice, cancelToken);
if (img != null)
{
sink.PutImage(img);
}
if (!done && scanProfile.PaperSource != ScanSource.Glass)
{
try
{
while (true)
{
(img, done) = await Transfer(options, ++pageNumber, scanProfile, scanParams, scanDevice, cancelToken);
if (done)
{
break;
}
if (img != null)
{
sink.PutImage(img);
}
}
}
catch (Exception e)
{
Log.ErrorException("Error in SANE. This may be a normal ADF termination.", e);
}
}
}
private KeyValueScanOptions GetOptions(ScanProfile scanProfile, ScanDevice scanDevice)
{
var saneOptions = SaneOptionCache.GetOrSet(scanDevice.ID, () => saneWrapper.GetOptions(scanDevice.ID));
var options = new KeyValueScanOptions(scanProfile.KeyValueOptions ?? new KeyValueScanOptions());
bool ChooseStringOption(string name, Func<string, bool> match)
{
var opt = saneOptions.Get(name);
var choice = opt?.StringList?.FirstOrDefault(match);
if (choice != null)
{
options[name] = choice;
return true;
}
return false;
}
bool ChooseNumericOption(string name, decimal value)
{
var opt = saneOptions.Get(name);
if (opt?.ConstraintType == SaneConstraintType.WordList)
{
var choice = opt.WordList?.OrderBy(x => Math.Abs(x - value)).FirstOrDefault();
if (choice != null)
{
options[name] = choice.Value.ToString(CultureInfo.InvariantCulture);
return true;
}
}
else if (opt?.ConstraintType == SaneConstraintType.Range)
{
if (value < opt.Range.Min)
{
value = opt.Range.Min;
}
if (value > opt.Range.Max)
{
value = opt.Range.Max;
}
if (opt.Range.Quant != 0)
{
var mod = (value - opt.Range.Min) % opt.Range.Quant;
if (mod != 0)
{
value = mod < opt.Range.Quant / 2 ? value - mod : value + opt.Range.Quant - mod;
}
}
options[name] = value.ToString("0.#####", CultureInfo.InvariantCulture);
return true;
}
return false;
}
bool IsFlatbedChoice(string choice) => choice.IndexOf("flatbed", StringComparison.InvariantCultureIgnoreCase) >= 0;
bool IsFeederChoice(string choice) => new[] { "adf", "feeder", "simplex" }.Any(x => choice.IndexOf(x, StringComparison.InvariantCultureIgnoreCase) >= 0);
bool IsDuplexChoice(string choice) => choice.IndexOf("duplex", StringComparison.InvariantCultureIgnoreCase) >= 0;
if (scanProfile.PaperSource == ScanSource.Glass)
{
ChooseStringOption("--source", IsFlatbedChoice);
}
else if (scanProfile.PaperSource == ScanSource.Feeder)
{
if (!ChooseStringOption("--source", x => IsFeederChoice(x) && !IsDuplexChoice(x)) &&
!ChooseStringOption("--source", IsFeederChoice) &&
!ChooseStringOption("--source", IsDuplexChoice))
{
throw new NoFeederSupportException();
}
}
else if (scanProfile.PaperSource == ScanSource.Duplex)
{
if (!ChooseStringOption("--source", IsDuplexChoice))
{
throw new NoDuplexSupportException();
}
}
if (scanProfile.BitDepth == ScanBitDepth.C24Bit)
{
ChooseStringOption("--mode", x => x == "Color");
ChooseNumericOption("--depth", 8);
}
else if (scanProfile.BitDepth == ScanBitDepth.Grayscale)
{
ChooseStringOption("--mode", x => x == "Gray");
ChooseNumericOption("--depth", 8);
}
else if (scanProfile.BitDepth == ScanBitDepth.BlackWhite)
{
if (!ChooseStringOption("--mode", x => x == "Lineart"))
{
ChooseStringOption("--mode", x => x == "Halftone");
}
ChooseNumericOption("--depth", 1);
ChooseNumericOption("--threshold", (-scanProfile.Brightness + 1000) / 20m);
}
var pageDimens = scanProfile.PageSize.PageDimensions() ?? scanProfile.CustomPageSize;
if (pageDimens != null)
{
var width = pageDimens.WidthInMm();
var height = pageDimens.HeightInMm();
ChooseNumericOption("-x", width);
ChooseNumericOption("-y", height);
var maxWidth = saneOptions.Get("-l")?.Range?.Max;
var maxHeight = saneOptions.Get("-t")?.Range?.Max;
if (maxWidth != null)
{
if (scanProfile.PageAlign == ScanHorizontalAlign.Center)
{
ChooseNumericOption("-l", (maxWidth.Value - width) / 2);
}
else if (scanProfile.PageAlign == ScanHorizontalAlign.Right)
{
ChooseNumericOption("-l", maxWidth.Value - width);
}
else
{
ChooseNumericOption("-l", 0);
}
}
if (maxHeight != null)
{
ChooseNumericOption("-t", 0);
}
}
var dpi = scanProfile.Resolution.ToIntDpi();
if (!ChooseNumericOption("--resolution", dpi))
{
ChooseNumericOption("--x-resolution", dpi);
ChooseNumericOption("--y-resolution", dpi);
}
return options;
}
private async Task<(ScannedImage, bool)> Transfer(Lazy<KeyValueScanOptions> options, int pageNumber, ScanProfile scanProfile, ScanParams scanParams, ScanDevice scanDevice, CancellationToken cancelToken)
{
return await Task.Run(() =>
{
Stream stream;
if (scanParams.NoUI)
{
stream = saneWrapper.ScanOne(scanDevice.ID, options.Value, null, cancelToken);
}
else
{
var form = formFactory.Create<FScanProgress>();
var unifiedCancelToken = CancellationTokenSource.CreateLinkedTokenSource(form.CancelToken, cancelToken).Token;
form.Transfer = () => saneWrapper.ScanOne(scanDevice.ID, options.Value, form.OnProgress, unifiedCancelToken);
form.PageNumber = pageNumber;
((FormBase)Application.OpenForms[0]).SafeInvoke(() => form.ShowDialog());
if (form.Exception != null)
{
form.Exception.PreserveStackTrace();
throw form.Exception;
}
if (form.DialogResult == DialogResult.Cancel)
{
return (null, true);
}
stream = form.ImageStream;
}
if (stream == null)
{
return (null, true);
}
using (stream)
using (var output = imageContext.ImageFactory.Decode(stream, ".bmp"))
using (var result = scannedImageHelper.PostProcessStep1(output, scanProfile, false))
{
if (blankDetector.ExcludePage(result, scanProfile))
{
return (null, false);
}
// By converting to 1bpp here we avoid the Win32 call in the BitmapHelper conversion
// This converter also has the side effect of working even if the scanner doesn't support Lineart
using (var encoded = scanProfile.BitDepth == ScanBitDepth.BlackWhite ? imageContext.PerformTransform(result, new BlackWhiteTransform(-scanProfile.Brightness)) : result)
{
var image = imageContext.CreateScannedImage(encoded, scanProfile.BitDepth.ToBitDepth(), scanProfile.MaxQuality, scanProfile.Quality);
scannedImageHelper.PostProcessStep2(image, result, scanProfile, scanParams, 1, false);
string tempPath = scannedImageHelper.SaveForBackgroundOcr(result, scanParams);
scannedImageHelper.RunBackgroundOcr(image, scanParams, tempPath);
return (image, false);
}
}
});
}
}
}

View File

@ -1,170 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.Lang.Resources;
using NAPS2.Scan.Exceptions;
using NAPS2.Util;
namespace NAPS2.Scan.Sane
{
public class SaneWrapper
{
private const string SCANIMAGE = "scanimage";
private const int SIGINT = 2;
private const int SIGTERM = 15;
private static readonly Regex ProgressRegex = new Regex(@"^Progress: (\d+(\.\d+)?)%");
public IEnumerable<ScanDevice> GetDeviceList()
{
var proc = StartProcess(SCANIMAGE, @"--formatted-device-list=%d|%m%n");
string line;
while ((line = proc.StandardOutput.ReadLine()?.Trim()) != null)
{
string[] parts = line.Split('|');
if (parts.Length == 2)
{
yield return new ScanDevice(parts[0], parts[1]);
}
}
}
public SaneOptionCollection GetOptions(string deviceId)
{
var proc = StartProcess(SCANIMAGE, $@"--help --device-name={deviceId}");
return new SaneOptionParser().Parse(proc.StandardOutput);
}
public Stream ScanOne(string deviceId, KeyValueScanOptions options, ProgressHandler progressCallback, CancellationToken cancelToken)
{
// Start the scanning process
var profileOptions = options == null ? "" : string.Join("", options.Select(kvp => $@" {kvp.Key} ""{kvp.Value.Replace("\"", "\\\"")}"""));
var allOptions = $@"--device-name=""{deviceId}"" --format=tiff --progress{profileOptions}";
var proc = StartProcess(SCANIMAGE, allOptions);
// Set up state
var procExitWaitHandle = new ManualResetEvent(false);
var outputFinishedWaitHandle = new ManualResetEvent(false);
var errorOutput = new List<string>();
bool cancelled = false;
const int maxProgress = 1000;
// Set up events
proc.ErrorDataReceived += (sender, args) =>
{
if (args.Data != null)
{
var match = ProgressRegex.Match(args.Data);
if (match.Success)
{
progressCallback?.Invoke((int)float.Parse(match.Groups[1].Value) * 10, maxProgress);
}
else
{
errorOutput.Add(args.Data);
}
}
};
proc.Exited += (sender, args) => procExitWaitHandle.Set();
proc.BeginErrorReadLine();
// Read the image output into a MemoryStream off-thread
var outputStream = new MemoryStream();
Task.Run(() =>
{
proc.StandardOutput.BaseStream.CopyTo(outputStream);
outputStream.Seek(0, SeekOrigin.Begin);
outputFinishedWaitHandle.Set();
});
// Wait for the process to stop (or for the user to cancel)
WaitHandle.WaitAny(new[] { procExitWaitHandle, cancelToken.WaitHandle });
if (cancelToken.IsCancellationRequested)
{
cancelled = true;
SafeStopProcess(proc, procExitWaitHandle);
}
// Ensure the image output thread has finished so we don't return an incomplete MemoryStream
outputFinishedWaitHandle.WaitOne();
if (cancelled)
{
// The user has cancelled, so we can ignore everything else
return null;
}
if (errorOutput.Count > 0)
{
// Non-progress output to stderr indicates that the scan was not successful
string stderr = string.Join(". ", errorOutput).Trim();
ThrowDeviceError(stderr);
}
// No unexpected stderr output, so we can assume that the output stream is complete and valid
return outputStream;
}
private static void ThrowDeviceError(string stderr)
{
if (stderr.EndsWith("Device busy", StringComparison.InvariantCultureIgnoreCase))
{
throw new DeviceException(MiscResources.DeviceBusy);
}
if (stderr.EndsWith("Invalid argument", StringComparison.InvariantCultureIgnoreCase))
{
throw new DeviceException(MiscResources.DeviceNotFound);
}
throw new ScanDriverUnknownException(new Exception(stderr));
}
private static void SafeStopProcess(Process proc, WaitHandle procExitWaitHandle)
{
Signal(proc, SIGINT);
if (!procExitWaitHandle.WaitOne(5000))
{
Signal(proc, SIGTERM);
if (!procExitWaitHandle.WaitOne(1000))
{
proc.Kill();
}
}
}
private static void Signal(Process proc, int signum)
{
var posix = Assembly.Load("Mono.Posix");
var syscall = posix?.GetType("Mono.Unix.Native.Syscall");
var kill = syscall?.GetMethod("kill", BindingFlags.Static | BindingFlags.Public);
kill?.Invoke(null, new object[] { proc.Id, signum });
}
private static Process StartProcess(string fileName, string args)
{
try
{
var proc = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
},
EnableRaisingEvents = true
};
proc.Start();
return proc;
}
catch (Exception e)
{
throw new SaneNotAvailableException(e);
}
}
}
}

View File

@ -1,210 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.Scan.Exceptions;
using NAPS2.Images;
using NAPS2.ImportExport;
using NAPS2.Lang.Resources;
using NAPS2.Logging;
using NAPS2.Util;
using NAPS2.WinForms;
namespace NAPS2.Scan
{
/// <summary>
/// A base class for IScanDriver implementing common error handling.
/// </summary>
public abstract class ScanDriverBase : IScanDriver
{
private readonly ErrorOutput errorOutput;
private readonly AutoSaver autoSaver;
protected ScanDriverBase(ErrorOutput errorOutput, AutoSaver autoSaver)
{
this.errorOutput = errorOutput;
this.autoSaver = autoSaver;
}
public abstract string DriverName { get; }
public abstract bool IsSupported { get; }
public ScanDevice PromptForDevice(ScanProfile scanProfile, IntPtr dialogParent = default)
{
if (!IsSupported)
{
throw new DriverNotSupportedException();
}
try
{
return PromptForDeviceInternal(scanProfile, dialogParent);
}
catch (ScanDriverException)
{
throw;
}
catch (Exception e)
{
throw new ScanDriverUnknownException(e);
}
}
protected virtual ScanDevice PromptForDeviceInternal(ScanProfile scanProfile, IntPtr dialogParent)
{
var deviceList = GetDeviceList(scanProfile);
if (!deviceList.Any())
{
throw new NoDevicesFoundException();
}
var form = new FSelectDevice
{
DeviceList = deviceList
};
form.ShowDialog();
return form.SelectedDevice;
}
public List<ScanDevice> GetDeviceList(ScanProfile scanProfile)
{
if (!IsSupported)
{
throw new DriverNotSupportedException();
}
try
{
return GetDeviceListInternal(scanProfile);
}
catch (ScanDriverException)
{
throw;
}
catch (Exception e)
{
throw new ScanDriverUnknownException(e);
}
}
protected abstract List<ScanDevice> GetDeviceListInternal(ScanProfile scanProfile);
public ScannedImageSource Scan(ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent = default, CancellationToken cancelToken = default)
{
if (!IsSupported)
{
throw new DriverNotSupportedException();
}
if (scanProfile == null)
{
throw new ArgumentNullException(nameof(scanProfile));
}
if (scanParams == null)
{
throw new ArgumentNullException(nameof(scanParams));
}
var sink = new ScannedImageSink();
Task.Run(async () =>
{
ScanDriverException error = null;
try
{
var device = GetScanDevice(scanProfile);
if (device != null)
{
await ScanInternal(sink, device, scanProfile, scanParams, dialogParent, cancelToken);
}
if (sink.ImageCount > 0)
{
Log.Event(EventType.Scan, new EventParams
{
Name = MiscResources.Scan,
Pages = sink.ImageCount,
DeviceName = scanProfile.Device?.Name,
ProfileName = scanProfile.DisplayName,
BitDepth = scanProfile.BitDepth.Description()
});
}
sink.SetCompleted();
}
catch (ScanDriverException e)
{
error = e;
}
// TODO
//catch (FaultException<ScanDriverExceptionDetail> e)
//{
// error = e.Detail.Exception;
//}
catch (Exception e)
{
error = new ScanDriverUnknownException(e);
}
if (error != null)
{
if (error is ScanDriverUnknownException)
{
Log.ErrorException(error.Message, error.InnerException);
errorOutput?.DisplayError(error.Message, error);
}
else
{
errorOutput?.DisplayError(error.Message);
}
if (scanParams.PropagateErrors)
{
sink.SetError(error);
}
else
{
sink.SetCompleted();
}
}
});
return AutoSave(sink, scanParams, scanProfile);
}
private ScannedImageSource AutoSave(ScannedImageSink sink, ScanParams scanParams, ScanProfile scanProfile)
{
bool doAutoSave = !scanParams.NoAutoSave && scanProfile.EnableAutoSave && scanProfile.AutoSaveSettings != null && autoSaver != null;
if (!doAutoSave)
{
// No auto save, so just pipe images back as we get them
return sink.AsSource();
}
return autoSaver.Save(scanProfile.AutoSaveSettings, sink.AsSource());
}
private ScanDevice GetScanDevice(ScanProfile scanProfile)
{
if (scanProfile.Device != null)
{
// The profile has a device specified, so use it
return scanProfile.Device;
}
// The profile has no device specified, so prompt the user to choose one
var device = PromptForDevice(scanProfile);
if (device == null)
{
// User cancelled
return null;
}
// TODO: Figure out how to handle this
//if (AppConfig.Current.AlwaysRememberDevice)
//{
// scanProfile.Device = device;
// ProfileManager.Current.Save();
//}
return device;
}
protected abstract Task ScanInternal(ScannedImageSink sink, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent, CancellationToken cancelToken);
}
}

View File

@ -1,35 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.Images;
namespace NAPS2.Scan
{
/// <summary>
/// A high-level interface used for scanning.
/// This abstracts away the logic of obtaining and using an instance of IScanDriver.
/// </summary>
public class ScanPerformer : IScanPerformer
{
private readonly IScanDriverFactory driverFactory;
public ScanPerformer(IScanDriverFactory driverFactory)
{
this.driverFactory = driverFactory;
}
// TODO: Move additional logic (auto save, event logging, device prompting) to the driver base class
// TODO: Probably ISaveNotify should follow the static default/injected pattern so it doesn't have to be a parameter.
public Task<ScanDevice> PromptForDevice(ScanProfile scanProfile, IntPtr dialogParent = default) => throw new NotImplementedException();
public Task<ScannedImageSource> PerformScan(ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent = default, CancellationToken cancelToken = default)
{
return Task.Run(() =>
{
var driver = driverFactory.Create(scanProfile.DriverName);
return driver.Scan(scanProfile, scanParams, dialogParent, cancelToken);
});
}
}
}

View File

@ -1,89 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using NAPS2.Images;
using NAPS2.Images.Storage;
using NAPS2.Scan.Experimental;
namespace NAPS2.Scan.Stub
{
public class StubScanDriver : IScanDriver
{
private static int _number = 1;
private readonly ImageContext imageContext;
public StubScanDriver(ImageContext imageContext, string driverName)
{
this.imageContext = imageContext;
DriverName = driverName;
}
public ScanProfile ScanProfile { get; set; }
public ScanParams ScanParams { get; set; }
public ScanDevice ScanDevice { get; set; }
public IWin32Window DialogParent { get; set; }
public CancellationToken CancelToken { get; set; }
public ScanDevice PromptForDevice(ScanProfile scanProfile, IntPtr dialogParent) => new ScanDevice("test", "Test Scanner");
public List<ScanDevice> GetDeviceList(ScanProfile scanProfile) => new List<ScanDevice>
{
new ScanDevice("test", "Test Scanner")
};
public ScannedImageSource Scan(ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent, CancellationToken cancelToken)
{
var sink = new ScannedImageSink();
Task.Run(() =>
{
for (int i = 0; i < ImageCount; i++)
{
Thread.Sleep(500);
sink.PutImage(MakeImage());
}
});
return sink.AsSource();
}
private int ImageCount
{
get
{
switch (ScanProfile.PaperSource)
{
case ScanSource.Glass:
return 1;
case ScanSource.Feeder:
return 3;
case ScanSource.Duplex:
return 4;
}
return 0;
}
}
private ScannedImage MakeImage()
{
var bitmap = new Bitmap(600, 800);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.FillRectangle(Brushes.LightGray, 0, 0, bitmap.Width, bitmap.Height);
g.DrawString((_number++).ToString("G"), new Font("Times New Roman", 80), Brushes.Black, 0, 350);
}
var image = imageContext.CreateScannedImage(new GdiImage(bitmap), BitDepth.Color, ScanProfile.MaxQuality, ScanProfile.Quality);
return image;
}
public string DriverName { get; }
public bool IsSupported => true;
}
}

View File

@ -1,16 +0,0 @@
using NAPS2.Images.Storage;
namespace NAPS2.Scan.Stub
{
public class StubScanDriverFactory : IScanDriverFactory
{
private readonly ImageContext imageContext;
public StubScanDriverFactory(ImageContext imageContext)
{
this.imageContext = imageContext;
}
public IScanDriver Create(string driverName) => new StubScanDriver(imageContext, driverName);
}
}

View File

@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using NAPS2.Images;
namespace NAPS2.Scan.Twain
{
public interface ITwainWrapper
{
List<ScanDevice> GetDeviceList(TwainImpl twainImpl);
void Scan(IntPtr dialogParent, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams,
CancellationToken cancelToken, ScannedImageSink sink, Action<ScannedImage, ScanParams, string> runBackgroundOcr);
}
}

View File

@ -1,61 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.Platform;
using NAPS2.Images;
using NAPS2.Images.Storage;
using NAPS2.ImportExport;
using NAPS2.Remoting.Worker;
using NAPS2.Util;
namespace NAPS2.Scan.Twain
{
public class TwainScanDriver : ScanDriverBase
{
public const string DRIVER_NAME = "twain";
private readonly ImageContext imageContext;
private readonly ITwainWrapper twainWrapper;
private readonly ScannedImageHelper scannedImageHelper;
private readonly IWorkerFactory workerFactory;
//public TwainScanDriver() : base(ErrorOutput.Default, new AutoSaver())
//{
// imageContext = ImageContext.Default;
// twainWrapper = new TwainWrapper();
// scannedImageHelper = new ScannedImageHelper();
//}
public TwainScanDriver(ImageContext imageContext, ITwainWrapper twainWrapper, ScannedImageHelper scannedImageHelper, ErrorOutput errorOutput, AutoSaver autoSaver, IWorkerFactory workerFactory) : base(errorOutput, autoSaver)
{
this.imageContext = imageContext;
this.twainWrapper = twainWrapper;
this.scannedImageHelper = scannedImageHelper;
this.workerFactory = workerFactory;
}
public override string DriverName => DRIVER_NAME;
public override bool IsSupported => PlatformCompat.System.IsTwainDriverSupported;
private bool UseWorker(ScanProfile scanProfile) => scanProfile.TwainImpl != TwainImpl.X64 && Environment.Is64BitProcess && PlatformCompat.Runtime.UseWorker;
protected override List<ScanDevice> GetDeviceListInternal(ScanProfile scanProfile)
{
// Exclude WIA proxy devices since NAPS2 already supports WIA
return GetFullDeviceList(scanProfile).Where(x => !x.ID.StartsWith("WIA-", StringComparison.InvariantCulture)).ToList();
}
private IEnumerable<ScanDevice> GetFullDeviceList(ScanProfile scanProfile)
{
throw new NotImplementedException();
}
protected override Task ScanInternal(ScannedImageSink sink, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent, CancellationToken cancelToken)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,496 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using NAPS2.Logging;
using NAPS2.Platform;
using NAPS2.Scan.Exceptions;
using NAPS2.Images;
using NAPS2.Images.Storage;
using NAPS2.WinForms;
using NTwain;
using NTwain.Data;
using NAPS2.Util;
namespace NAPS2.Scan.Twain
{
public class TwainWrapper : ITwainWrapper
{
private static readonly TWIdentity TwainAppId = TWIdentity.CreateFromAssembly(DataGroups.Image | DataGroups.Control, Assembly.GetEntryAssembly());
private readonly ImageContext imageContext;
private readonly BlankDetector blankDetector;
private readonly ScannedImageHelper scannedImageHelper;
static TwainWrapper()
{
// Path to the folder containing the 64-bit twaindsm.dll relative to NAPS2.Core.dll
if (PlatformCompat.System.CanUseWin32)
{
string libDir = Environment.Is64BitProcess ? "_win64" : "_win32";
var location = Assembly.GetExecutingAssembly().Location;
var coreDllDir = System.IO.Path.GetDirectoryName(location);
if (coreDllDir != null)
{
Win32.SetDllDirectory(System.IO.Path.Combine(coreDllDir, libDir));
}
}
#if DEBUG
PlatformInfo.Current.Log.IsDebugEnabled = true;
#endif
}
public TwainWrapper()
{
imageContext = ImageContext.Default;
blankDetector = BlankDetector.Default;
scannedImageHelper = new ScannedImageHelper();
}
public TwainWrapper(ImageContext imageContext, BlankDetector blankDetector, ScannedImageHelper scannedImageHelper)
{
this.imageContext = imageContext;
this.blankDetector = blankDetector;
this.scannedImageHelper = scannedImageHelper;
}
public List<ScanDevice> GetDeviceList(TwainImpl twainImpl)
{
var deviceList = InternalGetDeviceList(twainImpl);
if (twainImpl == TwainImpl.Default && deviceList.Count == 0)
{
// Fall back to OldDsm in case of no devices
// This is primarily for Citrix support, which requires using twain_32.dll for TWAIN passthrough
deviceList = InternalGetDeviceList(TwainImpl.OldDsm);
}
return deviceList;
}
private static List<ScanDevice> InternalGetDeviceList(TwainImpl twainImpl)
{
PlatformInfo.Current.PreferNewDSM = twainImpl != TwainImpl.OldDsm;
var session = new TwainSession(TwainAppId);
session.Open();
try
{
return session.GetSources().Select(ds => new ScanDevice(ds.Name, ds.Name)).ToList();
}
finally
{
try
{
session.Close();
}
catch (Exception e)
{
Log.ErrorException("Error closing TWAIN session", e);
}
}
}
public void Scan(IntPtr dialogParent, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams,
CancellationToken cancelToken, ScannedImageSink sink, Action<ScannedImage, ScanParams, string> runBackgroundOcr)
{
try
{
InternalScan(scanProfile.TwainImpl, dialogParent, scanDevice, scanProfile, scanParams, cancelToken, sink, runBackgroundOcr);
}
catch (DeviceNotFoundException)
{
if (scanProfile.TwainImpl == TwainImpl.Default)
{
// Fall back to OldDsm in case of no devices
// This is primarily for Citrix support, which requires using twain_32.dll for TWAIN passthrough
InternalScan(TwainImpl.OldDsm, dialogParent, scanDevice, scanProfile, scanParams, cancelToken, sink, runBackgroundOcr);
}
else
{
throw;
}
}
}
private void InternalScan(TwainImpl twainImpl, IntPtr dialogParent, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams,
CancellationToken cancelToken, ScannedImageSink sink, Action<ScannedImage, ScanParams, string> runBackgroundOcr)
{
if (dialogParent == IntPtr.Zero)
{
dialogParent = new BackgroundForm().Handle;
}
if (twainImpl == TwainImpl.Legacy)
{
Legacy.TwainApi.Scan(imageContext, scanProfile, scanDevice, new Win32Window(dialogParent), sink);
return;
}
PlatformInfo.Current.PreferNewDSM = twainImpl != TwainImpl.OldDsm;
var session = new TwainSession(TwainAppId);
var twainForm = Invoker.Current.InvokeGet(() => scanParams.NoUI ? null : new FTwainGui());
Exception error = null;
bool cancel = false;
DataSource ds = null;
var waitHandle = new AutoResetEvent(false);
int pageNumber = 0;
session.TransferReady += (sender, eventArgs) =>
{
Debug.WriteLine("NAPS2.TW - TransferReady");
if (cancel)
{
eventArgs.CancelAll = true;
}
};
session.DataTransferred += (sender, eventArgs) =>
{
try
{
Debug.WriteLine("NAPS2.TW - DataTransferred");
pageNumber++;
using (var output = twainImpl == TwainImpl.MemXfer
? GetBitmapFromMemXFer(eventArgs.MemoryData, eventArgs.ImageInfo)
: imageContext.ImageFactory.Decode(eventArgs.GetNativeImageStream(), ".bmp"))
{
using (var result = scannedImageHelper.PostProcessStep1(output, scanProfile))
{
if (blankDetector.ExcludePage(result, scanProfile))
{
return;
}
var bitDepth = output.PixelFormat == StoragePixelFormat.BW1
? ScanBitDepth.BlackWhite
: ScanBitDepth.C24Bit;
var image = imageContext.CreateScannedImage(result, bitDepth.ToBitDepth(), scanProfile.MaxQuality, scanProfile.Quality);
if (scanParams.DetectPatchCodes)
{
foreach (var patchCodeInfo in eventArgs.GetExtImageInfo(ExtendedImageInfo.PatchCode))
{
if (patchCodeInfo.ReturnCode == ReturnCode.Success)
{
image.PatchCode = GetPatchCode(patchCodeInfo);
}
}
}
scannedImageHelper.PostProcessStep2(image, result, scanProfile, scanParams, pageNumber);
string tempPath = scannedImageHelper.SaveForBackgroundOcr(result, scanParams);
runBackgroundOcr(image, scanParams, tempPath);
sink.PutImage(image);
}
}
}
catch (Exception ex)
{
Debug.WriteLine("NAPS2.TW - DataTransferred - Error");
error = ex;
cancel = true;
StopTwain();
}
};
session.TransferError += (sender, eventArgs) =>
{
Debug.WriteLine("NAPS2.TW - TransferError");
if (eventArgs.Exception != null)
{
error = eventArgs.Exception;
}
else if (eventArgs.SourceStatus != null)
{
Log.Error("TWAIN Transfer Error. Return code = {0}; condition code = {1}; data = {2}.",
eventArgs.ReturnCode, eventArgs.SourceStatus.ConditionCode, eventArgs.SourceStatus.Data);
}
else
{
Log.Error("TWAIN Transfer Error. Return code = {0}.", eventArgs.ReturnCode);
}
cancel = true;
StopTwain();
};
session.SourceDisabled += (sender, eventArgs) =>
{
Debug.WriteLine("NAPS2.TW - SourceDisabled");
StopTwain();
};
void StopTwain()
{
waitHandle.Set();
if (!scanParams.NoUI)
{
Invoker.Current.Invoke(() => twainForm.Close());
}
}
void InitTwain()
{
try
{
var windowHandle = (Invoker.Current as Form)?.Handle;
ReturnCode rc = windowHandle != null ? session.Open(new WindowsFormsMessageLoopHook(windowHandle.Value)) : session.Open();
if (rc != ReturnCode.Success)
{
Debug.WriteLine("NAPS2.TW - Could not open session - {0}", rc);
StopTwain();
return;
}
ds = session.FirstOrDefault(x => x.Name == scanDevice.ID);
if (ds == null)
{
Debug.WriteLine("NAPS2.TW - Could not find DS - DS count = {0}", session.Count());
throw new DeviceNotFoundException();
}
rc = ds.Open();
if (rc != ReturnCode.Success)
{
Debug.WriteLine("NAPS2.TW - Could not open DS - {0}", rc);
StopTwain();
return;
}
ConfigureDS(ds, scanProfile, scanParams);
var ui = scanProfile.UseNativeUI ? SourceEnableMode.ShowUI : SourceEnableMode.NoUI;
Debug.WriteLine("NAPS2.TW - Enabling DS");
rc = scanParams.NoUI ? ds.Enable(ui, true, windowHandle ?? IntPtr.Zero) : ds.Enable(ui, true, twainForm.Handle);
Debug.WriteLine("NAPS2.TW - Enable finished");
if (rc != ReturnCode.Success)
{
Debug.WriteLine("NAPS2.TW - Enable failed - {0}, rc");
StopTwain();
}
else
{
cancelToken.Register(() =>
{
Debug.WriteLine("NAPS2.TW - User Cancel");
cancel = true;
session.ForceStepDown(5);
});
}
}
catch (Exception ex)
{
Debug.WriteLine("NAPS2.TW - Error");
error = ex;
StopTwain();
}
}
if (!scanParams.NoUI)
{
twainForm.Shown += (sender, eventArgs) => { InitTwain(); };
twainForm.Closed += (sender, args) => waitHandle.Set();
}
if (scanParams.NoUI)
{
Debug.WriteLine("NAPS2.TW - Init with no form");
Invoker.Current.Invoke(InitTwain);
}
else if (!scanParams.Modal)
{
Debug.WriteLine("NAPS2.TW - Init with non-modal form");
Invoker.Current.Invoke(() => twainForm.Show(new Win32Window(dialogParent)));
}
else
{
Debug.WriteLine("NAPS2.TW - Init with modal form");
Invoker.Current.Invoke(() => twainForm.ShowDialog(new Win32Window(dialogParent)));
}
waitHandle.WaitOne();
Debug.WriteLine("NAPS2.TW - Operation complete");
if (ds != null && session.IsSourceOpen)
{
Debug.WriteLine("NAPS2.TW - Closing DS");
ds.Close();
}
if (session.IsDsmOpen)
{
Debug.WriteLine("NAPS2.TW - Closing session");
session.Close();
}
if (error != null)
{
Debug.WriteLine("NAPS2.TW - Throwing error - {0}", error);
if (error is ScanDriverException)
{
throw error;
}
throw new ScanDriverUnknownException(error);
}
}
private IImage GetBitmapFromMemXFer(byte[] memoryData, TWImageInfo imageInfo)
{
int bytesPerPixel = memoryData.Length / (imageInfo.ImageWidth * imageInfo.ImageLength);
var pixelFormat = bytesPerPixel == 0 ? StoragePixelFormat.BW1 : StoragePixelFormat.RGB24;
int imageWidth = imageInfo.ImageWidth;
int imageHeight = imageInfo.ImageLength;
var bitmap = imageContext.ImageFactory.FromDimensions(imageWidth, imageHeight, pixelFormat);
var data = bitmap.Lock(LockMode.WriteOnly, out var scan0, out var stride);
try
{
byte[] source = memoryData;
if (bytesPerPixel == 1)
{
// No 8-bit greyscale format, so we have to transform into 24-bit
int rowWidth = stride;
int originalRowWidth = source.Length / imageHeight;
byte[] source2 = new byte[rowWidth * imageHeight];
for (int row = 0; row < imageHeight; row++)
{
for (int col = 0; col < imageWidth; col++)
{
source2[row * rowWidth + col * 3] = source[row * originalRowWidth + col];
source2[row * rowWidth + col * 3 + 1] = source[row * originalRowWidth + col];
source2[row * rowWidth + col * 3 + 2] = source[row * originalRowWidth + col];
}
}
source = source2;
}
else if (bytesPerPixel == 3)
{
// Colors are provided as BGR, they need to be swapped to RGB
int rowWidth = stride;
for (int row = 0; row < imageHeight; row++)
{
for (int col = 0; col < imageWidth; col++)
{
(source[row * rowWidth + col * 3], source[row * rowWidth + col * 3 + 2]) =
(source[row * rowWidth + col * 3 + 2], source[row * rowWidth + col * 3]);
}
}
}
Marshal.Copy(source, 0, scan0, source.Length);
}
finally
{
bitmap.Unlock(data);
}
return bitmap;
}
private static PatchCode GetPatchCode(TWInfo patchCodeInfo)
{
switch ((NTwain.Data.PatchCode)patchCodeInfo.Item)
{
case NTwain.Data.PatchCode.Patch1:
return PatchCode.Patch1;
case NTwain.Data.PatchCode.Patch2:
return PatchCode.Patch2;
case NTwain.Data.PatchCode.Patch3:
return PatchCode.Patch3;
case NTwain.Data.PatchCode.Patch4:
return PatchCode.Patch4;
case NTwain.Data.PatchCode.Patch6:
return PatchCode.Patch6;
case NTwain.Data.PatchCode.PatchT:
return PatchCode.PatchT;
default:
throw new ArgumentException();
}
}
private void ConfigureDS(DataSource ds, ScanProfile scanProfile, ScanParams scanParams)
{
if (scanProfile.UseNativeUI)
{
return;
}
// Transfer Mode
if (scanProfile.TwainImpl == TwainImpl.MemXfer)
{
ds.Capabilities.ICapXferMech.SetValue(XferMech.Memory);
}
// Hide UI for console
if (scanParams.NoUI)
{
ds.Capabilities.CapIndicators.SetValue(BoolType.False);
}
// Paper Source
switch (scanProfile.PaperSource)
{
case ScanSource.Glass:
ds.Capabilities.CapFeederEnabled.SetValue(BoolType.False);
ds.Capabilities.CapDuplexEnabled.SetValue(BoolType.False);
break;
case ScanSource.Feeder:
ds.Capabilities.CapFeederEnabled.SetValue(BoolType.True);
ds.Capabilities.CapDuplexEnabled.SetValue(BoolType.False);
break;
case ScanSource.Duplex:
ds.Capabilities.CapFeederEnabled.SetValue(BoolType.True);
ds.Capabilities.CapDuplexEnabled.SetValue(BoolType.True);
break;
}
// Bit Depth
switch (scanProfile.BitDepth)
{
case ScanBitDepth.C24Bit:
ds.Capabilities.ICapPixelType.SetValue(PixelType.RGB);
break;
case ScanBitDepth.Grayscale:
ds.Capabilities.ICapPixelType.SetValue(PixelType.Gray);
break;
case ScanBitDepth.BlackWhite:
ds.Capabilities.ICapPixelType.SetValue(PixelType.BlackWhite);
break;
}
// Page Size, Horizontal Align
PageDimensions pageDimensions = scanProfile.PageSize.PageDimensions() ?? scanProfile.CustomPageSize;
if (pageDimensions == null)
{
throw new InvalidOperationException("No page size specified");
}
float pageWidth = pageDimensions.WidthInThousandthsOfAnInch() / 1000.0f;
float pageHeight = pageDimensions.HeightInThousandthsOfAnInch() / 1000.0f;
var pageMaxWidthFixed = ds.Capabilities.ICapPhysicalWidth.GetCurrent();
float pageMaxWidth = pageMaxWidthFixed.Whole + (pageMaxWidthFixed.Fraction / (float)UInt16.MaxValue);
float horizontalOffset = 0.0f;
if (scanProfile.PageAlign == ScanHorizontalAlign.Center)
horizontalOffset = (pageMaxWidth - pageWidth) / 2;
else if (scanProfile.PageAlign == ScanHorizontalAlign.Left)
horizontalOffset = (pageMaxWidth - pageWidth);
ds.Capabilities.ICapUnits.SetValue(Unit.Inches);
ds.DGImage.ImageLayout.Get(out TWImageLayout imageLayout);
imageLayout.Frame = new TWFrame
{
Left = horizontalOffset,
Right = horizontalOffset + pageWidth,
Top = 0,
Bottom = pageHeight
};
ds.DGImage.ImageLayout.Set(imageLayout);
// Brightness, Contrast
// Conveniently, the range of values used in settings (-1000 to +1000) is the same range TWAIN supports
if (!scanProfile.BrightnessContrastAfterScan)
{
ds.Capabilities.ICapBrightness.SetValue(scanProfile.Brightness);
ds.Capabilities.ICapContrast.SetValue(scanProfile.Contrast);
}
// Resolution
int dpi = scanProfile.Resolution.ToIntDpi();
ds.Capabilities.ICapXResolution.SetValue(dpi);
ds.Capabilities.ICapYResolution.SetValue(dpi);
// Patch codes
if (scanParams.DetectPatchCodes)
{
ds.Capabilities.ICapPatchCodeDetectionEnabled.SetValue(BoolType.True);
}
}
}
}

View File

@ -1,110 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NAPS2.Operation;
using NAPS2.Platform;
using NAPS2.Scan.Exceptions;
using NAPS2.Images;
using NAPS2.Images.Storage;
using NAPS2.ImportExport;
using NAPS2.Remoting.Worker;
using NAPS2.Scan.Wia.Native;
using NAPS2.Util;
namespace NAPS2.Scan.Wia
{
public class WiaScanDriver : ScanDriverBase
{
public const string DRIVER_NAME = "wia";
private readonly ImageContext imageContext;
private readonly OperationProgress operationProgress;
private readonly ScannedImageHelper scannedImageHelper;
private readonly IWorkerFactory workerFactory;
//public WiaScanDriver() : base(ErrorOutput.Default, new AutoSaver())
//{
// imageContext = ImageContext.Default;
// operationProgress = OperationProgress.Default;
// scannedImageHelper = new ScannedImageHelper();
//}
public WiaScanDriver(ImageContext imageContext, OperationProgress operationProgress, ScannedImageHelper scannedImageHelper, ErrorOutput errorOutput, AutoSaver autoSaver, IWorkerFactory workerFactory) : base(errorOutput, autoSaver)
{
this.imageContext = imageContext;
this.operationProgress = operationProgress;
this.scannedImageHelper = scannedImageHelper;
this.workerFactory = workerFactory;
}
public override string DriverName => DRIVER_NAME;
public override bool IsSupported => PlatformCompat.System.IsWiaDriverSupported;
protected override ScanDevice PromptForDeviceInternal(ScanProfile scanProfile, IntPtr dialogParent)
{
try
{
using (var deviceManager = new WiaDeviceManager(scanProfile.WiaVersion))
{
using (var device = deviceManager.PromptForDevice(dialogParent))
{
if (device == null)
{
return null;
}
return new ScanDevice(device.Id(), device.Name());
}
}
}
catch (WiaException e) when (e.ErrorCode == WiaErrorCodes.NO_DEVICE_AVAILABLE)
{
throw new NoDevicesFoundException();
}
}
protected override List<ScanDevice> GetDeviceListInternal(ScanProfile scanProfile)
{
using (var deviceManager = new WiaDeviceManager(scanProfile?.WiaVersion ?? WiaVersion.Default))
{
return deviceManager.GetDeviceInfos().Select(deviceInfo =>
{
using (deviceInfo)
{
return new ScanDevice(deviceInfo.Id(), deviceInfo.Name());
}
}).ToList();
}
}
protected override async Task ScanInternal(ScannedImageSink sink, ScanDevice scanDevice, ScanProfile scanProfile, ScanParams scanParams, IntPtr dialogParent, CancellationToken cancelToken)
{
var op = new WiaScanOperation(imageContext, scannedImageHelper, workerFactory);
using (cancelToken.Register(op.Cancel))
{
op.Start(scanProfile, scanDevice, scanParams, dialogParent, sink);
Invoker.Current.SafeInvoke(() =>
{
if (scanParams.Modal)
{
operationProgress.ShowModalProgress(op);
}
else
{
operationProgress.ShowBackgroundProgress(op);
}
});
await op.Success;
}
if (op.ScanException != null)
{
op.ScanException.PreserveStackTrace();
throw op.ScanException;
}
}
}
}

View File

@ -1,433 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NAPS2.Lang.Resources;
using NAPS2.Logging;
using NAPS2.Operation;
using NAPS2.Scan.Exceptions;
using NAPS2.Images;
using NAPS2.Images.Storage;
using NAPS2.Remoting.Worker;
using NAPS2.Scan.Wia.Native;
using NAPS2.Util;
namespace NAPS2.Scan.Wia
{
public class WiaScanOperation : OperationBase
{
private readonly ImageContext imageContext;
private readonly ScannedImageHelper scannedImageHelper;
private readonly IWorkerFactory workerFactory;
private readonly SmoothProgress smoothProgress = new SmoothProgress();
//public WiaScanOperation() : this(ImageContext.Default, new ScannedImageHelper())
//{
//}
public WiaScanOperation(ImageContext imageContext, ScannedImageHelper scannedImageHelper, IWorkerFactory workerFactory)
{
this.imageContext = imageContext;
this.scannedImageHelper = scannedImageHelper;
this.workerFactory = workerFactory;
AllowCancel = true;
AllowBackground = true;
smoothProgress.OutputProgressChanged += SmoothProgressChanged;
}
private void SmoothProgressChanged(object sender, SmoothProgress.ProgressChangeEventArgs args)
{
Status.CurrentProgress = (int)(args.Value * Status.MaxProgress);
InvokeStatusChanged();
}
public Exception ScanException { get; private set; }
private ScanProfile ScanProfile { get; set; }
private ScanDevice ScanDevice { get; set; }
private ScanParams ScanParams { get; set; }
private IntPtr DialogParent { get; set; }
public bool Start(ScanProfile scanProfile, ScanDevice scanDevice, ScanParams scanParams, IntPtr dialogParent, ScannedImageSink sink)
{
ScanProfile = scanProfile;
ScanDevice = scanDevice;
ScanParams = scanParams;
DialogParent = dialogParent;
ProgressTitle = ScanDevice.Name;
Status = new OperationStatus
{
StatusText = ScanProfile.PaperSource == ScanSource.Glass
? MiscResources.AcquiringData
: string.Format(MiscResources.ScanProgressPage, 1),
MaxProgress = 1000,
ProgressType = OperationProgressType.BarOnly
};
// TODO: NoUI
// TODO: Test native UI in console behaviour (versus older behaviour)
// TODO: What happens if you close FDesktop while a batch scan is in progress?
RunAsync(() =>
{
try
{
try
{
smoothProgress.Reset();
try
{
Scan(sink, ScanProfile.WiaVersion);
}
catch (WiaException e) when
(e.ErrorCode == Hresult.E_INVALIDARG &&
ScanProfile.WiaVersion == WiaVersion.Default &&
NativeWiaObject.DefaultWiaVersion == WiaVersion.Wia20
&& !ScanProfile.UseNativeUI)
{
Debug.WriteLine("Falling back to WIA 1.0 due to E_INVALIDARG");
Scan(sink, WiaVersion.Wia10);
}
}
catch (WiaException e)
{
WiaScanErrors.ThrowDeviceError(e);
}
return true;
}
catch (Exception e)
{
// Don't call InvokeError; the driver will do the actual error handling
ScanException = e;
return false;
}
finally
{
smoothProgress.Reset();
}
});
return true;
}
private void Scan(ScannedImageSink sink, WiaVersion wiaVersion)
{
using (var deviceManager = new WiaDeviceManager(wiaVersion))
using (var device = deviceManager.FindDevice(ScanDevice.ID))
{
if (device.Version == WiaVersion.Wia20 && ScanProfile.UseNativeUI)
{
DoWia20NativeTransfer(sink, deviceManager, device);
return;
}
using (var item = GetItem(device))
{
if (item == null)
{
return;
}
DoTransfer(sink, device, item);
}
}
}
private void InitProgress(WiaDevice device)
{
ProgressTitle = device.Name();
InvokeStatusChanged();
}
private void InitNextPageProgress(int pageNumber)
{
if (ScanProfile.PaperSource != ScanSource.Glass)
{
Status.StatusText = string.Format(MiscResources.ScanProgressPage, pageNumber);
smoothProgress.Reset();
}
}
private void ProduceImage(ScannedImageSink sink, IImage output, ref int pageNumber)
{
var image = scannedImageHelper.PostProcess(output, pageNumber, ScanProfile, ScanParams);
if (image != null)
{
sink.PutImage(image);
}
pageNumber++;
InitNextPageProgress(pageNumber);
}
private void DoWia20NativeTransfer(ScannedImageSink sink, WiaDeviceManager deviceManager, WiaDevice device)
{
// WIA 2.0 doesn't support normal transfers with native UI.
// Instead we need to have it write the scans to a set of files and load those.
var paths = deviceManager.PromptForImage(DialogParent, device);
if (paths == null)
{
return;
}
int pageNumber = 1;
InitProgress(device);
try
{
foreach (var path in paths)
{
using (var stream = new FileStream(path, FileMode.Open))
{
foreach (var storage in imageContext.ImageFactory.DecodeMultiple(stream, Path.GetExtension(path), out _))
{
using (storage)
{
ProduceImage(sink, storage, ref pageNumber);
}
}
}
}
}
finally
{
foreach (var path in paths)
{
try
{
File.Delete(path);
}
catch (Exception e)
{
Log.ErrorException("Error deleting WIA 2.0 native transferred file", e);
}
}
}
}
private void DoTransfer(ScannedImageSink sink, WiaDevice device, WiaItem item)
{
if (ScanProfile.PaperSource != ScanSource.Glass && !device.SupportsFeeder())
{
throw new NoFeederSupportException();
}
if (ScanProfile.PaperSource == ScanSource.Duplex && !device.SupportsDuplex())
{
throw new NoDuplexSupportException();
}
InitProgress(device);
ConfigureProps(device, item);
using (var transfer = item.StartTransfer())
{
int pageNumber = 1;
transfer.PageScanned += (sender, args) =>
{
try
{
using (args.Stream)
using (var storage = imageContext.ImageFactory.Decode(args.Stream, ".bmp"))
{
ProduceImage(sink, storage, ref pageNumber);
}
}
catch (Exception e)
{
ScanException = e;
}
};
transfer.Progress += (sender, args) => smoothProgress.InputProgressChanged(args.Percent / 100.0);
using (CancelToken.Register(transfer.Cancel))
{
transfer.Download();
if (device.Version == WiaVersion.Wia10 && ScanProfile.PaperSource != ScanSource.Glass)
{
// For WIA 1.0 feeder scans, we need to repeatedly call Download until WIA_ERROR_PAPER_EMPTY is received.
try
{
while (!CancelToken.IsCancellationRequested)
{
transfer.Download();
}
}
catch (WiaException e) when (e.ErrorCode == WiaErrorCodes.PAPER_EMPTY)
{
}
}
}
}
}
private WiaItem GetItem(WiaDevice device)
{
if (ScanProfile.UseNativeUI)
{
bool useWorker = Environment.Is64BitProcess && device.Version == WiaVersion.Wia10;
if (useWorker)
{
WiaConfiguration config;
using (var worker = workerFactory.Create())
{
config = worker.Service.Wia10NativeUI(device.Id(), DialogParent);
}
var item = device.FindSubItem(config.ItemName);
device.Properties.DeserializeEditable(device.Properties.Delta(config.DeviceProps));
item.Properties.DeserializeEditable(item.Properties.Delta(config.ItemProps));
return item;
}
else
{
return device.PromptToConfigure(DialogParent);
}
}
else if (device.Version == WiaVersion.Wia10)
{
// In WIA 1.0, the root device only has a single child, "Scan"
// https://docs.microsoft.com/en-us/windows-hardware/drivers/image/wia-scanner-tree
return device.GetSubItems().First();
}
else
{
// In WIA 2.0, the root device may have multiple children, i.e. "Flatbed" and "Feeder"
// https://docs.microsoft.com/en-us/windows-hardware/drivers/image/non-duplex-capable-document-feeder
// The "Feeder" child may also have a pair of children (for front/back sides with duplex)
// https://docs.microsoft.com/en-us/windows-hardware/drivers/image/simple-duplex-capable-document-feeder
var items = device.GetSubItems();
var preferredItemName = ScanProfile.PaperSource == ScanSource.Glass ? "Flatbed" : "Feeder";
return items.FirstOrDefault(x => x.Name() == preferredItemName) ?? items.First();
}
}
private void ConfigureProps(WiaDevice device, WiaItem item)
{
if (ScanProfile.UseNativeUI)
{
return;
}
if (ScanProfile.PaperSource != ScanSource.Glass)
{
if (device.Version == WiaVersion.Wia10)
{
device.SetProperty(WiaPropertyId.DPS_PAGES, 1);
}
else
{
item.SetProperty(WiaPropertyId.IPS_PAGES, 0);
}
}
if (device.Version == WiaVersion.Wia10)
{
switch (ScanProfile.PaperSource)
{
case ScanSource.Glass:
device.SetProperty(WiaPropertyId.DPS_DOCUMENT_HANDLING_SELECT, WiaPropertyValue.FLATBED);
break;
case ScanSource.Feeder:
device.SetProperty(WiaPropertyId.DPS_DOCUMENT_HANDLING_SELECT, WiaPropertyValue.FEEDER);
break;
case ScanSource.Duplex:
device.SetProperty(WiaPropertyId.DPS_DOCUMENT_HANDLING_SELECT, WiaPropertyValue.FEEDER | WiaPropertyValue.DUPLEX);
break;
}
}
else
{
switch (ScanProfile.PaperSource)
{
case ScanSource.Feeder:
item.SetProperty(WiaPropertyId.IPS_DOCUMENT_HANDLING_SELECT, WiaPropertyValue.FRONT_ONLY);
break;
case ScanSource.Duplex:
item.SetProperty(WiaPropertyId.IPS_DOCUMENT_HANDLING_SELECT, WiaPropertyValue.DUPLEX | WiaPropertyValue.FRONT_FIRST);
break;
}
}
switch (ScanProfile.BitDepth)
{
case ScanBitDepth.Grayscale:
item.SetProperty(WiaPropertyId.IPA_DATATYPE, 2);
break;
case ScanBitDepth.C24Bit:
item.SetProperty(WiaPropertyId.IPA_DATATYPE, 3);
break;
case ScanBitDepth.BlackWhite:
item.SetProperty(WiaPropertyId.IPA_DATATYPE, 0);
break;
}
int xRes = ScanProfile.Resolution.ToIntDpi();
int yRes = xRes;
item.SetPropertyClosest(WiaPropertyId.IPS_XRES, ref xRes);
item.SetPropertyClosest(WiaPropertyId.IPS_YRES, ref yRes);
PageDimensions pageDimensions = ScanProfile.PageSize.PageDimensions() ?? ScanProfile.CustomPageSize;
if (pageDimensions == null)
{
throw new InvalidOperationException("No page size specified");
}
int pageWidth = pageDimensions.WidthInThousandthsOfAnInch() * xRes / 1000;
int pageHeight = pageDimensions.HeightInThousandthsOfAnInch() * yRes / 1000;
int horizontalSize, verticalSize;
if (device.Version == WiaVersion.Wia10)
{
horizontalSize =
(int)device.Properties[ScanProfile.PaperSource == ScanSource.Glass
? WiaPropertyId.DPS_HORIZONTAL_BED_SIZE
: WiaPropertyId.DPS_HORIZONTAL_SHEET_FEED_SIZE].Value;
verticalSize =
(int)device.Properties[ScanProfile.PaperSource == ScanSource.Glass
? WiaPropertyId.DPS_VERTICAL_BED_SIZE
: WiaPropertyId.DPS_VERTICAL_SHEET_FEED_SIZE].Value;
}
else
{
horizontalSize = (int)item.Properties[WiaPropertyId.IPS_MAX_HORIZONTAL_SIZE].Value;
verticalSize = (int)item.Properties[WiaPropertyId.IPS_MAX_VERTICAL_SIZE].Value;
}
int pagemaxwidth = horizontalSize * xRes / 1000;
int pagemaxheight = verticalSize * yRes / 1000;
int horizontalPos = 0;
if (ScanProfile.PageAlign == ScanHorizontalAlign.Center)
horizontalPos = (pagemaxwidth - pageWidth) / 2;
else if (ScanProfile.PageAlign == ScanHorizontalAlign.Left)
horizontalPos = (pagemaxwidth - pageWidth);
pageWidth = pageWidth < pagemaxwidth ? pageWidth : pagemaxwidth;
pageHeight = pageHeight < pagemaxheight ? pageHeight : pagemaxheight;
if (ScanProfile.WiaOffsetWidth)
{
item.SetProperty(WiaPropertyId.IPS_XEXTENT, pageWidth + horizontalPos);
item.SetProperty(WiaPropertyId.IPS_XPOS, horizontalPos);
}
else
{
item.SetProperty(WiaPropertyId.IPS_XEXTENT, pageWidth);
item.SetProperty(WiaPropertyId.IPS_XPOS, horizontalPos);
}
item.SetProperty(WiaPropertyId.IPS_YEXTENT, pageHeight);
if (!ScanProfile.BrightnessContrastAfterScan)
{
item.SetPropertyRange(WiaPropertyId.IPS_CONTRAST, ScanProfile.Contrast, -1000, 1000);
item.SetPropertyRange(WiaPropertyId.IPS_BRIGHTNESS, ScanProfile.Brightness, -1000, 1000);
}
}
}
}

View File

@ -55,9 +55,9 @@ namespace NAPS2.WinForms
private void UpdateEnabled()
{
cmbTwainImpl.Enabled = ScanProfile.DriverName == TwainScanDriver.DRIVER_NAME;
cbWiaOffsetWidth.Enabled = ScanProfile.DriverName == WiaScanDriver.DRIVER_NAME;
cmbWiaVersion.Enabled = ScanProfile.DriverName == WiaScanDriver.DRIVER_NAME;
cmbTwainImpl.Enabled = ScanProfile.DriverName == DriverNames.TWAIN;
cbWiaOffsetWidth.Enabled = ScanProfile.DriverName == DriverNames.WIA;
cmbWiaVersion.Enabled = ScanProfile.DriverName == DriverNames.WIA;
tbImageQuality.Enabled = !cbHighQuality.Checked;
txtImageQuality.Enabled = !cbHighQuality.Checked;
tbWhiteThreshold.Enabled = cbExcludeBlankPages.Checked && ScanProfile.BitDepth != ScanBitDepth.BlackWhite;

View File

@ -71,7 +71,7 @@ namespace NAPS2.WinForms
CurrentDevice = ScanProfile.Device;
}
isDefault = ScanProfile.IsDefault;
useProxy = ScanProfile.DriverName == ProxiedScanDriver.DRIVER_NAME;
useProxy = ScanProfile.DriverName == DriverNames.PROXY;
iconID = ScanProfile.IconID;
cmbSource.SelectedIndex = (int)ScanProfile.PaperSource;
@ -188,20 +188,20 @@ namespace NAPS2.WinForms
private string DeviceDriverName
{
get => rdTWAIN.Checked ? TwainScanDriver.DRIVER_NAME
: rdSANE.Checked ? SaneScanDriver.DRIVER_NAME
: WiaScanDriver.DRIVER_NAME;
get => rdTWAIN.Checked ? DriverNames.TWAIN
: rdSANE.Checked ? DriverNames.SANE
: DriverNames.WIA;
set
{
if (value == TwainScanDriver.DRIVER_NAME)
if (value == DriverNames.TWAIN)
{
rdTWAIN.Checked = true;
}
else if (value == SaneScanDriver.DRIVER_NAME)
else if (value == DriverNames.SANE)
{
rdSANE.Checked = true;
}
else if (value == WiaScanDriver.DRIVER_NAME || PlatformCompat.System.IsWiaDriverSupported)
else if (value == DriverNames.WIA || PlatformCompat.System.IsWiaDriverSupported)
{
rdWIA.Checked = true;
}
@ -253,7 +253,7 @@ namespace NAPS2.WinForms
private async void btnChooseDevice_Click(object sender, EventArgs e)
{
ScanProfile.DriverName = useProxy ? ProxiedScanDriver.DRIVER_NAME : DeviceDriverName;
ScanProfile.DriverName = useProxy ? DriverNames.PROXY : DeviceDriverName;
ScanProfile.ProxyDriverName = useProxy ? DeviceDriverName : null;
await ChooseDevice();
}
@ -279,7 +279,7 @@ namespace NAPS2.WinForms
Device = CurrentDevice,
IsDefault = isDefault,
DriverName = useProxy ? ProxiedScanDriver.DRIVER_NAME : DeviceDriverName,
DriverName = useProxy ? DriverNames.PROXY : DeviceDriverName,
ProxyConfig = ScanProfile.ProxyConfig,
ProxyDriverName = useProxy ? DeviceDriverName : null,
DisplayName = txtName.Text,
@ -354,7 +354,7 @@ namespace NAPS2.WinForms
{
suppressChangeEvent = true;
bool canUseNativeUi = DeviceDriverName != SaneScanDriver.DRIVER_NAME && !useProxy;
bool canUseNativeUi = DeviceDriverName != DriverNames.SANE && !useProxy;
bool locked = ScanProfile.IsLocked;
bool deviceLocked = ScanProfile.IsDeviceLocked;
bool settingsEnabled = !locked && (rdbConfig.Checked || !canUseNativeUi);