diff --git a/Project.toml b/Project.toml index 153d562..651ddc1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QRCoders" uuid = "f42e9828-16f3-11ed-2883-9126170b272d" authors = ["Jérémie Gillet and contributors"] -version = "1.0.0" +version = "1.0.1" [deps] FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" diff --git a/README.md b/README.md index 1271f0e..711e1b6 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ This file will be saved as `./img/hello.png` (if the `img` directory already exi ### Error Correction Level -QR Codes and be encoded with four error correction levels `Low`, `Medium`, `Quartile` and `High`. Error correction can restore missing data from the QR code. +QR Codes can be encoded with four error correction levels `Low`, `Medium`, `Quartile` and `High`. Error correction can restore missing data from the QR code. * `Low` can restore up to 7% of missing codewords. * `Medium` can restore up to 15% of missing codewords. @@ -93,7 +93,7 @@ QR Codes can encode data using several encoding schemes. `QRCoders.jl` supports `Numeric` is used for messages composed of digits only, `Alphanumeric` for messages composed of digits, characters `A`-`Z` (capital only) space and `%` `*` `+` `-` `.` `/` `:` `\$`, `Kanji` for kanji for Shift JIS(Shift Japanese Industrial Standards) characters, `Bytes` for messages composed of one-byte characters(including undefined characters), and `UTF8` for messages composed of Unicode characters. -Please not that QR Code reader don't always support arbitrary UTF-8 characters. +Please note that QR Code reader don't always support arbitrary UTF-8 characters. Another thing to point out is that, for `Byte` mode, we allow the use of undefined characters(Unicode range from 0x7f to 0x9f), following the original setting in QRCode.jl. For example: ```jl diff --git a/src/encode.jl b/src/encode.jl index e39a96f..160e01b 100644 --- a/src/encode.jl +++ b/src/encode.jl @@ -12,7 +12,7 @@ using .Polynomial: Poly, geterrorcorrection Return the length of a UTF-8 message. Note: utf-8 character has flexialbe length range from 1 to 4. """ -utf8len(message::AbstractString) = length(Vector{UInt8}(message)) +utf8len(message::AbstractString) = length(codeunits(message)) """ bitarray2int(bits::AbstractVector) @@ -149,7 +149,7 @@ function encodedata(message::AbstractString, ::Byte)::BitArray{1} vcat(int2bitarray.(UInt8.(collect(message)))...) end function encodedata(message::AbstractString, ::UTF8)::BitArray{1} - vcat(int2bitarray.(Vector{UInt8}(message))...) + vcat(int2bitarray.(codeunits(message))...) end function encodedata(message::AbstractString, ::Kanji)::BitArray{1} diff --git a/src/errorcorrection.jl b/src/errorcorrection.jl index 6eb9299..235117f 100644 --- a/src/errorcorrection.jl +++ b/src/errorcorrection.jl @@ -57,13 +57,25 @@ function gflog2(n::Integer) end """ - function mult(a::Integer, b::Integer) +Multiplication table of non-zero elements in GF(256). +""" +const multtable = [gfpow2(gflog2(i) + gflog2(j)) for i in 1:255, j in 1:255] + + +""" +Division table of non-zero elements in GF(256). +""" +const divtable = [gfpow2(gflog2(i) - gflog2(j)) for i in 1:255, j in 1:255] + +""" + mult(a::Integer, b::Integer) Multiplies two integers in GF(256). """ function mult(a::Integer, b::Integer) (a == 0 || b == 0) && return 0 - return gfpow2(gflog2(a) + gflog2(b)) + # gfpow2(gflog2(a) - gflog2(b)) + return multtable[a, b] end """ @@ -74,8 +86,7 @@ Division of intergers in GF(256). function divide(a::Integer, b::Integer) b == 0 && throw(DivideError()) a == 0 && return 0 - b == 1 && return a ## cases when dealing with generator polynomial - return gfpow2(gflog2(a) - gflog2(b)) + return divtable[a, b] end """ @@ -95,9 +106,11 @@ iszeropoly(p::Poly) = all(iszero, p) Remove trailing zeros from polynomial p. """ -function rstripzeros(p::Poly) +rstripzeros(p::Poly) = rstripzeros!(copy(p)) +function rstripzeros!(p::Poly) iszeropoly(p) && return zero(Poly) - return Poly(p.coeff[1:findlast(!iszero, p.coeff)]) + deleteat!(p.coeff, findlast(!iszero, p.coeff) + 1:length(p)) + return p end """ @@ -105,9 +118,11 @@ end Add zeros to the right side of p(x) such that length(p) == n. """ -function rpadzeros(p::Poly, n::Int) +rpadzeros(p::Poly, n::Int) = rpadzeros!(copy(p), n) +function rpadzeros!(p::Poly, n::Int) length(p) > n && throw("rpadzeros: length(p) > n") - return Poly(vcat(p.coeff, zeros(Int, n - length(p)))) + append!(p.coeff, zeros(Int, n - length(p))) + return p end """ @@ -151,7 +166,7 @@ length(p::Poly) = length(p.coeff) iterate(p::Poly) = iterate(p.coeff) iterate(p::Poly, i) = iterate(p.coeff, i) -==(a::Poly, b::Poly)::Bool = a.coeff == b.coeff +==(a::Poly, b::Poly)::Bool = iszeropoly(a + b) """ <<(p::Poly, n::Int) @@ -166,9 +181,12 @@ function +(a::Poly, b::Poly)::Poly end *(a::Integer, p::Poly)::Poly = Poly(map(x->mult(a, x), p.coeff)) - function *(a::Poly, b::Poly)::Poly - return sum([ c * (a << (p - 1)) for (p, c) in enumerate(b.coeff)]) + prodpoly = Poly(zeros(Int, length(a) + length(b) - 1)) + @inbounds for (i, c1) in enumerate(a.coeff), (j, c2) in enumerate(b.coeff) + prodpoly.coeff[i + j - 1] ⊻= mult(c2, c1) # column first + end + return prodpoly end """ @@ -176,27 +194,42 @@ end Returns the quotient and the remainder of Euclidean division. """ -function euclidean_divide(f::Poly, g::Poly) +euclidean_divide(f::Poly, g::Poly) = euclidean_divide!(copy(f), copy(g)) + +""" + euclidean_divide!(f::Poly, g::Poly) + +Implement of Euclidean division with minimized allocations. +""" +function euclidean_divide!(f::Poly, g::Poly) ## remove trailing zeros - g, f = rstripzeros(g), rstripzeros(f) - g == zero(Poly) && throw(DivideError()) + g, f = rstripzeros!(g), rstripzeros!(f) + iszeropoly(g) && throw(DivideError()) + fcoef, gcoef, lf, lg = f.coeff, g.coeff, length(f), length(g) ## leading term of g(x) - gn = lead(g) + gn = last(gcoef) ## g(x) is a constant - length(g) == 1 && return Poly(divide.(f.coeff, gn)), zero(Poly) - diffdeg = length(f) - length(g) + if lg == 1 + @inbounds for i in eachindex(fcoef) + fcoef[i] = divide(fcoef[i], gn) + end + return f, zero(Poly) + end + ## degree of the quotient polynomial + quodeg = lf - lg ## deg(f) < deg(g) - diffdeg < 0 && return zero(Poly), f - g <<= diffdeg # g(x)⋅x^{diffdeg} + quodeg < 0 && return zero(Poly), f ## quotient polynomial - quocoef = Vector{Int}(undef, diffdeg + 1) - for i in 1:diffdeg - quocoef[i] = divide(lead(f), gn) - f = init!(quocoef[i] * g + f) - popfirst!(g.coeff) + @inbounds for i in 0:quodeg + leadterm = divide(fcoef[end-i], gn) + for (j, c) in enumerate(gcoef) + fcoef[quodeg - i + j] ⊻= mult(leadterm, c) + end + fcoef[end-i] = leadterm end - quocoef[end] = divide(lead(f), gn) - return Poly(reverse!(quocoef)), init!(divide(lead(f), gn) * g + f) + quo = Poly(fcoef[end-quodeg:end]) # here @view will be a little bit slower + deleteat!(fcoef, lf-quodeg:lf) + return quo, f end """ @@ -218,29 +251,12 @@ Remainder of Euclidean division. Create the Generator Polynomial of degree `n`. """ -function generator(n::Int)::Poly - prod([Poly([gfpow2(i - 1), 1]) for i in 1:n]) -end - -""" - lead(p::Poly) - -Return the leading coefficient of `p`. -""" -lead(p::Poly) = last(p.coeff) - -""" - init!(p::Poly) - -Delete the leading coefficient of `p`. -""" -init!(p::Poly)::Poly = Poly(deleteat!(p.coeff, length(p))) +generator(n::Int) = prod([Poly([gfpow2(i - 1), 1]) for i in 1:n]) """ geterrorcorrection(f::Poly, n::Int) Return a polynomial containing the `n` error correction codewords of `f`. """ -geterrorcorrection(f::Poly, n::Int) = rpadzeros(f << n % generator(n), n) - +geterrorcorrection(f::Poly, n::Int) = rpadzeros!(f << n % generator(n), n) end # module diff --git a/src/tables.jl b/src/tables.jl index 3b5df9e..4f0672a 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -127,7 +127,7 @@ const charactercountlength = Dict{Mode, Array{Int, 1}}( """ Information about the error correction codeblocks per level and version -(ec, block1length, numofblock1s, block2length, numofblock2s). +(eclevel, numofblock1s, block1length, numofblock2s, block2length). """ const ecblockinfo = Dict{ErrCorrLevel,Array{Int,2}}( Low() => diff --git a/test/runtests.jl b/test/runtests.jl index d108550..96d53f3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -25,7 +25,7 @@ using QRCoders.Polynomial: makelogtable, antilogtable, logtable, gfpow2, gflog2, gfinv, mult, divide, # operator for polynomials - iszeropoly, degree, init!, lead, zero, unit, + iszeropoly, degree, zero, unit, rpadzeros, rstripzeros, generator, geterrorcorrection, euclidean_divide diff --git a/test/tst_operation.jl b/test/tst_operation.jl index 7df6a97..081ebf3 100644 --- a/test/tst_operation.jl +++ b/test/tst_operation.jl @@ -4,6 +4,8 @@ @testset "Euclidean division" begin ## original method of `geterrorcorrection` tail!(p::Poly)::Poly = Poly(deleteat!(p.coeff, 1)) + init!(p::Poly)::Poly = Poly(deleteat!(p.coeff, length(p))) + Base.:(*)(a::Integer, p::Poly)::Poly = Poly(map(x->mult(a, x), p.coeff)) function geterrcode(a::Poly, n::Int)::Poly la = length(a) a = a << n @@ -11,7 +13,7 @@ for _ in 1:la tail!(g) - a = init!(lead(a) * g + a) + a = init!(last(a.coeff) * g + a) end return a end @@ -78,10 +80,13 @@ end p2.coeff[1] = 3 @test p2 != p1 - ## zeros, unit + ## zeros, unit, rstripzeros, rpadzeros @test zero(Poly) == Poly([0]) @test rstripzeros(Poly([1, 0, 2, 0, 0])) == Poly([1, 0, 2]) @test rstripzeros(Poly([0, 0, 0, 0, 0])) == Poly([0]) + @test rpadzeros(Poly([1, 0, 2]), 5) == Poly([1, 0, 2, 0, 0]) + @test rpadzeros(Poly([1, 0, 2]), 3) == Poly([1, 0, 2]) + @test_throws String rpadzeros(Poly([1, 0, 2]), 2) f = randpoly(1:255) @test f * unit(Poly) == f == unit(Poly) * f @test unit(Poly) == Poly([1]) @@ -108,8 +113,13 @@ end @test !(Alphanumeric() ⊆ Numeric()) end -## original tests @testset "Test set for polynomials and error encoding" begin + function oriprod(a::Poly, b::Poly)::Poly + return sum([ c * (a << (p - 1)) for (p, c) in enumerate(b.coeff)]) + end + a, b = randpoly(1:255), randpoly(1:255) + @test a * b == oriprod(a, b) + @test all(i == antilogtable[logtable[i]] for i in 0:254) @test all(i == logtable[antilogtable[i]] for i in 1:255) @@ -123,6 +133,8 @@ end @test Poly([1, 1]) * Poly([2, 1]) == Poly([2, 3, 1]) @test Poly([1, 1]) * Poly([2, 1]) * Poly([4, 1]) == Poly([8, 14, 7, 1]) + a, p = rand(UInt8), randpoly(1:255) + @test Poly([a]) * p == a * p @test generator(2) == Poly([2, 3, 1]) @test generator(3) == Poly([8, 14, 7, 1])