Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 182 additions & 4 deletions NAPS2.Sdk/Scan/BarcodeDetectionOptions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using ZXing.Common;

namespace NAPS2.Scan;

/// <summary>
Expand All @@ -10,6 +8,186 @@ public class BarcodeDetectionOptions
public bool DetectBarcodes { get; set; }

public bool PatchTOnly { get; set; }

public DecodingOptions? ZXingOptions { get; set; }

/// <summary>
/// Image is a pure monochrome image of a barcode.
/// </summary>
/// <value>
/// <c>true</c> if monochrome image of a barcode; otherwise, <c>false</c>.
/// </value>
public bool PureBarcode { get; set; }

/// <summary>
/// Specifies what character encoding to use when decoding, where applicable (type String)
/// </summary>
/// <value>
/// The character set.
/// </value>
public string? CharacterSet { get; set; }

/// <summary>
/// Image is known to be of one of a few possible formats.
/// Bitfield enum of {@link BarcodeFormat}s.
/// </summary>
/// <value>
/// The possible formats.
/// </value>
public BarcodeFormat PossibleFormats { get; set; }

/// <summary>
/// if Code39 could be detected try to use extended mode for full ASCII character set
/// </summary>
public bool UseCode39ExtendedMode { get; set; }

/// <summary>
/// Don't fail if a Code39 is detected but can't be decoded in extended mode.
/// Return the raw Code39 result instead. Maps to <see cref="bool" />.
/// </summary>
public bool UseCode39RelaxedExtendedMode { get; set; }

/// <summary>
/// Assume Code 39 codes employ a check digit. Maps to <see cref="bool" />.
/// </summary>
/// <value>
/// <c>true</c> if it should assume a Code 39 check digit; otherwise, <c>false</c>.
/// </value>
public bool AssumeCode39CheckDigit { get; set; }

/// <summary>
/// If true, return the start and end digits in a Codabar barcode instead of stripping them. They
/// are alpha, whereas the rest are numeric. By default, they are stripped, but this causes them
/// to not be. Doesn't matter what it maps to; use <see cref="bool" />.
/// </summary>
public bool ReturnCodabarStartEnd { get; set; }

/// <summary>
/// Assume the barcode is being processed as a GS1 barcode, and modify behavior as needed.
/// For example this affects FNC1 handling for Code 128 (aka GS1-128).
/// </summary>
/// <value>
/// <c>true</c> if it should assume GS1; otherwise, <c>false</c>.
/// </value>
public bool AssumeGS1 { get; set; }

/// <summary>
/// Assume MSI codes employ a check digit. Maps to <see cref="bool" />.
/// </summary>
/// <value>
/// <c>true</c> if it should assume a MSI check digit; otherwise, <c>false</c>.
/// </value>
public bool AssumeMSICheckDigit { get; set; }

/// <summary>
/// Allowed lengths of encoded data -- reject anything else. Maps to an int[].
/// </summary>
public int[]? AllowedLengths { get; set; }

/// <summary>
/// Allowed extension lengths for EAN or UPC barcodes. Other formats will ignore this.
/// Maps to an int[] of the allowed extension lengths, for example [2], [5], or [2, 5].
/// If it is optional to have an extension, do not set this hint. If this is set,
/// and a UPC or EAN barcode is found but an extension is not, then no result will be returned
/// at all.
/// </summary>
public int[]? AllowedEANExtensions { get; set; }

}

