dd9fd62dcb
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>
978 lines
25 KiB
ObjectPascal
978 lines
25 KiB
ObjectPascal
{
|
|
* 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.
|