Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support QR Code Model1 #633

Merged
merged 9 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
69 changes: 66 additions & 3 deletions core/src/qrcode/QRBitMatrixParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,66 @@ static ByteArray ReadQRCodewords(const BitMatrix& bitMatrix, const Version& vers
return result;
}

static ByteArray ReadQRCodewordsModel1(const BitMatrix& bitMatrix, const Version& version, const FormatInformation& formatInfo)
{

ByteArray result;
result.reserve(version.totalCodewords());
int dimension = bitMatrix.height();
int columns = dimension / 4 + 1 + 2;
for (int j = 0; j < columns; j++) {
if (j <= 1) { // vertical symbols on the right side
int rows = (dimension - 8) / 4;
for (int i = 0; i < rows; i++) {
if (j == 0 && i % 2 == 0 && i > 0 && i < rows - 1) // extension
continue;
int x = (dimension - 1) - (j * 2);
int y = (dimension - 1) - (i * 4);
uint8_t currentByte = 0;
for (int b = 0; b < 8; b++) {
AppendBit(currentByte, GetDataMaskBit(formatInfo.dataMask, x - b % 2, y - (b / 2))
!= getBit(bitMatrix, x - b % 2, y - (b / 2), formatInfo.isMirrored));
}
result.push_back(currentByte);
}
} else if (columns - j <= 4) { // vertical symbols on the left side
int rows = (dimension - 16) / 4;
for (int i = 0; i < rows; i++) {
int x = (columns - j - 1) * 2 + 1 + (columns - j == 4 ? 1 : 0); // timing
int y = (dimension - 1) - 8 - (i * 4);
uint8_t currentByte = 0;
for (int b = 0; b < 8; b++) {
AppendBit(currentByte, GetDataMaskBit(formatInfo.dataMask, x - b % 2, y - (b / 2))
!= getBit(bitMatrix, x - b % 2, y - (b / 2), formatInfo.isMirrored));
}
result.push_back(currentByte);
}
} else { // horizontal symbols
int rows = dimension / 2;
for (int i = 0; i < rows; i++) {
if (j == 2 && i >= rows - 4) // alignment & finder
continue;
if (i == 0 && j % 2 == 1 && j + 1 != columns - 4) // extension
continue;
int x = (dimension - 1) - (2 * 2) - (j - 2) * 4;
int y = (dimension - 1) - (i * 2) - (i >= rows - 3 ? 1 : 0); // timing
uint8_t currentByte = 0;
for (int b = 0; b < 8; b++) {
AppendBit(currentByte, GetDataMaskBit(formatInfo.dataMask, x - b % 4, y - (b / 4))
!= getBit(bitMatrix, x - b % 4, y - (b / 4), formatInfo.isMirrored));
}
result.push_back(currentByte);
}
}
}

result[0] &= 0xf; // ignore corner
if (Size(result) != version.totalCodewords())
return {};

return result;
}

