{ * Copyright 2007 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * Original Author: Sean Owen * Delphi Implementation by E. Spelt and K. Gossens } unit ZXing.QrCode.QRCodeReader; interface uses System.SysUtils, System.Math, System.Generics.Collections, ZXing.Common.BitArray, ZXing.ReadResult, ZXing.Reader, ZXing.DecodeHintType, ZXing.BarcodeFormat, ZXing.ResultPoint, ZXing.BinaryBitmap, ZXing.QrCode.Internal.Decoder, ZXing.Common.BitMatrix, ZXing.DecoderResult, ZXing.ResultMetadataType, ZXing.Common.DetectorResult, ZXing.QrCode.Internal.QRCodeDecoderMetaData, ZXing.QrCode.Internal.Detector, ZXing.QrCode.Internal.DecodedBitStreamParser; type /// /// This implementation can detect and decode QR Codes in an image. /// TQRCodeReader = class(TInterfacedObject, IReader) private FDecoder: TQRDecoder; NO_POINTS: TArray; /// /// This method detects a code in a "pure" image -- that is, pure monochrome image /// which contains only an unrotated, unskewed, image of a code, with some white border /// around it. This is a specialized method that works exceptionally fast in this special /// case. /// /// /// class function extractPureBits(const image: TBitMatrix): TBitMatrix; static; class function moduleSize(const leftTopBlack: TArray; const image: TBitMatrix; var msize: Single): Boolean; static; protected /// /// Gets the decoder. /// /// property Decoder: TQRDecoder read FDecoder; public constructor Create; destructor Destroy; override; /// /// Locates and decodes a QR code in an image. /// /// a String representing the content encoded by the QR code /// function decode(const image: TBinaryBitmap): TReadResult; overload; /// /// 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. /// /// image of barcode to decode /// passed as a from /// to arbitrary data. The /// meaning of the data depends upon the hint type. The implementation may or may not do /// anything with these hints. /// /// String which the barcode encodes /// function decode(const image: TBinaryBitmap; hints: TDictionary): TReadResult; overload; /// /// Resets any internal state the implementation has after a decode, to prepare it /// for reuse. /// procedure reset; end; implementation uses ZXing.ByteSegments; { TQRCodeReader } constructor TQRCodeReader.Create; begin inherited; FDecoder := TQRDecoder.Create; NO_POINTS := TArray.Create(); end; destructor TQRCodeReader.Destroy; begin NO_POINTS := nil; FDecoder.Free; inherited; end; function TQRCodeReader.decode(const image: TBinaryBitmap): TReadResult; begin Result := Self.decode(image, nil); end; function TQRCodeReader.decode(const image: TBinaryBitmap; hints: TDictionary): TReadResult; var DecoderResult: TDecoderResult; Detector: TDetector; points: TArray; bits: TBitMatrix; DetectorResult: TDetectorResult; data: TQRCodeDecoderMetaData; byteSegments: IByteSegments; begin Result := nil; DecoderResult := nil; if ((image = nil) or (image.BlackMatrix = nil)) then // something is wrong with the image exit; try if ((hints <> nil) and hints.ContainsKey(TDecodeHintType.PURE_BARCODE)) then begin bits := TQRCodeReader.extractPureBits(image.BlackMatrix); if Assigned(bits) then begin DecoderResult := Decoder.decode(bits, hints); points := NO_POINTS; bits.Free; end else exit; end else begin Detector := TDetector.Create(image.BlackMatrix); try DetectorResult := Detector.detect(hints); if Assigned(DetectorResult) then begin DecoderResult := Decoder.decode(DetectorResult.bits, hints); points := DetectorResult.points; DetectorResult.Free; end else exit; finally Detector.Free; end; end; if (DecoderResult = nil) then exit; // If the code was mirrored: swap the bottom-left and the top-right points. data := TQRCodeDecoderMetaData(DecoderResult.Other); if (data <> nil) then data.applyMirroredCorrection(points); Result := TReadResult.Create(DecoderResult.Text, DecoderResult.RawBytes, points, TBarcodeFormat.QR_CODE); byteSegments := DecoderResult.byteSegments; if (byteSegments <> nil) then Result.putMetadata(TResultMetadataType.BYTE_SEGMENTS, TResultMetaData.CreateByteSegmentsMetadata(byteSegments)); if (Length(DecoderResult.ecLevel) <> 0) then Result.putMetadata(TResultMetadataType.ERROR_CORRECTION_LEVEL, TResultMetaData.CreateStringMetadata(DecoderResult.ecLevel)); if (DecoderResult.StructuredAppend) then begin Result.putMetadata(TResultMetadataType.STRUCTURED_APPEND_SEQUENCE, TResultMetaData.CreateIntegerMetadata(DecoderResult.StructuredAppendSequenceNumber)); Result.putMetadata(TResultMetadataType.STRUCTURED_APPEND_PARITY, TResultMetaData.CreateIntegerMetadata(DecoderResult.StructuredAppendParity)) end; finally if Assigned(DecoderResult) then FreeAndNil(DecoderResult); end; end; procedure TQRCodeReader.reset; begin // do nothing end; class function TQRCodeReader.extractPureBits(const image: TBitMatrix) : TBitMatrix; var moduleSize: Single; leftTopBlack, rightBottomBlack: TArray; top, bottom, left, right, matrixWidth, matrixHeight, nudge, x, y, iOffset, nudgedTooFarRight, nudgedTooFarDown: Integer; bits: TBitMatrix; begin Result := nil; leftTopBlack := image.getTopLeftOnBit; rightBottomBlack := image.getBottomRightOnBit; if ((leftTopBlack = nil) or (rightBottomBlack = nil)) then exit; if (not TQRCodeReader.moduleSize(leftTopBlack, image, moduleSize)) then exit; top := leftTopBlack[1]; bottom := rightBottomBlack[1]; left := leftTopBlack[0]; right := rightBottomBlack[0]; // Sanity check! if ((left >= right) or (top >= bottom)) then exit; if ((bottom - top) <> (right - left)) then // Special case, where bottom-right module wasn't black so we found something else in the last row // Assume it's a square, so use height as the width right := (left + (bottom - top)); matrixWidth := Round(((right - left) + 1) / moduleSize); matrixHeight := Round(((bottom - top) + 1) / moduleSize); if ((matrixWidth <= 0) or (matrixHeight <= 0)) then exit; if (matrixHeight <> matrixWidth) then // Only possibly decode square regions exit; // Push in the "border" by half the module width so that we start // sampling in the middle of the module. Just in case the image is a // little off, this will help recover. nudge := Trunc(moduleSize / 2.0); Inc(top, nudge); Inc(left, nudge); // But careful that this does not sample off the edge // "right" is the farthest-right valid pixel location -- right+1 is not necessarily // This is positive by how much the inner x loop below would be too large nudgedTooFarRight := left + Trunc((matrixWidth - 1) * moduleSize) - right; if (nudgedTooFarRight > 0) then begin if (nudgedTooFarRight > nudge) then // Neither way fits; abort exit; Dec(left, nudgedTooFarRight); end; // See logic above nudgedTooFarDown := top + Trunc((matrixHeight - 1) * moduleSize) - bottom; if (nudgedTooFarDown > 0) then begin if (nudgedTooFarDown > nudge) then // Neither way fits; abort exit; Dec(top, nudgedTooFarDown); end; // Now just read off the bits bits := TBitMatrix.Create(matrixWidth, matrixHeight); for y := 0 to Pred(matrixHeight) do begin iOffset := top + Trunc(y * moduleSize); for x := 0 to Pred(matrixWidth) do begin if (image[left + Trunc((x * moduleSize)), iOffset]) then bits[x, y] := true; end; end; Result := bits; end; class function TQRCodeReader.moduleSize(const leftTopBlack: TArray; const image: TBitMatrix; var msize: Single): Boolean; var height, width, x, y: Integer; inBlack: Boolean; transitions: Integer; begin Result := false; height := image.height; width := image.width; x := leftTopBlack[0]; y := leftTopBlack[1]; inBlack := true; transitions := 0; while (((x < width) and (y < height))) do begin if (inBlack <> image[x, y]) then begin Inc(transitions); if (transitions = 5) then break; inBlack := not inBlack; end; Inc(x); Inc(y); end; if ((x = width) or (y = height)) then begin msize := 0.0; exit; end; msize := ((x - leftTopBlack[0]) / 7.0); Result := true; end; end.