/** * Calculates a single black point for each block of pixels and saves it away. * See the following thread for a discussion of this algorithm: * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 */ private static function calculateBlackPoints($luminances, $subWidth, $subHeight, $width, $height) { $blackPoints = fill_array(0, $subHeight, 0); foreach ($blackPoints as $key => $point) { $blackPoints[$key] = fill_array(0, $subWidth, 0); } for ($y = 0; $y < $subHeight; $y++) { $yoffset = intval32bits($y << self::$BLOCK_SIZE_POWER); $maxYOffset = $height - self::$BLOCK_SIZE; if ($yoffset > $maxYOffset) { $yoffset = $maxYOffset; } for ($x = 0; $x < $subWidth; $x++) { $xoffset = intval32bits($x << self::$BLOCK_SIZE_POWER); $maxXOffset = $width - self::$BLOCK_SIZE; if ($xoffset > $maxXOffset) { $xoffset = $maxXOffset; } $sum = 0; $min = 0xff; $max = 0; for ($yy = 0, $offset = $yoffset * $width + $xoffset; $yy < self::$BLOCK_SIZE; $yy++, $offset += $width) { for ($xx = 0; $xx < self::$BLOCK_SIZE; $xx++) { $pixel = intval32bits(intval($luminances[intval($offset + $xx)]) & 0xff); $sum += $pixel; // still looking for good contrast if ($pixel < $min) { $min = $pixel; } if ($pixel > $max) { $max = $pixel; } } // short-circuit min/max tests once dynamic range is met if ($max - $min > self::$MIN_DYNAMIC_RANGE) { // finish the rest of the rows quickly for ($yy++, $offset += $width; $yy < self::$BLOCK_SIZE; $yy++, $offset += $width) { for ($xx = 0; $xx < self::$BLOCK_SIZE; $xx++) { $sum += intval32bits($luminances[$offset + $xx] & 0xff); } } } } // The default estimate is the average of the values in the block. $average = intval32bits($sum >> self::$BLOCK_SIZE_POWER * 2); if ($max - $min <= self::$MIN_DYNAMIC_RANGE) { // If variation within the block is low, assume this is a block with only light or only // dark pixels. In that case we do not want to use the average, as it would divide this // low contrast area into black and white pixels, essentially creating data out of noise. // // The default assumption is that the block is light/background. Since no estimate for // the level of dark pixels exists locally, use half the min for the block. $average = intval($min / 2); if ($y > 0 && $x > 0) { // Correct the "white background" assumption for blocks that have neighbors by comparing // the pixels in this block to the previously calculated black points. This is based on // the fact that dark barcode symbology is always surrounded by some amount of light // background for which reasonable black point estimates were made. The bp estimated at // the boundaries is used for the interior. // The (min < bp) is arbitrary but works better than other heuristics that were tried. $averageNeighborBlackPoint = intval(($blackPoints[$y - 1][$x] + 2 * $blackPoints[$y][$x - 1] + $blackPoints[$y - 1][$x - 1]) / 4); if ($min < $averageNeighborBlackPoint) { $average = $averageNeighborBlackPoint; } } } $blackPoints[$y][$x] = intval($average); } } return $blackPoints; }
public function renderThumbnail() { $width = intval($this->getWidth() / self::$THUMBNAIL_SCALE_FACTOR); $height = intval($this->getHeight() / self::$THUMBNAIL_SCALE_FACTOR); $pixels = array(); //new int[width * height]; $yuv = $this->yuvData; $inputOffset = $this->top * $this->dataWidth + $this->left; for ($y = 0; $y < $height; $y++) { $outputOffset = $y * $width; for ($x = 0; $x < $width; $x++) { $grey = intval32bits($yuv[$inputOffset + $x * self::$THUMBNAIL_SCALE_FACTOR] & 0xff); $pixels[$outputOffset + $x] = intval32bits(0xff000000 | $grey * 0x10101); } $inputOffset += $this->dataWidth * self::$THUMBNAIL_SCALE_FACTOR; } return $pixels; }
/** * @param $i; bit to get * @return true iff bit i is set */ public function get($i) { $key = intval($i / 32); return intval32bits($this->bits[$key] & 1 << ($i & 0x1f)) != 0; }
private static function estimateBlackPoint($buckets) { // Find the tallest peak in the histogram. $numBuckets = count($buckets); $maxBucketCount = 0; $firstPeak = 0; $firstPeakSize = 0; for ($x = 0; $x < $numBuckets; $x++) { if ($buckets[$x] > $firstPeakSize) { $firstPeak = $x; $firstPeakSize = $buckets[$x]; } if ($buckets[$x] > $maxBucketCount) { $maxBucketCount = $buckets[$x]; } } // Find the second-tallest peak which is somewhat far from the tallest peak. $secondPeak = 0; $secondPeakScore = 0; for ($x = 0; $x < $numBuckets; $x++) { $distanceToBiggest = $x - $firstPeak; // Encourage more distant second peaks by multiplying by square of distance. $score = $buckets[$x] * $distanceToBiggest * $distanceToBiggest; if ($score > $secondPeakScore) { $secondPeak = $x; $secondPeakScore = $score; } } // Make sure firstPeak corresponds to the black peak. if ($firstPeak > $secondPeak) { $temp = $firstPeak; $firstPeak = $secondPeak; $secondPeak = $temp; } // If there is too little contrast in the image to pick a meaningful black point, throw rather // than waste time trying to decode the image, and risk false positives. if ($secondPeak - $firstPeak <= $numBuckets / 16) { throw NotFoundException::getNotFoundInstance(); } // Find a valley between them that is low and closer to the white peak. $bestValley = $secondPeak - 1; $bestValleyScore = -1; for ($x = $secondPeak - 1; $x > $firstPeak; $x--) { $fromFirst = $x - $firstPeak; $score = $fromFirst * $fromFirst * ($secondPeak - $x) * ($maxBucketCount - $buckets[$x]); if ($score > $bestValleyScore) { $bestValley = $x; $bestValleyScore = $score; } } return intval32bits($bestValley << self::$LUMINANCE_SHIFT); }