//
// Summary:
// Enumerates barcode formats known to this package.
//
[Flags]
public enum BarcodeFormat
{
//
// Summary:
// Aztec 2D barcode format.
AZTEC = 1,
//
// Summary:
// CODABAR 1D format.
CODABAR = 2,
//
// Summary:
// Code 39 1D format.
CODE_39 = 4,
//
// Summary:
// Code 93 1D format.
CODE_93 = 8,
//
// Summary:
// Code 128 1D format.
CODE_128 = 0x10,
//
// Summary:
// Data Matrix 2D barcode format.
DATA_MATRIX = 0x20,
//
// Summary:
// EAN-8 1D format.
EAN_8 = 0x40,
//
// Summary:
// EAN-13 1D format.
EAN_13 = 0x80,
//
// Summary:
// ITF (Interleaved Two of Five) 1D format.
ITF = 0x100,
//
// Summary:
// MaxiCode 2D barcode format.
MAXICODE = 0x200,
//
// Summary:
// PDF417 format.
PDF_417 = 0x400,
//
// Summary:
// QR Code 2D barcode format.
QR_CODE = 0x800,
//
// Summary:
// RSS 14
RSS_14 = 0x1000,
//
// Summary:
// RSS EXPANDED
RSS_EXPANDED = 0x2000,
//
// Summary:
// UPC-A 1D format.
UPC_A = 0x4000,
//
// Summary:
// UPC-E 1D format.
UPC_E = 0x8000,
//
// Summary:
// UPC/EAN extension format. Not a stand-alone format.
UPC_EAN_EXTENSION = 0x10000,
//
// Summary:
// MSI
MSI = 0x20000,
//
// Summary:
// Plessey
PLESSEY = 0x40000,
//
// Summary:
// Intelligent Mail barcode
IMB = 0x80000,
//
// Summary:
// Pharmacode format.
PHARMA_CODE = 0x100000,
//
// Summary:
// UPC_A | UPC_E | EAN_13 | EAN_8 | CODABAR | CODE_39 | CODE_93 | CODE_128 | ITF
// | RSS_14 | RSS_EXPANDED without MSI (to many false-positives) and IMB (not enough
// tested, and it looks more like a 2D)
All_1D = 0xF1DE
}
34 changes: 31 additions & 3 deletions NAPS2.Sdk/Scan/BarcodeDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace NAPS2.Scan;
/// </summary>
internal static class BarcodeDetector
{
private static readonly BarcodeFormat PATCH_T_FORMAT = BarcodeFormat.CODE_39;
private static readonly ZXing.BarcodeFormat PATCH_T_FORMAT = ZXing.BarcodeFormat.CODE_39;

public static Barcode Detect(IMemoryImage image, BarcodeDetectionOptions options)
{
Expand All @@ -20,11 +20,39 @@ public static Barcode Detect(IMemoryImage image, BarcodeDetectionOptions options
return Barcode.NoDetection;
}

var zxingOptions = options.ZXingOptions ?? new DecodingOptions
// Create a ZXing DecodingOptions object based on the provided options.
var zxingOptions = new DecodingOptions
{
TryHarder = true,
PossibleFormats = options.PatchTOnly ? [PATCH_T_FORMAT] : null
PureBarcode = options.PureBarcode,
CharacterSet = options.CharacterSet,
UseCode39ExtendedMode = options.UseCode39ExtendedMode,
UseCode39RelaxedExtendedMode = options.UseCode39RelaxedExtendedMode,
AssumeCode39CheckDigit = options.AssumeCode39CheckDigit,
ReturnCodabarStartEnd = options.ReturnCodabarStartEnd,
AssumeGS1 = options.AssumeGS1,
AssumeMSICheckDigit = options.AssumeMSICheckDigit,
AllowedLengths = options.AllowedLengths,
AllowedEANExtensions = options.AllowedEANExtensions
};
if (options.PatchTOnly)
{
zxingOptions.PossibleFormats ??= [];
zxingOptions.PossibleFormats = [PATCH_T_FORMAT];
}
else
{
// map the PossibleFormats bitfield to the matching ZXing.BarcodeFormat's
foreach (ZXing.BarcodeFormat format in Enum.GetValues(typeof(ZXing.BarcodeFormat)))
{
if ((((int) options.PossibleFormats) & (int) format) > 0)
{
zxingOptions.PossibleFormats ??= [];
zxingOptions.PossibleFormats.Add(format);
}
}
}

var reader = new BarcodeReader<IMemoryImage>(x => new MemoryImageLuminanceSource(x))
{
Options = zxingOptions
Expand Down
28 changes: 25 additions & 3 deletions NAPS2.Sdk/Scan/Internal/RemotePostProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,26 @@ options.Driver is not (Driver.Wia or Driver.Twain))
if (!data.Barcode.IsDetected)
{
// Even if barcode detection was attempted previously and failed, image adjustments may improve detection.
data = data with
try
{
Barcode = BarcodeDetector.Detect(image, options.BarcodeDetectionOptions)
};
data = data with
{
Barcode = DetectBarcode(image, options.BarcodeDetectionOptions)
};
}
catch (FileNotFoundException ex)
{
if (data.Barcode == Barcode.NoDetection)
{
// If we don't want a barcode we can ignore this
_logger.LogError(ex, "ZXing library not found. But Barcode detection is not enabled anyway.");
}
else
{
// If we want a barcode we should throw
throw;
}
}
}
if (options.ThumbnailSize.HasValue)
{
Expand All @@ -193,6 +209,12 @@ options.Driver is not (Driver.Wia or Driver.Twain))
processedImage = processedImage.WithPostProcessingData(data, true);
}

// Do the barcode detection
private static Barcode DetectBarcode(IMemoryImage image, BarcodeDetectionOptions options)
{
return BarcodeDetector.Detect(image, options);
}

private string? SaveForBackgroundOcr(IMemoryImage bitmap, ScanOptions options)
{
if (!string.IsNullOrEmpty(options.OcrParams.LanguageCode))
Expand Down