|
|
- /*
- Ported to JavaScript by Lazar Laszlo 2011
-
- lazarsoft@gmail.com, www.lazarsoft.info
-
- */
-
- /*
- *
- * 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.
- */
-
-
- var MIN_SKIP = 3;
- var MAX_MODULES = 57;
- var INTEGER_MATH_SHIFT = 8;
- var CENTER_QUORUM = 2;
-
- qrcode.orderBestPatterns=function(patterns)
- {
-
- function distance( pattern1, pattern2)
- {
- var xDiff = pattern1.X - pattern2.X;
- var yDiff = pattern1.Y - pattern2.Y;
- return Math.sqrt( (xDiff * xDiff + yDiff * yDiff));
- }
-
- /// <summary> Returns the z component of the cross product between vectors BC and BA.</summary>
- function crossProductZ( pointA, pointB, pointC)
- {
- var bX = pointB.x;
- var bY = pointB.y;
- return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX));
- }
-
-
- // Find distances between pattern centers
- var zeroOneDistance = distance(patterns[0], patterns[1]);
- var oneTwoDistance = distance(patterns[1], patterns[2]);
- var zeroTwoDistance = distance(patterns[0], patterns[2]);
-
- var pointA, pointB, pointC;
- // Assume one closest to other two is B; A and C will just be guesses at first
- if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance)
- {
- pointB = patterns[0];
- pointA = patterns[1];
- pointC = patterns[2];
- }
- else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance)
- {
- pointB = patterns[1];
- pointA = patterns[0];
- pointC = patterns[2];
- }
- else
- {
- pointB = patterns[2];
- pointA = patterns[0];
- pointC = patterns[1];
- }
-
- // Use cross product to figure out whether A and C are correct or flipped.
- // This asks whether BC x BA has a positive z component, which is the arrangement
- // we want for A, B, C. If it's negative, then we've got it flipped around and
- // should swap A and C.
- if (crossProductZ(pointA, pointB, pointC) < 0.0)
- {
- var temp = pointA;
- pointA = pointC;
- pointC = temp;
- }
-
- patterns[0] = pointA;
- patterns[1] = pointB;
- patterns[2] = pointC;
- }
-
-
- function FinderPattern(posX, posY, estimatedModuleSize)
- {
- this.x=posX;
- this.y=posY;
- this.count = 1;
- this.estimatedModuleSize = estimatedModuleSize;
-
- this.__defineGetter__("EstimatedModuleSize", function()
- {
- return this.estimatedModuleSize;
- });
- this.__defineGetter__("Count", function()
- {
- return this.count;
- });
- this.__defineGetter__("X", function()
- {
- return this.x;
- });
- this.__defineGetter__("Y", function()
- {
- return this.y;
- });
- this.incrementCount = function()
- {
- this.count++;
- }
- this.aboutEquals=function( moduleSize, i, j)
- {
- if (Math.abs(i - this.y) <= moduleSize && Math.abs(j - this.x) <= moduleSize)
- {
- var moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize);
- return moduleSizeDiff <= 1.0 || moduleSizeDiff / this.estimatedModuleSize <= 1.0;
- }
- return false;
- }
-
- }
-
- function FinderPatternInfo(patternCenters)
- {
- this.bottomLeft = patternCenters[0];
- this.topLeft = patternCenters[1];
- this.topRight = patternCenters[2];
- this.__defineGetter__("BottomLeft", function()
- {
- return this.bottomLeft;
- });
- this.__defineGetter__("TopLeft", function()
- {
- return this.topLeft;
- });
- this.__defineGetter__("TopRight", function()
- {
- return this.topRight;
- });
- }
-
- function FinderPatternFinder()
- {
- this.image=null;
- this.possibleCenters = [];
- this.hasSkipped = false;
- this.crossCheckStateCount = new Array(0,0,0,0,0);
- this.resultPointCallback = null;
-
- this.__defineGetter__("CrossCheckStateCount", function()
- {
- this.crossCheckStateCount[0] = 0;
- this.crossCheckStateCount[1] = 0;
- this.crossCheckStateCount[2] = 0;
- this.crossCheckStateCount[3] = 0;
- this.crossCheckStateCount[4] = 0;
- return this.crossCheckStateCount;
- });
-
- this.foundPatternCross=function( stateCount)
- {
- var totalModuleSize = 0;
- for (var i = 0; i < 5; i++)
- {
- var count = stateCount[i];
- if (count == 0)
- {
- return false;
- }
- totalModuleSize += count;
- }
- if (totalModuleSize < 7)
- {
- return false;
- }
- var moduleSize = Math.floor((totalModuleSize << INTEGER_MATH_SHIFT) / 7);
- var maxVariance = Math.floor(moduleSize / 2);
- // Allow less than 50% variance from 1-1-3-1-1 proportions
- return Math.abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance && Math.abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance;
- }
- this.centerFromEnd=function( stateCount, end)
- {
- return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0;
- }
- this.crossCheckVertical=function( startI, centerJ, maxCount, originalStateCountTotal)
- {
- var image = this.image;
-
- var maxI = qrcode.height;
- var stateCount = this.CrossCheckStateCount;
-
- // Start counting up from center
- var i = startI;
- while (i >= 0 && image[centerJ + i*qrcode.width])
- {
- stateCount[2]++;
- i--;
- }
- if (i < 0)
- {
- return NaN;
- }
- while (i >= 0 && !image[centerJ +i*qrcode.width] && stateCount[1] <= maxCount)
- {
- stateCount[1]++;
- i--;
- }
- // If already too many modules in this state or ran off the edge:
- if (i < 0 || stateCount[1] > maxCount)
- {
- return NaN;
- }
- while (i >= 0 && image[centerJ + i*qrcode.width] && stateCount[0] <= maxCount)
- {
- stateCount[0]++;
- i--;
- }
- if (stateCount[0] > maxCount)
- {
- return NaN;
- }
-
- // Now also count down from center
- i = startI + 1;
- while (i < maxI && image[centerJ +i*qrcode.width])
- {
- stateCount[2]++;
- i++;
- }
- if (i == maxI)
- {
- return NaN;
- }
- while (i < maxI && !image[centerJ + i*qrcode.width] && stateCount[3] < maxCount)
- {
- stateCount[3]++;
- i++;
- }
- if (i == maxI || stateCount[3] >= maxCount)
- {
- return NaN;
- }
- while (i < maxI && image[centerJ + i*qrcode.width] && stateCount[4] < maxCount)
- {
- stateCount[4]++;
- i++;
- }
- if (stateCount[4] >= maxCount)
- {
- return NaN;
- }
-
- // If we found a finder-pattern-like section, but its size is more than 40% different than
- // the original, assume it's a false positive
- var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
- if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal)
- {
- return NaN;
- }
-
- return this.foundPatternCross(stateCount)?this.centerFromEnd(stateCount, i):NaN;
- }
- this.crossCheckHorizontal=function( startJ, centerI, maxCount, originalStateCountTotal)
- {
- var image = this.image;
-
- var maxJ = qrcode.width;
- var stateCount = this.CrossCheckStateCount;
-
- var j = startJ;
- while (j >= 0 && image[j+ centerI*qrcode.width])
- {
- stateCount[2]++;
- j--;
- }
- if (j < 0)
- {
- return NaN;
- }
- while (j >= 0 && !image[j+ centerI*qrcode.width] && stateCount[1] <= maxCount)
- {
- stateCount[1]++;
- j--;
- }
- if (j < 0 || stateCount[1] > maxCount)
- {
- return NaN;
- }
- while (j >= 0 && image[j+ centerI*qrcode.width] && stateCount[0] <= maxCount)
- {
- stateCount[0]++;
- j--;
- }
- if (stateCount[0] > maxCount)
- {
- return NaN;
- }
-
- j = startJ + 1;
- while (j < maxJ && image[j+ centerI*qrcode.width])
- {
- stateCount[2]++;
- j++;
- }
- if (j == maxJ)
- {
- return NaN;
- }
- while (j < maxJ && !image[j+ centerI*qrcode.width] && stateCount[3] < maxCount)
- {
- stateCount[3]++;
- j++;
- }
- if (j == maxJ || stateCount[3] >= maxCount)
- {
- return NaN;
- }
- while (j < maxJ && image[j+ centerI*qrcode.width] && stateCount[4] < maxCount)
- {
- stateCount[4]++;
- j++;
- }
- if (stateCount[4] >= maxCount)
- {
- return NaN;
- }
-
- // If we found a finder-pattern-like section, but its size is significantly different than
- // the original, assume it's a false positive
- var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
- if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal)
- {
- return NaN;
- }
-
- return this.foundPatternCross(stateCount)?this.centerFromEnd(stateCount, j):NaN;
- }
- this.handlePossibleCenter=function( stateCount, i, j)
- {
- var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
- var centerJ = this.centerFromEnd(stateCount, j); //float
- var centerI = this.crossCheckVertical(i, Math.floor( centerJ), stateCount[2], stateCountTotal); //float
- if (!isNaN(centerI))
- {
- // Re-cross check
- centerJ = this.crossCheckHorizontal(Math.floor( centerJ), Math.floor( centerI), stateCount[2], stateCountTotal);
- if (!isNaN(centerJ))
- {
- var estimatedModuleSize = stateCountTotal / 7.0;
- var found = false;
- var max = this.possibleCenters.length;
- for (var index = 0; index < max; index++)
- {
- var center = this.possibleCenters[index];
- // Look for about the same center and module size:
- if (center.aboutEquals(estimatedModuleSize, centerI, centerJ))
- {
- center.incrementCount();
- found = true;
- break;
- }
- }
- if (!found)
- {
- var point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
- this.possibleCenters.push(point);
- if (this.resultPointCallback != null)
- {
- this.resultPointCallback.foundPossibleResultPoint(point);
- }
- }
- return true;
- }
- }
- return false;
- }
-
- this.selectBestPatterns=function()
- {
-
- var startSize = this.possibleCenters.length;
- if (startSize < 3)
- {
- // Couldn't find enough finder patterns
- throw "Couldn't find enough finder patterns (found " + startSize + ")"
- }
-
- // Filter outlier possibilities whose module size is too different
- if (startSize > 3)
- {
- // But we can only afford to do so if we have at least 4 possibilities to choose from
- var totalModuleSize = 0.0;
- var square = 0.0;
- for (var i = 0; i < startSize; i++)
- {
- //totalModuleSize += this.possibleCenters[i].EstimatedModuleSize;
- var centerValue=this.possibleCenters[i].EstimatedModuleSize;
- totalModuleSize += centerValue;
- square += (centerValue * centerValue);
- }
- var average = totalModuleSize / startSize;
- this.possibleCenters.sort(function(center1,center2) {
- var dA=Math.abs(center2.EstimatedModuleSize - average);
- var dB=Math.abs(center1.EstimatedModuleSize - average);
- if (dA < dB) {
- return (-1);
- } else if (dA == dB) {
- return 0;
- } else {
- return 1;
- }
- });
-
- var stdDev = Math.sqrt(square / startSize - average * average);
- var limit = Math.max(0.2 * average, stdDev);
- //for (var i = 0; i < this.possibleCenters.length && this.possibleCenters.length > 3; i++)
- for (var i = this.possibleCenters.length - 1; i >= 0 ; i--)
- {
- var pattern = this.possibleCenters[i];
- //if (Math.abs(pattern.EstimatedModuleSize - average) > 0.2 * average)
- if (Math.abs(pattern.EstimatedModuleSize - average) > limit)
- {
- //this.possibleCenters.remove(i);
- this.possibleCenters.splice(i,1);
- //i--;
- }
- }
- }
-
- if (this.possibleCenters.length > 3)
- {
- // Throw away all but those first size candidate points we found.
- this.possibleCenters.sort(function(a, b){
- if (a.count > b.count){return -1;}
- if (a.count < b.count){return 1;}
- return 0;
- });
- }
-
- return new Array( this.possibleCenters[0], this.possibleCenters[1], this.possibleCenters[2]);
- }
-
- this.findRowSkip=function()
- {
- var max = this.possibleCenters.length;
- if (max <= 1)
- {
- return 0;
- }
- var firstConfirmedCenter = null;
- for (var i = 0; i < max; i++)
- {
- var center = this.possibleCenters[i];
- if (center.Count >= CENTER_QUORUM)
- {
- if (firstConfirmedCenter == null)
- {
- firstConfirmedCenter = center;
- }
- else
- {
- // We have two confirmed centers
- // How far down can we skip before resuming looking for the next
- // pattern? In the worst case, only the difference between the
- // difference in the x / y coordinates of the two centers.
- // This is the case where you find top left last.
- this.hasSkipped = true;
- return Math.floor ((Math.abs(firstConfirmedCenter.X - center.X) - Math.abs(firstConfirmedCenter.Y - center.Y)) / 2);
- }
- }
- }
- return 0;
- }
-
- this.haveMultiplyConfirmedCenters=function()
- {
- var confirmedCount = 0;
- var totalModuleSize = 0.0;
- var max = this.possibleCenters.length;
- for (var i = 0; i < max; i++)
- {
- var pattern = this.possibleCenters[i];
- if (pattern.Count >= CENTER_QUORUM)
- {
- confirmedCount++;
- totalModuleSize += pattern.EstimatedModuleSize;
- }
- }
- if (confirmedCount < 3)
- {
- return false;
- }
- // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
- // and that we need to keep looking. We detect this by asking if the estimated module sizes
- // vary too much. We arbitrarily say that when the total deviation from average exceeds
- // 5% of the total module size estimates, it's too much.
- var average = totalModuleSize / max;
- var totalDeviation = 0.0;
- for (var i = 0; i < max; i++)
- {
- pattern = this.possibleCenters[i];
- totalDeviation += Math.abs(pattern.EstimatedModuleSize - average);
- }
- return totalDeviation <= 0.05 * totalModuleSize;
- }
-
- this.findFinderPattern = function(image){
- var tryHarder = false;
- this.image=image;
- var maxI = qrcode.height;
- var maxJ = qrcode.width;
- var iSkip = Math.floor((3 * maxI) / (4 * MAX_MODULES));
- if (iSkip < MIN_SKIP || tryHarder)
- {
- iSkip = MIN_SKIP;
- }
-
- var done = false;
- var stateCount = new Array(5);
- for (var i = iSkip - 1; i < maxI && !done; i += iSkip)
- {
- // Get a row of black/white values
- stateCount[0] = 0;
- stateCount[1] = 0;
- stateCount[2] = 0;
- stateCount[3] = 0;
- stateCount[4] = 0;
- var currentState = 0;
- for (var j = 0; j < maxJ; j++)
- {
- if (image[j+i*qrcode.width] )
- {
- // Black pixel
- if ((currentState & 1) == 1)
- {
- // Counting white pixels
- currentState++;
- }
- stateCount[currentState]++;
- }
- else
- {
- // White pixel
- if ((currentState & 1) == 0)
- {
- // Counting black pixels
- if (currentState == 4)
- {
- // A winner?
- if (this.foundPatternCross(stateCount))
- {
- // Yes
- var confirmed = this.handlePossibleCenter(stateCount, i, j);
- if (confirmed)
- {
- // Start examining every other line. Checking each line turned out to be too
- // expensive and didn't improve performance.
- iSkip = 2;
- if (this.hasSkipped)
- {
- done = this.haveMultiplyConfirmedCenters();
- }
- else
- {
- var rowSkip = this.findRowSkip();
- if (rowSkip > stateCount[2])
- {
- // Skip rows between row of lower confirmed center
- // and top of presumed third confirmed center
- // but back up a bit to get a full chance of detecting
- // it, entire width of center of finder pattern
-
- // Skip by rowSkip, but back off by stateCount[2] (size of last center
- // of pattern we saw) to be conservative, and also back off by iSkip which
- // is about to be re-added
- i += rowSkip - stateCount[2] - iSkip;
- j = maxJ - 1;
- }
- }
- }
- else
- {
- // Advance to next black pixel
- do
- {
- j++;
- }
- while (j < maxJ && !image[j + i*qrcode.width]);
- j--; // back up to that last white pixel
- }
- // Clear state to start looking again
- currentState = 0;
- stateCount[0] = 0;
- stateCount[1] = 0;
- stateCount[2] = 0;
- stateCount[3] = 0;
- stateCount[4] = 0;
- }
- else
- {
- // No, shift counts back by two
- stateCount[0] = stateCount[2];
- stateCount[1] = stateCount[3];
- stateCount[2] = stateCount[4];
- stateCount[3] = 1;
- stateCount[4] = 0;
- currentState = 3;
- }
- }
- else
- {
- stateCount[++currentState]++;
- }
- }
- else
- {
- // Counting white pixels
- stateCount[currentState]++;
- }
- }
- }
- if (this.foundPatternCross(stateCount))
- {
- var confirmed = this.handlePossibleCenter(stateCount, i, maxJ);
- if (confirmed)
- {
- iSkip = stateCount[0];
- if (this.hasSkipped)
- {
- // Found a third one
- done = this.haveMultiplyConfirmedCenters();
- }
- }
- }
- }
-
- var patternInfo = this.selectBestPatterns();
- qrcode.orderBestPatterns(patternInfo);
-
- return new FinderPatternInfo(patternInfo);
- };
- }
|