/* * Copyright 2012 ZXing.Net authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using ZXing.Common; using ZXing.Multi; using ZXing.Multi.QrCode; namespace ZXing { /// /// A smart class to decode the barcode inside a bitmap object /// public class BarcodeReaderGeneric : IBarcodeReaderGeneric, IMultipleBarcodeReaderGeneric { private static readonly Func defaultCreateBinarizer = (luminanceSource) => new HybridBinarizer(luminanceSource); protected static readonly Func defaultCreateRGBLuminanceSource = (rawBytes, width, height, format) => new RGBLuminanceSource(rawBytes, width, height, format); private Reader reader; private readonly Func createRGBLuminanceSource; #if !UNITY private readonly Func createLuminanceSource; #else private readonly Func createLuminanceSource; #endif private readonly Func createBinarizer; private bool usePreviousState; private DecodingOptions options; /// /// Gets or sets the options. /// /// /// The options. /// public DecodingOptions Options { get { return options ?? (options = new DecodingOptions()); } set { options = value; } } /// /// Gets the reader which should be used to find and decode the barcode. /// /// /// The reader. /// protected Reader Reader { get { return reader ?? (reader = new MultiFormatReader()); } } /// /// Gets or sets a method which is called if an important point is found /// /// /// The result point callback. /// public event Action ResultPointFound { add { if (!Options.Hints.ContainsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) { var callback = new ResultPointCallback(OnResultPointFound); Options.Hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK] = callback; } explicitResultPointFound += value; usePreviousState = false; } remove { explicitResultPointFound -= value; if (explicitResultPointFound == null) Options.Hints.Remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK); usePreviousState = false; } } private event Action explicitResultPointFound; /// /// event is executed if a result was found via decode /// public event Action ResultFound; /// /// Gets or sets a flag which cause a deeper look into the bitmap /// /// /// true if [try harder]; otherwise, false. /// [Obsolete("Please use the Options.TryHarder property instead.")] public bool TryHarder { get { return Options.TryHarder; } set { Options.TryHarder = value; } } /// /// Image is a pure monochrome image of a barcode. /// /// /// true if monochrome image of a barcode; otherwise, false. /// [Obsolete("Please use the Options.PureBarcode property instead.")] public bool PureBarcode { get { return Options.PureBarcode; } set { Options.PureBarcode = value; } } /// /// Specifies what character encoding to use when decoding, where applicable (type String) /// /// /// The character set. /// [Obsolete("Please use the Options.CharacterSet property instead.")] public string CharacterSet { get { return Options.CharacterSet; } set { Options.CharacterSet = value; } } /// /// Image is known to be of one of a few possible formats. /// Maps to a {@link java.util.List} of {@link BarcodeFormat}s. /// /// /// The possible formats. /// [Obsolete("Please use the Options.PossibleFormats property instead.")] public IList PossibleFormats { get { return Options.PossibleFormats; } set { Options.PossibleFormats = value; } } /// /// Gets or sets a value indicating whether the image should be automatically rotated. /// Rotation is supported for 90, 180 and 270 degrees /// /// /// true if image should be rotated; otherwise, false. /// public bool AutoRotate { get; set; } /// /// Gets or sets a value indicating whether the image should be automatically inverted /// if no result is found in the original image. /// ATTENTION: Please be carefully because it slows down the decoding process if it is used /// /// /// true if image should be inverted; otherwise, false. /// public bool TryInverted { get; set; } #if !UNITY /// /// Optional: Gets or sets the function to create a luminance source object for a bitmap. /// If null a platform specific default LuminanceSource is used /// /// /// The function to create a luminance source object. /// protected Func CreateLuminanceSource #else /// /// Optional: Gets or sets the function to create a luminance source object for a bitmap. /// If null a platform specific default LuminanceSource is used /// /// /// The function to create a luminance source object. /// protected Func CreateLuminanceSource #endif { get { return createLuminanceSource; } } /// /// Optional: Gets or sets the function to create a binarizer object for a luminance source. /// If null then HybridBinarizer is used /// /// /// The function to create a binarizer object. /// protected Func CreateBinarizer { get { return createBinarizer ?? defaultCreateBinarizer; } } /// /// Initializes a new instance of the class. /// public BarcodeReaderGeneric() : this(new MultiFormatReader(), null, defaultCreateBinarizer) { } /// /// Initializes a new instance of the class. /// /// Sets the reader which should be used to find and decode the barcode. /// If null then MultiFormatReader is used /// Sets the function to create a luminance source object for a bitmap. /// If null, an exception is thrown when Decode is called /// Sets the function to create a binarizer object for a luminance source. /// If null then HybridBinarizer is used public BarcodeReaderGeneric(Reader reader, #if !UNITY Func createLuminanceSource, #else Func createLuminanceSource, #endif Func createBinarizer ) : this(reader, createLuminanceSource, createBinarizer, null) { } /// /// Initializes a new instance of the class. /// /// Sets the reader which should be used to find and decode the barcode. /// If null then MultiFormatReader is used /// Sets the function to create a luminance source object for a bitmap. /// If null, an exception is thrown when Decode is called /// Sets the function to create a binarizer object for a luminance source. /// If null then HybridBinarizer is used /// Sets the function to create a luminance source object for a rgb array. /// If null the RGBLuminanceSource is used. The handler is only called when Decode with a byte[] array is called. public BarcodeReaderGeneric(Reader reader, #if !UNITY Func createLuminanceSource, #else Func createLuminanceSource, #endif Func createBinarizer, Func createRGBLuminanceSource ) { this.reader = reader ?? new MultiFormatReader(); this.createLuminanceSource = createLuminanceSource; this.createBinarizer = createBinarizer ?? defaultCreateBinarizer; this.createRGBLuminanceSource = createRGBLuminanceSource ?? defaultCreateRGBLuminanceSource; Options.ValueChanged += (o, args) => usePreviousState = false; usePreviousState = false; } #if !PORTABLE #if !UNITY /// /// Decodes the specified barcode bitmap. /// /// The barcode bitmap. /// the result data or null public Result Decode(T barcodeBitmap) #else /// /// Decodes the specified barcode bitmap. /// /// raw bytes of the image in RGB order /// /// /// /// the result data or null /// public Result Decode(T rawRGB, int width, int height) #endif { if (CreateLuminanceSource == null) { throw new InvalidOperationException("You have to declare a luminance source delegate."); } #if !UNITY if (barcodeBitmap == null) throw new ArgumentNullException("barcodeBitmap"); #else if (rawRGB == null) throw new ArgumentNullException("rawRGB"); #endif #if !UNITY var luminanceSource = CreateLuminanceSource(barcodeBitmap); #else var luminanceSource = CreateLuminanceSource(rawRGB, width, height); #endif return Decode(luminanceSource); } #endif /// /// Tries to decode a barcode within an image which is given by a luminance source. /// That method gives a chance to prepare a luminance source completely before calling /// the time consuming decoding method. On the other hand there is a chance to create /// a luminance source which is independent from external resources (like Bitmap objects) /// and the decoding call can be made in a background thread. /// /// The luminance source. /// virtual public Result Decode(LuminanceSource luminanceSource) { var result = default(Result); var binarizer = CreateBinarizer(luminanceSource); var binaryBitmap = new BinaryBitmap(binarizer); var multiformatReader = Reader as MultiFormatReader; var rotationCount = 0; var rotationMaxCount = 1; if (AutoRotate) { Options.Hints[DecodeHintType.TRY_HARDER_WITHOUT_ROTATION] = true; rotationMaxCount = 4; } else { if (Options.Hints.ContainsKey(DecodeHintType.TRY_HARDER_WITHOUT_ROTATION)) Options.Hints.Remove(DecodeHintType.TRY_HARDER_WITHOUT_ROTATION); } for (; rotationCount < rotationMaxCount; rotationCount++) { if (usePreviousState && multiformatReader != null) { result = multiformatReader.decodeWithState(binaryBitmap); } else { result = Reader.decode(binaryBitmap, Options.Hints); usePreviousState = true; } if (result == null) { if (TryInverted && luminanceSource.InversionSupported) { binaryBitmap = new BinaryBitmap(CreateBinarizer(luminanceSource.invert())); if (usePreviousState && multiformatReader != null) { result = multiformatReader.decodeWithState(binaryBitmap); } else { result = Reader.decode(binaryBitmap, Options.Hints); usePreviousState = true; } } } if (result != null || !luminanceSource.RotateSupported || !AutoRotate) break; binaryBitmap = new BinaryBitmap(CreateBinarizer(luminanceSource.rotateCounterClockwise())); } if (result != null) { if (result.ResultMetadata == null) { result.putMetadata(ResultMetadataType.ORIENTATION, rotationCount * 90); } else if (!result.ResultMetadata.ContainsKey(ResultMetadataType.ORIENTATION)) { result.ResultMetadata[ResultMetadataType.ORIENTATION] = rotationCount * 90; } else { // perhaps the core decoder rotates the image already (can happen if TryHarder is specified) result.ResultMetadata[ResultMetadataType.ORIENTATION] = ((int)(result.ResultMetadata[ResultMetadataType.ORIENTATION]) + rotationCount * 90) % 360; } OnResultFound(result); } return result; } #if !PORTABLE #if !UNITY /// /// Decodes the specified barcode bitmap. /// /// The barcode bitmap. /// the result data or null public Result[] DecodeMultiple(T barcodeBitmap) #else /// /// Decodes the specified barcode bitmap. /// /// raw bytes of the image in RGB order /// /// /// /// the result data or null /// public Result[] DecodeMultiple(T rawRGB, int width, int height) #endif { if (CreateLuminanceSource == null) { throw new InvalidOperationException("You have to declare a luminance source delegate."); } #if !UNITY if (barcodeBitmap == null) throw new ArgumentNullException("barcodeBitmap"); #else if (rawRGB == null) throw new ArgumentNullException("rawRGB"); #endif #if !UNITY var luminanceSource = CreateLuminanceSource(barcodeBitmap); #else var luminanceSource = CreateLuminanceSource(rawRGB, width, height); #endif return DecodeMultiple(luminanceSource); } #endif /// /// Tries to decode barcodes within an image which is given by a luminance source. /// That method gives a chance to prepare a luminance source completely before calling /// the time consuming decoding method. On the other hand there is a chance to create /// a luminance source which is independent from external resources (like Bitmap objects) /// and the decoding call can be made in a background thread. /// /// The luminance source. /// virtual public Result[] DecodeMultiple(LuminanceSource luminanceSource) { var results = default(Result[]); var binarizer = CreateBinarizer(luminanceSource); var binaryBitmap = new BinaryBitmap(binarizer); var rotationCount = 0; var rotationMaxCount = 1; MultipleBarcodeReader multiReader = null; if (AutoRotate) { Options.Hints[DecodeHintType.TRY_HARDER_WITHOUT_ROTATION] = true; rotationMaxCount = 4; } var formats = Options.PossibleFormats; if (formats != null && formats.Count == 1 && formats.Contains(BarcodeFormat.QR_CODE)) { multiReader = new QRCodeMultiReader(); } else { multiReader = new GenericMultipleBarcodeReader(Reader); } for (; rotationCount < rotationMaxCount; rotationCount++) { results = multiReader.decodeMultiple(binaryBitmap, Options.Hints); if (results == null) { if (TryInverted && luminanceSource.InversionSupported) { binaryBitmap = new BinaryBitmap(CreateBinarizer(luminanceSource.invert())); results = multiReader.decodeMultiple(binaryBitmap, Options.Hints); } } if (results != null || !luminanceSource.RotateSupported || !AutoRotate) break; binaryBitmap = new BinaryBitmap(CreateBinarizer(luminanceSource.rotateCounterClockwise())); } if (results != null) { foreach (var result in results) { if (result.ResultMetadata == null) { result.putMetadata(ResultMetadataType.ORIENTATION, rotationCount * 90); } else if (!result.ResultMetadata.ContainsKey(ResultMetadataType.ORIENTATION)) { result.ResultMetadata[ResultMetadataType.ORIENTATION] = rotationCount * 90; } else { // perhaps the core decoder rotates the image already (can happen if TryHarder is specified) result.ResultMetadata[ResultMetadataType.ORIENTATION] = ((int)(result.ResultMetadata[ResultMetadataType.ORIENTATION]) + rotationCount * 90) % 360; } } OnResultsFound(results); } return results; } protected void OnResultsFound(IEnumerable results) { if (ResultFound != null) { foreach (var result in results) { ResultFound(result); } } } protected void OnResultFound(Result result) { if (ResultFound != null) { ResultFound(result); } } protected void OnResultPointFound(ResultPoint resultPoint) { if (explicitResultPointFound != null) { explicitResultPointFound(resultPoint); } } /// /// Decodes the specified barcode bitmap. /// /// The image as byte[] array. /// The width. /// The height. /// The format. /// /// the result data or null /// public Result Decode(byte[] rawRGB, int width, int height, RGBLuminanceSource.BitmapFormat format) { if (rawRGB == null) throw new ArgumentNullException("rawRGB"); var luminanceSource = createRGBLuminanceSource(rawRGB, width, height, format); return Decode(luminanceSource); } /// /// Decodes the specified barcode bitmap. /// /// The image as byte[] array. /// The width. /// The height. /// The format. /// /// the result data or null /// public Result[] DecodeMultiple(byte[] rawRGB, int width, int height, RGBLuminanceSource.BitmapFormat format) { if (rawRGB == null) throw new ArgumentNullException("rawRGB"); var luminanceSource = createRGBLuminanceSource(rawRGB, width, height, format); return DecodeMultiple(luminanceSource); } } }