// -*- mode:c++; tab-width:2; indent-tabs-mode:nil; c-basic-offset:2 -*- /* * HybridBinarizer.cpp * zxing * * Copyright 2010 ZXing authors All rights reserved. * * 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. */ #include #include using namespace std; using namespace zxing; namespace { const int BLOCK_SIZE_POWER = 3; const int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00 const int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11 const int MINIMUM_DIMENSION = BLOCK_SIZE * 5; } HybridBinarizer::HybridBinarizer(Ref source) : GlobalHistogramBinarizer(source), matrix_(NULL), cached_row_(NULL) { } HybridBinarizer::~HybridBinarizer() { } Ref HybridBinarizer::createBinarizer(Ref source) { return Ref (new HybridBinarizer(source)); } /** * Calculates the final BitMatrix once for all requests. This could be called once from the * constructor instead, but there are some advantages to doing it lazily, such as making * profiling easier, and not doing heavy lifting when callers don't expect it. */ Ref HybridBinarizer::getBlackMatrix() { if (matrix_) { return matrix_; } LuminanceSource& source = *getLuminanceSource(); int width = source.getWidth(); int height = source.getHeight(); if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) { ArrayRef luminances = source.getMatrix(); int subWidth = width >> BLOCK_SIZE_POWER; if ((width & BLOCK_SIZE_MASK) != 0) { subWidth++; } int subHeight = height >> BLOCK_SIZE_POWER; if ((height & BLOCK_SIZE_MASK) != 0) { subHeight++; } ArrayRef blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height); Ref newMatrix (new BitMatrix(width, height)); calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, newMatrix); matrix_ = newMatrix; } else { // If the image is too small, fall back to the global histogram approach. matrix_ = GlobalHistogramBinarizer::getBlackMatrix(); } return matrix_; } namespace { inline int cap(int value, int min, int max) { return value < min ? min : value > max ? max : value; } } void HybridBinarizer::calculateThresholdForBlock(ArrayRef luminances, int subWidth, int subHeight, int width, int height, ArrayRef blackPoints, Ref const& matrix) { for (int y = 0; y < subHeight; y++) { int yoffset = y << BLOCK_SIZE_POWER; int maxYOffset = height - BLOCK_SIZE; if (yoffset > maxYOffset) { yoffset = maxYOffset; } for (int x = 0; x < subWidth; x++) { int xoffset = x << BLOCK_SIZE_POWER; int maxXOffset = width - BLOCK_SIZE; if (xoffset > maxXOffset) { xoffset = maxXOffset; } int left = cap(x, 2, subWidth - 3); int top = cap(y, 2, subHeight - 3); int sum = 0; for (int z = -2; z <= 2; z++) { int *blackRow = &blackPoints[(top + z) * subWidth]; sum += blackRow[left - 2]; sum += blackRow[left - 1]; sum += blackRow[left]; sum += blackRow[left + 1]; sum += blackRow[left + 2]; } int average = sum / 25; thresholdBlock(luminances, xoffset, yoffset, average, width, matrix); } } } void HybridBinarizer::thresholdBlock(ArrayRef luminances, int xoffset, int yoffset, int threshold, int stride, Ref const& matrix) { for (int y = 0, offset = yoffset * stride + xoffset; y < BLOCK_SIZE; y++, offset += stride) { for (int x = 0; x < BLOCK_SIZE; x++) { int pixel = luminances[offset + x] & 0xff; if (pixel <= threshold) { matrix->set(xoffset + x, yoffset + y); } } } } namespace { inline int getBlackPointFromNeighbors(ArrayRef blackPoints, int subWidth, int x, int y) { return (blackPoints[(y-1)*subWidth+x] + 2*blackPoints[y*subWidth+x-1] + blackPoints[(y-1)*subWidth+x-1]) >> 2; } } ArrayRef HybridBinarizer::calculateBlackPoints(ArrayRef luminances, int subWidth, int subHeight, int width, int height) { const int minDynamicRange = 24; ArrayRef blackPoints (subHeight * subWidth); for (int y = 0; y < subHeight; y++) { int yoffset = y << BLOCK_SIZE_POWER; int maxYOffset = height - BLOCK_SIZE; if (yoffset > maxYOffset) { yoffset = maxYOffset; } for (int x = 0; x < subWidth; x++) { int xoffset = x << BLOCK_SIZE_POWER; int maxXOffset = width - BLOCK_SIZE; if (xoffset > maxXOffset) { xoffset = maxXOffset; } int sum = 0; int min = 0xFF; int max = 0; for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width) { for (int xx = 0; xx < BLOCK_SIZE; xx++) { int pixel = luminances[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 > minDynamicRange) { // finish the rest of the rows quickly for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width) { for (int xx = 0; xx < BLOCK_SIZE; xx += 2) { sum += luminances[offset + xx] & 0xFF; sum += luminances[offset + xx + 1] & 0xFF; } } } } // See // http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 int average = sum >> (BLOCK_SIZE_POWER * 2); if (max - min <= minDynamicRange) { average = min >> 1; if (y > 0 && x > 0) { int bp = getBlackPointFromNeighbors(blackPoints, subWidth, x, y); if (min < bp) { average = bp; } } } blackPoints[y * subWidth + x] = average; } } return blackPoints; }