public double GetSkewAngle(Bitmap image) { var bitArrays = UnsafeImageOps.ConvertToBitArrays(image); var h = image.Height; var w = image.Width; // Ignore a bit of the top/bottom so that artifacts from the edge // of the scan area aren't counted. (Left/right don't matter since // we only look at near-horizontal lines.) var yOffset = (int)Math.Round(h * IGNORE_EDGE_FRACTION); var sinCos = PrecalculateSinCos(); // TODO: Consider reducing the precision of distance or angle, // TODO: possibly with a second pass to restore precision. var dCount = 2 * (w + h); var scores = new int[dCount, ANGLE_STEPS]; // TODO: This should be a good candidate for OpenCL optimization. // TODO: If you parallelize over the angle, you're operating over // TODO: the same input data with the same branches. for (var y = 1 + yOffset; y <= h - 2 - yOffset; y++) { for (var x = 1; x <= w - 2; x++) { if (!bitArrays[y][x] || bitArrays[y + 1][x]) { continue; } for (var i = 0; i < ANGLE_STEPS; i++) { var sc = sinCos[i]; var d = (int)(y * sc.cos - x * sc.sin + w); scores[d, i]++; } } } var angles = GetAnglesOfBestLines(scores, dCount); var cluster = ClusterAngles(angles); if (cluster.Length < angles.Length / 2) { // Could not find a consistent skew angle return(0); } return(cluster.Sum() / cluster.Length); }
private async Task <(ScannedImage, bool)> Transfer(Lazy <KeyValueScanOptions> options, int pageNumber) { return(await Task.Factory.StartNew(() => { 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 = Image.FromStream(stream)) 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 ? UnsafeImageOps.ConvertTo1Bpp(result, -ScanProfile.Brightness) : result) { var image = new ScannedImage(encoded, ScanProfile.BitDepth, ScanProfile.MaxQuality, ScanProfile.Quality); scannedImageHelper.PostProcessStep2(image, result, ScanProfile, ScanParams, 1, false); var tempPath = scannedImageHelper.SaveForBackgroundOcr(result, ScanParams); scannedImageHelper.RunBackgroundOcr(image, ScanParams, tempPath); return (image, false); } } }, TaskCreationOptions.LongRunning)); }