{ * Copyright 2008 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * Original Author: Sean Owen * Delphi Implementation by K. Gossens } unit ZXing.Datamatrix.Internal.Detector; interface uses System.SysUtils, System.Math, System.Generics.Defaults, System.Generics.Collections, ZXing.Common.BitMatrix, ZXing.DefaultGridSampler, ZXing.Common.DetectorResult, ZXing.ResultPoint, ZXing.Common.GridSampler, ZXing.Common.Detector.MathUtils, ZXing.Common.Detector.WhiteRectangleDetector; type /// ///

Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code /// is rotated or skewed, or partially obscured.

///
TDataMatrixDetector = class(TObject) private var Fimage: TBitMatrix; FrectangleDetector: TWhiteRectangleDetector; type /// /// Simply encapsulates two points and a number of transitions between them. /// TResultPointsAndTransitions = class sealed public From: IResultPoint; To_: IResultPoint; Transitions: Integer; constructor Create(From: IResultPoint; To_: IResultPoint; Transitions: Integer); destructor Destroy; override; function ToString: String; override; end; /// /// Orders ResultPointsAndTransitions by number of transitions, ascending. /// TResultPointsAndTransitionsComparator = class sealed (TComparer) public function Compare(const o1, o2: TResultPointsAndTransitions) : Integer; override; end; var FtransCompare: TResultPointsAndTransitionsComparator; public constructor Create(const image: TBitMatrix); destructor Destroy; override; function detect: TDetectorResult; function transitionsBetween(Afrom, Ato: IResultPoint) : TResultPointsAndTransitions; function distance(a: IResultPoint; b: IResultPoint): Integer; procedure increment(table: TDictionary; key: IResultPoint); function sampleGrid(image: TBitMatrix; topLeft, bottomLeft, bottomRight, topRight: IResultPoint; dimensionX, dimensionY: Integer): TBitMatrix; function isValid(p: IResultPoint): Boolean; function correctTopRight(bottomLeft, bottomRight, topLeft, topRight: IResultPoint; dimension: Integer): IResultPoint; function correctTopRightRectangular(bottomLeft, bottomRight, topLeft, topRight: IResultPoint; dimensionTop, dimensionRight: Integer) : IResultPoint; end; implementation { TDataMatrixDetector } /// /// Initializes a new instance of the class. /// /// The image. constructor TDataMatrixDetector.Create(const image: TBitMatrix); begin Self.Fimage := image; Self.FrectangleDetector := TWhiteRectangleDetector.New(image); Self.FtransCompare := TResultPointsAndTransitionsComparator.Create; end; destructor TDataMatrixDetector.Destroy; begin if (FrectangleDetector <> nil) then FrectangleDetector.Free; FtransCompare.Free; inherited; end; /// ///

Detects a Data Matrix Code in an image.

