Skip to content

Instantly share code, notes, and snippets.

@ZakBlystone
Created June 19, 2020 08:52
Show Gist options
  • Save ZakBlystone/f160a95c544f81d1d78e426e30c99799 to your computer and use it in GitHub Desktop.
Save ZakBlystone/f160a95c544f81d1d78e426e30c99799 to your computer and use it in GitHub Desktop.
GLua QR-Code generation library
-- QRCode Library (CC0 Public Domain, free to use in whatever you want)
-- Created by: Zachary Blystone ( [email protected] )
-- Based on https://github.com/nayuki/QR-Code-generator
--[[
USAGE:
-- To create a QR code object:
-- (text)
-- (ecl, can be LOW, MEDIUM, QUARTILE, or HIGH)
local myQRCode = qrcode.encodeText( "Hello", qrcode.ECC.LOW )
-- You can also use "qrcode.encodeBinary()" to encode binary data.
-- To print to console:
myQRCode:Print()
-- To draw to screen:
-- (blockSize = how big each pixel is)
-- (padding = white padding around code in pixels)
myQRCode:Draw(x, y, [blockSize], [padding])
-- To get size of drawn QR code:
-- (returns x, y)
myQRCode:GetDrawSize([blockSize], [padding])
-- Manually accessing the bitmap:
local size = myQRCode:GetSize()
for y=0, size-1 do
for x=0, size-1 do
local code = myQRCode:GetModule(x,y)
-- black if code is true
-- white if code is false
end
end
]]
local _floor = math.floor
local _min = math.min
local _max = math.max
local _abs = math.abs
local _or = bit.bor
local _xor = bit.bxor
local _and = bit.band
local _lshift = bit.lshift
local _rshift = bit.rshift
local _remove = table.remove
local _insert = table.insert
local _find = string.find
local _byte = string.byte
local assert = assert
local setmetatable = setmetatable
local ipairs = ipairs
local type = type
local tonumber = tonumber
local Color = Color
local MsgC = MsgC
local surface = surface
module("qrcode")
-- Utilities
local function getBit(x, i) return _and(_rshift(x, i), 1) ~= 0 end
local function strTable(str) local t = {} for i=1, #str do t[i] = str[i] end return t end
local function lookupify(t) for k, v in ipairs(t) do t[v] = k end return t end
local function filled(v, n) local t = {} for i=1, n do t[i] = (type(v) == "function" and v() or v) end return t end
local function slice(t, s, e) local o = {} for i=s, e do o[#o+1] = t[i] end return o end
local function cycle(...) local t = {...} local n, i = #t, 0 return function() i = i >= n and 1 or i + 1 return t[i] end end
local AlphaNumTable = lookupify(strTable("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"))
local Colors = {[true] = Color(0,0,0,255), [false] = Color(255,255,255,255)}
MIN_VERSION = 1
MAX_VERSION = 40
ECC = {
LOW = { ordinal = 1, formatBits = 1 }, -- Can tolerate about 7% erroneous codewords
MEDIUM = { ordinal = 2, formatBits = 0 }, -- Can tolerate about 15% erroneous codewords
QUARTILE = { ordinal = 3, formatBits = 3 }, -- Can tolerate about 25% erroneous codewords
HIGH = { ordinal = 4, formatBits = 2 }, -- Can tolerate about 30% erroneous codewords
CodewordsPerBlock = {
{ 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, -- Low
{10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, -- Medium
{13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, -- Quartile
{17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, -- High
},
NumBlocks = {
{1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, -- Low
{1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, -- Medium
{1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, -- Quartile
{1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, -- High
},
}
ECC.Modes = lookupify({ECC.LOW, ECC.MEDIUM, ECC.QUARTILE, ECC.HIGH})
local Mask = {
PENALTY_N1 = 3,
PENALTY_N2 = 3,
PENALTY_N3 = 40,
PENALTY_N4 = 10,
Patterns = {
function(x, y) return (x + y) % 2 end,
function(x, y) return y % 2 end,
function(x, y) return x % 3 end,
function(x, y) return (x + y) % 3 end,
function(x, y) return (_floor(x / 3) + _floor(y / 2)) % 2 end,
function(x, y) return x * y % 2 + x * y % 3 end,
function(x, y) return (x * y % 2 + x * y % 3) % 2 end,
function(x, y) return ((x + y) % 2 + x * y % 3) % 2 end,
}
}
-- Buffer to store an array of bits
local BitBuffer = {}
BitBuffer.__index = function(s, k) return BitBuffer[k] or s.bits[k] end
BitBuffer.__newindex = function(s, k, v) assert(s.bits[k] ~= nil and (v == 1 or v == 0)) s.bits[k] = v end
BitBuffer.new = function() return setmetatable({ bits = {} }, BitBuffer) end
function BitBuffer:Length() return #self.bits end
function BitBuffer:Copy() return self.new():Append(self) end
function BitBuffer:Append(t)
for i=1, #(t.bits or t) do
self.bits[#self.bits+1] = t[i]
end
return self
end
function BitBuffer:AppendBits(value, n)
for i=n-1, 0, -1 do
self.bits[#self.bits+1] = _and(_rshift(value, i), 1)
end
return self
end
Segment = {
MODE_NUMERIC = { modeBits = 0x1, charCounts = {10, 12, 14} },
MODE_ALPHANUM = { modeBits = 0x2, charCounts = {9, 11, 13} },
MODE_BYTE = { modeBits = 0x4, charCounts = {8, 16, 16} },
MODE_KANJI = { modeBits = 0x8, charCounts = {8, 10, 12} },
MODE_ECI = { modeBits = 0x7, charCounts = {0, 0, 0} },
PATTERN_NUMERIC = "[^%d]",
PATTERN_ALPHANUM = "[^%d%u %$%%%*%+%-%./:]",
}
Segment.Modes = lookupify({Segment.MODE_NUMERIC, Segment.MODE_ALPHANUM, Segment.MODE_BYTE, Segment.MODE_KANJI, Segment.MODE_ECI})
Segment.__index = Segment
Segment.makeBytes = function(data)
assert(type(data) == "string" or type(data) == "table", "data must be string or table")
local bb = BitBuffer.new()
for i=1, #data do
bb:AppendBits(_byte(data[i]), 8)
end
return Segment.new(Segment.MODE_BYTE, #data, bb)
end
Segment.makeNumeric = function(digits)
assert(type(digits) == "string", "digits must be string")
assert(not _find(digits, Segment.PATTERN_NUMERIC), "digits array must be numeric")
local bb = BitBuffer.new()
local i = 0
while i < #digits do
local n = _min(#digits - i, 3)
bb:AppendBits( tonumber(digits:sub(i+1, i+1 + n)), n * 3 + 1 )
i = i + n
end
return Segment.new(Segment.MODE_NUMERIC, #digits, bb)
end
Segment.makeAlphaNumeric = function(str)
assert(type(str) == "string", "str must be string")
assert(not _find(str, Segment.PATTERN_ALPHANUM), "str array must be alphanumeric")
local bb = BitBuffer.new()
for i=1, #str-1, 2 do
local t = (AlphaNumTable[str[i]]-1) * 45 + (AlphaNumTable[str[i+1]]-1)
bb:AppendBits(t, 11)
end
if #str % 2 > 0 then
bb:AppendBits(AlphaNumTable[str[#str]], 6)
end
return Segment.new(Segment.MODE_ALPHANUM, #str, bb)
end
Segment.makeSegments = function(str)
assert(type(str) == "string", "str must be string")
if str == "" then return {}
elseif not _find(str, Segment.PATTERN_NUMERIC) then return {Segment.makeNumeric(str)}
elseif not _find(str, Segment.PATTERN_ALPHANUM) then return {Segment.makeAlphaNumeric(str)}
else return {Segment.makeBytes(str)} end
end
Segment.makeECI = function(value)
assert(type(value) == "number" and value >= 0, "value must be number > 0")
local bb = BitBuffer.new()
if value < 0x1p7 then bb:AppendBits(value, 8)
elseif value < 0x1p14 then bb:AppendBits(2, 2) bb:AppendBits(value, 14)
elseif value < 1e6 then bb:AppendBits(6, 3) bb:AppendBits(value, 21)
else assert(false, "ECI assignment value out of range") end
return Segment.new(Segment.MODE_ECI, 0, bb)
end
Segment.getTotalBits = function(segs, version)
local result = 0
for _, seg in ipairs(segs) do
local ccbits = seg:GetNumCharCountBits(version)
if seg:GetNumChars() >= (_lshift(1, ccbits)) then
return nil
end
result = result + 4 + ccbits + seg.bitData:Length()
end
return result
end
Segment.new = function(mode, numChars, bitData)
assert(Segment.Modes[mode], "Invalid mode specified")
assert(numChars >= 0, "Invalid chars specified")
return setmetatable({
mode = mode,
numChars = numChars,
bitData = bitData:Copy(),
}, Segment)
end
function Segment:GetData() return self.bitData:Copy() end
function Segment:GetNumChars() return self.numChars end
function Segment:GetModeBits() return self.mode.modeBits end
function Segment:GetNumCharCountBits(ver) return self.mode.charCounts[1 + _floor((ver + 7) / 17)] end
local Code = {}
Code.__index = Code
Code.getNumRawDataModules = function(ver)
local result = (16 * ver + 128) * ver + 64
if ver >= 2 then
local numAlign = _floor(ver / 7) + 2
result = result - (25 * numAlign - 10) * numAlign + 55
if ver >= 7 then
result = result - 36
end
end
assert(208 <= result and result <= 29648)
return result
end
Code.getNumDataCodewords = function(ver, ecl)
return _floor(Code.getNumRawDataModules(ver) / 8)
- ECC.CodewordsPerBlock[ecl.ordinal][ver]
* ECC.NumBlocks[ecl.ordinal][ver]
end
Code.reedSolomonComputeDivisor = function(degree)
assert(1 <= degree and degree <= 255, "Invalid degree")
local result = filled(0, (degree - 1))
result[#result+1] = 1
local root = 1
for i=0, degree - 1 do
for j=0, degree - 1 do
result[j+1] = Code.reedSolomonMultiply(result[j+1], root)
if j + 1 < degree then
result[j+1] = _xor(result[j+1], result[j+2])
end
end
root = Code.reedSolomonMultiply(root, 0x02)
end
return result
end
Code.reedSolomonComputerRemainder = function(data, divisor)
local result = filled(0, #divisor)
for _, b in ipairs(data) do
local factor = _xor(b, _remove(result, 1))
result[#result+1] = 0
for k, v in ipairs(divisor) do
result[k] = _xor(result[k], Code.reedSolomonMultiply(v, factor))
end
end
return result
end
Code.reedSolomonMultiply = function(x, y)
assert( _rshift(x, 8) == 0 and _rshift(y, 8) == 0, "Byte out of range" )
local z = 0
for i=7, 0, -1 do
z = _xor(_lshift(z, 1), _rshift(z, 7) * 0x11D)
z = _xor(z, _and(_rshift(y, i), 1) * x)
end
assert( _rshift(x, 8) == 0 )
return z
end
Code.new = function(version, ecl, codewords, mask)
local code = setmetatable({
version = version,
size = version * 4 + 17,
ecl = ecl,
}, Code)
code.modules = filled(function() return filled(false, code.size) end, code.size)
code.isFunction = filled(function() return filled(false, code.size) end, code.size)
code:DrawFunctionPatterns()
code.allCodeWords = code:AddECCAndInterleave(codewords)
code:DrawCodewords(code.allCodeWords)
if mask == -1 then
local minPenalty = 0x1p32
for i=0, 7 do
code:ApplyMask(i)
code:DrawFormatBits(i)
local penalty = code:GetPenaltyScore()
if penalty < minPenalty then
mask = i
minPenalty = penalty
end
code:ApplyMask(i)
end
end
code:ApplyMask(mask)
code:DrawFormatBits(mask)
code.mask = mask
return code
end
function Code:Print()
for y=-5, self.size + 4 do
for x=-5, self.size + 4 do
local c = self:GetModule(x,y)
if c == nil then c = false end
MsgC( Colors[c], "██" )
end
MsgC( Colors[false], "\n" )
end
end
function Code:DrawFunctionPatterns()
for i=0, self.size-1 do
self:SetFunctionModule(6, i, (i%2) == 0)
self:SetFunctionModule(i, 6, (i%2) == 0)
end
self:DrawFinderPattern(3,3)
self:DrawFinderPattern(self.size - 4,3)
self:DrawFinderPattern(3, self.size - 4)
-- Draw alignment patterns
local patterns = self:GetAlignmentPatternPositions()
local num = #patterns
local skips = {[0] = {[0] = 1, [num-1] = 1}, [num-1] = {[0] = 1}}
for i=0, num-1 do
for j=0, num-1 do
if not skips[i] or not skips[i][j] then
self:DrawAlignmentPattern(patterns[i+1], patterns[j+1])
end
end
end
-- Configuration data
self:DrawFormatBits(0)
self:DrawVersion()
end
function Code:DrawFormatBits(mask)
local data = _or(_lshift(self.ecl.formatBits, 3), mask)
local rem = data
for i=0, 9 do
rem = _xor(_lshift(rem, 1), _rshift(rem, 9) * 0x537)
end
local bits = _xor(_or(_lshift(data, 10), rem), 0x5412)
assert( _rshift(bits, 15) == 0 )
-- First copy
for i=0, 5 do
self:SetFunctionModule(8, i, getBit(bits, i))
end
self:SetFunctionModule(8,7, getBit(bits, 6))
self:SetFunctionModule(8,8, getBit(bits, 7))
self:SetFunctionModule(7,8, getBit(bits, 8))
for i=9, 14 do
self:SetFunctionModule(14 - i, 8, getBit(bits, i))
end
-- Second copy
for i=0, 7 do
self:SetFunctionModule(self.size - 1 - i, 8, getBit(bits, i))
end
for i=8, 14 do
self:SetFunctionModule(8, self.size - 15 + i, getBit(bits, i))
end
self:SetFunctionModule(8, self.size-8, true)
end
function Code:DrawVersion()
if self.version < 7 then return end
local rem = self.version
for i=0, 11 do
rem = _xor(_lshift(rem, 1), _rshift(rem, 11) * 0x1F25)
end
local bits = _or(_lshift(self.version, 12), rem)
assert( _rshift(bits, 18) == 0 )
for i=0, 17 do
local bit = getBit(bits, i)
local a = self.size - 11 + i % 3
local b = _floor(i / 3)
self:SetFunctionModule(a, b, bit)
self:SetFunctionModule(b, a, bit)
end
end
function Code:DrawFinderPattern(x, y)
local k = {[2] = 1, [4] = 1}
for dy = -4, 4 do
for dx = -4, 4 do
local xx, yy = x + dx, y + dy
if 0 <= xx and xx < self.size and 0 <= yy and yy < self.size then
self:SetFunctionModule(xx, yy, not k[_max(_abs(dx), _abs(dy))] and true or false)
end
end
end
end
function Code:DrawAlignmentPattern(x,y)
for dy = -2, 2 do
for dx = -2, 2 do
self:SetFunctionModule(x + dx, y + dy, _max(_abs(dx), _abs(dy)) ~= 1)
end
end
end
function Code:GetAlignmentPatternPositions()
local ver = self.version
if ver == 1 then return {} end
local numAlign = _floor(ver / 7) + 2
local step = ver == 32 and 26 or _floor((ver * 4 + numAlign * 2 + 1) / (numAlign*2 - 2)) * 2
local result = {}
for i=0, numAlign - 2 do
result[#result+1] = self.size - 7 - i * step
end
result[#result+1] = 6
local r = {}
for i=#result, 1, -1 do r[#r+1] = result[i] end
return r
end
function Code:DrawCodewords(data)
assert(#data == _floor(Code.getNumRawDataModules(self.version) / 8))
local t = {}
local i = 0
for right = self.size-1, 1, -2 do
if right <= 6 then right = right - 1 end
for vert = 0, self.size-1 do
for j=0, 1 do
local x = right - j
local upward = _and(right + 1, 2) == 0
local y = upward and (self.size - 1 - vert) or vert
if not self:IsFunction(x,y) and i < #data * 8 then
local b = 7 - _and(i, 7)
self:SetModule(x, y, getBit(data[ _rshift(i, 3) + 1 ], b))
t[#t+1] = x
i = i + 1
end
end
end
end
assert(i == #data * 8)
end
function Code:AddECCAndInterleave(data)
local version = self.version
assert( #data == Code.getNumDataCodewords(version, self.ecl) )
local numBlocks = ECC.NumBlocks[self.ecl.ordinal][version]
local blockECCLen = ECC.CodewordsPerBlock[self.ecl.ordinal][version]
local rawCodewords = _floor(Code.getNumRawDataModules(version) / 8)
local numShortBlocks = numBlocks - rawCodewords % numBlocks
local shortBlockLen = _floor(rawCodewords / numBlocks)
-- Split data into blocks and append ECC
local blocks = {}
local rsdiv = Code.reedSolomonComputeDivisor(blockECCLen)
local k = 0
for i=0, numBlocks-1 do
local dat = slice(data, k+1, k+shortBlockLen-blockECCLen+(i < numShortBlocks and 0 or 1))
k = k + #dat
local ecc = Code.reedSolomonComputerRemainder(dat, rsdiv)
if i < numShortBlocks then dat[#dat+1] = 0 end
for _, v in ipairs(ecc) do dat[#dat+1] = v end
blocks[#blocks+1] = dat
end
-- Interleave bytes from every block into single sequence
local result = {}
for i=0, #blocks[1]-1 do
for j, blk in ipairs(blocks) do
if i ~= (shortBlockLen - blockECCLen) or j-1 >= numShortBlocks then
result[#result+1] = blk[i+1]
end
end
end
assert(#result == rawCodewords)
return result
end
function Code:ApplyMask(mask)
assert(0 <= mask and mask <= 7, "Invalid mask value")
local func = Mask.Patterns[mask+1]
for y=0, self.size-1 do
for x=0, self.size-1 do
if not self:IsFunction(x,y) then
local masked = _xor(self:GetModule(x,y) and 1 or 0, func(x,y) == 0 and 1 or 0)
self:SetModule(x,y,masked == 1)
end
end
end
end
function Code:GetPenaltyScore()
local result = 0
local size = self.size
local modules = self.modules
-- Adjacent modules in row having same color, and finder-like patterns
for y=0, size-1 do
local run = false
local runx = 0
local runhistory = filled(0, 7)
for x=0, size-1 do
if self:GetModule(x,y) == run then
runx = runx + 1
if runx == 5 then
result = result + Mask.PENALTY_N1
elseif runx > 5 then
result = result + 1
end
else
self:FinderPenaltyAddHistory(runx, runhistory)
if not run then
result = result + self:FinderPenaltyCountPatterns(runhistory) * Mask.PENALTY_N3
end
run = self:GetModule(x,y)
runx = 1
end
end
result = result + self:FinderPenaltyTerminateAndCount(run, runx, runhistory) * Mask.PENALTY_N3
end
-- Adjacent modules in column having same color, and finder-like patterns
for x=0, size-1 do
local run = false
local runy = 0
local runhistory = filled(0, 7)
for y=0, size-1 do
if self:GetModule(x,y) == run then
runy = runy + 1
if runy == 5 then
result = result + Mask.PENALTY_N1
elseif runy > 5 then
result = result + 1
end
else
self:FinderPenaltyAddHistory(runy, runhistory)
if not run then
result = result + self:FinderPenaltyCountPatterns(runhistory) * Mask.PENALTY_N3
end
run = self:GetModule(x,y)
runy = 1
end
end
result = result + self:FinderPenaltyTerminateAndCount(run, runy, runhistory) * Mask.PENALTY_N3
end
-- 2x2 blocks of modules having same color
for y=0, size-1 do
for x=0, size-1 do
if self:GetModule(x,y) == self:GetModule(x+1,y)
and self:GetModule(x+1,y) == self:GetModule(x,y+1)
and self:GetModule(x,y+1) == self:GetModule(x+1,y+1) then
result = result + Mask.PENALTY_N2
end
end
end
local black = 0
for y=0, size-1 do
for x=0, size-1 do
if self:GetModule(x,y) == false then black = black + 1 end
end
end
local total = size * size
local k = _floor((_abs(black * 20 - total * 10) + total - 1) / total) - 1
result = result + k * Mask.PENALTY_N4
return result
end
function Code:FinderPenaltyAddHistory(length, history)
if history[1] == 0 then
length = length + self.size
end
_insert(history, 1, length)
history[#history] = nil
end
function Code:FinderPenaltyCountPatterns(history)
local n = history[2]
assert(n <= self.size * 3)
local core = n > 0
and (history[3] == history[5] and history[5] == history[6] and history[6] == n)
and history[4] == n * 3
return ((core and history[1] >= n * 4 and history[7] >= n) and 1 or 0)
+ ((core and history[7] >= n * 4 and history[1] >= n) and 1 or 0)
end
function Code:FinderPenaltyTerminateAndCount(run, length, history)
if run then
self:FinderPenaltyAddHistory(length, history)
length = 0
end
length = length + self.size
self:FinderPenaltyAddHistory(length, history)
return self:FinderPenaltyCountPatterns(history)
end
function Code:GetVersion() return self.version end
function Code:GetSize() return self.size end
function Code:GetErrorCorrectionLevel() return self.ecl end
function Code:GetMask() return self.mask end
function Code:GetModule(x,y) if ((0 <= x and x < self.size) and (0 <= y and y < self.size)) then return self.modules[y+1][x+1] end end
function Code:SetModule(x,y,v) self.modules[y+1][x+1] = v end
function Code:IsFunction(x,y) return self.isFunction[y+1][x+1] == true end
function Code:SetFunctionModule(x,y,v) self.modules[y+1][x+1] = v self.isFunction[y+1][x+1] = true end
function Code:ComputeRLE()
local size = self:GetSize()
local p = nil
local vruns = {}
for y = 0, size-1 do
local run = {}
local hruns = {}
for x = 0, size do
local m = self:GetModule(x,y)
if run.b == nil then run.b = m run.x = x run.y = y run.n = 0 run.h = 1 end
if m ~= run.b then
hruns[#hruns+1] = run
run = {b = m, x = x, y = y, n = 1, h = 1}
else
run.n = run.n + 1
end
end
if p then
for k=#hruns, 1, -1 do
local run = hruns[k]
for j=#p, 1, -1 do
local old = p[j]
if old.x == run.x and old.n == run.n and old.b == run.b then
old.h = old.h + 1
_remove(p, j)
hruns[k] = old
break
end
end
end
end
vruns[#vruns+1] = hruns
p = hruns
end
self.RLE = vruns
end
function Code:GetDrawSize(blockSize, padding)
local size = self:GetSize()
local block = blockSize or 2
local pad = padding or 8
return (size+pad*2)*block, (size+pad*2)*block
end
function Code:Draw(x, y, blockSize, padding)
if not self.RLE then
self:ComputeRLE()
end
x = x or 0
y = y or 0
local size = self:GetSize()
local block = blockSize or 2
local pad = padding or 8
surface.SetDrawColor(Colors[false])
surface.DrawRect(x, y, (size+pad)*block, pad*block)
surface.DrawRect(x, y, pad*block, (size+pad)*block)
surface.DrawRect(x, y+(size+pad)*block, (size+pad)*block, pad*block)
surface.DrawRect(x+(size+pad)*block, y, pad*block, (size+pad*2)*block)
for k, v in ipairs(self.RLE) do
for n, h in ipairs(v) do
surface.SetDrawColor(Colors[h.b])
surface.DrawRect(x+(pad+h.x)*block, y+(pad+h.y)*block, h.n*block, h.h*block)
end
end
end
function encodeSegments(segs, ecl, minVersion, maxVersion, mask, boostEcl)
minVersion = minVersion or MIN_VERSION
maxVersion = maxVersion or MAX_VERSION
mask = mask or -1
if boostEcl == nil then boostEcl = true end
ecl = ecl or ECC.LOW
assert(ECC.Modes[ecl], "Invalid error correction level")
assert(minVersion >= MIN_VERSION and maxVersion <= MAX_VERSION and minVersion <= maxVersion, "Invalid version range")
assert(mask >= -1 and mask <= 7, "Invalid mask")
-- Find smallest version number that fits the data + error correction codes
local usedBits, capacityBits, version
for i=minVersion, maxVersion do
capacityBits = Code.getNumDataCodewords(i, ecl) * 8
usedBits = Segment.getTotalBits(segs, i)
if usedBits and usedBits <= capacityBits then version = i break end
if i >= maxVersion then error("Segment too long") end
end
assert(usedBits)
-- Increase error correction mode if there is free space for it
for i=2, #ECC.Modes do
if boostEcl and usedBits <= Code.getNumDataCodewords(version, ECC.Modes[i]) * 8 then
ecl = ECC.Modes[i]
end
end
-- Concatenate all segments into single bit buffer
local bb = BitBuffer.new()
for _, seg in ipairs(segs) do
bb:AppendBits( seg:GetModeBits(), 4 )
bb:AppendBits( seg:GetNumChars(), seg:GetNumCharCountBits(version) )
bb:Append( seg.bitData )
end
-- Add terminator and padding
assert( bb:Length() == usedBits, "Mismatch: " .. bb:Length() .. " ~= " .. usedBits )
capacityBits = Code.getNumDataCodewords(version, ecl) * 8
assert( bb:Length() <= capacityBits )
bb:AppendBits( 0, _min(4, capacityBits - bb:Length()) )
bb:AppendBits( 0, -bb:Length() % 8 )
assert( bb:Length() % 8 == 0 )
-- Pad with alternating bytes until capacity is reached
for byte in cycle(0xEC, 0x11) do
if bb:Length() >= capacityBits then break end
bb:AppendBits(byte, 8)
end
-- Pack bits into bytes
local codewords = filled(0, _floor(bb:Length() / 8))
for i=0, bb:Length()-1 do
local byte = 1 + _rshift(i, 3)
codewords[byte] = _or( codewords[byte], _lshift(bb[i+1], 7 - (_and(i, 7))))
end
return Code.new(version, ecl, codewords, mask)
end
function encodeText(text, ecl, ...)
local segs = Segment.makeSegments(text)
return encodeSegments(segs, ecl, ...)
end
function encodeBinary(bytes, ecl, ...)
return encodeSegments({Segment.makeBytes(bytes)}, ecl, ...)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment