feat(apk): scanner le QR du poste depuis l'app (ZXing.Delphi, Apache 2.0)
Le scanner du systeme demandait avec quoi ouvrir le lien. L'app ouvre la camera arriere, decode une image sur quatre et remplit URL + jeton des qu'un lien pmserver://sync est lu. Rien n'est enregistre avant Save. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Vendored
+564
@@ -0,0 +1,564 @@
|
||||
{
|
||||
* Copyright 2008 ZXing 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.
|
||||
|
||||
* Original Author: bbrown@google.com (Brian Brown)
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.Datamatrix.Internal.BitMatrixParser;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.QrCode.Internal.FormatInformation,
|
||||
ZXing.Common.Detector.MathUtils,
|
||||
ZXing.Datamatrix.Internal.Version;
|
||||
|
||||
type
|
||||
TBitMatrixParser = class sealed
|
||||
private
|
||||
mappingBitMatrix: TBitMatrix;
|
||||
readMappingMatrix: TBitMatrix;
|
||||
Fversion: TVersion;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Reads a bit of the mapping matrix accounting for boundary wrapping.</p>
|
||||
///
|
||||
/// <param name="row">Row to read in the mapping matrix</param>
|
||||
/// <param name="column">Column to read in the mapping matrix</param>
|
||||
/// <param name="numRows">Number of rows in the mapping matrix</param>
|
||||
/// <param name="numColumns">Number of columns in the mapping matrix</param>
|
||||
/// <returns>value of the given bit in the mapping matrix</returns>
|
||||
/// </summary>
|
||||
function readModule(row, column, numRows, numColumns: Integer): Boolean;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Reads the 8 bits of the standard Utah-shaped pattern.</p>
|
||||
///
|
||||
/// <p>See ISO 16022:2006, 5.8.1 Figure 6</p>
|
||||
///
|
||||
/// <param name="row">Current row in the mapping matrix, anchored at the 8th bit (LSB) of the pattern</param>
|
||||
/// <param name="column">Current column in the mapping matrix, anchored at the 8th bit (LSB) of the pattern</param>
|
||||
/// <param name="numRows">Number of rows in the mapping matrix</param>
|
||||
/// <param name="numColumns">Number of columns in the mapping matrix</param>
|
||||
/// <returns>byte from the utah shape</returns>
|
||||
/// </summary>
|
||||
function readUtah(row, column, numRows, numColumns: Integer): Integer;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Reads the 8 bits of the special corner condition 1.</p>
|
||||
///
|
||||
/// <p>See ISO 16022:2006, Figure F.3</p>
|
||||
///
|
||||
/// <param name="numRows">Number of rows in the mapping matrix</param>
|
||||
/// <param name="numColumns">Number of columns in the mapping matrix</param>
|
||||
/// <returns>byte from the Corner condition 1</returns>
|
||||
/// </summary>
|
||||
function readCorner1(numRows, numColumns: Integer): Integer;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Reads the 8 bits of the special corner condition 2.</p>
|
||||
///
|
||||
/// <p>See ISO 16022:2006, Figure F.4</p>
|
||||
///
|
||||
/// <param name="numRows">Number of rows in the mapping matrix</param>
|
||||
/// <param name="numColumns">Number of columns in the mapping matrix</param>
|
||||
/// <returns>byte from the Corner condition 2</returns>
|
||||
/// </summary>
|
||||
function readCorner2(numRows, numColumns: Integer): Integer;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Reads the 8 bits of the special corner condition 3.</p>
|
||||
///
|
||||
/// <p>See ISO 16022:2006, Figure F.5</p>
|
||||
///
|
||||
/// <param name="numRows">Number of rows in the mapping matrix</param>
|
||||
/// <param name="numColumns">Number of columns in the mapping matrix</param>
|
||||
/// <returns>byte from the Corner condition 3</returns>
|
||||
/// </summary>
|
||||
function readCorner3(numRows, numColumns: Integer): Integer;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Reads the 8 bits of the special corner condition 4.</p>
|
||||
///
|
||||
/// <p>See ISO 16022:2006, Figure F.6</p>
|
||||
///
|
||||
/// <param name="numRows">Number of rows in the mapping matrix</param>
|
||||
/// <param name="numColumns">Number of columns in the mapping matrix</param>
|
||||
/// <returns>byte from the Corner condition 4</returns>
|
||||
/// </summary>
|
||||
function readCorner4(numRows, numColumns: Integer): Integer;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Creates the version object based on the dimension of the original bit matrix from
|
||||
/// the datamatrix code.</p>
|
||||
///
|
||||
/// <p>See ISO 16022:2006 Table 7 - ECC 200 symbol attributes</p>
|
||||
///
|
||||
/// <param name="bitMatrix">Original <see cref="BitMatrix" />including alignment patterns</param>
|
||||
/// <returns><see cref="Version" />encapsulating the Data Matrix Code's "version"</returns>
|
||||
/// <exception cref="FormatException">if the dimensions of the mapping matrix are not valid</exception>
|
||||
/// Data Matrix dimensions.
|
||||
/// </summary>
|
||||
function readVersion(BitMatrix: TBitMatrix): TVersion;
|
||||
public
|
||||
/// <param name="bitMatrix">{@link TBitMatrix} to parse</param>
|
||||
/// <throws>ReaderException if dimension is not >= 21 and 1 mod 4</throws>
|
||||
constructor Create(const BitMatrix: TBitMatrix);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// <p>Extracts the data region from a <see cref="BitMatrix" />that contains
|
||||
/// alignment patterns.</p>
|
||||
///
|
||||
/// <param name="bitMatrix">Original <see cref="BitMatrix" />with alignment patterns</param>
|
||||
/// <returns>BitMatrix that has the alignment patterns removed</returns>
|
||||
/// </summary>
|
||||
function extractDataRegion(BitMatrix: TBitMatrix): TBitMatrix;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Reads the bits in the <see cref="BitMatrix" />representing the mapping matrix (No alignment patterns)
|
||||
/// in the correct order in order to reconstitute the codewords bytes contained within the
|
||||
/// Data Matrix Code.</p>
|
||||
///
|
||||
/// <returns>bytes encoded within the Data Matrix Code</returns>
|
||||
/// <exception cref="FormatException">if the exact number of bytes expected is not read</exception>
|
||||
/// </summary>
|
||||
function readCodewords: TArray<Byte>;
|
||||
destructor Destroy; override;
|
||||
|
||||
property Version: TVersion read Fversion;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TBitMatrixParser }
|
||||
|
||||
constructor TBitMatrixParser.Create(const BitMatrix: TBitMatrix);
|
||||
var
|
||||
dimension: Integer;
|
||||
begin
|
||||
dimension := BitMatrix.Height;
|
||||
if (((dimension >= 8) and (dimension <= 144)) and ((dimension and $01) = 0))
|
||||
then
|
||||
begin
|
||||
self.Fversion := readVersion(BitMatrix);
|
||||
if (self.Version <> nil) then
|
||||
begin
|
||||
self.mappingBitMatrix := extractDataRegion(BitMatrix);
|
||||
self.readMappingMatrix := TBitMatrix.Create(self.mappingBitMatrix.Width,
|
||||
self.mappingBitMatrix.Height)
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
destructor TBitMatrixParser.Destroy;
|
||||
begin
|
||||
self.mappingBitMatrix.Free;
|
||||
self.readMappingMatrix.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
|
||||
|
||||
function TBitMatrixParser.readVersion(BitMatrix: TBitMatrix): TVersion;
|
||||
begin
|
||||
Result := TVersion.getVersionForDimensions(BitMatrix.Height, BitMatrix.Width);
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readCodewords: TArray<Byte>;
|
||||
var
|
||||
resultOffset, row, column, numRows, numColumns: Integer;
|
||||
corner1Read, corner2Read, corner3Read, corner4Read: Boolean;
|
||||
begin
|
||||
Result := TArray<Byte>.Create();
|
||||
SetLength(Result, Version.TotalCodewords);
|
||||
resultOffset := 0;
|
||||
|
||||
row := 4;
|
||||
column := 0;
|
||||
|
||||
numRows := self.mappingBitMatrix.Height;
|
||||
numColumns := self.mappingBitMatrix.Width;
|
||||
|
||||
corner1Read := false;
|
||||
corner2Read := false;
|
||||
corner3Read := false;
|
||||
corner4Read := false;
|
||||
|
||||
// Read all of the codewords
|
||||
while ((row < numRows) or (column < numColumns)) do
|
||||
begin
|
||||
// Check the four corner cases
|
||||
if ((row = numRows) and (column = 0) and (not corner1Read)) then
|
||||
begin
|
||||
Result[resultOffset] := Byte(readCorner1(numRows, numColumns));
|
||||
Inc(resultOffset);
|
||||
Dec(row, 2);
|
||||
Inc(column, 2);
|
||||
corner1Read := true;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if ((row = (numRows - 2)) and (column = 0) and ((numColumns and $03) <> 0)
|
||||
and (not corner2Read)) then
|
||||
begin
|
||||
Result[resultOffset] := Byte(readCorner2(numRows, numColumns));
|
||||
Inc(resultOffset);
|
||||
Dec(row, 2);
|
||||
Inc(column, 2);
|
||||
corner2Read := true;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if ((row = (numRows + 4)) and (column = 2) and
|
||||
((numColumns and $07) = 0) and (not corner3Read)) then
|
||||
begin
|
||||
Result[resultOffset] := Byte(readCorner3(numRows, numColumns));
|
||||
Inc(resultOffset);
|
||||
Dec(row, 2);
|
||||
Inc(column, 2);
|
||||
corner3Read := true;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if ((row = (numRows - 2)) and (column = 0) and
|
||||
((numColumns and $07) = 4) and (not corner4Read)) then
|
||||
begin
|
||||
Result[resultOffset] := Byte(readCorner4(numRows, numColumns));
|
||||
Inc(resultOffset);
|
||||
Dec(row, 2);
|
||||
Inc(column, 2);
|
||||
corner4Read := true;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Sweep upward diagonally to the right
|
||||
while ((row >= 0) and (column < numColumns)) do
|
||||
begin
|
||||
if ((row < numRows) and (column >= 0) and
|
||||
(not readMappingMatrix[column, row])) then
|
||||
begin
|
||||
Result[resultOffset] :=
|
||||
Byte(readUtah(row, column, numRows, numColumns));
|
||||
Inc(resultOffset);
|
||||
end;
|
||||
Dec(row, 2);
|
||||
Inc(column, 2)
|
||||
end;
|
||||
Inc(row);
|
||||
Inc(column, 3);
|
||||
|
||||
// Sweep downward diagonally to the left
|
||||
while ((row < numRows) and (column >= 0)) do
|
||||
begin
|
||||
if ((row >= 0) and (column < numColumns) and
|
||||
(not readMappingMatrix[column, row])) then
|
||||
begin
|
||||
Result[resultOffset] :=
|
||||
Byte(readUtah(row, column, numRows, numColumns));
|
||||
Inc(resultOffset);
|
||||
end;
|
||||
Inc(row, 2);
|
||||
Dec(column, 2)
|
||||
end;
|
||||
Inc(row, 3);
|
||||
Inc(column)
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
if (resultOffset <> Version.TotalCodewords) then
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readModule(row, column, numRows,
|
||||
numColumns: Integer): Boolean;
|
||||
begin
|
||||
// Adjust the row and column indices based on boundary wrapping
|
||||
if (row < 0) then
|
||||
begin
|
||||
Inc(row, numRows);
|
||||
Inc(column, (4 - ((numRows + 4) and $07)))
|
||||
end;
|
||||
if (column < 0) then
|
||||
begin
|
||||
Inc(column, numColumns);
|
||||
Inc(row, (4 - ((numColumns + 4) and $07)))
|
||||
end;
|
||||
readMappingMatrix[column, row] := true;
|
||||
|
||||
Result := mappingBitMatrix[column, row];
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readUtah(row, column, numRows,
|
||||
numColumns: Integer): Integer;
|
||||
var
|
||||
currentByte: Byte;
|
||||
begin
|
||||
currentByte := 0;
|
||||
if (readModule((row - 2), (column - 2), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((row - 2), (column - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((row - 1), (column - 2), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((row - 1), (column - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((row - 1), column, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(row, (column - 2), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(row, (column - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(row, column, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
Result := currentByte;
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readCorner1(numRows, numColumns: Integer): Integer;
|
||||
var
|
||||
currentByte: Byte;
|
||||
begin
|
||||
currentByte := 0;
|
||||
if (readModule((numRows - 1), 0, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((numRows - 1), 1, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((numRows - 1), 2, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 2), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(1, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(2, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(3, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
Result := currentByte;
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readCorner2(numRows, numColumns: Integer): Integer;
|
||||
var
|
||||
currentByte: Byte;
|
||||
begin
|
||||
currentByte := 0;
|
||||
if (readModule((numRows - 3), 0, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((numRows - 2), 0, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((numRows - 1), 0, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 4), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 3), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 2), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(1, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
Result := currentByte;
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readCorner3(numRows, numColumns: Integer): Integer;
|
||||
var
|
||||
currentByte: Byte;
|
||||
begin
|
||||
currentByte := 0;
|
||||
if (readModule((numRows - 1), 0, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((numRows - 1), (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 3), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 2), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(1, (numColumns - 3), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(1, (numColumns - 2), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(1, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
Result := currentByte;
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readCorner4(numRows, numColumns: Integer): Integer;
|
||||
var
|
||||
currentByte: Byte;
|
||||
begin
|
||||
currentByte := 0;
|
||||
if (readModule((numRows - 3), 0, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((numRows - 2), 0, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule((numRows - 1), 0, numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (self.readModule(0, (numColumns - 2), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(0, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(1, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(2, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
currentByte := (currentByte shl 1);
|
||||
if (readModule(3, (numColumns - 1), numRows, numColumns)) then
|
||||
currentByte := (currentByte or 1);
|
||||
|
||||
Result := currentByte;
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.extractDataRegion(BitMatrix: TBitMatrix): TBitMatrix;
|
||||
var
|
||||
i, j, symbolSizeRows, dataRegionSizeRows,
|
||||
dataRegionSizeColumns, numDataRegionsRow, numDataRegionsColumn,
|
||||
sizeDataRegionRow, sizeDataRegionColumn, dataRegionRow, dataRegionRowOffset,
|
||||
dataRegionColumn, dataRegionColumnOffset, readRowOffset, writeRowOffset,
|
||||
readColumnOffset, writeColumnOffset: Integer;
|
||||
bitMatrixWithoutAlignment: TBitMatrix;
|
||||
begin
|
||||
symbolSizeRows := Version.symbolSizeRows;
|
||||
|
||||
if (BitMatrix.Height <> symbolSizeRows) then
|
||||
raise EArgumentException.Create
|
||||
('Dimension of bitMarix must match the version size');
|
||||
|
||||
dataRegionSizeRows := Version.dataRegionSizeRows;
|
||||
dataRegionSizeColumns := Version.dataRegionSizeColumns;
|
||||
|
||||
numDataRegionsRow := (symbolSizeRows div dataRegionSizeRows);
|
||||
numDataRegionsColumn := (Version.symbolSizeColumns div dataRegionSizeColumns);
|
||||
|
||||
sizeDataRegionRow := (numDataRegionsRow * dataRegionSizeRows);
|
||||
sizeDataRegionColumn := (numDataRegionsColumn * dataRegionSizeColumns);
|
||||
|
||||
bitMatrixWithoutAlignment := TBitMatrix.Create(sizeDataRegionColumn,
|
||||
sizeDataRegionRow);
|
||||
|
||||
for dataRegionRow := 0 to Pred(numDataRegionsRow) do
|
||||
begin
|
||||
dataRegionRowOffset := (dataRegionRow * dataRegionSizeRows);
|
||||
for dataRegionColumn := 0 to Pred(numDataRegionsColumn) do
|
||||
begin
|
||||
dataRegionColumnOffset := (dataRegionColumn * dataRegionSizeColumns);
|
||||
for i := 0 to Pred(dataRegionSizeRows) do
|
||||
begin
|
||||
readRowOffset := (dataRegionRow * (dataRegionSizeRows + 2)) + 1 + i;
|
||||
writeRowOffset := (dataRegionRowOffset + i);
|
||||
for j := 0 to Pred(dataRegionSizeColumns) do
|
||||
begin
|
||||
readColumnOffset :=
|
||||
(dataRegionColumn * (dataRegionSizeColumns + 2)) + 1 + j;
|
||||
if (BitMatrix[readColumnOffset, readRowOffset]) then
|
||||
begin
|
||||
writeColumnOffset := (dataRegionColumnOffset + j);
|
||||
bitMatrixWithoutAlignment[writeColumnOffset,
|
||||
writeRowOffset] := true;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := bitMatrixWithoutAlignment;
|
||||
|
||||
end;
|
||||
|
||||
end.
|
||||
Vendored
+179
@@ -0,0 +1,179 @@
|
||||
{
|
||||
* Copyright 2008 ZXing 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.
|
||||
|
||||
* Original Author: bbrown@google.com (Brian Brown)
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.Datamatrix.Internal.DataBlock;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
ZXing.Datamatrix.Internal.Version;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Encapsulates a block of data within a Data Matrix Code. Data Matrix Codes may split their data into
|
||||
/// multiple blocks, each of which is a unit of data and error-correction codewords. Each
|
||||
/// is represented by an instance of this class.</p>
|
||||
/// </summary>
|
||||
TDataBlock = class sealed
|
||||
private
|
||||
Fcodewords: TArray<Byte>;
|
||||
FNumDataCodewords: Integer;
|
||||
public
|
||||
constructor Create(const Acodewords: TArray<Byte>;
|
||||
const ANumDataCodewords: Integer);
|
||||
destructor Destroy;override;
|
||||
|
||||
class function getDataBlocks(rawCodewords: TArray<Byte>;
|
||||
version: TVersion): TArray<TDataBlock>;
|
||||
|
||||
property codewords: TArray<Byte> read Fcodewords;
|
||||
property NumDataCodewords: Integer read FNumDataCodewords;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TDataBlock }
|
||||
|
||||
constructor TDataBlock.Create(const Acodewords: TArray<Byte>;
|
||||
const ANumDataCodewords: Integer);
|
||||
begin
|
||||
FnumDataCodewords := AnumDataCodewords;
|
||||
Fcodewords := Acodewords;
|
||||
end;
|
||||
|
||||
destructor TDataBlock.Destroy;
|
||||
begin
|
||||
Fcodewords := nil;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
class function TDataBlock.getDataBlocks(rawCodewords: TArray<Byte>;
|
||||
version: TVersion): TArray<TDataBlock>;
|
||||
var
|
||||
i, j: Integer;
|
||||
ecBlock : TVersion.TECB;
|
||||
ecBlocks : TVersion.TECBlocks;
|
||||
totalBlocks : Integer;
|
||||
ecBlockArray : TArray<TVersion.TECB>;
|
||||
byteArray : TArray<Byte>;
|
||||
numResultBlocks,
|
||||
numDataCodewords,
|
||||
numBlockCodewords,
|
||||
longerBlocksTotalCodewords,
|
||||
//shorterBlocksTotalCodewords
|
||||
longerBlocksNumDataCodewords,
|
||||
shorterBlocksNumDataCodewords,
|
||||
rawCodewordsOffset,
|
||||
numLongerBlocks,
|
||||
max,
|
||||
iOffset,
|
||||
jOffset : Integer;
|
||||
specialVersion: Boolean;
|
||||
begin
|
||||
// Figure out the number and size of data blocks used by this version
|
||||
ecBlocks := version.ecBlocks;
|
||||
|
||||
// First count the total number of data blocks
|
||||
totalBlocks := 0;
|
||||
ecBlockArray := ecBlocks.ECBBlocksValue;
|
||||
for ecBlock in ecBlockArray do
|
||||
begin
|
||||
inc(totalBlocks, ecBlock.Count)
|
||||
end;
|
||||
|
||||
// Now establish DataBlocks of the appropriate size and number of data codewords
|
||||
result := TArray<TDataBlock>.Create();
|
||||
SetLength(result, totalBlocks);
|
||||
numResultBlocks := 0;
|
||||
|
||||
for ecBlock in ecBlockArray do
|
||||
begin
|
||||
for i := 0 to Pred(ecBlock.Count) do
|
||||
begin
|
||||
numDataCodewords := ecBlock.DataCodewords;
|
||||
numBlockCodewords := (ecBlocks.ECCodewords + numDataCodewords);
|
||||
byteArray := TArray<Byte>.Create();
|
||||
SetLength(byteArray, numBlockCodewords);
|
||||
result[numResultBlocks] := TDataBlock.Create(byteArray, numDataCodewords);
|
||||
Inc(numResultBlocks);
|
||||
end
|
||||
end;
|
||||
|
||||
// All blocks have the same amount of data, except that the last n
|
||||
// (where n may be 0) have 1 less byte. Figure out where these start.
|
||||
// TODO: There is only one case where there is a difference for Data Matrix for size 144
|
||||
longerBlocksTotalCodewords := Length(result[0].codewords);
|
||||
//shorterBlocksTotalCodewords := (longerBlocksTotalCodewords - 1);
|
||||
|
||||
longerBlocksNumDataCodewords := (longerBlocksTotalCodewords - ecBlocks.ECCodewords);
|
||||
shorterBlocksNumDataCodewords := (longerBlocksNumDataCodewords - 1);
|
||||
// The last elements of result may be 1 element shorter for 144 matrix
|
||||
// first fill out as many elements as all of them have minus 1
|
||||
rawCodewordsOffset := 0;
|
||||
for i := 0 to Pred(shorterBlocksNumDataCodewords) do
|
||||
begin
|
||||
for j := 0 to Pred(numResultBlocks) do
|
||||
begin
|
||||
result[j].codewords[i] := rawCodewords[rawCodewordsOffset];
|
||||
Inc(rawCodewordsOffset);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Fill out the last data block in the longer ones
|
||||
specialVersion := (version.versionNumber = 24);
|
||||
if specialVersion
|
||||
then
|
||||
numLongerBlocks := 8
|
||||
else
|
||||
numLongerBlocks := numResultBlocks;
|
||||
for j := 0 to Pred(numLongerBlocks) do
|
||||
begin
|
||||
result[j].codewords[(longerBlocksNumDataCodewords - 1)] := rawCodewords[rawCodewordsOffset];
|
||||
Inc(rawCodewordsOffset);
|
||||
end;
|
||||
|
||||
// Now add in error correction blocks
|
||||
max := Length(result[0].codewords);
|
||||
for i := longerBlocksNumDataCodewords to Pred(max) do
|
||||
begin
|
||||
for j := 0 to Pred(numResultBlocks) do
|
||||
begin
|
||||
if (specialVersion)
|
||||
then
|
||||
jOffset := (j + 8) mod numResultBlocks
|
||||
else
|
||||
jOffset := j;
|
||||
|
||||
if (specialVersion and (jOffset > 7))
|
||||
then
|
||||
iOffset := (i - 1)
|
||||
else
|
||||
iOffset := i;
|
||||
result[jOffset].codewords[iOffset] := rawCodewords[rawCodewordsOffset];
|
||||
Inc(rawCodewordsOffset);
|
||||
end;
|
||||
end;
|
||||
|
||||
if (not (rawCodewordsOffset = Length(rawCodewords)))
|
||||
then
|
||||
raise EArgumentException.Create('Arguments error');
|
||||
end;
|
||||
|
||||
end.
|
||||
+903
@@ -0,0 +1,903 @@
|
||||
{
|
||||
* Copyright 2008 ZXing 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.
|
||||
|
||||
* Original Authors: bbrown@google.com (Brian Brown) and Sean Owen
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.Datamatrix.Internal.DecodedBitStreamParser;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
ZXing.DecoderResult,
|
||||
ZXing.ByteSegments,
|
||||
ZXing.BitSource;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Data Matrix Codes can encode text as bits in one of several modes, and can use multiple modes
|
||||
/// in one Data Matrix Code. This class decodes the bits back into text.</p>
|
||||
///
|
||||
/// <p>See ISO 16022:2006, 5.2.1 - 5.2.9.2</p>
|
||||
/// </summary>
|
||||
TDecodedBitStreamParser = class abstract
|
||||
type
|
||||
TMode = (PAD_ENCODE, // Not really a mode
|
||||
ASCII_ENCODE, C40_ENCODE, TEXT_ENCODE, ANSIX12_ENCODE, EDIFACT_ENCODE,
|
||||
BASE256_ENCODE);
|
||||
private
|
||||
class var C40_BASIC_SET_CHARS, C40_SHIFT2_SET_CHARS, TEXT_BASIC_SET_CHARS,
|
||||
TEXT_SHIFT2_SET_CHARS, TEXT_SHIFT3_SET_CHARS: TArray<Char>;
|
||||
|
||||
class procedure InitializeClass;
|
||||
class procedure FinalizeClass;
|
||||
|
||||
class function decodeC40Segment(bits: TBitSource;
|
||||
res: TStringBuilder): boolean;
|
||||
class function decodeTextSegment(bits: TBitSource;
|
||||
res: TStringBuilder): boolean;
|
||||
class function decodeAnsiX12Segment(bits: TBitSource;
|
||||
res: TStringBuilder): boolean;
|
||||
class function decodeEdifactSegment(bits: TBitSource;
|
||||
res: TStringBuilder): boolean;
|
||||
class function decodeBase256Segment(bits: TBitSource; res: TStringBuilder;
|
||||
byteSegments:IByteSegments ): boolean;
|
||||
class function decodeAsciiSegment(bits: TBitSource; res: TStringBuilder;
|
||||
resultTrailer: TStringBuilder; var mode: TMode): boolean;
|
||||
class procedure parseTwoBytes(firstByte: Integer; secondByte: Integer;
|
||||
result: TArray<Integer>);
|
||||
class function unrandomize255State(randomizedBase256Codeword: Integer;
|
||||
base256CodewordPosition: Integer): Integer;
|
||||
public
|
||||
class function decode(bytes: TArray<Byte>): TDecoderResult;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TDecodedBitStreamParser }
|
||||
|
||||
class function TDecodedBitStreamParser.decode(bytes: TArray<Byte>)
|
||||
: TDecoderResult;
|
||||
var
|
||||
bits: TBitSource;
|
||||
res, resultTrailer: TStringBuilder;
|
||||
byteSegments: IByteSegments;
|
||||
mode: TMode;
|
||||
|
||||
begin
|
||||
bits := TBitSource.Create(bytes);
|
||||
res := TStringBuilder.Create(100);
|
||||
resultTrailer := TStringBuilder.Create(0);
|
||||
byteSegments := ByteSegmentsCreate;
|
||||
|
||||
try
|
||||
|
||||
byteSegments.Add(TArray<Byte>.Create()); // TODO (KG): Validation
|
||||
mode := TMode.ASCII_ENCODE;
|
||||
while ((mode <> TMode.PAD_ENCODE) and (bits.available() > 0)) do
|
||||
begin
|
||||
if (mode = TMode.ASCII_ENCODE) then
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.decodeAsciiSegment(bits, res,
|
||||
resultTrailer, mode)) then
|
||||
begin
|
||||
result := nil; // this line was totally missing. I assume the correct thing is to set result = nil.
|
||||
exit;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
case mode of
|
||||
TMode.C40_ENCODE:
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.decodeC40Segment(bits, res)) then
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
TMode.TEXT_ENCODE:
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.decodeTextSegment(bits, res)) then
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
TMode.ANSIX12_ENCODE:
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.decodeAnsiX12Segment(bits, res))
|
||||
then
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
TMode.EDIFACT_ENCODE:
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.decodeEdifactSegment(bits, res))
|
||||
then
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
TMode.BASE256_ENCODE:
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.decodeBase256Segment(bits, res,
|
||||
byteSegments)) then
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
else
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
mode := TMode.ASCII_ENCODE;
|
||||
end;
|
||||
end;
|
||||
|
||||
if (resultTrailer.Length > 0) then
|
||||
res.Append(resultTrailer.ToString);
|
||||
|
||||
if (byteSegments.Count = 0) then
|
||||
result := TDecoderResult.Create(bytes, res.ToString, nil, '')
|
||||
else
|
||||
result := TDecoderResult.Create(bytes, res.ToString, byteSegments, '');
|
||||
|
||||
finally
|
||||
bits.Free;
|
||||
res.Free;
|
||||
resultTrailer.Free;
|
||||
//
|
||||
// for i := 0 to byteSegments.Count - 1 do
|
||||
// begin
|
||||
//
|
||||
// end;
|
||||
//
|
||||
// byteSegments.Free;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006, 5.2.7
|
||||
/// </summary>
|
||||
class function TDecodedBitStreamParser.decodeAnsiX12Segment(bits: TBitSource;
|
||||
res: TStringBuilder): boolean;
|
||||
var
|
||||
i: Integer;
|
||||
cValues: TArray<Integer>;
|
||||
firstByte: Integer;
|
||||
cValue: Integer;
|
||||
begin
|
||||
// Three ANSI X12 values are encoded in a 16-bit value as
|
||||
// (1600 * C1) + (40 * C2) + C3 + 1
|
||||
|
||||
cValues := TArray<Integer>.Create(0, 0, 0);
|
||||
while (bits.available() > 0) do
|
||||
begin
|
||||
// If there is only one byte left then it will be encoded as ASCII
|
||||
if (bits.available = 8) then
|
||||
begin
|
||||
result := true;
|
||||
exit;
|
||||
end;
|
||||
firstByte := bits.readBits(8);
|
||||
if (firstByte = 254) then
|
||||
begin
|
||||
// Unlatch codeword
|
||||
result := true;
|
||||
exit;
|
||||
end;
|
||||
|
||||
TDecodedBitStreamParser.parseTwoBytes(firstByte, bits.readBits(8), cValues);
|
||||
|
||||
for i := 0 to 2 do
|
||||
begin
|
||||
cValue := cValues[i];
|
||||
case cValue of
|
||||
0:
|
||||
begin
|
||||
// X12 segment terminator <CR>
|
||||
res.Append(#13);
|
||||
end;
|
||||
1:
|
||||
begin
|
||||
// X12 segment separator *
|
||||
res.Append('*');
|
||||
end;
|
||||
2:
|
||||
begin
|
||||
// X12 sub-element separator >
|
||||
res.Append('>');
|
||||
end;
|
||||
3:
|
||||
begin
|
||||
// space
|
||||
res.Append(' ');
|
||||
end;
|
||||
else
|
||||
begin
|
||||
if (cValue < 14) then
|
||||
// 0 - 9
|
||||
res.Append(Char(cValue + 44))
|
||||
else if (cValue < 40) then
|
||||
// A - Z
|
||||
res.Append(Char(cValue + 51))
|
||||
else
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;;
|
||||
|
||||
result := true;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006, 5.2.3 and Annex C, Table C.2
|
||||
/// </summary>
|
||||
class function TDecodedBitStreamParser.decodeAsciiSegment(bits: TBitSource;
|
||||
res: TStringBuilder; resultTrailer: TStringBuilder; var mode: TMode): boolean;
|
||||
var
|
||||
oneByte: Integer;
|
||||
upperShift: boolean;
|
||||
value: Byte;
|
||||
begin
|
||||
upperShift := false;
|
||||
mode := TMode.ASCII_ENCODE;
|
||||
|
||||
while (bits.available() > 0) do
|
||||
begin
|
||||
oneByte := bits.readBits(8);
|
||||
if (oneByte = 0) then
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte <= 128) then
|
||||
begin
|
||||
// ASCII data (ASCII value + 1)
|
||||
if (upperShift) then
|
||||
begin
|
||||
Inc(oneByte, 128);
|
||||
// upperShift := false;
|
||||
end;
|
||||
res.Append(Char(oneByte - 1));
|
||||
mode := TMode.ASCII_ENCODE;
|
||||
|
||||
result := true;
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 129) then
|
||||
begin
|
||||
// Pad
|
||||
mode := TMode.PAD_ENCODE;
|
||||
result := true;
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte <= 229) then
|
||||
begin
|
||||
// 2-digit data 00-99 (Numeric Value + 130)
|
||||
value := (oneByte - 130);
|
||||
if (value < 10) then
|
||||
// pad with '0' for single digit values
|
||||
res.Append('0');
|
||||
res.Append(value);
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 230) then
|
||||
begin
|
||||
// Latch to C40 encodation
|
||||
mode := TMode.C40_ENCODE;
|
||||
result := true;
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 231) then
|
||||
begin
|
||||
// Latch to Base 256 encodation
|
||||
mode := TMode.BASE256_ENCODE;
|
||||
result := true;
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 232) then
|
||||
begin
|
||||
// FNC1
|
||||
res.Append(Char(29)); // translate as ASCII 29
|
||||
end
|
||||
else
|
||||
begin
|
||||
if ((oneByte = 233) or (oneByte = 234)) then
|
||||
begin
|
||||
// Structured Append, Reader Programming
|
||||
// Ignore these symbols for now
|
||||
// throw EReaderException.Instance;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 235) then
|
||||
begin
|
||||
// Upper Shift (shift to Extended ASCII)
|
||||
upperShift := true;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 236) then
|
||||
begin
|
||||
// 05 Macro
|
||||
res.Append('[)>' + #$001E05 + #$001D);
|
||||
res.Insert(0, #$001E + #$0004);
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 237) then
|
||||
begin
|
||||
// 06 Macro
|
||||
res.Append('[)>' + #$001E06 + #$001D);
|
||||
res.Insert(0, #$001E + #$0004);
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 238) then
|
||||
begin
|
||||
// Latch to ANSI X12 encodation
|
||||
mode := TMode.ANSIX12_ENCODE;
|
||||
result := true;
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 239) then
|
||||
begin
|
||||
// Latch to Text encodation
|
||||
mode := TMode.TEXT_ENCODE;
|
||||
result := true;
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 240) then
|
||||
begin
|
||||
mode := TMode.EDIFACT_ENCODE;
|
||||
result := true;
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (oneByte = 241) then
|
||||
begin
|
||||
// ECI Character
|
||||
// TODO: I think we need to support ECI
|
||||
// throw TReaderException.Instance;
|
||||
// Ignore this symbol for now
|
||||
end
|
||||
else if (oneByte >= 242) then
|
||||
begin
|
||||
// Not to be used in ASCII encodation
|
||||
// ... but work around encoders that end with 254, latch back to ASCII
|
||||
if ((oneByte <> 254) or (bits.available() <> 0)) then
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
mode := TMode.ASCII_ENCODE;
|
||||
result := true;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006, 5.2.9 and Annex B, B.2
|
||||
/// </summary>
|
||||
class function TDecodedBitStreamParser.decodeBase256Segment(bits: TBitSource;
|
||||
res: TStringBuilder; byteSegments: IByteSegments ): boolean;
|
||||
var
|
||||
i, Count, codewordPosition, d1: Integer;
|
||||
bytes: TArray<Byte>;
|
||||
begin
|
||||
// Figure out how long the Base 256 Segment is.
|
||||
codewordPosition := (1 + bits.ByteOffset); // position is 1-indexed
|
||||
d1 := TDecodedBitStreamParser.unrandomize255State(bits.readBits(8),
|
||||
codewordPosition);
|
||||
Inc(codewordPosition);
|
||||
if (d1 = 0) then
|
||||
// Read the remainder of the symbol
|
||||
Count := (bits.available div 8)
|
||||
else if (d1 < 250) then
|
||||
Count := d1
|
||||
else
|
||||
begin
|
||||
Count := ((250 * (d1 - 249)) + TDecodedBitStreamParser.unrandomize255State
|
||||
(bits.readBits(8), codewordPosition));
|
||||
Inc(codewordPosition);
|
||||
end;
|
||||
|
||||
// We're seeing NegativeArraySizeException errors from users.
|
||||
if (Count < 0) then
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
|
||||
bytes := TArray<Byte>.Create();
|
||||
SetLength(bytes, Count);
|
||||
|
||||
for i := 0 to Pred(Count) do
|
||||
begin
|
||||
// Have seen this particular error in the wild, such as at
|
||||
// http://www.bcgen.com/demo/IDAutomationStreamingDataMatrix.aspx?MODE=3&D=Fred&PFMT=3&PT=F&X=0.3&O=0&LM=0.2
|
||||
if (bits.available() < 8) then
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
bytes[i] := Byte(TDecodedBitStreamParser.unrandomize255State
|
||||
(bits.readBits(8), codewordPosition));
|
||||
Inc(codewordPosition);
|
||||
end;
|
||||
byteSegments.Add(bytes);
|
||||
try
|
||||
res.Append(TEncoding.GetEncoding('ISO-8859-1').GetString(bytes))
|
||||
except
|
||||
on uee: Exception do
|
||||
raise EInvalidOpException.Create
|
||||
('Platform does not support required encoding: ' + uee.Message)
|
||||
end;
|
||||
|
||||
result := true;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006, 5.2.5 and Annex C, Table C.1
|
||||
/// </summary>
|
||||
class function TDecodedBitStreamParser.decodeC40Segment(bits: TBitSource;
|
||||
res: TStringBuilder): boolean;
|
||||
var
|
||||
i: Integer;
|
||||
c40char: Char;
|
||||
cValues: TArray<Integer>;
|
||||
upperShift: boolean;
|
||||
shift: Integer;
|
||||
firstByte: Byte;
|
||||
cValue: Integer;
|
||||
begin
|
||||
// Three C40 values are encoded in a 16-bit value as
|
||||
// (1600 * C1) + (40 * C2) + C3 + 1
|
||||
// TODO: The Upper Shift with C40 doesn't work in the 4 value scenario all the time
|
||||
upperShift := false;
|
||||
|
||||
cValues := TArray<Integer>.Create(0, 0, 0);
|
||||
shift := 0;
|
||||
|
||||
while (bits.available() > 0) do
|
||||
begin
|
||||
// If there is only one byte left then it will be encoded as ASCII
|
||||
if (bits.available() = 8) then
|
||||
begin
|
||||
result := true;
|
||||
exit;
|
||||
end;
|
||||
firstByte := bits.readBits(8);
|
||||
if (firstByte = 254) then
|
||||
begin
|
||||
// Unlatch codeword
|
||||
result := true;
|
||||
exit;
|
||||
end;
|
||||
|
||||
TDecodedBitStreamParser.parseTwoBytes(firstByte, bits.readBits(8), cValues);
|
||||
|
||||
for i := 0 to 2 do
|
||||
begin
|
||||
cValue := cValues[i];
|
||||
case shift of
|
||||
0:
|
||||
begin
|
||||
if (cValue < 3) then
|
||||
shift := (cValue + 1)
|
||||
else
|
||||
begin
|
||||
if (cValue < Length(C40_BASIC_SET_CHARS)) then
|
||||
begin
|
||||
c40char := C40_BASIC_SET_CHARS[cValue];
|
||||
if (upperShift) then
|
||||
begin
|
||||
res.Append(Char(c40char) + Char(128));
|
||||
upperShift := false;
|
||||
end
|
||||
else
|
||||
res.Append(c40char);
|
||||
end
|
||||
else
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
1:
|
||||
begin
|
||||
if (upperShift) then
|
||||
begin
|
||||
res.Append(Char(cValue) + Char(128));
|
||||
upperShift := false;
|
||||
end
|
||||
else
|
||||
res.Append(Char(cValue));
|
||||
shift := 0;
|
||||
end;
|
||||
2:
|
||||
begin
|
||||
if (cValue < Length(C40_SHIFT2_SET_CHARS)) then
|
||||
begin
|
||||
c40char := C40_SHIFT2_SET_CHARS[cValue];
|
||||
if (upperShift) then
|
||||
begin
|
||||
res.Append(Char(c40char) + Char(128));
|
||||
upperShift := false;
|
||||
end
|
||||
else
|
||||
res.Append(c40char);
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (cValue = 27) then
|
||||
// FNC1
|
||||
res.Append(Char(29)) // translate as ASCII 29
|
||||
else if (cValue = 30) then
|
||||
// Upper Shift
|
||||
upperShift := true
|
||||
else
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
shift := 0;
|
||||
end;
|
||||
3:
|
||||
begin
|
||||
if (upperShift) then
|
||||
begin
|
||||
res.Append(Char(cValue + 254));
|
||||
upperShift := false;
|
||||
end
|
||||
else
|
||||
res.Append(Char(cValue + 96));
|
||||
shift := 0;
|
||||
end;
|
||||
else
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
result := true;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006, 5.2.8 and Annex C Table C.3
|
||||
/// </summary>
|
||||
class function TDecodedBitStreamParser.decodeEdifactSegment(bits: TBitSource;
|
||||
res: TStringBuilder): boolean;
|
||||
var
|
||||
i: Integer;
|
||||
edifactValue: Integer;
|
||||
bitsLeft: Integer;
|
||||
begin
|
||||
while (bits.available() > 0) do
|
||||
begin
|
||||
// If there is only two or less bytes left then it will be encoded as ASCII
|
||||
if (bits.available() <= 16) then
|
||||
begin
|
||||
result := true;
|
||||
exit;
|
||||
end;
|
||||
|
||||
for i := 0 to 3 do
|
||||
begin
|
||||
edifactValue := bits.readBits(6);
|
||||
|
||||
// Check for the unlatch character
|
||||
if (edifactValue = $1F) then
|
||||
begin
|
||||
// 011111
|
||||
// Read rest of byte, which should be 0, and stop
|
||||
bitsLeft := (8 - bits.BitOffset);
|
||||
if (not bitsLeft = 8) then
|
||||
bits.readBits(bitsLeft);
|
||||
|
||||
result := true;
|
||||
exit;
|
||||
end;
|
||||
|
||||
if ((edifactValue and $20) = 0) then
|
||||
begin
|
||||
// no 1 in the leading (6th) bit
|
||||
edifactValue := (edifactValue or $40);
|
||||
// Add a leading 01 to the 6 bit binary value
|
||||
end;
|
||||
res.Append(Char(edifactValue));
|
||||
end;
|
||||
end;
|
||||
|
||||
result := true;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006, 5.2.6 and Annex C, Table C.2
|
||||
/// </summary>
|
||||
class function TDecodedBitStreamParser.decodeTextSegment(bits: TBitSource;
|
||||
res: TStringBuilder): boolean;
|
||||
var
|
||||
textChar: Char;
|
||||
cValues: TArray<Integer>;
|
||||
upperShift: boolean;
|
||||
i, shift: Integer;
|
||||
firstByte: Integer;
|
||||
cValue: Integer;
|
||||
begin
|
||||
// Three Text values are encoded in a 16-bit value as
|
||||
// (1600 * C1) + (40 * C2) + C3 + 1
|
||||
// TODO: The Upper Shift with Text doesn't work in the 4 value scenario all the time
|
||||
upperShift := false;
|
||||
|
||||
cValues := TArray<Integer>.Create(0, 0, 0);
|
||||
shift := 0;
|
||||
|
||||
while (bits.available() > 0) do
|
||||
begin
|
||||
// If there is only one byte left then it will be encoded as ASCII
|
||||
if (bits.available() = 8) then
|
||||
begin
|
||||
result := true;
|
||||
exit;
|
||||
end;
|
||||
|
||||
firstByte := bits.readBits(8);
|
||||
if (firstByte = 254) then
|
||||
begin
|
||||
// Unlatch codeword
|
||||
result := true;
|
||||
exit;
|
||||
end;
|
||||
|
||||
TDecodedBitStreamParser.parseTwoBytes(firstByte, bits.readBits(8), cValues);
|
||||
|
||||
for i := 0 to 2 do
|
||||
begin
|
||||
cValue := cValues[i];
|
||||
|
||||
case (shift) of
|
||||
0:
|
||||
begin
|
||||
if (cValue < 3) then
|
||||
shift := (cValue + 1)
|
||||
else
|
||||
begin
|
||||
if (cValue < Length(TEXT_BASIC_SET_CHARS)) then
|
||||
begin
|
||||
textChar := TEXT_BASIC_SET_CHARS[cValue];
|
||||
if (upperShift) then
|
||||
begin
|
||||
res.Append(Char(textChar) + Char(128));
|
||||
upperShift := false;
|
||||
end
|
||||
else
|
||||
res.Append(textChar);
|
||||
end
|
||||
else
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
1:
|
||||
begin
|
||||
if (upperShift) then
|
||||
begin
|
||||
res.Append(Char(cValue + 128));
|
||||
upperShift := false;
|
||||
end
|
||||
else
|
||||
res.Append(Char(cValue));
|
||||
shift := 0;
|
||||
end;
|
||||
2:
|
||||
begin
|
||||
// Shift 2 for Text is the same encoding as C40
|
||||
if (cValue < Length(TEXT_SHIFT2_SET_CHARS)) then
|
||||
begin
|
||||
textChar := TEXT_SHIFT2_SET_CHARS[cValue];
|
||||
if (upperShift) then
|
||||
begin
|
||||
res.Append(Char(textChar) + Char(128));
|
||||
upperShift := false;
|
||||
end
|
||||
else
|
||||
res.Append(textChar);
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (cValue = 27) then
|
||||
// FNC1
|
||||
res.Append(Char(29)) // translate as ASCII 29
|
||||
else if (cValue = 30) then
|
||||
// Upper Shift
|
||||
upperShift := true
|
||||
else
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
shift := 0;
|
||||
end;
|
||||
3:
|
||||
begin
|
||||
if (cValue < Length(TEXT_SHIFT3_SET_CHARS)) then
|
||||
begin
|
||||
textChar := TEXT_SHIFT3_SET_CHARS[cValue];
|
||||
if (upperShift) then
|
||||
begin
|
||||
res.Append(Char(textChar) + Char(128));
|
||||
upperShift := false;
|
||||
end
|
||||
else
|
||||
res.Append(textChar);
|
||||
shift := 0;
|
||||
end
|
||||
else
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
else
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
result := true;
|
||||
end;
|
||||
|
||||
class procedure TDecodedBitStreamParser.parseTwoBytes(firstByte: Integer;
|
||||
secondByte: Integer; result: TArray<Integer>);
|
||||
var
|
||||
fullBitValue, temp: Integer;
|
||||
begin
|
||||
fullBitValue := (((firstByte shl 8) + secondByte) - 1);
|
||||
temp := (fullBitValue div $640);
|
||||
result[0] := temp;
|
||||
dec(fullBitValue, (temp * $640));
|
||||
temp := (fullBitValue div 40);
|
||||
result[1] := temp;
|
||||
result[2] := (fullBitValue - (temp * 40))
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006, Annex B, B.2
|
||||
/// </summary>
|
||||
class function TDecodedBitStreamParser.unrandomize255State
|
||||
(randomizedBase256Codeword: Integer;
|
||||
base256CodewordPosition: Integer): Integer;
|
||||
var
|
||||
pseudoRandomNumber: Integer;
|
||||
tempVariable: Integer;
|
||||
begin
|
||||
pseudoRandomNumber := (((149 * base256CodewordPosition) mod 255) + 1);
|
||||
tempVariable := (randomizedBase256Codeword - pseudoRandomNumber);
|
||||
if (tempVariable >= 0) then
|
||||
result := tempVariable
|
||||
else
|
||||
result := (tempVariable + 256);
|
||||
end;
|
||||
|
||||
class procedure TDecodedBitStreamParser.InitializeClass;
|
||||
begin
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006, Annex C Table C.1
|
||||
/// The C40 Basic Character Set (*'s used for placeholders for the shift values)
|
||||
/// </summary>
|
||||
C40_BASIC_SET_CHARS := TArray<Char>.Create('*', '*', '*', ' ', '0', '1', '2',
|
||||
'3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
|
||||
'X', 'Y', 'Z');
|
||||
|
||||
C40_SHIFT2_SET_CHARS := TArray<Char>.Create('!', '"', '#', '$', '%', '&',
|
||||
'''', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?',
|
||||
'@', '[', '\', ']', '^', '_');
|
||||
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006, Annex C Table C.2
|
||||
/// The Text Basic Character Set (*'s used for placeholders for the shift values)
|
||||
/// </summary>
|
||||
TEXT_BASIC_SET_CHARS := TArray<Char>.Create('*', '*', '*', ' ', '0', '1', '2',
|
||||
'3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
|
||||
'x', 'y', 'z');
|
||||
|
||||
// Shift 2 for Text is the same encoding as C40
|
||||
TEXT_SHIFT2_SET_CHARS := C40_SHIFT2_SET_CHARS;
|
||||
|
||||
TEXT_SHIFT3_SET_CHARS := TArray<Char>.Create('`', 'A', 'B', 'C', 'D', 'E',
|
||||
'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z', '{', '|', '}', '~', '');
|
||||
end;
|
||||
|
||||
class procedure TDecodedBitStreamParser.FinalizeClass;
|
||||
begin
|
||||
C40_BASIC_SET_CHARS := nil;
|
||||
C40_SHIFT2_SET_CHARS := nil;
|
||||
TEXT_BASIC_SET_CHARS := nil;
|
||||
TEXT_SHIFT2_SET_CHARS := nil;
|
||||
TEXT_SHIFT3_SET_CHARS := nil;
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TDecodedBitStreamParser.InitializeClass;
|
||||
|
||||
finalization
|
||||
|
||||
TDecodedBitStreamParser.FinalizeClass;
|
||||
|
||||
end.
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
{
|
||||
* Copyright 2008 ZXing 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.
|
||||
|
||||
* Original Author: bbrown@google.com (Brian Brown)
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.Datamatrix.Internal.Decoder;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.Datamatrix.Internal.BitMatrixParser,
|
||||
ZXing.Datamatrix.Internal.DataBlock,
|
||||
ZXing.Common.ReedSolomon.ReedSolomonDecoder,
|
||||
ZXing.Common.ReedSolomon.GenericGF,
|
||||
ZXing.DecoderResult,
|
||||
ZXing.Datamatrix.Internal.DecodedBitStreamParser;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>The main class which implements Data Matrix Code decoding -- as opposed to locating and extracting
|
||||
/// the Data Matrix Code from an image.</p>
|
||||
/// </summary>
|
||||
TDataMatrixDecoder = class sealed
|
||||
private
|
||||
rsDecoder: TReedSolomonDecoder;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
function correctErrors(codewordBytes: TArray<Byte>; numDataCodewords: Integer): boolean;
|
||||
function decode(bits: TBitMatrix): TDecoderResult; overload;
|
||||
function decode(image: TArray < TArray < boolean >> ): TDecoderResult; overload;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TDataMatrixDecoder }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Decoder"/> class.
|
||||
/// </summary>
|
||||
constructor TDataMatrixDecoder.Create;
|
||||
begin
|
||||
rsDecoder := TReedSolomonDecoder.Create(TGenericGF.DATA_MATRIX_FIELD_256);
|
||||
end;
|
||||
|
||||
destructor TDataMatrixDecoder.Destroy;
|
||||
begin
|
||||
FreeAndNil(rsDecoder);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Convenience method that can decode a Data Matrix Code represented as a 2D array of booleans.
|
||||
/// "true" is taken to mean a black module.</p>
|
||||
///
|
||||
/// <param name="image">booleans representing white/black Data Matrix Code modules</param>
|
||||
/// <returns>text and bytes encoded within the Data Matrix Code</returns>
|
||||
/// <exception cref="FormatException">if the Data Matrix Code cannot be decoded</exception>
|
||||
/// <exception cref="ChecksumException">if error correction fails</exception>
|
||||
/// </summary>
|
||||
function TDataMatrixDecoder.decode(image: TArray < TArray < boolean >> ): TDecoderResult;
|
||||
var
|
||||
i, j: Integer;
|
||||
dimension: Integer;
|
||||
bits: TBitMatrix;
|
||||
begin
|
||||
dimension := Length(image);
|
||||
bits := TBitMatrix.Create(dimension);
|
||||
for i := 0 to Pred(dimension) do
|
||||
begin
|
||||
for j := 0 to Pred(dimension) do
|
||||
begin
|
||||
if (image[i][j]) then
|
||||
bits[j, i] := true;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := decode(bits);
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Decodes a Data Matrix Code represented as a <see cref="BitMatrix" />. A 1 or "true" is taken
|
||||
/// to mean a black module.</p>
|
||||
/// </summary>
|
||||
/// <param name="bits">booleans representing white/black Data Matrix Code modules</param>
|
||||
/// <returns>text and bytes encoded within the Data Matrix Code</returns>
|
||||
function TDataMatrixDecoder.decode(bits: TBitMatrix): TDecoderResult;
|
||||
var
|
||||
i, j: Integer;
|
||||
db: TDataBlock;
|
||||
parser: TBitMatrixParser;
|
||||
codewords: TArray<Byte>;
|
||||
dataBlocks: TArray<TDataBlock>;
|
||||
dataBlocksCount, totalBytes: Integer;
|
||||
resultBytes: TArray<Byte>;
|
||||
DataBlock: TDataBlock;
|
||||
codewordBytes: TArray<Byte>;
|
||||
numDataCodewords: Integer;
|
||||
begin
|
||||
// Construct a parser and read version, error-correction level
|
||||
parser := TBitMatrixParser.Create(bits);
|
||||
DataBlock := nil;
|
||||
|
||||
try
|
||||
|
||||
if (parser.Version = nil) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Read codewords
|
||||
codewords := parser.readCodewords;
|
||||
if (codewords = nil) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit;
|
||||
end;
|
||||
// Separate into data blocks
|
||||
dataBlocks := TDataBlock.getDataBlocks(codewords, parser.Version);
|
||||
|
||||
dataBlocksCount := Length(dataBlocks);
|
||||
|
||||
// Count total number of data bytes
|
||||
totalBytes := 0;
|
||||
for db in dataBlocks do
|
||||
begin
|
||||
Inc(totalBytes, db.numDataCodewords)
|
||||
end;
|
||||
resultBytes := TArray<Byte>.Create();
|
||||
SetLength(resultBytes, totalBytes);
|
||||
|
||||
// Error-correct and copy data blocks together into a stream of bytes
|
||||
for j := 0 to Pred(dataBlocksCount) do
|
||||
begin
|
||||
DataBlock := dataBlocks[j];
|
||||
codewordBytes := DataBlock.codewords;
|
||||
numDataCodewords := DataBlock.numDataCodewords;
|
||||
if (not correctErrors(codewordBytes, numDataCodewords)) then
|
||||
begin
|
||||
if Assigned(DataBlock) then
|
||||
DataBlock.Free;
|
||||
DataBlock := nil;
|
||||
resultBytes := nil;
|
||||
codewordBytes := nil;
|
||||
Result := nil;
|
||||
exit;
|
||||
end;
|
||||
for i := 0 to Pred(numDataCodewords) do
|
||||
begin
|
||||
// De-interlace data blocks.
|
||||
resultBytes[(i * dataBlocksCount) + j] := codewordBytes[i];
|
||||
end;
|
||||
|
||||
DataBlock.Free;
|
||||
DataBlock := nil;
|
||||
end;
|
||||
|
||||
// Decode the contents of that stream of bytes
|
||||
Result := TDecodedBitStreamParser.decode(resultBytes);
|
||||
|
||||
finally
|
||||
|
||||
if Assigned(DataBlock) then
|
||||
DataBlock.Free;
|
||||
|
||||
resultBytes := nil;
|
||||
codewordBytes := nil;
|
||||
parser.Free;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
|
||||
/// correct the errors in-place using Reed-Solomon error correction.</p>
|
||||
///
|
||||
/// <param name="codewordBytes">data and error correction codewords</param>
|
||||
/// <param name="numDataCodewords">number of codewords that are data bytes</param>
|
||||
/// </summary>
|
||||
function TDataMatrixDecoder.correctErrors(codewordBytes: TArray<Byte>; numDataCodewords: Integer): boolean;
|
||||
var
|
||||
i, numCodewords, numECCodewords: Integer;
|
||||
codewordsInts: TArray<Integer>;
|
||||
begin
|
||||
numCodewords := Length(codewordBytes);
|
||||
// First read into an array of ints
|
||||
codewordsInts := TArray<Integer>.Create();
|
||||
SetLength(codewordsInts, numCodewords);
|
||||
for i := 0 to Pred(numCodewords) do
|
||||
begin
|
||||
codewordsInts[i] := (codewordBytes[i] and $FF);
|
||||
end;
|
||||
numECCodewords := (Length(codewordBytes) - numDataCodewords);
|
||||
if (not rsDecoder.decode(codewordsInts, numECCodewords)) then
|
||||
begin
|
||||
Result := false;
|
||||
exit;
|
||||
end;
|
||||
// Copy back into array of bytes -- only need to worry about the bytes that were data
|
||||
// We don't care about errors in the error-correction codewords
|
||||
for i := 0 to Pred(numDataCodewords) do
|
||||
begin
|
||||
codewordBytes[i] := Byte(codewordsInts[i]);
|
||||
end;
|
||||
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
end.
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: bbrown@google.com (Brian Brown)
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.Datamatrix.Internal.Version;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.QrCode.Internal.FormatInformation,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// The Version object encapsulates attributes about a particular
|
||||
/// size Data Matrix Code.
|
||||
/// </summary>
|
||||
TVersion = class sealed
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Encapsualtes the parameters for one error-correction block in one symbol version.
|
||||
/// This includes the number of data codewords, and the number of times a block with these
|
||||
/// parameters is used consecutively in the Data Matrix code version's format.</p>
|
||||
/// </summary>
|
||||
TECB = class
|
||||
private
|
||||
Fcount: Integer;
|
||||
FdataCodewords: Integer;
|
||||
public
|
||||
constructor Create(count: Integer; dataCodewords: Integer);
|
||||
|
||||
property count: Integer read Fcount;
|
||||
property dataCodewords: Integer read FdataCodewords;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Encapsulates a set of error-correction blocks in one symbol version. Most versions will
|
||||
/// use blocks of differing sizes within one version, so, this encapsulates the parameters for
|
||||
/// each set of blocks. It also holds the number of error-correction codewords per block since it
|
||||
/// will be the same across all blocks within one version.</p>
|
||||
/// </summary>
|
||||
TECBlocks = class
|
||||
private
|
||||
_ecBlocksValue: TArray<TECB>;
|
||||
_ecCodewords: Integer;
|
||||
public
|
||||
property ECBBlocksValue: TArray<TECB> read _ecBlocksValue;
|
||||
property ECCodewords: Integer read _ecCodewords;
|
||||
|
||||
constructor Create(ECCodewords: Integer; ecBlocks: TECB); overload;
|
||||
constructor Create(ECCodewords: Integer; ecBlocks1: TECB;
|
||||
ecBlocks2: TECB); overload;
|
||||
destructor Destroy; override;
|
||||
end;
|
||||
|
||||
private
|
||||
FdataRegionSizeColumns: Integer;
|
||||
FdataRegionSizeRows: Integer;
|
||||
FecBlocks: TECBlocks;
|
||||
FsymbolSizeColumns: Integer;
|
||||
FsymbolSizeRows: Integer;
|
||||
FtotalCodewords: Integer;
|
||||
FversionNumber: Integer;
|
||||
|
||||
class var buildVersions: TArray<TVersion>;
|
||||
class procedure ClassInit; static;
|
||||
class procedure ClassFinal; static;
|
||||
class function GetBuildVersions: TArray<TVersion>; static;
|
||||
|
||||
constructor Create(versionNumber, symbolSizeRows, symbolSizeColumns,
|
||||
dataRegionSizeRows, dataRegionSizeColumns: Integer; ecBlocks: TECBlocks);
|
||||
|
||||
public
|
||||
destructor Destroy; override;
|
||||
function ToString: string; override;
|
||||
class function getVersionForDimensions(numRows: Integer;
|
||||
numColumns: Integer): TVersion;
|
||||
|
||||
property dataRegionSizeColumns: Integer read FdataRegionSizeColumns;
|
||||
property dataRegionSizeRows: Integer read FdataRegionSizeRows;
|
||||
property ecBlocks: TECBlocks read FecBlocks;
|
||||
property symbolSizeColumns: Integer read FsymbolSizeColumns;
|
||||
property symbolSizeRows: Integer read FsymbolSizeRows;
|
||||
property totalCodewords: Integer read FtotalCodewords;
|
||||
property versionNumber: Integer read FversionNumber;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TVersion.TECB }
|
||||
|
||||
constructor TVersion.TECB.Create(count, dataCodewords: Integer);
|
||||
begin
|
||||
self.Fcount := count;
|
||||
self.FdataCodewords := dataCodewords;
|
||||
end;
|
||||
|
||||
{ TVersion.TECBlocks }
|
||||
|
||||
constructor TVersion.TECBlocks.Create(ECCodewords: Integer; ecBlocks: TECB);
|
||||
begin
|
||||
self._ecCodewords := ECCodewords;
|
||||
self._ecBlocksValue := TArray<TECB>.Create(ecBlocks);
|
||||
end;
|
||||
|
||||
constructor TVersion.TECBlocks.Create(ECCodewords: Integer; ecBlocks1: TECB;
|
||||
ecBlocks2: TECB);
|
||||
begin
|
||||
self._ecCodewords := ECCodewords;
|
||||
self._ecBlocksValue := TArray<TECB>.Create(ecBlocks1, ecBlocks2);
|
||||
end;
|
||||
|
||||
destructor TVersion.TECBlocks.Destroy;
|
||||
var
|
||||
ecb: TECB;
|
||||
i: Integer;
|
||||
|
||||
begin
|
||||
|
||||
for i := 0 to Length(_ecBlocksValue) - 1 do
|
||||
begin
|
||||
ecb := _ecBlocksValue[i];
|
||||
FreeAndNil(ecb);
|
||||
end;
|
||||
|
||||
_ecBlocksValue := nil;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
{ TVersion }
|
||||
|
||||
class procedure TVersion.ClassInit();
|
||||
begin
|
||||
TVersion.buildVersions := TVersion.GetBuildVersions;
|
||||
end;
|
||||
|
||||
class procedure TVersion.ClassFinal();
|
||||
var
|
||||
Version: TVersion;
|
||||
begin
|
||||
|
||||
for Version in TVersion.buildVersions do
|
||||
begin
|
||||
Version.Free;
|
||||
end;
|
||||
|
||||
TVersion.buildVersions := nil;
|
||||
end;
|
||||
|
||||
constructor TVersion.Create(versionNumber, symbolSizeRows, symbolSizeColumns,
|
||||
dataRegionSizeRows, dataRegionSizeColumns: Integer; ecBlocks: TECBlocks);
|
||||
var
|
||||
ecBlock: TECB;
|
||||
total: Integer;
|
||||
ECCodewords: Integer;
|
||||
begin
|
||||
self.FversionNumber := versionNumber;
|
||||
self.FsymbolSizeRows := symbolSizeRows;
|
||||
self.FsymbolSizeColumns := symbolSizeColumns;
|
||||
self.FdataRegionSizeRows := dataRegionSizeRows;
|
||||
self.FdataRegionSizeColumns := dataRegionSizeColumns;
|
||||
self.FecBlocks := ecBlocks;
|
||||
total := 0;
|
||||
ECCodewords := ecBlocks.ECCodewords;
|
||||
|
||||
for ecBlock in ecBlocks.ECBBlocksValue do
|
||||
begin
|
||||
Inc(total, (ecBlock.count * (ecBlock.dataCodewords + ECCodewords)))
|
||||
end;
|
||||
|
||||
self.FtotalCodewords := total;
|
||||
end;
|
||||
|
||||
destructor TVersion.Destroy;
|
||||
begin
|
||||
|
||||
FreeAndNil(FecBlocks);
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// See ISO 16022:2006 5.5.1 Table 7
|
||||
/// </summary>
|
||||
|
||||
class function TVersion.GetBuildVersions: TArray<TVersion>;
|
||||
begin
|
||||
Result := TArray<TVersion>.Create(TVersion.Create(1, 10, 10, 8, 8,
|
||||
TECBlocks.Create(5, TECB.Create(1, 3))), TVersion.Create(2, 12, 12, 10, 10,
|
||||
TECBlocks.Create(7, TECB.Create(1, 5))), TVersion.Create(3, 14, 14, 12, 12,
|
||||
TECBlocks.Create(10, TECB.Create(1, 8))), TVersion.Create(4, $10, $10, 14,
|
||||
14, TECBlocks.Create(12, TECB.Create(1, 12))), TVersion.Create(5, $12, $12,
|
||||
$10, $10, TECBlocks.Create(14, TECB.Create(1, $12))),
|
||||
TVersion.Create(6, 20, 20, $12, $12, TECBlocks.Create($12,
|
||||
TECB.Create(1, $16))), TVersion.Create(7, $16, $16, 20, 20,
|
||||
TECBlocks.Create(20, TECB.Create(1, 30))), TVersion.Create(8, $18, $18, $16,
|
||||
$16, TECBlocks.Create($18, TECB.Create(1, $24))),
|
||||
TVersion.Create(9, $1A, $1A, $18, $18, TECBlocks.Create($1C,
|
||||
TECB.Create(1, $2C))), TVersion.Create(10, $20, $20, 14, 14,
|
||||
TECBlocks.Create($24, TECB.Create(1, $3E))), TVersion.Create(11, $24, $24,
|
||||
$10, $10, TECBlocks.Create($2A, TECB.Create(1, $56))),
|
||||
TVersion.Create(12, 40, 40, $12, $12, TECBlocks.Create($30,
|
||||
TECB.Create(1, $72))), TVersion.Create(13, $2C, $2C, 20, 20,
|
||||
TECBlocks.Create($38, TECB.Create(1, $90))), TVersion.Create(14, $30, $30,
|
||||
$16, $16, TECBlocks.Create($44, TECB.Create(1, $AE))),
|
||||
TVersion.Create(15, $34, $34, $18, $18, TECBlocks.Create($2A,
|
||||
TECB.Create(2, $66))), TVersion.Create($10, $40, $40, 14, 14,
|
||||
TECBlocks.Create($38, TECB.Create(2, 140))), TVersion.Create($11, $48, $48,
|
||||
$10, $10, TECBlocks.Create($24, TECB.Create(4, $5C))),
|
||||
TVersion.Create($12, 80, 80, $12, $12, TECBlocks.Create($30,
|
||||
TECB.Create(4, $72))), TVersion.Create($13, $58, $58, 20, 20,
|
||||
TECBlocks.Create($38, TECB.Create(4, $90))), TVersion.Create(20, $60, $60,
|
||||
$16, $16, TECBlocks.Create($44, TECB.Create(4, $AE))),
|
||||
TVersion.Create($15, $68, $68, $18, $18, TECBlocks.Create($38,
|
||||
TECB.Create(6, $88))), TVersion.Create($16, 120, 120, $12, $12,
|
||||
TECBlocks.Create($44, TECB.Create(6, $AF))), TVersion.Create($17, $84, $84,
|
||||
20, 20, TECBlocks.Create($3E, TECB.Create(8, $A3))),
|
||||
TVersion.Create($18, $90, $90, $16, $16, TECBlocks.Create($3E,
|
||||
TECB.Create(8, $9C), TECB.Create(2, $9B))), TVersion.Create($19, 8, $12, 6,
|
||||
$10, TECBlocks.Create(7, TECB.Create(1, 5))), TVersion.Create($1A, 8, $20,
|
||||
6, 14, TECBlocks.Create(11, TECB.Create(1, 10))),
|
||||
TVersion.Create($1B, 12, $1A, 10, $18, TECBlocks.Create(14,
|
||||
TECB.Create(1, $10))), TVersion.Create($1C, 12, $24, 10, $10,
|
||||
TECBlocks.Create($12, TECB.Create(1, $16))), TVersion.Create($1D, $10, $24,
|
||||
14, $10, TECBlocks.Create($18, TECB.Create(1, $20))),
|
||||
TVersion.Create(30, $10, $30, 14, $16, TECBlocks.Create($1C,
|
||||
TECB.Create(1, $31))));
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Deduces version information from Data Matrix dimensions.</p>
|
||||
///
|
||||
/// <param name="numRows">Number of rows in modules</param>
|
||||
/// <param name="numColumns">Number of columns in modules</param>
|
||||
/// <returns>Version for a Data Matrix Code of those dimensions</returns>
|
||||
/// <exception cref="FormatException">if dimensions do correspond to a valid Data Matrix size</exception>
|
||||
/// </summary>
|
||||
class function TVersion.getVersionForDimensions(numRows: Integer;
|
||||
numColumns: Integer): TVersion;
|
||||
var
|
||||
Version: TVersion;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
if (((numRows and $01) = 0) and ((numColumns and $01) = 0)) then
|
||||
begin
|
||||
for Version in TVersion.buildVersions do
|
||||
begin
|
||||
if ((Version.symbolSizeRows = numRows) and
|
||||
(Version.symbolSizeColumns = numColumns)) then
|
||||
begin
|
||||
Result := Version;
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TVersion.ToString: string;
|
||||
begin
|
||||
Result := versionNumber.ToString();
|
||||
end;
|
||||
|
||||
Initialization
|
||||
|
||||
TVersion.ClassInit();
|
||||
|
||||
Finalization
|
||||
|
||||
TVersion.ClassFinal();
|
||||
|
||||
end.
|
||||
Vendored
+361
@@ -0,0 +1,361 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.BitMatrixParser;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
SysUtils,
|
||||
Generics.Collections,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.QrCode.Internal.Version,
|
||||
ZXing.QrCode.Internal.FormatInformation,
|
||||
ZXing.Common.Detector.MathUtils,
|
||||
ZXing.QrCode.Internal.DataMask;
|
||||
|
||||
type
|
||||
TBitMatrixParser = class
|
||||
private
|
||||
BitMatrix: TBitMatrix;
|
||||
mirrored: boolean;
|
||||
parsedVersion: TVersion;
|
||||
|
||||
function copyBit(const i, j, versionBits: Integer): Integer;
|
||||
public
|
||||
parsedFormatInfo: TFormatInformation;
|
||||
|
||||
constructor Create(const BitMatrix: TBitMatrix);
|
||||
|
||||
/// <param name="bitMatrix">{@link TBitMatrix} to parse</param>
|
||||
/// <throws>ReaderException if dimension is not >= 21 and 1 mod 4</throws>
|
||||
class function createBitMatrixParser(
|
||||
const bitMatrix: TBitMatrix): TBitMatrixParser; static;
|
||||
|
||||
(** Mirror the bit matrix in order to attempt a second reading. *)
|
||||
procedure mirror;
|
||||
|
||||
(**
|
||||
* Revert the mask removal done while reading the code words. The bit matrix should revert to its original state.
|
||||
*)
|
||||
procedure remask;
|
||||
(**
|
||||
* Prepare the parser for a mirrored operation.
|
||||
* This flag has effect only on the {@link #readFormatInformation()} and the
|
||||
* {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the
|
||||
* {@link #mirror()} method should be called.
|
||||
*
|
||||
* @param mirror Whether to read version and format information mirrored.
|
||||
*)
|
||||
procedure setMirror(const mirror: Boolean);
|
||||
|
||||
/// <summary> <p>Reads format information from one of its two locations within the QR Code.</p>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns> {@link FormatInformation} encapsulating the QR Code's format info
|
||||
/// </returns>
|
||||
/// <throws> ReaderException if both format information locations cannot be parsed as </throws>
|
||||
/// <summary> the valid encoding of format information
|
||||
/// </summary>
|
||||
function readFormatInformation: TFormatInformation;
|
||||
|
||||
/// <summary> <p>Reads version information from one of its two locations within the QR Code.</p>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns> {@link Version} encapsulating the QR Code's version
|
||||
/// </returns>
|
||||
/// <throws> ReaderException if both version information locations cannot be parsed as </throws>
|
||||
/// <summary> the valid encoding of version information
|
||||
/// </summary>
|
||||
function readVersion: TVersion;
|
||||
|
||||
// <summary> <p>Reads the bits in the {@link BitMatrix} representing the finder pattern in the
|
||||
// correct order in order to reconstruct the codewords bytes contained within the
|
||||
/// QR Code.</p>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns> bytes encoded within the QR Code
|
||||
/// </returns>
|
||||
/// <throws> ReaderException if the exact number of bytes expected is not read </throws>
|
||||
function readCodewords: TArray<Byte>;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TBitMatrixParser }
|
||||
|
||||
constructor TBitMatrixParser.Create(const bitMatrix: TBitMatrix);
|
||||
begin
|
||||
// Should only be called from createBitMatrixParser with the important checks before
|
||||
Self.bitMatrix := BitMatrix;
|
||||
end;
|
||||
|
||||
class function TBitMatrixParser.createBitMatrixParser(
|
||||
const bitMatrix: TBitMatrix): TBitMatrixParser;
|
||||
var
|
||||
dimension: Integer;
|
||||
begin
|
||||
dimension := BitMatrix.Height;
|
||||
if ((dimension < 21) and ((dimension and $03) <> 1))
|
||||
then
|
||||
Result := nil
|
||||
else
|
||||
Result := TBitMatrixParser.Create(bitMatrix);
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.copyBit(const i, j, versionBits: Integer): Integer;
|
||||
var
|
||||
bit: Boolean;
|
||||
begin
|
||||
if (mirrored)
|
||||
then
|
||||
bit := BitMatrix[j, i]
|
||||
else
|
||||
bit := BitMatrix[i, j];
|
||||
|
||||
if (bit)
|
||||
then
|
||||
Result := ((versionBits shl 1) or $1)
|
||||
else
|
||||
Result := (versionBits shl 1);
|
||||
end;
|
||||
|
||||
procedure TBitMatrixParser.mirror;
|
||||
var
|
||||
x, y: Integer;
|
||||
begin
|
||||
for x := 0 to Pred(bitMatrix.width) do
|
||||
for y := (x + 1) to Pred(bitMatrix.height) do
|
||||
if (bitMatrix[x, y] <> bitMatrix[y, x]) then
|
||||
begin
|
||||
bitMatrix.flip(y, x);
|
||||
bitMatrix.flip(x, y);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readCodewords: TArray<Byte>;
|
||||
var
|
||||
formatInfo: TFormatInformation;
|
||||
dimension, resultOffset,
|
||||
currentByte, bitsRead,
|
||||
j, i, count, col: Integer;
|
||||
functionPattern: TBitMatrix;
|
||||
readingUp: boolean;
|
||||
Version: TVersion;
|
||||
DataMask: TDataMask;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
formatInfo := readFormatInformation;
|
||||
if (formatInfo = nil)
|
||||
then
|
||||
exit;
|
||||
|
||||
Version := readVersion;
|
||||
if (Version = nil)
|
||||
then
|
||||
exit;
|
||||
|
||||
// Get the data mask for the format used in this QR Code. This will exclude
|
||||
// some bits from reading as we wind through the bit matrix.
|
||||
DataMask := TDataMask.forReference(formatInfo.DataMask);
|
||||
dimension := bitMatrix.Height;
|
||||
DataMask.unmaskBitMatrix(BitMatrix, dimension);
|
||||
|
||||
functionPattern := Version.buildFunctionPattern;
|
||||
|
||||
try
|
||||
readingUp := true;
|
||||
Result := TArray<Byte>.Create();
|
||||
SetLength(Result, version.TotalCodewords);
|
||||
resultOffset := 0;
|
||||
currentByte := 0;
|
||||
bitsRead := 0;
|
||||
j := (dimension - 1);
|
||||
// Read columns in pairs, from right to left
|
||||
while ((j > 0)) do
|
||||
begin
|
||||
if (j = 6)
|
||||
then
|
||||
// Skip whole column with vertical alignment pattern;
|
||||
// saves time and makes the other code proceed more cleanly
|
||||
Dec(j);
|
||||
// Read alternatingly from bottom to top then top to bottom
|
||||
for count := 0 to Pred(dimension) do
|
||||
begin
|
||||
if (readingUp)
|
||||
then
|
||||
i := ((dimension - 1) - count)
|
||||
else
|
||||
i := count;
|
||||
for col := 0 to Pred(2) do
|
||||
begin
|
||||
// Ignore bits covered by the function pattern
|
||||
if (not functionPattern[(j - col), i]) then
|
||||
begin
|
||||
// Read a bit
|
||||
Inc(bitsRead);
|
||||
currentByte := (currentByte shl 1);
|
||||
if (bitMatrix[(j - col), i])
|
||||
then
|
||||
currentByte := (currentByte or 1);
|
||||
// If we've made a whole byte, save it off
|
||||
if (bitsRead = 8) then
|
||||
begin
|
||||
// if resultoffset > high(Result) then
|
||||
// raise EProgrammerNotFound.CreateFmt('resultoffset %d> high(Result) %d',[resultOffset,high(Result)])
|
||||
// else if resultoffset < 0 then
|
||||
// raise EProgrammerNotFound.CreateFmt('resultoffset %d<0!',[resultOffset]);
|
||||
|
||||
if (resultoffset>=0) and (resultoffset <= high(Result)) then // minchiata aggiunta da carlo
|
||||
Result[resultOffset] := Byte(currentByte);
|
||||
|
||||
Inc(resultOffset);
|
||||
bitsRead := 0;
|
||||
currentByte := 0;
|
||||
end
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
readingUp := not readingUp; // readingUp = !readingUp; // switch directions
|
||||
Dec(j, 2);
|
||||
end;
|
||||
|
||||
if (resultOffset <> version.TotalCodewords)
|
||||
then
|
||||
Result := nil;
|
||||
finally
|
||||
FreeAndNil(functionPattern);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readFormatInformation: TFormatInformation;
|
||||
var
|
||||
formatInfoBits1,
|
||||
formatInfoBits2,
|
||||
dimension,
|
||||
i, j, jMin: Integer;
|
||||
begin
|
||||
if (Self.parsedFormatInfo <> nil) then
|
||||
begin
|
||||
Result := Self.parsedFormatInfo;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Read top-left format info bits
|
||||
formatInfoBits1 := 0;
|
||||
for i := 0 to Pred(6) do
|
||||
formatInfoBits1 := self.copyBit(i, 8, formatInfoBits1);
|
||||
// .. and skip a bit in the timing pattern ...
|
||||
formatInfoBits1 := self.copyBit(7, 8, formatInfoBits1);
|
||||
formatInfoBits1 := self.copyBit(8, 8, formatInfoBits1);
|
||||
formatInfoBits1 := self.copyBit(8, 7, formatInfoBits1);
|
||||
// .. and skip a bit in the timing pattern ...
|
||||
for j := 5 downto 0 do
|
||||
formatInfoBits1 := self.copyBit(8, j, formatInfoBits1);
|
||||
// Read the top-right/bottom-left pattern too
|
||||
dimension := Self.bitMatrix.Height;
|
||||
formatInfoBits2 := 0;
|
||||
jMin := (dimension - 7);
|
||||
for j := (dimension - 1) downto jMin do
|
||||
formatInfoBits2 := self.copyBit(8, j, formatInfoBits2);
|
||||
for i := (dimension - 8) to Pred(dimension) do
|
||||
formatInfoBits2 := self.copyBit(i, 8, formatInfoBits2);
|
||||
|
||||
parsedFormatInfo := TFormatInformation.DecodeFormatInformation(formatInfoBits1, formatInfoBits2);
|
||||
|
||||
if (parsedFormatInfo <> nil)
|
||||
then
|
||||
Result := Self.parsedFormatInfo
|
||||
else
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
function TBitMatrixParser.readVersion: TVersion;
|
||||
var
|
||||
dimension,
|
||||
provisionalVersion,
|
||||
versionBits,
|
||||
i, j, ijMin: Integer;
|
||||
begin
|
||||
if (Self.parsedVersion <> nil) then
|
||||
begin
|
||||
Result := Self.parsedVersion;
|
||||
exit;
|
||||
end;
|
||||
|
||||
dimension := Self.bitMatrix.Height;
|
||||
provisionalVersion := TMathUtils.Asr((dimension - 17), 2);
|
||||
if (provisionalVersion <= 6) then
|
||||
begin
|
||||
Result := TVersion.getVersionForNumber(provisionalVersion);
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Read top-right version info: 3 wide by 6 tall
|
||||
versionBits := 0;
|
||||
ijMin := (dimension - 11);
|
||||
for j := 5 downto 0 do
|
||||
for i := (dimension - 9) downto ijMin do
|
||||
versionBits := Self.copyBit(i, j, versionBits);
|
||||
|
||||
parsedVersion := TVersion.decodeVersionInformation(versionBits);
|
||||
if ((parsedVersion <> nil) and
|
||||
(parsedVersion.DimensionForVersion = dimension)) then
|
||||
begin
|
||||
Result := parsedVersion;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Hmm, failed. Try bottom left: 6 wide by 3 tall
|
||||
versionBits := 0;
|
||||
for i := 5 downto 0 do
|
||||
for j := (dimension - 9) downto ijMin do
|
||||
versionBits := self.copyBit(i, j, versionBits);
|
||||
|
||||
parsedVersion := TVersion.decodeVersionInformation(versionBits);
|
||||
if ((parsedVersion <> nil) and
|
||||
(parsedVersion.DimensionForVersion = dimension))
|
||||
then
|
||||
Result := self.parsedVersion
|
||||
else
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
procedure TBitMatrixParser.remask;
|
||||
var
|
||||
dimension: Integer;
|
||||
begin
|
||||
if (parsedFormatInfo <> nil) then
|
||||
begin
|
||||
dimension := bitMatrix.Height;
|
||||
TDataMask.forReference(parsedFormatInfo.DataMask)
|
||||
.unmaskBitMatrix(bitMatrix, dimension);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TBitMatrixParser.setMirror(const mirror: Boolean);
|
||||
begin
|
||||
Self.parsedVersion := nil;
|
||||
Self.parsedFormatInfo := nil;
|
||||
Self.mirrored := mirror;
|
||||
end;
|
||||
|
||||
end.
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Ported from ZXING Java Source: www.Redivivus.in (suraj.supekar@redivivus.in)
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.DataBlock;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
ZXing.QrCode.Internal.Version,
|
||||
ZXing.QrCode.Internal.ErrorCorrectionLevel;
|
||||
|
||||
type
|
||||
/// <summary> <p>Encapsulates a block of data within a QR Code. QR Codes may split their data into
|
||||
/// multiple blocks, each of which is a unit of data and error-correction codewords. Each
|
||||
/// is represented by an instance of this class.</p>
|
||||
/// </summary>
|
||||
TDataBlock = class sealed
|
||||
private
|
||||
Fcodewords: TArray<Byte>;
|
||||
FnumDataCodewords: Integer;
|
||||
public
|
||||
constructor Create(const codewords: TArray<Byte>;
|
||||
const NumDataCodewords: Integer);
|
||||
destructor Destroy; override;
|
||||
|
||||
/// <summary> <p>When QR Codes use multiple data blocks, they are actually interleaved.
|
||||
/// That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This
|
||||
/// method will separate the data into original blocks.</p>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="rawCodewords">bytes as read directly from the QR Code
|
||||
/// </param>
|
||||
/// <param name="version">version of the QR Code
|
||||
/// </param>
|
||||
/// <param name="ecLevel">error-correction level of the QR Code
|
||||
/// </param>
|
||||
/// <returns> {@link TDataBlock}s containing original bytes, "de-interleaved" from representation in the
|
||||
/// QR Code
|
||||
/// </returns>
|
||||
class function getDataBlocks(const rawCodewords: TArray<Byte>;
|
||||
const version: TVersion; const ecLevel: TErrorCorrectionLevel): TArray<TDataBlock>; static;
|
||||
|
||||
property codewords : TArray<Byte> read Fcodewords;
|
||||
property numDataCodewords : Integer read FnumDataCodewords;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TDataBlock }
|
||||
|
||||
constructor TDataBlock.Create(const codewords: TArray<Byte>;
|
||||
const numDataCodewords: Integer);
|
||||
begin
|
||||
Fcodewords := codewords;
|
||||
FnumDataCodewords := numDataCodewords;
|
||||
end;
|
||||
|
||||
destructor TDataBlock.Destroy;
|
||||
begin
|
||||
Fcodewords := nil;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
class function TDataBlock.getDataBlocks(const rawCodewords: TArray<Byte>;
|
||||
const version: TVersion; const ecLevel: TErrorCorrectionLevel): TArray<TDataBlock>;
|
||||
var
|
||||
numResultBlocks,
|
||||
shorterBlocksTotalCodewords,
|
||||
longerBlocksStartAt,
|
||||
shorterBlocksNumDataCodewords,
|
||||
max, i, j,
|
||||
totalBlocks,
|
||||
numBlockCodewords,
|
||||
iOffset,
|
||||
rawCodewordsOffset,
|
||||
NumDataCodewords: Integer;
|
||||
ecBlock: Tversion.TECB;
|
||||
ecBlocks: Tversion.TECBlocks;
|
||||
ecBlockArray: TArray<Tversion.TECB>;
|
||||
numBlockCodewordsBytes: TArray<Byte>;
|
||||
|
||||
begin
|
||||
if (Length(rawCodewords) <> Version.TotalCodewords)
|
||||
then
|
||||
raise EArgumentException.Create('Invalid arguments');
|
||||
|
||||
// Figure out the number and size of data blocks used by this version and
|
||||
// error correction level
|
||||
ecBlocks := version.getECBlocksForLevel(ecLevel);
|
||||
|
||||
// First count the total number of data blocks
|
||||
totalBlocks := 0;
|
||||
ecBlockArray := ecBlocks.getECBlocks;
|
||||
|
||||
for ecBlock in ecBlockArray do
|
||||
Inc(totalBlocks, ecBlock.Count);
|
||||
|
||||
// Now establish DataBlocks of the appropriate size and number of data codewords
|
||||
Result := TArray<TDataBlock>.Create();
|
||||
SetLength(result, totalBlocks);
|
||||
numResultBlocks := 0;
|
||||
for ecBlock in ecBlockArray do
|
||||
begin
|
||||
for i := 0 to Pred(ecBlock.count) do
|
||||
begin
|
||||
numDataCodewords := ecBlock.DataCodewords;
|
||||
numBlockCodewords := ecBlocks.ECCodewordsPerBlock + numDataCodewords;
|
||||
|
||||
numBlockCodewordsBytes := TArray<Byte>.Create();
|
||||
SetLength(numBlockCodewordsBytes, numBlockCodewords);
|
||||
|
||||
Result[numResultBlocks] := TDataBlock.Create(numBlockCodewordsBytes, numDataCodewords);
|
||||
Inc(numResultBlocks);
|
||||
end
|
||||
end;
|
||||
|
||||
numBlockCodewordsBytes := nil;
|
||||
// All blocks have the same amount of data, except that the last n
|
||||
// (where n may be 0) have 1 more byte. Figure out where these start.
|
||||
shorterBlocksTotalCodewords := Length(Result[0].codewords);
|
||||
longerBlocksStartAt := Length(result) - 1;
|
||||
while ((longerBlocksStartAt >= 0)) do
|
||||
begin
|
||||
if (Length(Result[longerBlocksStartAt].codewords) = shorterBlocksTotalCodewords)
|
||||
then
|
||||
break;
|
||||
Dec(longerBlocksStartAt);
|
||||
end;
|
||||
Inc(longerBlocksStartAt);
|
||||
|
||||
shorterBlocksNumDataCodewords := (shorterBlocksTotalCodewords - ecBlocks.ECCodewordsPerBlock);
|
||||
// The last elements of result may be 1 element longer;
|
||||
// first fill out as many elements as all of them have
|
||||
rawCodewordsOffset := 0;
|
||||
for i := 0 to Pred(shorterBlocksNumDataCodewords) do
|
||||
begin
|
||||
for j := 0 to Pred(numResultBlocks) do
|
||||
begin
|
||||
Result[j].codewords[i] := rawCodewords[rawCodewordsOffset];
|
||||
Inc(rawCodewordsOffset);
|
||||
end;
|
||||
end;
|
||||
// Fill out the last data block in the longer ones
|
||||
for j := longerBlocksStartAt to Pred(numResultBlocks) do
|
||||
begin
|
||||
result[j].codewords[shorterBlocksNumDataCodewords] :=
|
||||
rawCodewords[rawCodewordsOffset];
|
||||
Inc(rawCodewordsOffset);
|
||||
end;
|
||||
// Now add in error correction blocks
|
||||
max := Length(result[0].codewords);
|
||||
|
||||
for i := shorterBlocksNumDataCodewords to Pred(max) do
|
||||
begin
|
||||
for j := 0 to Pred(numResultBlocks) do
|
||||
begin
|
||||
if (j < longerBlocksStartAt)
|
||||
then
|
||||
iOffset := i
|
||||
else
|
||||
iOffset := i + 1;
|
||||
|
||||
Result[j].codewords[iOffset] := rawCodewords[rawCodewordsOffset];
|
||||
Inc(rawCodewordsOffset);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Ported from ZXING Java Source: www.Redivivus.in (suraj.supekar@redivivus.in)
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.DataMask;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
SysUtils,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
/// <summary> <p>Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations
|
||||
/// of this class can un-mask a raw BitMatrix. For simplicity, they will unmask the entire BitMatrix,
|
||||
/// including areas used for finder patterns, timing patterns, etc. These areas should be unused
|
||||
/// after the point they are unmasked anyway.</p>
|
||||
///
|
||||
/// <p>Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position
|
||||
/// and j is row position. In fact, as the text says, i is row position and j is column position.</p>
|
||||
/// </summary>
|
||||
TDataMask = class abstract
|
||||
private
|
||||
/// <summary> See ISO 18004:2006 6.8.1</summary>
|
||||
class var DATA_MASKS: TArray<TDataMask>;
|
||||
|
||||
class procedure InitializeClass;
|
||||
class procedure FinalizeClass;
|
||||
|
||||
function isMasked(const i, j: Integer): Boolean; virtual; abstract;
|
||||
public
|
||||
/// <param name="reference">a value between 0 and 7 indicating one of the eight possible
|
||||
/// data mask patterns a QR Code may use
|
||||
/// </param>
|
||||
/// <returns> {@link DataMask} encapsulating the data mask pattern
|
||||
/// </returns>
|
||||
class function forReference(const reference: Integer): TDataMask; static;
|
||||
|
||||
/// <summary> <p>Implementations of this method reverse the data masking process applied to a QR Code and
|
||||
/// make its bits ready to read.</p>
|
||||
/// </summary>
|
||||
/// <param name="bits">representation of QR Code bits
|
||||
/// </param>
|
||||
/// <param name="dimension">dimension of QR Code, represented by bits, being unmasked
|
||||
/// </param>
|
||||
procedure unmaskBitMatrix(const bits: TBitMatrix; const dimension: Integer);
|
||||
end;
|
||||
|
||||
/// <summary> 000: mask bits for which (x + y) mod 2 == 0</summary>
|
||||
TDataMask000 = class(TDataMask)
|
||||
private
|
||||
function isMasked(const i, j: Integer): Boolean; override;
|
||||
end;
|
||||
|
||||
/// <summary> 001: mask bits for which x mod 2 == 0</summary>
|
||||
TDataMask001 = class sealed(TDataMask)
|
||||
private
|
||||
function isMasked(const i, j: Integer): Boolean; override;
|
||||
end;
|
||||
|
||||
/// <summary> 010: mask bits for which y mod 3 == 0</summary>
|
||||
TDataMask010 = class sealed(TDataMask)
|
||||
private
|
||||
function isMasked(const i, j: Integer): Boolean; override;
|
||||
end;
|
||||
|
||||
/// <summary> 011: mask bits for which (x + y) mod 3 == 0</summary>
|
||||
TDataMask011 = class sealed(TDataMask)
|
||||
private
|
||||
function isMasked(const i, j: Integer): Boolean; override;
|
||||
end;
|
||||
|
||||
/// <summary> 100: mask bits for which (x/2 + y/3) mod 2 == 0</summary>
|
||||
TDataMask100 = class sealed(TDataMask)
|
||||
private
|
||||
function isMasked(const i, j: Integer): Boolean; override;
|
||||
end;
|
||||
|
||||
/// <summary> 101: mask bits for which xy mod 2 + xy mod 3 == 0</summary>
|
||||
TDataMask101 = class sealed(TDataMask)
|
||||
private
|
||||
function isMasked(const i, j: Integer): Boolean; override;
|
||||
end;
|
||||
|
||||
/// <summary> 110: mask bits for which (xy mod 2 + xy mod 3) mod 2 == 0</summary>
|
||||
TDataMask110 = class sealed(TDataMask)
|
||||
private
|
||||
function isMasked(const i, j: Integer): Boolean; override;
|
||||
end;
|
||||
|
||||
/// <summary> 111: mask bits for which ((x+y)mod 2 + xy mod 3) mod 2 == 0</summary>
|
||||
TDataMask111 = class sealed(TDataMask)
|
||||
private
|
||||
function isMasked(const i, j: Integer): Boolean; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TDataMask }
|
||||
|
||||
class procedure TDataMask.InitializeClass;
|
||||
begin
|
||||
TDataMask.DATA_MASKS := TArray<TDataMask>.Create(TDataMask000.Create,
|
||||
TDataMask001.Create,
|
||||
TDataMask010.Create,
|
||||
TDataMask011.Create,
|
||||
TDataMask100.Create,
|
||||
TDataMask101.Create,
|
||||
TDataMask110.Create,
|
||||
TDataMask111.Create);
|
||||
end;
|
||||
|
||||
class procedure TDataMask.FinalizeClass;
|
||||
var
|
||||
dam: TDataMask;
|
||||
begin
|
||||
for dam in TDataMask.DATA_MASKS do
|
||||
dam.Free;
|
||||
DATA_MASKS := nil;
|
||||
end;
|
||||
|
||||
class function TDataMask.forReference(const reference: Integer): TDataMask;
|
||||
begin
|
||||
if ((reference < 0) or (reference > 7))
|
||||
then
|
||||
raise EArgumentException.Create('Invalid arguments');
|
||||
|
||||
Result := TDataMask.DATA_MASKS[reference];
|
||||
end;
|
||||
|
||||
procedure TDataMask.unmaskBitMatrix(const bits: TBitMatrix;
|
||||
const dimension: Integer);
|
||||
var
|
||||
i, j: Integer;
|
||||
begin
|
||||
for i := 0 to Pred(dimension) do
|
||||
for j := 0 to Pred(dimension) do
|
||||
if (self.isMasked(i, j))
|
||||
then
|
||||
bits.flip(j, i);
|
||||
end;
|
||||
|
||||
{ TDataMask000 }
|
||||
|
||||
function TDataMask000.isMasked(const i, j: Integer): Boolean;
|
||||
begin
|
||||
Result := ((i + j) and $01) = 0;
|
||||
end;
|
||||
|
||||
{ TDataMask001 }
|
||||
|
||||
function TDataMask001.isMasked(const i, j: Integer): Boolean;
|
||||
begin
|
||||
Result := (i and $01) = 0;
|
||||
end;
|
||||
|
||||
{ TDataMask010 }
|
||||
|
||||
function TDataMask010.isMasked(const i, j: Integer): Boolean;
|
||||
begin
|
||||
Result := (j mod 3) = 0;
|
||||
end;
|
||||
|
||||
{ TDataMask011 }
|
||||
|
||||
function TDataMask011.isMasked(const i, j: Integer): Boolean;
|
||||
begin
|
||||
Result := (((i + j) mod 3) = 0)
|
||||
end;
|
||||
|
||||
{ TDataMask100 }
|
||||
|
||||
function TDataMask100.isMasked(const i, j: Integer): Boolean;
|
||||
begin
|
||||
Result := (( TMathUtils.Asr(UInt32(i),1) + (j div 3)) and $01) = 0;
|
||||
end;
|
||||
|
||||
{ TDataMask101 }
|
||||
|
||||
function TDataMask101.isMasked(const i, j: Integer): Boolean;
|
||||
var
|
||||
temp: Integer;
|
||||
begin
|
||||
temp := (i * j);
|
||||
Result := ((temp and $01) + (temp mod 3)) = 0;
|
||||
end;
|
||||
|
||||
{ TDataMask110 }
|
||||
|
||||
function TDataMask110.isMasked(const i, j: Integer): Boolean;
|
||||
var
|
||||
temp: Integer;
|
||||
begin
|
||||
temp := (i * j);
|
||||
Result := (((temp and $01) + (temp mod 3)) and 1) = 0;
|
||||
end;
|
||||
|
||||
{ TDataMask111 }
|
||||
|
||||
function TDataMask111.isMasked(const i, j: Integer): Boolean;
|
||||
begin
|
||||
Result := ((((i + j) and $01) + ((i * j) mod 3)) and $01) = 0;
|
||||
end;
|
||||
|
||||
initialization
|
||||
TDataMask.InitializeClass;
|
||||
finalization
|
||||
TDataMask.FinalizeClass;
|
||||
end.
|
||||
android-client/ThirdParty/ZXing/2D Barcodes/Decoder/ZXing.QrCode.Internal.DecodedBitStreamParser.pas
Vendored
+587
@@ -0,0 +1,587 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.DecodedBitStreamParser;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
ZXing.BitSource,
|
||||
SysUtils,
|
||||
ZXing.DecodeHintType,
|
||||
Generics.Collections,
|
||||
ZXing.QrCode.Internal.ErrorCorrectionLevel,
|
||||
ZXing.QrCode.Internal.Version,
|
||||
ZXing.DecoderResult,
|
||||
ZXing.CharacterSetECI,
|
||||
ZXing.StringUtils,
|
||||
ZXing.ByteSegments,
|
||||
ZXing.QrCode.Internal.Mode,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
/// <summary> <p>QR Codes can encode text as bits in one of several modes, and can use multiple modes
|
||||
/// in one QR Code. This class decodes the bits back into text.</p>
|
||||
///
|
||||
/// <p>See ISO 18004:2006, 6.4.3 - 6.4.7</p>
|
||||
/// </summary>
|
||||
TDecodedBitStreamParser = class abstract
|
||||
private
|
||||
/// <summary>
|
||||
/// See ISO 18004:2006, 6.4.4 Table 5
|
||||
/// </summary>
|
||||
class var ALPHANUMERIC_CHARS: TArray<Char>;
|
||||
|
||||
const
|
||||
GB2312_SUBSET: Integer = 1;
|
||||
|
||||
class procedure InitializeClass;
|
||||
class procedure FinalizeClass;
|
||||
|
||||
class function decodeAlphanumericSegment(const bits: TBitSource;
|
||||
const res: TStringBuilder; count: Integer;
|
||||
const fc1InEffect: Boolean): Boolean; static;
|
||||
|
||||
class function decodeByteSegment(const bits: TBitSource;
|
||||
const res: TStringBuilder; count: Integer;
|
||||
const currentCharacterSetECI: TCharacterSetECI;
|
||||
const byteSegments: IByteSegments;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): Boolean; static;
|
||||
|
||||
/// <summary>
|
||||
/// See specification GBT 18284-2000
|
||||
/// </summary>
|
||||
/// <param name="bits">The bits.</param>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <param name="count">The count.</param>
|
||||
/// <returns></returns>
|
||||
class function decodeHanziSegment(const bits: TBitSource;
|
||||
const res: TStringBuilder; count: Integer): Boolean; static;
|
||||
|
||||
class function decodeKanjiSegment(const bits: TBitSource;
|
||||
const res: TStringBuilder; count: Integer): boolean; static;
|
||||
|
||||
class function decodeNumericSegment(const bits: TBitSource;
|
||||
const res: TStringBuilder; count: Integer): Boolean; static;
|
||||
|
||||
class function parseECIValue(const bits: TBitSource): Integer; static;
|
||||
class function toAlphaNumericChar(const value: Integer): Char; static;
|
||||
public
|
||||
class function decode(const bytes: TArray<Byte>; const version: TVersion;
|
||||
const ecLevel: TErrorCorrectionLevel;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TDecoderResult; static;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TDecodedBitStreamParser }
|
||||
|
||||
class procedure TDecodedBitStreamParser.InitializeClass;
|
||||
begin
|
||||
ALPHANUMERIC_CHARS := TArray<Char>.Create('0', '1', '2', '3', '4', '5', '6',
|
||||
'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ' ',
|
||||
'$', '%', '*', '+', '-', '.', '/', ':');
|
||||
end;
|
||||
|
||||
class procedure TDecodedBitStreamParser.FinalizeClass;
|
||||
begin
|
||||
ALPHANUMERIC_CHARS := nil;
|
||||
end;
|
||||
|
||||
class function TDecodedBitStreamParser.decode(const bytes: TArray<Byte>;
|
||||
const version: TVersion; const ecLevel: TErrorCorrectionLevel;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TDecoderResult;
|
||||
var
|
||||
Mode: TMode;
|
||||
bits: TBitSource;
|
||||
byteSegments: IByteSegments;
|
||||
fc1InEffect: Boolean;
|
||||
symbolSequence,
|
||||
parityData,
|
||||
count, value,
|
||||
countHanzi, subSet: Integer;
|
||||
currentCharacterSetECI: TCharacterSetECI;
|
||||
ecstring,s: String;
|
||||
res: TStringBuilder;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
bits := TBitSource.Create(bytes);
|
||||
res := TStringBuilder.Create(50);
|
||||
byteSegments := ByteSegmentsCreate();
|
||||
byteSegments.Capacity := 1;
|
||||
symbolSequence := -1;
|
||||
parityData := -1;
|
||||
try
|
||||
try
|
||||
currentCharacterSetECI := nil;
|
||||
fc1InEffect := false;
|
||||
repeat
|
||||
// While still another segment to read...
|
||||
if (bits.available < 4)
|
||||
then
|
||||
// OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
|
||||
Mode := TMode.TERMINATOR
|
||||
else
|
||||
begin
|
||||
try
|
||||
Mode := TMode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
|
||||
except
|
||||
on E: EArgumentException do
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
if (Mode <> TMode.TERMINATOR) then
|
||||
begin
|
||||
if ((Mode = TMode.FNC1_FIRST_POSITION) or
|
||||
(Mode = TMode.FNC1_SECOND_POSITION)) then
|
||||
begin
|
||||
// We do little with FNC1 except alter the parsed result a bit according to the spec
|
||||
fc1InEffect := true;
|
||||
end else
|
||||
begin
|
||||
if (Mode = TMode.STRUCTURED_APPEND) then
|
||||
begin
|
||||
if (bits.available() < 16)
|
||||
then
|
||||
exit;
|
||||
// not really supported; but sequence number and parity is added later to the result metadata
|
||||
// Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
|
||||
symbolSequence := bits.readBits(8);
|
||||
parityData := bits.readBits(8);
|
||||
end else
|
||||
begin
|
||||
if (Mode = TMode.ECI) then
|
||||
begin
|
||||
// Count doesn't apply to ECI
|
||||
value := parseECIValue(bits);
|
||||
currentCharacterSetECI := TCharacterSetECI.getCharacterSetECIByValue(value);
|
||||
if (currentCharacterSetECI = nil)
|
||||
then
|
||||
exit;
|
||||
end else
|
||||
begin
|
||||
// First handle Hanzi mode which does not start with character count
|
||||
if (Mode = TMode.HANZI) then
|
||||
begin
|
||||
// chinese mode contains a sub set indicator right after mode indicator
|
||||
subSet := bits.readBits(4);
|
||||
countHanzi := bits.readBits(Mode.getCharacterCountBits(version));
|
||||
if (subSet = GB2312_SUBSET) then
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.decodeHanziSegment(bits,
|
||||
res, countHanzi))
|
||||
then
|
||||
exit;
|
||||
end;
|
||||
end else
|
||||
begin
|
||||
// "Normal" QR code modes:
|
||||
// How many characters will follow, encoded in this mode?
|
||||
count := bits.readBits(Mode.getCharacterCountBits(version));
|
||||
if (Mode = TMode.NUMERIC) then
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.decodeNumericSegment(bits,
|
||||
res, count))
|
||||
then
|
||||
exit;
|
||||
end else
|
||||
begin
|
||||
if (Mode = TMode.ALPHANUMERIC) then
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.
|
||||
decodeAlphanumericSegment(bits, res, count, fc1InEffect))
|
||||
then
|
||||
exit;
|
||||
end else
|
||||
begin
|
||||
if (Mode = TMode.BYTE) then
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.
|
||||
decodeByteSegment(bits, res, count,
|
||||
currentCharacterSetECI, byteSegments, hints))
|
||||
then
|
||||
exit;
|
||||
end else
|
||||
begin
|
||||
if (Mode <> TMode.KANJI) then
|
||||
begin
|
||||
if (not TDecodedBitStreamParser.
|
||||
decodeKanjiSegment(bits, res, count))
|
||||
then
|
||||
exit;
|
||||
end else exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
until (Mode = TMode.TERMINATOR)
|
||||
except
|
||||
on E: EArgumentException do
|
||||
exit;
|
||||
end;
|
||||
|
||||
if (byteSegments.count = 0)
|
||||
then
|
||||
byteSegments := nil;
|
||||
|
||||
if (ecLevel = nil)
|
||||
then
|
||||
ecstring := ''
|
||||
else
|
||||
ecstring := ecLevel.toString();
|
||||
|
||||
s:= res.toString.Replace(#13+#10, #10).Replace(#10, #13);
|
||||
Result := TDecoderResult.Create(bytes, s, byteSegments, ecstring, symbolSequence,
|
||||
parityData);
|
||||
finally
|
||||
// FreeAndNil(byteSegments);
|
||||
|
||||
res.Clear();
|
||||
|
||||
FreeAndNil(res);
|
||||
FreeAndNil(bits);
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
class function TDecodedBitStreamParser.decodeAlphanumericSegment(
|
||||
const bits: TBitSource; const res: TStringBuilder; count: Integer;
|
||||
const fc1InEffect: Boolean): Boolean;
|
||||
var
|
||||
start, i, nextTwoCharsBits: Integer;
|
||||
charArray: TArray<Char>;
|
||||
begin
|
||||
Result := false;
|
||||
// Read two characters at a time
|
||||
start := res.Length;
|
||||
while ((count > 1)) do
|
||||
begin
|
||||
if (bits.available < 11)
|
||||
then
|
||||
exit;
|
||||
|
||||
nextTwoCharsBits := bits.readBits(11);
|
||||
res.Append(TDecodedBitStreamParser.toAlphaNumericChar
|
||||
((nextTwoCharsBits div 45)));
|
||||
res.Append(TDecodedBitStreamParser.toAlphaNumericChar
|
||||
((nextTwoCharsBits mod 45)));
|
||||
Dec(count, 2);
|
||||
end;
|
||||
|
||||
if (count = 1) then
|
||||
begin
|
||||
// special case: one character left
|
||||
if (bits.available < 6)
|
||||
then
|
||||
exit;
|
||||
res.Append(TDecodedBitStreamParser.toAlphaNumericChar(bits.readBits(6)))
|
||||
end;
|
||||
|
||||
// See section 6.4.8.1, 6.4.8.2
|
||||
if (fc1InEffect) then
|
||||
begin
|
||||
// We need to massage the result a bit if in an FNC1 mode:
|
||||
for i := start to Pred(res.Length) do
|
||||
begin
|
||||
if (res.Chars[i] = '%') then
|
||||
begin
|
||||
if ((i < (res.Length - 1)) and (res.Chars[(i + 1)] = '%'))
|
||||
then
|
||||
// %% is rendered as %
|
||||
res.Remove((i + 1), 1)
|
||||
else
|
||||
begin
|
||||
// In alpha mode, % should be converted to FNC1 separator 0x1D
|
||||
res.Remove(i, 1);
|
||||
charArray := TArray<Char>.Create(Char($1D));
|
||||
res.Insert(i, charArray);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
class function TDecodedBitStreamParser.decodeByteSegment(const bits: TBitSource;
|
||||
const res: TStringBuilder; count: Integer;
|
||||
const currentCharacterSetECI: TCharacterSetECI;
|
||||
const byteSegments: IByteSegments;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): Boolean;
|
||||
var
|
||||
Enc:TEncoding;
|
||||
encodingS, s: string;
|
||||
readBytes: TArray<Byte>;
|
||||
i: Integer;
|
||||
begin
|
||||
Result := false;
|
||||
// Don't crash trying to read more bits than we have available.
|
||||
if ((count shl 3) > bits.available)
|
||||
then
|
||||
exit;
|
||||
|
||||
readBytes := TArray<Byte>.Create();
|
||||
SetLength(readBytes, count);
|
||||
for i := 0 to Pred(count) do
|
||||
readBytes[i] := Byte(bits.readBits(8));
|
||||
|
||||
if (currentCharacterSetECI = nil)
|
||||
then
|
||||
// The spec isn't clear on this mode; see
|
||||
// section 6.4.5: t does not say which encoding to assuming
|
||||
// upon decoding. I have seen ISO-8859-1 used as well as
|
||||
// Shift_JIS -- without anything like an ECI designator to
|
||||
// give a hint.
|
||||
encodingS := TStringUtils.guessEncoding(readBytes, hints)
|
||||
else
|
||||
encodingS := currentCharacterSetECI.EncodingName;
|
||||
|
||||
try
|
||||
enc := TEncoding.GetEncoding(encodingS);
|
||||
try
|
||||
s := enc.GetString(readBytes, 0, Length(readBytes));
|
||||
res.Append(s);
|
||||
finally
|
||||
FreeAndNil(enc);
|
||||
end;
|
||||
except
|
||||
on E: Exception do
|
||||
exit;
|
||||
end;
|
||||
|
||||
byteSegments.Add(readBytes);
|
||||
result := true;
|
||||
end;
|
||||
|
||||
class function TDecodedBitStreamParser.decodeHanziSegment(
|
||||
const bits: TBitSource; const res: TStringBuilder; count: Integer): Boolean;
|
||||
var
|
||||
buffer: TArray<Byte>;
|
||||
offset, twoBytes,
|
||||
assembledTwoBytes: Integer;
|
||||
begin
|
||||
Result := false;
|
||||
// Don't crash trying to read more bits than we have available.
|
||||
if ((count * 13) > bits.available)
|
||||
then
|
||||
exit;
|
||||
|
||||
// Each character will require 2 bytes. Read the characters as 2-byte pairs
|
||||
// and decode as GB2312 afterwards
|
||||
buffer := TArray<Byte>.Create();
|
||||
SetLength(buffer, 2 * count);
|
||||
offset := 0;
|
||||
|
||||
while ((count > 0)) do
|
||||
begin
|
||||
// Each 13 bits encodes a 2-byte character
|
||||
twoBytes := bits.readBits(13);
|
||||
assembledTwoBytes := (((twoBytes div $60) shl 8) or (twoBytes mod $60));
|
||||
if (assembledTwoBytes < $00A00)
|
||||
then
|
||||
// In the 0xA1A1 to 0xAAFE range
|
||||
Inc(assembledTwoBytes, $0A1A1)
|
||||
else
|
||||
// In the 0xB0A1 to 0xFAFE range
|
||||
Inc(assembledTwoBytes, $0A6A1);
|
||||
|
||||
buffer[offset] := Byte(TMathUtils.Asr(assembledTwoBytes, 8) and $FF);
|
||||
buffer[(offset + 1)] := Byte(assembledTwoBytes and $FF);
|
||||
Inc(offset, 2);
|
||||
Dec(count);
|
||||
end;
|
||||
|
||||
try
|
||||
res.Append(Tencoding.GetEncoding(TStringUtils.GB2312).GetString(buffer, 0,
|
||||
Length(buffer)))
|
||||
except
|
||||
on E: Exception do
|
||||
exit;
|
||||
end;
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
class function TDecodedBitStreamParser.decodeKanjiSegment(
|
||||
const bits: TBitSource; const res: TStringBuilder; count: Integer): Boolean;
|
||||
var
|
||||
buffer: TArray<Byte>;
|
||||
twoBytes, offset,
|
||||
assembledTwoBytes: Integer;
|
||||
begin
|
||||
Result := false;
|
||||
// Don't crash trying to read more bits than we have available.
|
||||
if ((count * 13) > bits.available)
|
||||
then
|
||||
exit;
|
||||
|
||||
// Each character will require 2 bytes. Read the characters as 2-byte pairs
|
||||
// and decode as Shift_JIS afterwards
|
||||
buffer := TArray<Byte>.Create();
|
||||
SetLength(buffer, 2 * count);
|
||||
offset := 0;
|
||||
|
||||
while ((count > 0)) do
|
||||
begin
|
||||
// Each 13 bits encodes a 2-byte character
|
||||
twoBytes := bits.readBits(13);
|
||||
assembledTwoBytes := (((twoBytes div $0C0) shl 8) or (twoBytes mod $0C0));
|
||||
if (assembledTwoBytes < $1F00)
|
||||
then
|
||||
// In the 0x8140 to 0x9FFC range
|
||||
Inc(assembledTwoBytes, $08140)
|
||||
else
|
||||
Inc(assembledTwoBytes, $0C140);
|
||||
buffer[offset] := Byte( TMathUtils.Asr(assembledTwoBytes,8));
|
||||
buffer[(offset + 1)] := Byte(assembledTwoBytes);
|
||||
Inc(offset, 2);
|
||||
Dec(count);
|
||||
end;
|
||||
|
||||
try
|
||||
res.Append(Tencoding.GetEncoding(TStringUtils.SHIFT_JIS).GetString(buffer,
|
||||
0, Length(buffer)))
|
||||
except
|
||||
on E: Exception do
|
||||
exit;
|
||||
end;
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
class function TDecodedBitStreamParser.decodeNumericSegment(
|
||||
const bits: TBitSource; const res: TStringBuilder; count: Integer): Boolean;
|
||||
var
|
||||
threeDigitsBits,
|
||||
twoDigitsBits,
|
||||
digitBits: Integer;
|
||||
begin
|
||||
Result := false;
|
||||
// Read three digits at a time
|
||||
while ((count >= 3)) do
|
||||
begin
|
||||
if (bits.available < 10)
|
||||
then
|
||||
exit;
|
||||
threeDigitsBits := bits.readBits(10);
|
||||
if (threeDigitsBits >= 1000)
|
||||
then
|
||||
exit;
|
||||
res.Append(TDecodedBitStreamParser.toAlphaNumericChar
|
||||
((threeDigitsBits div 100)));
|
||||
res.Append(TDecodedBitStreamParser.toAlphaNumericChar
|
||||
(((threeDigitsBits div 10) mod 10)));
|
||||
res.Append(TDecodedBitStreamParser.toAlphaNumericChar
|
||||
((threeDigitsBits mod 10)));
|
||||
Dec(count, 3)
|
||||
end;
|
||||
|
||||
if (count = 2) then
|
||||
begin
|
||||
// Two digits left over to read, encoded in 7 bits
|
||||
if (bits.available < 7)
|
||||
then
|
||||
exit;
|
||||
|
||||
twoDigitsBits := bits.readBits(7);
|
||||
if (twoDigitsBits >= 100)
|
||||
then
|
||||
exit;
|
||||
|
||||
res.Append(TDecodedBitStreamParser.toAlphaNumericChar
|
||||
((twoDigitsBits div 10)));
|
||||
res.Append(TDecodedBitStreamParser.toAlphaNumericChar
|
||||
((twoDigitsBits mod 10)));
|
||||
end else
|
||||
begin
|
||||
if (count = 1) then
|
||||
begin
|
||||
// One digit left over to read
|
||||
if (bits.available < 4)
|
||||
then
|
||||
exit;
|
||||
|
||||
digitBits := bits.readBits(4);
|
||||
if (digitBits >= 10)
|
||||
then
|
||||
exit;
|
||||
|
||||
res.Append(TDecodedBitStreamParser.toAlphaNumericChar(digitBits))
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
class function TDecodedBitStreamParser.parseECIValue(
|
||||
const bits: TBitSource): Integer;
|
||||
var
|
||||
firstByte,
|
||||
secondByte,
|
||||
SecondThirdBytes: Integer;
|
||||
begin
|
||||
firstByte := bits.readBits(8);
|
||||
|
||||
if ((firstByte and $80) = 0) then
|
||||
begin
|
||||
// just one byte
|
||||
Result := (firstByte and $7F);
|
||||
exit;
|
||||
end;
|
||||
|
||||
if ((firstByte and $C0) = $80) then
|
||||
begin
|
||||
// two bytes
|
||||
secondByte := bits.readBits(8);
|
||||
Result := (((firstByte and $3F) shl 8) or secondByte);
|
||||
exit;
|
||||
end;
|
||||
|
||||
if ((firstByte and $E0) = $C0) then
|
||||
begin
|
||||
// three bytes
|
||||
SecondThirdBytes := bits.readBits(16);
|
||||
Result := (((firstByte and $1F) shl 16) or SecondThirdBytes);
|
||||
exit;
|
||||
end;
|
||||
|
||||
raise EArgumentException.Create('Bad ECI bits starting with byte ' +
|
||||
firstByte.toString());
|
||||
end;
|
||||
|
||||
class function TDecodedBitStreamParser.toAlphaNumericChar(
|
||||
const value: Integer): Char;
|
||||
begin
|
||||
if (value >= Length(ALPHANUMERIC_CHARS))
|
||||
then
|
||||
raise Exception.Create('Format exception');
|
||||
|
||||
Result := ALPHANUMERIC_CHARS[value];
|
||||
end;
|
||||
|
||||
initialization
|
||||
TDecodedBitStreamParser.InitializeClass;
|
||||
finalization
|
||||
TDecodedBitStreamParser.FinalizeClass;
|
||||
end.
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E.Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.Decoder;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.QrCode.Internal.BitMatrixParser,
|
||||
ZXing.DecoderResult,
|
||||
ZXing.Common.ReedSolomon.ReedSolomonDecoder,
|
||||
ZXing.Common.ReedSolomon.GenericGF,
|
||||
ZXing.QrCode.Internal.QRCodeDecoderMetaData,
|
||||
ZXing.QrCode.Internal.Version,
|
||||
ZXing.QrCode.Internal.FormatInformation,
|
||||
ZXing.QrCode.Internal.ErrorCorrectionLevel,
|
||||
ZXing.QrCode.Internal.DataBlock,
|
||||
ZXing.QrCode.Internal.DecodedBitStreamParser;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>The main class which implements QR Code decoding -- as opposed to locating and extracting
|
||||
/// the QR Code from an image.</p>
|
||||
/// </summary>
|
||||
TQRDecoder = class
|
||||
private
|
||||
rsDecoder: TReedSolomonDecoder;
|
||||
/// <summary>
|
||||
/// <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
|
||||
/// correct the errors in-place using Reed-Solomon error correction.</p>
|
||||
/// </summary>
|
||||
/// <param name="codewordBytes">data and error correction codewords</param>
|
||||
/// <param name="numDataCodewords">number of codewords that are data bytes</param>
|
||||
/// <returns></returns>
|
||||
function correctErrors(const codewordBytes: TArray<Byte>;
|
||||
const numDataCodewords: Integer): Boolean;
|
||||
|
||||
function decode(const parser: TBitMatrixParser;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>)
|
||||
: TDecoderResult; overload;
|
||||
public
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Decoder"/> class.
|
||||
/// </summary>
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Convenience method that can decode a QR Code represented as a 2D array of booleans.
|
||||
/// "true" is taken to mean a black module.</p>
|
||||
/// </summary>
|
||||
/// <param name="image">booleans representing white/black QR Code modules</param>
|
||||
/// <param name="hints">decoding hints that should be used to influence decoding</param>
|
||||
/// <returns>
|
||||
/// text and bytes encoded within the QR Code
|
||||
/// </returns>
|
||||
function decode(const image: TArray<TArray<Boolean>>;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>)
|
||||
: TDecoderResult; overload;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.</p>
|
||||
/// </summary>
|
||||
/// <param name="bits">booleans representing white/black QR Code modules</param>
|
||||
/// <param name="hints">decoding hints that should be used to influence decoding</param>
|
||||
/// <returns>
|
||||
/// text and bytes encoded within the QR Code
|
||||
/// </returns>
|
||||
function decode(const bits: TBitMatrix;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>)
|
||||
: TDecoderResult; overload;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TQRDecoder }
|
||||
constructor TQRDecoder.Create;
|
||||
begin
|
||||
rsDecoder := TReedSolomonDecoder.Create(TGenericGF.QR_CODE_FIELD_256);
|
||||
end;
|
||||
|
||||
destructor TQRDecoder.Destroy;
|
||||
begin
|
||||
FreeAndNil(rsDecoder);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TQRDecoder.correctErrors(const codewordBytes: TArray<Byte>;
|
||||
const numDataCodewords: Integer): Boolean;
|
||||
var
|
||||
i, numCodewords, numECCodewords: Integer;
|
||||
codewordsInts: TArray<Integer>;
|
||||
begin
|
||||
Result := false;
|
||||
|
||||
numCodewords := Length(codewordBytes);
|
||||
// First read into an array of ints
|
||||
codewordsInts := TArray<Integer>.Create();
|
||||
SetLength(codewordsInts, numCodewords);
|
||||
for i := 0 to Pred(numCodewords) do
|
||||
codewordsInts[i] := (codewordBytes[i] and $FF);
|
||||
numECCodewords := Length(codewordBytes) - numDataCodewords;
|
||||
|
||||
if (not rsDecoder.decode(codewordsInts, numECCodewords)) then
|
||||
exit;
|
||||
|
||||
// Copy back into array of bytes -- only need to worry about the bytes that were data
|
||||
// We don't care about errors in the error-correction codewords
|
||||
for i := 0 to Pred(numDataCodewords) do
|
||||
codewordBytes[i] := Byte(codewordsInts[i]);
|
||||
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
function TQRDecoder.decode(const image: TArray<TArray<Boolean>>;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TDecoderResult;
|
||||
var
|
||||
dimension, i, j: Integer;
|
||||
bits: TBitMatrix;
|
||||
begin
|
||||
dimension := Length(image);
|
||||
bits := TBitMatrix.Create(dimension);
|
||||
try
|
||||
for i := 0 to Pred(dimension) do
|
||||
for j := 0 to Pred(dimension) do
|
||||
bits[j, i] := image[i][j];
|
||||
|
||||
Result := decode(bits, hints);
|
||||
finally
|
||||
bits.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TQRDecoder.decode(const bits: TBitMatrix;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TDecoderResult;
|
||||
var
|
||||
parser: TBitMatrixParser;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
// Construct a parser and read version, error-correction level
|
||||
parser := TBitMatrixParser.createBitMatrixParser(bits);
|
||||
if Assigned(parser) then
|
||||
begin
|
||||
try
|
||||
Result := decode(parser, hints);
|
||||
if (Result = nil) then
|
||||
begin
|
||||
// Revert the bit matrix
|
||||
parser.remask;
|
||||
|
||||
// Will be attempting a mirrored reading of the version and format info.
|
||||
parser.setMirror(true);
|
||||
|
||||
// Preemptively read the version.
|
||||
if (parser.readVersion = nil) then
|
||||
exit;
|
||||
|
||||
// Preemptively read the format information.
|
||||
if (parser.readFormatInformation = nil) then
|
||||
exit;
|
||||
|
||||
(*
|
||||
* Since we're here, this means we have successfully detected some kind
|
||||
* of version and format information when mirrored. This is a good sign,
|
||||
* that the QR code may be mirrored, and we should try once more with a
|
||||
* mirrored content.
|
||||
*)
|
||||
// Prepare for a mirrored reading.
|
||||
parser.mirror;
|
||||
|
||||
Result := decode(parser, hints);
|
||||
|
||||
if (Result <> nil) then
|
||||
// Success! Notify the caller that the code was mirrored.
|
||||
Result.Other := TQRCodeDecoderMetaData.Create(true);
|
||||
end;
|
||||
finally
|
||||
parser.Free;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TQRDecoder.decode(const parser: TBitMatrixParser;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TDecoderResult;
|
||||
var
|
||||
DataBlock: TDataBlock;
|
||||
dataBlocks: TArray<TDataBlock>;
|
||||
Version: TVersion;
|
||||
formatInfo: TFormatInformation;
|
||||
ecLevel: TErrorCorrectionLevel;
|
||||
codeWords, resultBytes, codewordBytes: TArray<Byte>;
|
||||
totalBytes, resultOffset, i, numDataCodewords: Integer;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
Version := parser.readVersion;
|
||||
if (Version = nil) then
|
||||
exit;
|
||||
|
||||
formatInfo := parser.readFormatInformation;
|
||||
if (formatInfo = nil) then
|
||||
exit;
|
||||
|
||||
ecLevel := formatInfo.ErrorCorrectionLevel;
|
||||
|
||||
// Read codewords
|
||||
codeWords := parser.readCodewords;
|
||||
if (codeWords = nil) then
|
||||
begin
|
||||
FreeAndNil(formatInfo);
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Separate into data blocks
|
||||
dataBlocks := TDataBlock.getDataBlocks(codeWords, Version, ecLevel);
|
||||
|
||||
// Count total number of data bytes
|
||||
totalBytes := 0;
|
||||
for DataBlock in dataBlocks do
|
||||
Inc(totalBytes, DataBlock.numDataCodewords);
|
||||
|
||||
resultBytes := TArray<Byte>.Create();
|
||||
SetLength(resultBytes, totalBytes);
|
||||
try
|
||||
|
||||
resultOffset := 0;
|
||||
|
||||
// Error-correct and copy data blocks together into a stream of bytes
|
||||
for DataBlock in dataBlocks do
|
||||
begin
|
||||
codewordBytes := DataBlock.codeWords;
|
||||
numDataCodewords := DataBlock.numDataCodewords;
|
||||
if (not self.correctErrors(codewordBytes, numDataCodewords)) then
|
||||
begin
|
||||
exit(nil);
|
||||
end;
|
||||
|
||||
for i := 0 to Pred(numDataCodewords) do
|
||||
begin
|
||||
resultBytes[resultOffset] := codewordBytes[i];
|
||||
Inc(resultOffset);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Decode the contents of that stream of bytes
|
||||
Result := TDecodedBitStreamParser.decode(resultBytes, Version,
|
||||
ecLevel, hints);
|
||||
|
||||
finally
|
||||
for DataBlock in dataBlocks do
|
||||
DataBlock.Free;
|
||||
FreeAndNil(formatInfo);
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
end.
|
||||
Vendored
+149
@@ -0,0 +1,149 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.ErrorCorrectionLevel;
|
||||
|
||||
interface
|
||||
|
||||
uses SysUtils;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>See ISO 18004:2006, 6.5.1. This enum encapsulates the four error correction levels
|
||||
/// defined by the QR code standard.</p>
|
||||
/// </summary>
|
||||
TErrorCorrectionLevel = class sealed
|
||||
private
|
||||
Fbits: Integer;
|
||||
Fname: string;
|
||||
Fordinal_Renamed_Field: Integer;
|
||||
class var FOR_BITS: TArray<TErrorCorrectionLevel>;
|
||||
|
||||
class procedure InitalizeClass; static;
|
||||
class procedure FinalizeClass; static;
|
||||
public
|
||||
constructor Create(const ordinal, bits: Integer; const name: String);
|
||||
|
||||
/// <summary>
|
||||
/// Fors the bits.
|
||||
/// </summary>
|
||||
/// <param name="bits">int containing the two bits encoding a QR Code's error correction level</param>
|
||||
/// <returns>
|
||||
/// <see cref="ErrorCorrectionLevel"/> representing the encoded error correction level
|
||||
/// </returns>
|
||||
class function forBits(const bits: Integer): TErrorCorrectionLevel; static;
|
||||
|
||||
/// <summary> L = ~7% correction</summary>
|
||||
class var L: TErrorCorrectionLevel;
|
||||
|
||||
/// <summary> M = ~15% correction</summary>
|
||||
class var M: TErrorCorrectionLevel;
|
||||
|
||||
/// <summary> Q = ~25% correction</summary>
|
||||
class var Q: TErrorCorrectionLevel;
|
||||
|
||||
/// <summary> H = ~30% correction</summary>
|
||||
class var H: TErrorCorrectionLevel;
|
||||
|
||||
/// <summary>
|
||||
/// Ordinals this instance.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
function ordinal: Integer;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String"/> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String"/> that represents this instance.
|
||||
/// </returns>
|
||||
function ToString: String; override;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bits.
|
||||
/// </summary>
|
||||
property Bits: Integer read Fbits;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
property Name: String read Fname;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TErrorCorrectionLevel }
|
||||
|
||||
constructor TErrorCorrectionLevel.Create(const ordinal, bits: Integer;
|
||||
const name: String);
|
||||
begin
|
||||
Fordinal_Renamed_Field := ordinal;
|
||||
Fbits := bits;
|
||||
Fname := name
|
||||
end;
|
||||
|
||||
class procedure TErrorCorrectionLevel.InitalizeClass;
|
||||
begin
|
||||
H := TErrorCorrectionLevel.Create(3, 2, 'H');
|
||||
L := TErrorCorrectionLevel.Create(0, 1, 'L');
|
||||
M := TErrorCorrectionLevel.Create(1, 0, 'M');
|
||||
Q := TErrorCorrectionLevel.Create(2, 3, 'Q');
|
||||
|
||||
FOR_BITS := TArray<TErrorCorrectionLevel>.Create(M, L, H, Q);
|
||||
end;
|
||||
|
||||
class procedure TErrorCorrectionLevel.FinalizeClass;
|
||||
begin
|
||||
H.FOR_BITS := nil;
|
||||
L.FOR_BITS := nil;
|
||||
M.FOR_BITS := nil;
|
||||
Q.FOR_BITS := nil;
|
||||
|
||||
FreeAndNil(H);
|
||||
FreeAndNil(L);
|
||||
FreeAndNil(M);
|
||||
FreeAndNil(Q);
|
||||
FOR_BITS := nil;
|
||||
end;
|
||||
|
||||
class function TErrorCorrectionLevel.forBits(const bits: Integer): TErrorCorrectionLevel;
|
||||
begin
|
||||
if ((bits < 0) or
|
||||
(bits >= Length(TErrorCorrectionLevel.FOR_BITS)))
|
||||
then
|
||||
raise EArgumentException.Create('Invalid argument');
|
||||
|
||||
Result := TErrorCorrectionLevel.FOR_BITS[bits];
|
||||
end;
|
||||
|
||||
function TErrorCorrectionLevel.ordinal: Integer;
|
||||
begin
|
||||
Result := Fordinal_Renamed_Field;
|
||||
end;
|
||||
|
||||
function TErrorCorrectionLevel.ToString: string;
|
||||
begin
|
||||
Result := Fname
|
||||
end;
|
||||
|
||||
initialization
|
||||
TErrorCorrectionLevel.InitalizeClass;
|
||||
finalization
|
||||
TErrorCorrectionLevel.FinalizeClass;
|
||||
end.
|
||||
Vendored
+256
@@ -0,0 +1,256 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Ported from ZXING Java Source: www.Redivivus.in (suraj.supekar@redivivus.in)
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.FormatInformation;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Generics.Collections,
|
||||
ZXing.QrCode.Internal.ErrorCorrectionLevel,
|
||||
Classes,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
/// <summary> <p>Encapsulates a QR Code's format information, including the data mask used and
|
||||
/// error correction level.</p>
|
||||
///
|
||||
/// </summary>
|
||||
/// <seealso cref="TDataMask">
|
||||
/// </seealso>
|
||||
/// <seealso cref="TErrorCorrectionLevel">
|
||||
/// </seealso>
|
||||
TFormatInformation = class sealed
|
||||
private
|
||||
FErrorCorrectionLevel: TErrorCorrectionLevel;
|
||||
FDataMask: Byte;
|
||||
|
||||
const
|
||||
FORMAT_INFO_MASK_QR: Integer = $5412;
|
||||
|
||||
/// <summary> See ISO 18004:2006, Annex C, Table C.1</summary>
|
||||
class var FORMAT_INFO_DECODE_LOOKUP: TArray<TArray<Integer>>;
|
||||
|
||||
/// <summary> Offset i holds the number of 1 bits in the binary representation of i</summary>
|
||||
class var BITS_SET_IN_HALF_BYTE: TArray<Integer>;
|
||||
|
||||
class procedure InitializeClass; static;
|
||||
class procedure FinalizeClass; static;
|
||||
|
||||
class function doDecodeFormatInformation(const maskedFormatInfo1,
|
||||
maskedFormatInfo2: Integer): TFormatInformation; static;
|
||||
public
|
||||
constructor Create(const formatInfo: Integer);
|
||||
|
||||
function Equals(o: TObject): Boolean; override;
|
||||
function GetHashCode: Integer; override;
|
||||
class function numBitsDiffering(a, b: Integer): Integer; static;
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the format information.
|
||||
/// </summary>
|
||||
/// <param name="maskedFormatInfo1">format info indicator, with mask still applied</param>
|
||||
/// <param name="maskedFormatInfo2">The masked format info2.</param>
|
||||
/// <returns>
|
||||
/// information about the format it specifies, or <code>null</code>
|
||||
/// if doesn't seem to match any known pattern
|
||||
/// </returns>
|
||||
class function decodeFormatInformation(const maskedFormatInfo1,
|
||||
maskedFormatInfo2: Integer): TFormatInformation; static;
|
||||
|
||||
property ErrorCorrectionLevel: TErrorCorrectionLevel
|
||||
read FErrorCorrectionLevel;
|
||||
property DataMask: Byte read FDataMask;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TFormatInformation }
|
||||
|
||||
constructor TFormatInformation.Create(const formatInfo: Integer);
|
||||
begin
|
||||
// Bits 3,4
|
||||
FErrorCorrectionLevel := TErrorCorrectionLevel.forBits
|
||||
(TMathUtils.Asr(formatInfo, 3) and $03);
|
||||
// Bottom 3 bits
|
||||
FDataMask := Byte(formatInfo and 7);
|
||||
end;
|
||||
|
||||
class function TFormatInformation.decodeFormatInformation
|
||||
(const maskedFormatInfo1, maskedFormatInfo2: Integer): TFormatInformation;
|
||||
var
|
||||
formatInfo: TFormatInformation;
|
||||
begin
|
||||
formatInfo := doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2);
|
||||
|
||||
if (formatInfo <> nil) then
|
||||
Result := formatInfo
|
||||
else
|
||||
// Should return null, but, some QR codes apparently
|
||||
// do not mask this info. Try again by actually masking the pattern
|
||||
// first
|
||||
Result := TFormatInformation.doDecodeFormatInformation
|
||||
((maskedFormatInfo1 xor FORMAT_INFO_MASK_QR),
|
||||
(maskedFormatInfo2 xor FORMAT_INFO_MASK_QR));
|
||||
end;
|
||||
|
||||
class function TFormatInformation.doDecodeFormatInformation
|
||||
(const maskedFormatInfo1, maskedFormatInfo2: Integer): TFormatInformation;
|
||||
var
|
||||
bestDifference, bestFormatInfo, bitsDifference, targetInfo: Integer;
|
||||
decodeInfo: TArray<Integer>;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
// Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing
|
||||
bestDifference := High(Integer);
|
||||
bestFormatInfo := 0;
|
||||
|
||||
for decodeInfo in FORMAT_INFO_DECODE_LOOKUP do
|
||||
begin
|
||||
targetInfo := decodeInfo[0];
|
||||
if ((targetInfo = maskedFormatInfo1) or (targetInfo = maskedFormatInfo2))
|
||||
then
|
||||
begin
|
||||
// Found an exact match
|
||||
Result := TFormatInformation.Create(decodeInfo[1]);
|
||||
exit;
|
||||
end;
|
||||
|
||||
bitsDifference := numBitsDiffering(maskedFormatInfo1, targetInfo);
|
||||
if (bitsDifference < bestDifference) then
|
||||
begin
|
||||
bestFormatInfo := decodeInfo[1];
|
||||
bestDifference := bitsDifference;
|
||||
end;
|
||||
|
||||
if (maskedFormatInfo1 <> maskedFormatInfo2) then
|
||||
begin
|
||||
// also try the other option
|
||||
bitsDifference := numBitsDiffering(maskedFormatInfo2, targetInfo);
|
||||
if (bitsDifference < bestDifference) then
|
||||
begin
|
||||
bestFormatInfo := decodeInfo[1];
|
||||
bestDifference := bitsDifference;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
// Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits
|
||||
// differing means we found a match
|
||||
if (bestDifference <= 3) then
|
||||
begin
|
||||
Result := TFormatInformation.Create(bestFormatInfo);
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TFormatInformation.Equals(o: TObject): Boolean;
|
||||
var
|
||||
other: TFormatInformation;
|
||||
begin
|
||||
Result := false;
|
||||
if (not(o is TFormatInformation)) then
|
||||
exit;
|
||||
|
||||
other := TFormatInformation(o);
|
||||
Result := ((FErrorCorrectionLevel = other.ErrorCorrectionLevel) and
|
||||
(FDataMask = other.DataMask));
|
||||
end;
|
||||
|
||||
function TFormatInformation.GetHashCode: Integer;
|
||||
begin
|
||||
Result := ((FErrorCorrectionLevel.ordinal shl 3) or FDataMask)
|
||||
end;
|
||||
|
||||
class procedure TFormatInformation.InitializeClass;
|
||||
begin
|
||||
SetLength(FORMAT_INFO_DECODE_LOOKUP, $20);
|
||||
|
||||
FORMAT_INFO_DECODE_LOOKUP[0] := TArray<Integer>.Create($5412, 0);
|
||||
FORMAT_INFO_DECODE_LOOKUP[1] := TArray<Integer>.Create($5125, 1);
|
||||
FORMAT_INFO_DECODE_LOOKUP[2] := TArray<Integer>.Create($5E7C, 2);
|
||||
FORMAT_INFO_DECODE_LOOKUP[3] := TArray<Integer>.Create($5B4B, 3);
|
||||
FORMAT_INFO_DECODE_LOOKUP[4] := TArray<Integer>.Create($45F9, 4);
|
||||
FORMAT_INFO_DECODE_LOOKUP[5] := TArray<Integer>.Create($40CE, 5);
|
||||
FORMAT_INFO_DECODE_LOOKUP[6] := TArray<Integer>.Create($4F97, 6);
|
||||
FORMAT_INFO_DECODE_LOOKUP[7] := TArray<Integer>.Create($4AA0, 7);
|
||||
FORMAT_INFO_DECODE_LOOKUP[8] := TArray<Integer>.Create($77C4, 8);
|
||||
FORMAT_INFO_DECODE_LOOKUP[9] := TArray<Integer>.Create($72F3, 9);
|
||||
FORMAT_INFO_DECODE_LOOKUP[10] := TArray<Integer>.Create($7DAA, 10);
|
||||
FORMAT_INFO_DECODE_LOOKUP[11] := TArray<Integer>.Create($789D, 11);
|
||||
FORMAT_INFO_DECODE_LOOKUP[12] := TArray<Integer>.Create($662F, 12);
|
||||
FORMAT_INFO_DECODE_LOOKUP[13] := TArray<Integer>.Create($6318, 13);
|
||||
FORMAT_INFO_DECODE_LOOKUP[14] := TArray<Integer>.Create($6C41, 14);
|
||||
FORMAT_INFO_DECODE_LOOKUP[15] := TArray<Integer>.Create($6976, 15);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$10] := TArray<Integer>.Create($1689, $10);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$11] := TArray<Integer>.Create($13BE, $11);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$12] := TArray<Integer>.Create($1CE7, $12);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$13] := TArray<Integer>.Create($19D0, $13);
|
||||
FORMAT_INFO_DECODE_LOOKUP[20] := TArray<Integer>.Create($762, 20);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$15] := TArray<Integer>.Create($255, $15);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$16] := TArray<Integer>.Create($D0C, $16);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$17] := TArray<Integer>.Create($83B, $17);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$18] := TArray<Integer>.Create($355F, $18);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$19] := TArray<Integer>.Create($3068, $19);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$1A] := TArray<Integer>.Create($3F31, $1A);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$1B] := TArray<Integer>.Create($3A06, $1B);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$1C] := TArray<Integer>.Create($24B4, $1C);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$1D] := TArray<Integer>.Create($2183, $1D);
|
||||
FORMAT_INFO_DECODE_LOOKUP[30] := TArray<Integer>.Create($2EDA, 30);
|
||||
FORMAT_INFO_DECODE_LOOKUP[$1F] := TArray<Integer>.Create($2BED, $1F);
|
||||
|
||||
BITS_SET_IN_HALF_BYTE := TArray<Integer>.Create(0, 1, 1, 2, 1, 2, 2, 3, 1, 2,
|
||||
2, 3, 2, 3, 3, 4);
|
||||
end;
|
||||
|
||||
class procedure TFormatInformation.FinalizeClass;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
for i := 0 to Pred(Length(FORMAT_INFO_DECODE_LOOKUP)) do
|
||||
FORMAT_INFO_DECODE_LOOKUP[i] := nil;
|
||||
|
||||
FORMAT_INFO_DECODE_LOOKUP := nil;
|
||||
BITS_SET_IN_HALF_BYTE := nil;
|
||||
end;
|
||||
|
||||
class function TFormatInformation.numBitsDiffering(a, b: Integer): Integer;
|
||||
begin
|
||||
a := (a xor b); // a now has a 1 bit exactly where its bit differs with b's
|
||||
// Count bits set quickly with a series of lookups:
|
||||
Result := BITS_SET_IN_HALF_BYTE[(a and $0F)] + BITS_SET_IN_HALF_BYTE
|
||||
[TMathUtils.Asr(UInt32(a), 4) and $0F] + BITS_SET_IN_HALF_BYTE
|
||||
[TMathUtils.Asr(UInt32(a), 8) and $0F] + BITS_SET_IN_HALF_BYTE
|
||||
[TMathUtils.Asr(UInt32(a), 12) and $0F] + BITS_SET_IN_HALF_BYTE
|
||||
[TMathUtils.Asr(UInt32(a), 16) and $0F] + BITS_SET_IN_HALF_BYTE
|
||||
[TMathUtils.Asr(UInt32(a), 20) and $0F] + BITS_SET_IN_HALF_BYTE
|
||||
[TMathUtils.Asr(UInt32(a), 24) and $0F] + BITS_SET_IN_HALF_BYTE
|
||||
[TMathUtils.Asr(UInt32(a), 28) and $0F];
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TFormatInformation.InitializeClass;
|
||||
|
||||
finalization
|
||||
|
||||
TFormatInformation.FinalizeClass;
|
||||
|
||||
end.
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E.Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.Mode;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
ZXing.QrCode.Internal.Version;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>See ISO 18004:2006, 6.4.1, Tables 2 and 3. This enum encapsulates the various modes in which
|
||||
/// data can be encoded to bits in the QR code standard.</p>
|
||||
/// </summary>
|
||||
TMode = class sealed
|
||||
private
|
||||
Fbits : Integer;
|
||||
Fname : String;
|
||||
characterCountBitsForVersions: TArray<Integer>;
|
||||
|
||||
class procedure InitializeClass; static;
|
||||
class procedure FinalizeClass; static;
|
||||
public
|
||||
constructor Create(const characterCountBitsForVersions: TArray<Integer>;
|
||||
const bits: Integer; const name: String); overload;
|
||||
|
||||
// No, we can't use an enum here. J2ME doesn't support it.
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class var TERMINATOR: TMode;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class var NUMERIC: TMode;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class var ALPHANUMERIC: TMode;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class var STRUCTURED_APPEND: TMode;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class var BYTE: TMode;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class var ECI: TMode; // character counts don't apply
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class var KANJI: TMode;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class var FNC1_FIRST_POSITION: TMode;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
class var FNC1_SECOND_POSITION: TMode;
|
||||
|
||||
/// <summary>See GBT 18284-2000; "Hanzi" is a transliteration of this mode name.</summary>
|
||||
class var HANZI: TMode;
|
||||
|
||||
/// <summary>
|
||||
/// Fors the bits.
|
||||
/// </summary>
|
||||
/// <param name="bits">four bits encoding a QR Code data mode</param>
|
||||
/// <returns>
|
||||
/// <see cref="Mode"/> encoded by these bits
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException">if bits do not correspond to a known mode</exception>
|
||||
class function forBits(const bits: Integer): TMode; static;
|
||||
|
||||
/// <param name="version">version in question
|
||||
/// </param>
|
||||
/// <returns> number of bits used, in this QR Code symbol {@link Version}, to encode the
|
||||
/// count of characters that will follow encoded in this {@link Mode}
|
||||
/// </returns>
|
||||
function getCharacterCountBits(const version: TVersion): Integer;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="System.String"/> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="System.String"/> that represents this instance.
|
||||
/// </returns>
|
||||
function ToString: String; override;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
property Name: String read Fname;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bits.
|
||||
/// </summary>
|
||||
property Bits: Integer read Fbits;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TMode }
|
||||
|
||||
constructor TMode.Create(const characterCountBitsForVersions: TArray<Integer>;
|
||||
const bits: Integer; const name: String);
|
||||
begin
|
||||
Self.characterCountBitsForVersions := characterCountBitsForVersions;
|
||||
Self.Fbits := bits;
|
||||
Self.Fname := name
|
||||
end;
|
||||
|
||||
class procedure TMode.InitializeClass;
|
||||
begin
|
||||
|
||||
TMode.TERMINATOR := TMode.Create(TArray<Integer>.Create(0, 0, 0), 0, 'TERMINATOR');
|
||||
TMode.NUMERIC := TMode.Create(TArray<Integer>.Create(10, 12, 14), 1,
|
||||
'NUMERIC');
|
||||
TMode.ALPHANUMERIC := TMode.Create(TArray<Integer>.Create(9, 11, 13), 2,
|
||||
'ALPHANUMERIC');
|
||||
TMode.STRUCTURED_APPEND := TMode.Create(TArray<Integer>.Create(0, 0, 0), 3,
|
||||
'STRUCTURED_APPEND');
|
||||
TMode.BYTE := TMode.Create(TArray<Integer>.Create(8, $10, $10), 4, 'BYTE');
|
||||
TMode.ECI := TMode.Create(nil, 7, 'ECI');
|
||||
TMode.KANJI := TMode.Create(TArray<Integer>.Create(8, 10, 12), 8, 'KANJI');
|
||||
TMode.FNC1_FIRST_POSITION := TMode.Create(nil, 5, 'FNC1_FIRST_POSITION');
|
||||
TMode.FNC1_SECOND_POSITION := TMode.Create(nil, 9, 'FNC1_SECOND_POSITION');
|
||||
TMode.HANZI := TMode.Create(TArray<Integer>.Create(8, 10, 12), 13, 'HANZI')
|
||||
end;
|
||||
|
||||
class procedure TMode.FinalizeClass;
|
||||
begin
|
||||
TMode.TERMINATOR.characterCountBitsForVersions := nil;
|
||||
TMode.NUMERIC.characterCountBitsForVersions := nil;
|
||||
TMode.ALPHANUMERIC.characterCountBitsForVersions := nil;
|
||||
TMode.STRUCTURED_APPEND.characterCountBitsForVersions := nil;
|
||||
TMode.BYTE.characterCountBitsForVersions := nil;
|
||||
TMode.ECI.characterCountBitsForVersions := nil;
|
||||
TMode.KANJI.characterCountBitsForVersions := nil;
|
||||
TMode.FNC1_FIRST_POSITION.characterCountBitsForVersions := nil;
|
||||
TMode.FNC1_SECOND_POSITION.characterCountBitsForVersions := nil;
|
||||
TMode.HANZI.characterCountBitsForVersions := nil;
|
||||
|
||||
// SetLength(TMode.TERMINATOR.name, 0);
|
||||
// SetLength(TMode.NUMERIC.name, 0);
|
||||
// SetLength(TMode.ALPHANUMERIC.name, 0);
|
||||
// SetLength(TMode.STRUCTURED_APPEND.name, 0);
|
||||
// SetLength(TMode.BYTE.name, 0);
|
||||
// SetLength(TMode.ECI.name, 0);
|
||||
// SetLength(TMode.KANJI.name, 0);
|
||||
// SetLength(TMode.FNC1_FIRST_POSITION.name, 0);
|
||||
// SetLength(TMode.FNC1_SECOND_POSITION.name, 0);
|
||||
// SetLength(TMode.HANZI.name, 0);
|
||||
|
||||
FreeAndNil(TMode.TERMINATOR);
|
||||
FreeAndNil(TMode.NUMERIC);
|
||||
FreeAndNil(TMode.ALPHANUMERIC);
|
||||
FreeAndNil(TMode.STRUCTURED_APPEND);
|
||||
FreeAndNil(TMode.BYTE);
|
||||
FreeAndNil(TMode.ECI);
|
||||
FreeAndNil(TMode.KANJI);
|
||||
FreeAndNil(TMode.FNC1_FIRST_POSITION);
|
||||
FreeAndNil(TMode.FNC1_SECOND_POSITION);
|
||||
FreeAndNil(TMode.HANZI);
|
||||
|
||||
end;
|
||||
|
||||
class function TMode.forBits(const bits: Integer): TMode;
|
||||
begin
|
||||
case bits of
|
||||
$0: Result := TMode.TERMINATOR;
|
||||
$1: Result := TMode.NUMERIC;
|
||||
$2: Result := TMode.ALPHANUMERIC;
|
||||
$3: Result := TMode.STRUCTURED_APPEND;
|
||||
$4: Result := TMode.BYTE;
|
||||
$5: Result := TMode.FNC1_FIRST_POSITION;
|
||||
$7: Result := TMode.ECI;
|
||||
$8: Result := TMode.KANJI;
|
||||
$9: Result := TMode.FNC1_SECOND_POSITION;
|
||||
$D: Result := TMode.HANZI; // 0xD is defined in GBT 18284-2000, may not be supported in foreign country
|
||||
else
|
||||
raise EArgumentException.Create('Invalid arguments');
|
||||
end;
|
||||
end;
|
||||
|
||||
function TMode.getCharacterCountBits(const version: TVersion): Integer;
|
||||
var
|
||||
offset,
|
||||
number : Integer;
|
||||
begin
|
||||
if (Self.characterCountBitsForVersions = nil)
|
||||
then
|
||||
raise EArgumentException.Create('Character count doesn''t apply to this mode');
|
||||
|
||||
number := version.VersionNumber;
|
||||
if (number <= 9)
|
||||
then
|
||||
offset := 0
|
||||
else
|
||||
if (number <= 26)
|
||||
then
|
||||
offset := 1
|
||||
else
|
||||
offset := 2;
|
||||
|
||||
Result := Self.characterCountBitsForVersions[offset];
|
||||
end;
|
||||
|
||||
function TMode.ToString: string;
|
||||
begin
|
||||
Result := Self.name;
|
||||
end;
|
||||
|
||||
initialization
|
||||
TMode.InitializeClass;
|
||||
finalization
|
||||
TMode.FinalizeClass;
|
||||
end.
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
{
|
||||
* Copyright 2013 ZXing 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.
|
||||
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
|
||||
unit ZXing.QrCode.Internal.QRCodeDecoderMetaData;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
ZXing.ResultPoint;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// Meta-data container for QR Code decoding. Instances of this class may be used to convey information back to the
|
||||
/// decoding caller. Callers are expected to process this.
|
||||
/// </summary>
|
||||
TQRCodeDecoderMetaData = class sealed
|
||||
private
|
||||
Fmirrored: Boolean;
|
||||
public
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TQRCodeDecoderMetaData"/> class.
|
||||
/// </summary>
|
||||
/// <param name="mirrored">if set to <c>true</c> [mirrored].</param>
|
||||
constructor Create(const mirrored: Boolean);
|
||||
|
||||
/// <summary>
|
||||
/// Apply the result points' order correction due to mirroring.
|
||||
/// </summary>
|
||||
/// <param name="points">Array of points to apply mirror correction to.</param>
|
||||
procedure applyMirroredCorrection(const points: TArray<IResultPoint>);
|
||||
|
||||
/// <summary>
|
||||
/// true if the QR Code was mirrored.
|
||||
/// </summary>
|
||||
property IsMirrored: boolean read Fmirrored;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TQRCodeDecoderMetaData }
|
||||
|
||||
constructor TQRCodeDecoderMetaData.Create(const mirrored: Boolean);
|
||||
begin
|
||||
Fmirrored := mirrored;
|
||||
end;
|
||||
|
||||
procedure TQRCodeDecoderMetaData.applyMirroredCorrection(
|
||||
const points: TArray<IResultPoint>);
|
||||
var
|
||||
bottomLeft: IResultPoint;
|
||||
begin
|
||||
if ((FMirrored and (points <> nil)) and (Length(points) >= 3)) then
|
||||
begin
|
||||
bottomLeft := points[0];
|
||||
points[0] := points[2];
|
||||
points[2] := bottomLeft;
|
||||
// No need to 'fix' top-left and alignment pattern.
|
||||
end
|
||||
end;
|
||||
|
||||
end.
|
||||
+681
@@ -0,0 +1,681 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.Version;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
ZXing.Common.Bitmatrix,
|
||||
ZXing.QrCode.Internal.ErrorCorrectionLevel,
|
||||
ZXing.QrCode.Internal.FormatInformation,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// See ISO 18004:2006 Annex D
|
||||
/// </summary>
|
||||
TVersion = class
|
||||
type
|
||||
TECB = class
|
||||
public
|
||||
count: Integer;
|
||||
dataCodewords: Integer;
|
||||
constructor Create(count: Integer; dataCodewords: Integer);
|
||||
end;
|
||||
|
||||
TECBlocks = class sealed
|
||||
private
|
||||
ecBlocks: TArray<TECB>;
|
||||
|
||||
function getTotalECCodeWords: Integer;
|
||||
function get_NumBlocks: Integer;
|
||||
public
|
||||
ecCodewordsPerBlock: Integer;
|
||||
constructor Create(ecCodewordsPerBlock: Integer; ecBlocks: TArray<TECB>);
|
||||
destructor Destroy; override;
|
||||
function getECBlocks: TArray<TECB>;
|
||||
{ property ecCodewordsPerBlock: Integer read get_ECCodewordsPerBlock;
|
||||
property NumBlocks: Integer read get_NumBlocks;
|
||||
property TotalECCodewords: Integer read get_TotalECCodewords;
|
||||
}
|
||||
property NumBlocks: Integer read get_NumBlocks;
|
||||
property TotalECCodewords: Integer read getTotalECCodeWords;
|
||||
end;
|
||||
|
||||
private
|
||||
FalignmentPatternCenters: TArray<Integer>;
|
||||
FecBlocks: TArray<TECBlocks>;
|
||||
FTotalCodewords: Integer;
|
||||
FversionNumber: Integer;
|
||||
|
||||
class var BuildVersions : TArray<TVersion>;
|
||||
/// <summary> See ISO 18004:2006 Annex D.
|
||||
/// Element i represents the raw version bits that specify version i + 7
|
||||
/// </summary>
|
||||
class var VERSION_DECODE_INFO: TArray<Integer>;
|
||||
|
||||
function CalcDimensionForVersion: Integer;
|
||||
class function GetBuildVersions: TArray<TVersion>; static;
|
||||
class procedure ClassInit; static;
|
||||
class procedure ClassFinal; static;
|
||||
|
||||
public
|
||||
constructor Create(const versionNumber: Integer;
|
||||
const alignmentPatternCenters: TArray<Integer>;
|
||||
const ecBlocks: TArray<TECBlocks>);
|
||||
destructor Destroy; override;
|
||||
|
||||
class function decodeVersionInformation(versionBits: Integer)
|
||||
: TVersion; static;
|
||||
function buildFunctionPattern: TBitMatrix;
|
||||
function getECBlocksForLevel(ecLevel: TErrorCorrectionLevel): TECBlocks;
|
||||
class function getProvisionalVersionForDimension(
|
||||
const dimension: Integer): TVersion; static;
|
||||
class function getVersionForNumber(
|
||||
const versionNumber: Integer): TVersion; static;
|
||||
function ToString: String; override;
|
||||
|
||||
property DimensionForVersion: Integer read CalcDimensionForVersion;
|
||||
property alignmentPatternCenters: TArray<Integer>
|
||||
read FalignmentPatternCenters;
|
||||
|
||||
property TotalCodewords: Integer read FTotalCodewords;
|
||||
property versionNumber: Integer read FversionNumber;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TVersion.TECB }
|
||||
|
||||
constructor TVersion.TECB.Create(count, dataCodewords: Integer);
|
||||
begin
|
||||
self.count := count;
|
||||
self.dataCodewords := dataCodewords;
|
||||
end;
|
||||
|
||||
{ TVersion.TECBlocks }
|
||||
|
||||
constructor TVersion.TECBlocks.Create(ecCodewordsPerBlock: Integer;
|
||||
ecBlocks: TArray<TECB>);
|
||||
begin
|
||||
self.ecBlocks := ecBlocks;
|
||||
self.ecCodewordsPerBlock := ecCodewordsPerBlock;
|
||||
end;
|
||||
|
||||
destructor TVersion.TECBlocks.Destroy;
|
||||
begin
|
||||
ecBlocks :=nil;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TVersion.TECBlocks.getECBlocks: TArray<TECB>;
|
||||
begin
|
||||
result := self.ecBlocks;
|
||||
end;
|
||||
|
||||
function TVersion.TECBlocks.getTotalECCodeWords: Integer;
|
||||
begin
|
||||
result := ecCodewordsPerBlock * NumBlocks;
|
||||
end;
|
||||
|
||||
function TVersion.TECBlocks.get_NumBlocks: Integer;
|
||||
var
|
||||
total: Integer;
|
||||
ecBlock: TECB;
|
||||
begin
|
||||
total := 0;
|
||||
for ecBlock in ecBlocks do
|
||||
begin
|
||||
inc(total, ecBlock.count);
|
||||
end;
|
||||
|
||||
result := total;
|
||||
|
||||
end;
|
||||
|
||||
{ TVersion }
|
||||
|
||||
constructor TVersion.Create(const versionNumber: Integer;
|
||||
const alignmentPatternCenters: TArray<Integer>;
|
||||
const ecBlocks: TArray<TECBlocks>);
|
||||
var
|
||||
ecBlock: TECB;
|
||||
total, ecCodewords: Integer;
|
||||
ecbArray: TArray<TECB>;
|
||||
begin
|
||||
FversionNumber := versionNumber;
|
||||
FalignmentPatternCenters := alignmentPatternCenters;
|
||||
FecBlocks := ecBlocks;
|
||||
total := 0;
|
||||
ecCodewords := ecBlocks[0].ecCodewordsPerBlock;
|
||||
ecbArray := ecBlocks[0].getECBlocks;
|
||||
|
||||
for ecBlock in ecbArray do
|
||||
Inc(total, (ecBlock.count * (ecBlock.dataCodewords + ecCodewords)));
|
||||
|
||||
FTotalCodewords := total;
|
||||
ecbArray := nil;
|
||||
end;
|
||||
|
||||
destructor TVersion.Destroy;
|
||||
var
|
||||
ecBlocks : TECBlocks;
|
||||
ECB : TECB;
|
||||
begin
|
||||
FalignmentPatternCenters := nil;
|
||||
for ecBlocks in FecBlocks do
|
||||
begin
|
||||
for ecb in ecBlocks.ecBlocks do
|
||||
begin
|
||||
ECB.Free;
|
||||
//ECB := nil;
|
||||
end;
|
||||
ecBlocks.free;
|
||||
end;
|
||||
|
||||
FecBlocks := nil;
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TVersion.buildFunctionPattern: TBitMatrix;
|
||||
var
|
||||
dimension, max, x, i, y: Integer;
|
||||
begin
|
||||
dimension := DimensionForVersion;
|
||||
result := TBitMatrix.Create(dimension);
|
||||
result.setRegion(0, 0, 9, 9);
|
||||
result.setRegion((dimension - 8), 0, 8, 9);
|
||||
result.setRegion(0, (dimension - 8), 9, 8);
|
||||
max := Length(FalignmentPatternCenters);
|
||||
x := 0;
|
||||
|
||||
while ((x < max)) do
|
||||
begin
|
||||
i := (FalignmentPatternCenters[x] - 2);
|
||||
y := 0;
|
||||
while ((y < max)) do
|
||||
begin
|
||||
if (((x <> 0) or ((y <> 0) and (y <> (max - 1)))) and
|
||||
((x <> (max - 1)) or (y <> 0))) then
|
||||
result.setRegion((self.alignmentPatternCenters[y] - 2), i, 5, 5);
|
||||
inc(y)
|
||||
end;
|
||||
inc(x)
|
||||
end;
|
||||
|
||||
result.setRegion(6, 9, 1, (dimension - $11));
|
||||
result.setRegion(9, 6, (dimension - $11), 1);
|
||||
if (self.versionNumber > 6) then
|
||||
begin
|
||||
result.setRegion((dimension - 11), 0, 3, 6);
|
||||
result.setRegion(0, (dimension - 11), 6, 3)
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
class function TVersion.GetBuildVersions: TArray<TVersion>;
|
||||
begin
|
||||
Result := TArray<TVersion>.Create(
|
||||
|
||||
TVersion.Create(1, TArray<Integer>.Create(),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(7,
|
||||
TArray<TECB>.Create(TECB.Create(1, 19))), TECBlocks.Create(10,
|
||||
TArray<TECB>.Create(TECB.Create(1, 16))), TECBlocks.Create(13,
|
||||
TArray<TECB>.Create(TECB.Create(1, 13))), TECBlocks.Create(17,
|
||||
TArray<TECB>.Create(TECB.Create(1, 9))))),
|
||||
|
||||
TVersion.Create(2, TArray<Integer>.Create(6, 18),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(10,
|
||||
TArray<TECB>.Create(TECB.Create(1, 34))), TECBlocks.Create(16,
|
||||
TArray<TECB>.Create(TECB.Create(1, 28))), TECBlocks.Create(22,
|
||||
TArray<TECB>.Create(TECB.Create(1, 22))), TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(1, 16))))),
|
||||
|
||||
TVersion.Create(3, TArray<Integer>.Create(6, 22),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(15,
|
||||
TArray<TECB>.Create(TECB.Create(1, 55))), TECBlocks.Create(26,
|
||||
TArray<TECB>.Create(TECB.Create(1, 44))), TECBlocks.Create(18,
|
||||
TArray<TECB>.Create(TECB.Create(2, 17))), TECBlocks.Create(22,
|
||||
TArray<TECB>.Create(TECB.Create(2, 13))))),
|
||||
|
||||
TVersion.Create(4, TArray<Integer>.Create(6, 26),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(20,
|
||||
TArray<TECB>.Create(TECB.Create(1, 80))), TECBlocks.Create(18,
|
||||
TArray<TECB>.Create(TECB.Create(2, 32))), TECBlocks.Create(26,
|
||||
TArray<TECB>.Create(TECB.Create(2, 24))), TECBlocks.Create(16,
|
||||
TArray<TECB>.Create(TECB.Create(4, 9))))),
|
||||
|
||||
TVersion.Create(5, TArray<Integer>.Create(6, 30),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(26,
|
||||
TArray<TECB>.Create(TECB.Create(1, 108))), TECBlocks.Create(24,
|
||||
TArray<TECB>.Create(TECB.Create(2, 43))), TECBlocks.Create(18,
|
||||
TArray<TECB>.Create(TECB.Create(2, 15), TECB.Create(2, 16))),
|
||||
TECBlocks.Create(22, TArray<TECB>.Create(TECB.Create(2, 11),
|
||||
TECB.Create(2, 12))))),
|
||||
|
||||
TVersion.Create(6, TArray<Integer>.Create(6, 34),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(18,
|
||||
TArray<TECB>.Create(TECB.Create(2, 68))), TECBlocks.Create(16,
|
||||
TArray<TECB>.Create(TECB.Create(4, 27))), TECBlocks.Create(24,
|
||||
TArray<TECB>.Create(TECB.Create(4, 19))), TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(4, 15))))),
|
||||
|
||||
TVersion.Create(7, TArray<Integer>.Create(6, 22, 38),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(20,
|
||||
TArray<TECB>.Create(TECB.Create(2, 78))), TECBlocks.Create(18,
|
||||
TArray<TECB>.Create(TECB.Create(4, 31))), TECBlocks.Create(18,
|
||||
TArray<TECB>.Create(TECB.Create(2, 14), TECB.Create(4, 15))),
|
||||
TECBlocks.Create(26, TArray<TECB>.Create(TECB.Create(4, 13),
|
||||
TECB.Create(1, 14))))),
|
||||
|
||||
TVersion.Create(8, TArray<Integer>.Create(6, 24, 42),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(24,
|
||||
TArray<TECB>.Create(TECB.Create(2, 97))), TECBlocks.Create(22,
|
||||
TArray<TECB>.Create(TECB.Create(2, 38), TECB.Create(2, 39))),
|
||||
TECBlocks.Create(22, TArray<TECB>.Create(TECB.Create(4, 18), TECB.Create(2,
|
||||
19))), TECBlocks.Create(26, TArray<TECB>.Create(TECB.Create(4, 14),
|
||||
TECB.Create(2, 15))))),
|
||||
|
||||
TVersion.Create(9, TArray<Integer>.Create(6, 26, 46),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(2, 116))), TECBlocks.Create(22,
|
||||
TArray<TECB>.Create(TECB.Create(3, 36), TECB.Create(2, 37))),
|
||||
TECBlocks.Create(20, TArray<TECB>.Create(TECB.Create(4, 16), TECB.Create(4,
|
||||
17))), TECBlocks.Create(24, TArray<TECB>.Create(TECB.Create(4, 12),
|
||||
TECB.Create(4, 13))))),
|
||||
|
||||
TVersion.Create(10, TArray<Integer>.Create(6, 28, 50),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(18,
|
||||
TArray<TECB>.Create(TECB.Create(2, 68), TECB.Create(2, 69))),
|
||||
TECBlocks.Create(26, TArray<TECB>.Create(TECB.Create(4, 43), TECB.Create(1,
|
||||
44))), TECBlocks.Create(24, TArray<TECB>.Create(TECB.Create(6, 19),
|
||||
TECB.Create(2, 20))), TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(6, 15), TECB.Create(2, 16))))),
|
||||
|
||||
TVersion.Create(11, TArray<Integer>.Create(6, 30, 54),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(20,
|
||||
TArray<TECB>.Create(TECB.Create(4, 81))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(1, 50), TECB.Create(4, 51))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(4, 22), TECB.Create(4,
|
||||
23))), TECBlocks.Create(24, TArray<TECB>.Create(TECB.Create(3, 12),
|
||||
TECB.Create(8, 13))))),
|
||||
|
||||
TVersion.Create(12, TArray<Integer>.Create(6, 32, 58),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(24,
|
||||
TArray<TECB>.Create(TECB.Create(2, 92), TECB.Create(2, 93))),
|
||||
TECBlocks.Create(22, TArray<TECB>.Create(TECB.Create(6, 36), TECB.Create(2,
|
||||
37))), TECBlocks.Create(26, TArray<TECB>.Create(TECB.Create(4, 20),
|
||||
TECB.Create(6, 21))), TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(7, 14), TECB.Create(4, 15))))),
|
||||
|
||||
TVersion.Create(13, TArray<Integer>.Create(6, 34, 62),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(26,
|
||||
TArray<TECB>.Create(TECB.Create(4, 107))), TECBlocks.Create(22,
|
||||
TArray<TECB>.Create(TECB.Create(8, 37), TECB.Create(1, 38))),
|
||||
TECBlocks.Create(24, TArray<TECB>.Create(TECB.Create(8, 20), TECB.Create(4,
|
||||
21))), TECBlocks.Create(22, TArray<TECB>.Create(TECB.Create(12, 11),
|
||||
TECB.Create(4, 12))))),
|
||||
|
||||
TVersion.Create(14, TArray<Integer>.Create(6, 26, 46, 66),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(3, 115), TECB.Create(1, 116))),
|
||||
TECBlocks.Create(24, TArray<TECB>.Create(TECB.Create(4, 40), TECB.Create(5,
|
||||
41))), TECBlocks.Create(20, TArray<TECB>.Create(TECB.Create(11, 16),
|
||||
TECB.Create(5, 17))), TECBlocks.Create(24,
|
||||
TArray<TECB>.Create(TECB.Create(11, 12), TECB.Create(5, 13))))),
|
||||
|
||||
TVersion.Create(15, TArray<Integer>.Create(6, 26, 48, 70),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(22,
|
||||
TArray<TECB>.Create(TECB.Create(5, 87), TECB.Create(1, 88))),
|
||||
TECBlocks.Create(24, TArray<TECB>.Create(TECB.Create(5, 41), TECB.Create(5,
|
||||
42))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(5, 24),
|
||||
TECB.Create(7, 25))), TECBlocks.Create(24,
|
||||
TArray<TECB>.Create(TECB.Create(11, 12), TECB.Create(7, 13))))),
|
||||
|
||||
TVersion.Create(16, TArray<Integer>.Create(6, 26, 50, 74),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(24,
|
||||
TArray<TECB>.Create(TECB.Create(5, 98), TECB.Create(1, 99))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(7, 45), TECB.Create(3,
|
||||
46))), TECBlocks.Create(24, TArray<TECB>.Create(TECB.Create(15, 19),
|
||||
TECB.Create(2, 20))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(3, 15), TECB.Create(13, 16))))),
|
||||
|
||||
TVersion.Create(17, TArray<Integer>.Create(6, 30, 54, 78),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(1, 107), TECB.Create(5, 108))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(10, 46), TECB.Create(1,
|
||||
47))), TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(1, 22),
|
||||
TECB.Create(15, 23))), TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(2, 14), TECB.Create(17, 15))))),
|
||||
|
||||
TVersion.Create(18, TArray<Integer>.Create(6, 30, 56, 82),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(5, 120), TECB.Create(1, 121))),
|
||||
TECBlocks.Create(26, TArray<TECB>.Create(TECB.Create(9, 43), TECB.Create(4,
|
||||
44))), TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(17, 22),
|
||||
TECB.Create(1, 23))), TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(2, 14), TECB.Create(19, 15))))),
|
||||
|
||||
TVersion.Create(19, TArray<Integer>.Create(6, 30, 58, 86),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(3, 113), TECB.Create(4, 114))),
|
||||
TECBlocks.Create(26, TArray<TECB>.Create(TECB.Create(3, 44), TECB.Create(11,
|
||||
45))), TECBlocks.Create(26, TArray<TECB>.Create(TECB.Create(17, 21),
|
||||
TECB.Create(4, 22))), TECBlocks.Create(26,
|
||||
TArray<TECB>.Create(TECB.Create(9, 13), TECB.Create(16, 14))))),
|
||||
|
||||
TVersion.Create(20, TArray<Integer>.Create(6, 34, 62, 90),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(3, 107), TECB.Create(5, 108))),
|
||||
TECBlocks.Create(26, TArray<TECB>.Create(TECB.Create(3, 41), TECB.Create(13,
|
||||
42))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(15, 24),
|
||||
TECB.Create(5, 25))), TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(15, 15), TECB.Create(10, 16))))),
|
||||
|
||||
TVersion.Create(21, TArray<Integer>.Create(6, 28, 50, 72, 94),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(4, 116), TECB.Create(4, 117))),
|
||||
TECBlocks.Create(26, TArray<TECB>.Create(TECB.Create(17, 42))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(17, 22), TECB.Create(6,
|
||||
23))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(19, 16),
|
||||
TECB.Create(6, 17))))),
|
||||
|
||||
TVersion.Create(22, TArray<Integer>.Create(6, 26, 50, 74, 98),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(2, 111), TECB.Create(7, 112))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(17, 46))),
|
||||
TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(7, 24), TECB.Create(16,
|
||||
25))), TECBlocks.Create(24, TArray<TECB>.Create(TECB.Create(34, 13))))),
|
||||
|
||||
TVersion.Create(23, TArray<Integer>.Create(6, 30, 54, 78, 102),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(4, 121), TECB.Create(5, 122))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(4, 47), TECB.Create(14,
|
||||
48))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(11, 24),
|
||||
TECB.Create(14, 25))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(16, 15), TECB.Create(14, 16))))),
|
||||
|
||||
TVersion.Create(24, TArray<Integer>.Create(6, 28, 54, 80, 106),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(6, 117), TECB.Create(4, 118))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(6, 45), TECB.Create(14,
|
||||
46))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(11, 24),
|
||||
TECB.Create(16, 25))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(30, 16), TECB.Create(2, 17))))),
|
||||
|
||||
TVersion.Create(25, TArray<Integer>.Create(6, 32, 58, 84, 110),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(26,
|
||||
TArray<TECB>.Create(TECB.Create(8, 106), TECB.Create(4, 107))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(8, 47), TECB.Create(13,
|
||||
48))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(7, 24),
|
||||
TECB.Create(22, 25))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(22, 15), TECB.Create(13, 16))))),
|
||||
|
||||
TVersion.Create(26, TArray<Integer>.Create(6, 30, 58, 86, 114),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(10, 114), TECB.Create(2, 115))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(19, 46), TECB.Create(4,
|
||||
47))), TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(28, 22),
|
||||
TECB.Create(6, 23))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(33, 16), TECB.Create(4, 17))))),
|
||||
|
||||
TVersion.Create(27, TArray<Integer>.Create(6, 34, 62, 90, 118),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(8, 122), TECB.Create(4, 123))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(22, 45), TECB.Create(3,
|
||||
46))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(8, 23),
|
||||
TECB.Create(26, 24))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(12, 15), TECB.Create(28, 16))))),
|
||||
|
||||
TVersion.Create(28, TArray<Integer>.Create(6, 26, 50, 74, 98, 122),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(3, 117), TECB.Create(10, 118))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(3, 45), TECB.Create(23,
|
||||
46))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(4, 24),
|
||||
TECB.Create(31, 25))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(11, 15), TECB.Create(31, 16))))),
|
||||
|
||||
TVersion.Create(29, TArray<Integer>.Create(6, 30, 54, 78, 102, 126),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(7, 116), TECB.Create(7, 117))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(21, 45), TECB.Create(7,
|
||||
46))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(1, 23),
|
||||
TECB.Create(37, 24))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(19, 15), TECB.Create(26, 16))))),
|
||||
|
||||
TVersion.Create(30, TArray<Integer>.Create(6, 26, 52, 78, 104, 130),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(5, 115), TECB.Create(10, 116))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(19, 47),
|
||||
TECB.Create(10, 48))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(15, 24), TECB.Create(25, 25))),
|
||||
TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(23, 15),
|
||||
TECB.Create(25, 16))))),
|
||||
|
||||
TVersion.Create(31, TArray<Integer>.Create(6, 30, 56, 82, 108, 134),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(13, 115), TECB.Create(3, 116))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(2, 46), TECB.Create(29,
|
||||
47))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(42, 24),
|
||||
TECB.Create(1, 25))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(23, 15), TECB.Create(28, 16))))),
|
||||
|
||||
TVersion.Create(32, TArray<Integer>.Create(6, 34, 60, 86, 112, 138),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(17, 115))), TECBlocks.Create(28,
|
||||
TArray<TECB>.Create(TECB.Create(10, 46), TECB.Create(23, 47))),
|
||||
TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(10, 24),
|
||||
TECB.Create(35, 25))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(19, 15), TECB.Create(35, 16))))),
|
||||
|
||||
TVersion.Create(33, TArray<Integer>.Create(6, 30, 58, 86, 114, 142),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(17, 115), TECB.Create(1, 116))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(14, 46),
|
||||
TECB.Create(21, 47))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(29, 24), TECB.Create(19, 25))),
|
||||
TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(11, 15),
|
||||
TECB.Create(46, 16))))),
|
||||
|
||||
TVersion.Create(34, TArray<Integer>.Create(6, 34, 62, 90, 118, 146),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(13, 115), TECB.Create(6, 116))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(14, 46),
|
||||
TECB.Create(23, 47))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(44, 24), TECB.Create(7, 25))),
|
||||
TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(59, 16),
|
||||
TECB.Create(1, 17))))),
|
||||
|
||||
TVersion.Create(35, TArray<Integer>.Create(6, 30, 54, 78, 102, 126, 150),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(12, 121), TECB.Create(7, 122))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(12, 47),
|
||||
TECB.Create(26, 48))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(39, 24), TECB.Create(14, 25))),
|
||||
TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(22, 15),
|
||||
TECB.Create(41, 16))))),
|
||||
|
||||
TVersion.Create(36, TArray<Integer>.Create(6, 24, 50, 76, 102, 128, 154),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(6, 121), TECB.Create(14, 122))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(6, 47), TECB.Create(34,
|
||||
48))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(46, 24),
|
||||
TECB.Create(10, 25))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(2, 15), TECB.Create(64, 16))))),
|
||||
|
||||
TVersion.Create(37, TArray<Integer>.Create(6, 28, 54, 80, 106, 132, 158),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(17, 122), TECB.Create(4, 123))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(29, 46),
|
||||
TECB.Create(14, 47))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(49, 24), TECB.Create(10, 25))),
|
||||
TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(24, 15),
|
||||
TECB.Create(46, 16))))),
|
||||
|
||||
TVersion.Create(38, TArray<Integer>.Create(6, 32, 58, 84, 110, 136, 162),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(4, 122), TECB.Create(18, 123))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(13, 46),
|
||||
TECB.Create(32, 47))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(48, 24), TECB.Create(14, 25))),
|
||||
TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(42, 15),
|
||||
TECB.Create(32, 16))))),
|
||||
|
||||
TVersion.Create(39, TArray<Integer>.Create(6, 26, 54, 82, 110, 138, 166),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(20, 117), TECB.Create(4, 118))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(40, 47), TECB.Create(7,
|
||||
48))), TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(43, 24),
|
||||
TECB.Create(22, 25))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(10, 15), TECB.Create(67, 16))))),
|
||||
|
||||
TVersion.Create(40, TArray<Integer>.Create(6, 30, 58, 86, 114, 142, 170),
|
||||
TArray<TECBlocks>.Create(TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(19, 118), TECB.Create(6, 119))),
|
||||
TECBlocks.Create(28, TArray<TECB>.Create(TECB.Create(18, 47),
|
||||
TECB.Create(31, 48))), TECBlocks.Create(30,
|
||||
TArray<TECB>.Create(TECB.Create(34, 24), TECB.Create(34, 25))),
|
||||
TECBlocks.Create(30, TArray<TECB>.Create(TECB.Create(20, 15),
|
||||
TECB.Create(61, 16)))))
|
||||
|
||||
);
|
||||
|
||||
end;
|
||||
|
||||
function TVersion.CalcDimensionForVersion: Integer;
|
||||
begin
|
||||
result := 17 + 4 * versionNumber;
|
||||
end;
|
||||
|
||||
class function TVersion.decodeVersionInformation(versionBits: Integer)
|
||||
: TVersion;
|
||||
var
|
||||
bestDifference, bestversion, i, targetVersion, bitsDifference: Integer;
|
||||
begin
|
||||
bestDifference := $7FFFFFFF;
|
||||
bestversion := 0;
|
||||
i := 0;
|
||||
|
||||
while ((i < Length(TVersion.VERSION_DECODE_INFO))) do
|
||||
begin
|
||||
targetVersion := TVersion.VERSION_DECODE_INFO[i];
|
||||
|
||||
if (targetVersion = versionBits) then
|
||||
begin
|
||||
result := TVersion.getVersionForNumber((i + 7));
|
||||
exit
|
||||
end;
|
||||
|
||||
bitsDifference := TFormatInformation.numBitsDiffering(versionBits,
|
||||
targetVersion);
|
||||
|
||||
if (bitsDifference < bestDifference) then
|
||||
begin
|
||||
bestversion := (i + 7);
|
||||
bestDifference := bitsDifference
|
||||
end;
|
||||
inc(i)
|
||||
|
||||
end;
|
||||
|
||||
if (bestDifference <= 3) then
|
||||
begin
|
||||
result := TVersion.getVersionForNumber(bestversion);
|
||||
exit
|
||||
end;
|
||||
|
||||
result := nil;
|
||||
end;
|
||||
|
||||
function TVersion.getECBlocksForLevel(ecLevel: TErrorCorrectionLevel)
|
||||
: TECBlocks;
|
||||
begin
|
||||
result := FecBlocks[ecLevel.ordinal]
|
||||
end;
|
||||
|
||||
class function TVersion.getProvisionalVersionForDimension(
|
||||
const dimension: Integer): TVersion;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
if ((dimension mod 4) <> 1)
|
||||
then
|
||||
exit;
|
||||
|
||||
try
|
||||
Result := TVersion.getVersionForNumber(TMathUtils.Asr(dimension - $11, 2));
|
||||
except
|
||||
on E: EArgumentException do
|
||||
Result := nil;
|
||||
end
|
||||
end;
|
||||
|
||||
class function TVersion.getVersionForNumber(
|
||||
const versionNumber: Integer): TVersion;
|
||||
begin
|
||||
if ((versionNumber < 1) or (versionNumber > 40)) then
|
||||
raise EArgumentException.Create
|
||||
('version number needs to be between 1 and 40');
|
||||
|
||||
Result := TVersion.BuildVersions[(versionNumber - 1)];
|
||||
end;
|
||||
|
||||
function TVersion.ToString: string;
|
||||
begin
|
||||
result := self.versionNumber.ToString();
|
||||
end;
|
||||
|
||||
class procedure TVersion.ClassInit();
|
||||
begin
|
||||
TVersion.VERSION_DECODE_INFO := TArray<Integer>.Create($7C94, $85BC, $9A99,
|
||||
$A4D3, $BBF6, $C762, $D847, $E60D, $F928, $10B78, $1145D, $12A17, $13532,
|
||||
$149A6, $15683, $168C9, $177EC, $18EC4, $191E1, $1AFAB, $1B08E, $1CC1A,
|
||||
$1D33F, $1ED75, $1F250, $209D5, $216F0, $228BA, $2379F, $24B0B, $2542E,
|
||||
$26A64, $27541, $28C69);
|
||||
|
||||
TVersion.BuildVersions := TVersion.GetBuildVersions;
|
||||
end;
|
||||
|
||||
class procedure TVersion.ClassFinal();
|
||||
var version :Tversion;
|
||||
begin
|
||||
TVersion.VERSION_DECODE_INFO := nil;
|
||||
|
||||
for version in TVersion.BuildVersions do
|
||||
begin
|
||||
version.Free;
|
||||
end;
|
||||
|
||||
TVersion.BuildVersions :=nil;
|
||||
end;
|
||||
|
||||
|
||||
Initialization
|
||||
|
||||
TVersion.ClassInit();
|
||||
|
||||
Finalization
|
||||
|
||||
TVersion.ClassFinal();
|
||||
|
||||
|
||||
end.
|
||||
Vendored
+632
@@ -0,0 +1,632 @@
|
||||
{
|
||||
* Copyright 2008 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.Datamatrix.Internal.Detector;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Math,
|
||||
System.Generics.Defaults,
|
||||
System.Generics.Collections,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.DefaultGridSampler,
|
||||
ZXing.Common.DetectorResult,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.Common.GridSampler,
|
||||
ZXing.Common.Detector.MathUtils,
|
||||
ZXing.Common.Detector.WhiteRectangleDetector;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code
|
||||
/// is rotated or skewed, or partially obscured.</p>
|
||||
/// </summary>
|
||||
TDataMatrixDetector = class(TObject)
|
||||
private
|
||||
var
|
||||
Fimage: TBitMatrix;
|
||||
FrectangleDetector: TWhiteRectangleDetector;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// Simply encapsulates two points and a number of transitions between them.
|
||||
/// </summary>
|
||||
TResultPointsAndTransitions = class sealed
|
||||
public
|
||||
From: IResultPoint;
|
||||
To_: IResultPoint;
|
||||
Transitions: Integer;
|
||||
|
||||
constructor Create(From: IResultPoint; To_: IResultPoint;
|
||||
Transitions: Integer);
|
||||
destructor Destroy; override;
|
||||
function ToString: String; override;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// Orders ResultPointsAndTransitions by number of transitions, ascending.
|
||||
/// </summary>
|
||||
TResultPointsAndTransitionsComparator = class sealed
|
||||
(TComparer<TResultPointsAndTransitions>)
|
||||
public
|
||||
function Compare(const o1, o2: TResultPointsAndTransitions)
|
||||
: Integer; override;
|
||||
end;
|
||||
|
||||
var
|
||||
FtransCompare: TResultPointsAndTransitionsComparator;
|
||||
public
|
||||
constructor Create(const image: TBitMatrix);
|
||||
destructor Destroy; override;
|
||||
function detect: TDetectorResult;
|
||||
function transitionsBetween(Afrom, Ato: IResultPoint)
|
||||
: TResultPointsAndTransitions;
|
||||
function distance(a: IResultPoint; b: IResultPoint): Integer;
|
||||
procedure increment(table: TDictionary<IResultPoint, Integer>;
|
||||
key: IResultPoint);
|
||||
function sampleGrid(image: TBitMatrix; topLeft, bottomLeft, bottomRight,
|
||||
topRight: IResultPoint; dimensionX, dimensionY: Integer): TBitMatrix;
|
||||
function isValid(p: IResultPoint): Boolean;
|
||||
function correctTopRight(bottomLeft, bottomRight, topLeft,
|
||||
topRight: IResultPoint; dimension: Integer): IResultPoint;
|
||||
function correctTopRightRectangular(bottomLeft, bottomRight, topLeft,
|
||||
topRight: IResultPoint; dimensionTop, dimensionRight: Integer)
|
||||
: IResultPoint;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TDataMatrixDetector }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Detector"/> class.
|
||||
/// </summary>
|
||||
/// <param name="image">The image.</param>
|
||||
constructor TDataMatrixDetector.Create(const image: TBitMatrix);
|
||||
begin
|
||||
Self.Fimage := image;
|
||||
Self.FrectangleDetector := TWhiteRectangleDetector.New(image);
|
||||
Self.FtransCompare := TResultPointsAndTransitionsComparator.Create;
|
||||
end;
|
||||
|
||||
destructor TDataMatrixDetector.Destroy;
|
||||
begin
|
||||
if (FrectangleDetector <> nil) then
|
||||
FrectangleDetector.Free;
|
||||
|
||||
FtransCompare.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Detects a Data Matrix Code in an image.</p>
|
||||
/// </summary>
|
||||
/// <returns><see cref="DetectorResult" />encapsulating results of detecting a Data Matrix Code or null</returns>
|
||||
function TDataMatrixDetector.detect(): TDetectorResult;
|
||||
var
|
||||
topRight, correctedTopRight, pointA, pointB, pointC, pointD, maybeTopLeft,
|
||||
bottomLeft, bottomRight, topLeft, maybeBottomRight, point: IResultPoint;
|
||||
|
||||
cornerPoints: TArray<IResultPoint>;
|
||||
bits: TBitMatrix;
|
||||
entry: TPair<IResultPoint, Integer>; // TKeyValuePair
|
||||
Transitions: TObjectList<TResultPointsAndTransitions>;
|
||||
lSideOne, lSideTwo, transBetween, transA, transB: TResultPointsAndTransitions;
|
||||
pointCount: TDictionary<IResultPoint, Integer>;
|
||||
|
||||
corners: TArray<IResultPoint>;
|
||||
|
||||
dimensionTop, dimensionRight, dimension, dimensionCorrected: Integer;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
if (FrectangleDetector = nil) then
|
||||
// can be null, if the image is to small
|
||||
exit;
|
||||
|
||||
cornerPoints := FrectangleDetector.detect();
|
||||
if (cornerPoints = nil) then
|
||||
exit;
|
||||
|
||||
pointA := cornerPoints[0];
|
||||
pointB := cornerPoints[1];
|
||||
pointC := cornerPoints[2];
|
||||
pointD := cornerPoints[3];
|
||||
|
||||
// Point A and D are across the diagonal from one another,
|
||||
// as are B and C. Figure out which are the solid black lines
|
||||
// by counting transitions
|
||||
Transitions := TObjectList<TResultPointsAndTransitions>.Create;
|
||||
pointCount := TDictionary<IResultPoint, Integer>.Create();
|
||||
try
|
||||
Transitions.Add(transitionsBetween(pointA, pointB));
|
||||
Transitions.Add(transitionsBetween(pointA, pointC));
|
||||
Transitions.Add(transitionsBetween(pointB, pointD));
|
||||
Transitions.Add(transitionsBetween(pointC, pointD));
|
||||
Transitions.Sort(FtransCompare);
|
||||
|
||||
// Sort by number of transitions. First two will be the two solid sides; last two
|
||||
// will be the two alternating black/white sides
|
||||
lSideOne := Transitions[0];
|
||||
lSideTwo := Transitions[1];
|
||||
|
||||
// Figure out which point is their intersection by tallying up the number of times we see the
|
||||
// endpoints in the four endpoints. One will show up twice.
|
||||
|
||||
increment(pointCount, lSideOne.From);
|
||||
increment(pointCount, lSideOne.To_);
|
||||
increment(pointCount, lSideTwo.From);
|
||||
increment(pointCount, lSideTwo.To_);
|
||||
|
||||
maybeTopLeft := nil;
|
||||
bottomLeft := nil;
|
||||
maybeBottomRight := nil;
|
||||
|
||||
for entry in pointCount do
|
||||
begin
|
||||
point := entry.key;
|
||||
if (entry.Value = 2) then
|
||||
bottomLeft := point
|
||||
// this is definitely the bottom left, then -- end of two L sides
|
||||
else
|
||||
begin
|
||||
// Otherwise it's either top left or bottom right -- just assign the two arbitrarily now
|
||||
if (maybeTopLeft = nil) then
|
||||
maybeTopLeft := point
|
||||
else
|
||||
maybeBottomRight := point;
|
||||
end;
|
||||
end;
|
||||
|
||||
if ((maybeTopLeft = nil) or (bottomLeft = nil) or (maybeBottomRight = nil))
|
||||
then
|
||||
exit;
|
||||
|
||||
// Bottom left is correct but top left and bottom right might be switched
|
||||
corners := TArray<IResultPoint>.Create(maybeTopLeft, bottomLeft,
|
||||
maybeBottomRight);
|
||||
// Use the dot product trick to sort them out
|
||||
TResultPointHelpers.orderBestPatterns(corners);
|
||||
|
||||
// Now we know which is which:
|
||||
bottomRight := corners[0];
|
||||
bottomLeft := corners[1];
|
||||
topLeft := corners[2];
|
||||
|
||||
// Which point didn't we find in relation to the "L" sides? that's the top right corner
|
||||
if (not pointCount.ContainsKey(pointA)) then
|
||||
topRight := pointA
|
||||
else if (not pointCount.ContainsKey(pointB)) then
|
||||
topRight := pointB
|
||||
else if (not pointCount.ContainsKey(pointC)) then
|
||||
topRight := pointC
|
||||
else
|
||||
topRight := pointD;
|
||||
|
||||
// Next determine the dimension by tracing along the top or right side and counting black/white
|
||||
// transitions. Since we start inside a black module, we should see a number of transitions
|
||||
// equal to 1 less than the code dimension. Well, actually 2 less, because we are going to
|
||||
// end on a black module:
|
||||
|
||||
// The top right point is actually the corner of a module, which is one of the two black modules
|
||||
// adjacent to the white module at the top right. Tracing to that corner from either the top left
|
||||
// or bottom right should work here.
|
||||
transBetween := transitionsBetween(topLeft, topRight);
|
||||
dimensionTop := transBetween.Transitions;
|
||||
transBetween.Free;
|
||||
|
||||
transBetween := transitionsBetween(bottomRight, topRight);
|
||||
dimensionRight := transBetween.Transitions;
|
||||
transBetween.Free;
|
||||
|
||||
if ((dimensionTop and $01) = 1) then
|
||||
// it can't be odd, so, round... up?
|
||||
Inc(dimensionTop);
|
||||
Inc(dimensionTop, 2);
|
||||
|
||||
if ((dimensionRight and $01) = 1) then
|
||||
// it can't be odd, so, round... up?
|
||||
Inc(dimensionRight);
|
||||
Inc(dimensionRight, 2);
|
||||
|
||||
// Rectangular symbols are 6x16, 6x28, 10x24, 10x32, 14x32, or 14x44. If one dimension is more
|
||||
// than twice the other, it's certainly rectangular, but to cut a bit more slack we accept it as
|
||||
// rectangular if the bigger side is at least 7/4 times the other:
|
||||
if (((4 * dimensionTop) >= (7 * dimensionRight)) or
|
||||
((4 * dimensionRight) >= (7 * dimensionTop))) then
|
||||
begin
|
||||
// The matrix is rectangular
|
||||
correctedTopRight := correctTopRightRectangular(bottomLeft, bottomRight,
|
||||
topLeft, topRight, dimensionTop, dimensionRight);
|
||||
if (correctedTopRight = nil) then
|
||||
correctedTopRight := topRight;
|
||||
|
||||
transBetween := transitionsBetween(topLeft, correctedTopRight);
|
||||
dimensionTop := transBetween.Transitions;
|
||||
transBetween.Free;
|
||||
|
||||
transBetween := transitionsBetween(bottomRight, correctedTopRight);
|
||||
dimensionRight := transBetween.Transitions;
|
||||
transBetween.Free;
|
||||
|
||||
if ((dimensionTop and $01) = 1) then
|
||||
// it can't be odd, so, round... up?
|
||||
Inc(dimensionTop);
|
||||
|
||||
if ((dimensionRight and $01) = 1) then
|
||||
// it can't be odd, so, round... up?
|
||||
Inc(dimensionRight);
|
||||
|
||||
bits := sampleGrid(Fimage, topLeft, bottomLeft, bottomRight,
|
||||
correctedTopRight, dimensionTop, dimensionRight)
|
||||
end
|
||||
else
|
||||
begin
|
||||
// The matrix is square
|
||||
dimension := System.Math.Min(dimensionRight, dimensionTop);
|
||||
// correct top right point to match the white module
|
||||
correctedTopRight := correctTopRight(bottomLeft, bottomRight, topLeft,
|
||||
topRight, dimension);
|
||||
if (correctedTopRight = nil) then
|
||||
correctedTopRight := topRight;
|
||||
|
||||
// Redetermine the dimension using the corrected top right point
|
||||
transA := transitionsBetween(topLeft, correctedTopRight);
|
||||
transB := transitionsBetween(bottomRight, correctedTopRight);
|
||||
dimensionCorrected :=
|
||||
(System.Math.Max(transA.Transitions, transB.Transitions) + 1);
|
||||
|
||||
transA.Free;
|
||||
transB.Free;
|
||||
|
||||
if ((dimensionCorrected and $01) = 1) then
|
||||
Inc(dimensionCorrected);
|
||||
|
||||
bits := sampleGrid(Fimage, topLeft, bottomLeft, bottomRight,
|
||||
correctedTopRight, dimensionCorrected, dimensionCorrected);
|
||||
end;
|
||||
|
||||
if (bits = nil) then
|
||||
exit;
|
||||
|
||||
Result := TDetectorResult.Create(bits, TArray<IResultPoint>.Create(topLeft,
|
||||
bottomLeft, bottomRight, correctedTopRight));
|
||||
|
||||
finally
|
||||
pointCount.Free;
|
||||
Transitions.Free;
|
||||
cornerPoints := nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the position of the white top right module using the output of the rectangle detector
|
||||
/// for a rectangular matrix
|
||||
/// </summary>
|
||||
function TDataMatrixDetector.correctTopRightRectangular(bottomLeft, bottomRight,
|
||||
topLeft, topRight: IResultPoint; dimensionTop, dimensionRight: Integer)
|
||||
: IResultPoint;
|
||||
var
|
||||
corr, norm, cos, sin: Single;
|
||||
c1, c2: IResultPoint;
|
||||
l1, l2: Integer;
|
||||
rpT1, rpT2: TResultPointsAndTransitions;
|
||||
begin
|
||||
corr := (distance(bottomLeft, bottomRight) / dimensionTop);
|
||||
norm := distance(topLeft, topRight);
|
||||
if (norm = 0) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
cos := ((topRight.X - topLeft.X) / norm);
|
||||
sin := ((topRight.Y - topLeft.Y) / norm);
|
||||
|
||||
c1 := TResultPointHelpers.CreateResultPoint((topRight.X + (corr * cos)),
|
||||
(topRight.Y + (corr * sin)));
|
||||
|
||||
corr := (distance(bottomLeft, topLeft) / dimensionRight);
|
||||
norm := distance(bottomRight, topRight);
|
||||
if (norm = 0) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit;
|
||||
end;
|
||||
cos := ((topRight.X - bottomRight.X) / norm);
|
||||
sin := ((topRight.Y - bottomRight.Y) / norm);
|
||||
|
||||
c2 := TResultPointHelpers.CreateResultPoint((topRight.X + (corr * cos)),
|
||||
(topRight.Y + (corr * sin)));
|
||||
if (not isValid(c1)) then
|
||||
begin
|
||||
if (isValid(c2)) then
|
||||
begin
|
||||
Result := c2;
|
||||
exit;
|
||||
end;
|
||||
|
||||
Result := nil;
|
||||
exit;
|
||||
end;
|
||||
if (not isValid(c2)) then
|
||||
begin
|
||||
Result := c1;
|
||||
exit;
|
||||
end;
|
||||
|
||||
rpT1 := transitionsBetween(topLeft, c1);
|
||||
rpT2 := transitionsBetween(bottomRight, c1);
|
||||
|
||||
l1 := (Abs((dimensionTop - rpT1.Transitions)) +
|
||||
Abs((dimensionRight - rpT2.Transitions)));
|
||||
|
||||
FreeAndNil(rpT1);
|
||||
FreeAndNil(rpT2);
|
||||
|
||||
rpT1 := transitionsBetween(topLeft, c2);
|
||||
rpT2 := transitionsBetween(bottomRight, c2);
|
||||
|
||||
l2 := (Abs((dimensionTop - rpT1.Transitions)) +
|
||||
Abs((dimensionRight - rpT2.Transitions)));
|
||||
|
||||
FreeAndNil(rpT1);
|
||||
FreeAndNil(rpT2);
|
||||
|
||||
if (l1 <= l2) then
|
||||
begin
|
||||
Result := c1;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := c2;
|
||||
end;
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the position of the white top right module using the output of the rectangle detector
|
||||
/// for a square matrix
|
||||
/// </summary>
|
||||
function TDataMatrixDetector.correctTopRight(bottomLeft, bottomRight, topLeft,
|
||||
topRight: IResultPoint; dimension: Integer): IResultPoint;
|
||||
var
|
||||
corr, cos, sin: Single;
|
||||
norm: Integer;
|
||||
c1, c2: IResultPoint;
|
||||
l1, l2: Integer;
|
||||
transA, transB: TResultPointsAndTransitions;
|
||||
begin
|
||||
corr := (distance(bottomLeft, bottomRight) / dimension);
|
||||
norm := distance(topLeft, topRight);
|
||||
if (norm = 0) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit;
|
||||
end;
|
||||
cos := ((topRight.X - topLeft.X) / norm);
|
||||
sin := ((topRight.Y - topLeft.Y) / norm);
|
||||
|
||||
c1 := TResultPointHelpers.CreateResultPoint((topRight.X + (corr * cos)),
|
||||
(topRight.Y + (corr * sin)));
|
||||
|
||||
corr := (distance(bottomLeft, topLeft) / dimension);
|
||||
norm := distance(bottomRight, topRight);
|
||||
if (norm = 0) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit;
|
||||
end;
|
||||
cos := ((topRight.X - bottomRight.X) / norm);
|
||||
sin := ((topRight.Y - bottomRight.Y) / norm);
|
||||
|
||||
c2 := TResultPointHelpers.CreateResultPoint((topRight.X + (corr * cos)),
|
||||
(topRight.Y + (corr * sin)));
|
||||
|
||||
if (not isValid(c1)) then
|
||||
begin
|
||||
if (isValid(c2)) then
|
||||
begin
|
||||
Result := c2;
|
||||
exit;
|
||||
end;
|
||||
|
||||
Result := nil;
|
||||
exit;
|
||||
end;
|
||||
|
||||
if (not isValid(c2)) then
|
||||
begin
|
||||
Result := c1;
|
||||
exit;
|
||||
end;
|
||||
|
||||
transA := transitionsBetween(topLeft, c1);
|
||||
transB := transitionsBetween(bottomRight, c1);
|
||||
l1 := (Abs(transA.Transitions - transB.Transitions));
|
||||
transA.Free;
|
||||
transB.Free;
|
||||
|
||||
transA := transitionsBetween(topLeft, c2);
|
||||
transB := transitionsBetween(bottomRight, c2);
|
||||
l2 := (Abs(transA.Transitions - transB.Transitions));
|
||||
transA.Free;
|
||||
transB.Free;
|
||||
|
||||
if (l1 <= l2) then
|
||||
begin
|
||||
Result := c1;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := c2;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
function TDataMatrixDetector.isValid(p: IResultPoint): Boolean;
|
||||
begin
|
||||
Result := ((((p.X >= 0) and (p.X < Fimage.Width)) and (p.Y > 0)) and
|
||||
(p.Y < Fimage.Height))
|
||||
end;
|
||||
|
||||
// L2 distance
|
||||
function TDataMatrixDetector.distance(a: IResultPoint; b: IResultPoint)
|
||||
: Integer;
|
||||
begin
|
||||
Result := TMathUtils.round(TResultPointHelpers.distance(a, b))
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// Increments the Integer associated with a key by one.
|
||||
/// </summary>
|
||||
procedure TDataMatrixDetector.increment
|
||||
(table: TDictionary<IResultPoint, Integer>; key: IResultPoint);
|
||||
var
|
||||
Value: Integer;
|
||||
begin
|
||||
if (table.ContainsKey(key)) then
|
||||
begin
|
||||
Value := table[key];
|
||||
table[key] := (Value + 1);
|
||||
end
|
||||
else
|
||||
table.Add(key, 1); // table[key] := 1;
|
||||
end;
|
||||
|
||||
function TDataMatrixDetector.sampleGrid(image: TBitMatrix;
|
||||
topLeft, bottomLeft, bottomRight, topRight: IResultPoint;
|
||||
dimensionX, dimensionY: Integer): TBitMatrix;
|
||||
begin
|
||||
// TGridSampler.instance
|
||||
Result := TDefaultGridSampler.sampleGrid(image, dimensionX, dimensionY, 0.5,
|
||||
0.5, (dimensionX - 0.5), 0.5, (dimensionX - 0.5), (dimensionY - 0.5), 0.5,
|
||||
(dimensionY - 0.5), topLeft.X, topLeft.Y, topRight.X, topRight.Y,
|
||||
bottomRight.X, bottomRight.Y, bottomLeft.X, bottomLeft.Y);
|
||||
end;
|
||||
|
||||
/// <summary>
|
||||
/// Counts the number of black/white transitions between two points, using something like Bresenham's algorithm.
|
||||
/// </summary>
|
||||
function TDataMatrixDetector.transitionsBetween(Afrom, Ato: IResultPoint)
|
||||
: TResultPointsAndTransitions;
|
||||
var
|
||||
temp, fromX, fromY, toX, toY: Integer;
|
||||
steep: Boolean;
|
||||
dx, dy: Int64;
|
||||
xstep, ystep, Transitions: Integer;
|
||||
error: Int64;
|
||||
inBlack, isBlack: Boolean;
|
||||
X, Y: Integer;
|
||||
begin
|
||||
// See QR Code Detector, sizeOfBlackWhiteBlackRun()
|
||||
fromX := Trunc(Afrom.X);
|
||||
fromY := Trunc(Afrom.Y);
|
||||
toX := Trunc(Ato.X);
|
||||
toY := Trunc(Ato.Y);
|
||||
steep := (Abs((toY - fromY)) > Abs((toX - fromX)));
|
||||
if (steep) then
|
||||
begin
|
||||
temp := fromX;
|
||||
fromX := fromY;
|
||||
fromY := temp;
|
||||
temp := toX;
|
||||
toX := toY;
|
||||
toY := temp;
|
||||
end;
|
||||
|
||||
dx := Abs(toX - fromX);
|
||||
dy := Abs(toY - fromY);
|
||||
error := TMathUtils.Asr(-dx, 1);
|
||||
if (fromY < toY) then
|
||||
ystep := 1
|
||||
else
|
||||
ystep := -1;
|
||||
if (fromX < toX) then
|
||||
xstep := 1
|
||||
else
|
||||
xstep := -1;
|
||||
Transitions := 0;
|
||||
if steep then
|
||||
inBlack := Fimage[fromY, fromX]
|
||||
else
|
||||
inBlack := Fimage[fromX, fromY];
|
||||
|
||||
X := fromX;
|
||||
Y := fromY;
|
||||
while ((X <> toX)) do
|
||||
begin
|
||||
if steep then
|
||||
isBlack := Fimage[Y, X]
|
||||
else
|
||||
isBlack := Fimage[X, Y];
|
||||
|
||||
if (isBlack <> inBlack) then
|
||||
begin
|
||||
Inc(Transitions);
|
||||
inBlack := isBlack;
|
||||
end;
|
||||
Inc(error, dy);
|
||||
if (error > 0) then
|
||||
begin
|
||||
if (Y = toY) then
|
||||
break;
|
||||
Inc(Y, ystep);
|
||||
Dec(error, dx);
|
||||
end;
|
||||
Inc(X, xstep)
|
||||
end;
|
||||
|
||||
Result := TResultPointsAndTransitions.Create(Afrom, Ato, Transitions);
|
||||
end;
|
||||
|
||||
{ TResultPointsAndTransitions }
|
||||
|
||||
constructor TDataMatrixDetector.TResultPointsAndTransitions.Create
|
||||
(From: IResultPoint; To_: IResultPoint; Transitions: Integer);
|
||||
begin
|
||||
Self.From := From;
|
||||
Self.To_ := To_;
|
||||
Self.Transitions := Transitions;
|
||||
end;
|
||||
|
||||
destructor TDataMatrixDetector.TResultPointsAndTransitions.Destroy;
|
||||
begin
|
||||
|
||||
// if (Assigned(Self.From)) then
|
||||
// Self.From := nil;
|
||||
//
|
||||
// if (Assigned(Self.To_)) then
|
||||
// Self.To_:=nil;
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TDataMatrixDetector.TResultPointsAndTransitions.ToString: string;
|
||||
begin
|
||||
Result := From.ToString + '/' + To_.ToString + '/' + IntToStr(Transitions);
|
||||
end;
|
||||
|
||||
function TDataMatrixDetector.TResultPointsAndTransitionsComparator.Compare
|
||||
(const o1, o2: TResultPointsAndTransitions): Integer;
|
||||
begin
|
||||
Result := (o1.Transitions - o2.Transitions);
|
||||
end;
|
||||
|
||||
end.
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Ported from ZXING Java Source: www.Redivivus.in (suraj.supekar@redivivus.in)
|
||||
* Delphi Implementation by E.Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.AlignmentPattern;
|
||||
interface
|
||||
uses ZXing.ResultPoint;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// As we did for ResultPoint, we use an interfaced object to implement automatic deallocation
|
||||
/// of TAligmnentPattern instances
|
||||
/// the actual implementation of this interface is in unit ZXing.QrCode.Internal.AlignmentPatternImplementation
|
||||
/// </summary>
|
||||
IAlignmentPattern = interface(IResultPoint)
|
||||
['{8C0A5D01-9620-42B6-BE3E-9C40717B5B38}']
|
||||
/// <summary> <p>Determines if this alignment pattern "about equals" an alignment pattern at the stated
|
||||
/// position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
|
||||
/// </summary>
|
||||
function aboutEquals(const moduleSize, i, j: Single): Boolean;
|
||||
|
||||
/// <summary>
|
||||
/// Combines this object's current estimate of a finder pattern position and module size
|
||||
/// with a new estimate. It returns a new {@code FinderPattern} containing an average of the two.
|
||||
/// </summary>
|
||||
/// <param name="i">The i.</param>
|
||||
/// <param name="j">The j.</param>
|
||||
/// <param name="newModuleSize">New size of the module.</param>
|
||||
/// <returns></returns>
|
||||
function combineEstimate(const i, j, newModuleSize: Single): IAlignmentPattern;
|
||||
end;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// contains all static methods for using IAlignmentPatterns instances
|
||||
/// </summary>
|
||||
TAlignmentPatternHelpers = class
|
||||
/// <summary>
|
||||
/// IAlignmentPattern instances must be obtained by calling this function
|
||||
/// </summary>
|
||||
class function CreateAlignmentPattern(const posX, posY, estimatedModuleSize: Single):IAlignmentPattern;
|
||||
end;
|
||||
|
||||
implementation
|
||||
uses ZXing.QrCode.Internal.AlignmentPatternImplementation;
|
||||
|
||||
{ TAlignmentPatternHelpers }
|
||||
class function TAlignmentPatternHelpers.CreateAlignmentPattern(const posX, posY, estimatedModuleSize: Single):IAlignmentPattern;
|
||||
begin
|
||||
result := ZXing.QrCode.Internal.AlignmentPatternImplementation.NewAlignmentPattern(posX,posY,estimatedModuleSize);
|
||||
end;
|
||||
|
||||
end.
|
||||
+407
@@ -0,0 +1,407 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Ported from ZXING Java Source: www.Redivivus.in (suraj.supekar@redivivus.in)
|
||||
* Delphi Implementation by E.Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.AlignmentPatternFinder;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Math,
|
||||
System.Generics.Collections,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.QrCode.Internal.AlignmentPattern, // nullableType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
/// <summary> <p>This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder
|
||||
/// patterns but are smaller and appear at regular intervals throughout the image.</p>
|
||||
///
|
||||
/// <p>At the moment this only looks for the bottom-right alignment pattern.</p>
|
||||
///
|
||||
/// <p>This is mostly a simplified copy of {@link FinderPatternFinder}. It is copied,
|
||||
/// pasted and stripped down here for maximum performance but does unfortunately duplicate
|
||||
/// some code.</p>
|
||||
///
|
||||
/// <p>This class is thread-safe but not reentrant. Each thread must allocate its own object.</p>
|
||||
///
|
||||
/// </summary>
|
||||
TAlignmentPatternFinder = class sealed
|
||||
private
|
||||
image: TBitMatrix;
|
||||
possibleCenters: TList<IAlignmentPattern>;
|
||||
startX, startY: Integer;
|
||||
width, height: Integer;
|
||||
moduleSize: Single;
|
||||
crossCheckStateCount: TArray<Integer>;
|
||||
resultPointCallback: TResultPointCallback;
|
||||
|
||||
/// <summary> Given a count of black/white/black pixels just seen and an end position,
|
||||
/// figures the location of the center of this black/white/black run.
|
||||
/// </summary>
|
||||
class function centerFromEnd(const stateCount: TArray<Integer>;
|
||||
const pEnd: Integer): Single; static;
|
||||
|
||||
/// <param name="stateCount">count of black/white/black pixels just read
|
||||
/// </param>
|
||||
/// <returns> true iff the proportions of the counts is close enough to the 1/1/1 ratios
|
||||
/// used by alignment patterns to be considered a match
|
||||
/// </returns>
|
||||
function foundPatternCross(const stateCount: TArray<Integer>): Boolean;
|
||||
|
||||
/// <summary>
|
||||
/// <p>After a horizontal scan finds a potential alignment pattern, this method
|
||||
/// "cross-checks" by scanning down vertically through the center of the possible
|
||||
/// alignment pattern to see if the same proportion is detected.</p>
|
||||
/// </summary>
|
||||
/// <param name="startI">row where an alignment pattern was detected</param>
|
||||
/// <param name="centerJ">center of the section that appears to cross an alignment pattern</param>
|
||||
/// <param name="maxCount">maximum reasonable number of modules that should be
|
||||
/// observed in any reading state, based on the results of the horizontal scan</param>
|
||||
/// <param name="originalStateCountTotal">The original state count total.</param>
|
||||
/// <returns>
|
||||
/// vertical center of alignment pattern, or null if not found
|
||||
/// </returns>
|
||||
function crossCheckVertical(const startI, centerJ, maxCount,
|
||||
originalStateCountTotal: Integer): Single;
|
||||
|
||||
/// <summary> <p>This is called when a horizontal scan finds a possible alignment pattern. It will
|
||||
/// cross check with a vertical scan, and if successful, will see if this pattern had been
|
||||
/// found on a previous horizontal scan. If so, we consider it confirmed and conclude we have
|
||||
/// found the alignment pattern.</p>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="stateCount">reading state module counts from horizontal scan
|
||||
/// </param>
|
||||
/// <param name="i">row where alignment pattern may be found
|
||||
/// </param>
|
||||
/// <param name="j">end of possible alignment pattern in row
|
||||
/// </param>
|
||||
/// <returns> {@link TAlignmentPattern} if we have found the same pattern twice, or null if not
|
||||
/// </returns>
|
||||
function handlePossibleCenter(const stateCount: TArray<Integer>;
|
||||
const i, j: Integer): IAlignmentPattern;
|
||||
public
|
||||
/// <summary> <p>Creates a finder that will look in a portion of the whole image.</p>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="image">image to search
|
||||
/// </param>
|
||||
/// <param name="startX">left column from which to start searching
|
||||
/// </param>
|
||||
/// <param name="startY">top row from which to start searching
|
||||
/// </param>
|
||||
/// <param name="width">width of region to search
|
||||
/// </param>
|
||||
/// <param name="height">height of region to search
|
||||
/// </param>
|
||||
/// <param name="moduleSize">estimated module size so far
|
||||
/// </param>
|
||||
constructor Create(const image: TBitMatrix; const startX, startY,
|
||||
width, height: Integer; const moduleSize: Single;
|
||||
resultPointCallback: TResultPointCallback);
|
||||
destructor Destroy; override;
|
||||
|
||||
/// <summary> <p>This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since
|
||||
/// it's pretty performance-critical and so is written to be fast foremost.</p>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns> {@link TAlignmentPattern} if found
|
||||
/// </returns>
|
||||
function find: IAlignmentPattern;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TAlignmentPatternFinder }
|
||||
|
||||
constructor TAlignmentPatternFinder.Create(const image: TBitMatrix;
|
||||
const startX, startY, width, height: Integer; const moduleSize: Single;
|
||||
resultPointCallback: TResultPointCallback);
|
||||
begin
|
||||
Self.image := image;
|
||||
Self.possibleCenters := TList<IAlignmentPattern>.Create;
|
||||
Self.possibleCenters.Capacity := 5;
|
||||
Self.startX := startX;
|
||||
Self.startY := startY;
|
||||
Self.width := width;
|
||||
Self.height := height;
|
||||
Self.moduleSize := moduleSize;
|
||||
Self.crossCheckStateCount := TArray<Integer>.Create(0, 0, 0);
|
||||
Self.resultPointCallback := resultPointCallback;
|
||||
end;
|
||||
|
||||
destructor TAlignmentPatternFinder.Destroy;
|
||||
begin
|
||||
if possibleCenters<>nil then
|
||||
possibleCenters.Clear;
|
||||
FreeAndNil(self.possibleCenters);
|
||||
self.crossCheckStateCount := nil;
|
||||
self.resultPointCallback :=nil;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TAlignmentPatternFinder.find: IAlignmentPattern;
|
||||
var
|
||||
confirmed: IAlignmentPattern;
|
||||
maxJ, middleI, iGen, i, j, currentState: Integer;
|
||||
stateCount: TArray<Integer>;
|
||||
begin
|
||||
startX := Self.startX;
|
||||
height := Self.height;
|
||||
maxJ := startX + self.width;
|
||||
middleI := Self.startY + (TMathUtils.Asr(height, 1));
|
||||
// We are looking for black/white/black modules in 1:1:1 ratio;
|
||||
// this tracks the number of black/white/black modules seen so far
|
||||
stateCount := TArray<Integer>.Create(0, 0, 0);
|
||||
for iGen := 0 to Pred(height) do
|
||||
begin
|
||||
// Search from middle outwards
|
||||
if ((iGen and 1) = 0)
|
||||
then
|
||||
i := middleI + (TMathUtils.Asr(iGen + 1, 1))
|
||||
else
|
||||
i := middleI + (-1 * TMathUtils.Asr(iGen + 1, 1));
|
||||
|
||||
stateCount[0] := 0;
|
||||
stateCount[1] := 0;
|
||||
stateCount[2] := 0;
|
||||
j := startX;
|
||||
// Burn off leading white pixels before anything else; if we start in the middle of
|
||||
// a white run, it doesn't make sense to count its length, since we don't know if the
|
||||
// white run continued to the left of the start point
|
||||
while ((j < maxJ) and (not Self.image[j, i])) do
|
||||
Inc(j);
|
||||
|
||||
currentState := 0;
|
||||
while ((j < maxJ)) do
|
||||
begin
|
||||
if (Self.image[j, i]) then
|
||||
begin
|
||||
// Black pixel
|
||||
if (currentState = 1) then
|
||||
begin
|
||||
// Counting black pixels
|
||||
Inc(stateCount[currentState]);
|
||||
end else
|
||||
begin
|
||||
// Counting white pixels
|
||||
if (currentState = 2) then
|
||||
begin
|
||||
// A winner?
|
||||
if (Self.foundPatternCross(stateCount)) then
|
||||
begin
|
||||
// Yes
|
||||
confirmed := self.handlePossibleCenter(stateCount, i, j);
|
||||
if (confirmed <> nil) then
|
||||
begin
|
||||
result := confirmed;
|
||||
exit
|
||||
end;
|
||||
end;
|
||||
stateCount[0] := stateCount[2];
|
||||
stateCount[1] := 1;
|
||||
stateCount[2] := 0;
|
||||
currentState := 1;
|
||||
end else
|
||||
begin
|
||||
Inc(currentState);
|
||||
Inc(stateCount[currentState]);
|
||||
end;
|
||||
end;
|
||||
end else
|
||||
begin
|
||||
// White pixel
|
||||
if (currentState = 1)
|
||||
then
|
||||
// Counting black pixels
|
||||
Inc(currentState);
|
||||
Inc(stateCount[currentState])
|
||||
end;
|
||||
Inc(j)
|
||||
end;
|
||||
|
||||
if (Self.foundPatternCross(stateCount)) then
|
||||
begin
|
||||
confirmed := self.handlePossibleCenter(stateCount, i, maxJ);
|
||||
if (confirmed <> nil) then
|
||||
begin
|
||||
result := confirmed;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Hmm, nothing we saw was observed and confirmed twice. If we had
|
||||
// any guess at all, return it.
|
||||
if (Self.possibleCenters.Count <> 0)
|
||||
then
|
||||
Result := Self.possibleCenters[0]
|
||||
else
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
class function TAlignmentPatternFinder.centerFromEnd(
|
||||
const stateCount: TArray<Integer>; const pEnd: Integer): Single;
|
||||
begin
|
||||
Result := (pEnd - stateCount[2]) - (stateCount[1] / 2.0);
|
||||
if (Single.IsNaN(Result))
|
||||
then
|
||||
Result := -1;
|
||||
end;
|
||||
|
||||
function TAlignmentPatternFinder.foundPatternCross(
|
||||
const stateCount: TArray<Integer>): Boolean;
|
||||
var
|
||||
maxVariance: Single;
|
||||
i: Integer;
|
||||
begin
|
||||
maxVariance := moduleSize / 2;
|
||||
for i := 0 to Pred(3) do
|
||||
begin
|
||||
if (Abs(self.moduleSize - stateCount[i]) >= maxVariance) then
|
||||
begin
|
||||
Result := false;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
function TAlignmentPatternFinder.crossCheckVertical(const startI, centerJ,
|
||||
maxCount, originalStateCountTotal: Integer): Single;
|
||||
var
|
||||
maxI,
|
||||
i, stateCountTotal: Integer;
|
||||
stateCount: TArray<Integer>;
|
||||
begin
|
||||
maxI := Self.image.height;
|
||||
stateCount := Self.crossCheckStateCount;
|
||||
stateCount[0] := 0;
|
||||
stateCount[1] := 0;
|
||||
stateCount[2] := 0;
|
||||
|
||||
// Start counting up from center
|
||||
i := startI;
|
||||
while (((i >= 0) and self.image[centerJ, i]) and
|
||||
(stateCount[1] <= maxCount)) do
|
||||
begin
|
||||
Inc(stateCount[1]);
|
||||
Dec(i);
|
||||
end;
|
||||
// If already too many modules in this state or ran off the edge:
|
||||
if ((i < 0) or (stateCount[1] > maxCount)) then
|
||||
begin
|
||||
Result := -1;
|
||||
exit;
|
||||
end;
|
||||
while (((i >= 0) and (not Self.image[centerJ, i])) and
|
||||
(stateCount[0] <= maxCount)) do
|
||||
begin
|
||||
Inc(stateCount[0]);
|
||||
Dec(i);
|
||||
end;
|
||||
if (stateCount[0] > maxCount) then
|
||||
begin
|
||||
Result := -1;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Now also count down from center
|
||||
i := (startI + 1);
|
||||
while (((i < maxI) and Self.image[centerJ, i]) and
|
||||
(stateCount[1] <= maxCount)) do
|
||||
begin
|
||||
Inc(stateCount[1]);
|
||||
Inc(i);
|
||||
end;
|
||||
if ((i = maxI) or (stateCount[1] > maxCount)) then
|
||||
begin
|
||||
Result := -1;
|
||||
exit;
|
||||
end;
|
||||
while (((i < maxI) and not self.image[centerJ, i]) and
|
||||
(stateCount[2] <= maxCount)) do
|
||||
begin
|
||||
Inc(stateCount[2]);
|
||||
Inc(i);
|
||||
end;
|
||||
if (stateCount[2] > maxCount) then
|
||||
begin
|
||||
Result := -1;
|
||||
exit;
|
||||
end;
|
||||
|
||||
stateCountTotal := (stateCount[0] + stateCount[1] + stateCount[2]);
|
||||
if ((5 * Abs(stateCountTotal - originalStateCountTotal)) >=
|
||||
(2 * originalStateCountTotal)) then
|
||||
begin
|
||||
Result := -1;
|
||||
exit;
|
||||
end;
|
||||
|
||||
if Self.foundPatternCross(stateCount)
|
||||
then
|
||||
Result := TAlignmentPatternFinder.centerFromEnd(stateCount, i)
|
||||
else
|
||||
Result := -1;
|
||||
end;
|
||||
|
||||
function TAlignmentPatternFinder.handlePossibleCenter(
|
||||
const stateCount: TArray<Integer>; const i, j: Integer): IAlignmentPattern;
|
||||
var
|
||||
center, point: IAlignmentPattern;
|
||||
stateCountTotal: Integer;
|
||||
estimatedModuleSize: Single;
|
||||
centerJ, centerI: Single;
|
||||
begin
|
||||
stateCountTotal := (stateCount[0] + stateCount[1] + stateCount[2]);
|
||||
centerJ := TAlignmentPatternFinder.centerFromEnd(stateCount, j);
|
||||
if (centerJ = -1) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit;
|
||||
end;
|
||||
centerI := crossCheckVertical(i, Floor(centerJ), (2 * stateCount[1]),
|
||||
stateCountTotal);
|
||||
if (centerI <> -1) then
|
||||
begin
|
||||
estimatedModuleSize := (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0;
|
||||
for center in Self.possibleCenters do
|
||||
begin
|
||||
// Look for about the same center and module size:
|
||||
if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) then
|
||||
begin
|
||||
Result := center.combineEstimate(centerI, centerJ, estimatedModuleSize);
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
// Hadn't found this before; save it
|
||||
point := TAlignmentPatternHelpers.CreateAlignmentPattern(centerJ, centerI, estimatedModuleSize);
|
||||
Self.possibleCenters.Add(point);
|
||||
if Assigned(resultPointCallback)
|
||||
then
|
||||
Self.resultPointCallback(point);
|
||||
end;
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
end.
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Ported from ZXING Java Source: www.Redivivus.in (suraj.supekar@redivivus.in)
|
||||
* Delphi Implementation by E.Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.AlignmentPatternImplementation;
|
||||
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
ZXing.QrCode.Internal.AlignmentPattern,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.ResultPointImplementation;
|
||||
|
||||
// this is the function that actually creates IAlignmentPattern instances.
|
||||
// the actual implementation of the interface is totally hidden to all other units,
|
||||
// since even its declaration is in the implementation section of this unit
|
||||
function NewAlignmentPattern(const posX, posY, estimatedModuleSize: Single):IAlignmentPattern;
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
{ TAlignmentPattern }
|
||||
|
||||
|
||||
type
|
||||
/// <summary> <p>Encapsulates an alignment pattern, which are the smaller square patterns found in
|
||||
/// all but the simplest QR Codes.</p>
|
||||
///
|
||||
/// </summary>
|
||||
TAlignmentPattern = class sealed(TResultPoint,IResultPoint,IAlignmentPattern)
|
||||
private
|
||||
estimatedModuleSize: Single;
|
||||
constructor Create(const posX, posY, estimatedModuleSize: Single);
|
||||
|
||||
/// <summary> <p>Determines if this alignment pattern "about equals" an alignment pattern at the stated
|
||||
/// position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
|
||||
/// </summary>
|
||||
function aboutEquals(const moduleSize, i, j: Single): Boolean;
|
||||
|
||||
/// <summary>
|
||||
/// Combines this object's current estimate of a finder pattern position and module size
|
||||
/// with a new estimate. It returns a new {@code FinderPattern} containing an average of the two.
|
||||
/// </summary>
|
||||
/// <param name="i">The i.</param>
|
||||
/// <param name="j">The j.</param>
|
||||
/// <param name="newModuleSize">New size of the module.</param>
|
||||
/// <returns></returns>
|
||||
function combineEstimate(const i, j,
|
||||
newModuleSize: Single): IAlignmentPattern;
|
||||
end;
|
||||
|
||||
|
||||
constructor TAlignmentPattern.Create(const posX, posY,
|
||||
estimatedModuleSize: Single);
|
||||
begin
|
||||
inherited Create(posX, posY);
|
||||
|
||||
Self.estimatedModuleSize := estimatedModuleSize;
|
||||
end;
|
||||
|
||||
function TAlignmentPattern.aboutEquals(const moduleSize, i, j: Single): Boolean;
|
||||
var
|
||||
moduleSizeDiff: Single;
|
||||
begin
|
||||
if ((Abs(i - Self.y) <= moduleSize) and
|
||||
(Abs(j - Self.x) <= moduleSize)) then
|
||||
begin
|
||||
moduleSizeDiff := Abs(moduleSize - self.estimatedModuleSize);
|
||||
Result := ((moduleSizeDiff <= 1) or
|
||||
(moduleSizeDiff <= self.estimatedModuleSize));
|
||||
end else Result := false;
|
||||
end;
|
||||
|
||||
function TAlignmentPattern.combineEstimate(const i, j,
|
||||
newModuleSize: Single): IAlignmentPattern;
|
||||
var
|
||||
combinedX,
|
||||
combinedY,
|
||||
combinedModuleSize : Single;
|
||||
begin
|
||||
combinedX := (self.x + j) / 2.0;
|
||||
combinedY := (self.y + i) / 2.0;
|
||||
combinedModuleSize := (estimatedModuleSize + newModuleSize) / 2.0;
|
||||
Result := TAlignmentPattern.Create(combinedX, combinedY, combinedModuleSize);
|
||||
end;
|
||||
|
||||
|
||||
|
||||
|
||||
function NewAlignmentPattern(const posX, posY, estimatedModuleSize: Single):IAlignmentPattern;
|
||||
begin
|
||||
result := TAlignmentPattern.Create(posX,posY,estimatedModuleSize);
|
||||
end;
|
||||
|
||||
end.
|
||||
+492
@@ -0,0 +1,492 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.Detector;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Math,
|
||||
Generics.Collections,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.Common.DetectorResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.Common.PerspectiveTransform,
|
||||
ZXing.QrCode.Internal.FinderPatternInfo,
|
||||
ZXing.QrCode.Internal.FinderPatternFinder,
|
||||
ZXing.QrCode.Internal.FinderPattern,
|
||||
ZXing.QrCode.Internal.AlignmentPattern,
|
||||
ZXing.QrCode.Internal.AlignmentPatternFinder,
|
||||
ZXing.QrCode.Internal.Version,
|
||||
ZXing.DefaultGridSampler,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Encapsulates logic that can detect a QR Code in an image, even if the QR Code
|
||||
/// is rotated or skewed, or partially obscured.</p>
|
||||
/// </summary>
|
||||
TDetector = class
|
||||
private
|
||||
FImage: TBitMatrix;
|
||||
FResultPointCallback: TResultPointCallback;
|
||||
|
||||
function calculateModuleSizeOneWay(pattern: IResultpoint;
|
||||
otherPattern: IResultpoint): Single;
|
||||
|
||||
class function computeDimension(topLeft: IResultpoint;
|
||||
topRight: IResultpoint; bottomLeft: IResultpoint; moduleSize: Single;
|
||||
var dimension: Integer): boolean; static;
|
||||
|
||||
class function createTransform(const topLeft, topRight, bottomLeft,
|
||||
AlignmentPattern: IResultpoint; const dimension: Integer)
|
||||
: TPerspectiveTransform; static;
|
||||
|
||||
class function sampleGrid(image: TBitMatrix;
|
||||
transform: TPerspectiveTransform; dimension: Integer): TBitMatrix; static;
|
||||
function sizeOfBlackWhiteBlackRun(fromX: Integer; fromY: Integer;
|
||||
toX: Integer; toY: Integer): Single;
|
||||
function sizeOfBlackWhiteBlackRunBothWays(fromX: Integer; fromY: Integer;
|
||||
toX: Integer; toY: Integer): Single;
|
||||
protected
|
||||
function calculateModuleSize(const topLeft, topRight,
|
||||
bottomLeft: IResultPoint): Single; virtual;
|
||||
|
||||
function findAlignmentInRegion(const overallEstModuleSize: Single;
|
||||
const estAlignmentX, estAlignmentY: Integer;
|
||||
const allowanceFactor: Single): IAlignmentPattern;
|
||||
|
||||
/// <summary>
|
||||
/// Processes the finder pattern info.
|
||||
/// </summary>
|
||||
/// <param name="info">The info.</param>
|
||||
/// <returns></returns>
|
||||
function processFinderPatternInfo(const info: TFinderPatternInfo)
|
||||
: TDetectorResult; virtual;
|
||||
public
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Detector"/> class.
|
||||
/// </summary>
|
||||
/// <param name="image">The image.</param>
|
||||
constructor Create(const image: TBitMatrix);
|
||||
destructor Destroy; override;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Detects a QR Code in an image.</p>
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <see cref="TDetectorResult"/> encapsulating results of detecting a QR Code
|
||||
/// </returns>
|
||||
function detect(): TDetectorResult; overload;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Detects a QR Code in an image.</p>
|
||||
/// </summary>
|
||||
/// <param name="hints">optional hints to detector</param>
|
||||
/// <returns>
|
||||
/// <see cref="TDetectorResult"/> encapsulating results of detecting a QR Code
|
||||
/// </returns>
|
||||
function detect(const hints: TDictionary<TDecodeHintType, TObject>)
|
||||
: TDetectorResult; overload;
|
||||
|
||||
property image: TBitMatrix read FImage;
|
||||
property ResultPointCallback: TResultPointCallback
|
||||
read FResultPointCallback;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TDetector }
|
||||
|
||||
constructor TDetector.Create(const image: TBitMatrix);
|
||||
begin
|
||||
FImage := image;
|
||||
end;
|
||||
|
||||
destructor TDetector.Destroy;
|
||||
begin
|
||||
FResultPointCallback := nil;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TDetector.calculateModuleSize(const topLeft, topRight,
|
||||
bottomLeft: IResultPoint): Single;
|
||||
begin
|
||||
Result := ((self.calculateModuleSizeOneWay(topLeft, topRight) +
|
||||
self.calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2)
|
||||
end;
|
||||
|
||||
function TDetector.calculateModuleSizeOneWay(pattern: IResultPoint;
|
||||
otherPattern: IResultPoint): Single;
|
||||
var
|
||||
moduleSizeEst1, moduleSizeEst2: Single;
|
||||
|
||||
begin
|
||||
moduleSizeEst1 := self.sizeOfBlackWhiteBlackRunBothWays(Floor(pattern.X),
|
||||
Floor(pattern.Y), Floor(otherPattern.X), Floor(otherPattern.Y));
|
||||
moduleSizeEst2 := self.sizeOfBlackWhiteBlackRunBothWays(Floor(otherPattern.X),
|
||||
Floor(otherPattern.Y), Floor(pattern.X), Floor(pattern.Y));
|
||||
|
||||
if (moduleSizeEst1 = -1) then
|
||||
begin
|
||||
Result := (moduleSizeEst2 / 7);
|
||||
exit
|
||||
end;
|
||||
|
||||
if (moduleSizeEst2 = -1) then
|
||||
begin
|
||||
Result := (moduleSizeEst1 / 7);
|
||||
exit
|
||||
end;
|
||||
|
||||
Result := ((moduleSizeEst1 + moduleSizeEst2) / 14);
|
||||
end;
|
||||
|
||||
class function TDetector.computeDimension(topLeft: IResultPoint;
|
||||
topRight: IResultPoint; bottomLeft: IResultPoint; moduleSize: Single;
|
||||
var dimension: Integer): boolean;
|
||||
|
||||
var
|
||||
tltrCentersDimension, tlblCentersDimension: Integer;
|
||||
begin
|
||||
tltrCentersDimension :=
|
||||
round((TResultPointHelpers.distance(topLeft, topRight) / moduleSize));
|
||||
tlblCentersDimension :=
|
||||
round((TResultPointHelpers.distance(topLeft, bottomLeft) / moduleSize));
|
||||
dimension := TMathUtils.Asr
|
||||
((tltrCentersDimension + tlblCentersDimension), 1) + 7;
|
||||
|
||||
case (dimension and 3) of
|
||||
0: inc(dimension); // below a valid dimension
|
||||
1: ; // exactly on a valid dimension e.g. 21, 25, 29, ..., 177
|
||||
2: dec(dimension); // above a valid dimension
|
||||
3: Exit(False); // exactly between two valid dimensions
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
class function TDetector.createTransform(const topLeft, topRight, bottomLeft,
|
||||
AlignmentPattern: IResultPoint; const dimension: Integer)
|
||||
: TPerspectiveTransform;
|
||||
var
|
||||
bottomRightX, bottomRightY, sourceBottomRightX, sourceBottomRightY,
|
||||
dimMinusThree: Single;
|
||||
begin
|
||||
dimMinusThree := (dimension - 3.5);
|
||||
if (AlignmentPattern <> nil) then
|
||||
begin
|
||||
bottomRightX := AlignmentPattern.X;
|
||||
bottomRightY := AlignmentPattern.Y;
|
||||
sourceBottomRightX := (dimMinusThree - 3);
|
||||
sourceBottomRightY := sourceBottomRightX;
|
||||
end
|
||||
else
|
||||
begin
|
||||
bottomRightX := ((topRight.X - topLeft.X) + bottomLeft.X);
|
||||
bottomRightY := ((topRight.Y - topLeft.Y) + bottomLeft.Y);
|
||||
sourceBottomRightX := dimMinusThree;
|
||||
sourceBottomRightY := sourceBottomRightX;
|
||||
end;
|
||||
|
||||
Result := TPerspectiveTransform.quadrilateralToQuadrilateral(3.5, 3.5,
|
||||
dimMinusThree, 3.5, sourceBottomRightX, sourceBottomRightY, 3.5,
|
||||
dimMinusThree, topLeft.X, topLeft.Y, topRight.X, topRight.Y, bottomRightX,
|
||||
bottomRightY, bottomLeft.X, bottomLeft.Y);
|
||||
end;
|
||||
|
||||
function TDetector.detect: TDetectorResult;
|
||||
begin
|
||||
Result := self.detect(nil)
|
||||
end;
|
||||
|
||||
function TDetector.detect(const hints: TDictionary<TDecodeHintType, TObject>)
|
||||
: TDetectorResult;
|
||||
var
|
||||
obj: TObject;
|
||||
info: TFinderPatternInfo;
|
||||
finder: TFinderPatternFinder;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
if ((hints = nil) or
|
||||
(not hints.ContainsKey(TDecodeHintType.NEED_RESULT_POINT_CALLBACK))) then
|
||||
FResultPointCallback := nil
|
||||
else
|
||||
begin
|
||||
obj := hints[TDecodeHintType.NEED_RESULT_POINT_CALLBACK];
|
||||
if (obj is TResultPointEventObject) then
|
||||
FResultPointCallback := TResultPointEventObject(obj).Event;
|
||||
end;
|
||||
|
||||
finder := TFinderPatternFinder.Create(FImage, ResultPointCallback);
|
||||
try
|
||||
info := finder.find(hints);
|
||||
if Assigned(info) then
|
||||
begin
|
||||
Result := processFinderPatternInfo(info);
|
||||
info.Free;
|
||||
end;
|
||||
finally
|
||||
finder.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TDetector.processFinderPatternInfo(const info: TFinderPatternInfo)
|
||||
: TDetectorResult;
|
||||
var
|
||||
topLeft, topRight, bottomLeft: IFinderPattern;
|
||||
moduleSize, bottomRightX, bottomRightY, correctionToTopLeft: Single;
|
||||
dimension, i, modulesBetweenFPCenters, estAlignmentX, estAlignmentY: Integer;
|
||||
points: TArray<IResultPoint>;
|
||||
provisionalVersion: TVersion;
|
||||
transform: TPerspectiveTransform;
|
||||
bits: TBitMatrix;
|
||||
AlignmentPattern: IAlignmentPattern;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
topLeft := info.topLeft;
|
||||
topRight := info.topRight;
|
||||
bottomLeft := info.bottomLeft;
|
||||
|
||||
moduleSize := calculateModuleSize(topLeft, topRight, bottomLeft);
|
||||
if (moduleSize < 1.0) then
|
||||
exit;
|
||||
|
||||
if (not TDetector.computeDimension(topLeft, topRight, bottomLeft, moduleSize,
|
||||
dimension)) then
|
||||
exit;
|
||||
|
||||
provisionalVersion := TVersion.getProvisionalVersionForDimension(dimension);
|
||||
if (provisionalVersion = nil) then
|
||||
exit;
|
||||
|
||||
modulesBetweenFPCenters := (provisionalVersion.DimensionForVersion - 7);
|
||||
|
||||
AlignmentPattern := nil;
|
||||
// Anything above version 1 has an alignment pattern
|
||||
if (Length(provisionalVersion.AlignmentPatternCenters) > 0) then
|
||||
begin
|
||||
// Guess where a "bottom right" finder pattern would have been
|
||||
bottomRightX := (topRight.X - topLeft.X) + bottomLeft.X;
|
||||
bottomRightY := (topRight.Y - topLeft.Y) + bottomLeft.Y;
|
||||
|
||||
// Estimate that alignment pattern is closer by 3 modules
|
||||
// from "bottom right" to known top left location
|
||||
correctionToTopLeft := (1.0 - (3.0 / modulesBetweenFPCenters));
|
||||
estAlignmentX :=
|
||||
Floor(topLeft.X + (correctionToTopLeft * (bottomRightX - topLeft.X)));
|
||||
estAlignmentY :=
|
||||
Floor(topLeft.Y + (correctionToTopLeft * (bottomRightY - topLeft.Y)));
|
||||
i := 4;
|
||||
while ((i <= 16)) do
|
||||
begin
|
||||
AlignmentPattern := findAlignmentInRegion(moduleSize, estAlignmentX,
|
||||
estAlignmentY, i);
|
||||
if (AlignmentPattern <> nil) then
|
||||
break;
|
||||
i := (i shl 1);
|
||||
end;
|
||||
// If we didn't find alignment pattern... well try anyway without it
|
||||
end;
|
||||
|
||||
transform := TDetector.createTransform(topLeft, topRight, bottomLeft,
|
||||
AlignmentPattern, dimension);
|
||||
try
|
||||
bits := TDetector.sampleGrid(FImage, transform, dimension);
|
||||
finally
|
||||
transform.Free;
|
||||
end;
|
||||
|
||||
if (bits = nil) then
|
||||
exit;
|
||||
|
||||
if (AlignmentPattern = nil) then
|
||||
points := TArray<IResultPoint>.Create(bottomLeft, topLeft, topRight)
|
||||
else
|
||||
points := TArray<IResultPoint>.Create(bottomLeft, topLeft, topRight,
|
||||
AlignmentPattern);
|
||||
|
||||
Result := TDetectorResult.Create(bits, points);
|
||||
end;
|
||||
|
||||
function TDetector.findAlignmentInRegion(const overallEstModuleSize: Single;
|
||||
const estAlignmentX, estAlignmentY: Integer; const allowanceFactor: Single)
|
||||
: IAlignmentPattern;
|
||||
var
|
||||
allowance, alignmentAreaRightX, alignmentAreaLeftX, alignmentAreaTopY,
|
||||
alignmentAreaBottomY: Integer;
|
||||
alignmentFinder: TAlignmentPatternFinder;
|
||||
candidateResult: IAlignmentPattern;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
allowance := Floor(allowanceFactor * overallEstModuleSize);
|
||||
alignmentAreaLeftX := System.Math.Max(0, (estAlignmentX - allowance));
|
||||
alignmentAreaRightX := System.Math.Min((FImage.Width - 1),
|
||||
(estAlignmentX + allowance));
|
||||
|
||||
if ((alignmentAreaRightX - alignmentAreaLeftX) < (overallEstModuleSize * 3))
|
||||
then
|
||||
exit;
|
||||
|
||||
alignmentAreaTopY := System.Math.Max(0, (estAlignmentY - allowance));
|
||||
|
||||
alignmentAreaBottomY := System.Math.Min((FImage.Height - 1),
|
||||
(estAlignmentY + allowance));
|
||||
|
||||
alignmentFinder := TAlignmentPatternFinder.Create(FImage, alignmentAreaLeftX,
|
||||
alignmentAreaTopY, (alignmentAreaRightX - alignmentAreaLeftX),
|
||||
(alignmentAreaBottomY - alignmentAreaTopY), overallEstModuleSize,
|
||||
self.ResultPointCallback);
|
||||
|
||||
candidateResult := alignmentFinder.find;
|
||||
|
||||
if candidateResult = nil then
|
||||
Result := nil
|
||||
else
|
||||
Result := candidateResult;
|
||||
|
||||
FreeAndNil(alignmentFinder);
|
||||
end;
|
||||
|
||||
class function TDetector.sampleGrid(image: TBitMatrix;
|
||||
transform: TPerspectiveTransform; dimension: Integer): TBitMatrix;
|
||||
begin
|
||||
Result := TDefaultGridSampler.sampleGrid(image, dimension, dimension,
|
||||
transform)
|
||||
end;
|
||||
|
||||
function TDetector.sizeOfBlackWhiteBlackRun(fromX: Integer; fromY: Integer;
|
||||
toX: Integer; toY: Integer): Single;
|
||||
var
|
||||
steep: boolean;
|
||||
temp, dx, dy, xstep, ystep, X, Y, state, xlimit, realx, realy, error: Integer;
|
||||
begin
|
||||
steep := Abs(toY - fromY) > Abs(toX - fromX);
|
||||
if (steep) then
|
||||
begin
|
||||
temp := fromX;
|
||||
fromX := fromY;
|
||||
fromY := temp;
|
||||
temp := toX;
|
||||
toX := toY;
|
||||
toY := temp
|
||||
end;
|
||||
|
||||
dx := Abs(toX - fromX);
|
||||
dy := Abs(toY - fromY);
|
||||
error := TMathUtils.Asr(-dx, 1);
|
||||
if (fromX < toX) then
|
||||
xstep := 1
|
||||
else
|
||||
xstep := -1;
|
||||
|
||||
if (fromY < toY) then
|
||||
ystep := 1
|
||||
else
|
||||
ystep := -1;
|
||||
|
||||
state := 0;
|
||||
xlimit := (toX + xstep);
|
||||
X := fromX;
|
||||
Y := fromY;
|
||||
while ((X <> xlimit)) do
|
||||
begin
|
||||
if steep then
|
||||
realx := Y
|
||||
else
|
||||
realx := X;
|
||||
if steep then
|
||||
realy := X
|
||||
else
|
||||
realy := Y;
|
||||
|
||||
if ((state = 1) = FImage[realx, realy]) then
|
||||
begin
|
||||
if (state = 2) then
|
||||
begin
|
||||
Result := TMathUtils.distance(X, Y, fromX, fromY);
|
||||
exit
|
||||
end;
|
||||
inc(state)
|
||||
end;
|
||||
|
||||
inc(error, dy);
|
||||
if (error > 0) then
|
||||
begin
|
||||
if (Y = toY) then
|
||||
break;;
|
||||
inc(Y, ystep);
|
||||
dec(error, dx)
|
||||
end;
|
||||
inc(X, xstep)
|
||||
end;
|
||||
|
||||
if (state = 2) then
|
||||
begin
|
||||
Result := TMathUtils.distance((toX + xstep), toY, fromX, fromY);
|
||||
exit
|
||||
end;
|
||||
|
||||
Result := Single.NaN;
|
||||
end;
|
||||
|
||||
function TDetector.sizeOfBlackWhiteBlackRunBothWays(fromX: Integer;
|
||||
fromY: Integer; toX: Integer; toY: Integer): Single;
|
||||
var
|
||||
scale: Double;
|
||||
otherToX, otherToY: Integer;
|
||||
begin
|
||||
Result := sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
|
||||
scale := 1;
|
||||
otherToX := (fromX - (toX - fromX));
|
||||
if (otherToX < 0) then
|
||||
begin
|
||||
scale := (fromX / (fromX - otherToX));
|
||||
otherToX := 0
|
||||
end
|
||||
else if (otherToX >= FImage.Width) then
|
||||
begin
|
||||
scale := (((FImage.Width - 1) - fromX) / (otherToX - fromX));
|
||||
otherToX := (FImage.Width - 1)
|
||||
end;
|
||||
otherToY := Round(fromY - ((toY - fromY) * scale));
|
||||
scale := 1;
|
||||
if (otherToY < 0) then
|
||||
begin
|
||||
scale := fromY / (fromY - otherToY);
|
||||
otherToY := 0
|
||||
end
|
||||
else if (otherToY >= FImage.Height) then
|
||||
begin
|
||||
scale := (((FImage.Height - 1) - fromY) / (otherToY - fromY));
|
||||
otherToY := (FImage.Height - 1)
|
||||
end;
|
||||
otherToX := Round(fromX + (otherToX - fromX) * scale);
|
||||
Result := Result + self.sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX,
|
||||
otherToY);
|
||||
|
||||
Result := (Result - 1);
|
||||
end;
|
||||
|
||||
end.
|
||||
Vendored
+147
@@ -0,0 +1,147 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.FinderPattern;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
ZXing.ResultPoint;
|
||||
|
||||
type
|
||||
|
||||
/// <summary>
|
||||
/// As we did for ResultPoint, we use an interfaced object to implement automatic deallocation
|
||||
/// of TAligmnentPattern instances
|
||||
/// the actual implementation of this interface is in unit ZXing.QrCode.Internal.FinderPatternImplementation
|
||||
/// </summary>
|
||||
IFinderPattern = interface(IResultPoint)
|
||||
['{EF95BB2F-8863-4A83-840E-699779592EC0}']
|
||||
function GetCount:integer;
|
||||
procedure SetCount(value:integer);
|
||||
/// <summary> <p>Determines if this finder pattern "about equals" a finder pattern at the stated
|
||||
/// position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
|
||||
/// </summary>
|
||||
function aboutEquals(const moduleSize, i, j: Single): Boolean;
|
||||
|
||||
/// <summary>
|
||||
/// Combines this object's current estimate of a finder pattern position and module size
|
||||
/// with a new estimate. It returns a new {@code FinderPattern} containing a weighted average
|
||||
/// based on count.
|
||||
/// </summary>
|
||||
/// <param name="i">The i.</param>
|
||||
/// <param name="j">The j.</param>
|
||||
/// <param name="newModuleSize">New size of the module.</param>
|
||||
/// <returns></returns>
|
||||
function combineEstimate(const i, j, newModuleSize: Single): IFinderPattern;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the estimated module.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The size of the estimated module.
|
||||
/// </value>
|
||||
function estimatedModuleSize : Single;
|
||||
property count : Integer read GetCount write SetCount;
|
||||
end;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// contains all static methods for using IFinderPattern instances
|
||||
/// </summary>
|
||||
TFinderPatternHelpers= class(TResultPointHelpers)
|
||||
class function CreateFinderPattern(const posX, posY, estimatedModuleSize: Single;
|
||||
const count: Integer):IFinderPattern; overload;
|
||||
class function CreateFinderPattern(const posX, posY, estimatedModuleSize: Single):IFinderPattern; overload;
|
||||
|
||||
class procedure orderBestPatterns(const patterns: TArray<IFinderPattern>); static;
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
uses ZXing.QrCode.Internal.FinderPatternImplementation;
|
||||
|
||||
class function TFinderPatternHelpers.CreateFinderPattern(const posX, posY, estimatedModuleSize: Single; const count: Integer):IFinderPattern;
|
||||
begin
|
||||
result := ZXing.QrCode.Internal.FinderPatternImplementation.NewFinderPattern(posX, posY, estimatedModuleSize ,count);
|
||||
end;
|
||||
|
||||
class function TFinderPatternHelpers.CreateFinderPattern(const posX, posY, estimatedModuleSize: Single):IFinderPattern;
|
||||
begin
|
||||
result := ZXing.QrCode.Internal.FinderPatternImplementation.NewFinderPattern(posX, posY, estimatedModuleSize);
|
||||
end;
|
||||
|
||||
|
||||
|
||||
class procedure TFinderPatternHelpers.orderBestPatterns( const patterns: TArray<IFinderPattern>);
|
||||
var
|
||||
zeroOneDistance,
|
||||
oneTwoDistance,
|
||||
zeroTwoDistance: Single;
|
||||
pointA,
|
||||
pointB,
|
||||
pointC,
|
||||
temp: IFinderPattern;
|
||||
begin
|
||||
// Find distances between pattern centers
|
||||
zeroOneDistance := distance(patterns[0], patterns[1]);
|
||||
oneTwoDistance := distance(patterns[1], patterns[2]);
|
||||
zeroTwoDistance := distance(patterns[0], patterns[2]);
|
||||
|
||||
// Assume one closest to other two is B; A and C will just be guesses at first
|
||||
if ((oneTwoDistance >= zeroOneDistance) and
|
||||
(oneTwoDistance >= zeroTwoDistance)) then
|
||||
begin
|
||||
pointB := patterns[0];
|
||||
pointA := patterns[1];
|
||||
pointC := patterns[2];
|
||||
end
|
||||
else if ((zeroTwoDistance >= oneTwoDistance) and
|
||||
(zeroTwoDistance >= zeroOneDistance)) then
|
||||
begin
|
||||
pointB := patterns[1];
|
||||
pointA := patterns[0];
|
||||
pointC := patterns[2];
|
||||
end
|
||||
else
|
||||
begin
|
||||
pointB := patterns[2];
|
||||
pointA := patterns[0];
|
||||
pointC := patterns[1];
|
||||
end;
|
||||
|
||||
// Use cross product to figure out whether A and C are correct or flipped.
|
||||
// This asks whether BC x BA has a positive z component, which is the arrangement
|
||||
// we want for A, B, C. If it's negative, then we've got it flipped around and
|
||||
// should swap A and C.
|
||||
if (TResultPointHelpers.crossProductZ(pointA, pointB, pointC) < 0) then
|
||||
begin
|
||||
temp := pointA;
|
||||
pointA := pointC;
|
||||
pointC := temp;
|
||||
end;
|
||||
|
||||
patterns[0] := pointA;
|
||||
patterns[1] := pointB;
|
||||
patterns[2] := pointC;
|
||||
end;
|
||||
|
||||
|
||||
|
||||
end.
|
||||
Vendored
+977
@@ -0,0 +1,977 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.FinderPatternFinder;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Math,
|
||||
System.Generics.Defaults,
|
||||
System.Generics.Collections,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.DecodeHinttype,
|
||||
ZXing.QrCode.Internal.FinderPatternInfo,
|
||||
ZXing.QrCode.Internal.FinderPattern;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>This class attempts to find finder patterns in a QR Code. Finder patterns are the square
|
||||
/// markers at three corners of a QR Code.</p>
|
||||
///
|
||||
/// <p>This class is thread-safe but not reentrant. Each thread must allocate its own object.
|
||||
/// </summary>
|
||||
TFinderPatternFinder = class
|
||||
private
|
||||
const
|
||||
CENTER_QUORUM: Integer = 2;
|
||||
INTEGER_MATH_SHIFT: Integer = 8;
|
||||
var
|
||||
FImage: TBitMatrix;
|
||||
FPossibleCenters: TList<IFinderPattern>;
|
||||
FhasSkipped: Boolean;
|
||||
FCrossCheckStateCount: TArray<Integer>;
|
||||
FresultPointCallback: TResultPointCallback;
|
||||
|
||||
class function centerFromEnd(stateCount: TArray<Integer>; pEnd: Integer): Single; static;
|
||||
|
||||
function crossCheckDiagonal(startI: Integer; centerJ: Integer; maxCount: Integer; originalStateCountTotal: Integer): boolean;
|
||||
function crossCheckHorizontal(startJ: Integer; centerI: Integer; maxCount: Integer; originalStateCountTotal: Integer): Single;
|
||||
function crossCheckVertical(startI: Integer; centerJ: Integer; maxCount: Integer; originalStateCountTotal: Integer): Single;
|
||||
function findRowSkip: Integer;
|
||||
function haveMultiplyConfirmedCenters: boolean;
|
||||
function selectBestPatterns: TArray<IFinderPattern>;
|
||||
|
||||
function CrossCheckStateCount: TArray<Integer>;
|
||||
protected
|
||||
const
|
||||
/// <summary>
|
||||
/// 1 pixel/module times 3 modules/center
|
||||
/// </summary>
|
||||
MIN_SKIP: Integer = 3;
|
||||
/// <summary>
|
||||
/// support up to version 10 for mobile clients
|
||||
/// </summary>
|
||||
MAX_MODULES: Integer = 97;
|
||||
class function foundPatternCross(const stateCount: TArray<Integer>): Boolean; static;
|
||||
function handlePossibleCenter(stateCount: TArray<Integer>; i: Integer;
|
||||
j: Integer): boolean;
|
||||
property image: TBitMatrix read FImage;
|
||||
public
|
||||
/// <summary>
|
||||
/// <p>Creates a finder that will search the image for three finder patterns.</p>
|
||||
/// </summary>
|
||||
/// <param name="image">image to search</param>
|
||||
constructor Create(const image: TBitMatrix); overload;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FinderPatternFinder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="image">The image.</param>
|
||||
/// <param name="resultPointCallback">The result point callback.</param>
|
||||
constructor Create(const image: TBitMatrix;
|
||||
resultPointCallback: TResultPointCallback); overload;
|
||||
|
||||
destructor Destroy; override;
|
||||
|
||||
function find(const hints: TDictionary<TDecodeHintType, TObject>): TFinderPatternInfo; virtual;
|
||||
end;
|
||||
|
||||
TFurthestFromAverageComparator = class sealed(TInterfacedObject,
|
||||
IComparer<IFinderPattern>)
|
||||
private
|
||||
average: Single;
|
||||
public
|
||||
constructor Create(f: Single);
|
||||
function Compare(const Left, Right: IFinderPattern): Integer;
|
||||
end;
|
||||
|
||||
TCenterComparator = class sealed(TInterfacedObject, IComparer<IFinderPattern>)
|
||||
private
|
||||
average: Single;
|
||||
public
|
||||
constructor Create(f: Single);
|
||||
function Compare(const Left, Right: IFinderPattern): Integer;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TFinderPatternFinder }
|
||||
|
||||
constructor TFinderPatternFinder.Create(const image: TBitMatrix);
|
||||
begin
|
||||
Self.Create(image, TResultPointCallback(nil));
|
||||
end;
|
||||
|
||||
constructor TFinderPatternFinder.Create(const image: TBitMatrix;
|
||||
resultPointCallback: TResultPointCallback);
|
||||
begin
|
||||
FImage := image;
|
||||
FPossibleCenters := TList<IFinderPattern>.Create;
|
||||
FCrossCheckStateCount := TArray<Integer>.Create(0, 0, 0, 0, 0);
|
||||
FresultPointCallback := resultPointCallback;
|
||||
end;
|
||||
|
||||
destructor TFinderPatternFinder.Destroy;
|
||||
begin
|
||||
FImage := nil;
|
||||
|
||||
{for i := Pred(PossibleCenters.Count) downto 0 do
|
||||
begin
|
||||
PossibleCenters[i].Free;
|
||||
PossibleCenters.Delete(i);
|
||||
end;}
|
||||
|
||||
FPossibleCenters.Free;
|
||||
FCrossCheckStateCount := nil;
|
||||
|
||||
inherited;
|
||||
end;
|
||||
|
||||
class function TFinderPatternFinder.centerFromEnd(stateCount: TArray<Integer>;
|
||||
pEnd: Integer): Single;
|
||||
var
|
||||
aResult: Single;
|
||||
begin
|
||||
aResult := (pEnd - stateCount[4] - stateCount[3]) - (stateCount[2] / 2);
|
||||
|
||||
if (Single.IsNaN(aResult)) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
result := aResult;
|
||||
end;
|
||||
|
||||
function TFinderPatternFinder.crossCheckDiagonal(startI, centerJ, maxCount,
|
||||
originalStateCountTotal: Integer): boolean;
|
||||
var
|
||||
stateCount: TArray<Integer>;
|
||||
i, maxI, maxJ, stateCountTotal: Integer;
|
||||
begin
|
||||
stateCount := CrossCheckStateCount;
|
||||
try
|
||||
i := 0;
|
||||
while ((((startI >= i) and (centerJ >= i)) and self.image[(centerJ - i),
|
||||
(startI - i)])) do
|
||||
begin
|
||||
inc(stateCount[2]);
|
||||
inc(i)
|
||||
end;
|
||||
if ((startI < i) or (centerJ < i)) then
|
||||
begin
|
||||
result := false;
|
||||
exit
|
||||
end;
|
||||
while (((((startI >= i) and (centerJ >= i)) and
|
||||
not self.image[(centerJ - i), (startI - i)]) and
|
||||
(stateCount[1] <= maxCount))) do
|
||||
begin
|
||||
inc(stateCount[1]);
|
||||
inc(i)
|
||||
end;
|
||||
if (((startI < i) or (centerJ < i)) or (stateCount[1] > maxCount)) then
|
||||
begin
|
||||
result := false;
|
||||
exit
|
||||
end;
|
||||
while (((((startI >= i) and (centerJ >= i)) and self.image[(centerJ - i),
|
||||
(startI - i)]) and (stateCount[0] <= maxCount))) do
|
||||
begin
|
||||
inc(stateCount[0]);
|
||||
inc(i)
|
||||
end;
|
||||
if (stateCount[0] > maxCount) then
|
||||
begin
|
||||
result := false;
|
||||
exit
|
||||
end;
|
||||
maxI := self.image.Height;
|
||||
maxJ := self.image.Width;
|
||||
i := 1;
|
||||
while (((((startI + i) < maxI) and ((centerJ + i) < maxJ)) and
|
||||
self.image[(centerJ + i), (startI + i)])) do
|
||||
begin
|
||||
inc(stateCount[2]);
|
||||
inc(i)
|
||||
end;
|
||||
if (((startI + i) >= maxI) or ((centerJ + i) >= maxJ)) then
|
||||
begin
|
||||
result := false;
|
||||
exit
|
||||
end;
|
||||
while ((((((startI + i) < maxI) and ((centerJ + i) < maxJ)) and
|
||||
not self.image[(centerJ + i), (startI + i)]) and
|
||||
(stateCount[3] < maxCount))) do
|
||||
begin
|
||||
inc(stateCount[3]);
|
||||
inc(i)
|
||||
end;
|
||||
if ((((startI + i) >= maxI) or ((centerJ + i) >= maxJ)) or
|
||||
(stateCount[3] >= maxCount)) then
|
||||
begin
|
||||
result := false;
|
||||
exit
|
||||
end;
|
||||
while ((((((startI + i) < maxI) and ((centerJ + i) < maxJ)) and
|
||||
self.image[(centerJ + i), (startI + i)]) and
|
||||
(stateCount[4] < maxCount))) do
|
||||
begin
|
||||
inc(stateCount[4]);
|
||||
inc(i)
|
||||
end;
|
||||
if (stateCount[4] >= maxCount) then
|
||||
begin
|
||||
result := false;
|
||||
exit
|
||||
end;
|
||||
|
||||
stateCountTotal :=
|
||||
((((stateCount[0] + stateCount[1]) + stateCount[2]) + stateCount[3]) +
|
||||
stateCount[4]);
|
||||
|
||||
Result := (Abs(stateCountTotal - originalStateCountTotal) <
|
||||
(2 * originalStateCountTotal)) and TFinderPatternFinder.foundPatternCross
|
||||
(stateCount);
|
||||
|
||||
finally
|
||||
stateCount := nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TFinderPatternFinder.crossCheckHorizontal(startJ: Integer;
|
||||
centerI: Integer; maxCount: Integer;
|
||||
originalStateCountTotal: Integer): Single;
|
||||
var
|
||||
stateCount: TArray<Integer>;
|
||||
maxJ, j, stateCountTotal: Integer;
|
||||
|
||||
begin
|
||||
maxJ := FImage.Width;
|
||||
stateCount := CrossCheckStateCount;
|
||||
|
||||
try
|
||||
|
||||
j := startJ;
|
||||
while (((j >= 0) and FImage[j, centerI])) do
|
||||
begin
|
||||
inc(stateCount[2]);
|
||||
dec(j)
|
||||
end;
|
||||
|
||||
if (j < 0) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
while ((((j >= 0) and not FImage[j, centerI]) and
|
||||
(stateCount[1] <= maxCount))) do
|
||||
begin
|
||||
inc(stateCount[1]);
|
||||
dec(j)
|
||||
end;
|
||||
|
||||
if ((j < 0) or (stateCount[1] > maxCount)) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
while ((((j >= 0) and FImage[j, centerI]) and
|
||||
(stateCount[0] <= maxCount))) do
|
||||
begin
|
||||
inc(stateCount[0]);
|
||||
dec(j)
|
||||
end;
|
||||
|
||||
if (stateCount[0] > maxCount) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
j := (startJ + 1);
|
||||
|
||||
while (((j < maxJ) and FImage[j, centerI])) do
|
||||
begin
|
||||
inc(stateCount[2]);
|
||||
inc(j)
|
||||
end;
|
||||
|
||||
if (j = maxJ) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
while ((((j < maxJ) and not FImage[j, centerI]) and
|
||||
(stateCount[3] < maxCount))) do
|
||||
begin
|
||||
inc(stateCount[3]);
|
||||
inc(j)
|
||||
end;
|
||||
|
||||
if ((j = maxJ) or (stateCount[3] >= maxCount)) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
while (((j < maxJ) and FImage[j, centerI]) and
|
||||
(stateCount[4] < maxCount)) do
|
||||
begin
|
||||
inc(stateCount[4]);
|
||||
inc(j)
|
||||
end;
|
||||
|
||||
if (stateCount[4] >= maxCount) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
stateCountTotal :=
|
||||
((((stateCount[0] + stateCount[1]) + stateCount[2]) + stateCount[3]) +
|
||||
stateCount[4]);
|
||||
|
||||
if (5 * Abs(stateCountTotal - originalStateCountTotal) >=
|
||||
originalStateCountTotal) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
if TFinderPatternFinder.foundPatternCross(stateCount) then
|
||||
begin
|
||||
result := TFinderPatternFinder.centerFromEnd(stateCount, j);
|
||||
exit;
|
||||
end;
|
||||
|
||||
finally
|
||||
stateCount := nil;
|
||||
end;
|
||||
|
||||
result := -1;
|
||||
|
||||
end;
|
||||
|
||||
function TFinderPatternFinder.CrossCheckStateCount: TArray<Integer>;
|
||||
begin
|
||||
FCrossCheckStateCount[0] := 0;
|
||||
FCrossCheckStateCount[1] := 0;
|
||||
FCrossCheckStateCount[2] := 0;
|
||||
FCrossCheckStateCount[3] := 0;
|
||||
FCrossCheckStateCount[4] := 0;
|
||||
result := FCrossCheckStateCount;
|
||||
end;
|
||||
|
||||
function TFinderPatternFinder.crossCheckVertical(startI: Integer;
|
||||
centerJ: Integer; maxCount: Integer;
|
||||
originalStateCountTotal: Integer): Single;
|
||||
var
|
||||
stateCount: TArray<Integer>;
|
||||
maxI, i, stateCountTotal: Integer;
|
||||
begin
|
||||
|
||||
maxI := FImage.Height;
|
||||
stateCount := CrossCheckStateCount;
|
||||
i := startI;
|
||||
try
|
||||
while (((i >= 0) and FImage[centerJ, i])) do
|
||||
begin
|
||||
inc(stateCount[2]);
|
||||
dec(i)
|
||||
end;
|
||||
|
||||
if (i < 0) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
while ((((i >= 0) and not FImage[centerJ, i]) and
|
||||
(stateCount[1] <= maxCount))) do
|
||||
begin
|
||||
inc(stateCount[1]);
|
||||
dec(i)
|
||||
end;
|
||||
|
||||
if ((i < 0) or (stateCount[1] > maxCount)) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
while ((((i >= 0) and FImage[centerJ, i]) and
|
||||
(stateCount[0] <= maxCount))) do
|
||||
begin
|
||||
inc(stateCount[0]);
|
||||
dec(i)
|
||||
end;
|
||||
|
||||
if (stateCount[0] > maxCount) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
i := (startI + 1);
|
||||
|
||||
while (((i < maxI) and FImage[centerJ, i])) do
|
||||
begin
|
||||
inc(stateCount[2]);
|
||||
inc(i)
|
||||
end;
|
||||
|
||||
if (i = maxI) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
while ((((i < maxI) and not FImage[centerJ, i]) and
|
||||
(stateCount[3] < maxCount))) do
|
||||
begin
|
||||
inc(stateCount[3]);
|
||||
inc(i)
|
||||
end;
|
||||
|
||||
if ((i = maxI) or (stateCount[3] >= maxCount)) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
while ((((i < maxI) and FImage[centerJ, i]) and
|
||||
(stateCount[4] < maxCount))) do
|
||||
begin
|
||||
inc(stateCount[4]);
|
||||
inc(i)
|
||||
end;
|
||||
|
||||
if (stateCount[4] >= maxCount) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
stateCountTotal :=
|
||||
((((stateCount[0] + stateCount[1]) + stateCount[2]) + stateCount[3]) +
|
||||
stateCount[4]);
|
||||
|
||||
if ((5 * Abs(stateCountTotal - originalStateCountTotal)) >=
|
||||
(2 * originalStateCountTotal)) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
if TFinderPatternFinder.foundPatternCross(stateCount) then
|
||||
begin
|
||||
result := TFinderPatternFinder.centerFromEnd(stateCount, i);
|
||||
exit;
|
||||
end;
|
||||
|
||||
finally
|
||||
stateCount := nil;
|
||||
end;
|
||||
|
||||
result := -1;
|
||||
|
||||
end;
|
||||
|
||||
function TFinderPatternFinder.find(
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TFinderPatternInfo;
|
||||
var
|
||||
tryHarder,
|
||||
done, confirmed: Boolean;
|
||||
maxI, maxJ, iSkip,
|
||||
i, currentState, j, rowSkip: Integer;
|
||||
stateCount: TArray<Integer>;
|
||||
patternInfo: TArray<IFinderPattern>;
|
||||
resultInfo: TArray<IResultPoint>;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
tryHarder := (hints <> nil) and (hints.ContainsKey(ZXing.DecodeHinttype.TRY_HARDER));
|
||||
maxI := Self.image.Height;
|
||||
maxJ := Self.image.Width;
|
||||
// We are looking for black/white/black/white/black modules in
|
||||
// 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
|
||||
// Let's assume that the maximum version QR Code we support takes up 1/4 the height of the
|
||||
// image, and then account for the center being 3 modules in size. This gives the smallest
|
||||
// number of pixels the center could be, so skip this often. When trying harder, look for all
|
||||
// QR versions regardless of how dense they are.
|
||||
iSkip := (3 * maxI) div (4 * MAX_MODULES);
|
||||
if (iSkip < MIN_SKIP) or (tryHarder)
|
||||
then
|
||||
iSkip := MIN_SKIP;
|
||||
|
||||
done := false;
|
||||
stateCount := TArray<Integer>.Create(0, 0, 0, 0, 0);
|
||||
try
|
||||
i := (iSkip - 1);
|
||||
while ((i < maxI) and (not done)) do
|
||||
begin
|
||||
// Get a row of black/white values
|
||||
stateCount[0] := 0;
|
||||
stateCount[1] := 0;
|
||||
stateCount[2] := 0;
|
||||
stateCount[3] := 0;
|
||||
stateCount[4] := 0;
|
||||
currentState := 0;
|
||||
|
||||
j := 0;
|
||||
while (j < maxJ) do
|
||||
begin
|
||||
if (FImage[j, i]) then
|
||||
begin
|
||||
// Black pixel
|
||||
if ((currentState and 1) = 1)
|
||||
then
|
||||
Inc(currentState);
|
||||
Inc(stateCount[currentState]);
|
||||
end else
|
||||
begin
|
||||
// White pixel
|
||||
if ((currentState and 1) = 0) then
|
||||
begin
|
||||
// Counting black pixels
|
||||
if (currentState = 4) then
|
||||
begin
|
||||
// A winner?
|
||||
if TFinderPatternFinder.foundPatternCross(stateCount) then
|
||||
begin
|
||||
// Yes
|
||||
confirmed := handlePossibleCenter(stateCount, i, j);
|
||||
if (confirmed) then
|
||||
begin
|
||||
// Start examining every other line. Checking each line turned out to be too
|
||||
// expensive and didn't improve performance.
|
||||
iSkip := 2;
|
||||
if (FhasSkipped)
|
||||
then
|
||||
done := haveMultiplyConfirmedCenters()
|
||||
else
|
||||
begin
|
||||
rowSkip := findRowSkip();
|
||||
if (rowSkip > stateCount[2]) then
|
||||
begin
|
||||
// Skip rows between row of lower confirmed center
|
||||
// and top of presumed third confirmed center
|
||||
// but back up a bit to get a full chance of detecting
|
||||
// it, entire width of center of finder pattern
|
||||
|
||||
// Skip by rowSkip, but back off by stateCount[2] (size of last center
|
||||
// of pattern we saw) to be conservative, and also back off by iSkip which
|
||||
// is about to be re-added
|
||||
Inc(i, ((rowSkip - stateCount[2]) - iSkip));
|
||||
j := (maxJ - 1);
|
||||
end;
|
||||
end;
|
||||
end else
|
||||
begin
|
||||
stateCount[0] := stateCount[2];
|
||||
stateCount[1] := stateCount[3];
|
||||
stateCount[2] := stateCount[4];
|
||||
stateCount[3] := 1;
|
||||
stateCount[4] := 0;
|
||||
currentState := 3;
|
||||
continue;
|
||||
end;
|
||||
// Clear state to start looking again
|
||||
currentState := 0;
|
||||
stateCount[0] := 0;
|
||||
stateCount[1] := 0;
|
||||
stateCount[2] := 0;
|
||||
stateCount[3] := 0;
|
||||
stateCount[4] := 0
|
||||
end else
|
||||
begin
|
||||
// No, shift counts back by two
|
||||
stateCount[0] := stateCount[2];
|
||||
stateCount[1] := stateCount[3];
|
||||
stateCount[2] := stateCount[4];
|
||||
stateCount[3] := 1;
|
||||
stateCount[4] := 0;
|
||||
currentState := 3;
|
||||
end;
|
||||
end else
|
||||
begin
|
||||
Inc(currentState);
|
||||
Inc(stateCount[currentState]);
|
||||
end;
|
||||
end else
|
||||
// Counting white pixels
|
||||
Inc(stateCount[currentState]);
|
||||
end;
|
||||
Inc(j);
|
||||
end;
|
||||
|
||||
if foundPatternCross(stateCount) then
|
||||
begin
|
||||
confirmed := Self.handlePossibleCenter(stateCount, i, maxJ);
|
||||
if (confirmed) then
|
||||
begin
|
||||
iSkip := stateCount[0];
|
||||
if (FhasSkipped)
|
||||
then
|
||||
// Found a third one
|
||||
done := self.haveMultiplyConfirmedCenters
|
||||
end;
|
||||
end;
|
||||
Inc(i, iSkip);
|
||||
end;
|
||||
|
||||
patternInfo := selectBestPatterns();
|
||||
if (patternInfo = nil)
|
||||
then
|
||||
exit;
|
||||
|
||||
TFinderPatternHelpers.orderBestPatterns(patternInfo);
|
||||
Result := TFinderPatternInfo.Create(patternInfo);
|
||||
finally
|
||||
resultInfo := nil;
|
||||
stateCount := nil;
|
||||
patternInfo := nil;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TFinderPatternFinder.findRowSkip: Integer;
|
||||
var
|
||||
center: IFinderPattern;
|
||||
firstConfirmedCenter: IResultPoint;
|
||||
begin
|
||||
Result := 0;
|
||||
|
||||
if (FPossibleCenters.Count > 1) then
|
||||
begin
|
||||
firstConfirmedCenter := nil;
|
||||
|
||||
for center in FPossibleCenters do
|
||||
begin
|
||||
if (center.Count >= 2) then
|
||||
begin
|
||||
if (firstConfirmedCenter <> nil) then
|
||||
begin
|
||||
FhasSkipped := true;
|
||||
Result := Floor(Abs(firstConfirmedCenter.X - center.X) -
|
||||
Abs(firstConfirmedCenter.Y - center.Y)) div 2;
|
||||
exit;
|
||||
end;
|
||||
firstConfirmedCenter := center;
|
||||
end
|
||||
end
|
||||
end;
|
||||
end;
|
||||
|
||||
/// <param name="stateCount">count of black/white/black/white/black pixels just read
|
||||
/// </param>
|
||||
/// <returns> true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios
|
||||
/// used by finder patterns to be considered a match
|
||||
/// </returns>
|
||||
class function TFinderPatternFinder.foundPatternCross(
|
||||
const stateCount: TArray<Integer>): Boolean;
|
||||
var
|
||||
i, totalModuleSize,
|
||||
count, moduleSize,
|
||||
maxVariance: Integer;
|
||||
begin
|
||||
Result := false;
|
||||
|
||||
totalModuleSize := 0;
|
||||
for i := 0 to Pred(5) do
|
||||
begin
|
||||
count := stateCount[i];
|
||||
if (count = 0)
|
||||
then
|
||||
exit;
|
||||
Inc(totalModuleSize, count);
|
||||
end;
|
||||
|
||||
if (totalModuleSize < 7)
|
||||
then
|
||||
exit;
|
||||
|
||||
moduleSize := ((totalModuleSize shl 8) div 7);
|
||||
maxVariance := (moduleSize div 2);
|
||||
// Allow less than 50% variance from 1-1-3-1-1 proportions
|
||||
Result := (Abs(moduleSize - (stateCount[0] shl INTEGER_MATH_SHIFT)) < maxVariance) and
|
||||
(Abs(moduleSize - (stateCount[1] shl INTEGER_MATH_SHIFT)) < maxVariance) and
|
||||
(Abs(3 * moduleSize - (stateCount[2] shl INTEGER_MATH_SHIFT)) < (3 * maxVariance)) and
|
||||
(Abs(moduleSize - (stateCount[3] shl INTEGER_MATH_SHIFT)) < maxVariance) and
|
||||
(Abs(moduleSize - (stateCount[4] shl INTEGER_MATH_SHIFT)) < maxVariance);
|
||||
end;
|
||||
|
||||
function TFinderPatternFinder.handlePossibleCenter(stateCount: TArray<Integer>;
|
||||
i, j: Integer): boolean;
|
||||
var
|
||||
stateCountTotal, index: Integer;
|
||||
centerJ, centerI: Single;
|
||||
center, point: IFinderPattern;
|
||||
found: boolean;
|
||||
estimatedModuleSize: Single;
|
||||
begin
|
||||
Result := false;
|
||||
|
||||
center := nil;
|
||||
stateCountTotal := ((((stateCount[0] + stateCount[1]) + stateCount[2]) +
|
||||
stateCount[3]) + stateCount[4]);
|
||||
|
||||
centerJ := TFinderPatternFinder.centerFromEnd(stateCount, j);
|
||||
|
||||
if (centerJ = -1)
|
||||
then
|
||||
exit;
|
||||
|
||||
centerI := self.crossCheckVertical(i, Floor(centerJ), stateCount[2],
|
||||
stateCountTotal);
|
||||
|
||||
if (centerI = -1)
|
||||
then
|
||||
exit;
|
||||
|
||||
centerJ := crossCheckHorizontal(Floor(centerJ), Floor(centerI), stateCount[2],
|
||||
stateCountTotal);
|
||||
|
||||
if ((centerJ <> -1) and (self.crossCheckDiagonal(Floor(centerI), Floor(centerJ), stateCount[2],
|
||||
stateCountTotal))) then
|
||||
begin
|
||||
estimatedModuleSize := stateCountTotal / 7;
|
||||
found := false;
|
||||
index := 0;
|
||||
while ((index < FPossibleCenters.Count)) do
|
||||
begin
|
||||
center := FPossibleCenters[index];
|
||||
if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) then
|
||||
begin
|
||||
FPossibleCenters[index] := nil;
|
||||
FPossibleCenters.Delete(index);
|
||||
FPossibleCenters.Insert(index, center.combineEstimate(centerI, centerJ,
|
||||
estimatedModuleSize));
|
||||
found := true;
|
||||
break;
|
||||
end;
|
||||
|
||||
inc(index)
|
||||
end;
|
||||
|
||||
if (not found) then
|
||||
begin
|
||||
point := TFinderPatternHelpers.CreateFinderPattern(centerJ, centerI, estimatedModuleSize);
|
||||
FPossibleCenters.Add(point);
|
||||
|
||||
if Assigned(FresultPointCallback)
|
||||
then
|
||||
FresultPointCallBack(point);
|
||||
end;
|
||||
Result := true;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TFinderPatternFinder.haveMultiplyConfirmedCenters: boolean;
|
||||
var
|
||||
confirmedCount, max, i: Integer;
|
||||
totalModuleSize, average, totalDeviation: Single;
|
||||
pattern: IFinderPattern;
|
||||
begin
|
||||
confirmedCount := 0;
|
||||
totalModuleSize := 0;
|
||||
|
||||
max := FPossibleCenters.Count;
|
||||
|
||||
for pattern in FPossibleCenters do
|
||||
begin
|
||||
if (pattern.Count >= CENTER_QUORUM) then
|
||||
begin
|
||||
inc(confirmedCount);
|
||||
totalModuleSize := totalModuleSize + pattern.estimatedModuleSize;
|
||||
end;
|
||||
end;
|
||||
|
||||
if (confirmedCount < 3) then
|
||||
begin
|
||||
result := false;
|
||||
exit
|
||||
end;
|
||||
|
||||
average := totalModuleSize / max;
|
||||
totalDeviation := 0;
|
||||
i := 0;
|
||||
|
||||
while ((i < max)) do
|
||||
begin
|
||||
pattern := FPossibleCenters[i];
|
||||
totalDeviation := totalDeviation +
|
||||
Abs(pattern.estimatedModuleSize - average);
|
||||
inc(i)
|
||||
end;
|
||||
|
||||
result := (totalDeviation <= (0.05 * totalModuleSize));
|
||||
|
||||
end;
|
||||
|
||||
function TFinderPatternFinder.selectBestPatterns: TArray<IFinderPattern>;
|
||||
var
|
||||
average, square, totalModuleSize: Double;
|
||||
size, stdDev, limit, avrsng: Single;
|
||||
center, possibleCenter, pattern: IFinderPattern;
|
||||
startSize, i: Integer;
|
||||
comparator: IComparer<IFinderPattern>;
|
||||
begin
|
||||
startSize := FPossibleCenters.Count;
|
||||
if (startSize < 3) then
|
||||
begin
|
||||
result := nil;
|
||||
exit
|
||||
end;
|
||||
if (startSize > 3) then
|
||||
begin
|
||||
totalModuleSize := 0;
|
||||
square := 0;
|
||||
|
||||
for center in FPossibleCenters do
|
||||
begin
|
||||
size := center.estimatedModuleSize;
|
||||
totalModuleSize := totalModuleSize + size;
|
||||
square := square + (size * size);
|
||||
end;
|
||||
|
||||
average := totalModuleSize / startSize;
|
||||
stdDev := Sqrt((square / startSize) - (average * average));
|
||||
// this intermediate assignment is a workaround for old-gen compilers
|
||||
// that are not deallocating TFurthestFromAverageComparator instance
|
||||
// (bug verified on XE8: just declare a destructor Destroy override
|
||||
// in TFurthestFromAverageComparator put a breakpoint in it and see
|
||||
// that the destructor never gets called if you don't use this intermediate
|
||||
// assignment)
|
||||
avrsng := average;
|
||||
comparator := TFurthestFromAverageComparator.Create(avrsng);
|
||||
FPossibleCenters.Sort(comparator);
|
||||
limit := System.Math.Max((0.2 * avrsng), stdDev);
|
||||
i := 0;
|
||||
while (((i < FPossibleCenters.Count) and
|
||||
(FPossibleCenters.Count > 3))) do
|
||||
begin
|
||||
pattern := FPossibleCenters[i];
|
||||
if (Abs(pattern.estimatedModuleSize - average) > limit) then
|
||||
begin
|
||||
FPossibleCenters.Delete(i);
|
||||
dec(i)
|
||||
end;
|
||||
inc(i)
|
||||
end
|
||||
end;
|
||||
|
||||
if (FPossibleCenters.Count > 3) then
|
||||
begin
|
||||
totalModuleSize := 0;
|
||||
|
||||
for possibleCenter in FPossibleCenters do
|
||||
begin
|
||||
totalModuleSize := totalModuleSize + possibleCenter.estimatedModuleSize;
|
||||
end;
|
||||
|
||||
average := totalModuleSize / FPossibleCenters.Count;
|
||||
// this intermediate assignment is a workaround for old-gen compilers
|
||||
// that are not deallocating TCenterComparator instance
|
||||
// (bug verified on XE8: just declare a destructor Destroy override in
|
||||
// TCenterComparator,put a breakpoint in it and see that the destructor
|
||||
// never gets called if you don't use this intermediate assignment)
|
||||
comparator := TCenterComparator.Create(average);
|
||||
|
||||
FPossibleCenters.Sort( comparator );
|
||||
|
||||
// if (PossibleCenters.Count > 4) then
|
||||
// begin
|
||||
// self.PossibleCenters.DeleteRange(4, self.PossibleCenters.Count - 1);
|
||||
// end;
|
||||
|
||||
end;
|
||||
|
||||
result := TArray<IFinderPattern>.Create(FPossibleCenters[0],
|
||||
FPossibleCenters[1], FPossibleCenters[2]);
|
||||
|
||||
end;
|
||||
|
||||
{ TFurthestFromAverageComparator }
|
||||
|
||||
function TFurthestFromAverageComparator.Compare(const Left,
|
||||
Right: IFinderPattern): Integer;
|
||||
var
|
||||
dA, dB: Single;
|
||||
begin
|
||||
dA := Abs(Right.estimatedModuleSize - self.average);
|
||||
dB := Abs(Left.estimatedModuleSize - self.average);
|
||||
|
||||
if (dA < dB) then
|
||||
begin
|
||||
result := -1;
|
||||
end
|
||||
else if (dA = dB) then
|
||||
begin
|
||||
result := 0;
|
||||
end
|
||||
else
|
||||
result := 1;
|
||||
|
||||
end;
|
||||
|
||||
constructor TFurthestFromAverageComparator.Create(f: Single);
|
||||
begin
|
||||
average := f;
|
||||
end;
|
||||
|
||||
{ TCenterComparator }
|
||||
|
||||
function TCenterComparator.Compare(const Left, Right: IFinderPattern): Integer;
|
||||
var
|
||||
dA, dB: Single;
|
||||
begin
|
||||
if (Right.Count = Left.Count) then
|
||||
begin
|
||||
dA := Abs(Right.estimatedModuleSize - self.average);
|
||||
dB := Abs(Left.estimatedModuleSize - self.average);
|
||||
|
||||
if (dA < dB) then
|
||||
begin
|
||||
result := 1;
|
||||
end
|
||||
else if (dA = dB) then
|
||||
begin
|
||||
result := 0;
|
||||
end
|
||||
else
|
||||
result := -1;
|
||||
|
||||
exit;
|
||||
end;
|
||||
|
||||
result := (Right.Count - Left.Count);
|
||||
|
||||
end;
|
||||
|
||||
constructor TCenterComparator.Create(f: Single);
|
||||
begin
|
||||
average := f;
|
||||
end;
|
||||
|
||||
end.
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.FinderPatternImplementation;
|
||||
|
||||
interface
|
||||
uses
|
||||
ZXing.QrCode.Internal.FinderPattern;
|
||||
|
||||
|
||||
function NewFinderPattern(const posX, posY, estimatedModuleSize: Single; const count: Integer):IFinderPattern; overload;
|
||||
function NewFinderPattern(const posX, posY, estimatedModuleSize: Single):IFinderPattern; overload;
|
||||
|
||||
|
||||
implementation
|
||||
uses ZXing.ResultPoint,ZXing.ResultPointImplementation;
|
||||
|
||||
{ TFinderPattern }
|
||||
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Encapsulates a finder pattern, which are the three square patterns found in
|
||||
/// the corners of QR Codes. It also encapsulates a count of similar finder patterns,
|
||||
/// as a convenience to the finder's bookkeeping.</p>
|
||||
/// </summary>
|
||||
TFinderPattern = class(TResultPoint,IResultPoint,IFinderPattern)
|
||||
private
|
||||
Fcount: Integer;
|
||||
FestimatedModuleSize: Single;
|
||||
|
||||
function GetCount:integer;
|
||||
procedure SetCount(value:integer);
|
||||
|
||||
constructor Create(const posX, posY, estimatedModuleSize: Single;
|
||||
const count: Integer); overload;
|
||||
constructor Create(const posX, posY, estimatedModuleSize: Single); overload;
|
||||
|
||||
/// <summary> <p>Determines if this finder pattern "about equals" a finder pattern at the stated
|
||||
/// position and size -- meaning, it is at nearly the same center with nearly the same size.</p>
|
||||
/// </summary>
|
||||
function aboutEquals(const moduleSize, i, j: Single): Boolean;
|
||||
|
||||
/// <summary>
|
||||
/// Combines this object's current estimate of a finder pattern position and module size
|
||||
/// with a new estimate. It returns a new {@code FinderPattern} containing a weighted average
|
||||
/// based on count.
|
||||
/// </summary>
|
||||
/// <param name="i">The i.</param>
|
||||
/// <param name="j">The j.</param>
|
||||
/// <param name="newModuleSize">New size of the module.</param>
|
||||
/// <returns></returns>
|
||||
function combineEstimate(const i, j, newModuleSize: Single): IFinderPattern;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the estimated module.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The size of the estimated module.
|
||||
/// </value>
|
||||
function estimatedModuleSize : Single;
|
||||
property count : Integer read GetCount write SetCount;
|
||||
end;
|
||||
|
||||
constructor TFinderPattern.Create(const posX, posY,
|
||||
estimatedModuleSize: Single; const count: Integer);
|
||||
begin
|
||||
inherited Create(posX, posY);
|
||||
|
||||
FestimatedModuleSize := estimatedModuleSize;
|
||||
Fcount := count;
|
||||
end;
|
||||
|
||||
constructor TFinderPattern.Create(const posX, posY,
|
||||
estimatedModuleSize: Single);
|
||||
begin
|
||||
Self.Create(posX, posY, estimatedModuleSize, 1);
|
||||
end;
|
||||
|
||||
function TFinderPattern.estimatedModuleSize: Single;
|
||||
begin
|
||||
result := FestimatedModuleSize;
|
||||
end;
|
||||
|
||||
function TFinderPattern.GetCount: integer;
|
||||
begin
|
||||
result := FCount;
|
||||
end;
|
||||
|
||||
function TFinderPattern.aboutEquals(const moduleSize, i, j: Single): Boolean;
|
||||
var
|
||||
moduleSizeDiff : Single;
|
||||
begin
|
||||
Result := false;
|
||||
|
||||
if ((Abs(i - y) <= moduleSize) and (Abs(j - x) <= moduleSize)) then
|
||||
begin
|
||||
moduleSizeDiff := Abs(moduleSize - self.estimatedModuleSize);
|
||||
|
||||
Result := ((moduleSizeDiff <= 1) or
|
||||
(moduleSizeDiff <= self.estimatedModuleSize));
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFinderPattern.SetCount(value: integer);
|
||||
begin
|
||||
FCount := value;
|
||||
end;
|
||||
|
||||
function TFinderPattern.combineEstimate(const i, j,
|
||||
newModuleSize: Single): IFinderPattern;
|
||||
var
|
||||
combinedCount: Integer;
|
||||
combinedX, combinedY: Single;
|
||||
begin
|
||||
combinedCount := (Self.count + 1);
|
||||
combinedX := ((Self.count * x) + j) / combinedCount;
|
||||
combinedY := ((Self.count * y) + i) / combinedCount;
|
||||
Result := TFinderPattern.Create(combinedX, combinedY,
|
||||
(((Self.count * Self.estimatedModuleSize) + newModuleSize) / combinedCount),
|
||||
combinedCount)
|
||||
end;
|
||||
|
||||
|
||||
function NewFinderPattern(const posX, posY, estimatedModuleSize: Single; const count: Integer):IFinderPattern;
|
||||
begin
|
||||
result := TFinderPattern.Create( posX, posY, estimatedModuleSize,count);
|
||||
end;
|
||||
|
||||
function NewFinderPattern(const posX, posY, estimatedModuleSize: Single):IFinderPattern;
|
||||
begin
|
||||
result := TFinderPattern.Create( posX, posY, estimatedModuleSize);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.Internal.FinderPatternInfo;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
ZXing.QrCode.Internal.FinderPattern;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Encapsulates information about finder patterns in an image, including the location of
|
||||
/// the three finder patterns, and their estimated module size.</p>
|
||||
/// </summary>
|
||||
TFinderPatternInfo = class sealed
|
||||
private
|
||||
FbottomLeft: IFinderPattern;
|
||||
FtopLeft: IFinderPattern;
|
||||
FtopRight: IFinderPattern;
|
||||
public
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FinderPatternInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="patternCenters">The pattern centers.</param>
|
||||
constructor Create(const patternCenters: TArray<IFinderPattern>);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bottom left.
|
||||
/// </summary>
|
||||
property bottomLeft : IFinderPattern read FbottomLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top left.
|
||||
/// </summary>
|
||||
property topLeft : IFinderPattern read FtopLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the top right.
|
||||
/// </summary>
|
||||
property topRight : IFinderPattern read FtopRight;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
constructor TFinderPatternInfo.Create(const patternCenters: TArray<IFinderPattern>);
|
||||
begin
|
||||
FbottomLeft := patternCenters[0];
|
||||
FtopLeft := patternCenters[1];
|
||||
FtopRight := patternCenters[2]
|
||||
end;
|
||||
|
||||
end.
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: bbrown@google.com (Brian Brown)
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.Datamatrix.DataMatrixReader;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
ZXing.Common.Detector.MathUtils,
|
||||
Math,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.BarcodeFormat,
|
||||
ZXing.ReadResult,
|
||||
ZXing.Reader,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.DecoderResult,
|
||||
ZXing.Common.DetectorResult,
|
||||
ZXing.ResultMetadataType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.BinaryBitmap,
|
||||
ZXing.Datamatrix.Internal.Decoder,
|
||||
ZXing.Datamatrix.Internal.Detector;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// This implementation can detect and decode Data Matrix codes in an image.
|
||||
/// </summary>
|
||||
TDataMatrixReader = class(TInterfacedObject, IReader)
|
||||
private
|
||||
FDecoder: TDataMatrixDecoder;
|
||||
NO_POINTS: TArray<IResultPoint>;
|
||||
|
||||
/// <summary>
|
||||
/// This method detects a code in a "pure" image -- that is, pure monochrome image
|
||||
/// which contains only an unrotated, unskewed, image of a code, with some white border
|
||||
/// around it. This is a specialized method that works exceptionally fast in this special
|
||||
/// case.
|
||||
///
|
||||
/// <seealso cref="ZXing.QrCode.QRCodeReader.extractPureBits(TBitMatrix)" />
|
||||
/// </summary>
|
||||
class function extractPureBits(const image: TBitMatrix): TBitMatrix; static;
|
||||
|
||||
class function moduleSize(const leftTopBlack: TArray<Integer>;
|
||||
const image: TBitMatrix; var moduleSize: Integer): Boolean; static;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
/// <summary>
|
||||
/// This implementation can detect and decode Data Matrix codes in an image.
|
||||
/// </summary>
|
||||
function decode(const image: TBinaryBitmap): TReadResult; overload;
|
||||
|
||||
function decode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult; overload;
|
||||
|
||||
procedure reset;
|
||||
end;
|
||||
|
||||
implementation
|
||||
uses ZXing.ByteSegments;
|
||||
|
||||
{ TDataMatrixReader }
|
||||
|
||||
constructor TDataMatrixReader.Create;
|
||||
begin
|
||||
inherited;
|
||||
FDecoder := TDataMatrixDecoder.Create;
|
||||
NO_POINTS := TArray<IResultPoint>.Create();
|
||||
end;
|
||||
|
||||
destructor TDataMatrixReader.Destroy;
|
||||
begin
|
||||
NO_POINTS := nil;
|
||||
FDecoder.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TDataMatrixReader.decode(const image: TBinaryBitmap): TReadResult;
|
||||
begin
|
||||
Result := decode(image, nil);
|
||||
end;
|
||||
|
||||
function TDataMatrixReader.decode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
var
|
||||
matrixDetector: TDataMatrixDetector;
|
||||
DecoderResult: TDecoderResult;
|
||||
points: TArray<IResultPoint>;
|
||||
bits: TBitMatrix;
|
||||
DetectorResult: TDetectorResult;
|
||||
ByteSegments: IByteSegments;
|
||||
begin
|
||||
Result := nil;
|
||||
DetectorResult := nil;
|
||||
DecoderResult := nil;
|
||||
try
|
||||
|
||||
if ((hints <> nil) and hints.ContainsKey(TDecodeHintType.PURE_BARCODE)) then
|
||||
begin
|
||||
bits := TDataMatrixReader.extractPureBits(image.BlackMatrix);
|
||||
if Assigned(bits) then
|
||||
begin
|
||||
DecoderResult := FDecoder.decode(bits);
|
||||
points := NO_POINTS;
|
||||
FreeAndNil(bits);
|
||||
end
|
||||
else
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
matrixDetector := TDataMatrixDetector.Create(image.BlackMatrix);
|
||||
try
|
||||
DetectorResult := matrixDetector.detect();
|
||||
finally
|
||||
matrixDetector.Free;
|
||||
end;
|
||||
|
||||
if (DetectorResult = nil) then
|
||||
exit;
|
||||
|
||||
DecoderResult := FDecoder.decode(DetectorResult.bits);
|
||||
points := DetectorResult.points;
|
||||
end;
|
||||
|
||||
if (DecoderResult = nil) then
|
||||
exit;
|
||||
|
||||
Result := TReadResult.Create(DecoderResult.Text, DecoderResult.RawBytes,
|
||||
points, TBarcodeFormat.DATA_MATRIX);
|
||||
|
||||
ByteSegments := DecoderResult.ByteSegments;
|
||||
|
||||
if (ByteSegments <> nil) then
|
||||
Result.putMetadata(TResultMetadataType.BYTE_SEGMENTS, TResultMetaData.CreateByteSegmentsMetadata( byteSegments));
|
||||
|
||||
if (Length(DecoderResult.ECLevel) <> 0) then
|
||||
Result.putMetadata(TResultMetadataType.ERROR_CORRECTION_LEVEL, TResultMetaData.CreateStringMetadata(DecoderResult.ECLevel));
|
||||
|
||||
finally
|
||||
|
||||
byteSegments:=nil;
|
||||
if Assigned(DetectorResult) then
|
||||
FreeAndNil(DetectorResult);
|
||||
|
||||
if Assigned(DecoderResult) then
|
||||
FreeAndNil(DecoderResult);
|
||||
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
class function TDataMatrixReader.extractPureBits(const image: TBitMatrix)
|
||||
: TBitMatrix;
|
||||
var
|
||||
moduleSize: Integer;
|
||||
leftTopBlack, rightBottomBlack: TArray<Integer>;
|
||||
top, bottom, left, right: Integer;
|
||||
matrixWidth, matrixHeight: Integer;
|
||||
nudge: Integer;
|
||||
bits: TBitMatrix;
|
||||
x, y, iOffset: Integer;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
leftTopBlack := image.getTopLeftOnBit;
|
||||
rightBottomBlack := image.getBottomRightOnBit;
|
||||
if ((leftTopBlack = nil) or (rightBottomBlack = nil)) then
|
||||
exit;
|
||||
|
||||
if (not TDataMatrixReader.moduleSize(leftTopBlack, image, moduleSize)) then
|
||||
exit;
|
||||
|
||||
top := leftTopBlack[1];
|
||||
bottom := rightBottomBlack[1];
|
||||
left := leftTopBlack[0];
|
||||
right := rightBottomBlack[0];
|
||||
|
||||
matrixWidth := ((right - left + 1) div moduleSize);
|
||||
matrixHeight := ((bottom - top + 1) div moduleSize);
|
||||
if ((matrixWidth <= 0) or (matrixHeight <= 0)) then
|
||||
exit;
|
||||
|
||||
// Push in the "border" by half the module width so that we start
|
||||
// sampling in the middle of the module. Just in case the image is a
|
||||
// little off, this will help recover.
|
||||
nudge := TMathUtils.Asr(moduleSize,1);
|
||||
Inc(top, nudge);
|
||||
Inc(left, nudge);
|
||||
|
||||
// Now just read off the bits
|
||||
bits := TBitMatrix.Create(matrixWidth, matrixHeight);
|
||||
for y := 0 to Pred(matrixHeight) do
|
||||
begin
|
||||
iOffset := (top + (y * moduleSize));
|
||||
for x := 0 to Pred(matrixWidth) do
|
||||
begin
|
||||
if (image[(left + (x * moduleSize)), iOffset]) then
|
||||
bits[x, y] := true;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := bits;
|
||||
end;
|
||||
|
||||
class function TDataMatrixReader.moduleSize(const leftTopBlack: TArray<Integer>;
|
||||
const image: TBitMatrix; var moduleSize: Integer): Boolean;
|
||||
var
|
||||
width, x, y: Integer;
|
||||
begin
|
||||
Result := false;
|
||||
|
||||
width := image.width;
|
||||
x := leftTopBlack[0];
|
||||
y := leftTopBlack[1];
|
||||
|
||||
while ((x < width) and image[x, y]) do
|
||||
Inc(x);
|
||||
|
||||
if (x = width) then
|
||||
begin
|
||||
moduleSize := 0;
|
||||
exit;
|
||||
end;
|
||||
|
||||
moduleSize := (x - leftTopBlack[0]);
|
||||
if (moduleSize = 0) then
|
||||
exit;
|
||||
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
procedure TDataMatrixReader.reset;
|
||||
begin
|
||||
// do nothing
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,340 @@
|
||||
{
|
||||
* Copyright 2007 ZXing 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.
|
||||
|
||||
* Original Author: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.QrCode.QRCodeReader;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Math,
|
||||
System.Generics.Collections,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.ReadResult,
|
||||
ZXing.Reader,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.BarcodeFormat,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BinaryBitmap,
|
||||
ZXing.QrCode.Internal.Decoder,
|
||||
ZXing.Common.BitMatrix,
|
||||
ZXing.DecoderResult,
|
||||
ZXing.ResultMetadataType,
|
||||
ZXing.Common.DetectorResult,
|
||||
ZXing.QrCode.Internal.QRCodeDecoderMetaData,
|
||||
ZXing.QrCode.Internal.Detector,
|
||||
ZXing.QrCode.Internal.DecodedBitStreamParser;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// This implementation can detect and decode QR Codes in an image.
|
||||
/// </summary>
|
||||
TQRCodeReader = class(TInterfacedObject, IReader)
|
||||
private
|
||||
FDecoder: TQRDecoder;
|
||||
NO_POINTS: TArray<IResultPoint>;
|
||||
|
||||
/// <summary>
|
||||
/// This method detects a code in a "pure" image -- that is, pure monochrome image
|
||||
/// which contains only an unrotated, unskewed, image of a code, with some white border
|
||||
/// around it. This is a specialized method that works exceptionally fast in this special
|
||||
/// case.
|
||||
///
|
||||
/// <seealso cref="ZXing.Datamatrix.DataMatrixReader.extractPureBits(TBitMatrix)" />
|
||||
/// </summary>
|
||||
class function extractPureBits(const image: TBitMatrix): TBitMatrix; static;
|
||||
class function moduleSize(const leftTopBlack: TArray<Integer>;
|
||||
const image: TBitMatrix; var msize: Single): Boolean; static;
|
||||
protected
|
||||
/// <summary>
|
||||
/// Gets the decoder.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
property Decoder: TQRDecoder read FDecoder;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
/// <summary>
|
||||
/// Locates and decodes a QR code in an image.
|
||||
///
|
||||
/// <returns>a String representing the content encoded by the QR code</returns>
|
||||
/// </summary>
|
||||
function decode(const image: TBinaryBitmap): TReadResult; overload;
|
||||
/// <summary>
|
||||
/// Locates and decodes a barcode in some format within an image. This method also accepts
|
||||
/// hints, each possibly associated to some data, which may help the implementation decode.
|
||||
/// </summary>
|
||||
/// <param name="image">image of barcode to decode</param>
|
||||
/// <param name="hints">passed as a <see cref="TDictionary{TKey, TValue}"/> from <see cref="TDecodeHintType"/>
|
||||
/// to arbitrary data. The
|
||||
/// meaning of the data depends upon the hint type. The implementation may or may not do
|
||||
/// anything with these hints.</param>
|
||||
/// <returns>
|
||||
/// String which the barcode encodes
|
||||
/// </returns>
|
||||
function decode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult; overload;
|
||||
|
||||
/// <summary>
|
||||
/// Resets any internal state the implementation has after a decode, to prepare it
|
||||
/// for reuse.
|
||||
/// </summary>
|
||||
procedure reset;
|
||||
end;
|
||||
|
||||
implementation
|
||||
uses ZXing.ByteSegments;
|
||||
|
||||
{ TQRCodeReader }
|
||||
|
||||
constructor TQRCodeReader.Create;
|
||||
begin
|
||||
inherited;
|
||||
FDecoder := TQRDecoder.Create;
|
||||
NO_POINTS := TArray<IResultPoint>.Create();
|
||||
end;
|
||||
|
||||
destructor TQRCodeReader.Destroy;
|
||||
begin
|
||||
NO_POINTS := nil;
|
||||
FDecoder.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TQRCodeReader.decode(const image: TBinaryBitmap): TReadResult;
|
||||
begin
|
||||
Result := Self.decode(image, nil);
|
||||
end;
|
||||
|
||||
function TQRCodeReader.decode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
var
|
||||
DecoderResult: TDecoderResult;
|
||||
Detector: TDetector;
|
||||
points: TArray<IResultPoint>;
|
||||
bits: TBitMatrix;
|
||||
DetectorResult: TDetectorResult;
|
||||
data: TQRCodeDecoderMetaData;
|
||||
byteSegments: IByteSegments;
|
||||
begin
|
||||
Result := nil;
|
||||
DecoderResult := nil;
|
||||
|
||||
if ((image = nil) or (image.BlackMatrix = nil)) then
|
||||
// something is wrong with the image
|
||||
exit;
|
||||
try
|
||||
|
||||
if ((hints <> nil) and hints.ContainsKey(TDecodeHintType.PURE_BARCODE)) then
|
||||
begin
|
||||
bits := TQRCodeReader.extractPureBits(image.BlackMatrix);
|
||||
if Assigned(bits) then
|
||||
begin
|
||||
DecoderResult := Decoder.decode(bits, hints);
|
||||
points := NO_POINTS;
|
||||
bits.Free;
|
||||
end
|
||||
else
|
||||
exit;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Detector := TDetector.Create(image.BlackMatrix);
|
||||
try
|
||||
DetectorResult := Detector.detect(hints);
|
||||
if Assigned(DetectorResult) then
|
||||
begin
|
||||
DecoderResult := Decoder.decode(DetectorResult.bits, hints);
|
||||
points := DetectorResult.points;
|
||||
DetectorResult.Free;
|
||||
end
|
||||
else
|
||||
exit;
|
||||
finally
|
||||
Detector.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
if (DecoderResult = nil) then
|
||||
exit;
|
||||
|
||||
// If the code was mirrored: swap the bottom-left and the top-right points.
|
||||
data := TQRCodeDecoderMetaData(DecoderResult.Other);
|
||||
if (data <> nil) then
|
||||
data.applyMirroredCorrection(points);
|
||||
|
||||
Result := TReadResult.Create(DecoderResult.Text, DecoderResult.RawBytes,
|
||||
points, TBarcodeFormat.QR_CODE);
|
||||
|
||||
byteSegments := DecoderResult.byteSegments;
|
||||
|
||||
if (byteSegments <> nil) then
|
||||
Result.putMetadata(TResultMetadataType.BYTE_SEGMENTS, TResultMetaData.CreateByteSegmentsMetadata(byteSegments));
|
||||
|
||||
if (Length(DecoderResult.ecLevel) <> 0) then
|
||||
Result.putMetadata(TResultMetadataType.ERROR_CORRECTION_LEVEL, TResultMetaData.CreateStringMetadata(DecoderResult.ecLevel));
|
||||
|
||||
if (DecoderResult.StructuredAppend) then
|
||||
begin
|
||||
Result.putMetadata(TResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
|
||||
TResultMetaData.CreateIntegerMetadata(DecoderResult.StructuredAppendSequenceNumber));
|
||||
Result.putMetadata(TResultMetadataType.STRUCTURED_APPEND_PARITY,
|
||||
TResultMetaData.CreateIntegerMetadata(DecoderResult.StructuredAppendParity))
|
||||
end;
|
||||
|
||||
finally
|
||||
|
||||
if Assigned(DecoderResult) then
|
||||
FreeAndNil(DecoderResult);
|
||||
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
procedure TQRCodeReader.reset;
|
||||
begin
|
||||
// do nothing
|
||||
end;
|
||||
|
||||
class function TQRCodeReader.extractPureBits(const image: TBitMatrix)
|
||||
: TBitMatrix;
|
||||
var
|
||||
moduleSize: Single;
|
||||
leftTopBlack, rightBottomBlack: TArray<Integer>;
|
||||
top, bottom, left, right, matrixWidth, matrixHeight, nudge, x, y, iOffset,
|
||||
nudgedTooFarRight, nudgedTooFarDown: Integer;
|
||||
bits: TBitMatrix;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
leftTopBlack := image.getTopLeftOnBit;
|
||||
rightBottomBlack := image.getBottomRightOnBit;
|
||||
if ((leftTopBlack = nil) or (rightBottomBlack = nil)) then
|
||||
exit;
|
||||
|
||||
if (not TQRCodeReader.moduleSize(leftTopBlack, image, moduleSize)) then
|
||||
exit;
|
||||
|
||||
top := leftTopBlack[1];
|
||||
bottom := rightBottomBlack[1];
|
||||
left := leftTopBlack[0];
|
||||
right := rightBottomBlack[0];
|
||||
|
||||
// Sanity check!
|
||||
if ((left >= right) or (top >= bottom)) then
|
||||
exit;
|
||||
|
||||
if ((bottom - top) <> (right - left)) then
|
||||
// Special case, where bottom-right module wasn't black so we found something else in the last row
|
||||
// Assume it's a square, so use height as the width
|
||||
right := (left + (bottom - top));
|
||||
|
||||
matrixWidth := Round(((right - left) + 1) / moduleSize);
|
||||
matrixHeight := Round(((bottom - top) + 1) / moduleSize);
|
||||
if ((matrixWidth <= 0) or (matrixHeight <= 0)) then
|
||||
exit;
|
||||
|
||||
if (matrixHeight <> matrixWidth) then
|
||||
// Only possibly decode square regions
|
||||
exit;
|
||||
|
||||
// Push in the "border" by half the module width so that we start
|
||||
// sampling in the middle of the module. Just in case the image is a
|
||||
// little off, this will help recover.
|
||||
nudge := Trunc(moduleSize / 2.0);
|
||||
Inc(top, nudge);
|
||||
Inc(left, nudge);
|
||||
|
||||
// But careful that this does not sample off the edge
|
||||
// "right" is the farthest-right valid pixel location -- right+1 is not necessarily
|
||||
// This is positive by how much the inner x loop below would be too large
|
||||
nudgedTooFarRight := left + Trunc((matrixWidth - 1) * moduleSize) - right;
|
||||
if (nudgedTooFarRight > 0) then
|
||||
begin
|
||||
if (nudgedTooFarRight > nudge) then
|
||||
// Neither way fits; abort
|
||||
exit;
|
||||
Dec(left, nudgedTooFarRight);
|
||||
end;
|
||||
// See logic above
|
||||
nudgedTooFarDown := top + Trunc((matrixHeight - 1) * moduleSize) - bottom;
|
||||
if (nudgedTooFarDown > 0) then
|
||||
begin
|
||||
if (nudgedTooFarDown > nudge) then
|
||||
// Neither way fits; abort
|
||||
exit;
|
||||
Dec(top, nudgedTooFarDown);
|
||||
end;
|
||||
|
||||
// Now just read off the bits
|
||||
bits := TBitMatrix.Create(matrixWidth, matrixHeight);
|
||||
for y := 0 to Pred(matrixHeight) do
|
||||
begin
|
||||
iOffset := top + Trunc(y * moduleSize);
|
||||
for x := 0 to Pred(matrixWidth) do
|
||||
begin
|
||||
if (image[left + Trunc((x * moduleSize)), iOffset]) then
|
||||
bits[x, y] := true;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := bits;
|
||||
end;
|
||||
|
||||
class function TQRCodeReader.moduleSize(const leftTopBlack: TArray<Integer>;
|
||||
const image: TBitMatrix; var msize: Single): Boolean;
|
||||
var
|
||||
height, width, x, y: Integer;
|
||||
inBlack: Boolean;
|
||||
transitions: Integer;
|
||||
begin
|
||||
Result := false;
|
||||
|
||||
height := image.height;
|
||||
width := image.width;
|
||||
x := leftTopBlack[0];
|
||||
y := leftTopBlack[1];
|
||||
|
||||
inBlack := true;
|
||||
transitions := 0;
|
||||
while (((x < width) and (y < height))) do
|
||||
begin
|
||||
if (inBlack <> image[x, y]) then
|
||||
begin
|
||||
Inc(transitions);
|
||||
if (transitions = 5) then
|
||||
break;
|
||||
inBlack := not inBlack;
|
||||
end;
|
||||
Inc(x);
|
||||
Inc(y);
|
||||
end;
|
||||
|
||||
if ((x = width) or (y = height)) then
|
||||
begin
|
||||
msize := 0.0;
|
||||
exit;
|
||||
end;
|
||||
|
||||
msize := ((x - leftTopBlack[0]) / 7.0);
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
end.
|
||||
Reference in New Issue
Block a user