{ * 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 /// ///

Encapsulates functionality and implementation that is common to UPC and EAN families /// of one-dimensional barcodes.

///
TUPCEANReader = class abstract(TOneDReader) private decodeRowStringBuffer: TStringBuilder; extensionReader: TUPCEANExtensionSupport; eanManSupport: TEANManufacturerOrgSupport; /// /// /// row of black/white values to search /// position to start search /// if true, indicates that the pattern specifies white/black/white/... /// pixel counts, otherwise, it is interpreted as black/white/black/... /// pattern of counts of number of black and white pixels that are being /// searched for as a pattern /// array of counters, as long as pattern, to re-use /// start/end horizontal offset of guard pattern, as an array of two ints class function findGuardPattern(const row: IBitArray; rowOffset: Integer; const whiteFirst: Boolean; const pattern: TArray; counters: TArray): TArray; overload; /// /// Computes the UPC/EAN checksum on a string of digits, and reports /// whether the checksum is correct or not. /// /// string of digits to check /// true iff string of digits passes the UPC/EAN checksum algorithm 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; /// /// Start/end guard pattern. /// START_END_PATTERN: TArray; /// /// Pattern marking the middle of a UPC/EAN pattern, separating the two halves. /// MIDDLE_PATTERN: TArray; /// /// "Odd", or "L" patterns used to encode UPC/EAN digits. /// L_PATTERNS: TArray>; /// /// Decodes the end. /// /// The row. /// The end start. /// class function decodeEnd(const row: IBitArray; const endStart: Integer) : TArray; virtual; /// /// /// string of digits to check /// see class function checkChecksum(const s: String): Boolean; virtual; public class var /// /// As above but also including the "even", or "G" patterns used to encode UPC/EAN digits. /// L_AND_G_PATTERNS: TArray>; /// /// Initializes a new instance of the class. /// constructor Create; virtual; destructor Destroy; override; /// /// Subclasses override this to decode the portion of a barcode between the start /// and end guard patterns. /// /// row of black/white values to search /// start/end offset of start guard pattern /// to append decoded chars to /// horizontal offset of first pixel after the "middle" that was decoded or -1 if decoding could not complete successfully class function DecodeMiddle(const row: IBitArray; const startRange: TArray; const resultString: TStringBuilder) : Integer; virtual; abstract; /// /// Get the format of this decoder. /// /// The 1D format. function BarcodeFormat: TBarcodeFormat; virtual; abstract; class function findStartGuardPattern(const row: IBitArray) : TArray; static; class function findGuardPattern(const row: IBitArray; const rowOffset: Integer; const whiteFirst: Boolean; const pattern: TArray): TArray; overload; /// /// Attempts to decode a single UPC/EAN-encoded digit. /// /// row of black/white values to decode /// the counts of runs of observed black/white/black/... values /// horizontal offset to start decoding from /// the set of patterns to use to decode -- sometimes different encodings /// for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should /// be used /// horizontal offset of first pixel beyond the decoded digit class function decodeDigit(const row: IBitArray; const counters: TArray; const rowOffset: Integer; const patterns: TArray>; var digit: Integer): Boolean; /// ///

Attempts to decode a one-dimensional barcode format given a single row of /// an image.

///
/// row number from top of the row /// the black/white pixel data of the row /// decode hints /// /// containing encoded string and start/end of barcode or null, if an error occurs or barcode cannot be found /// function decodeRow(const rowNumber: Integer; const row: IBitArray; const hints: TDictionary): TReadResult; overload; override; /// ///

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.

///
/// row index into the image /// encoding of the row of the barcode image /// start/end column where the opening start pattern was found /// optional hints that influence decoding /// encapsulating the result of decoding a barcode in the row function DoDecodeRow(const rowNumber: Integer; const row: IBitArray; const startGuardRange: TArray; const hints: TDictionary): 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; 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.Create(1, 1, 1); MIDDLE_PATTERN := TArray.Create(1, 1, 1, 1, 1); L_PATTERNS := TArray < TArray < Integer >> .Create(TArray.Create(3, 2, 1, 1), // 0 TArray.Create(2, 2, 2, 1), // 1 TArray.Create(2, 1, 2, 2), // 2 TArray.Create(1, 4, 1, 1), // 3 TArray.Create(1, 1, 3, 2), // 4 TArray.Create(1, 2, 3, 1), // 5 TArray.Create(1, 1, 1, 4), // 6 TArray.Create(1, 3, 1, 2), // 7 TArray.Create(1, 2, 1, 3), // 8 TArray.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.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; begin Result := findGuardPattern(row, endStart, false, START_END_PATTERN); end; class function TUPCEANReader.findStartGuardPattern(const row: IBitArray) : TArray; var foundStart: Boolean; startRange, counters: TArray; nextStart, start, quietStart, idx, l: Integer; begin Result := nil; foundStart := false; startRange := nil; nextStart := 0; counters := TArray.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): TArray; var counters: TArray; begin counters := TArray.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; counters: TArray): TArray; var patternLength, width, x: Integer; isWhite: Boolean; counterPosition, patternStart: Integer; curCounter: TArray; 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.Create(patternStart, x); break; end; Inc(patternStart, (counters[0] + counters[1])); curCounter := TArray.Create(); SetLength(curCounter, Length(counters)); TArray.Copy(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; const rowOffset: Integer; const patterns: TArray>; var digit: Integer): Boolean; var bestVariance, variance, i, max: Integer; pattern: TArray; 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): TReadResult; var startRange: TArray; 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; const hints: TDictionary): TReadResult; var len: Integer; res: TStringBuilder; resultString: String; endStart, ending, quietEnd: Integer; endRange: TArray; resultPoints: TArray; left, right: Single; resultPointCallback: TResultPointCallback; obj: TObject; resPoint: IResultPoint; decodeResult, extensionResult: TReadResult; format: TBarcodeFormat; allowedExtensions: TArray; 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.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 (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.