mirror of
https://github.com/cyanfish/naps2.git
synced 2024-09-21 20:59:01 +03:00
Remove old scanning types
This commit is contained in:
parent
60b81d7f90
commit
0d1e273983
@ -44,16 +44,6 @@ namespace NAPS2.Modules
|
|||||||
// Scan
|
// Scan
|
||||||
Bind<IScanPerformer>().To<NAPS2.Scan.Experimental.ScanPerformer>();
|
Bind<IScanPerformer>().To<NAPS2.Scan.Experimental.ScanPerformer>();
|
||||||
Bind<IBatchScanPerformer>().To<BatchScanPerformer>();
|
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
|
// Config
|
||||||
var configScopes = new ConfigScopes(Path.Combine(Paths.Executable, "appsettings.xml"), Path.Combine(Paths.AppData, "config.xml"));
|
var configScopes = new ConfigScopes(Path.Combine(Paths.Executable, "appsettings.xml"), Path.Combine(Paths.AppData, "config.xml"));
|
||||||
|
@ -81,7 +81,6 @@
|
|||||||
<Compile Include="NinjectEmailProviderFactory.cs" />
|
<Compile Include="NinjectEmailProviderFactory.cs" />
|
||||||
<Compile Include="NinjectFormFactory.cs" />
|
<Compile Include="NinjectFormFactory.cs" />
|
||||||
<Compile Include="NinjectOperationFactory.cs" />
|
<Compile Include="NinjectOperationFactory.cs" />
|
||||||
<Compile Include="NinjectScanDriverFactory.cs" />
|
|
||||||
<Compile Include="NLogLogger.cs" />
|
<Compile Include="NLogLogger.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="StaticConfiguration.cs" />
|
<Compile Include="StaticConfiguration.cs" />
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -130,7 +130,6 @@
|
|||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
<DependentUpon>PdfiumTestsData.resx</DependentUpon>
|
<DependentUpon>PdfiumTestsData.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Mocks\MockScanDriver.cs" />
|
|
||||||
<Compile Include="Mocks\StubScanBridge.cs" />
|
<Compile Include="Mocks\StubScanBridge.cs" />
|
||||||
<Compile Include="Scan\AutoSaveTests.cs" />
|
<Compile Include="Scan\AutoSaveTests.cs" />
|
||||||
<Compile Include="Config\ProfileSerializerTestsData.Designer.cs">
|
<Compile Include="Config\ProfileSerializerTestsData.Designer.cs">
|
||||||
|
@ -21,159 +21,160 @@ namespace NAPS2.Sdk.Tests.Scan
|
|||||||
{
|
{
|
||||||
public class AutoSaveTests : ContextualTexts
|
public class AutoSaveTests : ContextualTexts
|
||||||
{
|
{
|
||||||
[Fact]
|
// TODO: Need to make ScanPerformer testable
|
||||||
public async Task NoImages()
|
// [Fact]
|
||||||
{
|
// public async Task NoImages()
|
||||||
var errorOutput = new Mock<ErrorOutput>();
|
// {
|
||||||
var driver = Driver(errorOutput.Object, 0);
|
// var errorOutput = new Mock<ErrorOutput>();
|
||||||
|
// var driver = Driver(errorOutput.Object, 0);
|
||||||
var scanProfile = Profile(new AutoSaveSettings
|
//
|
||||||
{
|
// var scanProfile = Profile(new AutoSaveSettings
|
||||||
FilePath = Path.Combine(FolderPath, "test$(n).pdf")
|
// {
|
||||||
});
|
// FilePath = Path.Combine(FolderPath, "test$(n).pdf")
|
||||||
var scanParams = new ScanParams();
|
// });
|
||||||
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
// var scanParams = new ScanParams();
|
||||||
var files = Folder.GetFiles();
|
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
||||||
|
// var files = Folder.GetFiles();
|
||||||
Assert.Empty(scannedImages);
|
//
|
||||||
Assert.Empty(files);
|
// Assert.Empty(scannedImages);
|
||||||
errorOutput.VerifyNoOtherCalls();
|
// Assert.Empty(files);
|
||||||
}
|
// errorOutput.VerifyNoOtherCalls();
|
||||||
|
// }
|
||||||
[Fact]
|
//
|
||||||
public async Task OneImageOnePdf()
|
// [Fact]
|
||||||
{
|
// public async Task OneImageOnePdf()
|
||||||
var errorOutput = new Mock<ErrorOutput>();
|
// {
|
||||||
var driver = Driver(errorOutput.Object, 1);
|
// var errorOutput = new Mock<ErrorOutput>();
|
||||||
|
// var driver = Driver(errorOutput.Object, 1);
|
||||||
var scanProfile = Profile(new AutoSaveSettings
|
//
|
||||||
{
|
// var scanProfile = Profile(new AutoSaveSettings
|
||||||
FilePath = Path.Combine(FolderPath, "test$(n).pdf")
|
// {
|
||||||
});
|
// FilePath = Path.Combine(FolderPath, "test$(n).pdf")
|
||||||
var scanParams = new ScanParams();
|
// });
|
||||||
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
// var scanParams = new ScanParams();
|
||||||
var files = Folder.GetFiles();
|
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
||||||
|
// var files = Folder.GetFiles();
|
||||||
Assert.Single(scannedImages);
|
//
|
||||||
Assert.Single(files);
|
// Assert.Single(scannedImages);
|
||||||
PdfAsserts.AssertPageCount(1, files[0].FullName);
|
// Assert.Single(files);
|
||||||
errorOutput.VerifyNoOtherCalls();
|
// PdfAsserts.AssertPageCount(1, files[0].FullName);
|
||||||
}
|
// errorOutput.VerifyNoOtherCalls();
|
||||||
|
// }
|
||||||
[Fact]
|
//
|
||||||
public async Task TwoImagesOnePdf()
|
// [Fact]
|
||||||
{
|
// public async Task TwoImagesOnePdf()
|
||||||
var errorOutput = new Mock<ErrorOutput>();
|
// {
|
||||||
var driver = Driver(errorOutput.Object, 2);
|
// var errorOutput = new Mock<ErrorOutput>();
|
||||||
|
// var driver = Driver(errorOutput.Object, 2);
|
||||||
var scanProfile = Profile(new AutoSaveSettings
|
//
|
||||||
{
|
// var scanProfile = Profile(new AutoSaveSettings
|
||||||
FilePath = Path.Combine(FolderPath, "test$(n).pdf"),
|
// {
|
||||||
Separator = SaveSeparator.FilePerScan
|
// FilePath = Path.Combine(FolderPath, "test$(n).pdf"),
|
||||||
});
|
// Separator = SaveSeparator.FilePerScan
|
||||||
var scanParams = new ScanParams();
|
// });
|
||||||
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
// var scanParams = new ScanParams();
|
||||||
var files = Folder.GetFiles();
|
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
||||||
|
// var files = Folder.GetFiles();
|
||||||
Assert.Equal(2, scannedImages.Count);
|
//
|
||||||
Assert.Single(files);
|
// Assert.Equal(2, scannedImages.Count);
|
||||||
PdfAsserts.AssertPageCount(2, files[0].FullName);
|
// Assert.Single(files);
|
||||||
errorOutput.VerifyNoOtherCalls();
|
// PdfAsserts.AssertPageCount(2, files[0].FullName);
|
||||||
}
|
// errorOutput.VerifyNoOtherCalls();
|
||||||
|
// }
|
||||||
[Fact]
|
//
|
||||||
public async Task TwoImagesTwoPdfs()
|
// [Fact]
|
||||||
{
|
// public async Task TwoImagesTwoPdfs()
|
||||||
var errorOutput = new Mock<ErrorOutput>();
|
// {
|
||||||
var driver = Driver(errorOutput.Object, 2);
|
// var errorOutput = new Mock<ErrorOutput>();
|
||||||
|
// var driver = Driver(errorOutput.Object, 2);
|
||||||
var scanProfile = Profile(new AutoSaveSettings
|
//
|
||||||
{
|
// var scanProfile = Profile(new AutoSaveSettings
|
||||||
FilePath = Path.Combine(FolderPath, "test$(n).pdf"),
|
// {
|
||||||
Separator = SaveSeparator.FilePerPage
|
// FilePath = Path.Combine(FolderPath, "test$(n).pdf"),
|
||||||
});
|
// Separator = SaveSeparator.FilePerPage
|
||||||
var scanParams = new ScanParams();
|
// });
|
||||||
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
// var scanParams = new ScanParams();
|
||||||
var files = Folder.GetFiles();
|
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
||||||
|
// var files = Folder.GetFiles();
|
||||||
Assert.Equal(2, scannedImages.Count);
|
//
|
||||||
Assert.Equal(2, files.Length);
|
// Assert.Equal(2, scannedImages.Count);
|
||||||
PdfAsserts.AssertPageCount(1, files[0].FullName);
|
// Assert.Equal(2, files.Length);
|
||||||
PdfAsserts.AssertPageCount(1, files[1].FullName);
|
// PdfAsserts.AssertPageCount(1, files[0].FullName);
|
||||||
errorOutput.VerifyNoOtherCalls();
|
// PdfAsserts.AssertPageCount(1, files[1].FullName);
|
||||||
}
|
// errorOutput.VerifyNoOtherCalls();
|
||||||
|
// }
|
||||||
[Fact]
|
//
|
||||||
public async Task TwoImagesTwoJpegs()
|
// [Fact]
|
||||||
{
|
// public async Task TwoImagesTwoJpegs()
|
||||||
var errorOutput = new Mock<ErrorOutput>();
|
// {
|
||||||
var driver = Driver(errorOutput.Object, 2);
|
// var errorOutput = new Mock<ErrorOutput>();
|
||||||
|
// var driver = Driver(errorOutput.Object, 2);
|
||||||
var scanProfile = Profile(new AutoSaveSettings
|
//
|
||||||
{
|
// var scanProfile = Profile(new AutoSaveSettings
|
||||||
FilePath = Path.Combine(FolderPath, "test$(n).jpg")
|
// {
|
||||||
});
|
// FilePath = Path.Combine(FolderPath, "test$(n).jpg")
|
||||||
var scanParams = new ScanParams();
|
// });
|
||||||
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
// var scanParams = new ScanParams();
|
||||||
var files = Folder.GetFiles();
|
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
||||||
|
// var files = Folder.GetFiles();
|
||||||
Assert.Equal(2, scannedImages.Count);
|
//
|
||||||
Assert.Equal(2, files.Length);
|
// Assert.Equal(2, scannedImages.Count);
|
||||||
errorOutput.VerifyNoOtherCalls();
|
// Assert.Equal(2, files.Length);
|
||||||
}
|
// errorOutput.VerifyNoOtherCalls();
|
||||||
|
// }
|
||||||
[Fact]
|
//
|
||||||
public async Task ClearAfterSaving()
|
// [Fact]
|
||||||
{
|
// public async Task ClearAfterSaving()
|
||||||
var errorOutput = new Mock<ErrorOutput>();
|
// {
|
||||||
var driver = Driver(errorOutput.Object, 2);
|
// var errorOutput = new Mock<ErrorOutput>();
|
||||||
|
// var driver = Driver(errorOutput.Object, 2);
|
||||||
var scanProfile = Profile(new AutoSaveSettings
|
//
|
||||||
{
|
// var scanProfile = Profile(new AutoSaveSettings
|
||||||
FilePath = Path.Combine(FolderPath, "test$(n).jpg"),
|
// {
|
||||||
ClearImagesAfterSaving = true
|
// FilePath = Path.Combine(FolderPath, "test$(n).jpg"),
|
||||||
});
|
// ClearImagesAfterSaving = true
|
||||||
var scanParams = new ScanParams();
|
// });
|
||||||
var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
// var scanParams = new ScanParams();
|
||||||
var files = Folder.GetFiles();
|
// var scannedImages = await driver.Scan(scanProfile, scanParams).ToList();
|
||||||
|
// var files = Folder.GetFiles();
|
||||||
Assert.Empty(scannedImages);
|
//
|
||||||
Assert.Equal(2, files.Length);
|
// Assert.Empty(scannedImages);
|
||||||
errorOutput.VerifyNoOtherCalls();
|
// Assert.Equal(2, files.Length);
|
||||||
}
|
// errorOutput.VerifyNoOtherCalls();
|
||||||
|
// }
|
||||||
// TODO: ClearAfterSaving with error, PromptForFilePath, SaveSeparator
|
//
|
||||||
|
// // TODO: ClearAfterSaving with error, PromptForFilePath, SaveSeparator
|
||||||
private ScanDevice Device => new ScanDevice("test_id", "test_name");
|
//
|
||||||
|
// private ScanDevice Device => new ScanDevice("test_id", "test_name");
|
||||||
private MockScanDriver Driver(ErrorOutput errorOutput, int images) => new MockScanDriver(errorOutput, CreateAutoSaver(errorOutput))
|
//
|
||||||
{
|
// 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()
|
// MockDevices = new List<ScanDevice> { Device },
|
||||||
};
|
// MockOutput = Enumerable.Range(0, images).Select(i => CreateScannedImage()).ToList()
|
||||||
|
// };
|
||||||
private ScanProfile Profile(AutoSaveSettings autoSaveSettings) => new ScanProfile
|
//
|
||||||
{
|
// private ScanProfile Profile(AutoSaveSettings autoSaveSettings) => new ScanProfile
|
||||||
Device = Device,
|
// {
|
||||||
EnableAutoSave = true,
|
// Device = Device,
|
||||||
AutoSaveSettings = autoSaveSettings
|
// EnableAutoSave = true,
|
||||||
};
|
// AutoSaveSettings = autoSaveSettings
|
||||||
|
// };
|
||||||
private AutoSaver CreateAutoSaver(ErrorOutput errorOutput)
|
//
|
||||||
{
|
// private AutoSaver CreateAutoSaver(ErrorOutput errorOutput)
|
||||||
return new AutoSaver(
|
// {
|
||||||
new StubConfigProvider<PdfSettings>(new PdfSettings()),
|
// return new AutoSaver(
|
||||||
new StubConfigProvider<ImageSettings>(new ImageSettings()),
|
// new StubConfigProvider<PdfSettings>(new PdfSettings()),
|
||||||
new OcrEngineManager(),
|
// new StubConfigProvider<ImageSettings>(new ImageSettings()),
|
||||||
new OcrRequestQueue(new OcrEngineManager(), new StubOperationProgress()),
|
// new OcrEngineManager(),
|
||||||
errorOutput,
|
// new OcrRequestQueue(new OcrEngineManager(), new StubOperationProgress()),
|
||||||
new StubDialogHelper(),
|
// errorOutput,
|
||||||
new StubOperationProgress(),
|
// new StubDialogHelper(),
|
||||||
null,
|
// new StubOperationProgress(),
|
||||||
new PdfSharpExporter(new MemoryStreamRenderer(ImageContext)),
|
// null,
|
||||||
new StubOverwritePrompt(),
|
// new PdfSharpExporter(new MemoryStreamRenderer(ImageContext)),
|
||||||
new BitmapRenderer(ImageContext),
|
// new StubOverwritePrompt(),
|
||||||
new StubConfigProvider<CommonConfig>(InternalDefaults.GetCommonConfig()));
|
// new BitmapRenderer(ImageContext),
|
||||||
}
|
// new StubConfigProvider<CommonConfig>(InternalDefaults.GetCommonConfig()));
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ namespace NAPS2.Config
|
|||||||
{
|
{
|
||||||
if (settings.Version == 1)
|
if (settings.Version == 1)
|
||||||
{
|
{
|
||||||
if (settings.DriverName == TwainScanDriver.DRIVER_NAME)
|
if (settings.DriverName == DriverNames.TWAIN)
|
||||||
{
|
{
|
||||||
settings.UseNativeUI = true;
|
settings.UseNativeUI = true;
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ namespace NAPS2.Config
|
|||||||
{
|
{
|
||||||
if (settings.Version == 1)
|
if (settings.Version == 1)
|
||||||
{
|
{
|
||||||
if (settings.DriverName == TwainScanDriver.DRIVER_NAME)
|
if (settings.DriverName == DriverNames.TWAIN)
|
||||||
{
|
{
|
||||||
settings.UseNativeUI = true;
|
settings.UseNativeUI = true;
|
||||||
}
|
}
|
||||||
@ -123,7 +123,7 @@ namespace NAPS2.Config
|
|||||||
IsDefault = profile.IsDefault,
|
IsDefault = profile.IsDefault,
|
||||||
IconID = profile.IconID,
|
IconID = profile.IconID,
|
||||||
// If the driver is WIA and the profile type is not Extended, that meant the native UI was to be used
|
// 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)
|
if (profile is ExtendedScanSettingsV0 ext)
|
||||||
{
|
{
|
||||||
|
@ -148,7 +148,6 @@
|
|||||||
<Compile Include="Remoting\ClientServer\ClientContextFactory.cs" />
|
<Compile Include="Remoting\ClientServer\ClientContextFactory.cs" />
|
||||||
<Compile Include="Remoting\ClientServer\IScanCallback.cs" />
|
<Compile Include="Remoting\ClientServer\IScanCallback.cs" />
|
||||||
<Compile Include="Remoting\ClientServer\IScanService.cs" />
|
<Compile Include="Remoting\ClientServer\IScanService.cs" />
|
||||||
<Compile Include="Remoting\ClientServer\ProxiedScanDriver.cs" />
|
|
||||||
<Compile Include="Remoting\ClientServer\ScanCallback.cs" />
|
<Compile Include="Remoting\ClientServer\ScanCallback.cs" />
|
||||||
<Compile Include="Remoting\ClientServer\ScanService.cs" />
|
<Compile Include="Remoting\ClientServer\ScanService.cs" />
|
||||||
<Compile Include="Remoting\ClientServer\ServerDiscovery.cs" />
|
<Compile Include="Remoting\ClientServer\ServerDiscovery.cs" />
|
||||||
@ -231,6 +230,7 @@
|
|||||||
<Compile Include="Scan\Batch\BatchOutputType.cs" />
|
<Compile Include="Scan\Batch\BatchOutputType.cs" />
|
||||||
<Compile Include="Scan\Batch\BatchScanType.cs" />
|
<Compile Include="Scan\Batch\BatchScanType.cs" />
|
||||||
<Compile Include="Scan\Batch\IBatchScanPerformer.cs" />
|
<Compile Include="Scan\Batch\IBatchScanPerformer.cs" />
|
||||||
|
<Compile Include="Scan\DriverNames.cs" />
|
||||||
<Compile Include="Scan\Exceptions\DriverNotSupportedException.cs" />
|
<Compile Include="Scan\Exceptions\DriverNotSupportedException.cs" />
|
||||||
<Compile Include="Images\ScannedImageSource.cs" />
|
<Compile Include="Images\ScannedImageSource.cs" />
|
||||||
<Compile Include="Images\Storage\GdiTransformers.cs" />
|
<Compile Include="Images\Storage\GdiTransformers.cs" />
|
||||||
@ -304,11 +304,8 @@
|
|||||||
<Compile Include="Scan\Sane\SaneOption.cs" />
|
<Compile Include="Scan\Sane\SaneOption.cs" />
|
||||||
<Compile Include="Scan\Sane\SaneOptionCollection.cs" />
|
<Compile Include="Scan\Sane\SaneOptionCollection.cs" />
|
||||||
<Compile Include="Scan\Sane\SaneOptionParser.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\Driver.cs" />
|
||||||
<Compile Include="Scan\Experimental\ScanOptions.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\Hresult.cs" />
|
||||||
<Compile Include="Scan\Wia\Native\IWiaDeviceProps.cs" />
|
<Compile Include="Scan\Wia\Native\IWiaDeviceProps.cs" />
|
||||||
<Compile Include="Scan\Wia\Native\NativeStreamWrapper.cs" />
|
<Compile Include="Scan\Wia\Native\NativeStreamWrapper.cs" />
|
||||||
@ -333,7 +330,6 @@
|
|||||||
<Compile Include="Scan\Wia\Native\WiaVersion.cs" />
|
<Compile Include="Scan\Wia\Native\WiaVersion.cs" />
|
||||||
<Compile Include="Scan\Wia\WiaConfiguration.cs" />
|
<Compile Include="Scan\Wia\WiaConfiguration.cs" />
|
||||||
<Compile Include="Scan\Wia\WiaScanErrors.cs" />
|
<Compile Include="Scan\Wia\WiaScanErrors.cs" />
|
||||||
<Compile Include="Scan\Wia\WiaScanOperation.cs" />
|
|
||||||
<Compile Include="Serialization\SerializedImageHelper.cs" />
|
<Compile Include="Serialization\SerializedImageHelper.cs" />
|
||||||
<Compile Include="Update\UpdateOperation.cs" />
|
<Compile Include="Update\UpdateOperation.cs" />
|
||||||
<Compile Include="Update\UpdateInfo.cs" />
|
<Compile Include="Update\UpdateInfo.cs" />
|
||||||
@ -460,7 +456,6 @@
|
|||||||
<Compile Include="Scan\Twain\Legacy\TwainApi.cs" />
|
<Compile Include="Scan\Twain\Legacy\TwainApi.cs" />
|
||||||
<Compile Include="Scan\Twain\Legacy\TwainDefs.cs" />
|
<Compile Include="Scan\Twain\Legacy\TwainDefs.cs" />
|
||||||
<Compile Include="Scan\Twain\Legacy\TwainLib.cs" />
|
<Compile Include="Scan\Twain\Legacy\TwainLib.cs" />
|
||||||
<Compile Include="Scan\Twain\TwainWrapper.cs" />
|
|
||||||
<Compile Include="Images\Transforms\UnsafeImageOps.cs" />
|
<Compile Include="Images\Transforms\UnsafeImageOps.cs" />
|
||||||
<Compile Include="Util\ChangeTracker.cs" />
|
<Compile Include="Util\ChangeTracker.cs" />
|
||||||
<Compile Include="Util\ChaosMonkey.cs" />
|
<Compile Include="Util\ChaosMonkey.cs" />
|
||||||
@ -508,7 +503,6 @@
|
|||||||
<Compile Include="Images\Transforms\CropTransform.cs" />
|
<Compile Include="Images\Transforms\CropTransform.cs" />
|
||||||
<Compile Include="Images\Transforms\RotationTransform.cs" />
|
<Compile Include="Images\Transforms\RotationTransform.cs" />
|
||||||
<Compile Include="Images\Transforms\Transform.cs" />
|
<Compile Include="Images\Transforms\Transform.cs" />
|
||||||
<Compile Include="Scan\IScanDriverFactory.cs" />
|
|
||||||
<Compile Include="Util\CultureInitializer.cs" />
|
<Compile Include="Util\CultureInitializer.cs" />
|
||||||
<Compile Include="Util\ErrorOutput.cs" />
|
<Compile Include="Util\ErrorOutput.cs" />
|
||||||
<Compile Include="Logging\ILogger.cs" />
|
<Compile Include="Logging\ILogger.cs" />
|
||||||
@ -562,16 +556,9 @@
|
|||||||
<Compile Include="Images\ScannedImageHelper.cs" />
|
<Compile Include="Images\ScannedImageHelper.cs" />
|
||||||
<Compile Include="Images\ScannedImageList.cs" />
|
<Compile Include="Images\ScannedImageList.cs" />
|
||||||
<Compile Include="Images\ThumbnailRenderer.cs" />
|
<Compile Include="Images\ThumbnailRenderer.cs" />
|
||||||
<Compile Include="Scan\IScanDriver.cs" />
|
|
||||||
<Compile Include="Scan\IScanPerformer.cs" />
|
<Compile Include="Scan\IScanPerformer.cs" />
|
||||||
<Compile Include="Scan\LocalizedDescriptionAttribute.cs" />
|
<Compile Include="Scan\LocalizedDescriptionAttribute.cs" />
|
||||||
<Compile Include="Scan\ScanDevice.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\OperationErrorEventArgs.cs" />
|
||||||
<Compile Include="Operation\OperationStatus.cs" />
|
<Compile Include="Operation\OperationStatus.cs" />
|
||||||
<Compile Include="Util\NumberExtensions.cs" />
|
<Compile Include="Util\NumberExtensions.cs" />
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ServiceModel;
|
using System.ServiceModel;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NAPS2.Platform;
|
|
||||||
using NAPS2.Scan;
|
using NAPS2.Scan;
|
||||||
using NAPS2.Scan.Sane;
|
using NAPS2.Scan.Experimental.Internal;
|
||||||
using NAPS2.Scan.Twain;
|
|
||||||
using NAPS2.Scan.Wia;
|
|
||||||
|
|
||||||
namespace NAPS2.Remoting.ClientServer
|
namespace NAPS2.Remoting.ClientServer
|
||||||
{
|
{
|
||||||
@ -15,95 +13,100 @@ namespace NAPS2.Remoting.ClientServer
|
|||||||
ConcurrencyMode = ConcurrencyMode.Multiple)]
|
ConcurrencyMode = ConcurrencyMode.Multiple)]
|
||||||
public class ScanService : IScanService
|
public class ScanService : IScanService
|
||||||
{
|
{
|
||||||
private readonly IScanDriverFactory scanDriverFactory;
|
private readonly IRemoteScanController remoteScanController;
|
||||||
private readonly IScanPerformer scanPerformer;
|
|
||||||
|
|
||||||
private CancellationTokenSource scanCts = new CancellationTokenSource();
|
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()
|
public List<string> GetSupportedDriverNames()
|
||||||
{
|
{
|
||||||
var driverNames = new List<string>();
|
throw new NotImplementedException();
|
||||||
if (PlatformCompat.System.IsWiaDriverSupported)
|
// var driverNames = new List<string>();
|
||||||
{
|
// if (PlatformCompat.System.IsWiaDriverSupported)
|
||||||
driverNames.Add(WiaScanDriver.DRIVER_NAME);
|
// {
|
||||||
}
|
// driverNames.Add(WiaScanDriver.DRIVER_NAME);
|
||||||
|
// }
|
||||||
if (PlatformCompat.System.IsTwainDriverSupported)
|
//
|
||||||
{
|
// if (PlatformCompat.System.IsTwainDriverSupported)
|
||||||
driverNames.Add(TwainScanDriver.DRIVER_NAME);
|
// {
|
||||||
}
|
// driverNames.Add(TwainScanDriver.DRIVER_NAME);
|
||||||
|
// }
|
||||||
if (PlatformCompat.System.IsSaneDriverSupported)
|
//
|
||||||
{
|
// if (PlatformCompat.System.IsSaneDriverSupported)
|
||||||
driverNames.Add(SaneScanDriver.DRIVER_NAME);
|
// {
|
||||||
}
|
// driverNames.Add(SaneScanDriver.DRIVER_NAME);
|
||||||
|
// }
|
||||||
return driverNames;
|
//
|
||||||
|
// return driverNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ScanDevice> GetDeviceList(ScanProfile scanProfile)
|
public List<ScanDevice> GetDeviceList(ScanProfile scanProfile)
|
||||||
{
|
{
|
||||||
if (scanProfile.DriverName == ProxiedScanDriver.DRIVER_NAME)
|
throw new NotImplementedException();
|
||||||
{
|
// if (scanProfile.DriverName == ProxiedScanDriver.DRIVER_NAME)
|
||||||
scanProfile.DriverName = scanProfile.ProxyDriverName;
|
// {
|
||||||
}
|
// scanProfile.DriverName = scanProfile.ProxyDriverName;
|
||||||
var driver = scanDriverFactory.Create(scanProfile.DriverName);
|
// }
|
||||||
return driver.GetDeviceList(scanProfile);
|
// var driver = scanDriverFactory.Create(scanProfile.DriverName);
|
||||||
|
// return driver.GetDeviceList(scanProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> Scan(ScanProfile scanProfile, ScanParams scanParams)
|
public async Task<int> Scan(ScanProfile scanProfile, ScanParams scanParams)
|
||||||
{
|
{
|
||||||
if (scanProfile.DriverName == ProxiedScanDriver.DRIVER_NAME)
|
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),
|
// scanProfile.DriverName = scanProfile.ProxyDriverName;
|
||||||
// TransformList = indexImage.TransformList,
|
// }
|
||||||
// BitDepth = indexImage.BitDepth,
|
// if (scanProfile.TwainImpl == TwainImpl.Legacy)
|
||||||
// HighQuality = indexImage.HighQuality
|
// {
|
||||||
|
// 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
|
||||||
// };
|
// };
|
||||||
//callback.ImageReceived(imageBytes, sanitizedIndexImage);
|
//
|
||||||
//pages++;
|
// var callback = OperationContext.Current.GetCallbackChannel<IScanCallback>();
|
||||||
}
|
//
|
||||||
});
|
// int pages = 0;
|
||||||
return pages;
|
// 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()
|
public void CancelScan()
|
||||||
|
12
NAPS2.Sdk/Scan/DriverNames.cs
Normal file
12
NAPS2.Sdk/Scan/DriverNames.cs
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
@ -133,9 +133,9 @@ namespace NAPS2.Scan.Experimental
|
|||||||
{
|
{
|
||||||
var options = new ScanOptions
|
var options = new ScanOptions
|
||||||
{
|
{
|
||||||
Driver = scanProfile.DriverName == "wia" ? Driver.Wia
|
Driver = scanProfile.DriverName == DriverNames.WIA ? Driver.Wia
|
||||||
: scanProfile.DriverName == "sane" ? Driver.Sane
|
: scanProfile.DriverName == DriverNames.SANE ? Driver.Sane
|
||||||
: scanProfile.DriverName == "twain" ? Driver.Twain
|
: scanProfile.DriverName == DriverNames.TWAIN ? Driver.Twain
|
||||||
: Driver.Default,
|
: Driver.Default,
|
||||||
WiaOptions =
|
WiaOptions =
|
||||||
{
|
{
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -55,9 +55,9 @@ namespace NAPS2.WinForms
|
|||||||
|
|
||||||
private void UpdateEnabled()
|
private void UpdateEnabled()
|
||||||
{
|
{
|
||||||
cmbTwainImpl.Enabled = ScanProfile.DriverName == TwainScanDriver.DRIVER_NAME;
|
cmbTwainImpl.Enabled = ScanProfile.DriverName == DriverNames.TWAIN;
|
||||||
cbWiaOffsetWidth.Enabled = ScanProfile.DriverName == WiaScanDriver.DRIVER_NAME;
|
cbWiaOffsetWidth.Enabled = ScanProfile.DriverName == DriverNames.WIA;
|
||||||
cmbWiaVersion.Enabled = ScanProfile.DriverName == WiaScanDriver.DRIVER_NAME;
|
cmbWiaVersion.Enabled = ScanProfile.DriverName == DriverNames.WIA;
|
||||||
tbImageQuality.Enabled = !cbHighQuality.Checked;
|
tbImageQuality.Enabled = !cbHighQuality.Checked;
|
||||||
txtImageQuality.Enabled = !cbHighQuality.Checked;
|
txtImageQuality.Enabled = !cbHighQuality.Checked;
|
||||||
tbWhiteThreshold.Enabled = cbExcludeBlankPages.Checked && ScanProfile.BitDepth != ScanBitDepth.BlackWhite;
|
tbWhiteThreshold.Enabled = cbExcludeBlankPages.Checked && ScanProfile.BitDepth != ScanBitDepth.BlackWhite;
|
||||||
|
@ -71,7 +71,7 @@ namespace NAPS2.WinForms
|
|||||||
CurrentDevice = ScanProfile.Device;
|
CurrentDevice = ScanProfile.Device;
|
||||||
}
|
}
|
||||||
isDefault = ScanProfile.IsDefault;
|
isDefault = ScanProfile.IsDefault;
|
||||||
useProxy = ScanProfile.DriverName == ProxiedScanDriver.DRIVER_NAME;
|
useProxy = ScanProfile.DriverName == DriverNames.PROXY;
|
||||||
iconID = ScanProfile.IconID;
|
iconID = ScanProfile.IconID;
|
||||||
|
|
||||||
cmbSource.SelectedIndex = (int)ScanProfile.PaperSource;
|
cmbSource.SelectedIndex = (int)ScanProfile.PaperSource;
|
||||||
@ -188,20 +188,20 @@ namespace NAPS2.WinForms
|
|||||||
|
|
||||||
private string DeviceDriverName
|
private string DeviceDriverName
|
||||||
{
|
{
|
||||||
get => rdTWAIN.Checked ? TwainScanDriver.DRIVER_NAME
|
get => rdTWAIN.Checked ? DriverNames.TWAIN
|
||||||
: rdSANE.Checked ? SaneScanDriver.DRIVER_NAME
|
: rdSANE.Checked ? DriverNames.SANE
|
||||||
: WiaScanDriver.DRIVER_NAME;
|
: DriverNames.WIA;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == TwainScanDriver.DRIVER_NAME)
|
if (value == DriverNames.TWAIN)
|
||||||
{
|
{
|
||||||
rdTWAIN.Checked = true;
|
rdTWAIN.Checked = true;
|
||||||
}
|
}
|
||||||
else if (value == SaneScanDriver.DRIVER_NAME)
|
else if (value == DriverNames.SANE)
|
||||||
{
|
{
|
||||||
rdSANE.Checked = true;
|
rdSANE.Checked = true;
|
||||||
}
|
}
|
||||||
else if (value == WiaScanDriver.DRIVER_NAME || PlatformCompat.System.IsWiaDriverSupported)
|
else if (value == DriverNames.WIA || PlatformCompat.System.IsWiaDriverSupported)
|
||||||
{
|
{
|
||||||
rdWIA.Checked = true;
|
rdWIA.Checked = true;
|
||||||
}
|
}
|
||||||
@ -253,7 +253,7 @@ namespace NAPS2.WinForms
|
|||||||
|
|
||||||
private async void btnChooseDevice_Click(object sender, EventArgs e)
|
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;
|
ScanProfile.ProxyDriverName = useProxy ? DeviceDriverName : null;
|
||||||
await ChooseDevice();
|
await ChooseDevice();
|
||||||
}
|
}
|
||||||
@ -279,7 +279,7 @@ namespace NAPS2.WinForms
|
|||||||
|
|
||||||
Device = CurrentDevice,
|
Device = CurrentDevice,
|
||||||
IsDefault = isDefault,
|
IsDefault = isDefault,
|
||||||
DriverName = useProxy ? ProxiedScanDriver.DRIVER_NAME : DeviceDriverName,
|
DriverName = useProxy ? DriverNames.PROXY : DeviceDriverName,
|
||||||
ProxyConfig = ScanProfile.ProxyConfig,
|
ProxyConfig = ScanProfile.ProxyConfig,
|
||||||
ProxyDriverName = useProxy ? DeviceDriverName : null,
|
ProxyDriverName = useProxy ? DeviceDriverName : null,
|
||||||
DisplayName = txtName.Text,
|
DisplayName = txtName.Text,
|
||||||
@ -354,7 +354,7 @@ namespace NAPS2.WinForms
|
|||||||
{
|
{
|
||||||
suppressChangeEvent = true;
|
suppressChangeEvent = true;
|
||||||
|
|
||||||
bool canUseNativeUi = DeviceDriverName != SaneScanDriver.DRIVER_NAME && !useProxy;
|
bool canUseNativeUi = DeviceDriverName != DriverNames.SANE && !useProxy;
|
||||||
bool locked = ScanProfile.IsLocked;
|
bool locked = ScanProfile.IsLocked;
|
||||||
bool deviceLocked = ScanProfile.IsDeviceLocked;
|
bool deviceLocked = ScanProfile.IsDeviceLocked;
|
||||||
bool settingsEnabled = !locked && (rdbConfig.Checked || !canUseNativeUi);
|
bool settingsEnabled = !locked && (rdbConfig.Checked || !canUseNativeUi);
|
||||||
|
Loading…
Reference in New Issue
Block a user