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>(); var 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.Factory.StartNew(() => { proc.StandardOutput.BaseStream.CopyTo(outputStream); outputStream.Seek(0, SeekOrigin.Begin); outputFinishedWaitHandle.Set(); }, TaskCreationOptions.LongRunning); // 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 var 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 KeyValueScanOptions GetOptions() { 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); }