Skip to content

Commit

Permalink
[third_party][qrcodegen] remove remaining allocations
Browse files Browse the repository at this point in the history
- an internal codewords array is used to assemble
  the codewords for drawing, instead of using the
  stack
- an version of encodeBinary() that takes pointer
  and length use the module array as scratch space
  to assemble the datawords instead of allocating
  a BitBuffer.
- defer erasing the module array until after we
  assemble the codewords to allow its use as
  temporary storage

Change-Id: Id779319b19bca3e5f49a224d4ec93544038db065
  • Loading branch information
swetland committed Nov 24, 2016
1 parent 0e5022e commit e800bc4
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 72 deletions.
135 changes: 76 additions & 59 deletions third_party/ulib/qrcodegen/include/qrcodegen/qrcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,58 @@ class ReedSolomonGenerator final {
static Error multiply(uint8_t x, uint8_t y, uint8_t& out);
};

class Codebits;
/*
* Helper to iterate over the bits in an array of data and ecc blocks
* The data consists of a set of blocks of data and ecc data.
* The short blocks have a dummy byte after the data and before the ecc
* data that must be skipped.
* The bitstream is built from block1 byte1, block2 byte1, ... blockN byte1,
* block1 byte2, block2 byte2, ... blockN byte2, skipping the dummy byte
* on short blocks, until all bits have been streamed out.
*/

class Codebits {
public:
Codebits(uint8_t* data, size_t blocks, size_t blocklen,
size_t shortblocks, size_t skipbyte) :
data_(data), i_(0), j_(0), imax_(blocklen), jmax_(blocks),
shortblocks_(shortblocks), skip_(skipbyte), mask_(0), bits_(0) {
}
Codebits() {}

size_t maxbits() const {
return (jmax_ * imax_ - shortblocks_) * 8;
}
size_t size() const {
return (jmax_ * imax_ - shortblocks_);
}
bool next() {
while (mask_ == 0) {
if (i_ < imax_) {
if ((i_ != skip_) || (j_ >= shortblocks_)) {
mask_ = 0x80;
bits_ = data_[j_ * imax_ + i_];
}
if (++j_ == jmax_) {
j_ = 0;
++i_;
}
} else {
return false;
}
}
bool res = ((bits_ & mask_) != 0);
mask_ >>= 1;
return res;
}

private:
uint8_t* data_;
size_t i_, j_;
size_t imax_, jmax_, shortblocks_, skip_;
unsigned mask_, bits_;
};


