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
+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.
|
||||
Reference in New Issue
Block a user