Skip to content

Commit

Permalink
Merge pull request #27 from RexWzh/master
Browse files Browse the repository at this point in the history
Improve Polynomial operation
  • Loading branch information
RexWzh authored Oct 1, 2022
2 parents c55b759 + 5d53b70 commit 4dc72ae
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 55 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "QRCoders"
uuid = "f42e9828-16f3-11ed-2883-9126170b272d"
authors = ["Jérémie Gillet <[email protected]> and contributors"]
version = "1.0.0"
version = "1.0.1"

[deps]
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/encode.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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}
Expand Down
106 changes: 61 additions & 45 deletions src/errorcorrection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

"""
Expand All @@ -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

"""
Expand All @@ -95,19 +106,23 @@ 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

"""
rpadzeros(p::Poly, n::Int)
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

"""
Expand Down Expand Up @@ -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)
Expand All @@ -166,37 +181,55 @@ 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

"""
euclidean_divide(f::Poly, g::Poly)
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

"""
Expand All @@ -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
2 changes: 1 addition & 1 deletion src/tables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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() =>
Expand Down
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
18 changes: 15 additions & 3 deletions test/tst_operation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
@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
g = generator(n) << la

for _ in 1:la
tail!(g)
a = init!(lead(a) * g + a)
a = init!(last(a.coeff) * g + a)
end
return a
end
Expand Down Expand Up @@ -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])
Expand All @@ -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)

Expand All @@ -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])
Expand Down

0 comments on commit 4dc72ae

Please sign in to comment.