1*35238bceSAndroid Build Coastguard Worker<!-- 2*35238bceSAndroid Build Coastguard Worker-------------------------------------- 3*35238bceSAndroid Build Coastguard WorkerHTML QPA Image Viewer 4*35238bceSAndroid Build Coastguard Worker-------------------------------------- 5*35238bceSAndroid Build Coastguard Worker 6*35238bceSAndroid Build Coastguard WorkerCopyright (c) 2020 The Khronos Group Inc. 7*35238bceSAndroid Build Coastguard WorkerCopyright (c) 2020 Valve Corporation. 8*35238bceSAndroid Build Coastguard Worker 9*35238bceSAndroid Build Coastguard WorkerLicensed under the Apache License, Version 2.0 (the "License"); 10*35238bceSAndroid Build Coastguard Workeryou may not use this file except in compliance with the License. 11*35238bceSAndroid Build Coastguard WorkerYou may obtain a copy of the License at 12*35238bceSAndroid Build Coastguard Worker 13*35238bceSAndroid Build Coastguard Workerhttp://www.apache.org/licenses/LICENSE-2.0 14*35238bceSAndroid Build Coastguard Worker 15*35238bceSAndroid Build Coastguard WorkerUnless required by applicable law or agreed to in writing, software 16*35238bceSAndroid Build Coastguard Workerdistributed under the License is distributed on an "AS IS" BASIS, 17*35238bceSAndroid Build Coastguard WorkerWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18*35238bceSAndroid Build Coastguard WorkerSee the License for the specific language governing permissions and 19*35238bceSAndroid Build Coastguard Workerlimitations under the License. 20*35238bceSAndroid Build Coastguard Worker--> 21*35238bceSAndroid Build Coastguard Worker<html> 22*35238bceSAndroid Build Coastguard Worker <head> 23*35238bceSAndroid Build Coastguard Worker <meta charset="utf-8"/> 24*35238bceSAndroid Build Coastguard Worker <title>Load PNGs from QPA output</title> 25*35238bceSAndroid Build Coastguard Worker <style> 26*35238bceSAndroid Build Coastguard Worker body { 27*35238bceSAndroid Build Coastguard Worker background: white; 28*35238bceSAndroid Build Coastguard Worker text-align: left; 29*35238bceSAndroid Build Coastguard Worker font-family: sans-serif; 30*35238bceSAndroid Build Coastguard Worker } 31*35238bceSAndroid Build Coastguard Worker h1 { 32*35238bceSAndroid Build Coastguard Worker margin-top: 2ex; 33*35238bceSAndroid Build Coastguard Worker } 34*35238bceSAndroid Build Coastguard Worker h2 { 35*35238bceSAndroid Build Coastguard Worker font-size: large; 36*35238bceSAndroid Build Coastguard Worker } 37*35238bceSAndroid Build Coastguard Worker figure { 38*35238bceSAndroid Build Coastguard Worker display: flex; 39*35238bceSAndroid Build Coastguard Worker flex-direction: column; 40*35238bceSAndroid Build Coastguard Worker } 41*35238bceSAndroid Build Coastguard Worker img { 42*35238bceSAndroid Build Coastguard Worker margin-right: 1ex; 43*35238bceSAndroid Build Coastguard Worker margin-bottom: 1ex; 44*35238bceSAndroid Build Coastguard Worker /* Attempt to zoom images using the nearest-neighbor scaling 45*35238bceSAndroid Build Coastguard Worker algorithm. */ 46*35238bceSAndroid Build Coastguard Worker image-rendering: pixelated; 47*35238bceSAndroid Build Coastguard Worker image-rendering: crisp-edges; 48*35238bceSAndroid Build Coastguard Worker /* Use a black background color for images in case some pixels 49*35238bceSAndroid Build Coastguard Worker are transparent to some degree. In the worst case, the image 50*35238bceSAndroid Build Coastguard Worker could appear to be missing. */ 51*35238bceSAndroid Build Coastguard Worker background: black; 52*35238bceSAndroid Build Coastguard Worker } 53*35238bceSAndroid Build Coastguard Worker button { 54*35238bceSAndroid Build Coastguard Worker margin: 1ex; 55*35238bceSAndroid Build Coastguard Worker border: none; 56*35238bceSAndroid Build Coastguard Worker border-radius: .5ex; 57*35238bceSAndroid Build Coastguard Worker padding: 1ex; 58*35238bceSAndroid Build Coastguard Worker background-color: steelblue; 59*35238bceSAndroid Build Coastguard Worker color: white; 60*35238bceSAndroid Build Coastguard Worker font-size: large; 61*35238bceSAndroid Build Coastguard Worker } 62*35238bceSAndroid Build Coastguard Worker button:hover { 63*35238bceSAndroid Build Coastguard Worker opacity: .8; 64*35238bceSAndroid Build Coastguard Worker } 65*35238bceSAndroid Build Coastguard Worker #clearimagesbutton,#cleartextbutton { 66*35238bceSAndroid Build Coastguard Worker background-color: seagreen; 67*35238bceSAndroid Build Coastguard Worker } 68*35238bceSAndroid Build Coastguard Worker select { 69*35238bceSAndroid Build Coastguard Worker font-size: large; 70*35238bceSAndroid Build Coastguard Worker padding: 1ex; 71*35238bceSAndroid Build Coastguard Worker border-radius: .5ex; 72*35238bceSAndroid Build Coastguard Worker border: 1px solid darkgrey; 73*35238bceSAndroid Build Coastguard Worker } 74*35238bceSAndroid Build Coastguard Worker select:hover { 75*35238bceSAndroid Build Coastguard Worker opacity: .8; 76*35238bceSAndroid Build Coastguard Worker } 77*35238bceSAndroid Build Coastguard Worker .loadoption { 78*35238bceSAndroid Build Coastguard Worker text-align: center; 79*35238bceSAndroid Build Coastguard Worker margin: 1ex; 80*35238bceSAndroid Build Coastguard Worker padding: 2ex; 81*35238bceSAndroid Build Coastguard Worker border: 1px solid darkgrey; 82*35238bceSAndroid Build Coastguard Worker border-radius: 1ex; 83*35238bceSAndroid Build Coastguard Worker } 84*35238bceSAndroid Build Coastguard Worker #options { 85*35238bceSAndroid Build Coastguard Worker display: flex; 86*35238bceSAndroid Build Coastguard Worker flex-wrap: wrap; 87*35238bceSAndroid Build Coastguard Worker } 88*35238bceSAndroid Build Coastguard Worker #qpatext { 89*35238bceSAndroid Build Coastguard Worker display: block; 90*35238bceSAndroid Build Coastguard Worker min-width: 80ex; 91*35238bceSAndroid Build Coastguard Worker max-width: 132ex; 92*35238bceSAndroid Build Coastguard Worker min-height: 25ex; 93*35238bceSAndroid Build Coastguard Worker max-height: 25ex; 94*35238bceSAndroid Build Coastguard Worker margin: 1ex auto; 95*35238bceSAndroid Build Coastguard Worker } 96*35238bceSAndroid Build Coastguard Worker #fileselector { 97*35238bceSAndroid Build Coastguard Worker display: none; 98*35238bceSAndroid Build Coastguard Worker } 99*35238bceSAndroid Build Coastguard Worker #zoomandclear { 100*35238bceSAndroid Build Coastguard Worker margin: 2ex; 101*35238bceSAndroid Build Coastguard Worker } 102*35238bceSAndroid Build Coastguard Worker #images { 103*35238bceSAndroid Build Coastguard Worker margin: 2ex; 104*35238bceSAndroid Build Coastguard Worker display: flex; 105*35238bceSAndroid Build Coastguard Worker flex-direction: column; 106*35238bceSAndroid Build Coastguard Worker } 107*35238bceSAndroid Build Coastguard Worker .imagesblock { 108*35238bceSAndroid Build Coastguard Worker display: flex; 109*35238bceSAndroid Build Coastguard Worker flex-wrap: wrap; 110*35238bceSAndroid Build Coastguard Worker } 111*35238bceSAndroid Build Coastguard Worker </style> 112*35238bceSAndroid Build Coastguard Worker </head> 113*35238bceSAndroid Build Coastguard Worker <body> 114*35238bceSAndroid Build Coastguard Worker <h1>Load PNGs from QPA output</h1> 115*35238bceSAndroid Build Coastguard Worker 116*35238bceSAndroid Build Coastguard Worker <div id="options"> 117*35238bceSAndroid Build Coastguard Worker <div class="loadoption"> 118*35238bceSAndroid Build Coastguard Worker <h2>Option 1: Load local QPA files</h2> 119*35238bceSAndroid Build Coastguard Worker <!-- The file selector text cannot be changed, so we use a hidden selector trick. --> 120*35238bceSAndroid Build Coastguard Worker <button id="fileselectorbutton">📂 Load files</button> 121*35238bceSAndroid Build Coastguard Worker <input id="fileselector" type="file" multiple> 122*35238bceSAndroid Build Coastguard Worker </div> 123*35238bceSAndroid Build Coastguard Worker 124*35238bceSAndroid Build Coastguard Worker <div class="loadoption"> 125*35238bceSAndroid Build Coastguard Worker <h2>Option 2: Paste QPA text or text extract containing <Image> elements below and click "Load images"</h2> 126*35238bceSAndroid Build Coastguard Worker <textarea id="qpatext"></textarea> 127*35238bceSAndroid Build Coastguard Worker <button id="loadimagesbutton">📃 Load images</button> 128*35238bceSAndroid Build Coastguard Worker <button id="cleartextbutton">♻ Clear text</button> 129*35238bceSAndroid Build Coastguard Worker </div> 130*35238bceSAndroid Build Coastguard Worker </div> 131*35238bceSAndroid Build Coastguard Worker 132*35238bceSAndroid Build Coastguard Worker <div id="zoomandclear"> 133*35238bceSAndroid Build Coastguard Worker 🔎 Image zoom 134*35238bceSAndroid Build Coastguard Worker <select id="zoomselect"> 135*35238bceSAndroid Build Coastguard Worker <option value="1" selected>1x</option> 136*35238bceSAndroid Build Coastguard Worker <option value="2">2x</option> 137*35238bceSAndroid Build Coastguard Worker <option value="4">4x</option> 138*35238bceSAndroid Build Coastguard Worker <option value="8">8x</option> 139*35238bceSAndroid Build Coastguard Worker <option value="16">16x</option> 140*35238bceSAndroid Build Coastguard Worker <option value="32">32x</option> 141*35238bceSAndroid Build Coastguard Worker </select> 142*35238bceSAndroid Build Coastguard Worker <button id="clearimagesbutton">♻ Clear images</button> 143*35238bceSAndroid Build Coastguard Worker </div> 144*35238bceSAndroid Build Coastguard Worker 145*35238bceSAndroid Build Coastguard Worker <div id="images"></div> 146*35238bceSAndroid Build Coastguard Worker 147*35238bceSAndroid Build Coastguard Worker <script> 148*35238bceSAndroid Build Coastguard Worker // Returns zoom factor as a number. 149*35238bceSAndroid Build Coastguard Worker var getSelectedZoom = function () { 150*35238bceSAndroid Build Coastguard Worker return new Number(document.getElementById("zoomselect").value); 151*35238bceSAndroid Build Coastguard Worker } 152*35238bceSAndroid Build Coastguard Worker 153*35238bceSAndroid Build Coastguard Worker // Scales a given image with the selected zoom factor. 154*35238bceSAndroid Build Coastguard Worker var scaleSingleImage = function (img) { 155*35238bceSAndroid Build Coastguard Worker var factor = getSelectedZoom(); 156*35238bceSAndroid Build Coastguard Worker img.style.width = (img.naturalWidth * factor) + "px"; 157*35238bceSAndroid Build Coastguard Worker img.style.height = (img.naturalHeight * factor) + "px"; 158*35238bceSAndroid Build Coastguard Worker } 159*35238bceSAndroid Build Coastguard Worker 160*35238bceSAndroid Build Coastguard Worker // Rescales all <img> elements in the page. Used after changing the selected zoom. 161*35238bceSAndroid Build Coastguard Worker var rescaleImages = function () { 162*35238bceSAndroid Build Coastguard Worker var imageList = document.getElementsByTagName("img"); 163*35238bceSAndroid Build Coastguard Worker for (var i = 0; i < imageList.length; i++) { 164*35238bceSAndroid Build Coastguard Worker scaleSingleImage(imageList[i]) 165*35238bceSAndroid Build Coastguard Worker } 166*35238bceSAndroid Build Coastguard Worker } 167*35238bceSAndroid Build Coastguard Worker 168*35238bceSAndroid Build Coastguard Worker // Removes everything contained in the images <div>. 169*35238bceSAndroid Build Coastguard Worker var clearImages = function () { 170*35238bceSAndroid Build Coastguard Worker var imagesNode = document.getElementById("images"); 171*35238bceSAndroid Build Coastguard Worker while (imagesNode.hasChildNodes()) { 172*35238bceSAndroid Build Coastguard Worker imagesNode.removeChild(imagesNode.lastChild); 173*35238bceSAndroid Build Coastguard Worker } 174*35238bceSAndroid Build Coastguard Worker } 175*35238bceSAndroid Build Coastguard Worker 176*35238bceSAndroid Build Coastguard Worker // Clears textarea text. 177*35238bceSAndroid Build Coastguard Worker var clearText = function() { 178*35238bceSAndroid Build Coastguard Worker document.getElementById("qpatext").value = ""; 179*35238bceSAndroid Build Coastguard Worker } 180*35238bceSAndroid Build Coastguard Worker 181*35238bceSAndroid Build Coastguard Worker // Returns a properly sized image with the given base64-encoded PNG data. 182*35238bceSAndroid Build Coastguard Worker var createImage = function (pngData, imageName) { 183*35238bceSAndroid Build Coastguard Worker var imageContainer = document.createElement("figure"); 184*35238bceSAndroid Build Coastguard Worker if (imageName.length > 0) { 185*35238bceSAndroid Build Coastguard Worker var newFileNameHeader = document.createElement("figcaption"); 186*35238bceSAndroid Build Coastguard Worker newFileNameHeader.textContent = escape(imageName); 187*35238bceSAndroid Build Coastguard Worker imageContainer.appendChild(newFileNameHeader); 188*35238bceSAndroid Build Coastguard Worker } 189*35238bceSAndroid Build Coastguard Worker var newImage = document.createElement("img"); 190*35238bceSAndroid Build Coastguard Worker newImage.src = "data:image/png;base64," + pngData; 191*35238bceSAndroid Build Coastguard Worker newImage.onload = (function () { 192*35238bceSAndroid Build Coastguard Worker // Grab the image for the callback. We need to wait until 193*35238bceSAndroid Build Coastguard Worker // the image has been properly loaded to access its 194*35238bceSAndroid Build Coastguard Worker // naturalWidth and naturalHeight properties, needed for 195*35238bceSAndroid Build Coastguard Worker // scaling. 196*35238bceSAndroid Build Coastguard Worker var cbImage = newImage; 197*35238bceSAndroid Build Coastguard Worker return function () { 198*35238bceSAndroid Build Coastguard Worker scaleSingleImage(cbImage); 199*35238bceSAndroid Build Coastguard Worker }; 200*35238bceSAndroid Build Coastguard Worker })(); 201*35238bceSAndroid Build Coastguard Worker imageContainer.appendChild(newImage); 202*35238bceSAndroid Build Coastguard Worker return imageContainer; 203*35238bceSAndroid Build Coastguard Worker } 204*35238bceSAndroid Build Coastguard Worker 205*35238bceSAndroid Build Coastguard Worker // Returns a new h3 header with the given file name. 206*35238bceSAndroid Build Coastguard Worker var createFileNameHeader = function (fileName) { 207*35238bceSAndroid Build Coastguard Worker var newHeader = document.createElement("h3"); 208*35238bceSAndroid Build Coastguard Worker newHeader.textContent = fileName; 209*35238bceSAndroid Build Coastguard Worker return newHeader; 210*35238bceSAndroid Build Coastguard Worker } 211*35238bceSAndroid Build Coastguard Worker 212*35238bceSAndroid Build Coastguard Worker // Returns a new image block to contain images from a file. 213*35238bceSAndroid Build Coastguard Worker var createImagesBlock = function () { 214*35238bceSAndroid Build Coastguard Worker var imagesBlock = document.createElement("div"); 215*35238bceSAndroid Build Coastguard Worker imagesBlock.className = "imagesblock"; 216*35238bceSAndroid Build Coastguard Worker return imagesBlock; 217*35238bceSAndroid Build Coastguard Worker } 218*35238bceSAndroid Build Coastguard Worker 219*35238bceSAndroid Build Coastguard Worker // Processes a chunk of QPA text from the given file name. Creates 220*35238bceSAndroid Build Coastguard Worker // the file name header and a list of images in the images <div>, as 221*35238bceSAndroid Build Coastguard Worker // found in the text. 222*35238bceSAndroid Build Coastguard Worker var processText = function(textString, fileName) { 223*35238bceSAndroid Build Coastguard Worker var imagesNode = document.getElementById("images"); 224*35238bceSAndroid Build Coastguard Worker var newHeader = createFileNameHeader(fileName); 225*35238bceSAndroid Build Coastguard Worker imagesNode.appendChild(newHeader); 226*35238bceSAndroid Build Coastguard Worker var imagesBlock = createImagesBlock(); 227*35238bceSAndroid Build Coastguard Worker // [\s\S] is a match-anything regexp like the dot, except it 228*35238bceSAndroid Build Coastguard Worker // also matches newlines. Ideally, browsers would need to widely 229*35238bceSAndroid Build Coastguard Worker // support the "dotall" regexp modifier, but that's not the case 230*35238bceSAndroid Build Coastguard Worker // yet and this does the trick. 231*35238bceSAndroid Build Coastguard Worker // Group 1 are the image element properties, if any. 232*35238bceSAndroid Build Coastguard Worker // Group 2 is the base64 PNG data. 233*35238bceSAndroid Build Coastguard Worker var imageRegexp = /<Image\b(.*?)>([\s\S]*?)<\/Image>/g; 234*35238bceSAndroid Build Coastguard Worker var imageNameRegexp = /\bName="(.*?)"/; 235*35238bceSAndroid Build Coastguard Worker var result; 236*35238bceSAndroid Build Coastguard Worker var innerResult; 237*35238bceSAndroid Build Coastguard Worker var imageName; 238*35238bceSAndroid Build Coastguard Worker while ((result = imageRegexp.exec(textString)) !== null) { 239*35238bceSAndroid Build Coastguard Worker innerResult = result[1].match(imageNameRegexp); 240*35238bceSAndroid Build Coastguard Worker imageName = ((innerResult !== null) ? innerResult[1] : ""); 241*35238bceSAndroid Build Coastguard Worker // Blanks need to be removed from the base64 string. 242*35238bceSAndroid Build Coastguard Worker var pngData = result[2].replace(/\s+/g, ""); 243*35238bceSAndroid Build Coastguard Worker imagesBlock.appendChild(createImage(pngData, imageName)); 244*35238bceSAndroid Build Coastguard Worker } 245*35238bceSAndroid Build Coastguard Worker imagesNode.appendChild(imagesBlock); 246*35238bceSAndroid Build Coastguard Worker } 247*35238bceSAndroid Build Coastguard Worker 248*35238bceSAndroid Build Coastguard Worker // Loads images from the text in the text area. 249*35238bceSAndroid Build Coastguard Worker var loadImages = function () { 250*35238bceSAndroid Build Coastguard Worker processText(document.getElementById("qpatext").value, "<Pasted Text>"); 251*35238bceSAndroid Build Coastguard Worker } 252*35238bceSAndroid Build Coastguard Worker 253*35238bceSAndroid Build Coastguard Worker // Loads images from the files in the file selector. 254*35238bceSAndroid Build Coastguard Worker var handleFileSelect = function (evt) { 255*35238bceSAndroid Build Coastguard Worker var files = evt.target.files; 256*35238bceSAndroid Build Coastguard Worker for (var i = 0; i < files.length; i++) { 257*35238bceSAndroid Build Coastguard Worker // Creates a reader per file. 258*35238bceSAndroid Build Coastguard Worker var reader = new FileReader(); 259*35238bceSAndroid Build Coastguard Worker // Grab the needed objects to use them after the file has 260*35238bceSAndroid Build Coastguard Worker // been read, in order to process its contents and add 261*35238bceSAndroid Build Coastguard Worker // images, if found, in the images <div>. 262*35238bceSAndroid Build Coastguard Worker reader.onload = (function () { 263*35238bceSAndroid Build Coastguard Worker var cbFileName = files[i].name; 264*35238bceSAndroid Build Coastguard Worker var cbReader = reader; 265*35238bceSAndroid Build Coastguard Worker return function () { 266*35238bceSAndroid Build Coastguard Worker processText(cbReader.result, cbFileName); 267*35238bceSAndroid Build Coastguard Worker }; 268*35238bceSAndroid Build Coastguard Worker })(); 269*35238bceSAndroid Build Coastguard Worker // Reads file contents. This will trigger the event above. 270*35238bceSAndroid Build Coastguard Worker reader.readAsText(files[i]); 271*35238bceSAndroid Build Coastguard Worker } 272*35238bceSAndroid Build Coastguard Worker } 273*35238bceSAndroid Build Coastguard Worker 274*35238bceSAndroid Build Coastguard Worker // File selector trick: click on the selector when clicking on the 275*35238bceSAndroid Build Coastguard Worker // custom button. 276*35238bceSAndroid Build Coastguard Worker var clickFileSelector = function () { 277*35238bceSAndroid Build Coastguard Worker document.getElementById("fileselector").click(); 278*35238bceSAndroid Build Coastguard Worker } 279*35238bceSAndroid Build Coastguard Worker 280*35238bceSAndroid Build Coastguard Worker // Clears selected files to be able to select them again if needed. 281*35238bceSAndroid Build Coastguard Worker var clearSelectedFiles = function() { 282*35238bceSAndroid Build Coastguard Worker document.getElementById("fileselector").value = ""; 283*35238bceSAndroid Build Coastguard Worker } 284*35238bceSAndroid Build Coastguard Worker 285*35238bceSAndroid Build Coastguard Worker // Set event handlers for interactive elements in the page. 286*35238bceSAndroid Build Coastguard Worker document.getElementById("fileselector").onclick = clearSelectedFiles; 287*35238bceSAndroid Build Coastguard Worker document.getElementById("fileselector").addEventListener("change", handleFileSelect, false); 288*35238bceSAndroid Build Coastguard Worker document.getElementById("fileselectorbutton").onclick = clickFileSelector; 289*35238bceSAndroid Build Coastguard Worker document.getElementById("loadimagesbutton").onclick = loadImages; 290*35238bceSAndroid Build Coastguard Worker document.getElementById("cleartextbutton").onclick = clearText; 291*35238bceSAndroid Build Coastguard Worker document.getElementById("zoomselect").onchange = rescaleImages; 292*35238bceSAndroid Build Coastguard Worker document.getElementById("clearimagesbutton").onclick = clearImages; 293*35238bceSAndroid Build Coastguard Worker </script> 294*35238bceSAndroid Build Coastguard Worker </body> 295*35238bceSAndroid Build Coastguard Worker</html> 296