490 lines
15 KiB
C#
490 lines
15 KiB
C#
using System;
|
|
using System.Text;
|
|
|
|
using QRCodeImage = ThoughtWorks.QRCode.Codec.Data.QRCodeImage;
|
|
using QRCodeSymbol = ThoughtWorks.QRCode.Codec.Data.QRCodeSymbol;
|
|
using ReedSolomon = ThoughtWorks.QRCode.Codec.Ecc.ReedSolomon;
|
|
using DecodingFailedException = ThoughtWorks.QRCode.ExceptionHandler.DecodingFailedException;
|
|
using InvalidDataBlockException = ThoughtWorks.QRCode.ExceptionHandler.InvalidDataBlockException;
|
|
using SymbolNotFoundException = ThoughtWorks.QRCode.ExceptionHandler.SymbolNotFoundException;
|
|
using Point = ThoughtWorks.QRCode.Geom.Point;
|
|
using QRCodeDataBlockReader = ThoughtWorks.QRCode.Codec.Reader.QRCodeDataBlockReader;
|
|
using QRCodeImageReader = ThoughtWorks.QRCode.Codec.Reader.QRCodeImageReader;
|
|
using DebugCanvas = ThoughtWorks.QRCode.Codec.Util.DebugCanvas;
|
|
using DebugCanvasAdapter = ThoughtWorks.QRCode.Codec.Util.DebugCanvasAdapter;
|
|
using QRCodeUtility = ThoughtWorks.QRCode.Codec.Util.QRCodeUtility;
|
|
|
|
namespace ThoughtWorks.QRCode.Codec
|
|
{
|
|
|
|
public class QRCodeDecoder
|
|
{
|
|
internal QRCodeSymbol qrCodeSymbol;
|
|
internal int numTryDecode;
|
|
internal System.Collections.ArrayList results;
|
|
internal System.Collections.ArrayList lastResults = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
|
|
internal static DebugCanvas canvas;
|
|
internal QRCodeImageReader imageReader;
|
|
internal int numLastCorrections;
|
|
internal bool correctionSucceeded;
|
|
|
|
public static DebugCanvas Canvas
|
|
{
|
|
get
|
|
{
|
|
return QRCodeDecoder.canvas;
|
|
}
|
|
|
|
set
|
|
{
|
|
QRCodeDecoder.canvas = value;
|
|
}
|
|
|
|
}
|
|
virtual internal Point[] AdjustPoints
|
|
{
|
|
get
|
|
{
|
|
// note that adjusts affect dependently
|
|
// i.e. below means (0,0), (2,3), (3,4), (1,2), (2,1), (1,1), (-1,-1)
|
|
|
|
|
|
// Point[] adjusts = {new Point(0,0), new Point(2,3), new Point(1,1),
|
|
// new Point(-2,-2), new Point(1,-1), new Point(-1,0), new Point(-2,-2)};
|
|
System.Collections.ArrayList adjustPoints = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
|
|
for (int d = 0; d < 4; d++)
|
|
adjustPoints.Add(new Point(1, 1));
|
|
int lastX = 0, lastY = 0;
|
|
for (int y = 0; y > - 4; y--)
|
|
{
|
|
for (int x = 0; x > - 4; x--)
|
|
{
|
|
if (x != y && ((x + y) % 2 == 0))
|
|
{
|
|
adjustPoints.Add(new Point(x - lastX, y - lastY));
|
|
lastX = x;
|
|
lastY = y;
|
|
}
|
|
}
|
|
}
|
|
Point[] adjusts = new Point[adjustPoints.Count];
|
|
for (int i = 0; i < adjusts.Length; i++)
|
|
adjusts[i] = (Point) adjustPoints[i];
|
|
return adjusts;
|
|
}
|
|
}
|
|
|
|
internal class DecodeResult
|
|
{
|
|
internal int numCorrections;
|
|
internal bool correctionSucceeded;
|
|
internal sbyte[] decodedBytes;
|
|
private QRCodeDecoder enclosingInstance;
|
|
|
|
public DecodeResult(QRCodeDecoder enclosingInstance, sbyte[] decodedBytes, int numErrors, bool correctionSucceeded)
|
|
{
|
|
InitBlock(enclosingInstance);
|
|
this.decodedBytes = decodedBytes;
|
|
this.numCorrections = numErrors;
|
|
this.correctionSucceeded = correctionSucceeded;
|
|
}
|
|
|
|
private void InitBlock(QRCodeDecoder enclosingInstance)
|
|
{
|
|
this.enclosingInstance = enclosingInstance;
|
|
}
|
|
|
|
virtual public sbyte[] DecodedBytes
|
|
{
|
|
get
|
|
{
|
|
return decodedBytes;
|
|
}
|
|
|
|
}
|
|
virtual public int NumErrors
|
|
{
|
|
get
|
|
{
|
|
return numCorrections;
|
|
}
|
|
|
|
}
|
|
virtual public bool CorrectionSucceeded
|
|
{
|
|
get
|
|
{
|
|
return correctionSucceeded;
|
|
}
|
|
|
|
}
|
|
public QRCodeDecoder Enclosing_Instance
|
|
{
|
|
get
|
|
{
|
|
return enclosingInstance;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public QRCodeDecoder()
|
|
{
|
|
numTryDecode = 0;
|
|
results = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
|
|
QRCodeDecoder.canvas = new DebugCanvasAdapter();
|
|
}
|
|
|
|
/* public byte[] decode(QRCodeImage qrCodeImage) throws DecodingFailedException{
|
|
canvas.println("Decoding started.");
|
|
int[][] intImage = imageToIntArray(qrCodeImage);
|
|
try {
|
|
QRCodeImageReader reader = new QRCodeImageReader();
|
|
qrCodeSymbol = reader.getQRCodeSymbol(intImage);
|
|
} catch (SymbolNotFoundException e) {
|
|
throw new DecodingFailedException(e.getMessage());
|
|
}
|
|
canvas.println("Created QRCode symbol.");
|
|
canvas.println("Reading symbol.");
|
|
canvas.println("Version: " + qrCodeSymbol.getVersionReference());
|
|
canvas.println("Mask pattern: " + qrCodeSymbol.getMaskPatternRefererAsString());
|
|
int[] blocks = qrCodeSymbol.getBlocks();
|
|
canvas.println("Correcting data errors.");
|
|
int[] dataBlocks = correctDataBlocks(blocks);
|
|
try {
|
|
byte[] decodedByteArray =
|
|
getDecodedByteArray(dataBlocks, qrCodeSymbol.getVersion());
|
|
canvas.println("Decoding finished.");
|
|
return decodedByteArray;
|
|
} catch (InvalidDataBlockException e) {
|
|
throw new DecodingFailedException(e.getMessage());
|
|
}
|
|
}*/
|
|
|
|
public virtual sbyte[] decodeBytes(QRCodeImage qrCodeImage)
|
|
{
|
|
Point[] adjusts = AdjustPoints;
|
|
System.Collections.ArrayList results = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
|
|
while (numTryDecode < adjusts.Length)
|
|
{
|
|
try
|
|
{
|
|
DecodeResult result = decode(qrCodeImage, adjusts[numTryDecode]);
|
|
if (result.CorrectionSucceeded)
|
|
{
|
|
return result.DecodedBytes;
|
|
}
|
|
else
|
|
{
|
|
results.Add(result);
|
|
canvas.println("Decoding succeeded but could not correct");
|
|
canvas.println("all errors. Retrying..");
|
|
}
|
|
}
|
|
catch (DecodingFailedException dfe)
|
|
{
|
|
if (dfe.Message.IndexOf("Finder Pattern") >= 0)
|
|
throw dfe;
|
|
}
|
|
finally
|
|
{
|
|
numTryDecode += 1;
|
|
}
|
|
}
|
|
|
|
if (results.Count == 0)
|
|
throw new DecodingFailedException("Give up decoding");
|
|
|
|
int lowestErrorIndex = - 1;
|
|
int lowestError = System.Int32.MaxValue;
|
|
for (int i = 0; i < results.Count; i++)
|
|
{
|
|
DecodeResult result = (DecodeResult) results[i];
|
|
if (result.NumErrors < lowestError)
|
|
{
|
|
lowestError = result.NumErrors;
|
|
lowestErrorIndex = i;
|
|
}
|
|
}
|
|
canvas.println("All trials need for correct error");
|
|
canvas.println("Reporting #" + (lowestErrorIndex) + " that,");
|
|
canvas.println("corrected minimum errors (" + lowestError + ")");
|
|
|
|
canvas.println("Decoding finished.");
|
|
return ((DecodeResult) results[lowestErrorIndex]).DecodedBytes;
|
|
}
|
|
|
|
public virtual String decode(QRCodeImage qrCodeImage, Encoding encoding)
|
|
{
|
|
sbyte[] data = decodeBytes(qrCodeImage);
|
|
byte[] byteData = new byte[data.Length];
|
|
|
|
Buffer.BlockCopy(data, 0, byteData, 0, byteData.Length);
|
|
/*
|
|
char[] decodedData = new char[data.Length];
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
decodedData[i] = Convert.to(data[i]);
|
|
|
|
}
|
|
return new String(decodedData);
|
|
*/
|
|
String decodedData;
|
|
decodedData = encoding.GetString(byteData);
|
|
return decodedData;
|
|
}
|
|
|
|
public virtual String decode(QRCodeImage qrCodeImage)
|
|
{
|
|
sbyte[] data = decodeBytes(qrCodeImage);
|
|
byte[] byteData = new byte[data.Length];
|
|
Buffer.BlockCopy(data, 0, byteData, 0, byteData.Length);
|
|
|
|
Encoding encoding;
|
|
if (QRCodeUtility.IsUnicode(byteData))
|
|
{
|
|
encoding = Encoding.Unicode;
|
|
}
|
|
else
|
|
{
|
|
encoding = Encoding.ASCII;
|
|
}
|
|
String decodedData;
|
|
decodedData = encoding.GetString(byteData);
|
|
return decodedData;
|
|
}
|
|
|
|
internal virtual DecodeResult decode(QRCodeImage qrCodeImage, Point adjust)
|
|
{
|
|
try
|
|
{
|
|
if (numTryDecode == 0)
|
|
{
|
|
canvas.println("Decoding started");
|
|
int[][] intImage = imageToIntArray(qrCodeImage);
|
|
imageReader = new QRCodeImageReader();
|
|
qrCodeSymbol = imageReader.getQRCodeSymbol(intImage);
|
|
}
|
|
else
|
|
{
|
|
canvas.println("--");
|
|
canvas.println("Decoding restarted #" + (numTryDecode));
|
|
qrCodeSymbol = imageReader.getQRCodeSymbolWithAdjustedGrid(adjust);
|
|
}
|
|
}
|
|
catch (SymbolNotFoundException e)
|
|
{
|
|
throw new DecodingFailedException(e.Message);
|
|
}
|
|
canvas.println("Created QRCode symbol.");
|
|
canvas.println("Reading symbol.");
|
|
canvas.println("Version: " + qrCodeSymbol.VersionReference);
|
|
canvas.println("Mask pattern: " + qrCodeSymbol.MaskPatternRefererAsString);
|
|
// blocks contains all (data and RS) blocks in QR Code symbol
|
|
int[] blocks = qrCodeSymbol.Blocks;
|
|
canvas.println("Correcting data errors.");
|
|
// now blocks turn to data blocks (corrected and extracted from original blocks)
|
|
blocks = correctDataBlocks(blocks);
|
|
try
|
|
{
|
|
sbyte[] decodedByteArray = getDecodedByteArray(blocks, qrCodeSymbol.Version, qrCodeSymbol.NumErrorCollectionCode);
|
|
return new DecodeResult(this, decodedByteArray, numLastCorrections, correctionSucceeded);
|
|
}
|
|
catch (InvalidDataBlockException e)
|
|
{
|
|
canvas.println(e.Message);
|
|
throw new DecodingFailedException(e.Message);
|
|
}
|
|
}
|
|
|
|
|
|
internal virtual int[][] imageToIntArray(QRCodeImage image)
|
|
{
|
|
int width = image.Width;
|
|
int height = image.Height;
|
|
int[][] intImage = new int[width][];
|
|
for (int i = 0; i < width; i++)
|
|
{
|
|
intImage[i] = new int[height];
|
|
}
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
intImage[x][y] = image.getPixel(x, y);
|
|
}
|
|
}
|
|
return intImage;
|
|
}
|
|
|
|
internal virtual int[] correctDataBlocks(int[] blocks)
|
|
{
|
|
int numCorrections = 0;
|
|
int dataCapacity = qrCodeSymbol.DataCapacity;
|
|
int[] dataBlocks = new int[dataCapacity];
|
|
int numErrorCollectionCode = qrCodeSymbol.NumErrorCollectionCode;
|
|
int numRSBlocks = qrCodeSymbol.NumRSBlocks;
|
|
int eccPerRSBlock = numErrorCollectionCode / numRSBlocks;
|
|
if (numRSBlocks == 1)
|
|
{
|
|
ReedSolomon corrector = new ReedSolomon(blocks, eccPerRSBlock);
|
|
corrector.correct();
|
|
numCorrections += corrector.NumCorrectedErrors;
|
|
if (numCorrections > 0)
|
|
canvas.println(System.Convert.ToString(numCorrections) + " data errors corrected.");
|
|
else
|
|
canvas.println("No errors found.");
|
|
numLastCorrections = numCorrections;
|
|
correctionSucceeded = corrector.CorrectionSucceeded;
|
|
return blocks;
|
|
}
|
|
else
|
|
{
|
|
//we have to interleave data blocks because symbol has 2 or more RS blocks
|
|
int numLongerRSBlocks = dataCapacity % numRSBlocks;
|
|
if (numLongerRSBlocks == 0)
|
|
{
|
|
//symbol has only 1 type of RS block
|
|
int lengthRSBlock = dataCapacity / numRSBlocks;
|
|
int[][] tmpArray = new int[numRSBlocks][];
|
|
for (int i = 0; i < numRSBlocks; i++)
|
|
{
|
|
tmpArray[i] = new int[lengthRSBlock];
|
|
}
|
|
int[][] RSBlocks = tmpArray;
|
|
//obtain RS blocks
|
|
for (int i = 0; i < numRSBlocks; i++)
|
|
{
|
|
for (int j = 0; j < lengthRSBlock; j++)
|
|
{
|
|
RSBlocks[i][j] = blocks[j * numRSBlocks + i];
|
|
}
|
|
ReedSolomon corrector = new ReedSolomon(RSBlocks[i], eccPerRSBlock);
|
|
corrector.correct();
|
|
numCorrections += corrector.NumCorrectedErrors;
|
|
correctionSucceeded = corrector.CorrectionSucceeded;
|
|
}
|
|
//obtain only data part
|
|
int p = 0;
|
|
for (int i = 0; i < numRSBlocks; i++)
|
|
{
|
|
for (int j = 0; j < lengthRSBlock - eccPerRSBlock; j++)
|
|
{
|
|
dataBlocks[p++] = RSBlocks[i][j];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//symbol has 2 types of RS blocks
|
|
int lengthShorterRSBlock = dataCapacity / numRSBlocks;
|
|
int lengthLongerRSBlock = dataCapacity / numRSBlocks + 1;
|
|
int numShorterRSBlocks = numRSBlocks - numLongerRSBlocks;
|
|
int[][] tmpArray2 = new int[numShorterRSBlocks][];
|
|
for (int i2 = 0; i2 < numShorterRSBlocks; i2++)
|
|
{
|
|
tmpArray2[i2] = new int[lengthShorterRSBlock];
|
|
}
|
|
int[][] shorterRSBlocks = tmpArray2;
|
|
int[][] tmpArray3 = new int[numLongerRSBlocks][];
|
|
for (int i3 = 0; i3 < numLongerRSBlocks; i3++)
|
|
{
|
|
tmpArray3[i3] = new int[lengthLongerRSBlock];
|
|
}
|
|
int[][] longerRSBlocks = tmpArray3;
|
|
for (int i = 0; i < numRSBlocks; i++)
|
|
{
|
|
if (i < numShorterRSBlocks)
|
|
{
|
|
//get shorter RS Block(s)
|
|
int mod = 0;
|
|
for (int j = 0; j < lengthShorterRSBlock; j++)
|
|
{
|
|
if (j == lengthShorterRSBlock - eccPerRSBlock)
|
|
mod = numLongerRSBlocks;
|
|
shorterRSBlocks[i][j] = blocks[j * numRSBlocks + i + mod];
|
|
}
|
|
ReedSolomon corrector = new ReedSolomon(shorterRSBlocks[i], eccPerRSBlock);
|
|
corrector.correct();
|
|
numCorrections += corrector.NumCorrectedErrors;
|
|
correctionSucceeded = corrector.CorrectionSucceeded;
|
|
}
|
|
else
|
|
{
|
|
//get longer RS Blocks
|
|
int mod = 0;
|
|
for (int j = 0; j < lengthLongerRSBlock; j++)
|
|
{
|
|
if (j == lengthShorterRSBlock - eccPerRSBlock)
|
|
mod = numShorterRSBlocks;
|
|
longerRSBlocks[i - numShorterRSBlocks][j] = blocks[j * numRSBlocks + i - mod];
|
|
}
|
|
|
|
ReedSolomon corrector = new ReedSolomon(longerRSBlocks[i - numShorterRSBlocks], eccPerRSBlock);
|
|
corrector.correct();
|
|
numCorrections += corrector.NumCorrectedErrors;
|
|
correctionSucceeded = corrector.CorrectionSucceeded;
|
|
}
|
|
}
|
|
int p = 0;
|
|
for (int i = 0; i < numRSBlocks; i++)
|
|
{
|
|
if (i < numShorterRSBlocks)
|
|
{
|
|
for (int j = 0; j < lengthShorterRSBlock - eccPerRSBlock; j++)
|
|
{
|
|
dataBlocks[p++] = shorterRSBlocks[i][j];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int j = 0; j < lengthLongerRSBlock - eccPerRSBlock; j++)
|
|
{
|
|
dataBlocks[p++] = longerRSBlocks[i - numShorterRSBlocks][j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (numCorrections > 0)
|
|
canvas.println(System.Convert.ToString(numCorrections) + " data errors corrected.");
|
|
else
|
|
canvas.println("No errors found.");
|
|
numLastCorrections = numCorrections;
|
|
return dataBlocks;
|
|
}
|
|
}
|
|
|
|
internal virtual sbyte[] getDecodedByteArray(int[] blocks, int version, int numErrorCorrectionCode)
|
|
{
|
|
sbyte[] byteArray;
|
|
QRCodeDataBlockReader reader = new QRCodeDataBlockReader(blocks, version, numErrorCorrectionCode);
|
|
try
|
|
{
|
|
byteArray = reader.DataByte;
|
|
}
|
|
catch (InvalidDataBlockException e)
|
|
{
|
|
throw e;
|
|
}
|
|
return byteArray;
|
|
}
|
|
|
|
internal virtual String getDecodedString(int[] blocks, int version, int numErrorCorrectionCode)
|
|
{
|
|
String dataString = null;
|
|
QRCodeDataBlockReader reader = new QRCodeDataBlockReader(blocks, version, numErrorCorrectionCode);
|
|
try
|
|
{
|
|
dataString = reader.DataString;
|
|
}
|
|
catch (System.IndexOutOfRangeException e)
|
|
{
|
|
throw new InvalidDataBlockException(e.Message);
|
|
}
|
|
return dataString;
|
|
}
|
|
|
|
|
|
}
|
|
} |