/*
****************************************
GLOBAL VARIABLES
****************************************
*/
var APP_VERSION = '0.4.0';
var qr_version = 1; //Current QR version (1-9)
var qr_pixel_size = 10; //Current view size of QR code (pixel per module)
var qr_pixel_size_togglesave = 10; //Last toggle view size of QR code (pixel per module)
var qr_size = 17+(qr_version*4); //Current size of QR code
var qr_array = []; //Main array to store QR data
var qr_format_array = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; //Store QR format information
var active_painter = "0"; //Current active painter tool (0,1,2)
var fill_painter = false; //Is flood fill tool active?
var changed_state = false; //Is document in changed state and not saved yet?
var show_grey = true; //Show grey modules in Decode mode
var extract_info_mode = false; //Is Extract QR Information active?
var brute_force_mode = false; //Is Brute-force Format Info active?
var analysis_mode = false; //Is Data Analysis tool active?
var masking_mode = false; //Is Masking active?
var qr_temp_array = []; //Temporary variable to handle qr_array duplicates
var qr_data_block = []; //Array to store data block in "Data Analysis tool"
var is_data_module = []; //Store data that separate between data module and fixed module (function pattern, alignment pattern, etc)
var history_array = []; //Store history information and its qr_array data
var active_history = -1; //Current active history
const maxSupportedSize = 177; // max is 177 for v40
const maxVersion = 40; // max is not 50
/***
*
* generate QR table based on qr_array
*
***/
function generateTable(version){
qr_array = JSON.parse(JSON.stringify(generate_qr(version)));
if (version > 9 && version <= 20){
qr_pixel_size = 5;
} else if (version > 20 && version <=35){
qr_pixel_size = 2;
} else if (version > 35 ){
qr_pixel_size = 1;
}
changed_state = false;
var element = "";
var size = 17+(version*4);
for(var i=0; i < qr_array.length; i++){
element += "
";
} else if(pattern_array[i][j] == 1){
element += "
";
} else {
element += "
";
}
}
element += "
";
$("#qr-format-info").append(element);
}
for(var i=start_index; i < end_index; i++){
if(qr_format_array[i] == 1){
$("td#qr-info-"+i).addClass("black");
} else {
$("td#qr-info-"+i).addClass("white");
}
}
}
/***
*
* Save format information value to qr_format_array
*
***/
function saveInfoTable(size){
for(var i=0; i < 8; i++){
if(i > 5)
qr_array[i+1][8] = parseInt(qr_format_array[i]);
else
qr_array[i][8] = parseInt(qr_format_array[i]);
qr_array[8][size-(i+1)] = parseInt(qr_format_array[i]);
}
var index = 0;
for(var i=14; i >= 8; i--){
if(index > 5)
qr_array[8][index+1] = parseInt(qr_format_array[i]);
else
qr_array[8][index] = parseInt(qr_format_array[i]);
qr_array[size-(index+1)][8] = parseInt(qr_format_array[i]);
index++;
}
refreshTable();
$("#format-information-box").hide();
changed_state = true;
}
/***
*
* Reload QR table based on qr_array value
*
***/
function refreshTable(){
for(var i=0; i < qr_array.length; i++){
for(var j=0; j < qr_array[i].length; j++){
if(!$("#qr-"+i+"-"+j).hasClass("static")){
$("#qr-"+i+"-"+j).removeClass("black");
$("#qr-"+i+"-"+j).removeClass("white");
if(qr_array[i][j] == 1){
$("#qr-"+i+"-"+j).addClass("black");
} else if(qr_array[i][j] == 0) {
$("#qr-"+i+"-"+j).addClass("white");
}
}
}
}
}
/***
*
* Update qr_array from new array (exclude fixed pattern: function pattern, alignment pattern, timing, and version information)
*
***/
function updateQRArray(new_data){
for(var i=0; i < qr_array.length; i++){
for(var j=0; j < qr_array[i].length; j++){
if(is_data_module[i][j]){
qr_array[i][j] = new_data[i][j];
}
}
}
//update format information when using image upload
var size = qr_size;
for(var i=0; i < 8; i++){
if(i > 5)
qr_array[i+1][8] = new_data[i+1][8];
else
qr_array[i][8] = new_data[i][8];
qr_array[8][size-(i+1)] = new_data[8][size-(i+1)];
}
var index = 0;
for(var i=14; i >= 8; i--){
if(index > 5)
qr_array[8][index+1] = new_data[8][index+1];
else
qr_array[8][index] = new_data[8][index];;
qr_array[size-(index+1)][8] = new_data[size-(index+1)][8];;
index++;
}
}
/***
*
* Get format information from format information overlay dialog
*
***/
function getInfoBits(){
var result = {ecc:"",mask:-1};
$("#slider-mask div.active").removeClass("active");
$("#slider-ecc div.active").removeClass("active");
var bits = "";
bits = qr_format_array.join("");
bits = bits.split("").reverse().join("");
var raw_bits = [bits.substring(0,2), bits.substring(2,5)];
var bch_bits = bits.substring(5);
if(format_information_bits_raw.ecc.indexOf(raw_bits[0]) > -1){
if(format_information_bits_raw.ecc.indexOf(raw_bits[0]) == 0) result.ecc = "L";
else if(format_information_bits_raw.ecc.indexOf(raw_bits[0]) == 1) result.ecc = "M";
else if(format_information_bits_raw.ecc.indexOf(raw_bits[0]) == 2) result.ecc = "Q";
else result.ecc = "H";
}
if(format_information_bits_raw.mask.indexOf(raw_bits[1]) > -1){
result.mask = format_information_bits_raw.mask.indexOf(raw_bits[1]);
}
if(format_information_bits[0].indexOf(bits) > -1){
result.ecc = "L";
result.mask = format_information_bits[0].indexOf(bits);
} else if(format_information_bits[1].indexOf(bits) > -1){
result.ecc = "M";
result.mask = format_information_bits[1].indexOf(bits);
} else if(format_information_bits[2].indexOf(bits) > -1){
result.ecc = "Q";
result.mask = format_information_bits[2].indexOf(bits);
} else if(format_information_bits[3].indexOf(bits) > -1){
result.ecc = "H";
result.mask = format_information_bits[3].indexOf(bits);
}
console.log(bits);
return result;
}
/***
*
* Sanitize and verify qr is square before parsing it
*
***/
function sanitizeQrTxt(lines) {
const linesOut = [];
for(let i=0;i ( x===1 ? '#' : ( x===0 ? '_' : ( show_grey ? '?' : '_' ) ) ) ).join('');
console.log(line);
dump_qr += line + "\n"
}
return dump_qr;
}
/***
*
* generate QR code made from canvas based on qr_array values
*
***/
function generateResult(){
var c = document.getElementById("qr-result-canvas");
var size = 17+(qr_version*4);
var ctx = c.getContext("2d");
c.width = qr_pixel_size*size;
c.height = qr_pixel_size*size;
// add quiet zone border and white fill
c.width += (qr_pixel_size*4) * 2;
c.height += (qr_pixel_size*4) * 2;
ctx.fillStyle = "#fff";
ctx.fillRect(0,0,c.width,c.height);
ctx.fillStyle = "#000";
for(var i=0; i < qr_array.length; i++){
for(var j=0; j < qr_array[i].length; j++){
var x = qr_pixel_size*j;
var y = qr_pixel_size*i;
//shift due to quiet zone
x += qr_pixel_size*4;
y += qr_pixel_size*4;
if(qr_array[i][j] == 1){
ctx.fillStyle = "#000";
ctx.fillRect(x,y,qr_pixel_size,qr_pixel_size);
} else if(qr_array[i][j] == 0) {
ctx.fillStyle = "#fff";
ctx.fillRect(x,y,qr_pixel_size,qr_pixel_size);
} else {
if(show_grey){
ctx.fillStyle = "#bdbdbd";
ctx.fillRect(x,y,qr_pixel_size,qr_pixel_size);
}
else{
ctx.fillStyle = "#fff";
ctx.fillRect(x,y,qr_pixel_size,qr_pixel_size);
}
}
}
}
$("#qr-result-dump").attr('rows', qr_version*4 +17 );
$("#qr-result-dump").attr('cols', qr_version*4 +17 );
$("#qr-result-dump").css( 'font-family', 'Courier New');
$("#qr-result-dump").css("font-size", "7px");
$("#qr-result-dump").val( dumpQRArray() );
$("#qr-result").show();
$("#qr-table").hide();
$("#qr-overlay").hide();
$("body").css("background-color","#FFFFFF");
}
/***
*
* Update toolbox values
*
***/
function updateToolbox(){
$("#qr-version").val(qr_size+"x"+qr_size+" (ver. "+qr_version+")");
$("#qr-size").val(qr_pixel_size+"px");
}
/***
*
* Resize QR size
*
***/
function resize(size){
$("td").each(function(){
$(this).css({"min-width":size+"px","min-height":size+"px","width":size+"px","height":size+"px"});
})
}
/***
*
* Toggle between Editor and Decode mode
*
***/
function toggleResult(){
if(!$("#btn-switch-mode").hasClass("active")){
$(".mode-indicator button").removeClass("active");
$("#mobile-decode-mode").addClass("active");
//resize for decode ( minimum 2px module width needed for standard device decoding )
qr_pixel_size_togglesave = qr_pixel_size;
if (qr_pixel_size == 1 ) {
$("#btn-size-plus").trigger("click");
}
generateResult();
$("#btn-switch-mode").addClass("active");
$("#div-tool-work, #box-history").hide();
$("#div-tool-result").show();
$("#btn-switch-mode").text("Decode Mode");
$("#box-tools-masking").hide();
if(brute_force_mode)
$("#h6-brute-force-msg").show();
else
$("#h6-brute-force-msg").hide();
if(analysis_mode){
$("#box-work").show();
$("#box-tools-analysis").hide();
}
} else {
$(".mode-indicator button").removeClass("active");
$("#mobile-editor-mode").addClass("active");
//restore to previous encode mode pixel size
if (qr_pixel_size - qr_pixel_size_togglesave >= 0){
for (i = qr_pixel_size - qr_pixel_size_togglesave ; i > 0 ; i-- ){
$("#btn-size-min").trigger("click");
}
}
else {
for (i = qr_pixel_size_togglesave - qr_pixel_size ; i > 0 ; i-- ){
$("#btn-size-plus").trigger("click");
}
}
$("#qr-result").hide();
$(".qr-tab").show();
$("#btn-switch-mode").removeClass("active");
$("body").css("background-color","#eceff1");
$("#div-tool-result").hide();
$("#div-tool-work, #box-history").show();
$("#btn-switch-mode").text("Editor Mode");
if(analysis_mode){
$("#box-work").hide();
$("#box-tools-analysis").show();
}
}
}
/***
*
* Load image from file and put to {target}
*
***/
function loadImage(input, target){
if(input.files && input.files[0]){
var reader = new FileReader();
reader.onload = function(e){
$(target).attr("src",e.target.result);
}
reader.readAsDataURL(input.files[0]);
}
}
/***
*
* Save project to LocalStorage
*
***/
function saveProject(projectName){
if(projectName == ""){
alert("Please, enter name of your Project!");
return;
}
var saveData = [qr_array, qr_version, qr_format_array];
var dataList = JSON.parse(localStorage.getItem("dataList"));
var timeNow = new Date();
var timeData = timeNow.toDateString();
var projectNameList = [];
if(dataList == undefined){
dataList = [];
}
for(var i=0; i < dataList.length; i++){
projectNameList[i] = dataList[i][0];
}
if(!projectNameList.includes(projectName)) {
dataList.push([projectName, timeData]);
} else {
var index = projectNameList.indexOf(projectName);
dataList[index][1] = timeData;
}
localStorage.setItem("saveData_"+projectName,JSON.stringify(saveData));
localStorage.setItem("dataList",JSON.stringify(dataList));
$("#div-save").hide();
changed_state = false;
}
/***
*
* Load project from LocalStorage
*
***/
function loadProject(name){
if(changed_state){
if(!confirm("Are you sure want to proceed?\nYour unsaved progress will be lost!"))
return;
}
var loadedData = JSON.parse(localStorage.getItem("saveData_"+name));
qr_version = loadedData[1];
qr_size = 17+(qr_version*4);
generateTable(qr_version);
qr_array = loadedData[0];
qr_format_array = loadedData[2];
brute_force_mode = false;
$("#tools-brute-force, #tools-unmasking").removeClass("active");
refreshTable();
$("#qr-version").val(qr_size+"x"+qr_size+" (ver. "+qr_version+")");
$("#div-load").hide();
if($("#btn-switch-mode").hasClass("active")){
toggleResult();
}
if($("#div-extract").css("display") != "none"){
$("#btn-tools-extract").trigger("click");
}
if(analysis_mode){
$("#tools-data-analysis").trigger("click");
}
$("#box-tools-masking").hide();
$("#qr-overlay").html("");
clearHistory();
updateHistory("Load project");
}
/***
*
* Remove project from LocalStorage
*
***/
function removeProject(name, origin){
if(confirm("Are you sure want to permanently delete this project?")){
var dataList = JSON.parse(localStorage.getItem("dataList"));
var projectNameList = [];
for(var i=0; i < dataList.length; i++){
projectNameList[i] = dataList[i][0];
}
var index = projectNameList.indexOf(name);
if(index >= 0){
dataList.remove(index);
localStorage.removeItem("saveData_"+name);
localStorage.setItem("dataList",JSON.stringify(dataList));
refreshLoadList(origin);
}
}
}
/***
*
* Refresh list of project in LocalStorage
*
***/
function refreshLoadList(origin){
var dataList = JSON.parse(localStorage.getItem("dataList"));
if(dataList == undefined){
$("#list-"+origin).html("
There's no saved project in Local Storage.
");
} else {
var element = "";
for(var i=0; i < dataList.length; i++){
element += "
"+dataList[i][0]+"
"+dataList[i][1]+"
✖
";
}
$("#list-"+origin).html(element);
}
}
/***
*
* Decode Base64 to image
*
***/
function decodeFromBase64(img, callback){
qrcode.callback = callback;
qrcode.decode(img, callback);
}
function importFromImage(src, cb){
var img = new Image();
img.crossOrigin = "Anonymous";
console.log(src);
img.onload = function(){
var canvas_qr = document.createElement("canvas");
var context = canvas_qr.getContext('2d');
var nheight = img.height;
var nwidth = img.width;
if(img.width*img.height>qrcode.maxImgSize){
var ir = img.width / img.height;
nheight = Math.sqrt(qrcode.maxImgSize/ir);
nwidth=ir*nheight;
}
canvas_qr.width = nwidth;
canvas_qr.height = nheight;
context.drawImage(img, 0, 0, canvas_qr.width, canvas_qr.height );
qrcode.width = canvas_qr.width;
qrcode.height = canvas_qr.height;
try{
qrcode.imagedata = context.getImageData(0, 0, canvas_qr.width, canvas_qr.height);
}catch(e){
cb(e);
return;
}
var image = qrcode.grayScaleToBitmap(qrcode.grayscale());
var detector = new Detector(image);
try {
var qRCodeMatrix = detector.detect();
} catch(error){
console.log(error);
cb(error);
return;
}
var qrArray = qRCodeMatrix.bits.bits;
var size = qRCodeMatrix.bits.width;
if(size > maxSupportedSize){
alert("QR version is unsupported");
return;
}
qr_size = size;
qr_version = (size-17)/4;
updateToolbox();
var result = [];
for(var x=0; x < size; x++){
result[x] = [];
for(var y=0; y < size; y++){
result[x][y] = qRCodeMatrix.bits.get_Renamed(y,x) ? 1 : 0;
}
}
cb(result);
}
img.src = src;
}
/***
*
* Flood fill algorithm to perform paint-like bucket tool
*
* Reference : https://jsfiddle.net/eWxNE/2/
*
***/
function floodFill(x, y, oldVal, newVal){
x = parseInt(x);
y = parseInt(y);
if(oldVal == null){
oldVal = parseInt(qr_array[x][y]);
}
if(qr_array[x][y] !== oldVal){
return;
}
if(is_data_module[x][y])
qr_array[x][y] = parseInt(newVal);
else
return;
if (x > 0){
floodFill(x-1, y, oldVal, newVal);
}
if(y > 0){
floodFill(x, y-1, oldVal, newVal);
}
if(x < qr_size-1){
floodFill(x+1, y, oldVal, newVal);
}
if(y < qr_size-1){
floodFill(x, y+1, oldVal, newVal);
}
}
/***
*
* Update history
*
***/
function updateHistory(msg){
if(active_history < 10) active_history++;
history_array = history_array.slice(0, active_history);
if(history_array.length == 10){
history_array.shift();
}
history_array.push([msg, JSON.stringify(qr_array)]);
var html = "";
for(var i=history_array.length-1; i >= 0; i--){
if(i == history_array.length-1){
html += "
"+history_array[i][0]+"
";
} else {
html += "
"+history_array[i][0]+"
";
}
}
$(".history").html(html);
}
/***
*
* Get history value and refresh QR table
*
***/
function getHistory(index){
qr_array = JSON.parse(history_array[index][1]);
if(qr_array.length != qr_size){
qr_size = qr_array.length;
qr_version = (qr_size-17)/4;
updateToolbox();
}
refreshTable();
}
/***
*
* Clear all history value
*
***/
function clearHistory(){
history_array = [];
active_history = -1;
$(".history").html("");
}
/***
*
* Display information text result in Extract QR Information tool and
* load it to #div-extract
*
***/
function extractInfo(){
var data_array = JSON.stringify(qr_array);
var result = QRDecode(JSON.parse(data_array));
var size = 17+(qr_version*4);
console.log(result);
var html = "
QR version : "+qr_version+" ("+size+"x"+size+")
\
Error correction level : "+result.ecc+"
\
Mask pattern : "+result.mask_pattern+"
\
\
Number of missing bytes (erasures) : "+result.erasure_count+" bytes ("+(result.erasure_count/result.data_module_count * 100).toFixed(2)+"%)
\
\
Data blocks :
\
"+result.data_blocks+"
\
";
if($("#btn-extract-show-rs").hasClass("active")){
for(var i=0; i < result.rs_block.length; i++){
html += "