#include "HybridBinarizer.h"
#include "BitMatrix.h"
#include "Matrix.h"
#include <algorithm>
#include <cstdint>
#include <fstream>
#include <memory>
#define USE_NEW_ALGORITHM
namespace ZXing {
static constexpr int BLOCK_SIZE = 8;
static constexpr int WINDOW_SIZE = BLOCK_SIZE * (1 + 2 * 2);
static constexpr int MIN_DYNAMIC_RANGE = 24;
HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer(iv) {}
HybridBinarizer::~HybridBinarizer() = default;
bool HybridBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const
{
#if 1
return GlobalHistogramBinarizer::getPatternRow(row, rotation, res);
#else#endif
}
using T_t = uint8_t;
static void ThresholdBlock(const uint8_t* __restrict luminances, int xoffset, int yoffset, T_t threshold, int rowStride,
BitMatrix& matrix)
{
for (int y = yoffset; y < yoffset + BLOCK_SIZE; ++y) {
auto* src = luminances + y * rowStride + xoffset;
auto* const dstBegin = matrix.row(y).begin() + xoffset;
for (auto* dst = dstBegin; dst < dstBegin + BLOCK_SIZE; ++dst, ++src)
*dst = (*src <= threshold) * BitMatrix::SET_V;
}
}
#ifndef USE_NEW_ALGORITHM
static Matrix<T_t> CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height,
int rowStride)
{
Matrix<T_t> blackPoints(subWidth, subHeight);
for (int y = 0; y < subHeight; y++) {
int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE);
for (int x = 0; x < subWidth; x++) {
int xoffset = std::min(x * BLOCK_SIZE, width - BLOCK_SIZE);
int sum = 0;
uint8_t min = luminances[yoffset * rowStride + xoffset];
uint8_t max = min;
for (int yy = 0, offset = yoffset * rowStride + xoffset; yy < BLOCK_SIZE; yy++, offset += rowStride) {
for (int xx = 0; xx < BLOCK_SIZE; xx++) {
auto pixel = luminances[offset + xx];
sum += pixel;
if (pixel < min)
min = pixel;
if (pixel > max)
max = pixel;
}
if (max - min > MIN_DYNAMIC_RANGE) {
for (yy++, offset += rowStride; yy < BLOCK_SIZE; yy++, offset += rowStride) {
for (int xx = 0; xx < BLOCK_SIZE; xx++) {
sum += luminances[offset + xx];
}
}
}
}
int average = sum / (BLOCK_SIZE * BLOCK_SIZE);
if (max - min <= MIN_DYNAMIC_RANGE) {
average = min / 2;
if (y > 0 && x > 0) {
int averageNeighborBlackPoint =
(blackPoints(x, y - 1) + (2 * blackPoints(x - 1, y)) + blackPoints(x - 1, y - 1)) / 4;
if (min < averageNeighborBlackPoint) {
average = averageNeighborBlackPoint;
}
}
}
blackPoints(x, y) = average;
}
}
return blackPoints;
}
static std::shared_ptr<BitMatrix> CalculateMatrix(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width,
int height, int rowStride, const Matrix<T_t>& blackPoints)
{
auto matrix = std::make_shared<BitMatrix>(width, height);
#ifdef PRINT_DEBUG
Matrix<uint8_t> out(width, height);
Matrix<uint8_t> out2(width, height);
#endif
for (int y = 0; y < subHeight; y++) {
int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE);
for (int x = 0; x < subWidth; x++) {
int xoffset = std::min(x * BLOCK_SIZE, width - BLOCK_SIZE);
int left = std::clamp(x, 2, subWidth - 3);
int top = std::clamp(y, 2, subHeight - 3);
int sum = 0;
for (int dy = -2; dy <= 2; ++dy) {
for (int dx = -2; dx <= 2; ++dx) {
sum += blackPoints(left + dx, top + dy);
}
}
int average = sum / 25;
ThresholdBlock(luminances, xoffset, yoffset, average, rowStride, *matrix);
#ifdef PRINT_DEBUG
for (int yy = 0; yy < 8; ++yy)
for (int xx = 0; xx < 8; ++xx) {
out.set(xoffset + xx, yoffset + yy, blackPoints(x, y));
out2.set(xoffset + xx, yoffset + yy, average);
}
#endif
}
}
#ifdef PRINT_DEBUG
std::ofstream file("thresholds.pnm");
file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n";
file.write(reinterpret_cast<const char*>(out.data()), out.size());
std::ofstream file2("thresholds_avg.pnm");
file2 << "P5\n" << out.width() << ' ' << out.height() << "\n255\n";
file2.write(reinterpret_cast<const char*>(out2.data()), out2.size());
#endif
return matrix;
}
#else
static Matrix<T_t> BlockThresholds(const ImageView iv)
{
int subWidth = (iv.width() + BLOCK_SIZE - 1) / BLOCK_SIZE; int subHeight = (iv.height() + BLOCK_SIZE - 1) / BLOCK_SIZE;
Matrix<T_t> thresholds(subWidth, subHeight);
for (int y = 0; y < subHeight; y++) {
int y0 = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE);
for (int x = 0; x < subWidth; x++) {
int x0 = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE);
uint8_t min = 255;
uint8_t max = 0;
for (int yy = 0; yy < BLOCK_SIZE; yy++) {
auto line = iv.data(x0, y0 + yy);
for (int xx = 0; xx < BLOCK_SIZE; xx++)
UpdateMinMax(min, max, line[xx]);
}
thresholds(x, y) = (max - min > MIN_DYNAMIC_RANGE) ? (int(max) + min) / 2 : 0;
}
}
return thresholds;
}
static Matrix<T_t> SmoothThresholds(Matrix<T_t>&& in)
{
Matrix<T_t> out(in.width(), in.height());
constexpr int R = WINDOW_SIZE / BLOCK_SIZE / 2;
for (int y = 0; y < in.height(); y++) {
for (int x = 0; x < in.width(); x++) {
int left = std::clamp(x, R, in.width() - R - 1);
int top = std::clamp(y, R, in.height() - R - 1);
int sum = in(x, y) * 2;
int n = (sum > 0) * 2;
auto add = [&](int x, int y) {
int t = in(x, y);
sum += t;
n += t > 0;
};
for (int dy = -R; dy <= R; ++dy)
for (int dx = -R; dx <= R; ++dx)
add(left + dx, top + dy);
out(x, y) = n > 0 ? sum / n : 0;
}
}
auto last = out.begin() - 1;
for (auto* i = out.begin(); i != out.end(); ++i) {
if (*i) {
if (last != i - 1)
std::fill(last + 1, i, *i);
last = i;
}
}
std::fill(last + 1, out.end(), *(std::max(last, out.begin())));
return out;
}
static std::shared_ptr<BitMatrix> ThresholdImage(const ImageView iv, const Matrix<T_t>& thresholds)
{
auto matrix = std::make_shared<BitMatrix>(iv.width(), iv.height());
#ifdef PRINT_DEBUG
Matrix<uint8_t> out(iv.width(), iv.height());
#endif
for (int y = 0; y < thresholds.height(); y++) {
int yoffset = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE);
for (int x = 0; x < thresholds.width(); x++) {
int xoffset = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE);
ThresholdBlock(iv.data(), xoffset, yoffset, thresholds(x, y), iv.rowStride(), *matrix);
#ifdef PRINT_DEBUG
for (int yy = 0; yy < 8; ++yy)
for (int xx = 0; xx < 8; ++xx)
out.set(xoffset + xx, yoffset + yy, thresholds(x, y));
#endif
}
}
#ifdef PRINT_DEBUG
std::ofstream file("thresholds_new.pnm");
file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n";
file.write(reinterpret_cast<const char*>(out.data()), out.size());
#endif
return matrix;
}
#endif
std::shared_ptr<const BitMatrix> HybridBinarizer::getBlackMatrix() const
{
if (width() >= WINDOW_SIZE && height() >= WINDOW_SIZE) {
#ifdef USE_NEW_ALGORITHM
auto thrs = SmoothThresholds(BlockThresholds(_buffer));
return ThresholdImage(_buffer, thrs);
#else
const uint8_t* luminances = _buffer.data();
int subWidth = (width() + BLOCK_SIZE - 1) / BLOCK_SIZE; int subHeight = (height() + BLOCK_SIZE - 1) / BLOCK_SIZE; auto blackPoints =
CalculateBlackPoints(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride());
return CalculateMatrix(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride(), blackPoints);
#endif
} else {
return GlobalHistogramBinarizer::getBlackMatrix();
}
}
}