Skip to content

Commit

Permalink
Merge pull request #43 from RexWzh/master
Browse files Browse the repository at this point in the history
reorganize file structure
  • Loading branch information
RexWzh authored Nov 18, 2022
2 parents ec64084 + 287707a commit 0d8f5bc
Show file tree
Hide file tree
Showing 12 changed files with 640 additions and 608 deletions.
457 changes: 14 additions & 443 deletions src/QRCoders.jl

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/encode.jl
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,15 @@ function encodemessage(msg::AbstractString, mode::Mode, eclevel::ErrCorrLevel, v

return msgbits
end

"""
function addborder(matrix::AbstractMatrix, width::Int)
Add a white border of width `width` to the QR code.
"""
function addborder(matrix::AbstractMatrix, width::Int)
width == 0 && return matrix
background = falses(size(matrix) .+ (width*2, width*2))
background[width+1:end-width, width+1:end-width] = matrix
return background
end
282 changes: 282 additions & 0 deletions src/export.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
# Generate and export QR codes

"""
qrcode( message::AbstractString
; eclevel::ErrCorrLevel = Medium()
, version::Int = 0
, mode::Mode = Numeric()
, mask::Int = -1
, width::Int=0)
Create a `BitArray{2}` with the encoded `message`, with `true` (`1`) for the black
areas and `false` (`0`) as the white ones.
The error correction level `eclevel` can be picked from four values: `Low()`
(7% of missing data can be restored), `Medium()` (15%), `Quartile()` (25%) or
`High()` (30%). Higher levels make denser QR codes.
The version of the QR code can be picked from 1 to 40. If the assigned version is
too small to contain the message, the first available version is used.
The encoding mode `mode` can be picked from five values: `Numeric()`, `Alphanumeric()`,
`Byte()`, `Kanji()` or `UTF8()`. If the assigned `mode` is `nothing` or failed to contain the message,
the mode is automatically picked.
The mask pattern `mask` can be picked from 0 to 7. If the assigned `mask` is `nothing`,
the mask pattern will picked by the penalty rules.
"""
function qrcode( message::AbstractString
; eclevel::ErrCorrLevel = Medium()
, version::Int = 0
, mode::Mode = Numeric()
, mask::Int = -1
, compact::Bool = false
, width::Int = 0)
isempty(message) && @warn(
"Most QR code scanners don't support empty message!")
# Determining mode and version of the QR code
bestmode = getmode(message)
mode = bestmode mode ? mode : bestmode

version > 40 && throw(EncodeError("Version $version should be no larger than 40"))
minversion = getversion(message, mode, eclevel)
if version < minversion # the specified version is too small
version = minversion
end

# encode message
data = encodemessage(message, mode, eclevel, version)

# Generate qr code matrix
matrix = emptymatrix(version)
masks = makemasks(matrix) # 8 masks
matrix = placedata!(matrix, data) # fill in data bits
addversion!(matrix, version) # fill in version bits

# Apply mask and add format information
maskedmats = [addformat!(xor.(matrix, mat), i-1, eclevel)
for (i, mat) in enumerate(masks)]

# Pick the best mask
if !(0 mask 7) # invalid mask
mask = argmin(penalty.(maskedmats)) - 1
end
matrix = maskedmats[mask + 1]

# white border
(compact || width == 0) && return matrix # keyword compact will be removed in the future
return addborder(matrix, width)
end

"""
_resize(matrix::AbstractMatrix, widthpixels::Int)
Resize the width of the QR code to `widthpixels` pixels(approximately).
Note: the size of the resulting matrix is an integer multiple of the size of the original one.
"""
function _resize(matrix::AbstractMatrix, widthpixels::Int = 160)
scale = ceil(Int, widthpixels / size(matrix, 1))
kron(matrix, trues(scale, scale))
end

"""
exportbitmat(matrix::BitMatrix, path::AbstractString; pixels::Int = 160)
Export the `BitMatrix` `matrix` to an image with file path `path`.
"""
function exportbitmat( matrix::BitMatrix
, path::AbstractString
; targetsize::Int=0
, pixels::Int=160)
# check whether the image format is supported
supportexts = ["png", "jpg", "gif"]
if !endswith(path, r"\.\w+")
path *= ".png"
else
ext = last(split(path, '.'))
ext supportexts || throw(EncodeError(
"Unsupported file extension: $ext\n Supported extensions: $supportexts"))
end
# resize the matrix
if targetsize > 0 # original keyword -- will be removed in the future
n = size(matrix, 1)
pixels = ceil(Int, 72 * targetsize / 2.45 / n) * n
end
save(path, .! _resize(matrix, pixels))
end

"""
exportqrcode( message::AbstractString
, path::AbstractString = "qrcode.png"
; eclevel::ErrCorrLevel = Medium()
, version::Int = 0
, mode::Mode = nothing
, width::int = 4
, pixels::Int = 160)
Create an image with the encoded `message` of approximate size `pixels x pixels`.
The error correction level `eclevel` can be picked from four values: `Low()`
(7% of missing data can be restored), `Medium()` (15%), `Quartile()` (25%) or
`High()` (30%). Higher levels make denser QR codes.
The version of the QR code can be picked from 1 to 40. If the assigned version is
too small to contain the message, the first available version is used.
The encoding mode `mode` can be picked from four values: `Numeric()`, `Alphanumeric()`,
`Byte()`, `Kanji()` or `UTF8()`. If the assigned `mode` is `nothing` or failed to contain the message,
the mode is automatically picked.
The mask pattern `mask` can be picked from 0 to 7. If the assigned `mask` is `nothing`,
the mask pattern will picked by the penalty rules.
"""
function exportqrcode( message::AbstractString
, path::AbstractString = "qrcode.png"
; eclevel::ErrCorrLevel = Medium()
, version::Int = 0
, mode::Mode = Numeric()
, mask::Int = -1
, width::Int = 4
, compact::Bool = false
, targetsize::Int = 0
, pixels::Int = 160)
# encode data
matrix = qrcode(message; eclevel=eclevel,
version=version,
mode=mode,
mask=mask,
compact=compact,
width=width)
exportbitmat(matrix, path; targetsize=targetsize, pixels=pixels)
end

# new API for qrcode and exportqrcode
"""
qrcode(code::QRCode)
Create a QR code matrix by the `QRCode` object.
Note: It would raise an error if failed to use the specified `mode`` or `version`.
"""
function qrcode(code::QRCode)
# raise error if failed to use the specified mode or version
mode, eclevel, version, mask = code.mode, code.eclevel, code.version, code.mask
message, width = code.message, code.border
getmode(message) mode || throw(EncodeError("Mode $mode can not encode the message"))
getversion(message, mode, eclevel) version 40 || throw(EncodeError("The version $version is too small"))

# encode message
data = encodemessage(message, mode, eclevel, version)

# Generate qr code matrix
matrix = emptymatrix(version)
maskmat = makemask(matrix, mask)
matrix = placedata!(matrix, data) # fill in data bits
addversion!(matrix, version) # fill in version bits
matrix = addformat!(xor.(matrix, maskmat), mask, eclevel)

# white border
addborder(matrix, width)
end

"""
exportqrcode( code::QRCode
, path::AbstractString = "qrcode.png"
; pixels::Int = 160)
Create an image with the encoded `message` of approximate size `targetsize`.
"""
function exportqrcode( code::QRCode
, path::AbstractString = "qrcode.png"
; targetsize::Int = 0
, pixels::Int = 160)
matrix = qrcode(code)
exportbitmat(matrix, path; targetsize=targetsize, pixels=pixels)
end

"""
exportqrcode( codes::AbstractVector{QRCode}
, path::AbstractString = "qrcode.gif"
; pixels::Int = 160
, fps::Int = 2)
Create an animated gif with `codes` of approximate size `targetsize`.
The frame rate `fps` is the number of frames per second.
Note: The `codes` should have the same size while the other properties can be different.
"""
function exportqrcode( codes::AbstractVector{QRCode}
, path::AbstractString = "qrcode.gif"
; targetsize::Int = 0
, pixels::Int = 160
, fps::Int = 2)
matwidth = qrwidth(first(codes))
all(==(matwidth), qrwidth.(codes)) || throw(EncodeError("The codes should have the same size"))
# check whether the image format is supported
if !endswith(path, r"\.\w+")
path *= ".gif"
else
ext = last(split(path, '.'))
ext == "gif" || throw(EncodeError(
"$ext\n is not a valid format for animated images"))
end
# generate frames
if targetsize > 0 # original keyword -- will be removed in the future
Base.depwarn("keyword `targetsize` will be removed in the future, use `pixels` instead", :exportbitmat)
pixels = ceil(Int, 72 * targetsize / 2.45 / matwidth) * matwidth
else
pixels = ceil(Int, pixels / matwidth) * matwidth
end
code = Array{Bool}(undef, pixels, pixels, length(codes))
for (i, c) in enumerate(codes)
code[:,:,i] = _resize(qrcode(c), pixels)
end
save(path, .! code, fps=fps)
end

"""
exportqrcode( msgs::AbstractVector{<:AbstractString}
, path::AbstractString = "qrcode.gif"
; eclevel::ErrCorrLevel = Medium()
, version::Int = 0
, mode::Mode = Numeric()
, mask::Int = -1
, width::Int = 4
, targetsize::Int = 5
, pixels::Int = 160
, fps::Int = 2)
Create an animated gif with `msgs` of approximate size `pixels x pixels`.
The frame rate `fps` is the number of frames per second.
"""
function exportqrcode( msgs::AbstractVector{<:AbstractString}
, path::AbstractString = "qrcode.gif"
; eclevel::ErrCorrLevel = Medium()
, version::Int = 0
, mode::Mode = Numeric()
, mask::Int = -1
, width::Int = 4
, targetsize::Int = 0
, pixels::Int = 160
, fps::Int = 2)
codes = QRCode.( msgs
; eclevel=eclevel
, version=version
, mode=mode
, mask=mask
, width=width)
# the image should have the same size
## method 1: edit version
version = maximum(getproperty.(codes, :version))
setproperty!.(codes, :version, version)
## method 2: enlarge width of the small ones
# maxwidth = first(maximum(qrwidth.(codes)))
# for code in codes
# code.border += (maxwidth - qrwidth(code)) ÷ 2
# end
# setproperty!.(codes, :width, width)
exportqrcode(codes, path; targetsize=targetsize, pixels=pixels, fps=fps)
end
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 2 additions & 1 deletion src/tables.jl → src/preprocess/tables.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Data tables from the specificatioms

include("kanji.jl")

"""
Allowed characters for `Alphanumeric()` mode and their number.
"""
Expand All @@ -8,7 +10,6 @@ const alphanumeric = Dict{AbstractChar, Int}(

const antialphanumeric = Dict{Int, AbstractChar}(val => key for (key, val) in alphanumeric)

include("kanji.jl")
const antikanji = Dict{Int, AbstractChar}(val => key for (key, val) in kanji)

"""
Expand Down
Loading

0 comments on commit 0d8f5bc

Please sign in to comment.