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