Created
June 19, 2020 08:52
-
-
Save ZakBlystone/f160a95c544f81d1d78e426e30c99799 to your computer and use it in GitHub Desktop.
GLua QR-Code generation library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- 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