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