Skip to content

Instantly share code, notes, and snippets.

@yoshimov
Last active August 14, 2022 17:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yoshimov/c664e58fcfd0d5df72c0660e9a33f43c to your computer and use it in GitHub Desktop.
Save yoshimov/c664e58fcfd0d5df72c0660e9a33f43c to your computer and use it in GitHub Desktop.
<html>
<head>
<meta charset="utf-8">
<title>QR Shooter</title>
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Ropa+Sans" rel="stylesheet">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
<style>
body {
font-family: 'Ropa Sans', sans-serif;
color: #333;
max-width: 640px;
margin: 0 auto;
position: relative;
}
#githubLink {
position: absolute;
right: 0;
top: 12px;
color: #2D99FF;
}
h1 {
margin: 10px 0;
font-size: 40px;
}
#loadingMessage {
text-align: center;
padding: 40px;
background-color: #eee;
}
#canvas {
width: 100%;
}
#output {
margin-top: 20px;
background: #eee;
padding: 10px;
padding-bottom: 0;
}
#output div {
padding-bottom: 10px;
word-wrap: break-word;
}
#noQRFound {
text-align: center;
}
</style>
</head>
<body>
<h1>QR Shooter</h1>
<div id="loadingMessage">🎥 Unable to access video stream (please make sure you have a webcam enabled)</div>
<canvas id="canvas" hidden></canvas>
<canvas id="canvas2" hidden></canvas>
<div id="output" hidden>
<div id="outputMessage">No QR code detected.</div>
<div hidden><b>Data:</b> <span id="outputData"></span></div>
</div>
<input type="button" id="clear_button" value="Clear" />
<script>
var video = document.createElement("video");
var canvasElement = document.getElementById("canvas");
var canvas = canvasElement.getContext("2d");
var canvasElement2 = document.getElementById("canvas2");
var canvas2 = canvasElement.getContext("2d");
var loadingMessage = document.getElementById("loadingMessage");
var outputContainer = document.getElementById("output");
var outputMessage = document.getElementById("outputMessage");
var outputData = document.getElementById("outputData");
var color_red = "#FF3B58";
var color_green = "#20FF30";
var center_x, center_y;
var focus = false;
var fire = false;
var score = 0;
var shootMap = {};
function drawLine(begin, end, color) {
canvas.beginPath();
canvas.moveTo(begin.x, begin.y);
canvas.lineTo(end.x, end.y);
canvas.lineWidth = 4;
canvas.strokeStyle = color;
canvas.stroke();
}
function drawPrompt() {
drawLine({x:center_x, y:center_y - 10}, {x:center_x, y:center_y - 50}, color_green);
drawLine({x:center_x, y:center_y + 10}, {x:center_x, y:center_y + 50}, color_green);
drawLine({x:center_x - 10, y:center_y}, {x:center_x - 50, y:center_y}, color_green);
drawLine({x:center_x + 10, y:center_y}, {x:center_x + 50, y:center_y}, color_green);
}
function drawFire() {
drawLine({x:center_x + 100, y:center_y + 120}, {x:center_x + 5, y:center_y + 5}, color_red);
drawLine({x:center_x - 100, y:center_y + 120}, {x:center_x - 5, y:center_y + 5}, color_red);
}
function drawScore() {
canvas.font = "48px 'Ropa Sans', serif";
canvas.fillStyle = color_green;
canvas.fillText(score, 10, 60);
}
// Use facingMode: environment to attemt to get the front camera on phones
//
navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment", width:{ideal:1280, max:1280}, height:{ideal:1920, max:1920} } }).then(function(stream) {
video.srcObject = stream;
video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
video.play();
requestAnimationFrame(tick);
});
function tick() {
loadingMessage.innerText = "⌛ Loading video..."
if (video.readyState === video.HAVE_ENOUGH_DATA) {
loadingMessage.hidden = true;
canvasElement.hidden = false;
outputContainer.hidden = false;
canvasElement.height = window.innerWidth;//video.videoHeight;
canvasElement.width = window.innerWidth;//video.videoWidth;
canvasElement2.height = video.videoHeight;
canvasElement2.width = video.videoWidth;
var orgSize;
var shiftX, shiftY, ratioView;
if (canvasElement2.height > canvasElement2.width) {
orgSize = canvasElement2.width;
shiftX = 0;
shiftY = Math.abs(canvasElement2.height - canvasElement2.width) / 2;
ratioView = canvasElement.width / canvasElement2.width;
} else {
orgSize = canvasElement2.height;
shiftY = 0;
shiftX = Math.abs(canvasElement2.height - canvasElement2.width) / 2;
ratioView = canvasElement.height / canvasElement2.height;
}
//outputMessage.innerText = 'height: ' + canvasElement2.height + ' width:' + canvasElement2.width; // for debug
canvas2.scale(1, 1);
canvas2.drawImage(video, 0, 0, video.videoHeight, video.videoWidth);
canvas.save();
canvas.scale(ratioView, ratioView);
canvas.drawImage(video, shiftX, shiftY, orgSize, orgSize, 0, 0, orgSize, orgSize);
canvas.restore();
var imageData = canvas2.getImageData(0, 0, canvasElement2.width, canvasElement2.height);//canvas2.getImageData(shiftX, shiftY, orgSize, orgSize);
center_x = canvasElement.width /2;
center_y = canvasElement.height /2;
// prompt
drawPrompt();
var code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: "dontInvert",
});
if (code) {
var c = color_red;
var dx = Math.abs((code.location.topLeftCorner.x + code.location.bottomRightCorner.x) /2 - center_x);
var dy = Math.abs((code.location.topLeftCorner.y + code.location.bottomRightCorner.y) /2 - center_y);
//outputMessage.innerText = 'dx: ' + dx + ' dy:' + dy; // for debug
if ( dx < 50 && dy < 50) {
c = color_green;
focus = true;
} else {
focus = false;
}
//canvas.save();
//canvas.scale(ratioView, ratioView);
drawLine(code.location.topLeftCorner, code.location.topRightCorner, c);
drawLine(code.location.topRightCorner, code.location.bottomRightCorner, c);
drawLine(code.location.bottomRightCorner, code.location.bottomLeftCorner, c);
drawLine(code.location.bottomLeftCorner, code.location.topLeftCorner, c);
//canvas.restore();
outputMessage.hidden = true;
outputData.parentElement.hidden = false;
if (fire && focus) {
// increment score
var list = code.data.split(':');
if (!(list[0] in shootMap)) {
shootMap[list[0]] = true;
score += parseInt(list[1], 10);
outputData.innerText = 'increment score ' + list[1];
} else {
outputData.innerText = 'miss';
}
} else {
outputData.innerText = code.data;
}
} else { // code
outputMessage.hidden = false;
outputData.parentElement.hidden = true;
}
if (fire) {
drawFire();
fire = false;
}
drawScore();
}
requestAnimationFrame(tick);
}
canvasElement.addEventListener('click', function() {
fire = true;
});
var clear_button = document.getElementById("clear_button");
clear_button.onclick = function() {
score = 0;
shootMap = {};
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment