diff --git a/NAPS2.Sdk/Scan/BarcodeDetectionOptions.cs b/NAPS2.Sdk/Scan/BarcodeDetectionOptions.cs index 986e8aef49..a357ca7362 100644 --- a/NAPS2.Sdk/Scan/BarcodeDetectionOptions.cs +++ b/NAPS2.Sdk/Scan/BarcodeDetectionOptions.cs @@ -1,5 +1,3 @@ -using ZXing.Common; - namespace NAPS2.Scan; /// @@ -10,6 +8,186 @@ public class BarcodeDetectionOptions public bool DetectBarcodes { get; set; } public bool PatchTOnly { get; set; } - - public DecodingOptions? ZXingOptions { get; set; } + + /// + /// Image is a pure monochrome image of a barcode. + /// + /// + /// true if monochrome image of a barcode; otherwise, false. + /// + public bool PureBarcode { get; set; } + + /// + /// Specifies what character encoding to use when decoding, where applicable (type String) + /// + /// + /// The character set. + /// + public string? CharacterSet { get; set; } + + /// + /// Image is known to be of one of a few possible formats. + /// Bitfield enum of {@link BarcodeFormat}s. + /// + /// + /// The possible formats. + /// + public BarcodeFormat PossibleFormats { get; set; } + + /// + /// if Code39 could be detected try to use extended mode for full ASCII character set + /// + public bool UseCode39ExtendedMode { get; set; } + + /// + /// Don't fail if a Code39 is detected but can't be decoded in extended mode. + /// Return the raw Code39 result instead. Maps to . + /// + public bool UseCode39RelaxedExtendedMode { get; set; } + + /// + /// Assume Code 39 codes employ a check digit. Maps to . + /// + /// + /// true if it should assume a Code 39 check digit; otherwise, false. + /// + public bool AssumeCode39CheckDigit { get; set; } + + /// + /// 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 . + /// + public bool ReturnCodabarStartEnd { get; set; } + + /// + /// 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). + /// + /// + /// true if it should assume GS1; otherwise, false. + /// + public bool AssumeGS1 { get; set; } + + /// + /// Assume MSI codes employ a check digit. Maps to . + /// + /// + /// true if it should assume a MSI check digit; otherwise, false. + /// + public bool AssumeMSICheckDigit { get; set; } + + /// + /// Allowed lengths of encoded data -- reject anything else. Maps to an int[]. + /// + public int[]? AllowedLengths { get; set; } + + /// + /// 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. + /// + 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 } \ No newline at end of file diff --git a/NAPS2.Sdk/Scan/BarcodeDetector.cs b/NAPS2.Sdk/Scan/BarcodeDetector.cs index 8ee54e0c97..e040843161 100644 --- a/NAPS2.Sdk/Scan/BarcodeDetector.cs +++ b/NAPS2.Sdk/Scan/BarcodeDetector.cs @@ -10,7 +10,7 @@ namespace NAPS2.Scan; /// 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) { @@ -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(x => new MemoryImageLuminanceSource(x)) { Options = zxingOptions diff --git a/NAPS2.Sdk/Scan/Internal/RemotePostProcessor.cs b/NAPS2.Sdk/Scan/Internal/RemotePostProcessor.cs index 84ff388efa..38ef786a2d 100644 --- a/NAPS2.Sdk/Scan/Internal/RemotePostProcessor.cs +++ b/NAPS2.Sdk/Scan/Internal/RemotePostProcessor.cs @@ -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) { @@ -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))