static ByteArray ReadMQRCodewords(const BitMatrix& bitMatrix, const QRCode::Version& version, const FormatInformation& formatInfo)
{
BitMatrix functionPattern = version.buildFunctionPattern();
Expand Down Expand Up @@ -185,9 +245,12 @@ ByteArray ReadCodewords(const BitMatrix& bitMatrix, const Version& version, cons
{
if (!hasValidDimension(bitMatrix, version.isMicroQRCode()))
return {};

return version.isMicroQRCode() ? ReadMQRCodewords(bitMatrix, version, formatInfo)
: ReadQRCodewords(bitMatrix, version, formatInfo);
if (version.isMicroQRCode())
return ReadMQRCodewords(bitMatrix, version, formatInfo);
else if (formatInfo.isModel1)
return ReadQRCodewordsModel1(bitMatrix, version, formatInfo);
else
return ReadQRCodewords(bitMatrix, version, formatInfo);
}

} // namespace ZXing::QRCode
15 changes: 10 additions & 5 deletions core/src/qrcode/QRDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo
StructuredAppendInfo structuredAppend;
const int modeBitLength = CodecModeBitsLength(version);

if (version.isQRCodeModel1())
bits.readBits(4); // Model 1 is leading with 4 0-bits -> drop them

try
{
while(!IsEndOfStream(bits, version)) {
Expand Down Expand Up @@ -316,14 +319,16 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo

DecoderResult Decode(const BitMatrix& bits)
{
const Version* pversion = ReadVersion(bits);
bool isMicroQRCode = bits.height() < 21;
auto formatInfo = ReadFormatInformation(bits, isMicroQRCode);
if (!formatInfo.isValid())
return FormatError("Invalid format information");

const Version* pversion = formatInfo.isModel1 ? Version::FromDimension(bits.height(), true) : ReadVersion(bits);
if (!pversion)
return FormatError("Invalid version");
const Version& version = *pversion;

auto formatInfo = ReadFormatInformation(bits, version.isMicroQRCode());
if (!formatInfo.isValid())
return FormatError("Invalid format information");
const Version& version = *pversion;

// Read codewords
ByteArray codewords = ReadCodewords(bits, version, formatInfo);
Expand Down
19 changes: 14 additions & 5 deletions core/src/qrcode/QRFormatInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace ZXing::QRCode {

static const int FORMAT_INFO_MASK_QR = 0x5412;

static const int FORMAT_INFO_MASK_QR_MODEL1 = 0x2825;

/**
* See ISO 18004:2006, Annex C, Table C.1
*/
Expand Down Expand Up @@ -93,13 +95,12 @@ static uint32_t MirrorBits(uint32_t bits)
return BitHacks::Reverse(bits) >> 17;
}

static FormatInformation FindBestFormatInfo(int mask, const std::array<std::pair<int, int>, 32> lookup,
static FormatInformation FindBestFormatInfo(const std::vector<uint32_t>& masks, const std::array<std::pair<int, int>, 32> lookup,
const std::vector<uint32_t>& bits)
{
FormatInformation fi;

// Some QR codes apparently do not apply the XOR mask. Try without and with additional masking.
for (auto mask : {0, mask})
for (auto mask : masks)
for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex)
for (const auto& [pattern, index] : lookup) {
// Find the int in lookup with fewest bits differing
Expand All @@ -122,8 +123,16 @@ FormatInformation FormatInformation::DecodeQR(uint32_t formatInfoBits1, uint32_t
// maks out the 'Dark Module' for mirrored and non-mirrored case (see Figure 25 in ISO/IEC 18004:2015)
uint32_t mirroredFormatInfoBits2 = MirrorBits(((formatInfoBits2 >> 1) & 0b111111110000000) | (formatInfoBits2 & 0b1111111));
formatInfoBits2 = ((formatInfoBits2 >> 1) & 0b111111100000000) | (formatInfoBits2 & 0b11111111);
auto fi = FindBestFormatInfo(FORMAT_INFO_MASK_QR, FORMAT_INFO_DECODE_LOOKUP,
// Some QR codes apparently do not apply the XOR mask. Try without and with additional masking.
auto fi = FindBestFormatInfo({0, FORMAT_INFO_MASK_QR}, FORMAT_INFO_DECODE_LOOKUP,
{formatInfoBits1, formatInfoBits2, MirrorBits(formatInfoBits1), mirroredFormatInfoBits2});
auto fi_model1 = FindBestFormatInfo({FORMAT_INFO_MASK_QR ^ FORMAT_INFO_MASK_QR_MODEL1}, FORMAT_INFO_DECODE_LOOKUP,
{formatInfoBits1, formatInfoBits2, MirrorBits(formatInfoBits1), mirroredFormatInfoBits2});

if (fi_model1.hammingDistance < fi.hammingDistance) {
fi_model1.isModel1 = true;
fi = fi_model1;
}

// Use bits 3/4 for error correction, and 0-2 for mask.
fi.ecLevel = ECLevelFromBits((fi.index >> 3) & 0x03);
Expand All @@ -139,7 +148,7 @@ FormatInformation FormatInformation::DecodeQR(uint32_t formatInfoBits1, uint32_t
FormatInformation FormatInformation::DecodeMQR(uint32_t formatInfoBits)
{
// We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders
auto fi = FindBestFormatInfo(0, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)});
auto fi = FindBestFormatInfo({0}, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)});

constexpr uint8_t BITS_TO_VERSION[] = {1, 2, 2, 3, 3, 4, 4, 4};

Expand Down
1 change: 1 addition & 0 deletions core/src/qrcode/QRFormatInformation.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class FormatInformation
uint8_t index = 255;
uint8_t hammingDistance = 255;
bool isMirrored = false;
bool isModel1 = false;
uint8_t dataMask = 0;
uint8_t microVersion = 0;
uint8_t bitsIndex = 255;
Expand Down
115 changes: 108 additions & 7 deletions core/src/qrcode/QRVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,35 +292,136 @@ const Version* Version::AllMicroVersions()
return allVersions;
}

const Version* Version::AllModel1Versions()
{
/**
* See ISO 18004:2000 M.4.2 Table M.2
* See ISO 18004:2000 M.5 Table M.4
*/
static const Version allVersions[] = {
{1, {
7 , 1, 19, 0, 0,
10, 1, 16, 0, 0,
13, 1, 13, 0, 0,
17, 1, 9 , 0, 0
}},
{2, {
10, 1, 36, 0, 0,
16, 1, 30, 0, 0,
22, 1, 24, 0, 0,
30, 1, 16, 0, 0,
}},
{3, {
15, 1, 57, 0, 0,
28, 1, 44, 0, 0,
36, 1, 36, 0, 0,
48, 1, 24, 0, 0,
}},
{4, {
20, 1, 80, 0, 0,
40, 1, 60, 0, 0,
50, 1, 50, 0, 0,
66, 1, 34, 0, 0,
}},
{5, {
26, 1, 108, 0, 0,
52, 1, 82 , 0, 0,
66, 1, 68 , 0, 0,
88, 2, 46 , 0, 0,
}},
{6, {
34 , 1, 136, 0, 0,
63 , 2, 106, 0, 0,
84 , 2, 86 , 0, 0,
112, 2, 58 , 0, 0,
}},
{7, {
42 , 1, 170, 0, 0,
80 , 2, 132, 0, 0,
104, 2, 108, 0, 0,
138, 3, 72 , 0, 0,
}},
{8, {
48 , 2, 208, 0, 0,
96 , 2, 160, 0, 0,
128, 2, 128, 0, 0,
168, 3, 87 , 0, 0,
}},
{9, {
60 , 2, 246, 0, 0,
120, 2, 186, 0, 0,
150, 3, 156, 0, 0,
204, 3, 102, 0, 0,
}},
{10, {
68 , 2, 290, 0, 0,
136, 2, 222, 0, 0,
174, 3, 183, 0, 0,
232, 4, 124, 0, 0,
}},
{11, {
80 , 2, 336, 0, 0,
160, 4, 256, 0, 0,
208, 4, 208, 0, 0,
270, 5, 145, 0, 0,
}},
{12, {
92 , 2, 384, 0, 0,
184, 4, 292, 0, 0,
232, 4, 244, 0, 0,
310, 5, 165, 0, 0,
}},
{13, {
108, 3, 432, 0, 0,
208, 4, 332, 0, 0,
264, 4, 276, 0, 0,
348, 6, 192, 0, 0,
}},
{14, {
120, 3, 489, 0, 0,
240, 4, 368, 0, 0,
300, 5, 310, 0, 0,
396, 6, 210, 0, 0,
}},
};
return allVersions;
}

static inline bool isMicro(const std::array<ECBlocks, 4>& ecBlocks)
{
return ecBlocks[0].codewordsPerBlock < 7 || ecBlocks[0].codewordsPerBlock == 8;
}

Version::Version(int versionNumber, std::initializer_list<int> alignmentPatternCenters, const std::array<ECBlocks, 4>& ecBlocks)
: _versionNumber(versionNumber), _alignmentPatternCenters(alignmentPatternCenters), _ecBlocks(ecBlocks), _isMicro(false)
: _versionNumber(versionNumber), _alignmentPatternCenters(alignmentPatternCenters), _ecBlocks(ecBlocks), _isMicro(false), _isModel1(false)
{
_totalCodewords = ecBlocks[0].totalDataCodewords();
}

Version::Version(int versionNumber, const std::array<ECBlocks, 4>& ecBlocks)
: _versionNumber(versionNumber), _ecBlocks(ecBlocks), _isMicro(true)
: _versionNumber(versionNumber), _ecBlocks(ecBlocks), _isMicro(isMicro(ecBlocks)), _isModel1(!isMicro(ecBlocks))
{
_totalCodewords = ecBlocks[0].totalDataCodewords();
}

const Version* Version::FromNumber(int versionNumber, bool isMicro)
const Version* Version::FromNumber(int versionNumber, bool isMicro, bool isModel1)
{
if (versionNumber < 1 || versionNumber > (isMicro ? 4 : 40)) {
if (versionNumber < 1 || versionNumber > (isMicro ? 4 : (isModel1 ? 14 : 40))) {
//throw std::invalid_argument("Version should be in range [1-40].");
return nullptr;
}
return &(isMicro ? AllMicroVersions() : AllVersions())[versionNumber - 1];

return &(isMicro ? AllMicroVersions() : (isModel1 ? AllModel1Versions() : AllVersions()))[versionNumber - 1];
}

const Version* Version::FromDimension(int dimension)
const Version* Version::FromDimension(int dimension, bool isModel1)
{
bool isMicro = dimension < 21;
if (dimension % DimensionStep(isMicro) != 1) {
//throw std::invalid_argument("Unexpected dimension");
return nullptr;
}
return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro);
return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro, isModel1);
}

const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBitsB)
Expand Down
7 changes: 5 additions & 2 deletions core/src/qrcode/QRVersion.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Version
BitMatrix buildFunctionPattern() const;

bool isMicroQRCode() const { return _isMicro; }
bool isQRCodeModel1() const { return _isModel1; }

static constexpr int DimensionStep(bool isMicro) { return std::array{4, 2}[isMicro]; }
static constexpr int DimensionOffset(bool isMicro) { return std::array{17, 9}[isMicro]; }
Expand All @@ -54,9 +55,9 @@ class Version
* @param dimension dimension in modules
* @return Version for a QR Code of that dimension
*/
static const Version* FromDimension(int dimension);
static const Version* FromDimension(int dimension, bool isModel1 = false);

static const Version* FromNumber(int versionNumber, bool isMicro = false);
static const Version* FromNumber(int versionNumber, bool isMicro = false, bool isModel1 = false);

static const Version* DecodeVersionInformation(int versionBitsA, int versionBitsB = 0);

Expand All @@ -66,11 +67,13 @@ class Version
std::array<ECBlocks, 4> _ecBlocks;
int _totalCodewords;
bool _isMicro;
bool _isModel1;

Version(int versionNumber, std::initializer_list<int> alignmentPatternCenters, const std::array<ECBlocks, 4> &ecBlocks);
Version(int versionNumber, const std::array<ECBlocks, 4>& ecBlocks);
static const Version* AllVersions();
static const Version* AllMicroVersions();
static const Version* AllModel1Versions();
};

} // QRCode
Expand Down
12 changes: 6 additions & 6 deletions test/blackbox/BlackboxTestRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,12 +562,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set<std::string>
{ 16, 16, 270 },
});

runTests("qrcode-2", "QRCode", 49, {
{ 45, 47, 0 },
{ 45, 47, 90 },
{ 45, 47, 180 },
{ 45, 47, 270 },
{ 21, 1, pure }, // the misread is the 'outer' symbol in 16.png
runTests("qrcode-2", "QRCode", 50, {
{ 46, 48, 0 },
{ 46, 48, 90 },
{ 46, 48, 180 },
{ 46, 48, 270 },
{ 22, 1, pure }, // the misread is the 'outer' symbol in 16.png
});

runTests("qrcode-3", "QRCode", 28, {
Expand Down
Binary file added test/samples/qrcode-2/qr-model-1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions test/samples/qrcode-2/qr-model-1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
QR Code Model 1