qr-mask/script.js

204 lines
5.6 KiB
JavaScript
Raw Permalink Normal View History

/// Division with fast fraction truncating
function intDiv(a, b) {
return ~~(a / b)
}
let getCodeVersion
const alignmentCoords = [
[6, 18],
[6, 22],
[6, 26],
[6, 30],
[6, 34],
[6, 22, 38],
[6, 24, 42],
[6, 26, 46],
[6, 28, 50],
[6, 30, 54],
[6, 32, 58],
[6, 34, 62],
[6, 26, 46, 66],
[6, 26, 48, 70],
[6, 26, 50, 74],
[6, 30, 54, 78],
[6, 30, 56, 82],
[6, 30, 58, 86],
[6, 34, 62, 90],
[6, 28, 50, 72, 94],
[6, 26, 50, 74, 98],
[6, 30, 54, 78, 102],
[6, 28, 54, 80, 106],
[6, 32, 58, 84, 110],
[6, 30, 58, 86, 114],
[6, 34, 62, 90, 118],
[6, 26, 50, 74, 98, 122],
[6, 30, 54, 78, 102, 126],
[6, 26, 52, 78, 104, 130],
[6, 30, 56, 82, 108, 134],
[6, 34, 60, 86, 112, 138],
[6, 30, 58, 86, 114, 142],
[6, 34, 62, 90, 118, 146],
[6, 30, 54, 78, 102, 126, 150],
[6, 24, 50, 76, 102, 128, 154],
[6, 28, 54, 80, 106, 132, 158],
[6, 32, 58, 84, 110, 136, 162],
[6, 26, 54, 82, 110, 138, 166],
[6, 30, 58, 86, 114, 142, 170],
]
2024-07-06 10:36:07 +03:00
addEventListener('DOMContentLoaded', () => {
const canvas = document.getElementById("canvas")
const ctx = canvas.getContext("2d")
ctx.fillStyle = "black"
const options = {
mask: document.getElementById("mask-expr"),
size: document.getElementById("width"),
ver: document.getElementById("version"),
2024-07-06 10:42:26 +03:00
scale: document.getElementById("scale"),
2024-07-11 17:55:11 +03:00
gap: document.getElementById("gap"),
2024-07-06 10:36:07 +03:00
}
document.getElementById("gen-btn").addEventListener("click", () => {
const mask = options.mask.value
2024-07-11 17:55:11 +03:00
const size = Number(options.size.value)
const scale = Number(options.scale.value)
2024-07-11 19:17:48 +03:00
// not more than (half of 1 scaled module size) - 1
// at gap=scale/2 squares disappear
const gap = Math.min(Number(options.gap.value), intDiv(scale, 2) - 1)
2024-07-11 17:55:11 +03:00
const wh = scale - gap * 2 // width&height for 1 module with grid gap
2024-07-06 10:36:07 +03:00
canvas.width = canvas.height = size * scale
2024-07-06 10:40:44 +03:00
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black"
2024-07-06 10:36:07 +03:00
for (let row = 0; row < size; row++) {
for (let col = 0; col < size; col++) {
if (eval(mask) == 0) {
2024-07-11 17:55:11 +03:00
ctx.fillRect(col * scale + gap, row * scale + gap, wh, wh)
2024-07-06 10:36:07 +03:00
}
}
}
const idSize = 8 * scale
{ // finder pattern
ctx.fillStyle = "rgba(224, 63, 102, 0.5)"
ctx.fillRect(0, 0, idSize, idSize)
ctx.fillRect(canvas.width - idSize, 0, idSize, idSize)
ctx.fillRect(0, canvas.height - idSize, idSize, idSize)
}
const ver = getCodeVersion()
if (ver > 1) { // alignment pattern
ctx.fillStyle = "rgba(209, 63, 224, 0.5)"
const alignSize = 5 * scale
const coords = alignmentCoords[ver - 2] // array begins with version 2
const len = coords.length
for (let i = 0; i < len; i++) {
for (let j = 0; j < len; j++) {
const row = coords[i], col = coords[j]
if (row < 8 && col < 8 || row < 8 && col > size - 8 || row > size - 8 && col < 8) {
// do not overlap with finder pattern
continue
}
ctx.fillRect((row - 2) * scale, (col - 2) * scale, alignSize, alignSize)
}
}
}
{ // timing pattern
ctx.fillStyle = "rgba(76, 63, 224, 0.5)"
const idEndPos = 6 * scale
const betweenIds = canvas.width - idSize * 2
ctx.fillRect(idSize, idEndPos, betweenIds, scale)
ctx.fillRect(idEndPos, idSize, scale, betweenIds)
}
{ // format info
// ctx.fillStyle = "rgba(63, 149, 224, 0.5)"
ctx.fillStyle = "rgba(63, 203, 224, 0.5)"
ctx.fillRect(0, idSize, idSize, scale)
ctx.fillRect(idSize, 0, scale, idSize + scale)
ctx.fillRect(idSize, canvas.height - idSize, scale, idSize)
ctx.fillRect(canvas.width - idSize, idSize, idSize, scale)
}
if (ver >= 7) { // version info
ctx.fillStyle = "rgba(63, 224, 171, 0.5)"
const verWidth = 3 * scale
const verHeight = 6 * scale
const verBeginPos = canvas.width - idSize - verWidth
ctx.fillRect(verBeginPos, 0, verWidth, verHeight)
ctx.fillRect(0, verBeginPos, verHeight, verWidth)
}
2024-07-06 10:36:07 +03:00
})
{ // Choose best scale for screen height
2024-07-11 19:08:26 +03:00
let padding = getComputedStyle(document.body).paddingBottom
// remove `px` at the end and convert to number
padding = Number(padding.slice(0, padding.length - 2))
// compute available height
const height =
document.documentElement.clientHeight
- canvas.offsetTop // substract height of everything above canvas
- padding // substract body padding
const width =
document.documentElement.clientWidth
- padding * 2
options.scale.value = Math.max(
intDiv(
width < height ? width : height,
25, // divide least dimension by QR version 2 size
),
10, // minimum scale for this auto-detect algorithm
)
2024-07-06 10:36:07 +03:00
}
{ // QR version related stuff
const MICRO_4 = 17 // size for version M4, before version 1
const DIFF = 4 // difference between versions
const MIN_VER = 1
const MAX_VER = 40
getCodeVersion = () => {
2024-07-06 10:36:07 +03:00
const size = options.size.value
return Math.floor((size - MICRO_4) / DIFF)
}
const updateSizeVersion = (versionStep) => {
const ver = getCodeVersion() + versionStep
if (ver > MAX_VER || ver < MIN_VER) {
return
}
options.size.value = ver * DIFF + MICRO_4
options.ver.innerText = ver
}
document.getElementById("width-incr").addEventListener("click", () => {
updateSizeVersion(+1)
})
document.getElementById("width-decr").addEventListener("click", () => {
updateSizeVersion(-1)
})
options.size.addEventListener("input", () => {
options.ver.innerText = getCodeVersion()
})
}
})