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);
        }
Beispiel #2
0
        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);
        }