///
/// encapsulating results of detecting a Data Matrix Code or null function TDataMatrixDetector.detect(): TDetectorResult; var topRight, correctedTopRight, pointA, pointB, pointC, pointD, maybeTopLeft, bottomLeft, bottomRight, topLeft, maybeBottomRight, point: IResultPoint; cornerPoints: TArray; bits: TBitMatrix; entry: TPair; // TKeyValuePair Transitions: TObjectList; lSideOne, lSideTwo, transBetween, transA, transB: TResultPointsAndTransitions; pointCount: TDictionary; corners: TArray; dimensionTop, dimensionRight, dimension, dimensionCorrected: Integer; begin Result := nil; if (FrectangleDetector = nil) then // can be null, if the image is to small exit; cornerPoints := FrectangleDetector.detect(); if (cornerPoints = nil) then exit; pointA := cornerPoints[0]; pointB := cornerPoints[1]; pointC := cornerPoints[2]; pointD := cornerPoints[3]; // Point A and D are across the diagonal from one another, // as are B and C. Figure out which are the solid black lines // by counting transitions Transitions := TObjectList.Create; pointCount := TDictionary.Create(); try Transitions.Add(transitionsBetween(pointA, pointB)); Transitions.Add(transitionsBetween(pointA, pointC)); Transitions.Add(transitionsBetween(pointB, pointD)); Transitions.Add(transitionsBetween(pointC, pointD)); Transitions.Sort(FtransCompare); // Sort by number of transitions. First two will be the two solid sides; last two // will be the two alternating black/white sides lSideOne := Transitions[0]; lSideTwo := Transitions[1]; // Figure out which point is their intersection by tallying up the number of times we see the // endpoints in the four endpoints. One will show up twice. increment(pointCount, lSideOne.From); increment(pointCount, lSideOne.To_); increment(pointCount, lSideTwo.From); increment(pointCount, lSideTwo.To_); maybeTopLeft := nil; bottomLeft := nil; maybeBottomRight := nil; for entry in pointCount do begin point := entry.key; if (entry.Value = 2) then bottomLeft := point // this is definitely the bottom left, then -- end of two L sides else begin // Otherwise it's either top left or bottom right -- just assign the two arbitrarily now if (maybeTopLeft = nil) then maybeTopLeft := point else maybeBottomRight := point; end; end; if ((maybeTopLeft = nil) or (bottomLeft = nil) or (maybeBottomRight = nil)) then exit; // Bottom left is correct but top left and bottom right might be switched corners := TArray.Create(maybeTopLeft, bottomLeft, maybeBottomRight); // Use the dot product trick to sort them out TResultPointHelpers.orderBestPatterns(corners); // Now we know which is which: bottomRight := corners[0]; bottomLeft := corners[1]; topLeft := corners[2]; // Which point didn't we find in relation to the "L" sides? that's the top right corner if (not pointCount.ContainsKey(pointA)) then topRight := pointA else if (not pointCount.ContainsKey(pointB)) then topRight := pointB else if (not pointCount.ContainsKey(pointC)) then topRight := pointC else topRight := pointD; // Next determine the dimension by tracing along the top or right side and counting black/white // transitions. Since we start inside a black module, we should see a number of transitions // equal to 1 less than the code dimension. Well, actually 2 less, because we are going to // end on a black module: // The top right point is actually the corner of a module, which is one of the two black modules // adjacent to the white module at the top right. Tracing to that corner from either the top left // or bottom right should work here. transBetween := transitionsBetween(topLeft, topRight); dimensionTop := transBetween.Transitions; transBetween.Free; transBetween := transitionsBetween(bottomRight, topRight); dimensionRight := transBetween.Transitions; transBetween.Free; if ((dimensionTop and $01) = 1) then // it can't be odd, so, round... up? Inc(dimensionTop); Inc(dimensionTop, 2); if ((dimensionRight and $01) = 1) then // it can't be odd, so, round... up? Inc(dimensionRight); Inc(dimensionRight, 2); // Rectangular symbols are 6x16, 6x28, 10x24, 10x32, 14x32, or 14x44. If one dimension is more // than twice the other, it's certainly rectangular, but to cut a bit more slack we accept it as // rectangular if the bigger side is at least 7/4 times the other: if (((4 * dimensionTop) >= (7 * dimensionRight)) or ((4 * dimensionRight) >= (7 * dimensionTop))) then begin // The matrix is rectangular correctedTopRight := correctTopRightRectangular(bottomLeft, bottomRight, topLeft, topRight, dimensionTop, dimensionRight); if (correctedTopRight = nil) then correctedTopRight := topRight; transBetween := transitionsBetween(topLeft, correctedTopRight); dimensionTop := transBetween.Transitions; transBetween.Free; transBetween := transitionsBetween(bottomRight, correctedTopRight); dimensionRight := transBetween.Transitions; transBetween.Free; if ((dimensionTop and $01) = 1) then // it can't be odd, so, round... up? Inc(dimensionTop); if ((dimensionRight and $01) = 1) then // it can't be odd, so, round... up? Inc(dimensionRight); bits := sampleGrid(Fimage, topLeft, bottomLeft, bottomRight, correctedTopRight, dimensionTop, dimensionRight) end else begin // The matrix is square dimension := System.Math.Min(dimensionRight, dimensionTop); // correct top right point to match the white module correctedTopRight := correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension); if (correctedTopRight = nil) then correctedTopRight := topRight; // Redetermine the dimension using the corrected top right point transA := transitionsBetween(topLeft, correctedTopRight); transB := transitionsBetween(bottomRight, correctedTopRight); dimensionCorrected := (System.Math.Max(transA.Transitions, transB.Transitions) + 1); transA.Free; transB.Free; if ((dimensionCorrected and $01) = 1) then Inc(dimensionCorrected); bits := sampleGrid(Fimage, topLeft, bottomLeft, bottomRight, correctedTopRight, dimensionCorrected, dimensionCorrected); end; if (bits = nil) then exit; Result := TDetectorResult.Create(bits, TArray.Create(topLeft, bottomLeft, bottomRight, correctedTopRight)); finally pointCount.Free; Transitions.Free; cornerPoints := nil; end; end; /// /// Calculates the position of the white top right module using the output of the rectangle detector /// for a rectangular matrix /// function TDataMatrixDetector.correctTopRightRectangular(bottomLeft, bottomRight, topLeft, topRight: IResultPoint; dimensionTop, dimensionRight: Integer) : IResultPoint; var corr, norm, cos, sin: Single; c1, c2: IResultPoint; l1, l2: Integer; rpT1, rpT2: TResultPointsAndTransitions; begin corr := (distance(bottomLeft, bottomRight) / dimensionTop); norm := distance(topLeft, topRight); if (norm = 0) then begin Result := nil; exit end; cos := ((topRight.X - topLeft.X) / norm); sin := ((topRight.Y - topLeft.Y) / norm); c1 := TResultPointHelpers.CreateResultPoint((topRight.X + (corr * cos)), (topRight.Y + (corr * sin))); corr := (distance(bottomLeft, topLeft) / dimensionRight); norm := distance(bottomRight, topRight); if (norm = 0) then begin Result := nil; exit; end; cos := ((topRight.X - bottomRight.X) / norm); sin := ((topRight.Y - bottomRight.Y) / norm); c2 := TResultPointHelpers.CreateResultPoint((topRight.X + (corr * cos)), (topRight.Y + (corr * sin))); if (not isValid(c1)) then begin if (isValid(c2)) then begin Result := c2; exit; end; Result := nil; exit; end; if (not isValid(c2)) then begin Result := c1; exit; end; rpT1 := transitionsBetween(topLeft, c1); rpT2 := transitionsBetween(bottomRight, c1); l1 := (Abs((dimensionTop - rpT1.Transitions)) + Abs((dimensionRight - rpT2.Transitions))); FreeAndNil(rpT1); FreeAndNil(rpT2); rpT1 := transitionsBetween(topLeft, c2); rpT2 := transitionsBetween(bottomRight, c2); l2 := (Abs((dimensionTop - rpT1.Transitions)) + Abs((dimensionRight - rpT2.Transitions))); FreeAndNil(rpT1); FreeAndNil(rpT2); if (l1 <= l2) then begin Result := c1; end else begin Result := c2; end; end; /// /// Calculates the position of the white top right module using the output of the rectangle detector /// for a square matrix /// function TDataMatrixDetector.correctTopRight(bottomLeft, bottomRight, topLeft, topRight: IResultPoint; dimension: Integer): IResultPoint; var corr, cos, sin: Single; norm: Integer; c1, c2: IResultPoint; l1, l2: Integer; transA, transB: TResultPointsAndTransitions; begin corr := (distance(bottomLeft, bottomRight) / dimension); norm := distance(topLeft, topRight); if (norm = 0) then begin Result := nil; exit; end; cos := ((topRight.X - topLeft.X) / norm); sin := ((topRight.Y - topLeft.Y) / norm); c1 := TResultPointHelpers.CreateResultPoint((topRight.X + (corr * cos)), (topRight.Y + (corr * sin))); corr := (distance(bottomLeft, topLeft) / dimension); norm := distance(bottomRight, topRight); if (norm = 0) then begin Result := nil; exit; end; cos := ((topRight.X - bottomRight.X) / norm); sin := ((topRight.Y - bottomRight.Y) / norm); c2 := TResultPointHelpers.CreateResultPoint((topRight.X + (corr * cos)), (topRight.Y + (corr * sin))); if (not isValid(c1)) then begin if (isValid(c2)) then begin Result := c2; exit; end; Result := nil; exit; end; if (not isValid(c2)) then begin Result := c1; exit; end; transA := transitionsBetween(topLeft, c1); transB := transitionsBetween(bottomRight, c1); l1 := (Abs(transA.Transitions - transB.Transitions)); transA.Free; transB.Free; transA := transitionsBetween(topLeft, c2); transB := transitionsBetween(bottomRight, c2); l2 := (Abs(transA.Transitions - transB.Transitions)); transA.Free; transB.Free; if (l1 <= l2) then begin Result := c1; end else begin Result := c2; end; end; function TDataMatrixDetector.isValid(p: IResultPoint): Boolean; begin Result := ((((p.X >= 0) and (p.X < Fimage.Width)) and (p.Y > 0)) and (p.Y < Fimage.Height)) end; // L2 distance function TDataMatrixDetector.distance(a: IResultPoint; b: IResultPoint) : Integer; begin Result := TMathUtils.round(TResultPointHelpers.distance(a, b)) end; /// /// Increments the Integer associated with a key by one. /// procedure TDataMatrixDetector.increment (table: TDictionary; key: IResultPoint); var Value: Integer; begin if (table.ContainsKey(key)) then begin Value := table[key]; table[key] := (Value + 1); end else table.Add(key, 1); // table[key] := 1; end; function TDataMatrixDetector.sampleGrid(image: TBitMatrix; topLeft, bottomLeft, bottomRight, topRight: IResultPoint; dimensionX, dimensionY: Integer): TBitMatrix; begin // TGridSampler.instance Result := TDefaultGridSampler.sampleGrid(image, dimensionX, dimensionY, 0.5, 0.5, (dimensionX - 0.5), 0.5, (dimensionX - 0.5), (dimensionY - 0.5), 0.5, (dimensionY - 0.5), topLeft.X, topLeft.Y, topRight.X, topRight.Y, bottomRight.X, bottomRight.Y, bottomLeft.X, bottomLeft.Y); end; /// /// Counts the number of black/white transitions between two points, using something like Bresenham's algorithm. /// function TDataMatrixDetector.transitionsBetween(Afrom, Ato: IResultPoint) : TResultPointsAndTransitions; var temp, fromX, fromY, toX, toY: Integer; steep: Boolean; dx, dy: Int64; xstep, ystep, Transitions: Integer; error: Int64; inBlack, isBlack: Boolean; X, Y: Integer; begin // See QR Code Detector, sizeOfBlackWhiteBlackRun() fromX := Trunc(Afrom.X); fromY := Trunc(Afrom.Y); toX := Trunc(Ato.X); toY := Trunc(Ato.Y); steep := (Abs((toY - fromY)) > Abs((toX - fromX))); if (steep) then begin temp := fromX; fromX := fromY; fromY := temp; temp := toX; toX := toY; toY := temp; end; dx := Abs(toX - fromX); dy := Abs(toY - fromY); error := TMathUtils.Asr(-dx, 1); if (fromY < toY) then ystep := 1 else ystep := -1; if (fromX < toX) then xstep := 1 else xstep := -1; Transitions := 0; if steep then inBlack := Fimage[fromY, fromX] else inBlack := Fimage[fromX, fromY]; X := fromX; Y := fromY; while ((X <> toX)) do begin if steep then isBlack := Fimage[Y, X] else isBlack := Fimage[X, Y]; if (isBlack <> inBlack) then begin Inc(Transitions); inBlack := isBlack; end; Inc(error, dy); if (error > 0) then begin if (Y = toY) then break; Inc(Y, ystep); Dec(error, dx); end; Inc(X, xstep) end; Result := TResultPointsAndTransitions.Create(Afrom, Ato, Transitions); end; { TResultPointsAndTransitions } constructor TDataMatrixDetector.TResultPointsAndTransitions.Create (From: IResultPoint; To_: IResultPoint; Transitions: Integer); begin Self.From := From; Self.To_ := To_; Self.Transitions := Transitions; end; destructor TDataMatrixDetector.TResultPointsAndTransitions.Destroy; begin // if (Assigned(Self.From)) then // Self.From := nil; // // if (Assigned(Self.To_)) then // Self.To_:=nil; inherited; end; function TDataMatrixDetector.TResultPointsAndTransitions.ToString: string; begin Result := From.ToString + '/' + To_.ToString + '/' + IntToStr(Transitions); end; function TDataMatrixDetector.TResultPointsAndTransitionsComparator.Compare (const o1, o2: TResultPointsAndTransitions): Integer; begin Result := (o1.Transitions - o2.Transitions); end; end.