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:
@@ -0,0 +1,696 @@
|
||||
{
|
||||
* Copyright 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
* Original Authors: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.OneD.Code128Reader;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
ZXing.Helpers,
|
||||
ZXing.OneD.OneDReader,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BarcodeFormat;
|
||||
|
||||
var
|
||||
CODE_PATTERNS: TArray<TArray<Integer>>;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Decodes Code 128 barcodes.</p>
|
||||
/// </summary>
|
||||
TCode128Reader = class(TOneDReader)
|
||||
private const
|
||||
CODE_SHIFT = 98;
|
||||
CODE_CODE_C = 99;
|
||||
CODE_CODE_B = 100;
|
||||
CODE_CODE_A = 101;
|
||||
CODE_FNC_1 = 102;
|
||||
CODE_FNC_2 = 97;
|
||||
CODE_FNC_3 = 96;
|
||||
CODE_FNC_4_A = 101;
|
||||
CODE_FNC_4_B = 100;
|
||||
CODE_START_A = 103;
|
||||
CODE_START_B = 104;
|
||||
CODE_START_C = 105;
|
||||
CODE_STOP = 106;
|
||||
|
||||
class var MAX_AVG_VARIANCE: Integer;
|
||||
class var MAX_INDIVIDUAL_VARIANCE: Integer;
|
||||
class function FindStartPattern(row: IBitArray): TArray<Integer>;
|
||||
class function DecodeCode(row: IBitArray; counters: TArray<Integer>;
|
||||
rowOffset: Integer; var code: Integer): Boolean;
|
||||
|
||||
class procedure DoInitialize();
|
||||
class procedure DoFinalize();
|
||||
public
|
||||
function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
class procedure TCode128Reader.DoFinalize;
|
||||
begin
|
||||
CODE_PATTERNS := nil;
|
||||
end;
|
||||
|
||||
class procedure TCode128Reader.DoInitialize;
|
||||
begin
|
||||
CODE_PATTERNS := [[2, 1, 2, 2, 2, 2], // 0
|
||||
[2, 2, 2, 1, 2, 2], [2, 2, 2, 2, 2, 1], [1, 2, 1, 2, 2, 3],
|
||||
[1, 2, 1, 3, 2, 2], [1, 3, 1, 2, 2, 2], // 5
|
||||
[1, 2, 2, 2, 1, 3], [1, 2, 2, 3, 1, 2], [1, 3, 2, 2, 1, 2],
|
||||
[2, 2, 1, 2, 1, 3], [2, 2, 1, 3, 1, 2], // 10
|
||||
[2, 3, 1, 2, 1, 2], [1, 1, 2, 2, 3, 2], [1, 2, 2, 1, 3, 2],
|
||||
[1, 2, 2, 2, 3, 1], [1, 1, 3, 2, 2, 2], // 15
|
||||
[1, 2, 3, 1, 2, 2], [1, 2, 3, 2, 2, 1], [2, 2, 3, 2, 1, 1],
|
||||
[2, 2, 1, 1, 3, 2], [2, 2, 1, 2, 3, 1], // 20
|
||||
[2, 1, 3, 2, 1, 2], [2, 2, 3, 1, 1, 2], [3, 1, 2, 1, 3, 1],
|
||||
[3, 1, 1, 2, 2, 2], [3, 2, 1, 1, 2, 2], // 25
|
||||
[3, 2, 1, 2, 2, 1], [3, 1, 2, 2, 1, 2], [3, 2, 2, 1, 1, 2],
|
||||
[3, 2, 2, 2, 1, 1], [2, 1, 2, 1, 2, 3], // 30
|
||||
[2, 1, 2, 3, 2, 1], [2, 3, 2, 1, 2, 1], [1, 1, 1, 3, 2, 3],
|
||||
[1, 3, 1, 1, 2, 3], [1, 3, 1, 3, 2, 1], // 35
|
||||
[1, 1, 2, 3, 1, 3], [1, 3, 2, 1, 1, 3], [1, 3, 2, 3, 1, 1],
|
||||
[2, 1, 1, 3, 1, 3], [2, 3, 1, 1, 1, 3], // 40
|
||||
[2, 3, 1, 3, 1, 1], [1, 1, 2, 1, 3, 3], [1, 1, 2, 3, 3, 1],
|
||||
[1, 3, 2, 1, 3, 1], [1, 1, 3, 1, 2, 3], // 45
|
||||
[1, 1, 3, 3, 2, 1], [1, 3, 3, 1, 2, 1], [3, 1, 3, 1, 2, 1],
|
||||
[2, 1, 1, 3, 3, 1], [2, 3, 1, 1, 3, 1], // 50
|
||||
[2, 1, 3, 1, 1, 3], [2, 1, 3, 3, 1, 1], [2, 1, 3, 1, 3, 1],
|
||||
[3, 1, 1, 1, 2, 3], [3, 1, 1, 3, 2, 1], // 55
|
||||
[3, 3, 1, 1, 2, 1], [3, 1, 2, 1, 1, 3], [3, 1, 2, 3, 1, 1],
|
||||
[3, 3, 2, 1, 1, 1], [3, 1, 4, 1, 1, 1], // 60
|
||||
[2, 2, 1, 4, 1, 1], [4, 3, 1, 1, 1, 1], [1, 1, 1, 2, 2, 4],
|
||||
[1, 1, 1, 4, 2, 2], [1, 2, 1, 1, 2, 4], // 65
|
||||
[1, 2, 1, 4, 2, 1], [1, 4, 1, 1, 2, 2], [1, 4, 1, 2, 2, 1],
|
||||
[1, 1, 2, 2, 1, 4], [1, 1, 2, 4, 1, 2], // 70
|
||||
[1, 2, 2, 1, 1, 4], [1, 2, 2, 4, 1, 1], [1, 4, 2, 1, 1, 2],
|
||||
[1, 4, 2, 2, 1, 1], [2, 4, 1, 2, 1, 1], // 75
|
||||
[2, 2, 1, 1, 1, 4], [4, 1, 3, 1, 1, 1], [2, 4, 1, 1, 1, 2],
|
||||
[1, 3, 4, 1, 1, 1], [1, 1, 1, 2, 4, 2], // 80
|
||||
[1, 2, 1, 1, 4, 2], [1, 2, 1, 2, 4, 1], [1, 1, 4, 2, 1, 2],
|
||||
[1, 2, 4, 1, 1, 2], [1, 2, 4, 2, 1, 1], // 85
|
||||
[4, 1, 1, 2, 1, 2], [4, 2, 1, 1, 1, 2], [4, 2, 1, 2, 1, 1],
|
||||
[2, 1, 2, 1, 4, 1], [2, 1, 4, 1, 2, 1], // 90
|
||||
[4, 1, 2, 1, 2, 1], [1, 1, 1, 1, 4, 3], [1, 1, 1, 3, 4, 1],
|
||||
[1, 3, 1, 1, 4, 1], [1, 1, 4, 1, 1, 3], // 95
|
||||
[1, 1, 4, 3, 1, 1], [4, 1, 1, 1, 1, 3], [4, 1, 1, 3, 1, 1],
|
||||
[1, 1, 3, 1, 4, 1], [1, 1, 4, 1, 3, 1], // 100
|
||||
[3, 1, 1, 1, 4, 1], [4, 1, 1, 1, 3, 1], [2, 1, 1, 4, 1, 2],
|
||||
[2, 1, 1, 2, 1, 4], [2, 1, 1, 2, 3, 2], // 105
|
||||
[2, 3, 3, 1, 1, 1, 2]];
|
||||
|
||||
MAX_AVG_VARIANCE := Floor(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.25);
|
||||
MAX_INDIVIDUAL_VARIANCE := Floor(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7);
|
||||
|
||||
end;
|
||||
|
||||
class function TCode128Reader.FindStartPattern(row: IBitArray): TArray<Integer>;
|
||||
var
|
||||
i, bestMatch, startCode, bestVariance, width, rowOffset, patternStart,
|
||||
patternLength, variance, counterPosition: Integer;
|
||||
counters: TArray<Integer>;
|
||||
isWhite: Boolean;
|
||||
|
||||
begin
|
||||
width := row.Size;
|
||||
rowOffset := row.getNextSet(0);
|
||||
counterPosition := 0;
|
||||
counters := TArray<Integer>.Create();
|
||||
SetLength(counters, 6);
|
||||
patternStart := rowOffset;
|
||||
isWhite := false;
|
||||
patternLength := Length(counters);
|
||||
|
||||
for i := rowOffset to width - 1 do
|
||||
begin
|
||||
|
||||
if row[i] xor isWhite then
|
||||
begin
|
||||
inc(counters[counterPosition])
|
||||
end
|
||||
else
|
||||
begin
|
||||
if counterPosition = patternLength - 1 then
|
||||
begin
|
||||
bestVariance := MAX_AVG_VARIANCE;
|
||||
bestMatch := -1;
|
||||
|
||||
startCode := CODE_START_A;
|
||||
while startCode <= CODE_START_C do
|
||||
begin
|
||||
|
||||
variance := patternMatchVariance(counters, CODE_PATTERNS[startCode],
|
||||
MAX_INDIVIDUAL_VARIANCE);
|
||||
|
||||
if variance < bestVariance then
|
||||
begin
|
||||
bestVariance := variance;
|
||||
bestMatch := startCode
|
||||
end;
|
||||
|
||||
inc(startCode);
|
||||
end;
|
||||
|
||||
if bestMatch >= 0 then
|
||||
begin
|
||||
// Look for whitespace before start pattern, >= 50% of width of start pattern
|
||||
if row.isRange(Max(0, patternStart - (i - patternStart) div 2),
|
||||
patternStart, false) then
|
||||
begin
|
||||
result := [patternStart, i, bestMatch];
|
||||
exit;
|
||||
end
|
||||
end;
|
||||
|
||||
patternStart := patternStart + counters[0] + counters[1];
|
||||
|
||||
// Array.Copy(counters, 2, counters, 0, patternLength - 2);
|
||||
// source, source index, dest, dest index, lengte
|
||||
|
||||
counters := TArray.CopyInSameArray(counters, 2, patternLength - 2);
|
||||
counters[patternLength - 2] := 0;
|
||||
counters[patternLength - 1] := 0;
|
||||
|
||||
dec(counterPosition);
|
||||
end
|
||||
else
|
||||
begin
|
||||
inc(counterPosition);
|
||||
end;
|
||||
|
||||
counters[counterPosition] := 1;
|
||||
isWhite := not isWhite
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
result := nil;
|
||||
|
||||
end;
|
||||
|
||||
class function TCode128Reader.DecodeCode(row: IBitArray;
|
||||
counters: TArray<Integer>; rowOffset: Integer; var code: Integer): Boolean;
|
||||
var
|
||||
bestVariance, l, d, variance: Integer;
|
||||
pattern: TArray<Integer>;
|
||||
begin
|
||||
|
||||
code := -1;
|
||||
if not recordPattern(row, rowOffset, counters) then
|
||||
begin
|
||||
result := false;
|
||||
exit;
|
||||
end;
|
||||
|
||||
bestVariance := MAX_AVG_VARIANCE;
|
||||
|
||||
// worst variance we'll accept
|
||||
l := Length(CODE_PATTERNS) - 1;
|
||||
for d := 0 to l do
|
||||
begin
|
||||
pattern := CODE_PATTERNS[d];
|
||||
variance := patternMatchVariance(counters, pattern,
|
||||
MAX_INDIVIDUAL_VARIANCE);
|
||||
if variance < bestVariance then
|
||||
begin
|
||||
bestVariance := variance;
|
||||
code := d
|
||||
end;
|
||||
end;
|
||||
|
||||
// TODO We're overlooking the fact that the STOP pattern has 7 values, not 6.
|
||||
result := code >= 0;
|
||||
end;
|
||||
|
||||
function TCode128Reader.decodeRow(const rowNumber: Integer;
|
||||
const row: IBitArray; const hints: TDictionary<TDecodeHintType, TObject>)
|
||||
: TReadResult;
|
||||
var
|
||||
shiftUpperMode, upperMode, lastCharacterWasPrintable, convertFNC1, done,
|
||||
unshift, isNextShifted: Boolean;
|
||||
counters, startPatternInfo: TArray<Integer>;
|
||||
lastPatternSize, multiplier, checksumTotal, code, lastCode, nextStart,
|
||||
lastStart, startCode, codeSet, l, rawCodesSize: Integer;
|
||||
rawCodes: TList<Byte>;
|
||||
rawBytes: TArray<Byte>;
|
||||
aResult: String;
|
||||
counter, resultLength, i: Integer;
|
||||
left, right: Single;
|
||||
resultPointCallback: TResultPointCallback;
|
||||
obj: TObject;
|
||||
resultPoints: TArray<IResultPoint>;
|
||||
resultPointLeft, resultPointRight: IResultPoint;
|
||||
begin
|
||||
convertFNC1 := (hints <> nil) and
|
||||
(hints.ContainsKey(ZXing.DecodeHintType.ASSUME_GS1));
|
||||
|
||||
startPatternInfo := FindStartPattern(row);
|
||||
if (startPatternInfo = nil) then
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
|
||||
startCode := startPatternInfo[2];
|
||||
|
||||
rawCodes := TList<Byte>.Create();
|
||||
|
||||
try
|
||||
|
||||
rawCodes.Add(Byte(startCode));
|
||||
|
||||
case startCode of
|
||||
CODE_START_A:
|
||||
begin
|
||||
codeSet := CODE_CODE_A;
|
||||
end;
|
||||
CODE_START_B:
|
||||
begin
|
||||
codeSet := CODE_CODE_B;
|
||||
end;
|
||||
CODE_START_C:
|
||||
begin
|
||||
codeSet := CODE_CODE_C;
|
||||
end
|
||||
else
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
|
||||
done := false;
|
||||
isNextShifted := false;
|
||||
lastStart := startPatternInfo[0];
|
||||
nextStart := startPatternInfo[1];
|
||||
SetLength(counters, 6);
|
||||
lastCode := 0;
|
||||
code := 0;
|
||||
checksumTotal := startCode;
|
||||
multiplier := 0;
|
||||
lastCharacterWasPrintable := true;
|
||||
upperMode := false;
|
||||
shiftUpperMode := false;
|
||||
|
||||
while not done do
|
||||
begin
|
||||
|
||||
unshift := isNextShifted;
|
||||
isNextShifted := false;
|
||||
|
||||
// Save off last code
|
||||
lastCode := code;
|
||||
|
||||
// Decode another code from image
|
||||
if not DecodeCode(row, counters, nextStart, code) then
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
|
||||
rawCodes.Add(Byte(code));
|
||||
|
||||
// Remember whether the last code was printable or not (excluding CODE_STOP)
|
||||
if code <> CODE_STOP then
|
||||
begin
|
||||
lastCharacterWasPrintable := true
|
||||
end;
|
||||
|
||||
// Add to checksum computation (if not CODE_STOP of course)
|
||||
if code <> CODE_STOP then
|
||||
begin
|
||||
inc(multiplier);
|
||||
checksumTotal := checksumTotal + (multiplier * code);
|
||||
end;
|
||||
|
||||
// Advance to where the next code will to start
|
||||
lastStart := nextStart;
|
||||
l := Length(counters) - 1;
|
||||
for counter := 0 to l do
|
||||
begin
|
||||
nextStart := nextStart + counters[counter];
|
||||
end;
|
||||
|
||||
// Take care of illegal start codes
|
||||
case code of
|
||||
CODE_START_A, CODE_START_B, CODE_START_C:
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
case codeSet of
|
||||
|
||||
CODE_CODE_A:
|
||||
begin
|
||||
if code < 64 then
|
||||
begin
|
||||
if shiftUpperMode = upperMode then
|
||||
begin
|
||||
aResult := aResult + Char(32 + code);
|
||||
end
|
||||
else
|
||||
begin
|
||||
aResult := aResult + Char(32 + 128)
|
||||
end;
|
||||
shiftUpperMode := false
|
||||
end
|
||||
else if code < 96 then
|
||||
begin
|
||||
if shiftUpperMode = upperMode then
|
||||
begin
|
||||
aResult := aResult + Char(code - 64)
|
||||
end
|
||||
else
|
||||
begin
|
||||
aResult := aResult + Char(code + 64)
|
||||
end;
|
||||
shiftUpperMode := false
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Don't let CODE_STOP, which always appears, affect whether whether we think the last
|
||||
// code was printable or not.
|
||||
if code <> CODE_STOP then
|
||||
begin
|
||||
lastCharacterWasPrintable := false
|
||||
end;
|
||||
case code of
|
||||
CODE_FNC_1:
|
||||
begin
|
||||
if convertFNC1 then
|
||||
begin
|
||||
if Length(aResult) = 0 then
|
||||
begin
|
||||
// GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
|
||||
// is FNC1 then this is GS1-128. We add the symbology identifier.
|
||||
aResult := aResult + ']C1'
|
||||
end
|
||||
else
|
||||
begin
|
||||
// GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
|
||||
aResult := aResult + Char(29)
|
||||
end
|
||||
end;
|
||||
end;
|
||||
CODE_FNC_2, CODE_FNC_3:
|
||||
begin
|
||||
|
||||
// do nothing?
|
||||
end;
|
||||
CODE_FNC_4_A:
|
||||
begin
|
||||
if (not upperMode) and (shiftUpperMode) then
|
||||
begin
|
||||
upperMode := true;
|
||||
shiftUpperMode := false
|
||||
end
|
||||
else if (upperMode) and (shiftUpperMode) then
|
||||
begin
|
||||
upperMode := false;
|
||||
shiftUpperMode := false
|
||||
end
|
||||
else
|
||||
begin
|
||||
shiftUpperMode := true
|
||||
end;
|
||||
end;
|
||||
CODE_SHIFT:
|
||||
begin
|
||||
isNextShifted := true;
|
||||
codeSet := CODE_CODE_B;
|
||||
end;
|
||||
CODE_CODE_B:
|
||||
begin
|
||||
codeSet := CODE_CODE_B;
|
||||
end;
|
||||
CODE_CODE_C:
|
||||
begin
|
||||
codeSet := CODE_CODE_C;
|
||||
end;
|
||||
CODE_STOP:
|
||||
begin
|
||||
done := true;
|
||||
end;
|
||||
end
|
||||
end;
|
||||
end;
|
||||
CODE_CODE_B:
|
||||
begin
|
||||
if code < 96 then
|
||||
begin
|
||||
if shiftUpperMode = upperMode then
|
||||
begin
|
||||
aResult := aResult + Char(32 + code)
|
||||
end
|
||||
else
|
||||
begin
|
||||
aResult := aResult + Char(32 + code + 128)
|
||||
end;
|
||||
shiftUpperMode := false
|
||||
end
|
||||
else
|
||||
begin
|
||||
if code <> CODE_STOP then
|
||||
begin
|
||||
lastCharacterWasPrintable := false
|
||||
end;
|
||||
case code of
|
||||
CODE_FNC_1:
|
||||
begin
|
||||
if convertFNC1 then
|
||||
begin
|
||||
if Length(aResult) = 0 then
|
||||
begin
|
||||
// GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
|
||||
// is FNC1 then this is GS1-128. We add the symbology identifier.
|
||||
aResult := aResult + (']C1')
|
||||
end
|
||||
else
|
||||
begin
|
||||
// GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
|
||||
aResult := aResult + Char(29)
|
||||
end
|
||||
end;
|
||||
end;
|
||||
CODE_FNC_2, CODE_FNC_3:
|
||||
begin
|
||||
// do nothing?
|
||||
end;
|
||||
CODE_FNC_4_B:
|
||||
begin
|
||||
if (not upperMode) and (shiftUpperMode) then
|
||||
begin
|
||||
upperMode := true;
|
||||
shiftUpperMode := false
|
||||
end
|
||||
else if (upperMode) and (shiftUpperMode) then
|
||||
begin
|
||||
upperMode := false;
|
||||
shiftUpperMode := false
|
||||
end
|
||||
else
|
||||
begin
|
||||
shiftUpperMode := true
|
||||
end;
|
||||
end;
|
||||
CODE_SHIFT:
|
||||
begin
|
||||
isNextShifted := true;
|
||||
codeSet := CODE_CODE_A;
|
||||
end;
|
||||
CODE_CODE_A:
|
||||
begin
|
||||
codeSet := CODE_CODE_A;
|
||||
end;
|
||||
CODE_CODE_C:
|
||||
begin
|
||||
codeSet := CODE_CODE_C;
|
||||
end;
|
||||
CODE_STOP:
|
||||
begin
|
||||
done := true;
|
||||
end;
|
||||
end
|
||||
end;
|
||||
end;
|
||||
CODE_CODE_C:
|
||||
begin
|
||||
if code < 100 then
|
||||
begin
|
||||
if code < 10 then
|
||||
begin
|
||||
aResult := aResult + ('0')
|
||||
end;
|
||||
aResult := aResult + IntToStr(code);
|
||||
end
|
||||
else
|
||||
begin
|
||||
if code <> CODE_STOP then
|
||||
begin
|
||||
lastCharacterWasPrintable := false
|
||||
end;
|
||||
case code of
|
||||
CODE_FNC_1:
|
||||
begin
|
||||
if convertFNC1 then
|
||||
begin
|
||||
if Length(aResult) = 0 then
|
||||
begin
|
||||
// GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
|
||||
// is FNC1 then this is GS1-128. We add the symbology identifier.
|
||||
aResult := aResult + (']C1')
|
||||
end
|
||||
else
|
||||
begin
|
||||
// GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
|
||||
aResult := aResult + Char(29)
|
||||
end
|
||||
end;
|
||||
end;
|
||||
CODE_CODE_A:
|
||||
begin
|
||||
codeSet := CODE_CODE_A;
|
||||
end;
|
||||
CODE_CODE_B:
|
||||
begin
|
||||
codeSet := CODE_CODE_B;
|
||||
end;
|
||||
CODE_STOP:
|
||||
begin
|
||||
done := true;
|
||||
end;
|
||||
end
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Unshift back to another code set if we were shifted
|
||||
if (unshift) then
|
||||
begin
|
||||
if (codeSet = CODE_CODE_A) then
|
||||
codeSet := CODE_CODE_B
|
||||
else
|
||||
codeSet := CODE_CODE_A;
|
||||
end;
|
||||
end;
|
||||
|
||||
lastPatternSize := nextStart - lastStart;
|
||||
|
||||
// Check for ample whitespace following pattern, but, to do this we first need to remember that
|
||||
// we fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left
|
||||
// to read off. Would be slightly better to properly read. Here we just skip it:
|
||||
nextStart := row.getNextUnset(nextStart);
|
||||
if not row.isRange(nextStart, System.Math.Min(row.Size,
|
||||
nextStart + (nextStart - lastStart) div 2), false) then
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Pull out from sum the value of the penultimate check code
|
||||
checksumTotal := checksumTotal - (multiplier * lastCode);
|
||||
// lastCode is the checksum then:
|
||||
if checksumTotal mod 103 <> lastCode then
|
||||
begin
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Need to pull out the check digits from string
|
||||
resultLength := Length(aResult);
|
||||
if resultLength = 0 then
|
||||
begin
|
||||
// false positive
|
||||
result := nil;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// Only bother if the result had at least one character, and if the checksum digit happened to
|
||||
// be a printable character. If it was just interpreted as a control code, nothing to remove.
|
||||
if (resultLength > 0) and (lastCharacterWasPrintable) then
|
||||
begin
|
||||
if codeSet = CODE_CODE_C then
|
||||
begin
|
||||
aResult := aResult.Remove(resultLength - 2, 2);
|
||||
end
|
||||
else
|
||||
begin
|
||||
aResult := aResult.Remove(resultLength - 1, 1);
|
||||
end
|
||||
end;
|
||||
|
||||
left := (startPatternInfo[1] + startPatternInfo[0]) / 2;
|
||||
right := lastStart + lastPatternSize / 2;
|
||||
|
||||
rawCodesSize := rawCodes.Count;
|
||||
SetLength(rawBytes, rawCodesSize);
|
||||
|
||||
for i := 0 to rawCodesSize - 1 do
|
||||
rawBytes[i] := rawCodes[i];
|
||||
|
||||
resultPointLeft := TResultPointHelpers.CreateResultPoint(left, rowNumber);
|
||||
resultPointRight := TResultPointHelpers.CreateResultPoint(right, rowNumber);
|
||||
resultPoints := [resultPointLeft, resultPointRight];
|
||||
|
||||
resultPointCallback := nil;
|
||||
// had to add this initialization because the following if/else construct does not assign a value
|
||||
// for all possible cases. Being resultPointCallback a local variable it is not initialized by default to null,
|
||||
// so you could get unpredictable results
|
||||
|
||||
if ((hints = nil) or
|
||||
(not hints.ContainsKey(TDecodeHintType.NEED_RESULT_POINT_CALLBACK))) then
|
||||
resultPointCallback := nil
|
||||
else
|
||||
begin
|
||||
obj := hints[TDecodeHintType.NEED_RESULT_POINT_CALLBACK];
|
||||
if (obj is TResultPointEventObject) then
|
||||
resultPointCallback := TResultPointEventObject(obj).Event;
|
||||
end;
|
||||
|
||||
if Assigned(resultPointCallback) then
|
||||
begin
|
||||
resultPointCallback(resultPointLeft);
|
||||
resultPointCallback(resultPointRight);
|
||||
end;
|
||||
finally
|
||||
FreeAndNil(rawCodes);
|
||||
end;
|
||||
|
||||
result := TReadResult.Create(aResult, rawBytes, resultPoints,
|
||||
TBarcodeFormat.CODE_128);
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TCode128Reader.DoInitialize;
|
||||
|
||||
finalization
|
||||
|
||||
TCode128Reader.DoFinalize;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,471 @@
|
||||
unit ZXing.OneD.Code39Reader;
|
||||
{
|
||||
* Copyright 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
* Original Authors: Sean Owen
|
||||
* Implemented by Nano103 and E. Spelt for Delphi
|
||||
}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
Math,
|
||||
ZXing.OneD.OneDReader,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BarcodeFormat,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
|
||||
TCode39Reader = class sealed(TOneDReader)
|
||||
|
||||
private
|
||||
class var ALPHABET: TArray<Char>;
|
||||
class function decodeExtended(encoded: string): string; static;
|
||||
|
||||
function findAsteriskPattern(row: IBitArray): TArray<Integer>;
|
||||
class function patternToChar(pattern: Integer; var c: Char)
|
||||
: boolean; static;
|
||||
class function toNarrowWidePattern(counters: TArray<Integer>)
|
||||
: Integer; static;
|
||||
|
||||
const
|
||||
ALPHABET_STRING: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%';
|
||||
class var ASTERISK_ENCODING: Integer;
|
||||
|
||||
class var
|
||||
CHARACTER_ENCODINGS: TArray<Integer>;
|
||||
counters: TArray<Integer>;
|
||||
decodeRowResult: TStringBuilder;
|
||||
usingCheckDigit: boolean;
|
||||
extendedMode: boolean;
|
||||
public
|
||||
function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
override;
|
||||
|
||||
constructor Create(AUsingCheckDigit, AExtendedMode: boolean);
|
||||
destructor Destroy(); override;
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ Code93Reader }
|
||||
|
||||
constructor TCode39Reader.Create(AUsingCheckDigit, AExtendedMode: boolean);
|
||||
begin
|
||||
ALPHABET := ALPHABET_STRING.ToCharArray;
|
||||
CHARACTER_ENCODINGS := TArray<Integer>.Create($034, $121, $061, $160, $031,
|
||||
$130, $070, $025, $124, $064, // 0-9
|
||||
$109, $049, $148, $019, $118, $058, $00D, $10C, $04C, $01C, // A-J
|
||||
$103, $043, $142, $013, $112, $052, $007, $106, $046, $016, // K-T
|
||||
$181, $0C1, $1C0, $091, $190, $0D0, $085, $184, $0C4, $094, // U-*
|
||||
$0A8, $0A2, $08A, $02A // $-%
|
||||
);
|
||||
|
||||
ASTERISK_ENCODING := TCode39Reader.CHARACTER_ENCODINGS[$27];
|
||||
|
||||
counters := TArray<Integer>.Create();
|
||||
SetLength(counters, 9);
|
||||
decodeRowResult := TStringBuilder.Create();
|
||||
usingCheckDigit := AUsingCheckDigit;
|
||||
extendedMode := AExtendedMode;
|
||||
|
||||
end;
|
||||
|
||||
destructor TCode39Reader.Destroy;
|
||||
begin
|
||||
ALPHABET := nil;
|
||||
CHARACTER_ENCODINGS := nil;
|
||||
counters := nil;
|
||||
FreeAndNil(decodeRowResult);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
class function TCode39Reader.decodeExtended(encoded: string): string;
|
||||
var
|
||||
next, c, decodedChar: Char;
|
||||
length, i: Integer;
|
||||
decoded: TStringBuilder;
|
||||
|
||||
begin
|
||||
length := encoded.length;
|
||||
decoded := TStringBuilder.Create(length);
|
||||
try
|
||||
|
||||
i := 0;
|
||||
while i < length do
|
||||
begin
|
||||
c := encoded.Chars[i];
|
||||
if (((c = '+') or (c = '$')) or ((c = '%') or (c = '/'))) then
|
||||
begin
|
||||
if ((i + 1) >= encoded.length) then
|
||||
begin
|
||||
Result := '';
|
||||
exit
|
||||
end;
|
||||
next := encoded.Chars[(i + 1)];
|
||||
decodedChar := Char(#0);
|
||||
case c of
|
||||
'+':
|
||||
begin
|
||||
if ((next >= 'A') and (next <= 'Z')) then
|
||||
begin
|
||||
decodedChar := Char(ord(next) + 32);
|
||||
end
|
||||
else
|
||||
begin
|
||||
exit('');
|
||||
end
|
||||
end;
|
||||
'$':
|
||||
begin
|
||||
if ((next >= 'A') and (next <= 'Z')) then
|
||||
begin
|
||||
decodedChar := Char(ord(next) - 64);
|
||||
end
|
||||
else
|
||||
begin
|
||||
exit('');
|
||||
end
|
||||
end;
|
||||
'%':
|
||||
begin
|
||||
if ((next >= 'A') and (next <= 'E')) then
|
||||
decodedChar := Char(ord(next) - 38)
|
||||
else if ((next >= 'F') and (next <= 'W')) then
|
||||
decodedChar := Char(ord(next) - 11)
|
||||
else
|
||||
begin
|
||||
exit('');
|
||||
end;
|
||||
|
||||
end;
|
||||
'/':
|
||||
begin
|
||||
if ((next >= 'A') and (next <= 'O')) then
|
||||
begin
|
||||
decodedChar := Char(ord(next) - 32);
|
||||
end
|
||||
else if (next = 'Z') then
|
||||
begin
|
||||
decodedChar := ':';
|
||||
end
|
||||
else
|
||||
exit('');
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
decoded.Append(decodedChar);
|
||||
Inc(i);
|
||||
end
|
||||
else
|
||||
decoded.Append(c);
|
||||
|
||||
Inc(i);
|
||||
end; // end loop/while
|
||||
|
||||
Result := decoded.ToString;
|
||||
finally
|
||||
decoded.Free;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
function TCode39Reader.decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
var
|
||||
decodedChar: Char;
|
||||
lastStart: Integer;
|
||||
index, nextStart, counter, aEnd, pattern, lastPatternSize: Integer;
|
||||
start: TArray<Integer>;
|
||||
resultString: String;
|
||||
Left, Right: Single;
|
||||
resultPoints: TArray<IResultPoint>;
|
||||
resultPointLeft, resultPointRight: IResultPoint;
|
||||
whiteSpaceAfterEnd: Integer;
|
||||
i, max, total: Integer;
|
||||
begin
|
||||
for index := 0 to length(counters) - 1 do
|
||||
begin
|
||||
counters[index] := 0;
|
||||
end;
|
||||
|
||||
decodeRowResult.length := 0;
|
||||
|
||||
start := self.findAsteriskPattern(row);
|
||||
if (start = nil) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
nextStart := row.getNextSet(start[1]);
|
||||
aEnd := row.Size;
|
||||
repeat
|
||||
if (not TOneDReader.recordPattern(row, nextStart, self.counters)) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
pattern := TCode39Reader.toNarrowWidePattern(self.counters);
|
||||
if (pattern < 0) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
if (not TCode39Reader.patternToChar(pattern, decodedChar)) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
self.decodeRowResult.Append(decodedChar);
|
||||
lastStart := nextStart;
|
||||
|
||||
for counter in self.counters do
|
||||
begin
|
||||
Inc(nextStart, counter)
|
||||
end;
|
||||
|
||||
nextStart := row.getNextSet(nextStart)
|
||||
|
||||
until (decodedChar = '*');
|
||||
|
||||
self.decodeRowResult.Remove((self.decodeRowResult.length - 1), 1);
|
||||
|
||||
lastPatternSize := 0;
|
||||
|
||||
for counter in self.counters do
|
||||
begin
|
||||
Inc(lastPatternSize, counter)
|
||||
end;
|
||||
|
||||
whiteSpaceAfterEnd := nextStart - lastStart - lastPatternSize;
|
||||
|
||||
if (nextStart <> aEnd) and ((whiteSpaceAfterEnd shl 1) < lastPatternSize) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
if (usingCheckDigit) then
|
||||
begin
|
||||
max := self.decodeRowResult.length - 1;
|
||||
total := 0;
|
||||
for i := 0 to max - 1 do
|
||||
begin
|
||||
Inc(total, ALPHABET_STRING.indexOf(decodeRowResult.Chars[i]));
|
||||
end;
|
||||
|
||||
if (self.decodeRowResult.Chars[max] <> ALPHABET_STRING[total mod 43]) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
self.decodeRowResult.Remove(max, self.decodeRowResult.length);
|
||||
end;
|
||||
|
||||
if (self.decodeRowResult.length = 0) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
if extendedMode then
|
||||
resultString := TCode39Reader.decodeExtended
|
||||
(self.decodeRowResult.ToString())
|
||||
else
|
||||
resultString := decodeRowResult.ToString();
|
||||
|
||||
if (resultString = '') then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
Left := (start[1] + start[0]) / 2;
|
||||
Right := lastStart + (lastPatternSize / 2);
|
||||
|
||||
resultPointLeft := TResultPointHelpers.CreateResultPoint(Left, rowNumber);
|
||||
resultPointRight := TResultPointHelpers.CreateResultPoint(Right, rowNumber);
|
||||
resultPoints := [resultPointLeft, resultPointRight];
|
||||
|
||||
Result := TReadResult.Create(resultString, nil, resultPoints,
|
||||
TBarcodeFormat.CODE_39);
|
||||
|
||||
end;
|
||||
|
||||
function TCode39Reader.findAsteriskPattern(row: IBitArray): TArray<Integer>;
|
||||
var
|
||||
i, l, patternStart, patternLength, width, counterPosition, rowOffset,
|
||||
index: Integer;
|
||||
isWhite: boolean;
|
||||
|
||||
begin
|
||||
width := row.Size;
|
||||
rowOffset := row.getNextSet(0);
|
||||
index := 0;
|
||||
while (index < length(self.counters)) do
|
||||
begin
|
||||
self.counters[index] := 0;
|
||||
Inc(index)
|
||||
end;
|
||||
|
||||
counterPosition := 0;
|
||||
patternStart := rowOffset;
|
||||
isWhite := False;
|
||||
patternLength := length(counters);
|
||||
|
||||
for i := rowOffset to width - 1 do
|
||||
begin
|
||||
|
||||
if (row[i] xor isWhite) then
|
||||
begin
|
||||
Inc(counters[counterPosition])
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (counterPosition = (patternLength - 1)) then
|
||||
begin
|
||||
|
||||
if (TCode39Reader.toNarrowWidePattern(counters)
|
||||
= TCode39Reader.ASTERISK_ENCODING) then
|
||||
begin
|
||||
Result := TArray<Integer>.Create(patternStart, i);
|
||||
exit
|
||||
end;
|
||||
|
||||
Inc(patternStart, (counters[0] + counters[1]));
|
||||
|
||||
for l := 2 to patternLength - 2 do
|
||||
begin
|
||||
counters[l - 2] := counters[l];
|
||||
end;
|
||||
|
||||
counters[(patternLength - 2)] := 0;
|
||||
counters[(patternLength - 1)] := 0;
|
||||
dec(counterPosition)
|
||||
|
||||
end
|
||||
else
|
||||
Inc(counterPosition);
|
||||
|
||||
counters[counterPosition] := 1;
|
||||
isWhite := not isWhite
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := nil;
|
||||
|
||||
end;
|
||||
|
||||
class function TCode39Reader.patternToChar(pattern: Integer;
|
||||
var c: Char): boolean;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
i := 0;
|
||||
while (i < length(TCode39Reader.CHARACTER_ENCODINGS)) do
|
||||
begin
|
||||
if (TCode39Reader.CHARACTER_ENCODINGS[i] = pattern) then
|
||||
begin
|
||||
c := TCode39Reader.ALPHABET[i];
|
||||
begin
|
||||
Result := true;
|
||||
exit
|
||||
end
|
||||
end;
|
||||
Inc(i)
|
||||
end;
|
||||
c := '*';
|
||||
Result := False;
|
||||
end;
|
||||
|
||||
class function TCode39Reader.toNarrowWidePattern
|
||||
(counters: TArray<Integer>): Integer;
|
||||
var
|
||||
numCounters: Integer;
|
||||
maxNarrowCounter: Integer;
|
||||
wideCounters: Integer;
|
||||
minCounter: Integer;
|
||||
counter: Integer;
|
||||
totalWideCountersWidth: Integer;
|
||||
pattern: Integer;
|
||||
i: Integer;
|
||||
begin
|
||||
numCounters := length(counters);
|
||||
maxNarrowCounter := 0;
|
||||
|
||||
repeat
|
||||
minCounter := High(Integer);
|
||||
for counter in counters do
|
||||
begin
|
||||
if (counter < minCounter) and (counter > maxNarrowCounter) then
|
||||
minCounter := counter;
|
||||
end;
|
||||
|
||||
maxNarrowCounter := minCounter;
|
||||
wideCounters := 0;
|
||||
totalWideCountersWidth := 0;
|
||||
pattern := 0;
|
||||
for i := 0 to numCounters - 1 do
|
||||
begin
|
||||
counter := counters[i];
|
||||
if (counter > maxNarrowCounter) then
|
||||
begin
|
||||
pattern := pattern or (1 shl (numCounters - 1 - i));
|
||||
Inc(wideCounters);
|
||||
Inc(totalWideCountersWidth, counter);
|
||||
end;
|
||||
end;
|
||||
if (wideCounters = 3) then
|
||||
begin
|
||||
// Found 3 wide counters, but are they close enough in width?
|
||||
// We can perform a cheap, conservative check to see if any individual
|
||||
// counter is more than 1.5 times the average:
|
||||
i := 0;
|
||||
while (i < numCounters) and (wideCounters > 0) do
|
||||
begin
|
||||
counter := counters[i];
|
||||
if (counter > maxNarrowCounter) then
|
||||
begin
|
||||
dec(wideCounters);
|
||||
// totalWideCountersWidth = 3 * average, so this checks if counter >= 3/2 * average
|
||||
if ((counter * 2) >= totalWideCountersWidth) then
|
||||
begin
|
||||
Result := -1;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
Inc(i);
|
||||
end;
|
||||
Result := pattern;
|
||||
exit;
|
||||
end;
|
||||
until (wideCounters <= 3);
|
||||
Result := -1;
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,523 @@
|
||||
{
|
||||
* Copyright 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
* Original Authors: Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.OneD.Code93Reader;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
Math,
|
||||
ZXing.OneD.OneDReader,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BarcodeFormat,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Decodes Code 93 barcodes.</p>
|
||||
/// <see cref="TCode39Reader" />
|
||||
/// </summary>
|
||||
TCode93Reader = class sealed(TOneDReader)
|
||||
private
|
||||
class var ALPHABET: TArray<Char>;
|
||||
class function checkChecksums(pResult: TStringBuilder): boolean; static;
|
||||
class function checkOneChecksum(pResult: TStringBuilder;
|
||||
checkPosition, weightMax: Integer): boolean;
|
||||
class function decodeExtended(encoded: TStringBuilder): string; static;
|
||||
|
||||
function findAsteriskPattern(row: IBitArray): TArray<Integer>;
|
||||
class function patternToChar(pattern: Integer; var c: Char)
|
||||
: boolean; static;
|
||||
class function toPattern(counters: TArray<Integer>): Integer; static;
|
||||
|
||||
const
|
||||
ALPHABET_STRING
|
||||
: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*';
|
||||
class var ASTERISK_ENCODING: Integer;
|
||||
|
||||
class var
|
||||
CHARACTER_ENCODINGS: TArray<Integer>;
|
||||
counters: TArray<Integer>;
|
||||
decodeRowResult: TStringBuilder;
|
||||
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ Code93Reader }
|
||||
|
||||
constructor TCode93Reader.Create;
|
||||
begin
|
||||
ALPHABET := '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*'.ToCharArray;
|
||||
CHARACTER_ENCODINGS := TArray<Integer>.Create($114, $148, $144, $142, $128,
|
||||
$124, 290, $150, $112, $10A, $1A8, 420, $1A2, $194, $192, $18A, 360, $164,
|
||||
$162, $134, $11A, $158, $14C, $146, 300, $116, $1B4, $1B2, $1AC, $1A6, $196,
|
||||
410, $16C, $166, 310, $13A, $12E, $1D4, $1D2, $1CA, $16E, $176, 430, $126,
|
||||
$1DA, 470, $132, 350);
|
||||
|
||||
ASTERISK_ENCODING := TCode93Reader.CHARACTER_ENCODINGS[$2F];
|
||||
|
||||
counters := TArray<Integer>.Create();
|
||||
SetLength(counters, 6);
|
||||
decodeRowResult := TStringBuilder.Create();
|
||||
end;
|
||||
|
||||
destructor TCode93Reader.Destroy;
|
||||
begin
|
||||
ALPHABET := nil;
|
||||
CHARACTER_ENCODINGS := nil;
|
||||
counters := nil;
|
||||
FreeAndNil(decodeRowResult);
|
||||
inherited;
|
||||
end;
|
||||
|
||||
class function TCode93Reader.checkChecksums(pResult: TStringBuilder): boolean;
|
||||
var
|
||||
length: Integer;
|
||||
begin
|
||||
length := pResult.length;
|
||||
if (not TCode93Reader.checkOneChecksum(pResult, (length - 2), 20)) then
|
||||
begin
|
||||
Result := false;
|
||||
exit
|
||||
end;
|
||||
if (not TCode93Reader.checkOneChecksum(pResult, (length - 1), 15)) then
|
||||
begin
|
||||
Result := false;
|
||||
exit
|
||||
end;
|
||||
begin
|
||||
Result := true;
|
||||
exit
|
||||
end
|
||||
end;
|
||||
|
||||
class function TCode93Reader.checkOneChecksum(pResult: TStringBuilder;
|
||||
checkPosition, weightMax: Integer): boolean;
|
||||
var
|
||||
weight, total, i: Integer;
|
||||
begin
|
||||
weight := 1;
|
||||
total := 0;
|
||||
i := (checkPosition - 1);
|
||||
while ((i >= 0)) do
|
||||
begin
|
||||
|
||||
inc(total, (weight * ALPHABET_STRING.IndexOf(pResult.Chars[i])));
|
||||
|
||||
inc(weight);
|
||||
|
||||
if (weight > weightMax) then
|
||||
weight := 1;
|
||||
|
||||
dec(i)
|
||||
end;
|
||||
|
||||
if (pResult.Chars[checkPosition] <> TCode93Reader.ALPHABET[(total mod $2F)])
|
||||
then
|
||||
begin
|
||||
Result := false;
|
||||
exit
|
||||
end;
|
||||
begin
|
||||
Result := true;
|
||||
exit
|
||||
end
|
||||
end;
|
||||
|
||||
class function TCode93Reader.decodeExtended(encoded: TStringBuilder): string;
|
||||
var
|
||||
next, c: Char;
|
||||
decodedChar: Char;
|
||||
length, i: Integer;
|
||||
decoded: TStringBuilder;
|
||||
|
||||
label Label_0179, Label_0169, Label_014A;
|
||||
|
||||
begin
|
||||
length := encoded.length;
|
||||
decoded := TStringBuilder.Create(length);
|
||||
try
|
||||
i := 0;
|
||||
next := Char(0);
|
||||
while ((i < length)) do
|
||||
begin
|
||||
|
||||
c := encoded.Chars[i];
|
||||
|
||||
if ((c < 'a') or (c > 'd')) then
|
||||
goto Label_0179;
|
||||
|
||||
if (i < (length - 1)) then
|
||||
begin
|
||||
|
||||
next := encoded.Chars[(i + 1)];
|
||||
decodedChar := #0;
|
||||
|
||||
case c of
|
||||
'a':
|
||||
begin
|
||||
if ((next < 'A') or (next > 'Z')) then
|
||||
begin
|
||||
Result := '';
|
||||
exit
|
||||
end;
|
||||
decodedChar := Char(ord(next) - ord('@'));
|
||||
goto Label_0169
|
||||
end;
|
||||
'b':
|
||||
begin
|
||||
if ((next < 'A') or (next > 'E')) then
|
||||
break;;
|
||||
decodedChar := Char(ord(next) - ord('&'));
|
||||
goto Label_0169
|
||||
end;
|
||||
'c':
|
||||
begin
|
||||
if ((next < 'A') or (next > 'O')) then
|
||||
goto Label_014A;
|
||||
decodedChar := Char(ord(next) - ord(' '));
|
||||
goto Label_0169
|
||||
end;
|
||||
'd':
|
||||
begin
|
||||
if ((next < 'A') or (next > 'Z')) then
|
||||
begin
|
||||
Result := '';
|
||||
exit
|
||||
end;
|
||||
decodedChar := Char(ord(next) + ord(' '));
|
||||
goto Label_0169
|
||||
end;
|
||||
else
|
||||
begin
|
||||
goto Label_0169
|
||||
end;
|
||||
end;
|
||||
if ((next >= 'F') and (next <= 'W')) then
|
||||
begin
|
||||
decodedChar := Char(ord(next) - ord(''));
|
||||
goto Label_0169
|
||||
end
|
||||
end;
|
||||
begin
|
||||
Result := '';
|
||||
exit
|
||||
end;
|
||||
|
||||
Label_014A:
|
||||
if (next = 'Z') then
|
||||
decodedChar := ':'
|
||||
else
|
||||
begin
|
||||
Result := '';
|
||||
exit
|
||||
end;
|
||||
|
||||
Label_0169:
|
||||
decoded.Append(decodedChar);
|
||||
inc(i);
|
||||
continue;
|
||||
|
||||
Label_0179:
|
||||
decoded.Append(c);
|
||||
inc(i)
|
||||
end;
|
||||
|
||||
Result := decoded.ToString;
|
||||
|
||||
finally
|
||||
FreeAndNil(decoded);
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
function TCode93Reader.decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
var
|
||||
decodedChar: Char;
|
||||
lastStart: Integer;
|
||||
index, nextStart, counter, aEnd, pattern, lastPatternSize: Integer;
|
||||
start: TArray<Integer>;
|
||||
resultString: String;
|
||||
Left, Right: Single;
|
||||
resultPointCallback: TResultPointCallback;
|
||||
obj: TObject;
|
||||
resultPoints: TArray<IResultPoint>;
|
||||
resultPointLeft, resultPointRight: IResultPoint;
|
||||
begin
|
||||
for index := 0 to length(counters) - 1 do
|
||||
counters[index] := 0;
|
||||
|
||||
decodeRowResult.length := 0;
|
||||
|
||||
start := self.findAsteriskPattern(row);
|
||||
if (start = nil) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
nextStart := row.getNextSet(start[1]);
|
||||
aEnd := row.Size;
|
||||
repeat
|
||||
if (not TOneDReader.recordPattern(row, nextStart, self.counters)) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
pattern := TCode93Reader.toPattern(self.counters);
|
||||
if (pattern < 0) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
if (not TCode93Reader.patternToChar(pattern, decodedChar)) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
self.decodeRowResult.Append(decodedChar);
|
||||
lastStart := nextStart;
|
||||
|
||||
for counter in self.counters do
|
||||
begin
|
||||
inc(nextStart, counter)
|
||||
end;
|
||||
nextStart := row.getNextSet(nextStart)
|
||||
|
||||
until (decodedChar = '*');
|
||||
|
||||
self.decodeRowResult.Remove((self.decodeRowResult.length - 1), 1);
|
||||
|
||||
lastPatternSize := 0;
|
||||
|
||||
for counter in self.counters do
|
||||
begin
|
||||
inc(lastPatternSize, counter)
|
||||
end;
|
||||
|
||||
if (not((nextStart <> aEnd) and row[nextStart])) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
if (self.decodeRowResult.length < 2) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
if (not TCode93Reader.checkChecksums(self.decodeRowResult)) then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
self.decodeRowResult.length := self.decodeRowResult.length - 2;
|
||||
|
||||
resultString := TCode93Reader.decodeExtended(self.decodeRowResult);
|
||||
if (resultString = '') then
|
||||
begin
|
||||
Result := nil;
|
||||
exit
|
||||
end;
|
||||
|
||||
Left := (start[1] + start[0]) div 2;
|
||||
Right := (lastStart + lastPatternSize) / 2;
|
||||
|
||||
resultPointLeft := TResultPointHelpers.CreateResultPoint(Left, rowNumber);
|
||||
resultPointRight := TResultPointHelpers.CreateResultPoint(Right, rowNumber);
|
||||
resultPoints := [resultPointLeft, resultPointRight];
|
||||
|
||||
resultPointCallback := nil;
|
||||
// it is a local variable: it doesn't get NIL as default value, and the following ifs do not assign a value for all possible cases
|
||||
|
||||
if ((hints = nil) or
|
||||
(not hints.ContainsKey(TDecodeHintType.NEED_RESULT_POINT_CALLBACK))) then
|
||||
resultPointCallback := nil
|
||||
else
|
||||
begin
|
||||
obj := hints[TDecodeHintType.NEED_RESULT_POINT_CALLBACK];
|
||||
if (obj is TResultPointEventObject) then
|
||||
resultPointCallback := TResultPointEventObject(obj).Event;
|
||||
end;
|
||||
|
||||
if Assigned(resultPointCallback) then
|
||||
begin
|
||||
resultPointCallback(resultPointLeft);
|
||||
resultPointCallback(resultPointRight);
|
||||
end;
|
||||
|
||||
Result := TReadResult.Create(resultString, nil, resultPoints,
|
||||
TBarcodeFormat.CODE_93);
|
||||
end;
|
||||
|
||||
function TCode93Reader.findAsteriskPattern(row: IBitArray): TArray<Integer>;
|
||||
var
|
||||
i, l, patternStart, patternLength, width, counterPosition, rowOffset,
|
||||
index: Integer;
|
||||
isWhite: boolean;
|
||||
|
||||
begin
|
||||
width := row.Size;
|
||||
rowOffset := row.getNextSet(0);
|
||||
index := 0;
|
||||
while (index < length(self.counters)) do
|
||||
begin
|
||||
self.counters[index] := 0;
|
||||
inc(index)
|
||||
end;
|
||||
|
||||
counterPosition := 0;
|
||||
patternStart := rowOffset;
|
||||
isWhite := false;
|
||||
patternLength := length(counters);
|
||||
|
||||
for i := rowOffset to width - 1 do
|
||||
begin
|
||||
|
||||
if (row[i] xor isWhite) then
|
||||
begin
|
||||
inc(counters[counterPosition])
|
||||
end
|
||||
else
|
||||
begin
|
||||
if (counterPosition = (patternLength - 1)) then
|
||||
begin
|
||||
|
||||
if (TCode93Reader.toPattern(counters) = TCode93Reader.ASTERISK_ENCODING)
|
||||
then
|
||||
begin
|
||||
Result := TArray<Integer>.Create(patternStart, i);
|
||||
exit
|
||||
end;
|
||||
|
||||
inc(patternStart, (counters[0] + counters[1]));
|
||||
|
||||
for l := 2 to patternLength - 2 do
|
||||
begin
|
||||
counters[l - 2] := counters[l];
|
||||
end;
|
||||
|
||||
counters[(patternLength - 2)] := 0;
|
||||
counters[(patternLength - 1)] := 0;
|
||||
dec(counterPosition)
|
||||
|
||||
end
|
||||
else
|
||||
inc(counterPosition);
|
||||
|
||||
counters[counterPosition] := 1;
|
||||
isWhite := not isWhite
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := nil;
|
||||
|
||||
end;
|
||||
|
||||
class function TCode93Reader.patternToChar(pattern: Integer;
|
||||
var c: Char): boolean;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
i := 0;
|
||||
while (i < length(TCode93Reader.CHARACTER_ENCODINGS)) do
|
||||
begin
|
||||
if (TCode93Reader.CHARACTER_ENCODINGS[i] = pattern) then
|
||||
begin
|
||||
c := TCode93Reader.ALPHABET[i];
|
||||
begin
|
||||
Result := true;
|
||||
exit
|
||||
end
|
||||
end;
|
||||
inc(i)
|
||||
end;
|
||||
c := '*';
|
||||
Result := false;
|
||||
end;
|
||||
|
||||
class function TCode93Reader.toPattern(counters: TArray<Integer>): Integer;
|
||||
var
|
||||
counter, max, sum, pattern, i, j, scaledShifted, scaledUnshifted: Integer;
|
||||
begin
|
||||
max := length(counters);
|
||||
sum := 0;
|
||||
|
||||
for counter in counters do
|
||||
begin
|
||||
inc(sum, counter)
|
||||
end;
|
||||
|
||||
pattern := 0;
|
||||
i := 0;
|
||||
while ((i < max)) do
|
||||
begin
|
||||
|
||||
scaledShifted := (((counters[i] shl TOneDReader.INTEGER_MATH_SHIFT) *
|
||||
9) div sum);
|
||||
|
||||
scaledUnshifted := TMathUtils.Asr(scaledShifted,
|
||||
TOneDReader.INTEGER_MATH_SHIFT);
|
||||
|
||||
if ((scaledShifted and $FF) > $7F) then
|
||||
inc(scaledUnshifted);
|
||||
|
||||
if ((scaledUnshifted < 1) or (scaledUnshifted > 4)) then
|
||||
begin
|
||||
Result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
if ((i and 1) = 0) then
|
||||
begin
|
||||
j := 0;
|
||||
|
||||
while ((j < scaledUnshifted)) do
|
||||
begin
|
||||
pattern := ((pattern shl 1) or 1);
|
||||
inc(j)
|
||||
end
|
||||
end
|
||||
else
|
||||
pattern := (pattern shl scaledUnshifted);
|
||||
|
||||
inc(i)
|
||||
end;
|
||||
|
||||
Result := pattern;
|
||||
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,219 @@
|
||||
{
|
||||
* Copyright 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
* Original Authors: dswitkin@google.com (Daniel Switkin), Sean Owen and
|
||||
* alasdair@google.com (Alasdair Mackintosh)
|
||||
* Delphi Implementation by K. Gossens, E. Spelt
|
||||
}
|
||||
|
||||
unit ZXing.OneD.EAN13Reader;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
ZXing.OneD.OneDReader,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.OneD.UPCEANReader,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BarcodeFormat;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Implements decoding of the EAN-13 format.</p>
|
||||
/// </summary>
|
||||
TEAN13Reader = class(TUPCEANReader)
|
||||
private
|
||||
// For an EAN-13 barcode, the first digit is represented by the parities used
|
||||
// to encode the next six digits, according to the table below. For example,
|
||||
// if the barcode is 5 123456 789012 then the value of the first digit is
|
||||
// signified by using odd for '1', even for '2', even for '3', odd for '4',
|
||||
// odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13
|
||||
//
|
||||
// Parity of next 6 digits
|
||||
// Digit 0 1 2 3 4 5
|
||||
// 0 Odd Odd Odd Odd Odd Odd
|
||||
// 1 Odd Odd Even Odd Even Even
|
||||
// 2 Odd Odd Even Even Odd Even
|
||||
// 3 Odd Odd Even Even Even Odd
|
||||
// 4 Odd Even Odd Odd Even Even
|
||||
// 5 Odd Even Even Odd Odd Even
|
||||
// 6 Odd Even Even Even Odd Odd
|
||||
// 7 Odd Even Odd Even Odd Even
|
||||
// 8 Odd Even Odd Even Even Odd
|
||||
// 9 Odd Even Even Odd Even Odd
|
||||
//
|
||||
// Note that the encoding for '0' uses the same parity as a UPC barcode. Hence
|
||||
// a UPC barcode can be converted to an EAN-13 barcode by prepending a 0.
|
||||
//
|
||||
// The encoding is represented by the following array, which is a bit pattern
|
||||
// using Odd = 0 and Even = 1. For example, 5 is represented by:
|
||||
//
|
||||
// Odd Even Even Odd Odd Even
|
||||
// in binary:
|
||||
// 0 1 1 0 0 1 == 0x19
|
||||
//
|
||||
class var
|
||||
FIRST_DIGIT_ENCODINGS: TArray<Integer>;
|
||||
decodeMiddleCounters: TArray<Integer>;
|
||||
|
||||
/// <summary>
|
||||
/// Based on pattern of odd-even ('L' and 'G') patterns used to encoded the explicitly-encoded
|
||||
/// digits in a barcode, determines the implicitly encoded first digit and adds it to the
|
||||
/// result string.
|
||||
/// </summary>
|
||||
/// <param name="resultString">string to insert decoded first digit into</param>
|
||||
/// <param name="lgPatternFound">int whose bits indicates the pattern of odd/even L/G patterns used to</param>
|
||||
/// encode digits
|
||||
/// <return>-1 if first digit cannot be determined</return>
|
||||
class function determineFirstDigit(const resultString: TStringBuilder;
|
||||
const lgPatternFound: Integer): Boolean; static;
|
||||
|
||||
class procedure InitializeClass; static;
|
||||
class procedure FinalizeClass; static;
|
||||
protected
|
||||
public
|
||||
|
||||
/// <summary>
|
||||
/// Subclasses override this to decode the portion of a barcode between the start
|
||||
/// and end guard patterns.
|
||||
/// </summary>
|
||||
/// <param name="row">row of black/white values to search</param>
|
||||
/// <param name="startRange">start/end offset of start guard pattern</param>
|
||||
/// <param name="resultString"><see cref="StringBuilder"/>to append decoded chars to</param>
|
||||
/// <returns>
|
||||
/// horizontal offset of first pixel after the "middle" that was decoded or -1 if decoding could not complete successfully
|
||||
/// </returns>
|
||||
class function DecodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>; const resultString: TStringBuilder)
|
||||
: Integer; override;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the format of this decoder.
|
||||
/// <returns>The 1D format.</returns>
|
||||
/// </summary>
|
||||
function BarcodeFormat: TBarcodeFormat; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TEAN13Reader }
|
||||
|
||||
class function TEAN13Reader.DecodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>;
|
||||
const resultString: TStringBuilder): Integer;
|
||||
var
|
||||
bestMatch: Integer;
|
||||
counter: Integer;
|
||||
ending: Integer;
|
||||
counters, middleRange: TArray<Integer>;
|
||||
rowOffset, x, lgPatternFound: Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
counters := decodeMiddleCounters;
|
||||
counters[0] := 0;
|
||||
counters[1] := 0;
|
||||
counters[2] := 0;
|
||||
counters[3] := 0;
|
||||
ending := row.Size;
|
||||
rowOffset := startRange[1];
|
||||
lgPatternFound := 0;
|
||||
|
||||
x := 0;
|
||||
while ((x < 6) and (rowOffset < ending)) do
|
||||
begin
|
||||
if (not decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS, bestMatch))
|
||||
then
|
||||
exit;
|
||||
resultString.Append( IntToStr ( bestMatch mod 10) );
|
||||
for counter in counters do
|
||||
Inc(rowOffset, counter);
|
||||
if (bestMatch >= 10) then
|
||||
lgPatternFound := lgPatternFound or (1 shl (5 - x));
|
||||
Inc(x);
|
||||
end;
|
||||
|
||||
if (not determineFirstDigit(resultString, lgPatternFound)) then
|
||||
exit;
|
||||
|
||||
middleRange := findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
|
||||
if (middleRange = nil) then
|
||||
exit;
|
||||
rowOffset := middleRange[1];
|
||||
|
||||
x := 0;
|
||||
while ((x < 6) and (rowOffset < ending)) do
|
||||
begin
|
||||
if (not decodeDigit(row, counters, rowOffset, L_PATTERNS, bestMatch)) then
|
||||
exit;
|
||||
resultString.Append(IntToStr(bestMatch));
|
||||
for counter in counters do
|
||||
Inc(rowOffset, counter);
|
||||
Inc(x);
|
||||
end;
|
||||
|
||||
Result := rowOffset;
|
||||
end;
|
||||
|
||||
function TEAN13Reader.BarcodeFormat: TBarcodeFormat;
|
||||
begin
|
||||
Result := TBarcodeFormat.EAN_13;
|
||||
end;
|
||||
|
||||
class function TEAN13Reader.determineFirstDigit(const resultString
|
||||
: TStringBuilder; const lgPatternFound: Integer): Boolean;
|
||||
var
|
||||
d: Integer;
|
||||
begin
|
||||
Result := false;
|
||||
for d := 0 to Pred(10) do
|
||||
begin
|
||||
if (lgPatternFound = FIRST_DIGIT_ENCODINGS[d]) then
|
||||
begin
|
||||
resultString.Insert(0, IntToStr(d));
|
||||
Result := true;
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
class procedure TEAN13Reader.InitializeClass;
|
||||
begin
|
||||
FIRST_DIGIT_ENCODINGS := TArray<Integer>.Create($00, $0B, $0D, $0E, $13, $19,
|
||||
$1C, $15, $16, $1A);
|
||||
SetLength(decodeMiddleCounters, 4);
|
||||
end;
|
||||
|
||||
class procedure TEAN13Reader.FinalizeClass;
|
||||
begin
|
||||
FIRST_DIGIT_ENCODINGS := nil;
|
||||
decodeMiddleCounters := nil;
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TEAN13Reader.InitializeClass;
|
||||
|
||||
finalization
|
||||
|
||||
TEAN13Reader.FinalizeClass;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,148 @@
|
||||
{
|
||||
* Copyright 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
* Original Authors: dswitkin@google.com (Daniel Switkin), Sean Owen and
|
||||
* alasdair@google.com (Alasdair Mackintosh)
|
||||
*
|
||||
* Delphi Implementation by E. Spelt
|
||||
}
|
||||
|
||||
unit ZXing.OneD.EAN8Reader;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
ZXing.OneD.OneDReader,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.OneD.UPCEANReader,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BarcodeFormat;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Implements decoding of the EAN-8 format.</p>
|
||||
/// </summary>
|
||||
TEAN8Reader = class(TUPCEANReader)
|
||||
|
||||
private
|
||||
class var DecodeMiddleCounters: TArray<Integer>;
|
||||
|
||||
class procedure DoInitialize();
|
||||
class procedure DoFinalize();
|
||||
public
|
||||
class function DecodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>; const resultString: TStringBuilder)
|
||||
: Integer; override;
|
||||
|
||||
function BarcodeFormat: TBarcodeFormat; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
function TEAN8Reader.BarcodeFormat: TBarcodeFormat;
|
||||
begin
|
||||
result := TBarcodeFormat.EAN_8;
|
||||
end;
|
||||
|
||||
class procedure TEAN8Reader.DoFinalize;
|
||||
begin
|
||||
DecodeMiddleCounters := nil;
|
||||
end;
|
||||
|
||||
class procedure TEAN8Reader.DoInitialize;
|
||||
begin
|
||||
SetLength(DecodeMiddleCounters, 4);
|
||||
end;
|
||||
|
||||
class function TEAN8Reader.DecodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>;
|
||||
const resultString: TStringBuilder): Integer;
|
||||
var
|
||||
ending, rowOffset, x, bestMatch: Integer;
|
||||
counter: Integer;
|
||||
counters, middleRange: TArray<Integer>;
|
||||
begin
|
||||
counters := self.DecodeMiddleCounters;
|
||||
counters[0] := 0;
|
||||
counters[1] := 0;
|
||||
counters[2] := 0;
|
||||
counters[3] := 0;
|
||||
ending := row.Size;
|
||||
rowOffset := startRange[1];
|
||||
x := 0;
|
||||
while (((x < 4) and (rowOffset < ending))) do
|
||||
begin
|
||||
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset,
|
||||
TUPCEANReader.L_PATTERNS, bestMatch)) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
resultString.Append(IntToStr(bestMatch));
|
||||
|
||||
for counter in counters do
|
||||
begin
|
||||
inc(rowOffset, counter)
|
||||
end;
|
||||
inc(x)
|
||||
end;
|
||||
|
||||
middleRange := TUPCEANReader.findGuardPattern(row, rowOffset, true,
|
||||
TUPCEANReader.MIDDLE_PATTERN);
|
||||
if (middleRange = nil) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
rowOffset := middleRange[1];
|
||||
x := 0;
|
||||
while (((x < 4) and (rowOffset < ending))) do
|
||||
begin
|
||||
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset,
|
||||
TUPCEANReader.L_PATTERNS, bestMatch)) then
|
||||
begin
|
||||
result := -1;
|
||||
exit
|
||||
end;
|
||||
|
||||
resultString.Append(IntToStr(bestMatch));
|
||||
for counter in counters do
|
||||
begin
|
||||
inc(rowOffset, counter)
|
||||
end;
|
||||
|
||||
inc(x)
|
||||
end;
|
||||
|
||||
result := rowOffset;
|
||||
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TEAN8Reader.DoInitialize;
|
||||
|
||||
finalization
|
||||
|
||||
TEAN8Reader.DoFinalize;
|
||||
|
||||
end.
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
{
|
||||
* Copyright 2010 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.OneD.EANManufacturerOrgSupport;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Classes,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
ZXing.OneD.UPCEANExtension2Support,
|
||||
ZXing.OneD.UPCEANExtension5Support,
|
||||
ZXing.Reader,
|
||||
ZXing.BinaryBitmap,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultMetadataType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
// <summary>
|
||||
/// Records EAN prefix to GS1 Member Organization, where the member organization
|
||||
/// correlates strongly with a country. This is an imperfect means of identifying
|
||||
/// a country of origin by EAN-13 barcode value. See
|
||||
/// <a href="http://en.wikipedia.org/wiki/List_of_GS1_country_codes">
|
||||
/// http://en.wikipedia.org/wiki/List_of_GS1_country_codes</a>.
|
||||
/// </summary>
|
||||
TEANManufacturerOrgSupport = class sealed
|
||||
private
|
||||
var
|
||||
ranges: TList<TArray<Integer>>;
|
||||
countryIdentifiers: TStringList;
|
||||
|
||||
procedure add(const range: TArray<Integer>; const id: string);
|
||||
procedure initIfNeeded;
|
||||
public
|
||||
constructor Create;
|
||||
destructor Destroy; override;
|
||||
|
||||
function lookupCountryIdentifier(const productCode: String): String;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TEANManufacturerOrgSupport }
|
||||
|
||||
constructor TEANManufacturerOrgSupport.Create;
|
||||
begin
|
||||
ranges := TList < TArray < Integer >>.Create;
|
||||
countryIdentifiers := TStringList.Create;
|
||||
end;
|
||||
|
||||
destructor TEANManufacturerOrgSupport.Destroy;
|
||||
begin
|
||||
countryIdentifiers.Free;
|
||||
ranges.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
function TEANManufacturerOrgSupport.lookupCountryIdentifier(const productCode
|
||||
: String): String;
|
||||
var
|
||||
i, prefix, max: Integer;
|
||||
range: TArray<Integer>;
|
||||
start, ending: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
|
||||
initIfNeeded;
|
||||
prefix := Integer.Parse(productCode.Substring(0, 3));
|
||||
max := ranges.Count;
|
||||
for i := 0 to Pred(max) do
|
||||
begin
|
||||
range := ranges[i];
|
||||
start := range[0];
|
||||
if (prefix < start) then
|
||||
exit;
|
||||
if (Length(range) = 1) then
|
||||
ending := start
|
||||
else
|
||||
ending := range[1];
|
||||
if (prefix <= ending) then
|
||||
begin
|
||||
Result := countryIdentifiers[i];
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TEANManufacturerOrgSupport.add(const range: TArray<Integer>;
|
||||
const id: string);
|
||||
begin
|
||||
ranges.add(range);
|
||||
countryIdentifiers.add(id);
|
||||
end;
|
||||
|
||||
procedure TEANManufacturerOrgSupport.initIfNeeded;
|
||||
begin
|
||||
if (ranges.Count = 0) then
|
||||
begin
|
||||
add(TArray<Integer>.Create(0, 19), 'US/CA');
|
||||
add(TArray<Integer>.Create(30, 39), 'US');
|
||||
add(TArray<Integer>.Create(60, 139), 'US/CA');
|
||||
add(TArray<Integer>.Create(300, 379), 'FR');
|
||||
add(TArray<Integer>.Create(380), 'BG');
|
||||
add(TArray<Integer>.Create(383), 'SI');
|
||||
add(TArray<Integer>.Create(385), 'HR');
|
||||
add(TArray<Integer>.Create(387), 'BA');
|
||||
add(TArray<Integer>.Create(400, 440), 'DE');
|
||||
add(TArray<Integer>.Create(450, 459), 'JP');
|
||||
add(TArray<Integer>.Create(460, 469), 'RU');
|
||||
add(TArray<Integer>.Create(471), 'TW');
|
||||
add(TArray<Integer>.Create(474), 'EE');
|
||||
add(TArray<Integer>.Create(475), 'LV');
|
||||
add(TArray<Integer>.Create(476), 'AZ');
|
||||
add(TArray<Integer>.Create(477), 'LT');
|
||||
add(TArray<Integer>.Create(478), 'UZ');
|
||||
add(TArray<Integer>.Create(479), 'LK');
|
||||
add(TArray<Integer>.Create(480), 'PH');
|
||||
add(TArray<Integer>.Create(481), 'BY');
|
||||
add(TArray<Integer>.Create(482), 'UA');
|
||||
add(TArray<Integer>.Create(484), 'MD');
|
||||
add(TArray<Integer>.Create(485), 'AM');
|
||||
add(TArray<Integer>.Create(486), 'GE');
|
||||
add(TArray<Integer>.Create(487), 'KZ');
|
||||
add(TArray<Integer>.Create(489), 'HK');
|
||||
add(TArray<Integer>.Create(490, 499), 'JP');
|
||||
add(TArray<Integer>.Create(500, 509), 'GB');
|
||||
add(TArray<Integer>.Create(520), 'GR');
|
||||
add(TArray<Integer>.Create(528), 'LB');
|
||||
add(TArray<Integer>.Create(529), 'CY');
|
||||
add(TArray<Integer>.Create(531), 'MK');
|
||||
add(TArray<Integer>.Create(535), 'MT');
|
||||
add(TArray<Integer>.Create(539), 'IE');
|
||||
add(TArray<Integer>.Create(540, 549), 'BE/LU');
|
||||
add(TArray<Integer>.Create(560), 'PT');
|
||||
add(TArray<Integer>.Create(569), 'IS');
|
||||
add(TArray<Integer>.Create(570, 579), 'DK');
|
||||
add(TArray<Integer>.Create(590), 'PL');
|
||||
add(TArray<Integer>.Create(594), 'RO');
|
||||
add(TArray<Integer>.Create(599), 'HU');
|
||||
add(TArray<Integer>.Create(600, 601), 'ZA');
|
||||
add(TArray<Integer>.Create(603), 'GH');
|
||||
add(TArray<Integer>.Create(608), 'BH');
|
||||
add(TArray<Integer>.Create(609), 'MU');
|
||||
add(TArray<Integer>.Create(611), 'MA');
|
||||
add(TArray<Integer>.Create(613), 'DZ');
|
||||
add(TArray<Integer>.Create(616), 'KE');
|
||||
add(TArray<Integer>.Create(618), 'CI');
|
||||
add(TArray<Integer>.Create(619), 'TN');
|
||||
add(TArray<Integer>.Create(621), 'SY');
|
||||
add(TArray<Integer>.Create(622), 'EG');
|
||||
add(TArray<Integer>.Create(624), 'LY');
|
||||
add(TArray<Integer>.Create(625), 'JO');
|
||||
add(TArray<Integer>.Create(626), 'IR');
|
||||
add(TArray<Integer>.Create(627), 'KW');
|
||||
add(TArray<Integer>.Create(628), 'SA');
|
||||
add(TArray<Integer>.Create(629), 'AE');
|
||||
add(TArray<Integer>.Create(640, 649), 'FI');
|
||||
add(TArray<Integer>.Create(690, 695), 'CN');
|
||||
add(TArray<Integer>.Create(700, 709), 'NO');
|
||||
add(TArray<Integer>.Create(729), 'IL');
|
||||
add(TArray<Integer>.Create(730, 739), 'SE');
|
||||
add(TArray<Integer>.Create(740), 'GT');
|
||||
add(TArray<Integer>.Create(741), 'SV');
|
||||
add(TArray<Integer>.Create(742), 'HN');
|
||||
add(TArray<Integer>.Create(743), 'NI');
|
||||
add(TArray<Integer>.Create(744), 'CR');
|
||||
add(TArray<Integer>.Create(745), 'PA');
|
||||
add(TArray<Integer>.Create(746), 'DO');
|
||||
add(TArray<Integer>.Create(750), 'MX');
|
||||
add(TArray<Integer>.Create(754, 755), 'CA');
|
||||
add(TArray<Integer>.Create(759), 'VE');
|
||||
add(TArray<Integer>.Create(760, 769), 'CH');
|
||||
add(TArray<Integer>.Create(770), 'CO');
|
||||
add(TArray<Integer>.Create(773), 'UY');
|
||||
add(TArray<Integer>.Create(775), 'PE');
|
||||
add(TArray<Integer>.Create(777), 'BO');
|
||||
add(TArray<Integer>.Create(779), 'AR');
|
||||
add(TArray<Integer>.Create(780), 'CL');
|
||||
add(TArray<Integer>.Create(784), 'PY');
|
||||
add(TArray<Integer>.Create(785), 'PE');
|
||||
add(TArray<Integer>.Create(786), 'EC');
|
||||
add(TArray<Integer>.Create(789, 790), 'BR');
|
||||
add(TArray<Integer>.Create(800, 839), 'IT');
|
||||
add(TArray<Integer>.Create(840, 849), 'ES');
|
||||
add(TArray<Integer>.Create(850), 'CU');
|
||||
add(TArray<Integer>.Create(858), 'SK');
|
||||
add(TArray<Integer>.Create(859), 'CZ');
|
||||
add(TArray<Integer>.Create(860), 'YU');
|
||||
add(TArray<Integer>.Create(865), 'MN');
|
||||
add(TArray<Integer>.Create(867), 'KP');
|
||||
add(TArray<Integer>.Create(868, 869), 'TR');
|
||||
add(TArray<Integer>.Create(870, 879), 'NL');
|
||||
add(TArray<Integer>.Create(880), 'KR');
|
||||
add(TArray<Integer>.Create(885), 'TH');
|
||||
add(TArray<Integer>.Create(888), 'SG');
|
||||
add(TArray<Integer>.Create(890), 'IN');
|
||||
add(TArray<Integer>.Create(893), 'VN');
|
||||
add(TArray<Integer>.Create(896), 'PK');
|
||||
add(TArray<Integer>.Create(899), 'ID');
|
||||
add(TArray<Integer>.Create(900, 919), 'AT');
|
||||
add(TArray<Integer>.Create(930, 939), 'AU');
|
||||
add(TArray<Integer>.Create(940, 949), 'AZ');
|
||||
add(TArray<Integer>.Create(955), 'MY');
|
||||
add(TArray<Integer>.Create(958), 'MO');
|
||||
end;
|
||||
end;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,497 @@
|
||||
{
|
||||
* 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: kevin.osullivan@sita.aero, SITA Lab.
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.OneD.ITFReader;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
Math,
|
||||
ZXing.OneD.OneDReader,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BarcodeFormat,
|
||||
ZXing.Helpers;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Implements decoding of the ITF format, or Interleaved Two of Five.</p>
|
||||
///
|
||||
/// <p>This Reader will scan ITF barcodes of certain lengths only.
|
||||
/// At the moment it reads length 6, 8, 10, 12, 14, 16, 18, 20, 24, 44 and 48 as these have appeared "in the wild". Not all
|
||||
/// lengths are scanned, especially shorter ones, to avoid false positives. This in turn is due to a lack of
|
||||
/// required checksum function.</p>
|
||||
///
|
||||
/// <p>The checksum is optional and is not applied by this Reader. The consumer of the decoded
|
||||
/// value will have to apply a checksum if required.</p>
|
||||
///
|
||||
/// <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>
|
||||
/// is a great reference for Interleaved 2 of 5 information.</p>
|
||||
/// </summary>
|
||||
TITFReader = class(TOneDReader)
|
||||
const
|
||||
LARGEST_DEFAULT_ALLOWED_LENGTH: Integer = 14;
|
||||
N: Integer = 1;
|
||||
W: Integer = 3;
|
||||
private
|
||||
|
||||
class var PATTERNS: TArray<TArray<Integer>>;
|
||||
class var END_PATTERN_REVERSED: TArray<Integer>;
|
||||
class var MAX_AVG_VARIANCE: Integer;
|
||||
class var MAX_INDIVIDUAL_VARIANCE: Integer;
|
||||
class var DEFAULT_ALLOWED_LENGTHS: TArray<Integer>;
|
||||
|
||||
class var
|
||||
START_PATTERN: TArray<Integer>;
|
||||
narrowLineWidth: Integer;
|
||||
|
||||
class function decodeDigit(counters: TArray<Integer>;
|
||||
out bestMatch: Integer): boolean; static;
|
||||
function decodeEnd(row: IBitArray): TArray<Integer>;
|
||||
class function skipWhiteSpace(row: IBitArray): Integer; static;
|
||||
class function findGuardPattern(row: IBitArray; rowOffset: Integer;
|
||||
pattern: TArray<Integer>): TArray<Integer>; static;
|
||||
function validateQuietZone(row: IBitArray; startPattern: Integer): boolean;
|
||||
function decodeStart(row: IBitArray): TArray<Integer>;
|
||||
class function decodeMiddle(row: IBitArray;
|
||||
payloadStart, payloadEnd: Integer; SBResult: TStringBuilder)
|
||||
: boolean; static;
|
||||
class procedure ClassInit();
|
||||
public
|
||||
function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
{ TITFReader }
|
||||
|
||||
class procedure TITFReader.ClassInit();
|
||||
begin
|
||||
MAX_AVG_VARIANCE := Floor(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42);
|
||||
MAX_INDIVIDUAL_VARIANCE := Floor(PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.78);
|
||||
|
||||
DEFAULT_ALLOWED_LENGTHS := [6, 8, 10, 12, 14];
|
||||
|
||||
START_PATTERN := [N, N, N, N];
|
||||
END_PATTERN_REVERSED := [N, N, W];
|
||||
|
||||
PATTERNS := [[N, N, W, W, N], // 0
|
||||
[W, N, N, N, W], // 1
|
||||
[N, W, N, N, W], // 2
|
||||
[W, W, N, N, N], // 3
|
||||
[N, N, W, N, W], // 4
|
||||
[W, N, W, N, N], // 5
|
||||
[N, W, W, N, N], // 6
|
||||
[N, N, N, W, W], // 7
|
||||
[W, N, N, W, N], // 8
|
||||
[N, W, N, W, N]]; // 9
|
||||
end;
|
||||
|
||||
function TITFReader.decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
var
|
||||
allowedLength: Integer;
|
||||
startRange: TArray<Integer>;
|
||||
endRange: TArray<Integer>;
|
||||
stringResult: string;
|
||||
allowedLengths: TArray<Integer>;
|
||||
maxAllowedLength: Integer;
|
||||
len: Integer;
|
||||
lengthOK: boolean;
|
||||
SBResult: TStringBuilder;
|
||||
resultPointCallback: TResultPointCallback;
|
||||
obj: TObject;
|
||||
resultPoints: TArray<IResultPoint>;
|
||||
resultPointLeft,
|
||||
resultPointRight: IResultPoint;
|
||||
begin
|
||||
startRange := decodeStart(row);
|
||||
if (startRange = nil) then
|
||||
begin
|
||||
Exit(nil);
|
||||
end;
|
||||
|
||||
endRange := self.decodeEnd(row);
|
||||
if (endRange = nil) then
|
||||
begin
|
||||
Exit(nil);
|
||||
end;
|
||||
|
||||
SBResult := TStringBuilder.Create(20);
|
||||
if (not decodeMiddle(row, startRange[1], endRange[0], SBResult)) then
|
||||
begin
|
||||
FreeAndNil(sbResult);
|
||||
Exit(nil);
|
||||
end;
|
||||
stringResult := SBResult.ToString();
|
||||
FreeAndNil(sbResult);
|
||||
|
||||
allowedLengths := nil;
|
||||
maxAllowedLength := LARGEST_DEFAULT_ALLOWED_LENGTH;
|
||||
if ((hints <> nil) and hints.ContainsKey(ZXing.DecodeHintType.ALLOWED_LENGTHS)) then
|
||||
begin
|
||||
allowedLengths := TArray<Integer>(hints[ZXing.DecodeHintType.ALLOWED_LENGTHS]);
|
||||
maxAllowedLength := 0
|
||||
end;
|
||||
|
||||
if (allowedLengths = nil) then
|
||||
begin
|
||||
allowedLengths := DEFAULT_ALLOWED_LENGTHS;
|
||||
maxAllowedLength := LARGEST_DEFAULT_ALLOWED_LENGTH;
|
||||
end;
|
||||
|
||||
len := Length(stringResult);
|
||||
lengthOK := (len > LARGEST_DEFAULT_ALLOWED_LENGTH);
|
||||
if (not lengthOK) then
|
||||
begin
|
||||
|
||||
for allowedLength in allowedLengths do
|
||||
begin
|
||||
|
||||
if (len = allowedLength) then
|
||||
begin
|
||||
lengthOK := true;
|
||||
break;
|
||||
end;
|
||||
|
||||
if (allowedLength > maxAllowedLength) then
|
||||
begin
|
||||
maxAllowedLength := allowedLength;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
if ((not lengthOK) and (len > maxAllowedLength)) then
|
||||
begin
|
||||
lengthOK := true;
|
||||
end;
|
||||
|
||||
if (not lengthOK) then
|
||||
begin
|
||||
Exit(nil);
|
||||
end
|
||||
|
||||
end;
|
||||
|
||||
resultPointLeft := TResultPointHelpers.CreateResultPoint(startRange[1], rowNumber);
|
||||
resultPointRight := TResultPointHelpers.CreateResultPoint(endRange[0], rowNumber);
|
||||
resultPoints := [resultPointLeft, resultPointRight];
|
||||
|
||||
resultPointCallback := nil; // had to add this initialization because the following if/else construct does not assign a value
|
||||
// for all possible cases. Being resultPointCallback a local variable it is not initialized by default to null,
|
||||
// so you could get unpredictable results
|
||||
|
||||
|
||||
if ((hints = nil) or
|
||||
(not hints.ContainsKey(TDecodeHintType.NEED_RESULT_POINT_CALLBACK)))
|
||||
then
|
||||
resultPointCallback := nil
|
||||
else
|
||||
begin
|
||||
obj := hints[TDecodeHintType.NEED_RESULT_POINT_CALLBACK];
|
||||
if (obj is TResultPointEventObject)
|
||||
then
|
||||
resultPointCallback := TResultPointEventObject(obj).Event;
|
||||
end;
|
||||
|
||||
if Assigned(resultPointCallback) then
|
||||
begin
|
||||
resultPointCallback(resultPointLeft);
|
||||
resultPointCallback(resultPointRight);
|
||||
end;
|
||||
|
||||
Result := TReadResult.Create(stringResult, nil, resultPoints,
|
||||
TBarcodeFormat.ITF);
|
||||
end;
|
||||
|
||||
class function TITFReader.decodeDigit(counters: TArray<Integer>;
|
||||
out bestMatch: Integer): boolean;
|
||||
var
|
||||
bestVariance: Integer;
|
||||
max: Integer;
|
||||
i: Integer;
|
||||
pattern: TArray<Integer>;
|
||||
variance: Integer;
|
||||
begin
|
||||
bestVariance := MAX_AVG_VARIANCE;
|
||||
bestMatch := -1;
|
||||
max := Length(PATTERNS);
|
||||
i := 0;
|
||||
while ((i < max)) do
|
||||
begin
|
||||
pattern := PATTERNS[i];
|
||||
|
||||
variance := PatternMatchVariance(counters, pattern,
|
||||
MAX_INDIVIDUAL_VARIANCE);
|
||||
if (variance < bestVariance) then
|
||||
begin
|
||||
bestVariance := variance;
|
||||
bestMatch := i
|
||||
end;
|
||||
|
||||
inc(i)
|
||||
end;
|
||||
|
||||
Result := (bestMatch >= 0);
|
||||
|
||||
end;
|
||||
|
||||
function TITFReader.decodeStart(row: IBitArray): TArray<Integer>;
|
||||
var
|
||||
endStart: Integer;
|
||||
startPattern: TArray<Integer>;
|
||||
begin
|
||||
endStart := skipWhiteSpace(row);
|
||||
if (endStart < 0) then
|
||||
begin
|
||||
Exit(nil);
|
||||
end;
|
||||
|
||||
startPattern := findGuardPattern(row, endStart, START_PATTERN);
|
||||
if (startPattern = nil) then
|
||||
begin
|
||||
Exit(nil);
|
||||
end;
|
||||
|
||||
narrowLineWidth := TMathUtils.Asr(startPattern[1] - startPattern[0], 2);
|
||||
if (not validateQuietZone(row, startPattern[0])) then
|
||||
begin
|
||||
Exit(nil);
|
||||
end;
|
||||
|
||||
Result := startPattern;
|
||||
end;
|
||||
|
||||
class function TITFReader.decodeMiddle(row: IBitArray;
|
||||
payloadStart, payloadEnd: Integer; SBResult: TStringBuilder): boolean;
|
||||
var
|
||||
bestMatch: Integer;
|
||||
counterDigit: Integer;
|
||||
counterDigitPair: TArray<Integer>;
|
||||
counterBlack: TArray<Integer>;
|
||||
counterWhite: TArray<Integer>;
|
||||
k: Integer;
|
||||
twoK: Integer;
|
||||
|
||||
begin
|
||||
SetLength(counterDigitPair, 10);
|
||||
SetLength(counterBlack, 5);
|
||||
SetLength(counterWhite, 5);
|
||||
|
||||
while ((payloadStart < payloadEnd)) do
|
||||
begin
|
||||
if (not RecordPattern(row, payloadStart, counterDigitPair)) then
|
||||
begin
|
||||
Exit(false);
|
||||
end;
|
||||
|
||||
k := 0;
|
||||
while ((k < 5)) do
|
||||
begin
|
||||
twoK := (k shl 1);
|
||||
counterBlack[k] := counterDigitPair[twoK];
|
||||
counterWhite[k] := counterDigitPair[(twoK + 1)];
|
||||
inc(k)
|
||||
end;
|
||||
|
||||
if (not decodeDigit(counterBlack, bestMatch)) then // @(bestMatch))) then
|
||||
begin
|
||||
Exit(false);
|
||||
end;
|
||||
SBResult.Append(char($30 + bestMatch));
|
||||
|
||||
if (not decodeDigit(counterWhite, bestMatch)) then // @(bestMatch))) then
|
||||
begin
|
||||
Exit(false);
|
||||
end;
|
||||
|
||||
SBResult.Append(char($30 + bestMatch));
|
||||
|
||||
for counterDigit in counterDigitPair do
|
||||
begin
|
||||
inc(payloadStart, counterDigit);
|
||||
end
|
||||
end;
|
||||
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
function TITFReader.decodeEnd(row: IBitArray): TArray<Integer>;
|
||||
var
|
||||
endStart: Integer;
|
||||
endPattern: TArray<Integer>;
|
||||
temp: Integer;
|
||||
begin
|
||||
row.reverse;
|
||||
endStart := skipWhiteSpace(row);
|
||||
if (endStart < 0) then
|
||||
begin
|
||||
Exit(nil);
|
||||
end;
|
||||
|
||||
// !!
|
||||
endPattern := findGuardPattern(row, endStart, END_PATTERN_REVERSED);
|
||||
if (endPattern = nil) then
|
||||
begin
|
||||
row.reverse;
|
||||
Exit(nil);
|
||||
end;
|
||||
|
||||
if (not validateQuietZone(row, endPattern[0])) then
|
||||
begin
|
||||
row.reverse;
|
||||
Exit(nil);
|
||||
end;
|
||||
|
||||
temp := endPattern[0];
|
||||
endPattern[0] := row.Size - endPattern[1];
|
||||
endPattern[1] := row.Size - temp;
|
||||
row.reverse;
|
||||
|
||||
Result := endPattern;
|
||||
end;
|
||||
|
||||
class function TITFReader.findGuardPattern(row: IBitArray; rowOffset: Integer;
|
||||
pattern: TArray<Integer>): TArray<Integer>;
|
||||
var
|
||||
patternLength: Integer;
|
||||
counters: TArray<Integer>;
|
||||
width: Integer;
|
||||
isWhite: boolean;
|
||||
counterPosition: Integer;
|
||||
patternStart: Integer;
|
||||
x: Integer;
|
||||
begin
|
||||
patternLength := Length(pattern);
|
||||
counters := TArray<Integer>.Create();
|
||||
SetLength(counters, patternLength);
|
||||
width := row.Size;
|
||||
isWhite := false;
|
||||
|
||||
counterPosition := 0;
|
||||
patternStart := rowOffset;
|
||||
x := rowOffset;
|
||||
|
||||
while ((x < width)) do
|
||||
begin
|
||||
|
||||
if (row[x] xor isWhite) then
|
||||
begin
|
||||
inc(counters[counterPosition]);
|
||||
end
|
||||
else
|
||||
begin
|
||||
|
||||
if (counterPosition = (patternLength - 1)) then
|
||||
begin
|
||||
|
||||
if (PatternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) <
|
||||
MAX_AVG_VARIANCE) then
|
||||
begin
|
||||
Result := [patternStart, x];
|
||||
Exit(Result);
|
||||
end;
|
||||
inc(patternStart, (counters[0] + counters[1]));
|
||||
|
||||
counters := TArray.CopyInSameArray(counters, 2, patternLength - 2);
|
||||
|
||||
counters[(patternLength - 2)] := 0;
|
||||
counters[(patternLength - 1)] := 0;
|
||||
dec(counterPosition);
|
||||
|
||||
end
|
||||
else
|
||||
begin
|
||||
inc(counterPosition);
|
||||
end;
|
||||
|
||||
counters[counterPosition] := 1;
|
||||
isWhite := not isWhite
|
||||
|
||||
end;
|
||||
|
||||
inc(x);
|
||||
end;
|
||||
|
||||
Result := nil;
|
||||
|
||||
end;
|
||||
|
||||
class function TITFReader.skipWhiteSpace(row: IBitArray): Integer;
|
||||
var
|
||||
width: Integer;
|
||||
endStart: Integer;
|
||||
begin
|
||||
width := row.Size;
|
||||
endStart := row.getNextSet(0);
|
||||
if (endStart = width) then
|
||||
begin
|
||||
Result := -1;
|
||||
end
|
||||
else
|
||||
begin
|
||||
Result := endStart;
|
||||
end
|
||||
|
||||
end;
|
||||
|
||||
function TITFReader.validateQuietZone(row: IBitArray;
|
||||
startPattern: Integer): boolean;
|
||||
var
|
||||
quietCount: Integer;
|
||||
i: Integer;
|
||||
begin
|
||||
quietCount := (self.narrowLineWidth * 10);
|
||||
if (quietCount >= startPattern) then
|
||||
begin
|
||||
quietCount := startPattern;
|
||||
end;
|
||||
|
||||
i := (startPattern - 1);
|
||||
while (((quietCount > 0) and (i >= 0))) do
|
||||
begin
|
||||
if (row[i]) then
|
||||
begin
|
||||
break;
|
||||
end;
|
||||
dec(quietCount);
|
||||
dec(i)
|
||||
end;
|
||||
if (quietCount <> 0) then
|
||||
begin
|
||||
Exit(false);
|
||||
end;
|
||||
|
||||
Result := true;
|
||||
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TITFReader.ClassInit();
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,499 @@
|
||||
{
|
||||
* 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 Authors: dswitkin@google.com (Daniel Switkin) and Sean Owen
|
||||
* Delphi Implementation by E. Spelt and K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.OneD.OneDReader;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
Math,
|
||||
ZXing.Reader,
|
||||
ZXing.BinaryBitmap,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultMetadataType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// Encapsulates functionality and implementation that is common to all families
|
||||
/// of one-dimensional barcodes.
|
||||
/// </summary>
|
||||
TOneDReader = class(TInterfacedObject, IReader)
|
||||
private
|
||||
/// <summary>
|
||||
/// We're going to examine rows from the middle outward, searching alternately above and below the
|
||||
/// middle, and farther out each time. rowStep is the number of rows between each successive
|
||||
/// attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
|
||||
/// middle + rowStep, then middle - (2 * rowStep), etc.
|
||||
/// rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
|
||||
/// decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
|
||||
/// image if "trying harder".
|
||||
/// </summary>
|
||||
/// <param name="image">The image to decode</param>
|
||||
/// <param name="hints">Any hints that were requested</param>
|
||||
/// <returns>The contents of the decoded barcode</returns>
|
||||
function doDecode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
class procedure InitializeClass; static;
|
||||
protected
|
||||
class var INTEGER_MATH_SHIFT: Integer;
|
||||
class var PATTERN_MATCH_RESULT_SCALE_FACTOR: Integer;
|
||||
|
||||
/// <summary>
|
||||
/// Determines how closely a set of observed counts of runs of black/white values matches a given
|
||||
/// target pattern. This is reported as the ratio of the total variance from the expected pattern
|
||||
/// proportions across all pattern elements, to the length of the pattern.
|
||||
/// </summary>
|
||||
/// <param name="counters">observed counters</param>
|
||||
/// <param name="pattern">expected pattern</param>
|
||||
/// <param name="maxIndividualVariance">The most any counter can differ before we give up</param>
|
||||
/// <returns>ratio of total variance between counters and pattern compared to total pattern size,
|
||||
/// where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
|
||||
/// the total variance between counters and patterns equals the pattern length, higher values mean
|
||||
/// even more variance</returns>
|
||||
class function patternMatchVariance(counters, pattern: TArray<Integer>;
|
||||
maxIndividualVariance: Integer): Integer; static;
|
||||
|
||||
/// <summary>
|
||||
/// Records the pattern in reverse.
|
||||
/// </summary>
|
||||
/// <param name="row">The row.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="counters">The counters.</param>
|
||||
/// <returns></returns>
|
||||
class function RecordPatternInReverse(row: IBitArray; start: Integer;
|
||||
counters: TArray<Integer>): Boolean; Static;
|
||||
|
||||
/// <summary>
|
||||
/// Records the size of successive runs of white and black pixels in a row, starting at a given point.
|
||||
/// The values are recorded in the given array, and the number of runs recorded is equal to the size
|
||||
/// of the array. If the row starts on a white pixel at the given start point, then the first count
|
||||
/// recorded is the run of white pixels starting from that point; likewise it is the count of a run
|
||||
/// of black pixels if the row begin on a black pixels at that point.
|
||||
/// </summary>
|
||||
/// <param name="row">row to count from</param>
|
||||
/// <param name="start">offset into row to start at</param>
|
||||
/// <param name="counters">array into which to record counts</param>
|
||||
class function recordPattern(row: IBitArray; start: Integer;
|
||||
counters: TArray<Integer>; numCounters: Integer): Boolean;
|
||||
overload; static;
|
||||
public
|
||||
/// <summary>
|
||||
/// Resets any internal state the implementation has after a decode, to prepare it
|
||||
/// for reuse.
|
||||
/// </summary>
|
||||
procedure reset(); virtual;
|
||||
/// <summary>
|
||||
/// Locates and decodes a barcode in some format within an image.
|
||||
/// </summary>
|
||||
/// <param name="image">image of barcode to decode</param>
|
||||
/// <returns>
|
||||
/// String which the barcode encodes
|
||||
/// </returns>
|
||||
function decode(const image: TBinaryBitmap): TReadResult; overload;
|
||||
/// <summary>
|
||||
/// Locates and decodes a barcode in some format within an image. This method also accepts
|
||||
/// hints, each possibly associated to some data, which may help the implementation decode.
|
||||
/// Note that we don't try rotation without the try harder flag, even if rotation was supported.
|
||||
/// </summary>
|
||||
/// <param name="image">image of barcode to decode</param>
|
||||
/// <param name="hints">passed as a <see cref="IDictionary{TKey, TValue}"/> from <see cref="DecodeHintType"/>
|
||||
/// to arbitrary data. The
|
||||
/// meaning of the data depends upon the hint type. The implementation may or may not do
|
||||
/// anything with these hints.</param>
|
||||
/// <returns>
|
||||
/// String which the barcode encodes
|
||||
/// </returns>
|
||||
function decode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
overload; virtual;
|
||||
|
||||
/// <summary>
|
||||
/// Records the size of successive runs of white and black pixels in a row, starting at a given point.
|
||||
/// The values are recorded in the given array, and the number of runs recorded is equal to the size
|
||||
/// of the array. If the row starts on a white pixel at the given start point, then the first count
|
||||
/// recorded is the run of white pixels starting from that point; likewise it is the count of a run
|
||||
/// of black pixels if the row begin on a black pixels at that point.
|
||||
/// </summary>
|
||||
/// <param name="row">row to count from</param>
|
||||
/// <param name="start">offset into row to start at</param>
|
||||
/// <param name="counters">array into which to record counts</param>
|
||||
class function recordPattern(row: IBitArray; start: Integer;
|
||||
counters: TArray<Integer>): Boolean; overload; static;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to decode a one-dimensional barcode format given a single row of
|
||||
/// an image.
|
||||
/// </summary>
|
||||
/// <param name="rowNumber">row number from top of the row</param>
|
||||
/// <param name="row">the black/white pixel data of the row</param>
|
||||
/// <param name="hints">decode hints</param>
|
||||
/// <returns>
|
||||
/// <see cref="Result"/>containing encoded string and start/end of barcode
|
||||
/// </returns>
|
||||
function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
virtual; abstract;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TOneDReader }
|
||||
|
||||
function TOneDReader.decode(const image: TBinaryBitmap): TReadResult;
|
||||
begin
|
||||
Result := decode(image, nil);
|
||||
end;
|
||||
|
||||
class procedure TOneDReader.InitializeClass();
|
||||
begin
|
||||
INTEGER_MATH_SHIFT := 8;
|
||||
PATTERN_MATCH_RESULT_SCALE_FACTOR := (1 shl INTEGER_MATH_SHIFT);
|
||||
end;
|
||||
|
||||
function TOneDReader.decode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
var
|
||||
tryHarder, tryHarderWithoutRotation: Boolean;
|
||||
rotatedImage: TBinaryBitmap;
|
||||
metadata: TResultMetadata;
|
||||
orientation, height, i, l: Integer;
|
||||
points: TArray<IResultPoint>;
|
||||
|
||||
begin
|
||||
Result := doDecode(image, hints);
|
||||
if (Result = nil) then
|
||||
begin
|
||||
Exit;
|
||||
end;
|
||||
|
||||
tryHarder := (hints <> nil) and
|
||||
(hints.ContainsKey(ZXing.DecodeHintType.TRY_HARDER));
|
||||
|
||||
tryHarderWithoutRotation := (hints <> nil) and
|
||||
(hints.ContainsKey(ZXing.DecodeHintType.TRY_HARDER_WITHOUT_ROTATION));
|
||||
|
||||
if (tryHarder and ((not tryHarderWithoutRotation) and image.RotateSupported))
|
||||
then
|
||||
begin
|
||||
rotatedImage := image.rotateCounterClockwise();
|
||||
Result := doDecode(rotatedImage, hints);
|
||||
if (Result = nil) then
|
||||
begin
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// Record that we found it rotated 90 degrees CCW / 270 degrees CW
|
||||
metadata := Result.ResultMetadata;
|
||||
orientation := 270;
|
||||
if ((metadata <> nil) and metadata.ContainsKey
|
||||
(ZXing.ResultMetadataType.orientation)) then
|
||||
begin
|
||||
// But if we found it reversed in doDecode(), add in that result here:
|
||||
orientation :=
|
||||
(orientation + (metadata[ZXing.ResultMetadataType.orientation]
|
||||
as IIntegerMetadata).Value) mod 360;
|
||||
end;
|
||||
|
||||
Result.putMetadata(ZXing.ResultMetadataType.orientation,
|
||||
TResultMetadata.CreateIntegerMetadata(orientation));
|
||||
// Update result points
|
||||
points := Result.ResultPoints;
|
||||
if (points <> nil) then
|
||||
begin
|
||||
height := rotatedImage.height;
|
||||
l := Length(points) - 1;
|
||||
for i := 0 to l do
|
||||
begin
|
||||
points[i] := TResultPointHelpers.CreateResultPoint
|
||||
(height - points[i].Y - 1, points[i].X);
|
||||
end;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
function TOneDReader.doDecode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
|
||||
var
|
||||
attempt, X, rowNumber, rowStepsAboveOrBelow, width, height, middle, rowStep,
|
||||
maxLines: Integer;
|
||||
row: IBitArray;
|
||||
isAbove, hadResultPointCallBack: Boolean;
|
||||
ReadResult: TReadResult;
|
||||
points: TArray<IResultPoint>;
|
||||
obj: TObject;
|
||||
needsCallBack : boolean;
|
||||
begin
|
||||
needsCallBack := (hints <> nil) and (hints.ContainsKey(ZXing.DecodeHintType.NEED_RESULT_POINT_CALLBACK));
|
||||
width := image.width;
|
||||
height := image.height;
|
||||
row := TBitArrayHelpers.CreateBitArray(width);
|
||||
// row is a interfaced object: we don't need to free it explicitly
|
||||
|
||||
middle := TMathUtils.Asr(height, 1);
|
||||
|
||||
rowStep := 5;
|
||||
maxLines := height; // Look at the whole image, not just the center
|
||||
|
||||
for X := 0 to maxLines - 1 do
|
||||
begin
|
||||
// Scanning from the middle out. Determine which row we're looking at next:
|
||||
rowStepsAboveOrBelow := TMathUtils.Asr((X + 1), 1);
|
||||
isAbove := (X and $01) = 0; // i.e. is x even?
|
||||
|
||||
if (not isAbove) then
|
||||
begin
|
||||
rowStepsAboveOrBelow := rowStepsAboveOrBelow * -1;
|
||||
end;
|
||||
rowNumber := middle + rowStep * rowStepsAboveOrBelow;
|
||||
|
||||
if ((rowNumber < 0) or (rowNumber >= height)) then
|
||||
begin
|
||||
// Oops, if we run off the top or bottom, stop
|
||||
break;
|
||||
end;
|
||||
|
||||
// Estimate black point for this row and load it:
|
||||
row := image.getBlackRow(rowNumber, row);
|
||||
if (row = nil) then
|
||||
begin
|
||||
continue;
|
||||
end;
|
||||
|
||||
// While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
|
||||
// handle decoding upside down barcodes.
|
||||
// for attempt := 0 to (attempt < 2) do
|
||||
for attempt := 0 to 1 do
|
||||
begin
|
||||
|
||||
hadResultPointCallBack := false;
|
||||
if (attempt = 1) then
|
||||
begin
|
||||
// trying again?
|
||||
row.Reverse();
|
||||
|
||||
|
||||
// This means we will only ever draw result points *once* in the life of this method
|
||||
// since we want to avoid drawing the wrong points after flipping the row, and,
|
||||
// don't want to clutter with noise from every single row scan -- just the scans
|
||||
// that start on the center line.
|
||||
|
||||
if needsCallBack then
|
||||
begin
|
||||
hints.TryGetValue
|
||||
(ZXing.DecodeHintType.NEED_RESULT_POINT_CALLBACK, obj);
|
||||
hints.Remove(ZXing.DecodeHintType.NEED_RESULT_POINT_CALLBACK);
|
||||
hadResultPointCallBack := true;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
// Look for a barcode
|
||||
ReadResult := decodeRow(rowNumber, row, hints);
|
||||
if hadResultPointCallBack then
|
||||
begin
|
||||
hints.Add(ZXing.DecodeHintType.NEED_RESULT_POINT_CALLBACK, obj);
|
||||
end;
|
||||
|
||||
if (ReadResult = nil) then
|
||||
begin
|
||||
continue;
|
||||
end;
|
||||
|
||||
// We found our barcode
|
||||
if (attempt = 1) then
|
||||
begin
|
||||
// But it was upside down, so note that
|
||||
// ReadResult.putMetadata(ResultMetadataType.orientation, TObject(180));
|
||||
// And remember to flip the result points horizontally.
|
||||
points := ReadResult.ResultPoints;
|
||||
if (points <> nil) then
|
||||
begin
|
||||
points[0] := TResultPointHelpers.CreateResultPoint
|
||||
(width - points[0].X - 1, points[0].Y);
|
||||
points[1] := TResultPointHelpers.CreateResultPoint
|
||||
(width - points[1].X - 1, points[1].Y);
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
Exit(ReadResult);
|
||||
|
||||
end; // loop
|
||||
|
||||
end;
|
||||
|
||||
Result := nil;
|
||||
end;
|
||||
|
||||
class function TOneDReader.patternMatchVariance(counters,
|
||||
pattern: TArray<Integer>; maxIndividualVariance: Integer): Integer;
|
||||
var
|
||||
scaledPattern, variance, counter, totalVariance, X, unitBarWidth, i,
|
||||
patternLength, numCounters, total: Integer;
|
||||
begin
|
||||
Result := High(Integer);
|
||||
|
||||
numCounters := Length(counters);
|
||||
total := 0;
|
||||
patternLength := 0;
|
||||
for i := 0 to numCounters - 1 do
|
||||
begin
|
||||
total := total + counters[i];
|
||||
patternLength := patternLength + pattern[i];
|
||||
end;
|
||||
|
||||
if (total < patternLength) then
|
||||
// If we don't even have one pixel per unit of bar width, assume this is too small
|
||||
// to reliably match, so fail:
|
||||
Exit;
|
||||
|
||||
// We're going to fake floating-point math in integers. We just need to use more bits.
|
||||
// Scale up patternLength so that intermediate values below like scaledCounter will have
|
||||
// more "significant digits"
|
||||
unitBarWidth := (total shl INTEGER_MATH_SHIFT) div patternLength;
|
||||
maxIndividualVariance :=
|
||||
TMathUtils.Asr((maxIndividualVariance * unitBarWidth), INTEGER_MATH_SHIFT);
|
||||
|
||||
totalVariance := 0;
|
||||
for X := 0 to numCounters - 1 do
|
||||
begin
|
||||
counter := counters[X] shl INTEGER_MATH_SHIFT;
|
||||
scaledPattern := pattern[X] * unitBarWidth;
|
||||
|
||||
if (counter > scaledPattern) then
|
||||
variance := counter - scaledPattern
|
||||
else
|
||||
variance := scaledPattern - counter;
|
||||
|
||||
if (variance > maxIndividualVariance) then
|
||||
Exit;
|
||||
|
||||
totalVariance := totalVariance + variance;
|
||||
end;
|
||||
|
||||
Result := totalVariance div total;
|
||||
end;
|
||||
|
||||
class function TOneDReader.recordPattern(row: IBitArray; start: Integer;
|
||||
counters: TArray<Integer>): Boolean;
|
||||
begin
|
||||
Result := recordPattern(row, start, counters, Length(counters));
|
||||
end;
|
||||
|
||||
class function TOneDReader.recordPattern(row: IBitArray; start: Integer;
|
||||
counters: TArray<Integer>; numCounters: Integer): Boolean;
|
||||
|
||||
var
|
||||
i, counterPosition, idx, ending: Integer;
|
||||
isWhite: Boolean;
|
||||
|
||||
begin
|
||||
for idx := 0 to numCounters - 1 do
|
||||
begin
|
||||
counters[idx] := 0;
|
||||
end;
|
||||
|
||||
ending := row.Size;
|
||||
|
||||
if (start >= ending) then
|
||||
begin
|
||||
Result := false;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
isWhite := not row[start];
|
||||
counterPosition := 0;
|
||||
i := start;
|
||||
while (i < ending) do
|
||||
begin
|
||||
if (row[i] xor isWhite) then
|
||||
begin // that is, exactly one is true
|
||||
inc(counters[counterPosition]);
|
||||
end
|
||||
else
|
||||
begin
|
||||
inc(counterPosition);
|
||||
if (counterPosition = numCounters) then
|
||||
begin
|
||||
break;
|
||||
end
|
||||
else
|
||||
begin
|
||||
counters[counterPosition] := 1;
|
||||
isWhite := not isWhite;
|
||||
end;
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
|
||||
// If we read fully the last section of pixels and filled up our counters -- or filled
|
||||
// the last counter but ran off the side of the image, OK. Otherwise, a problem.
|
||||
Result := ((counterPosition = numCounters) or
|
||||
((counterPosition = (numCounters - 1)) and (i = ending)));
|
||||
|
||||
end;
|
||||
|
||||
class function TOneDReader.RecordPatternInReverse(row: IBitArray;
|
||||
start: Integer; counters: TArray<Integer>): Boolean;
|
||||
|
||||
var
|
||||
numTransitionsLeft: Integer;
|
||||
last: Boolean;
|
||||
|
||||
begin
|
||||
// This could be more efficient I guess
|
||||
numTransitionsLeft := Length(counters);
|
||||
last := row[start];
|
||||
while ((start > 0) and (numTransitionsLeft >= 0)) do
|
||||
begin
|
||||
dec(start);
|
||||
if (row[start] <> last) then
|
||||
begin
|
||||
dec(numTransitionsLeft);
|
||||
last := not last;
|
||||
end;
|
||||
end;
|
||||
|
||||
if (numTransitionsLeft >= 0) then
|
||||
begin
|
||||
Result := false;
|
||||
Exit;
|
||||
end;
|
||||
Result := recordPattern(row, start + 1, counters);
|
||||
end;
|
||||
|
||||
procedure TOneDReader.reset;
|
||||
begin
|
||||
// do nothing
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TOneDReader.InitializeClass();
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,140 @@
|
||||
unit ZXing.OneD.UPCAReader;
|
||||
{
|
||||
* Copyright 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
* Original Authors: Sean Owen
|
||||
* Delphi Implementation by E. Spelt
|
||||
}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
Math,
|
||||
ZXing.OneD.OneDReader,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.BinaryBitmap,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BarcodeFormat,
|
||||
ZXing.Helpers,
|
||||
ZXing.OneD.EAN13Reader,
|
||||
ZXing.OneD.UPCEANReader;
|
||||
|
||||
type
|
||||
|
||||
TUPCAReader = class(TUPCEANReader)
|
||||
private
|
||||
class var EAN13Reader: TUPCEANReader;
|
||||
class function maybeReturnResult(pResult: TReadResult): TReadResult; static;
|
||||
class procedure DoInitialize();
|
||||
class procedure DoFinalize();
|
||||
protected
|
||||
|
||||
public
|
||||
class function DecodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>; const resultString: TStringBuilder)
|
||||
: Integer; override;
|
||||
|
||||
function decode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult; override;
|
||||
|
||||
function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
overload; override;
|
||||
|
||||
function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const startGuardRange: TArray<Integer>;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
reintroduce; overload;
|
||||
|
||||
function BarcodeFormat: TBarcodeFormat; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TUPCAReader }
|
||||
|
||||
class procedure TUPCAReader.DoFinalize;
|
||||
begin
|
||||
EAN13Reader.Free;
|
||||
end;
|
||||
|
||||
class procedure TUPCAReader.DoInitialize;
|
||||
begin
|
||||
EAN13Reader := TEAN13Reader.Create();
|
||||
end;
|
||||
|
||||
function TUPCAReader.decode(const image: TBinaryBitmap;
|
||||
hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
begin
|
||||
result := TUPCAReader.maybeReturnResult(self.EAN13Reader.decode(image, hints))
|
||||
end;
|
||||
|
||||
class function TUPCAReader.DecodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>;
|
||||
const resultString: TStringBuilder): Integer;
|
||||
begin
|
||||
result := self.EAN13Reader.DecodeMiddle(row, startRange, resultString)
|
||||
end;
|
||||
|
||||
function TUPCAReader.decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
begin
|
||||
result := TUPCAReader.maybeReturnResult(self.EAN13Reader.decodeRow(rowNumber,
|
||||
row, hints))
|
||||
end;
|
||||
|
||||
function TUPCAReader.decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const startGuardRange: TArray<Integer>;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
begin
|
||||
result := TUPCAReader.maybeReturnResult
|
||||
(self.EAN13Reader.doDecodeRow(rowNumber, row, startGuardRange, hints))
|
||||
end;
|
||||
|
||||
function TUPCAReader.BarcodeFormat: TBarcodeFormat;
|
||||
begin
|
||||
result := TBarcodeFormat.UPC_A;
|
||||
end;
|
||||
|
||||
class function TUPCAReader.maybeReturnResult(pResult: TReadResult): TReadResult;
|
||||
begin
|
||||
if not Assigned(pResult) then
|
||||
Exit(nil);
|
||||
|
||||
{$ZEROBASEDSTRINGS ON}
|
||||
if (copy(pResult.text, 0, 1) = '0') then
|
||||
begin
|
||||
pResult.text := copy(pResult.text, 2, length(pResult.text));
|
||||
pResult.BarcodeFormat := TBarcodeFormat.UPC_A;
|
||||
end;
|
||||
{$ZEROBASEDSTRINGS OFF}
|
||||
result := pResult;
|
||||
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TUPCAReader.DoInitialize;
|
||||
|
||||
finalization
|
||||
|
||||
TUPCAReader.DoFinalize;
|
||||
|
||||
|
||||
end.
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
{
|
||||
* 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.
|
||||
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.OneD.UPCEANExtension2Support;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
ZXing.Reader,
|
||||
ZXing.BarcodeFormat,
|
||||
ZXing.BinaryBitmap,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultMetadataType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.Common.BitArray;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// @see UPCEANExtension5Support
|
||||
/// </summary>
|
||||
TUPCEANExtension2Support = class sealed
|
||||
private
|
||||
class var
|
||||
decodeMiddleCounters : TArray<Integer>;
|
||||
decodeRowStringBuffer : TStringBuilder;
|
||||
|
||||
class procedure InitializeClass; static;
|
||||
class procedure FinalizeClass; static;
|
||||
|
||||
class function decodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>;
|
||||
const resultString: TStringBuilder): Integer;
|
||||
|
||||
/// <summary>
|
||||
/// Parses the extension string.
|
||||
/// </summary>
|
||||
/// <param name="raw">raw content of extension</param>
|
||||
/// <returns>formatted interpretation of raw content as a {@link TMap} mapping
|
||||
class function parseExtensionString(const raw: String): TResultMetadata; static;
|
||||
public
|
||||
class function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const extensionStartRange: TArray<Integer>): TReadResult;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
ZXing.OneD.UPCEANReader;
|
||||
|
||||
{ TUPCEANExtension2Support }
|
||||
|
||||
class procedure TUPCEANExtension2Support.InitializeClass;
|
||||
begin
|
||||
decodeMiddleCounters := TArray<Integer>.Create(0, 0, 0, 0);
|
||||
decodeRowStringBuffer := TStringBuilder.Create;
|
||||
end;
|
||||
|
||||
class procedure TUPCEANExtension2Support.FinalizeClass;
|
||||
begin
|
||||
decodeMiddleCounters := nil;
|
||||
decodeRowStringBuffer.Free;
|
||||
end;
|
||||
|
||||
class function TUPCEANExtension2Support.decodeRow(const rowNumber: Integer;
|
||||
const row: IBitArray; const extensionStartRange: TArray<Integer>): TReadResult;
|
||||
var
|
||||
res : TStringBuilder;
|
||||
ending : Integer;
|
||||
resultString : String;
|
||||
extensionResult : TReadResult;
|
||||
extensionData : TResultMetadata;
|
||||
resultPoints : TArray<IResultPoint>;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
res := decodeRowStringBuffer;
|
||||
res.Length := 0;
|
||||
ending := decodeMiddle(row, extensionStartRange, res);
|
||||
if (ending < 0)
|
||||
then
|
||||
exit;
|
||||
|
||||
resultString := res.ToString;
|
||||
extensionData := TUPCEANExtension2Support.parseExtensionString(resultString);
|
||||
|
||||
resultPoints := TArray<IResultPoint>.Create(
|
||||
TResultPointHelpers.CreateResultPoint((extensionStartRange[0] + extensionStartRange[1]) div 2, rowNumber),
|
||||
TResultPointHelpers.CreateResultPoint(ending, rowNumber));
|
||||
|
||||
extensionResult := TReadResult.Create(resultString, nil, resultPoints, TBarcodeFormat.UPC_EAN_EXTENSION);
|
||||
if (extensionData <> nil)
|
||||
then
|
||||
begin
|
||||
extensionResult.putAllMetadata(extensionData);
|
||||
FreeAndNil(extensionData);
|
||||
end;
|
||||
Result := extensionResult;
|
||||
end;
|
||||
|
||||
class function TUPCEANExtension2Support.decodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>; const resultString: TStringBuilder): Integer;
|
||||
var
|
||||
bestMatch: Integer;
|
||||
counter: Integer;
|
||||
ending: Integer;
|
||||
counters : TArray<Integer>;
|
||||
rowOffset, x,
|
||||
checkParity: Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
|
||||
counters := decodeMiddleCounters;
|
||||
counters[0] := 0;
|
||||
counters[1] := 0;
|
||||
counters[2] := 0;
|
||||
counters[3] := 0;
|
||||
ending := row.Size;
|
||||
rowOffset := startRange[1];
|
||||
checkParity := 0;
|
||||
x := 0;
|
||||
while ((x < 2) and (rowOffset < ending)) do
|
||||
begin
|
||||
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset, TUPCEANReader.L_AND_G_PATTERNS, bestMatch))
|
||||
then
|
||||
exit;
|
||||
|
||||
resultString.Append(IntToStr(bestMatch mod 10));
|
||||
|
||||
for counter in counters do
|
||||
Inc(rowOffset, counter);
|
||||
|
||||
if (bestMatch >= 10)
|
||||
then
|
||||
checkParity := checkParity or (1 shl (1 - x));
|
||||
|
||||
if (x <> 1) then
|
||||
begin
|
||||
// Read off separator if not last
|
||||
rowOffset := row.getNextSet(rowOffset);
|
||||
rowOffset := row.getNextUnset(rowOffset);
|
||||
end;
|
||||
Inc(x);
|
||||
end;
|
||||
|
||||
if (resultString.Length <> 2)
|
||||
then
|
||||
exit;
|
||||
|
||||
if ((Integer.Parse(resultString.ToString) mod 4) <> checkParity)
|
||||
then
|
||||
exit;
|
||||
|
||||
Result := rowOffset;
|
||||
end;
|
||||
|
||||
class function TUPCEANExtension2Support.parseExtensionString(const raw: String): TResultMetadata;
|
||||
var
|
||||
dictionary1: TResultMetadata;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
if (Length(raw) <> 2)
|
||||
then
|
||||
exit;
|
||||
|
||||
dictionary1 := TResultMetadata.Create;
|
||||
dictionary1.Add(ZXing.ResultMetadataType.ISSUE_NUMBER, TResultMetaData.CreateStringMetadata(raw));
|
||||
Result := dictionary1;
|
||||
end;
|
||||
|
||||
initialization
|
||||
TUPCEANExtension2Support.InitializeClass;
|
||||
finalization
|
||||
TUPCEANExtension2Support.FinalizeClass;
|
||||
end.
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
{
|
||||
* 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.
|
||||
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.OneD.UPCEANExtension5Support;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
ZXing.Reader,
|
||||
ZXing.BinaryBitmap,
|
||||
ZXing.ReadResult,
|
||||
ZXing.BarcodeFormat,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultMetadataType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
(**
|
||||
* @see TUPCEANExtension2Support
|
||||
*)
|
||||
TUPCEANExtension5Support = class sealed
|
||||
private
|
||||
class var
|
||||
CHECK_DIGIT_ENCODINGS : TArray<Integer>;
|
||||
decodeMiddleCounters : TArray<Integer>;
|
||||
decodeRowStringBuffer : TStringBuilder;
|
||||
|
||||
class procedure InitializeClass; static;
|
||||
class procedure FinalizeClass; static;
|
||||
|
||||
class function decodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>;
|
||||
const resultString: TStringBuilder): Integer;
|
||||
|
||||
class function extensionChecksum(const s: string): Integer; static;
|
||||
|
||||
class function determineCheckDigit(const lgPatternFound: Integer;
|
||||
var checkDigit: Integer): Boolean; static;
|
||||
|
||||
/// <summary>
|
||||
/// Parses the extension string.
|
||||
/// </summary>
|
||||
/// <param name="raw">raw content of extension</param>
|
||||
/// <returns>formatted interpretation of raw content as a {@link TMap} mapping
|
||||
/// one {@link TResultMetadataType} to appropriate value, or {@code nil} if not known</returns>
|
||||
class function parseExtensionString(
|
||||
const raw: string): TResultMetadata; static;
|
||||
|
||||
class function parseExtension5String(const raw: String): String; static;
|
||||
public
|
||||
class function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const extensionStartRange: TArray<Integer>): TReadResult;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
ZXing.OneD.UPCEANReader;
|
||||
|
||||
{ TUPCEANExtension2Support }
|
||||
|
||||
class procedure TUPCEANExtension5Support.InitializeClass;
|
||||
begin
|
||||
CHECK_DIGIT_ENCODINGS := TArray<Integer>.Create($18, $14, $12, $11, $0C,
|
||||
$06, $03, $0A, $09, $05);
|
||||
decodeMiddleCounters := TArray<Integer>.Create(0, 0, 0, 0);
|
||||
decodeRowStringBuffer := TStringBuilder.Create;
|
||||
end;
|
||||
|
||||
class procedure TUPCEANExtension5Support.FinalizeClass;
|
||||
begin
|
||||
CHECK_DIGIT_ENCODINGS := nil;
|
||||
decodeMiddleCounters := nil;
|
||||
decodeRowStringBuffer.Free;
|
||||
end;
|
||||
|
||||
class function TUPCEANExtension5Support.decodeRow(const rowNumber: Integer;
|
||||
const row: IBitArray; const extensionStartRange: TArray<Integer>): TReadResult;
|
||||
var
|
||||
res : TStringBuilder;
|
||||
ending : Integer;
|
||||
resultString : String;
|
||||
extensionData : TResultMetadata;
|
||||
resultPoints : TArray<IResultPoint>;
|
||||
extensionResult : TReadResult;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
res := decodeRowStringBuffer;
|
||||
res.Length := 0;
|
||||
ending := decodeMiddle(row, extensionStartRange, res);
|
||||
if (ending < 0)
|
||||
then
|
||||
exit;
|
||||
|
||||
resultString := res.ToString;
|
||||
extensionData := parseExtensionString(resultString);
|
||||
resultPoints := TArray<IResultPoint>.Create(
|
||||
TResultPointHelpers.CreateResultPoint((extensionStartRange[0] + extensionStartRange[1]) div 2, rowNumber),
|
||||
TResultPointHelpers.CreateResultPoint(ending, rowNumber));
|
||||
|
||||
extensionResult := TReadResult.Create(resultString, nil, resultPoints, TBarcodeFormat.UPC_EAN_EXTENSION);
|
||||
if (extensionData <> nil)
|
||||
then
|
||||
begin
|
||||
extensionResult.putAllMetadata(extensionData);
|
||||
FreeAndNil(extensionData);
|
||||
end;
|
||||
Result := extensionResult;
|
||||
end;
|
||||
|
||||
class function TUPCEANExtension5Support.decodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>; const resultString: TStringBuilder): Integer;
|
||||
var
|
||||
bestMatch: Integer;
|
||||
counter: Integer;
|
||||
ending: Integer;
|
||||
counters : TArray<Integer>;
|
||||
rowOffset, x,
|
||||
lgPatternFound,
|
||||
checkDigit : Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
|
||||
counters := decodeMiddleCounters;
|
||||
counters[0] := 0;
|
||||
counters[1] := 0;
|
||||
counters[2] := 0;
|
||||
counters[3] := 0;
|
||||
ending := row.Size;
|
||||
rowOffset := startRange[1];
|
||||
|
||||
lgPatternFound := 0;
|
||||
|
||||
x := 0;
|
||||
while (((x < 5) and (rowOffset < ending))) do
|
||||
begin
|
||||
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset, TUPCEANReader.L_AND_G_PATTERNS, bestMatch))
|
||||
then
|
||||
exit;
|
||||
|
||||
resultString.Append(IntToStr(bestMatch mod 10));
|
||||
|
||||
for counter in counters do
|
||||
Inc(rowOffset, counter);
|
||||
|
||||
if (bestMatch >= 10)
|
||||
then
|
||||
lgPatternFound := lgPatternFound or (1 shl (4 - x));
|
||||
|
||||
if (x <> 4) then
|
||||
begin
|
||||
// Read off separator if not last
|
||||
rowOffset := row.getNextSet(rowOffset);
|
||||
rowOffset := row.getNextUnset(rowOffset)
|
||||
end;
|
||||
Inc(x);
|
||||
end;
|
||||
|
||||
if (resultString.Length <> 5)
|
||||
then
|
||||
exit;
|
||||
|
||||
if (not determineCheckDigit(lgPatternFound, checkDigit))
|
||||
then
|
||||
exit;
|
||||
|
||||
if (extensionChecksum(resultString.ToString) <> checkDigit)
|
||||
then
|
||||
exit;
|
||||
|
||||
Result := rowOffset;
|
||||
end;
|
||||
|
||||
class function TUPCEANExtension5Support.extensionChecksum(
|
||||
const s: String): Integer;
|
||||
var
|
||||
len,
|
||||
sum, i : Integer;
|
||||
begin
|
||||
len := Length(s);
|
||||
sum := 0;
|
||||
i := (len - 2);
|
||||
while ((i >= 0)) do
|
||||
begin
|
||||
Inc(sum, StrToIntDef(s.Chars[i], 0));
|
||||
Dec(i, 2);
|
||||
end;
|
||||
sum := (sum * 3);
|
||||
i := (len - 1);
|
||||
while ((i >= 0)) do
|
||||
begin
|
||||
Inc(sum, StrToIntDef(s.Chars[i], 0));
|
||||
Dec(i, 2);
|
||||
end;
|
||||
sum := (sum * 3);
|
||||
|
||||
Result := (sum mod 10);
|
||||
end;
|
||||
|
||||
class function TUPCEANExtension5Support.determineCheckDigit(
|
||||
const lgPatternFound: Integer; var checkDigit: Integer): Boolean;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
Result := false;
|
||||
|
||||
for i := 0 to Pred(10) do
|
||||
begin
|
||||
if (lgPatternFound = CHECK_DIGIT_ENCODINGS[i]) then
|
||||
begin
|
||||
Result := true;
|
||||
checkDigit := i;
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TUPCEANExtension5Support.parseExtensionString(
|
||||
const raw: String): TResultMetadata;
|
||||
var
|
||||
value: String;
|
||||
dictionary1: TResultMetadata;
|
||||
begin
|
||||
Result := nil;
|
||||
if (Length(raw) <> 5)
|
||||
then
|
||||
exit;
|
||||
|
||||
value := TUPCEANExtension5Support.parseExtension5String(raw);
|
||||
//
|
||||
// if (Length(value) = 0)
|
||||
// then
|
||||
// Result := nil;
|
||||
|
||||
dictionary1 := TResultMetadata.Create();
|
||||
dictionary1.Add(ZXing.ResultMetadataType.SUGGESTED_PRICE, TResultMetadata.CreateStringMetadata(value));
|
||||
|
||||
Result := dictionary1;
|
||||
end;
|
||||
|
||||
class function TUPCEANExtension5Support.parseExtension5String(
|
||||
const raw: String): String;
|
||||
var
|
||||
currency,
|
||||
unitsString,
|
||||
hundredthsString : string;
|
||||
rawAmount,
|
||||
hundredths : Integer;
|
||||
begin
|
||||
Result := '';
|
||||
|
||||
case raw[1] of
|
||||
'0' : currency := '£';
|
||||
'5' : currency := '$';
|
||||
'9' : begin
|
||||
// Reference: http://www.jollytech.com
|
||||
if ('90000'.Equals(raw))
|
||||
then
|
||||
// No suggested retail price
|
||||
exit;
|
||||
if ('99991'.Equals(raw))
|
||||
then
|
||||
// Complementary
|
||||
Result := '0.00';
|
||||
if ('99990'.Equals(raw))
|
||||
then
|
||||
Result := 'Used';
|
||||
// Otherwise... unknown currency?
|
||||
currency := '';
|
||||
end;
|
||||
else currency := '';
|
||||
end;
|
||||
|
||||
rawAmount := Integer.Parse(raw.Substring(1));
|
||||
unitsString := ((rawAmount div 100)).ToString;
|
||||
hundredths := (rawAmount mod 100);
|
||||
if (hundredths < 10)
|
||||
then
|
||||
hundredthsString := '0' + hundredths.ToString
|
||||
else
|
||||
hundredthsString := hundredths.ToString;
|
||||
|
||||
Result := currency + unitsString + '.' + hundredthsString;
|
||||
end;
|
||||
|
||||
initialization
|
||||
TUPCEANExtension5Support.InitializeClass;
|
||||
finalization
|
||||
TUPCEANExtension5Support.FinalizeClass;
|
||||
end.
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
{
|
||||
* 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.
|
||||
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.OneD.UPCEANExtensionSupport;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
ZXing.OneD.UPCEANExtension2Support,
|
||||
ZXing.OneD.UPCEANExtension5Support,
|
||||
ZXing.Reader,
|
||||
ZXing.BinaryBitmap,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultMetadataType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.Common.Detector.MathUtils;
|
||||
|
||||
type
|
||||
TUPCEANExtensionSupport = class sealed
|
||||
private
|
||||
class var
|
||||
EXTENSION_START_PATTERN : TArray<Integer>;
|
||||
|
||||
twoSupport : TUPCEANExtension2Support;
|
||||
fiveSupport : TUPCEANExtension5Support;
|
||||
|
||||
class procedure InitializeClass; static;
|
||||
class procedure FinalizeClass; static;
|
||||
public
|
||||
function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const rowOffset: Integer): TReadResult;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
ZXing.OneD.UPCEANReader;
|
||||
|
||||
{ TUPCEANExtensionSupport }
|
||||
|
||||
class procedure TUPCEANExtensionSupport.InitializeClass();
|
||||
begin
|
||||
EXTENSION_START_PATTERN := TArray<Integer>.Create(1, 1, 2);
|
||||
twoSupport := TUPCEANExtension2Support.Create();
|
||||
fiveSupport := TUPCEANExtension5Support.Create();
|
||||
end;
|
||||
|
||||
class procedure TUPCEANExtensionSupport.FinalizeClass();
|
||||
begin
|
||||
EXTENSION_START_PATTERN := nil;
|
||||
twoSupport.Free;
|
||||
fiveSupport.Free;
|
||||
end;
|
||||
|
||||
function TUPCEANExtensionSupport.decodeRow(const rowNumber: Integer;
|
||||
const row: IBitArray; const rowOffset: Integer): TReadResult;
|
||||
var
|
||||
extensionStartRange: TArray<Integer>;
|
||||
res : TReadResult;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
extensionStartRange := TUPCEANReader.findGuardPattern(row, rowOffset,
|
||||
false, EXTENSION_START_PATTERN);
|
||||
if (extensionStartRange = nil)
|
||||
then
|
||||
exit;
|
||||
res := fiveSupport.decodeRow(rowNumber, row, extensionStartRange);
|
||||
if (res = nil)
|
||||
then
|
||||
res := twoSupport.decodeRow(rowNumber, row, extensionStartRange);
|
||||
Result := res;
|
||||
end;
|
||||
|
||||
initialization
|
||||
TUPCEANExtensionSupport.InitializeClass;
|
||||
finalization
|
||||
TUPCEANExtensionSupport.FinalizeClass;
|
||||
end.
|
||||
@@ -0,0 +1,592 @@
|
||||
{
|
||||
* Copyright 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
* Original Authors: dswitkin@google.com (Daniel Switkin), Sean Owen and
|
||||
* alasdair@google.com (Alasdair Mackintosh)
|
||||
* Delphi Implementation by K. Gossens
|
||||
}
|
||||
|
||||
unit ZXing.OneD.UPCEANReader;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
System.Math,
|
||||
ZXing.OneD.OneDReader,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.OneD.UPCEANExtensionSupport,
|
||||
ZXing.OneD.EANManufacturerOrgSupport,
|
||||
ZXing.ResultMetadataType,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BarcodeFormat;
|
||||
|
||||
type
|
||||
/// <summary>
|
||||
/// <p>Encapsulates functionality and implementation that is common to UPC and EAN families
|
||||
/// of one-dimensional barcodes.</p>
|
||||
/// </summary>
|
||||
TUPCEANReader = class abstract(TOneDReader)
|
||||
private
|
||||
decodeRowStringBuffer: TStringBuilder;
|
||||
extensionReader: TUPCEANExtensionSupport;
|
||||
eanManSupport: TEANManufacturerOrgSupport;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="row">row of black/white values to search</param>
|
||||
/// <param name="rowOffset">position to start search</param>
|
||||
/// <param name="whiteFirst">if true, indicates that the pattern specifies white/black/white/...</param>
|
||||
/// pixel counts, otherwise, it is interpreted as black/white/black/...
|
||||
/// <param name="pattern">pattern of counts of number of black and white pixels that are being</param>
|
||||
/// searched for as a pattern
|
||||
/// <param name="counters">array of counters, as long as pattern, to re-use</param>
|
||||
/// <returns>start/end horizontal offset of guard pattern, as an array of two ints</returns>
|
||||
class function findGuardPattern(const row: IBitArray; rowOffset: Integer;
|
||||
const whiteFirst: Boolean; const pattern: TArray<Integer>;
|
||||
counters: TArray<Integer>): TArray<Integer>; overload;
|
||||
|
||||
/// <summary>
|
||||
/// Computes the UPC/EAN checksum on a string of digits, and reports
|
||||
/// whether the checksum is correct or not.
|
||||
/// </summary>
|
||||
/// <param name="s">string of digits to check</param>
|
||||
/// <returns>true iff string of digits passes the UPC/EAN checksum algorithm</returns>
|
||||
class function checkStandardUPCEANChecksum(const s: String)
|
||||
: Boolean; static;
|
||||
|
||||
class procedure InitializeClass; static;
|
||||
class procedure FinalizeClass; static;
|
||||
protected
|
||||
class var
|
||||
// These two values are critical for determining how permissive the decoding will be.
|
||||
// We've arrived at these values through a lot of trial and error. Setting them any higher
|
||||
// lets false positives creep in quickly.
|
||||
MAX_AVG_VARIANCE: Integer;
|
||||
MAX_INDIVIDUAL_VARIANCE: Integer;
|
||||
|
||||
/// <summary>
|
||||
/// Start/end guard pattern.
|
||||
/// </summary>
|
||||
START_END_PATTERN: TArray<Integer>;
|
||||
|
||||
/// <summary>
|
||||
/// Pattern marking the middle of a UPC/EAN pattern, separating the two halves.
|
||||
/// </summary>
|
||||
MIDDLE_PATTERN: TArray<Integer>;
|
||||
|
||||
/// <summary>
|
||||
/// "Odd", or "L" patterns used to encode UPC/EAN digits.
|
||||
/// </summary>
|
||||
L_PATTERNS: TArray<TArray<Integer>>;
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the end.
|
||||
/// </summary>
|
||||
/// <param name="row">The row.</param>
|
||||
/// <param name="endStart">The end start.</param>
|
||||
/// <returns></returns>
|
||||
class function decodeEnd(const row: IBitArray; const endStart: Integer)
|
||||
: TArray<Integer>; virtual;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="s">string of digits to check</param>
|
||||
/// <returns>see <see cref="checkStandardUPCEANChecksum(String)"/></returns>
|
||||
class function checkChecksum(const s: String): Boolean; virtual;
|
||||
public
|
||||
class var
|
||||
/// <summary>
|
||||
/// As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.
|
||||
/// </summary>
|
||||
L_AND_G_PATTERNS: TArray<TArray<Integer>>;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TUPCEANReader"/> class.
|
||||
/// </summary>
|
||||
constructor Create; virtual;
|
||||
destructor Destroy; override;
|
||||
|
||||
/// <summary>
|
||||
/// Subclasses override this to decode the portion of a barcode between the start
|
||||
/// and end guard patterns.
|
||||
/// </summary>
|
||||
/// <param name="row">row of black/white values to search</param>
|
||||
/// <param name="startRange">start/end offset of start guard pattern</param>
|
||||
/// <param name="resultString"><see cref="StringBuilder" />to append decoded chars to</param>
|
||||
/// <returns>horizontal offset of first pixel after the "middle" that was decoded or -1 if decoding could not complete successfully</returns>
|
||||
class function DecodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>; const resultString: TStringBuilder)
|
||||
: Integer; virtual; abstract;
|
||||
|
||||
/// <summary>
|
||||
/// Get the format of this decoder.
|
||||
/// </summary>
|
||||
/// <returns>The 1D format.</returns>
|
||||
function BarcodeFormat: TBarcodeFormat; virtual; abstract;
|
||||
|
||||
class function findStartGuardPattern(const row: IBitArray)
|
||||
: TArray<Integer>; static;
|
||||
|
||||
class function findGuardPattern(const row: IBitArray;
|
||||
const rowOffset: Integer; const whiteFirst: Boolean;
|
||||
const pattern: TArray<Integer>): TArray<Integer>; overload;
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to decode a single UPC/EAN-encoded digit.
|
||||
/// </summary>
|
||||
/// <param name="row">row of black/white values to decode</param>
|
||||
/// <param name="counters">the counts of runs of observed black/white/black/... values</param>
|
||||
/// <param name="rowOffset">horizontal offset to start decoding from</param>
|
||||
/// <param name="patterns">the set of patterns to use to decode -- sometimes different encodings</param>
|
||||
/// for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
|
||||
/// be used
|
||||
/// <returns>horizontal offset of first pixel beyond the decoded digit</returns>
|
||||
class function decodeDigit(const row: IBitArray;
|
||||
const counters: TArray<Integer>; const rowOffset: Integer;
|
||||
const patterns: TArray<TArray<Integer>>; var digit: Integer): Boolean;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Attempts to decode a one-dimensional barcode format given a single row of
|
||||
/// an image.</p>
|
||||
/// </summary>
|
||||
/// <param name="rowNumber">row number from top of the row</param>
|
||||
/// <param name="row">the black/white pixel data of the row</param>
|
||||
/// <param name="hints">decode hints</param>
|
||||
/// <returns>
|
||||
/// <see cref="TReadResult"/>containing encoded string and start/end of barcode or null, if an error occurs or barcode cannot be found
|
||||
/// </returns>
|
||||
function decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
overload; override;
|
||||
|
||||
/// <summary>
|
||||
/// <p>Like decodeRow(int, BitArray, java.util.Map), but
|
||||
/// allows caller to inform method about where the UPC/EAN start pattern is
|
||||
/// found. This allows this to be computed once and reused across many implementations.</p>
|
||||
/// </summary>
|
||||
/// <param name="rowNumber">row index into the image</param>
|
||||
/// <param name="row">encoding of the row of the barcode image</param>
|
||||
/// <param name="startGuardRange">start/end column where the opening start pattern was found</param>
|
||||
/// <param name="hints">optional hints that influence decoding</param>
|
||||
/// <returns><see cref="TReadResult"/> encapsulating the result of decoding a barcode in the row</returns>
|
||||
function DoDecodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const startGuardRange: TArray<Integer>;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TUPCEANReader }
|
||||
|
||||
constructor TUPCEANReader.Create;
|
||||
begin
|
||||
inherited;
|
||||
|
||||
decodeRowStringBuffer := TStringBuilder.Create(20);
|
||||
extensionReader := TUPCEANExtensionSupport.Create;
|
||||
eanManSupport := TEANManufacturerOrgSupport.Create;
|
||||
end;
|
||||
|
||||
destructor TUPCEANReader.Destroy;
|
||||
begin
|
||||
eanManSupport.Free;
|
||||
extensionReader.Free;
|
||||
decodeRowStringBuffer.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
class procedure TUPCEANReader.InitializeClass;
|
||||
var
|
||||
i, j: Integer;
|
||||
widths, reversedWidths: TArray<Integer>;
|
||||
begin
|
||||
MAX_AVG_VARIANCE :=
|
||||
Trunc(TOneDReader.PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.48);
|
||||
MAX_INDIVIDUAL_VARIANCE :=
|
||||
Trunc(TOneDReader.PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7);
|
||||
|
||||
START_END_PATTERN := TArray<Integer>.Create(1, 1, 1);
|
||||
MIDDLE_PATTERN := TArray<Integer>.Create(1, 1, 1, 1, 1);
|
||||
|
||||
L_PATTERNS := TArray < TArray < Integer >>
|
||||
.Create(TArray<Integer>.Create(3, 2, 1, 1), // 0
|
||||
TArray<Integer>.Create(2, 2, 2, 1), // 1
|
||||
TArray<Integer>.Create(2, 1, 2, 2), // 2
|
||||
TArray<Integer>.Create(1, 4, 1, 1), // 3
|
||||
TArray<Integer>.Create(1, 1, 3, 2), // 4
|
||||
TArray<Integer>.Create(1, 2, 3, 1), // 5
|
||||
TArray<Integer>.Create(1, 1, 1, 4), // 6
|
||||
TArray<Integer>.Create(1, 3, 1, 2), // 7
|
||||
TArray<Integer>.Create(1, 2, 1, 3), // 8
|
||||
TArray<Integer>.Create(3, 1, 1, 2) // 9
|
||||
);
|
||||
|
||||
L_AND_G_PATTERNS := L_PATTERNS;
|
||||
SetLength(L_AND_G_PATTERNS, 20);
|
||||
|
||||
// Move(L_PATTERNS[0], L_AND_G_PATTERNS[0], 10);
|
||||
for i := 10 to Pred(20) do
|
||||
begin
|
||||
widths := L_PATTERNS[(i - 10)];
|
||||
reversedWidths := TArray<Integer>.Create();
|
||||
SetLength(reversedWidths, Length(widths));
|
||||
for j := 0 to Pred(Length(widths)) do
|
||||
reversedWidths[j] := widths[((Length(widths) - j) - 1)];
|
||||
L_AND_G_PATTERNS[i] := reversedWidths;
|
||||
end;
|
||||
end;
|
||||
|
||||
class procedure TUPCEANReader.FinalizeClass;
|
||||
begin
|
||||
START_END_PATTERN := nil;
|
||||
MIDDLE_PATTERN := nil;
|
||||
L_PATTERNS := nil;
|
||||
L_AND_G_PATTERNS := nil;
|
||||
|
||||
end;
|
||||
|
||||
class function TUPCEANReader.checkChecksum(const s: String): Boolean;
|
||||
begin
|
||||
Result := checkStandardUPCEANChecksum(s);
|
||||
end;
|
||||
|
||||
class function TUPCEANReader.checkStandardUPCEANChecksum
|
||||
(const s: String): Boolean;
|
||||
var
|
||||
len, sum, i, digit, ZeroInt: Integer;
|
||||
begin
|
||||
{$ZEROBASEDSTRINGS ON}
|
||||
Result := false;
|
||||
|
||||
len := Length(s);
|
||||
if (len = 0) then
|
||||
exit;
|
||||
|
||||
sum := 0;
|
||||
ZeroInt := Ord('0');
|
||||
i := (len - 2);
|
||||
while ((i >= 0)) do
|
||||
begin
|
||||
digit := Ord(s[i]) - ZeroInt;
|
||||
if ((digit < 0) or (digit > 9)) then
|
||||
exit;
|
||||
Inc(sum, digit);
|
||||
Dec(i, 2);
|
||||
end;
|
||||
sum := (sum * 3);
|
||||
i := (len - 1);
|
||||
while ((i >= 0)) do
|
||||
begin
|
||||
digit := Ord(s[i]) - ZeroInt;
|
||||
if ((digit < 0) or (digit > 9)) then
|
||||
exit;
|
||||
Inc(sum, digit);
|
||||
Dec(i, 2);
|
||||
end;
|
||||
|
||||
{$ZEROBASEDSTRINGS OFF}
|
||||
Result := ((sum mod 10) = 0);
|
||||
end;
|
||||
|
||||
class function TUPCEANReader.decodeEnd(const row: IBitArray;
|
||||
const endStart: Integer): TArray<Integer>;
|
||||
begin
|
||||
Result := findGuardPattern(row, endStart, false, START_END_PATTERN);
|
||||
end;
|
||||
|
||||
class function TUPCEANReader.findStartGuardPattern(const row: IBitArray)
|
||||
: TArray<Integer>;
|
||||
var
|
||||
foundStart: Boolean;
|
||||
startRange, counters: TArray<Integer>;
|
||||
nextStart, start, quietStart, idx, l: Integer;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
foundStart := false;
|
||||
startRange := nil;
|
||||
nextStart := 0;
|
||||
counters := TArray<Integer>.Create();
|
||||
l := Length(START_END_PATTERN);
|
||||
SetLength(counters, l);
|
||||
while (not foundStart) do
|
||||
begin
|
||||
for idx := 0 to l - 1 do
|
||||
counters[idx] := 0;
|
||||
startRange := findGuardPattern(row, nextStart, false, START_END_PATTERN,
|
||||
counters);
|
||||
if (startRange = nil) then
|
||||
exit;
|
||||
start := startRange[0];
|
||||
nextStart := startRange[1];
|
||||
// Make sure there is a quiet zone at least as big as the start pattern before the barcode.
|
||||
// If this check would run off the left edge of the image, do not accept this barcode,
|
||||
// as it is very likely to be a false positive.
|
||||
quietStart := start - (nextStart - start);
|
||||
if (quietStart >= 0) then
|
||||
foundStart := row.isRange(quietStart, start, false);
|
||||
end;
|
||||
|
||||
counters:=nil;
|
||||
Result := startRange;
|
||||
end;
|
||||
|
||||
class function TUPCEANReader.findGuardPattern(const row: IBitArray;
|
||||
const rowOffset: Integer; const whiteFirst: Boolean;
|
||||
const pattern: TArray<Integer>): TArray<Integer>;
|
||||
var
|
||||
counters: TArray<Integer>;
|
||||
begin
|
||||
counters := TArray<Integer>.Create();
|
||||
SetLength(counters, Length(pattern));
|
||||
Result := findGuardPattern(row, rowOffset, whiteFirst, pattern, counters);
|
||||
counters:=nil;
|
||||
end;
|
||||
|
||||
class function TUPCEANReader.findGuardPattern(const row: IBitArray;
|
||||
rowOffset: Integer; const whiteFirst: Boolean; const pattern: TArray<Integer>;
|
||||
counters: TArray<Integer>): TArray<Integer>;
|
||||
var
|
||||
patternLength, width, x: Integer;
|
||||
isWhite: Boolean;
|
||||
counterPosition, patternStart: Integer;
|
||||
curCounter: TArray<Integer>;
|
||||
begin
|
||||
Result := nil;
|
||||
|
||||
patternLength := Length(pattern);
|
||||
width := row.Size;
|
||||
isWhite := whiteFirst;
|
||||
if whiteFirst then
|
||||
rowOffset := row.getNextUnset(rowOffset)
|
||||
else
|
||||
rowOffset := row.getNextSet(rowOffset);
|
||||
|
||||
counterPosition := 0;
|
||||
patternStart := rowOffset;
|
||||
for x := rowOffset to Pred(width) do
|
||||
begin
|
||||
if (row[x] xor isWhite) then
|
||||
Inc(counters[counterPosition])
|
||||
else
|
||||
begin
|
||||
if (counterPosition = (patternLength - 1)) then
|
||||
begin
|
||||
if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) <
|
||||
MAX_AVG_VARIANCE) then
|
||||
begin
|
||||
Result := TArray<Integer>.Create(patternStart, x);
|
||||
break;
|
||||
end;
|
||||
Inc(patternStart, (counters[0] + counters[1]));
|
||||
curCounter := TArray<Integer>.Create();
|
||||
SetLength(curCounter, Length(counters));
|
||||
TArray.Copy<Integer>(counters, curCounter, 2, 0, (patternLength - 2));
|
||||
{ curCounter[patternLength - 2] := 0;
|
||||
curCounter[patternLength - 1] := 0; }
|
||||
counters := curCounter;
|
||||
Dec(counterPosition);
|
||||
end
|
||||
else
|
||||
Inc(counterPosition);
|
||||
counters[counterPosition] := 1;
|
||||
isWhite := not isWhite;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
class function TUPCEANReader.decodeDigit(const row: IBitArray;
|
||||
const counters: TArray<Integer>; const rowOffset: Integer;
|
||||
const patterns: TArray<TArray<Integer>>; var digit: Integer): Boolean;
|
||||
var
|
||||
bestVariance, variance, i, max: Integer;
|
||||
pattern: TArray<Integer>;
|
||||
begin
|
||||
Result := false;
|
||||
|
||||
digit := -1;
|
||||
if (not TOneDReader.recordPattern(row, rowOffset, counters)) then
|
||||
exit;
|
||||
|
||||
bestVariance := TUPCEANReader.MAX_AVG_VARIANCE; // worst variance we'll accept
|
||||
max := Length(patterns);
|
||||
for i := 0 to Pred(max) do
|
||||
begin
|
||||
pattern := patterns[i];
|
||||
variance := TOneDReader.patternMatchVariance(counters, pattern,
|
||||
TUPCEANReader.MAX_INDIVIDUAL_VARIANCE);
|
||||
if (variance < bestVariance) then
|
||||
begin
|
||||
bestVariance := variance;
|
||||
digit := i;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := (digit >= 0);
|
||||
end;
|
||||
|
||||
function TUPCEANReader.decodeRow(const rowNumber: Integer; const row: IBitArray;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
var
|
||||
startRange: TArray<Integer>;
|
||||
begin
|
||||
startRange := findStartGuardPattern(row);
|
||||
if startRange = nil then
|
||||
exit(nil);
|
||||
|
||||
try
|
||||
Result := DoDecodeRow(rowNumber, row, startRange, hints);
|
||||
except
|
||||
result := nil;
|
||||
end;
|
||||
|
||||
end;
|
||||
|
||||
function TUPCEANReader.DoDecodeRow(const rowNumber: Integer;
|
||||
const row: IBitArray;
|
||||
|
||||
const startGuardRange: TArray<Integer>;
|
||||
const hints: TDictionary<TDecodeHintType, TObject>): TReadResult;
|
||||
var
|
||||
len: Integer;
|
||||
res: TStringBuilder;
|
||||
resultString: String;
|
||||
endStart, ending, quietEnd: Integer;
|
||||
endRange: TArray<Integer>;
|
||||
resultPoints: TArray<IResultPoint>;
|
||||
left, right: Single;
|
||||
resultPointCallback: TResultPointCallback;
|
||||
obj: TObject;
|
||||
resPoint: IResultPoint;
|
||||
decodeResult, extensionResult: TReadResult;
|
||||
format: TBarcodeFormat;
|
||||
allowedExtensions: TArray<Integer>;
|
||||
valid: Boolean;
|
||||
extensionLength: Integer;
|
||||
countryID: String;
|
||||
begin
|
||||
Result := nil;
|
||||
resultPointCallback := nil;
|
||||
|
||||
if ((hints <> nil) and
|
||||
(hints.ContainsKey(TDecodeHintType.NEED_RESULT_POINT_CALLBACK))) then
|
||||
begin
|
||||
obj := hints[TDecodeHintType.NEED_RESULT_POINT_CALLBACK];
|
||||
if (obj is TResultPointEventObject) then
|
||||
resultPointCallback := TResultPointEventObject(obj).Event;
|
||||
end;
|
||||
|
||||
res := decodeRowStringBuffer;
|
||||
res.Length := 0;
|
||||
endStart := DecodeMiddle(row, startGuardRange, res);
|
||||
if (endStart < 0) then
|
||||
exit;
|
||||
|
||||
if Assigned(resultPointCallback) then
|
||||
begin
|
||||
resPoint := TResultPointHelpers.CreateResultPoint(endStart, rowNumber);
|
||||
resultPointCallback(resPoint);
|
||||
end;
|
||||
|
||||
endRange := decodeEnd(row, endStart);
|
||||
if (endRange = nil) then
|
||||
exit;
|
||||
|
||||
if Assigned(resultPointCallback) then
|
||||
begin
|
||||
resPoint := TResultPointHelpers.CreateResultPoint
|
||||
((endRange[0] + endRange[1]) div 2, rowNumber);
|
||||
resultPointCallback(resPoint);
|
||||
end;
|
||||
|
||||
ending := endRange[1];
|
||||
quietEnd := (ending + (ending - endRange[0]));
|
||||
if ((quietEnd >= row.Size) or not row.isRange(ending, quietEnd, false)) then
|
||||
exit;
|
||||
|
||||
resultString := res.ToString;
|
||||
if (Length(resultString) < 8) then
|
||||
exit;
|
||||
if (not checkChecksum(resultString)) then
|
||||
exit;
|
||||
left := ((startGuardRange[1] + startGuardRange[0]) div 2);
|
||||
right := ((endRange[1] + endRange[0]) div 2);
|
||||
format := BarcodeFormat;
|
||||
resultPoints := TArray<IResultPoint>.Create
|
||||
(TResultPointHelpers.CreateResultPoint(left, rowNumber),
|
||||
TResultPointHelpers.CreateResultPoint(right, rowNumber));
|
||||
|
||||
decodeResult := TReadResult.Create(resultString, nil, resultPoints, format);
|
||||
extensionResult := extensionReader.decodeRow(rowNumber, row, endRange[1]);
|
||||
if (extensionResult <> nil) then
|
||||
begin
|
||||
decodeResult.putMetadata(TResultMetadataType.UPC_EAN_EXTENSION,
|
||||
TResultMetaData.CreateStringMetadata(extensionResult.Text));
|
||||
decodeResult.putAllMetadata(extensionResult.ResultMetadata);
|
||||
decodeResult.addResultPoints(extensionResult.resultPoints);
|
||||
extensionLength := Length(extensionResult.Text);
|
||||
extensionResult.Free;
|
||||
|
||||
if (hints <> nil) and
|
||||
(hints.ContainsKey(TDecodeHintType.ALLOWED_EAN_EXTENSIONS)) then
|
||||
allowedExtensions := TArray<Integer>
|
||||
(hints[TDecodeHintType.ALLOWED_EAN_EXTENSIONS])
|
||||
else
|
||||
allowedExtensions := nil;
|
||||
|
||||
if (allowedExtensions <> nil) then
|
||||
begin
|
||||
valid := false;
|
||||
|
||||
for len in allowedExtensions do
|
||||
begin
|
||||
if (extensionLength = len) then
|
||||
begin
|
||||
valid := true;
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
if (not valid) then
|
||||
exit;
|
||||
end
|
||||
end;
|
||||
|
||||
case format of
|
||||
TBarcodeFormat.EAN_13, TBarcodeFormat.UPC_A:
|
||||
begin
|
||||
countryID := eanManSupport.lookupCountryIdentifier(resultString);
|
||||
if (countryID <> '') then
|
||||
decodeResult.putMetadata(TResultMetadataType.POSSIBLE_COUNTRY,
|
||||
TResultMetaData.CreateStringMetadata(countryID));
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := decodeResult;
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TUPCEANReader.InitializeClass;
|
||||
|
||||
finalization
|
||||
|
||||
TUPCEANReader.FinalizeClass;
|
||||
|
||||
end.
|
||||
@@ -0,0 +1,235 @@
|
||||
unit ZXing.OneD.UPCEReader;
|
||||
{
|
||||
* Copyright 2008 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
* Original Authors: Sean Owen
|
||||
* Delphi Implementation by E. Spelt
|
||||
}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
System.SysUtils,
|
||||
System.Generics.Collections,
|
||||
Math,
|
||||
ZXing.OneD.OneDReader,
|
||||
ZXing.Common.BitArray,
|
||||
ZXing.BinaryBitmap,
|
||||
ZXing.ReadResult,
|
||||
ZXing.DecodeHintType,
|
||||
ZXing.ResultPoint,
|
||||
ZXing.BarcodeFormat,
|
||||
ZXing.Helpers,
|
||||
ZXing.OneD.EAN13Reader,
|
||||
ZXing.OneD.UPCEANReader;
|
||||
|
||||
type
|
||||
|
||||
T2DIntArray = TArray<TArray<Integer>>;
|
||||
|
||||
TUPCEReader = class(TUPCEANReader)
|
||||
private
|
||||
class var decodeMiddleCounters: TArray<Integer>;
|
||||
class var MIDDLE_END_PATTERN: TArray<Integer>;
|
||||
class var NUMSYS_AND_CHECK_DIGIT_PATTERNS: T2DIntArray;
|
||||
|
||||
class procedure DoInitialize();
|
||||
class procedure DoFinalize();
|
||||
class function determineNumSysAndCheckDigit(resultString: TStringBuilder;
|
||||
lgPatternFound: Integer): boolean; static;
|
||||
|
||||
protected
|
||||
|
||||
class function checkChecksum(const s: string): boolean; override;
|
||||
class function decodeEnd(const row: IBitArray; const endStart: Integer)
|
||||
: TArray<Integer>; override;
|
||||
|
||||
public
|
||||
class function DecodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>; const res: TStringBuilder)
|
||||
: Integer; override;
|
||||
|
||||
public
|
||||
class function convertUPCEtoUPCA(upce: string): string; static;
|
||||
|
||||
function BarcodeFormat: TBarcodeFormat; override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TUPCEReader }
|
||||
|
||||
class function TUPCEReader.determineNumSysAndCheckDigit
|
||||
(resultString: TStringBuilder; lgPatternFound: Integer): boolean;
|
||||
var
|
||||
numSys, d: Integer;
|
||||
begin
|
||||
numSys := 0;
|
||||
while ((numSys <= 1)) do
|
||||
begin
|
||||
d := 0;
|
||||
while ((d < 10)) do
|
||||
begin
|
||||
if (lgPatternFound = TUPCEReader.NUMSYS_AND_CHECK_DIGIT_PATTERNS
|
||||
[numSys][d]) then
|
||||
begin
|
||||
resultString.Insert(0, Char($30 + numSys) );
|
||||
resultString.Append(char($30 + d));
|
||||
begin
|
||||
Result := true;
|
||||
exit
|
||||
end
|
||||
end;
|
||||
inc(d)
|
||||
end;
|
||||
inc(numSys)
|
||||
end;
|
||||
|
||||
Result := false;
|
||||
end;
|
||||
|
||||
class function TUPCEReader.checkChecksum(const s: string): boolean;
|
||||
begin
|
||||
Result := inherited checkChecksum(TUPCEReader.convertUPCEtoUPCA(s))
|
||||
end;
|
||||
|
||||
class function TUPCEReader.convertUPCEtoUPCA(upce: string): string;
|
||||
var
|
||||
upceChars: string;
|
||||
res: TStringBuilder;
|
||||
lastChar: char;
|
||||
begin
|
||||
upceChars := upce.Substring(1, 6);
|
||||
res := TStringBuilder.Create(12);
|
||||
res.Append(upce.Chars[0]);
|
||||
lastChar := upceChars.Chars[5];
|
||||
case lastChar of
|
||||
'0', '1', '2':
|
||||
begin
|
||||
res.Append(upceChars, 0, 2);
|
||||
res.Append(lastChar);
|
||||
res.Append('0000');
|
||||
res.Append(upceChars, 2, 3);
|
||||
end;
|
||||
'3':
|
||||
begin
|
||||
res.Append(upceChars, 0, 3);
|
||||
res.Append('00000');
|
||||
res.Append(upceChars, 3, 2);
|
||||
end;
|
||||
'4':
|
||||
begin
|
||||
res.Append(upceChars, 0, 4);
|
||||
res.Append('00000');
|
||||
res.Append(upceChars.Chars[4]);
|
||||
end;
|
||||
else
|
||||
begin
|
||||
res.Append(upceChars, 0, 5);
|
||||
res.Append('0000');
|
||||
res.Append(lastChar);
|
||||
end;
|
||||
end;
|
||||
|
||||
res.Append(upce.Chars[7]);
|
||||
Result := res.ToString;
|
||||
res.Free;
|
||||
end;
|
||||
|
||||
class function TUPCEReader.decodeEnd(const row: IBitArray;
|
||||
const endStart: Integer): TArray<Integer>;
|
||||
begin
|
||||
Result := TUPCEANReader.findGuardPattern(row, endStart, true,
|
||||
TUPCEReader.MIDDLE_END_PATTERN);
|
||||
end;
|
||||
|
||||
class function TUPCEReader.DecodeMiddle(const row: IBitArray;
|
||||
const startRange: TArray<Integer>; const res: TStringBuilder): Integer;
|
||||
var
|
||||
bestMatch: Integer;
|
||||
counter, ending, lgPatternFound, rowOffset, x: Integer;
|
||||
counters: TArray<Integer>;
|
||||
begin
|
||||
|
||||
counters := self.decodeMiddleCounters;
|
||||
counters[0] := 0;
|
||||
counters[1] := 0;
|
||||
counters[2] := 0;
|
||||
counters[3] := 0;
|
||||
ending := row.Size;
|
||||
rowOffset := startRange[1];
|
||||
lgPatternFound := 0;
|
||||
x := 0;
|
||||
|
||||
while ((x < 6) and (rowOffset < ending)) do
|
||||
begin
|
||||
if (not TUPCEANReader.decodeDigit(row, counters, rowOffset,
|
||||
TUPCEANReader.L_AND_G_PATTERNS, bestMatch)) then
|
||||
begin
|
||||
exit(-1);
|
||||
end;
|
||||
|
||||
res.Append(Char(ord('0') + bestMatch mod 10));
|
||||
|
||||
for counter in counters do
|
||||
begin
|
||||
inc(rowOffset, counter)
|
||||
end;
|
||||
|
||||
if (bestMatch >= 10) then
|
||||
lgPatternFound := lgPatternFound or (1 shl (5 - x));
|
||||
|
||||
inc(x)
|
||||
end;
|
||||
|
||||
if (not TUPCEReader.determineNumSysAndCheckDigit(res, lgPatternFound)) then
|
||||
begin
|
||||
exit(-1);
|
||||
end;
|
||||
|
||||
Result := rowOffset;
|
||||
end;
|
||||
|
||||
function TUPCEReader.BarcodeFormat: TBarcodeFormat;
|
||||
begin
|
||||
Result := TBarcodeFormat.UPC_E;
|
||||
end;
|
||||
|
||||
class procedure TUPCEReader.DoInitialize;
|
||||
begin
|
||||
MIDDLE_END_PATTERN := TArray<Integer>.Create(1, 1, 1, 1, 1, 1);
|
||||
NUMSYS_AND_CHECK_DIGIT_PATTERNS :=
|
||||
T2DIntArray.Create(TArray<Integer>.Create($38, $34, 50, $31, $2C, $26, $23,
|
||||
$2A, $29, $25), TArray<Integer>.Create(7, 11, 13, 14, $13, $19, $1C, $15,
|
||||
$16, $1A));
|
||||
decodeMiddleCounters := TArray<Integer>.Create(0,0,0,0);
|
||||
end;
|
||||
|
||||
class procedure TUPCEReader.DoFinalize;
|
||||
begin
|
||||
MIDDLE_END_PATTERN := nil;
|
||||
NUMSYS_AND_CHECK_DIGIT_PATTERNS := nil;
|
||||
decodeMiddleCounters := nil;
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
TUPCEReader.DoInitialize;
|
||||
|
||||
finalization
|
||||
|
||||
TUPCEReader.DoFinalize;
|
||||
|
||||
end.
|
||||
Reference in New Issue
Block a user