2021-12-17 23:18:20 +03:00
|
|
|
|
using System.Runtime.InteropServices;
|
2021-12-23 04:45:22 +03:00
|
|
|
|
using NAPS2.Images.Gdi;
|
2018-12-04 04:12:20 +03:00
|
|
|
|
|
2021-12-17 22:59:54 +03:00
|
|
|
|
namespace NAPS2.Images;
|
|
|
|
|
|
|
|
|
|
public static class BlankDetector
|
2018-12-04 04:12:20 +03:00
|
|
|
|
{
|
2021-12-17 22:59:54 +03:00
|
|
|
|
// If the pixel value (0-255) >= white_threshold, then it counts as a white pixel.
|
|
|
|
|
private const int WHITE_THRESHOLD_MIN = 1;
|
|
|
|
|
private const int WHITE_THRESHOLD_MAX = 255;
|
|
|
|
|
// If the fraction of non-white pixels > coverage_threshold, then it counts as a non-blank page.
|
|
|
|
|
private const double COVERAGE_THRESHOLD_MIN = 0.00;
|
|
|
|
|
private const double COVERAGE_THRESHOLD_MAX = 0.01;
|
2018-12-04 04:12:20 +03:00
|
|
|
|
|
2022-06-09 05:39:27 +03:00
|
|
|
|
public static bool IsBlank(IMemoryImage image, int whiteThresholdNorm, int coverageThresholdNorm)
|
2021-12-17 22:59:54 +03:00
|
|
|
|
{
|
2021-12-23 04:45:22 +03:00
|
|
|
|
if (image.PixelFormat == ImagePixelFormat.BW1)
|
2018-12-04 04:12:20 +03:00
|
|
|
|
{
|
2021-12-17 22:59:54 +03:00
|
|
|
|
// TODO: Make more generic
|
|
|
|
|
if (!(image is GdiImage gdiImage))
|
2020-02-04 02:26:24 +03:00
|
|
|
|
{
|
2021-12-17 22:59:54 +03:00
|
|
|
|
throw new InvalidOperationException("Patch code detection only supported for GdiStorage");
|
2020-02-04 02:26:24 +03:00
|
|
|
|
}
|
2021-12-17 22:59:54 +03:00
|
|
|
|
using var bitmap2 = BitmapHelper.CopyToBpp(gdiImage.Bitmap, 8);
|
|
|
|
|
return IsBlankRGB(new GdiImage(bitmap2), whiteThresholdNorm, coverageThresholdNorm);
|
2018-12-04 04:12:20 +03:00
|
|
|
|
}
|
2021-12-23 04:45:22 +03:00
|
|
|
|
if (image.PixelFormat != ImagePixelFormat.RGB24)
|
2020-02-04 02:26:24 +03:00
|
|
|
|
{
|
2021-12-17 22:59:54 +03:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return IsBlankRGB(image, whiteThresholdNorm, coverageThresholdNorm);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-09 05:39:27 +03:00
|
|
|
|
private static bool IsBlankRGB(IMemoryImage image, int whiteThresholdNorm, int coverageThresholdNorm)
|
2021-12-17 22:59:54 +03:00
|
|
|
|
{
|
|
|
|
|
var whiteThreshold = (int)Math.Round(WHITE_THRESHOLD_MIN + (whiteThresholdNorm / 100.0) * (WHITE_THRESHOLD_MAX - WHITE_THRESHOLD_MIN));
|
|
|
|
|
var coverageThreshold = COVERAGE_THRESHOLD_MIN + (coverageThresholdNorm / 100.0) * (COVERAGE_THRESHOLD_MAX - COVERAGE_THRESHOLD_MIN);
|
2020-02-04 02:26:24 +03:00
|
|
|
|
|
2021-12-17 22:59:54 +03:00
|
|
|
|
long totalPixels = image.Width * image.Height;
|
|
|
|
|
long matchPixels = 0;
|
2018-12-04 04:12:20 +03:00
|
|
|
|
|
2022-07-01 22:39:26 +03:00
|
|
|
|
using var data = image.Lock(LockMode.ReadOnly, out var scan0, out var stride);
|
2021-12-17 22:59:54 +03:00
|
|
|
|
var bytes = new byte[stride * image.Height];
|
|
|
|
|
Marshal.Copy(scan0, bytes, 0, bytes.Length);
|
2022-07-01 22:39:26 +03:00
|
|
|
|
data.Dispose();
|
2021-12-17 22:59:54 +03:00
|
|
|
|
for (int x = 0; x < image.Width; x++)
|
|
|
|
|
{
|
|
|
|
|
for (int y = 0; y < image.Height; y++)
|
2020-02-04 02:26:24 +03:00
|
|
|
|
{
|
2021-12-17 22:59:54 +03:00
|
|
|
|
int r = bytes[stride * y + x * 3];
|
|
|
|
|
int g = bytes[stride * y + x * 3 + 1];
|
|
|
|
|
int b = bytes[stride * y + x * 3 + 2];
|
|
|
|
|
// Use standard values for grayscale conversion to weight the RGB values
|
|
|
|
|
int luma = r * 299 + g * 587 + b * 114;
|
|
|
|
|
if (luma < whiteThreshold * 1000)
|
2020-02-04 02:26:24 +03:00
|
|
|
|
{
|
2021-12-17 22:59:54 +03:00
|
|
|
|
matchPixels++;
|
2020-02-04 02:26:24 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-17 22:59:54 +03:00
|
|
|
|
|
|
|
|
|
var coverage = matchPixels / (double)totalPixels;
|
|
|
|
|
return coverage < coverageThreshold;
|
2018-12-04 04:12:20 +03:00
|
|
|
|
}
|
2021-12-17 22:59:54 +03:00
|
|
|
|
}
|