/*
* 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);
}
}
}