/*
* Represents an immutable square grid of black and white cells for a QR Code symbol, and
Expand Down Expand Up @@ -161,11 +212,16 @@ class QrCode final {
* between modes (such as alphanumeric and binary) to encode text more efficiently.
* This function is considered to be lower level than simply encoding text or binary data.
*/
Error encodeSegments(const std::vector<QrSegment> &segs, Ecc ecl,
int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters
Error encodeSegments(const std::vector<QrSegment> &segs,
Ecc ecl, int minVersion=1, int maxVersion=40,
int mask=-1, bool boostEcl=true);
#endif
#endif

Error encodeBinary(const void* data, size_t datalen,
Ecc ecl=Ecc::LOW, int minVersion=1, int maxVersion=40,
int mask=-1);


private:
int version_;
Expand All @@ -177,8 +233,19 @@ class QrCode final {
static constexpr size_t kMaxHeight = 177;
static constexpr size_t kStride = (kMaxWidth + 7) / 8;

uint8_t module_[kStride * kMaxHeight]; // The modules of this QR Code symbol (false = white, true = black)
uint8_t isfunc_[kStride * kMaxHeight]; // Indicates function modules that are not subjected to masking
static constexpr size_t kMaxCodeWords = 3706;
static constexpr size_t kMaxDataWords = 2956;
static constexpr size_t kMaxBinaryData = 2953;

// The modules of this QR Code symbol (false = white, true = black)
uint8_t module_[kStride * kMaxHeight];

// Indicates function modules that are not subjected to masking
uint8_t isfunc_[kStride * kMaxHeight];

// Assembly buffer
uint8_t codewords_[kMaxCodeWords];
Codebits codebits_;

ReedSolomonGenerator rsg_;

Expand Down Expand Up @@ -231,11 +298,13 @@ class QrCode final {
private:

// Computes the error correction codewords and then calls drawCodewords()
Error drawDataWords(const uint8_t* data, size_t len);
// codebits_ is ready for use on success
Error computeCodewords(const uint8_t* data, size_t len);

// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
// data area of this QR Code symbol. Function modules need to be marked off before this is called.
Error drawCodewords(Codebits& codebits);
// Codewords are provided by codebits_
Error drawCodewords();

// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical
// properties, calling applyMask(m) twice with the same value is equivalent to no change at all.
Expand Down Expand Up @@ -280,56 +349,4 @@ class QrCode final {
static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41];
};


/*
* Helper to iterate over the bits in an array of data and ecc blocks
* The data consists of a set of blocks of data and ecc data.
* The short blocks have a dummy byte after the data and before the ecc
* data that must be skipped.
* The bitstream is built from block1 byte1, block2 byte1, ... blockN byte1,
* block1 byte2, block2 byte2, ... blockN byte2, skipping the dummy byte
* on short blocks, until all bits have been streamed out.
*/

class Codebits {
public:
Codebits(uint8_t* data, size_t blocks, size_t blocklen,
size_t shortblocks, size_t skipbyte) :
data_(data), i_(0), j_(0), imax_(blocklen), jmax_(blocks),
shortblocks_(shortblocks), skip_(skipbyte), mask_(0), bits_(0) {
}

size_t maxbits() const {
return (jmax_ * imax_ - shortblocks_) * 8;
}
size_t size() const {
return (jmax_ * imax_ - shortblocks_);
}
bool next() {
while (mask_ == 0) {
if (i_ < imax_) {
if ((i_ != skip_) || (j_ >= shortblocks_)) {
mask_ = 0x80;
bits_ = data_[j_ * imax_ + i_];
}
if (++j_ == jmax_) {
j_ = 0;
++i_;
}
} else {
return false;
}
}
bool res = ((bits_ & mask_) != 0);
mask_ >>= 1;
return res;
}

private:
const uint8_t* data_;
size_t i_, j_;
const size_t imax_, jmax_, shortblocks_, skip_;
unsigned mask_, bits_;
};

}
127 changes: 114 additions & 13 deletions third_party/ulib/qrcodegen/qrcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,49 @@
* Software.
*/

#include <assert.h>
#include <string.h>

#include <qrcodegen/qrcode.h>

namespace qrcodegen {

class BitBufferFiller {
public:
BitBufferFiller(uint8_t* buffer, size_t len) :
data_(buffer), maxbits_(len * 8), bitlen_(0), valid_(true) {
memset(buffer, 0, len);
}

size_t bitlen() { return bitlen_; }
bool valid() { return valid_; }

void appendBits(uint32_t val, size_t len) {
if ((maxbits_ - bitlen_) < len) {
valid_ = false;
return;
}

for (int i = (int)len - 1; i >= 0; i--) {
data_[bitlen_ >> 3] |= static_cast<uint8_t>(((val >> i) & 1) << (7 - (bitlen_ & 7)));
++bitlen_;
}
}

void appendData(const void* data, size_t len) {
const uint8_t* bytes = static_cast<const uint8_t*>(data);
while (len > 0) {
appendBits(*bytes++, 8);
len--;
}
}

private:
uint8_t* data_;
size_t maxbits_;
size_t bitlen_;
bool valid_;
};

int eccOrdinal(Ecc ecc) {
if (ecc > Ecc::HIGH)
Expand Down Expand Up @@ -119,6 +156,55 @@ Error QrCode::encodeSegments(const std::vector<QrSegment> &segs, Ecc ecl,
#endif
#endif

Error QrCode::encodeBinary(const void* data, size_t datalen, Ecc ecl,
int minVersion, int maxVersion, int mask) {
if (!(1 <= minVersion && minVersion <= maxVersion && maxVersion <= 40) || mask < -1 || mask > 7)
return Error::InvalidArgs;

// Find the minimal version number to use
int version;
size_t sizeBits;
size_t dataUsedBits;
size_t dataCapacityBits;
for (version = minVersion; version <= maxVersion; version++) {
sizeBits = (version < 10) ? 8 : 16;
dataUsedBits = 4 + sizeBits + datalen * 8;
dataCapacityBits = getNumDataCodewords(version, ecl) * 8;
if (dataUsedBits <= dataCapacityBits)
goto match;
}
return Error::OutOfSpace;

match:
// we use the module_ array (which will be erased and
// redrawn in draw() as temporary storage here).
static_assert(sizeof(module_) >= kMaxDataWords, "");

BitBufferFiller bb(module_, kMaxDataWords);

// Header: Mode(4bits) = BYTE(4), Count(16bits) = datalen
bb.appendBits(4, 4);
bb.appendBits(static_cast<uint32_t>(datalen), sizeBits);
bb.appendData(data, datalen);

// Add terminator and pad up to a byte if applicable
size_t leftover = dataCapacityBits - bb.bitlen();
bb.appendBits(0, (leftover > 4) ? 4 : leftover);
bb.appendBits(0, (8 - bb.bitlen() % 8) % 8);

// Pad with alternate bytes until data capacity is reached
for (uint8_t padByte = 0xEC; bb.bitlen() < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
bb.appendBits(padByte, 8);

if (!bb.valid())
return Error::BadData;
if (bb.bitlen() % 8 != 0)
return Error::Internal;

// Create the QR Code symbol
return draw(version, ecl, module_, bb.bitlen() / 8, mask);
}

QrCode::QrCode() : version_(1), size_(21), ecc_(Ecc::LOW) {
}

Expand All @@ -133,14 +219,25 @@ Error QrCode::draw(int ver, Ecc ecl, const uint8_t* data, size_t len, int mask)
size_ = (1 <= ver && ver <= 40 ? ver * 4 + 17 : -1), // Avoid signed overflow undefined behavior
ecc_ = ecl;

Error e;
if ((e = computeCodewords(data, len)))
return e;

// only clear these *after* the computation
// as they may be used as input buffers
memset(module_, 0, sizeof(module_));
memset(isfunc_, 0, sizeof(isfunc_));

// Draw function patterns, draw all codewords, do masking
drawFunctionPatterns();
drawDataWords(data, len);
handleConstructorMasking(mask);

if ((e = drawFunctionPatterns())) {
return e;
}
if ((e = drawCodewords())) {
return e;
}
if ((e = handleConstructorMasking(mask))) {
return e;
}
return Error::None;
}

Expand Down Expand Up @@ -290,7 +387,7 @@ void QrCode::setFunctionModule(int x, int y, bool isBlack) {
}


Error QrCode::drawDataWords(const uint8_t* data, size_t len) {
Error QrCode::computeCodewords(const uint8_t* data, size_t len) {
if (len != static_cast<unsigned int>(getNumDataCodewords(version_, ecc_)))
return Error::InvalidArgs;

Expand All @@ -310,8 +407,10 @@ Error QrCode::drawDataWords(const uint8_t* data, size_t len) {
if ((e = rsg_.init(blockEccLen)))
return e;

uint8_t outdata[fullBlockLen * numBlocks];
uint8_t* outptr = outdata;
if (static_cast<size_t>(fullBlockLen * numBlocks) > sizeof(codewords_))
return Error::Internal;

uint8_t* outptr = codewords_;

for (int i = 0, k = 0; i < numBlocks; i++) {
int blocklen = shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1);
Expand All @@ -328,17 +427,19 @@ Error QrCode::drawDataWords(const uint8_t* data, size_t len) {
k += blocklen;
}

Codebits codebits(outdata, numBlocks, fullBlockLen, numShortBlocks, shortBlockLen - blockEccLen);
Codebits codebits(codewords_, numBlocks, fullBlockLen, numShortBlocks, shortBlockLen - blockEccLen);
codebits_ = codebits;

return drawCodewords(codebits);
return Error::None;
}


Error QrCode::drawCodewords(Codebits& codebits) {
if (codebits.size() != static_cast<unsigned int>(getNumRawDataModules(version_) / 8))
Error QrCode::drawCodewords() {
if (codebits_.size() != static_cast<unsigned int>(getNumRawDataModules(version_) / 8))
return Error::InvalidArgs;

size_t count = codebits.maxbits();
size_t count = codebits_.maxbits();

// Do the funny zigzag scan
for (int right = size_ - 1; right >= 1; right -= 2) { // Index of right column in each column pair
if (right == 6)
Expand All @@ -349,7 +450,7 @@ Error QrCode::drawCodewords(Codebits& codebits) {
bool upwards = ((right & 2) == 0) ^ (x < 6);
int y = upwards ? size_ - 1 - vert : vert; // Actual y coordinate
if (!isFunction(x,y) && (count > 0)) {
setModule(x, y, codebits.next());
setModule(x, y, codebits_.next());
count--;
}
// If there are any remainder bits (0 to 7), they are already
Expand Down

0 comments on commit e800bc4

Please sign in to comment.